v0.2.3 — Promoted sections + turn grouping + smarter tool calls - #17
Merged
Conversation
Both REQUEST panels used to render Claude `<system-reminder>` blocks
as one generic "system-reminder" chip and showed Codex framework
chunks as plain TEXT blocks. The user can't tell at a glance whether
chrome is CLAUDE.md memory, the skills catalogue, environment
context, or something else.
This version adds a shared classifier (`src/lib/framework-blocks.ts`)
that recognises a finite set of kinds — skills, memory, agents-md,
tools, date, environment, permissions, apps, plugins, collaboration,
custom-instructions — using leading XML-ish tags and known opening
sentences. A new presentational chip (`FrameworkBlockChip`) renders
each with a kind-specific icon, label, and concise preview ("12
skills", "cwd: ~/foo · zsh", "AGENTS.md · ~/repo", "Today:
2026-05-15", …). Unrecognised reminders fall through to a generic
`system-reminder` chip so nothing ever renders as raw chrome text.
Wired into:
- `RequestPanel.tsx` — `splitReminders` now classifies each reminder
body before emitting a chip; the inline `SystemReminderChip` is
gone.
- `ResponsesRequestPanel.tsx` — each `input_text` content block is
classified; framework blocks render as chips, prose renders as a
normal TEXT block. `summariseItem` and `findUserPromptIndex` skip
framework chrome so the inline preview and the `prompt` highlight
land on actual human-typed content.
Verified the rule table against `~/.agentmind/projects/*.jsonl`:
100% of observed framework blocks classify cleanly across both
agents (Claude: 4 distinct kinds, Codex: 9 distinct kinds), with
zero false-positive prose classifications.
The v0.2.3 classifier already labelled `skills` and `memory` chunks distinctly, but they still sat as plain chips inside the user message — the same as `permissions`, `apps`, `plugins`, and so on. Skills and memory carry actual user data (installed skill catalogue, CLAUDE.md / cross-session memory summary) and deserve more weight than platform protocol chrome. This promotes both to their own top-level Sections, rendered at the same hierarchy as `tools` and `instructions` in both REQUEST panels. The first occurrence wins (both agents re-inject identical content on every iter), and the in-message chips for these kinds are suppressed to remove the duplicate-across-N-iters noise. - `framework-blocks.ts`: adds `PROMOTED_KINDS` set, `parseSkillsBullets` (splits `- name: description` rows from the catalog, handles Codex's `(file: rN/…)` locator suffix and multi-line descriptions), and `memoryHeadings` (extracts markdown headings for a TOC preview, filtering boilerplate framing labels). - `FrameworkSections.tsx`: new file with `SkillsSection` (one collapsible row per skill, max-h-96 cap so 60-skill catalogs don't blow up the layout, raw-body escape hatch) and `MemorySection` (markdown body with up-to-3-heading TOC in the header summary). - `RequestPanel.tsx` / `ResponsesRequestPanel.tsx`: extract promoted blocks via `findPromotedBlocks`, render the sections below the message list, and skip the matching chips inside the message body. Codex `summariseItem` also drops promoted kinds from the inline preview so the developer-item summary matches what's actually visible. Verified against real `~/.agentmind/projects/*.jsonl` data: - Claude (3e3b5b…): 10 skills, 2 memory TOC headings - Codex (5df6e3…): 60 skills, 16 memory TOC headings spanning user profile / preferences / per-cwd memory entries — all parsed cleanly.
The promoted Skills and Memory sections used a `ToolsSection`-style
header (kind label as the title) that looked different from the
neighbouring message sections (`input#user` title + role-coloured
hash). Now they share one header idiom: `input#<role>` on the left
naming the source field, a kind-specific icon, an inline preview,
and a `muted` Badge on the right pinning the kind label — same slots
in the same order as the existing `prompt` / `new` badges.
Mechanics:
- `FrameworkSections.tsx`: introduces `PromotedRole` type plus two
shared header helpers (`InputRoleTitle`, `SectionSummary`).
Title carries the role accent colour (user vs llm); summary slot
uses `Badge variant="muted"` so the kind label reads as metadata,
not an action (`prompt` / `new` stay on `success` green).
- `RequestPanel.tsx` / `ResponsesRequestPanel.tsx`: `findPromotedBlocks`
now returns `{ body, role }` so the source role can flow through
to the section header. Claude always yields `'user'`; Codex
forwards whatever role the source input item carried (typically
`'developer'` for the system-style item).
The skills preview also got richer: shows `${count} · skill1 ·
skill2 · skill3` so the collapsed header carries actual catalogue
samples instead of just a count.
…s/memory Reverts the previous `input#<role>` title experiment for skills/memory and instead unifies prompt + skills + memory under one visual idiom: - Kind name as the title (`prompt`, `skills`, `memory`) — the loud, scannable label the user reaches for. - Inline preview in the summary slot (prose excerpt for prompt, count+names for skills, count+TOC for memory). - Small dimmed source-field icon at the very right of the header marking which wire field the content came from (User for `input#user`, Settings2 for `input#developer` / `input#system`, Bot for `input#assistant`). Reads as a metadata footnote, not an action — keeps the loud `prompt`/`new` action badges in their own visual lane. Mechanics: - `FrameworkSections.tsx`: adds `PromptSection` (peach-tinted body matching the existing user-prompt highlight, defaults to open), reverts Skills/Memory headers to kind-first, and introduces a shared `SourceFieldIcon` helper. - `RequestPanel.tsx` (Claude): `findPromotedBlocks` now also extracts the prompt prose from the user-prompt message; the same message stays in the messages list with a new `promotedPrompt` prop that filters prose text blocks out of the rendered body. If nothing remains after dropping prose (the common case), the section bails to null — orphan chrome chips (date / deferred- tools) that happened to share the user-prompt message stay visible. - `ResponsesRequestPanel.tsx` (Codex): same prompt extraction; the user-prompt input item is filtered from the items list since Codex puts the prompt in its own item with prose-only content. Layout order for both panels is now: PROMPT → messages/items → SKILLS → MEMORY → (instructions / system) → TOOLS — PROMPT anchors the eye at the top, the trace follows newest- first, resources and chrome land at the bottom. This matches the "Prompt / SKILLS / Memory / TOOLS / System" mental model the user sketched in feedback.
…enance into body
Two related UX wins on top of the v0.2.3 prompt/skills/memory promotion.
System parent
- Bundle everything that's not PROMPT/TOOLS/SKILLS/MEMORY into a single
foldable "system" section at the bottom of the request panel:
- Claude req.system blocks (kept with cache_control awareness via
ClaudeSystemPromptBlocks)
- Codex req.instructions (rendered via CodexInstructionsBlock)
- Every system-reminder / framework input_text classified as
permissions / apps / plugins / collaboration / environment /
agents-md / date / deferred-tools / instructions / ...
- Dedup chrome by exact body so a 60-iter conversation re-injecting
the same date or permissions reminder produces ONE chip, not 60.
- Drop the in-message chrome chip rendering: splitReminders now emits
prose-only; Codex MessageBody skips non-prose blocks; the items
list filter drops message items whose body would be empty after
suppression (developer chrome stack, AGENTS.md+env user item). Net
effect: the trace between PROMPT and SKILLS reads as pure agent
action history (assistant text, reasoning, tool calls, tool
results).
Source provenance
- Drop the right-edge SourceFieldIcon from section headers -- it was
too dim to find, too noisy to ignore.
- Add a SourceCaption rendered at the top of expanded bodies:
arrow + role icon + "input#<role>", dimmed and small. Section
headers stay minimal (kind + preview); provenance appears only
when the user opens the section, which is when they'd actually
care.
Layout for both panels is now:
PROMPT -> agent trace -> SKILLS -> MEMORY -> TOOLS -> SYSTEM
New 'system-prompt' kind added to FrameworkKind union for future
classifier reuse; not currently emitted by classifyFrameworkBlock
(the API-level system prompt is plumbed as a pre-rendered ReactNode).
…utputs
Phase 2 of the v0.2.3 polish — making the function-call traffic easy
to scan at a glance instead of forcing the eye through a JSON dump.
New helpers (src/lib/tool-call.ts)
- parseToolArgs: best-effort JSON parse of function_call.arguments,
with graceful raw-string fallback for mid-stream deltas.
- tryShellArgs: detects the shell-tool shape ({cmd, workdir,
yield_time_ms, max_output_tokens}) so we can surface the command
line as the focus instead of burying it in a JSON blob.
- parseShellEnvelope: pulls Codex's 4-line tool-output wrapper
("Chunk ID / Wall time / Process exited / Original token count /
Output:") apart so the renderer can hoist exit + duration as
badges and treat the trailing stdout as the focus.
- formatDurationSec: sub-second -> ms, otherwise s / m s.
Codex REQUEST panel (ResponsesRequestPanel.tsx)
- function_call items now render via FunctionCallCard with a
pretty-printed args view. Shell-shaped calls show as
$ <cmd>
cwd: ... yield: ... max_out: ...
Non-shell args fall through to pretty JSON. Previously: raw
JSON.stringify dump.
- function_call_output items now render the parsed envelope: exit
code chip (green for 0, red otherwise), duration chip, token-
count chip, then the actual stdout as the body. The full envelope
stays one click away via a "show envelope" toggle.
- custom_tool_call gets its own card matching the response-side
rendering.
- Inline summaries (the iter header preview) parse the same data,
so an iter card now reads
input#function_call · "$ wc -c google.html"
input#function_call_output · "exit 0 · 80562 google.html"
rather than dumping the JSON or the envelope header.
Codex RESPONSE panel (ResponsesResponsePanel.tsx)
- ArgumentsPre now uses the same parseToolArgs + tryShellArgs path,
so the model's outgoing shell call reads identically to its
replay on the next iter's input transcript. Symmetric idiom on
both sides of the wire.
Types (openai-responses-types.ts)
- Extend ResponsesInputItem with function_call / custom_tool_call /
reasoning. Codex CLI rebuilds the full transcript on every turn,
so these assistant-emitted shapes ALSO appear verbatim in
subsequent input[] arrays — the prior narrower union was missing
them and forced an unsafe JSON.stringify fallback in the panel.
Four follow-ups to the 0.2.3 phase 1/2 work, all driven by:
"too much detail leaking out of collapsed headers, too many new
badges per iter, instructions stuck unfoldable, nested system-prompt
wrapper inside system".
1. Strip per-content previews from PROMPT / SKILLS / MEMORY / SYSTEM
section headers — just a count ("skills · 56", "memory · 16",
"system · 10"). The chips inside still reveal everything when
expanded; the headers stop competing with the trace.
2. Codex input[] items now group into TURNS. Each turn = one
model-action cycle (reasoning + assistant text + function_call(s)
+ their tool_call_outputs), rendered as one Section with an
abstract title ("tool_use × N" / "response × N" / "user × N")
and one badge. Iter cards with 4–6 new entries collapse to one
new badge per cycle. Boundaries: reasoning starts a new turn;
function_call after function_call_output starts a new turn;
user message always its own turn.
3. Claude messages[] grouped the same way: an assistant tool_use
message glues with the following user tool_reply (tool_result
block, even when Claude pairs it with auto-injected text like
"Tool loaded." from ToolSearch). One turn = one cycle.
4. System section now flat: instead of a nested "system prompt"
card wrapping the API system field, every system blob is run
through splitSystemPromptByH1 — each markdown H1 section becomes
its own foldable FrameworkBlockChip with the heading as label
("doing tasks", "environment", "frontend guidance", "identity",
…). Codex req.instructions and Claude req.system both flow
through the same idiom, sitting side-by-side with the existing
chrome chips. Cache_control gets dropped at this layer — power
users can find it in raw JSON.
Side-fix: findUserPromptIndex (Claude) now skips messages with
tool_result blocks. Without this, iter ≥2 picked up "Tool loaded."
(the ToolSearch tool_result + paired prose) as the user prompt and
PromptSection surfaced "Tool loaded." instead of the original
"fetch google.com…".
Verified: typecheck, pnpm smoke (all 11 checks), visual inspection
of Codex msg #6 (3 iter) and Claude msg #16 (5 iter, ToolSearch +
WebFetch + Write + Bash chain). One new badge per iter on both
agents; PROMPT renders the right text; system section expands to
13 chips on Claude, 10 on Codex with no nested wrapper.
Two follow-ups on the turn-coalescing pass:
1. PROMPT was top-of-panel and expanded on every iter. On iter ≥ 2
the prompt is inherited context the user already saw on iter 1 —
pinning it above the trace pushed the new TOOL_USE turn (the
actually-actionable content) below the fold. Now: on iter 1 the
prompt renders at the top and starts open; on iter 2+ it slides
down into the inherited-context cluster (alongside skills /
memory / tools / system) and starts collapsed. Still one click
away when you want to re-read what was asked.
2. The turn section titles carried a ×N count ("tool_use × 4",
"response × 2") that read more like a tally of tool invocations
than what it actually was — the count of *items inside the
collapsed body* (reasoning, assistant text, function_call,
function_call_output). Misleading enough to remove. Titles
collapse to just "tool_use" / "response" / "user" and the
internal item breakdown is one expand away.
Side cleanup: drop the now-unused `accent` field from TURN_META /
MSG_TURN_META — was only powering the dropped count's tinted text.
Verified on Codex msg #6 (3 iter) and Claude msg #16 (5 iter):
iter 1 opens with PROMPT expanded at the top, every subsequent
iter opens with the new TOOL_USE turn at the top + PROMPT
collapsed below. Smoke + typecheck green.
Two follow-ups to the new section layout. 1. SourceCaption now reads `<container>#<role>` per protocol instead of hard-coding `input#<role>`. Anthropic blocks come from `MessageParam.content[]` (-> `messages#user`); OpenAI Responses blocks come from top-level `input[]` items (-> `input#user`). The earlier label lied for Claude — "input#user" pointed to a field that doesn't exist on the wire. 2. Response panel now flashes a `~Nt` order-of-magnitude token badge on every category header (message, reasoning, function_call, custom_tool_call, local_shell_call, web_search_call, image_gen, plus Claude's text / thinking / tool_use / tool_result). Estimate is `chars / 4`; tooltip shows raw chars. Opt-in via `showTokens` for the shared ContentBlockView so the request side stays clean (those blocks already group into TOOL_USE / RESPONSE turns and per-block size chips there would re-introduce the noise the user previously asked to remove).
Move the wire-source caption from inside the expanded body up to the header row, since the body never carried anything else and the empty caption-only top line read as wasted space. Every top-level Section (PROMPT / SKILLS / MEMORY / TOOLS / SYSTEM) now renders as a uniform three-cell header: [chevron] [icon] NAME source(dim) count(dim) `source` is the outer-most wire field only (`messages` / `input` / `system` / `instructions` / `tools`) — no `#role` suffix, matching the user's "显示最外层的就行" — so a column of headers scans cleanly against a tiny vocabulary. For mixed-origin SystemSection, the cell joins unique outer sources in declaration order: `system, messages` for Claude with reminder chrome, `instructions, input` for Codex. ClassifiedBlock now carries an optional `.source` field stamped at construction (Claude `req.system` → 'system', Codex `req.instructions` → 'instructions', extractRequestContent.chrome → 'messages' / 'input') so SystemSection's header can summarise without re-deriving origin. Drops SourceCaption + TextSummary (no remaining callers) and trims the now-unused lucide imports.
Pre-0.2.3 the launcher parked after `claude` / `codex` exited so the
just-captured trace could be browsed without re-launching. In
practice users always Ctrl+C immediately — the parked window felt
like a hang, and the "press Ctrl+C to stop AgentMind" banner was
greeted with surprise more than relief.
Now `child.on('exit')` tears down the dashboard and propagates the
agent's exit code (signal → 128+signum, code → code, clean → 0) via
a tiny `exitCodeFor()` helper, so shell pipelines and `set -e`
scripts see the agent's verdict instead of AgentMind's "I stopped
fine".
To revisit a saved trace, run `agentmind-cli --no-agent` — it reads
the same `~/.agentmind/projects/*.jsonl` files the launcher just
wrote. README + help text + AGENTS.md updated to reflect the new
flow.
Smoke test: the launcher previously stayed alive after the shim
exited so the test could SIGTERM it; now it races to exit on its
own. Reworked `runOne()` to attach the 'exit' listener BEFORE the
snapshot wait (so we never miss the event) and to assert the auto-
exit actually happens within a generous 5s window — the test now
exercises the new behaviour rather than working around it.
Four follow-ups to the unified header layout.
1. Drop the trailing `·` dot on TOOL_USE (and any other turn section
that just wants a right-side `new` badge). The dot was inherited
from the legacy `· summary` slot; replaced with an explicit
`trailing` slot in Section so badges sit on the right cleanly,
no leading punctuation.
2. Move `count` from the right cell to immediately after the title.
A column of headers now reads `skills 10 messages` left-to-right
like a sentence rather than `skills … messages 10` as two
right-aligned columns. The source cell takes the right-pinned
slot on its own.
3. Promote the API-level system prompt (Anthropic `req.system` /
OpenAI `req.instructions`) into a new top-level
`InstructionsSection`. The remaining SYSTEM bucket now only
holds message-injected chrome, so every section in the column
has a single-source header:
prompt … messages | input
skills N messages | input
memory N messages | input
tools N tools
instructions N system | instructions
system N messages | input
4. Make the source cell visible alongside the `new` badge. Pre-
change a turn-section's `new` badge took the right slot via
ml-auto, hiding the source field. The new Section component
pins `source` right and renders `trailing` after it, so
`tool_use messages new` and `tool_use input new` both surface
provenance even when the badge is present.
Header layout, final form:
[chevron] [icon] NAME [count] [source] [trailing?]
`summary` prop removed from Section — every caller now uses the
structured cells, which keeps the column visually aligned across
section kinds.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
v0.2.3 reshapes the REQUEST panel around what the user actually wants
to see — promoted content + grouped turns + honest provenance — and
adds a few quality-of-life launcher tweaks. Everything below is one
release.
REQUEST panel
Framework chrome classifier (
src/lib/framework-blocks.ts):recognises the recurring user-input chunks both Claude Code and
Codex CLI inject (
skills,memory,agents-md,tools,date,environment,permissions,apps,plugins,collaboration,instructions). Each chunk renders as a labelled chip with aconcise preview (
12 skills,cwd: ~/repo · zsh,Today: …)instead of a single generic
system-reminderblob.Promoted sections (PROMPT / SKILLS / MEMORY / TOOLS /
INSTRUCTIONS / SYSTEM): the most useful classifications get
hoisted to top-level Sections so the eye lands on them first.
PROMPT shows the actual human-typed text; SKILLS / MEMORY parse
out their per-item structure; INSTRUCTIONS carries the API-level
system prompt (Anthropic
req.system/ OpenAIreq.instructions)split per H1 heading; SYSTEM holds the remaining message-injected
chrome. Every section header reads as a uniform three-cell row:
where
sourceis the outer-most wire field (messages/input/system/instructions/tools) — no#rolesuffix, so thecolumn has a tiny scannable vocabulary.
Turn grouping: Codex
input[](and Claudemessages[]) usedto render one Section per item, producing 4–6 cards per model-
action cycle. They now coalesce into TOOL_USE / RESPONSE / USER
turns with a single
newbadge per turn, and the turn headercarries source provenance alongside the badge so users can still
see where a turn lives on the wire.
PROMPT visibility: pinned to the top and expanded on iter 1
(the iter that introduced it), demoted next to inherited context
and collapsed by default on iter ≥2.
Tool-call rendering
function_callshell envelopes ({cmd, workdir, yield_time_ms, max_output_tokens}) now surface the command lineas
$ <cmd>with metadata fields collapsing to a small captionbelow.
function_call_outputextractsexit_codeandduration_secondsinto the row title, and renders the actual stdout/stderr below.
Response panel
(
message,reasoning,function_call,custom_tool_call,local_shell_call, …). Estimate ischars / 4(order-of-magnitude, à la "is this 100 tokens or 10k tokens"); raw char
count surfaces on hover. Opt-in via a
showTokensprop so therequest side stays clean.
Provenance
sourcecell names the outer-most wire field achunk lives in. Anthropic chunks read
messages(block livedinside
MessageParam.content[]); OpenAI chunks readinput(top-level
input[]item). The API-level system prompt readssystem(Anthropic) orinstructions(OpenAI).CLI launcher
Ctrl+C" flow felt like a hang in practice; users always Ctrl+C
immediately. Exit code propagates honestly via a tiny
exitCodeFor()helper (signal -> 128+signum,code -> code,clean -> 0) so shell pipelines see the agent's verdict, notAgentMind's "I stopped fine". Run
agentmind-cli --no-agentlater to revisit any saved trace under
~/.agentmind/projects.Verification
pnpm typecheckclean.pnpm smoke(build + Codex/Anthropic capture + launcher) — all11 scenarios pass, including the reworked launcher smoke that
asserts the new auto-exit behaviour.
traces: the section column reads
PROMPT messages / SKILLS 10 messages / MEMORY 2 messages / TOOLS 55 tools / INSTRUCTIONS 12 system(Claude) andPROMPT input / SKILLS 56 input / MEMORY 16 input / TOOLS 18 tools / INSTRUCTIONS 4 instructions / SYSTEM 6 input(Codex).TOOL_USE turns show
tool_use messages new/tool_use input newwith a singlenewbadge per turn.Test plan
pnpm smoke) — 11/11