Commit Graph
10 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.8 dea3e70e49 fold sub-agent prompts via parent_tool_use_id (T-338)
In live stream-json sessions a sub-agent's spawning prompt is tagged
with parent_tool_use_id, not the transcript JSONL's isSidechain +
parentUuid. The parser ignored that field, so the prompt parsed as a
main-thread user turn and rendered as a blue "you" card above the
Activity Agent card instead of folding into it.

Carry parent_tool_use_id onto ConversationItem; its presence now marks
the item as a sidechain message. The sidechain fold resolves ownership
directly by tool-use id (no transcript-only uuid chain to walk), so the
prompt folds into its Agent card and the run nests under it as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:03:50 +02:00
jpmschweitzerandClaude Opus 4.8 a7a7b04a05 fold sub-agent prompt into the Agent card; relabel you → agent prompt (T-263)
A sidechain sub-agent prompt was rendered with the blue "you" label,
falsely implying the user typed it. Now:

- transcript_reader parses parentUuid (was dropped) onto every
  ConversationItem.
- conversation_view resolves each sidechain prompt to its spawning
  Agent/Task card via parentUuid (nearest-preceding Agent as fallback),
  folds the prompt into that card as a collapsed "prompt" segment, and
  suppresses the standalone block. Layered order when expanded:
  call → prompt → result (note E).
- A sidechain UserMessage never gets the "you" treatment: folded into its
  card, or — when orphaned — rendered as a muted standalone "agent prompt".

Tests: parentUuid parse, fold + suppression, parallel-agent attachment
(would fail under a nearest-preceding heuristic), orphan relabel, and a
golden for the Agent card's call → prompt → result layering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:45:48 +02:00
jpmschweitzerandClaude c5e58733a2 render typed tool cards and live session status from stream-json
The conversation pane now exploits the structured stream instead of
dumping tool input as JSON. ConversationController indexes tool_use by id
so a tool_result pairs back to its call and renders the Edit/Write diff or
is_error failure in place; per-tool bodies (Bash command+output, Read/Grep
file/query) reuse the shared renderers factored out of the permission
card. SessionStatus gains cost + contextWindow + rate-limit, read straight
off the init/result/rate_limit_event events, so the in-pane status line
reflects live state without the config probe.

Partial-message streaming is wired behind --include-partial-messages but
its event shape is unverified against the live binary and degrades to a
no-op if it differs — see T-184.

T-168.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-30 13:44:19 +02:00
jpmschweitzerandClaude Opus 4.7 06cf9f8298 add per-session status strip: model / permission-mode / context (T-145)
test / unit + widget + golden + a11y (push) Failing after 28s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 23s
The transcript reader now also extracts a SessionStatus — current model
(assistant message.model), permission mode (the permission-mode records,
previously skipped), and context-window tokens (message.usage input +
cache-read + cache-creation) — and emits it on a statusStream, merging
deltas so it only fires on change. All CC-internals parsing stays in the
drift-contained reader (D-75).

The Claude pane renders this as a thin strip above the conversation
(model · permission-mode · context). Context is shown as a token count,
not a percentage: the transcript carries usage but not the model's window
limit, and the model id doesn't encode the 1M vs 200k tier.

Lead pane done; teammate-tile mirror and the sidebar (T-141) consume the
same status next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 10:14:05 +02:00
jpmschweitzerandClaude Opus 4.7 caf8fd95cf make the transcript session-switch test deterministic
The "session switch: newer file triggers replay" test relied on
wall-clock mtimes to decide which .jsonl was newest. Under timing
pressure the two files' mtimes could tie, so the reader never switched
and the test timed out — it failed ~60% of full-suite runs (measured),
the source of the intermittent red I'd been waving off as "a flake".

Backdate session A to a fixed past time so the newer file is
unambiguously newer; the switch is now guaranteed regardless of load.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 08:48:59 +02:00
jpmschweitzerandClaude Opus 4.7 e1cd4653c0 bind each Claude pane to its own session id (T-146)
A regression from T-137: every pane rendered the newest .jsonl in the
workspace dir, so concurrent sessions collided — a secondary tab showed
the primary's conversation. Each pane now spawns claude with its own
--session-id (a transcript is named <session-id>.jsonl), tails that
exact file via TranscriptReader's file: param, and uses a per-session
MessageBus channel so controllers don't cross-talk.

The primary's id is deterministic from its session name (stable → it
resumes across restarts, like /resume off the same history file);
secondaries get a fresh random id so a clean session is always available.
The reader now waits for the bound file to appear rather than throwing.

Migration: an existing tmux session created before this (no --session-id,
claude chose its own id) must be killed once (claude.kill-all-sessions)
so the next spawn binds the controlled id.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 08:28:55 +02:00
jpmschweitzerandClaude Opus 4.7 fc55f58e6b add tmux team observer + member lifecycle events (T-139, D-75)
test / unit + widget + golden + a11y (push) Failing after 27s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 24s
team_observer.dart is the single drift-containment point for Claude
Code's experimental tmux team mode. It discovers the active team for a
workspace (~/.claude/teams/<team>/config.json, matched by member cwd),
polls `tmux -L clide list-panes -a`, and correlates live panes with the
config's tmuxPaneId to emit TeamMemberBorn / TeamMemberDied — identity
(name, agentType, model, colour, pane) comes from the config, so it's
reliable regardless of transcript drift.

Each teammate's subagent transcript is resolved best-effort and streamed
on a per-agent MessageBus channel via TranscriptPublisher (TranscriptReader
gains an explicit `file:` for this). The config<->transcript join is the
fragile part: no shared key, so it uses a sibling .meta.json agentType
when present, else zips members-by-joinedAt against files-by-mtime. This
join needs validation against a live team run.

App wiring + visible surfacing land with the teammate tiles (T-140).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 01:13:46 +02:00
jpmschweitzerandClaude Opus 4.7 0abc14ed1a parse only large transcript chunks off-isolate; deflake stream tests
test / unit + widget + golden + a11y (push) Failing after 27s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 24s
The hang fix offloaded every transcript parse to Isolate.run, including
the small per-poll appends. Spawning a one-shot isolate each tick is pure
overhead and, under concurrent test load, the spawn+round-trip latency
blew the streaming tests' fixed-delay window — transcript_reader_test
flaked intermittently in the full suite.

Only chunks >= 64KB now go off-isolate (the initial-tail case that
actually janks a frame); small appends parse inline. The streaming tests
poll until the expected items arrive instead of waiting a fixed delay, so
they're robust regardless of parse latency or scheduler load.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 23:47:25 +02:00
jpmschweitzerandClaude Opus 4.7 a6ed22405f fix Claude pane hang: cap tail, parse off-isolate, coalesce notifies
test / unit + widget + golden + a11y (push) Failing after 27s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 27s
The native Claude pane (T-137) picks the newest transcript JSONL by
mtime — which on a live session is this multi-MB active file. On attach
it read and parsed the whole file synchronously on the UI isolate and
fired notifyListeners per item, freezing the app.

Three fixes: cap the initial read to a recent tail (256KB, injectable
for tests); run JSON parsing in a background isolate via Isolate.run;
coalesce the controller's notifications with a zero-duration Timer so a
burst collapses into one rebuild (a microtask-scheduled notify wouldn't
— stream events deliver one per microtask and the notify interleaves).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 20:30:30 +02:00
jpmschweitzerandClaude 0eee456426 add TranscriptReader: tail + parse Claude transcript JSONL (T-136)
test / unit + widget + golden + a11y (push) Failing after 27s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 30s
Data layer for native Claude rendering (T-132). Resolves the munged
project dir, picks the newest session .jsonl by mtime (Claude doesn't
expose its session id), tails it append-only via a dart:io byte cursor,
and emits a sealed ConversationItem stream (user / tool-result /
assistant text / thinking / tool-use). Skips bookkeeping record types
and degrades gracefully on an unfamiliar transcript `version`. Pure
dart:io + dart:convert, Flutter-free, zero new deps.

The parser is a pure public parseLine(line) -> List<ConversationItem>
so tests exercise the real code (an injectable projectsBase lets the
streaming tests point the real reader at a temp dir) — no shadow
re-implementation. 31 tests under dart test.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-22 19:36:53 +02:00