From 44531820d0395e4b8ead67a98c3e95457087595d Mon Sep 17 00:00:00 2001 From: iris Date: Sat, 4 Jul 2026 11:43:11 +0200 Subject: [PATCH 1/3] feat(agent-term): show details for plugin_install and commands_changed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently both system subtypes fall through to the generic muted note renderer (⚙ ), giving the operator no insight into what is happening. plugin_install: Render status explicitly — "loading…" on started, "✓ done" on completed — so a slow plugin boot is visible in the scrollback instead of two identical cryptic rows. commands_changed: Render the count of available slash commands in the summary row and expand to the full list in a collapsible details block. The list is most useful right after a fresh plugin_install so the operator can see exactly which /commands are now on offer. Both handlers sit immediately before the generic catch-all in the system-subtype dispatch in renderStream, preserving the existing fall-through for other subtypes (context_window_exceeded etc.). Closes #2183. --- frontend/packages/agent/src/app.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index abde53ea..80c7b15a 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -1809,6 +1809,34 @@ window.marked = marked; } return; } + // plugin_install: claude is loading/finishing a plugin (MCP server or + // slash-command provider). Show the status so the operator knows when + // a fresh session is loading its toolset. + if (v.subtype === 'plugin_install') { + const status = v.status === 'completed' ? '✓ done' + : v.status === 'started' ? 'loading…' + : (v.status || '?'); + api.row('note', '⚙ plugin install · ' + status); + return; + } + // commands_changed: the set of available slash commands changed (usually + // right after plugin_install). Show the count in the summary; expand to + // see the full list. + if (v.subtype === 'commands_changed') { + const cmds = Array.isArray(v.commands) ? v.commands : []; + if (!cmds.length) { + api.row('note', '⚙ commands changed · (empty)'); + return; + } + const summary = '⚙ commands changed · ' + cmds.length + ' available'; + const body = cmds.map((c) => { + const aliases = c.aliases && c.aliases.length + ? ' [/' + c.aliases.join(', /') + ']' : ''; + return '/' + c.name + aliases; + }).join('\n'); + api.details('note', summary, body); + return; + } // Other system subtypes (context_window_exceeded, etc.) — render a // muted note with the subtype label; reserve the loud orange `sys` // catch-all for truly unrecognised top-level types. From 1fe67837563e9037e2588adbb90d5594bffbcd35 Mon Sep 17 00:00:00 2001 From: iris Date: Sat, 4 Jul 2026 11:46:15 +0200 Subject: [PATCH 2/3] feat(agent-term): show compact_boundary details in per-agent terminal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit compact_boundary events currently render as the generic "⚙ compact_boundary" muted note. The event carries useful metadata — pre/post token counts, duration and trigger — that are invisible to the operator. With this change the row reads: · ⚙ compact · manual · 772k→6k tokens · 101s Fields rendered (all guarded — missing fields are silently omitted): - trigger ("manual" or "auto") - pre_tokens→post_tokens (formatted with k/M suffixes) - duration_ms (ms or s) Closes #2187. --- frontend/packages/agent/src/app.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index 80c7b15a..013fe80b 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -1837,6 +1837,27 @@ window.marked = marked; api.details('note', summary, body); return; } + // compact_boundary: claude completed a compaction pass. The metadata + // carries pre/post token counts, duration, and the trigger (manual vs + // auto). Show a single summary line so the operator can gauge how much + // context was shed. + if (v.subtype === 'compact_boundary') { + const m = v.compact_metadata || {}; + const parts = ['⚙ compact']; + if (m.trigger) parts.push(m.trigger); + if (m.pre_tokens != null && m.post_tokens != null) { + const fmtTok = (n) => n >= 1_000_000 ? (n / 1_000_000).toFixed(1) + 'M' + : n >= 1_000 ? Math.round(n / 1000) + 'k' + : String(n); + parts.push(fmtTok(m.pre_tokens) + '→' + fmtTok(m.post_tokens) + ' tokens'); + } + if (m.duration_ms != null) { + const ms = m.duration_ms; + parts.push(ms < 1000 ? ms + 'ms' : (ms / 1000).toFixed(1) + 's'); + } + api.row('note', parts.join(' · ')); + return; + } // Other system subtypes (context_window_exceeded, etc.) — render a // muted note with the subtype label; reserve the loud orange `sys` // catch-all for truly unrecognised top-level types. From 1011428cc7c06e5d9096382155363b2db45d1dc1 Mon Sep 17 00:00:00 2001 From: iris Date: Sat, 4 Jul 2026 11:50:57 +0200 Subject: [PATCH 3/3] docs(terminal-rendering): document plugin_install, commands_changed, compact_boundary rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The renderer dispatch section was missing entries for three system subtypes that now have specific renderers (added in the preceding commits on this branch). Update the row taxonomy table and the numbered dispatch description to cover all four system-subtype handlers (thinking_tokens was already documented; 1b–1e are new). --- docs/terminal-rendering.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/terminal-rendering.md b/docs/terminal-rendering.md index 6b060c76..6ad9da54 100644 --- a/docs/terminal-rendering.md +++ b/docs/terminal-rendering.md @@ -67,6 +67,10 @@ parent's negative pull. | `.tool-result-block.error` `
` | `Nl · headline` | red, body is text | long error `tool_result` (`is_error: true`); wrapper stripped | stream-json | | `.tool-use` | `⌁ task started · [type]` | cyan | claude Task-tool subagent start (dead path — `Task` omitted from agent allow-list) | `renderTaskEvent` | | `.turn-end-ok` / `.turn-end-fail` / `.tool-result` | `⌁ task ✓/✗/◌ · · → ` | green / red / muted | claude Task-tool result (dead path for agents) | `renderTaskEvent` | +| `.note` | `· ⚙ plugin install · loading…` or `✓ done` | muted | `system/plugin_install` (`status` = started/completed) | stream-json | +| `.note` | `· ⚙ commands changed · N available` (expandable list of `/name` entries) | muted | `system/commands_changed` (slash-command set updated, usually post-plugin_install) | stream-json | +| `.note` | `· ⚙ compact · ·
 tokens · ` | muted | `system/compact_boundary` (compaction completed; metadata includes pre/post token counts, duration, trigger) | stream-json |
+| `.note` | `· ⚙ ` | muted | other `system` subtypes (context_window_exceeded, etc.) | stream-json catch-all |
 | `.note` | `· ` | muted | harness chatter | `LiveEvent::Note` |
 | `.note.stderr` | `! stderr: ` | amber/orange | stderr lines off claude | `LiveEvent::Note` (`text` starts `stderr:`) |
 | `.note.op` | `· operator: ` | mauve italic | operator-initiated notes (/cancel, /compact, /model, new-session) | `LiveEvent::Note` (`text` starts `operator:`) |
@@ -95,6 +99,19 @@ suffix, so the terminal degrades cleanly against older event shapes.
    while it's still the last one rendered (`nextElementSibling ==
    null`); any other event after it makes the next tick start a
    fresh row. Avoids a note-per-tick scrollback flood.
+1b. `system/plugin_install` → muted note `⚙ plugin install · loading…`
+   (on `started`) or `⚙ plugin install · ✓ done` (on `completed`).
+   Emitted in pairs: started fires before the plugin loads, completed
+   fires when it's ready. The `uuid` links the pair.
+1c. `system/commands_changed` → collapsible `.note` details row showing
+   the new slash-command count (`⚙ commands changed · N available`).
+   Expanding reveals each `/name` and its aliases. Fires after
+   `plugin_install` when a plugin registers new commands.
+1d. `system/compact_boundary` → muted note showing compaction summary:
+   `⚙ compact ·  · 
 tokens · `. Fields are
+   guarded individually — a missing field is silently omitted. Trigger
+   is `"manual"` (operator `/compact`) or `"auto"`.
+1e. Other `system/` subtypes → muted note `⚙ `.
 2. `subtype == "task_started" | "task_notification"` →
    `renderTaskEvent` (subagent activity gets the `⌁` glyph).
 3. `type == "assistant"` → walk `message.content[]`: