Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
74e9e7e
feat(workbench-shell): ✨ add structured chart artifact rendering to t…
jorben May 6, 2026
861724b
feat(agent-session): ✨ add render_chart tool with spec validation and…
jorben May 6, 2026
41b0f09
Merge branch 'master' into feat/thread-chart-artifact-rendering
jorben May 6, 2026
a8077b2
feat(chart): ✨ add chart artifact validation and rendering support
jorben May 7, 2026
c2a2355
style(tests): 🎨 format assert line for readability
jorben May 7, 2026
88774aa
test: ✅ add tests for render_chart tool call and message parts migration
jorben May 7, 2026
dcd787d
feat(chart): ✨ Adapt Vega-Lite theme to application theme
jorben May 7, 2026
a7186d7
feat(runtime-thread-surface): ✨ add render_chart to default collapsed…
jorben May 7, 2026
dbc284d
feat(agent): ✨ rename `render_chart` tool to `render` and support HTM…
jorben May 7, 2026
5a9f8c6
feat(render): ✨ add file path support for render source and fix strea…
jorben May 7, 2026
3e9ab8b
feat(repo): ✨ add source field to chart artifact merge
jorben May 7, 2026
74bd469
refactor(workbench-shell): ♻️ generalize default-collapsed behavior f…
jorben May 7, 2026
e631cd4
refactor(workbench-shell): ♻️ reorganize thread-chart-artifact-card U…
jorben May 7, 2026
2567766
feat(ui): ✨ replace dialog with custom preview overlay and use CodeBlock
jorben May 7, 2026
bbcbe18
fix(agent): 🐛 fix race condition in artifact handling and streaming m…
jorben May 7, 2026
e44fb7f
refactor(workbench-shell): ♻️ extract reusable file preview overlay a…
jorben May 7, 2026
db8828a
refactor(artifact): ♻️ extract artifact merging logic and add typed s…
jorben May 7, 2026
b3339cc
fix(agent): 🐛 skip reasoning messages when fetching active message ID
jorben May 8, 2026
f65e44f
fix(ui): 🐛 sanitize HTML preview content and fix artifact cleanup
jorben May 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
705 changes: 699 additions & 6 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"dompurify": "^3.4.2",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] No tests for new frontend dependencies (vega, dompurify)

New frontend dependencies (vega visualization libraries and dompurify for HTML sanitization) have been added without any unit tests to verify their integration, correct rendering behavior, or sanitization efficacy.

Suggestion: Add Vitest unit tests for: 1) dompurify sanitization of potentially dangerous HTML/script inputs, 2) vega-embed rendering edge cases (invalid specs, malformed data, empty datasets), 3) correct graceful degradation when vega rendering fails. Mock the DOM environment as needed.

Risk: Unsanitized HTML could lead to XSS vulnerabilities if dompurify is misconfigured or not applied consistently, and broken vega rendering could produce silent UI errors.

Confidence: 0.85

[From SubAgent: testing]

"lucide-react": "^0.542.0",
"motion": "^12.36.0",
"nanoid": "^5.1.6",
Expand All @@ -42,11 +43,15 @@
"tailwind-merge": "^3.3.1",
"tw-animate-css": "^1.3.7",
"use-stick-to-bottom": "^1.1.3",
"vega": "^6.2.0",

This comment was marked as outdated.

This comment was marked as outdated.

This comment was marked as outdated.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Large dependency tree addition (vega, vega-lite, vega-embed) unguarded

Adding vega, vega-lite, and vega-embed brings in a large dependency tree. Confirm that tree-shaking, bundle size impact, and license compatibility (BSD-3-Clause, ISC, MIT) are reviewed. No usage of these libraries appears in the provided source diffs, so this is a placeholder dependency add.

Suggestion: Run a bundle analyzer (npm run build:web with stats) to measure impact. Add a comment in package.json or an ADR explaining the intended use of vega (charts/visualizations). If usage is optional/feature-flagged, consider dynamic imports.

Risk: Increased bundle size and potential noise in supply-chain audits; no functional risk yet.

Confidence: 0.95

[From SubAgent: general]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Heavy vega visualization dependency bundle impact

Adding vega, vega-lite, and vega-embed as direct runtime dependencies significantly increases JavaScript bundle size and memory footprint, potentially slowing initial load and parse times.

Suggestion: Consider dynamic/lazy loading (React.lazy + import() or code splitting) for the visualization feature so vega libraries are only fetched when a visualization message is actually rendered.

Risk: Larger bundle slows cold load and parse, and canvas rendering can block the UI thread for complex or many charts per message.

Confidence: 0.85

[From SubAgent: performance]

"vega-embed": "^7.1.0",
"vega-lite": "^6.4.3",
"xterm": "^5.3.0"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.12",
"@tauri-apps/cli": "^2",
"@types/dompurify": "^3.0.5",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.6.0",
Expand Down
1 change: 1 addition & 0 deletions src-tauri/migrations/20260506000000_message_parts_json.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE messages ADD COLUMN parts_json TEXT;
3 changes: 3 additions & 0 deletions src-tauri/src/commands/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ mod tests {
run_id: None,
role: role.into(),
content_markdown: content.into(),
parts_json: None,
message_type: "plain_message".into(),
status: "completed".into(),
metadata_json: None,
Expand All @@ -436,6 +437,7 @@ mod tests {
run_id: None,
role: "system".into(),
content_markdown: "summary".into(),
parts_json: None,
message_type: "summary_marker".into(),
status: "completed".into(),
metadata_json: Some(serde_json::json!({ "kind": "context_summary" }).to_string()),
Expand Down Expand Up @@ -465,6 +467,7 @@ mod tests {
run_id: None,
role: "tool".into(),
content_markdown: "tool output".into(),
parts_json: None,
message_type: "plain_message".into(),
status: "completed".into(),
metadata_json: None,
Expand Down
6 changes: 6 additions & 0 deletions src-tauri/src/core/agent_run_compaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub(crate) async fn persist_clear_context_reset_to_pool(
run_id: None,
role: "user".to_string(),
content_markdown: "/clear".to_string(),
parts_json: None,
message_type: "plain_message".to_string(),
status: "completed".to_string(),
metadata_json: Some(command_metadata.to_string()),
Expand All @@ -64,6 +65,7 @@ pub(crate) async fn persist_clear_context_reset_to_pool(
run_id: None,
role: "system".to_string(),
content_markdown: "Context is now reset".to_string(),
parts_json: None,

This comment was marked as outdated.

message_type: "summary_marker".to_string(),
status: "completed".to_string(),
metadata_json: Some(reset_metadata.to_string()),
Expand Down Expand Up @@ -228,6 +230,7 @@ impl AgentRunManager {
run_id: None,
role: "user".to_string(),
content_markdown: command_display_text.clone(),
parts_json: None,
message_type: "plain_message".to_string(),
status: "completed".to_string(),
metadata_json: Some(command_metadata.to_string()),
Expand All @@ -245,6 +248,7 @@ impl AgentRunManager {
run_id: None,
role: "system".to_string(),
content_markdown: "Context is now reset".to_string(),
parts_json: None,
message_type: "summary_marker".to_string(),
status: "completed".to_string(),
metadata_json: Some(reset_metadata.to_string()),
Expand Down Expand Up @@ -356,6 +360,7 @@ impl AgentRunManager {
run_id: None,
role: "system".to_string(),
content_markdown: summary,
parts_json: None,
message_type: "summary_marker".to_string(),
status: "completed".to_string(),
metadata_json: Some(summary_metadata.to_string()),
Expand Down Expand Up @@ -418,6 +423,7 @@ impl AgentRunManager {
run_id: None,
role: "system".to_string(),
content_markdown: heuristic_summary,
parts_json: None,
message_type: "summary_marker".to_string(),
status: "completed".to_string(),
metadata_json: Some(summary_metadata.to_string()),
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/core/agent_run_event_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ impl AgentRunManager {
run_id: Some(run_id.to_string()),
role: "assistant".to_string(),
content_markdown: String::new(),
parts_json: None,
message_type: "plain_message".to_string(),
status: "streaming".to_string(),
metadata_json: None,
Expand Down Expand Up @@ -414,6 +415,7 @@ impl AgentRunManager {
run_id: Some(run_id.to_string()),
role: "assistant".to_string(),
content_markdown: String::new(),
parts_json: None,
message_type: "reasoning".to_string(),
status: "streaming".to_string(),
metadata_json: None,
Expand Down
8 changes: 7 additions & 1 deletion src-tauri/src/core/agent_run_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ impl AgentRunManager {
run_id: None,
role: "user".to_string(),
content_markdown: display_prompt.unwrap_or_else(|| prompt.to_string()),
parts_json: None,
message_type: "plain_message".to_string(),
status: "completed".to_string(),
metadata_json: prompt_metadata.map(|value| value.to_string()),
Expand Down Expand Up @@ -257,7 +258,10 @@ impl AgentRunManager {
}

let (runtime_tx, runtime_rx) = mpsc::unbounded_channel::<ThreadStreamEvent>();
let runtime_finish_rx = self.runtime.start_session(spec, runtime_tx).await?;
let runtime_finish_rx = self
.runtime
.start_session(spec, runtime_tx, Arc::clone(&self.active_runs))
.await?;
self.spawn_runtime_event_loop(run_id.clone(), runtime_rx);
self.spawn_runtime_finish_watchdog(run_id.clone(), runtime_finish_rx);

Expand Down Expand Up @@ -515,6 +519,7 @@ impl AgentRunManager {
run_id: None,
role: "system".to_string(),
content_markdown: "Context is now reset".to_string(),
parts_json: None,
message_type: "summary_marker".to_string(),
status: "completed".to_string(),
metadata_json: Some(
Expand All @@ -534,6 +539,7 @@ impl AgentRunManager {
run_id: None,
role: "assistant".to_string(),
content_markdown: crate::core::plan_checkpoint::plan_markdown(plan_metadata),
parts_json: None,
message_type: "plan".to_string(),
status: "completed".to_string(),
metadata_json: serde_json::to_string(plan_metadata).ok(),
Expand Down
6 changes: 6 additions & 0 deletions src-tauri/src/core/agent_run_manager_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ pub(super) mod tests {
run_id: None,
role: "user".into(),
content_markdown: "oldest user message".into(),
parts_json: None,
message_type: "plain_message".into(),
status: "completed".into(),
metadata_json: None,
Expand All @@ -619,6 +620,7 @@ pub(super) mod tests {
run_id: None,
role: "assistant".into(),
content_markdown: "newer assistant reply".into(),
parts_json: None,
message_type: "plain_message".into(),
status: "completed".into(),
metadata_json: None,
Expand All @@ -631,6 +633,7 @@ pub(super) mod tests {
run_id: None,
role: "user".into(),
content_markdown: "newest user follow-up".into(),
parts_json: None,
message_type: "plain_message".into(),
status: "completed".into(),
metadata_json: None,
Expand Down Expand Up @@ -1410,6 +1413,7 @@ pub(super) mod tests {
run_id: None,
role: "user".into(),
content_markdown: "请帮我分析标题生成策略".into(),
parts_json: None,
message_type: "plain_message".into(),
status: "completed".into(),
metadata_json: None,
Expand All @@ -1432,6 +1436,7 @@ pub(super) mod tests {
run_id: None,
role: "user".into(),
content_markdown: "Need a short title".into(),
parts_json: None,
message_type: "plain_message".into(),
status: "completed".into(),
metadata_json: None,
Expand All @@ -1457,6 +1462,7 @@ pub(super) mod tests {
run_id: None,
role: "assistant".into(),
content_markdown: "Let's decide whether to keep fallback behavior.".into(),
parts_json: None,
message_type: "plain_message".into(),
status: "completed".into(),
metadata_json: None,
Expand Down
15 changes: 14 additions & 1 deletion src-tauri/src/core/agent_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,28 @@ pub struct AgentSession {
pub(crate) checkpoint_requested: AtomicBool,
pub(crate) abort_signal: tiycore::agent::AbortSignal,
context_compression_state: Arc<StdMutex<ContextCompressionRuntimeState>>,
/// Shared reference to the active-runs map so we can read the in-memory
/// `streaming_message_id` without querying the database (avoids race).
pub(crate) active_runs: Arc<
tokio::sync::Mutex<
std::collections::HashMap<String, crate::core::agent_run_manager::ActiveRun>,
>,
>,
}

impl AgentSession {
pub fn new(
pub(crate) fn new(
pool: SqlitePool,
tool_gateway: Arc<ToolGateway>,
helper_orchestrator: Arc<HelperAgentOrchestrator>,
event_tx: mpsc::UnboundedSender<ThreadStreamEvent>,
spec: AgentSessionSpec,
max_turns: usize,
active_runs: Arc<
tokio::sync::Mutex<
std::collections::HashMap<String, crate::core::agent_run_manager::ActiveRun>,
>,
>,
) -> Arc<Self> {
Arc::new_cyclic(|weak_self| {
let agent = Arc::new(Agent::with_model(spec.model_plan.primary.model.clone()));
Expand All @@ -158,6 +170,7 @@ impl AgentSession {
checkpoint_requested: AtomicBool::new(false),
abort_signal: tiycore::agent::AbortSignal::new(),
context_compression_state,
active_runs,

This comment was marked as outdated.

}
})
}
Expand Down
Loading
Loading