A `Workflow` tool-use launches its multi-agent run in the background and
returns immediately; the real fan-out arrives out-of-band on stream-json
`type:"system"` task_* events (task_started / task_progress / task_updated /
task_notification) keyed by the launching tool-use id — which clide was
dropping. (Wire shape captured by two live stream-json probes; recorded on
the ticket.)
- workflow_run.dart: a pure, Flutter-free WorkflowRun/WorkflowAgent model
that folds those events (phases, per-agent start→progress→done deltas,
usage) into a snapshot.
- StreamJsonSession recognises the events, accumulates a
Map<toolUseId, WorkflowRun>, and exposes `workflows` + `workflowsStream`.
- A `Workflow` tool-use with a live run renders a dedicated run card —
phase groups, per-agent rows with spinner/check status, usage, and the
script — falling back to the generic tool card pre-progress or on reload.
The run breaks the activity cluster so it's always first-class (like T-342).
- The sidebar Activity tab adds a WORKFLOWS section: one row per run with its
done/total agent count, tinted by running/done state.
Closes T-416 and the T-410 epic (all children done).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The epic's new lib surface dropped coverage to 94.87% (floor 95). Add
the missing tests: the command-bus → _send path end-to-end in the pane
(effort respawn with --effort, invalid-level notice, both pickers,
set_permission_mode write, sidebar navigation messages, /memory
editor.open, /help summary, TUI-only notice without a session write)
and ActivityTabView's USAGE block + placeholder branch. 95.14% after.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Typed into the conversation view, /model was forwarded to the session's
stdin as message text — the CLI's interactive picker only exists in its
own TUI, so nothing happened. clide now owns it like /clear//resume//fork
(T-156).
/model <name> sends a set_model control_request (verified against
claude 2.1.175: subtype accepted alongside set_permission_mode;
"default" resets to the CLI's configured model) with an optimistic
status merge, rolled back with a toast if the CLI rejects the name.
Bare /model swaps a picker card into the interaction zone (D-78) —
numbers / arrows + Enter / Esc, mirroring the prompt card's shortcuts.
The model list comes from the `initialize` handshake response, which
the session now always sends — the spike verified it is side-effect-
free, and it previously went out only when MCP servers were hosted.
Until the response lands the picker falls back to the stable aliases.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
widget.forkSourceId took precedence over the fresh/resume logic on
EVERY (re)bind, so /clear in a fork pane re-forked the original
conversation instead of clearing, and /resume re-forked the same way.
The source is now copied into one-shot pane state and cleared after
the first successful fork spawn; later respawns operate on the pane's
own session.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The session observed its child only via stdout. Two failure modes:
with --verbose the CLI chats on stderr, and an undrained 64KB pipe
blocks the child mid-turn with zero diagnostics; and nothing watched
the exit code, so a crashed process just looked thoughtful forever.
ClaudeStreamJsonProcess now drains stderr from construction into a
bounded tail buffer, and StreamJsonSession watches exitCode: on death
it flips busy off, clears any unanswerable pending prompt, and emits
a SessionEnd (exit code + stderr tail) — replayed via session.end for
late binders. The pane reports the exit in its status line and logs
the stderr tail; a deliberate dispose suppresses the watch so /clear
and teardown don't read as crashes. Test fakes extend the process
base instead of implementing it, so its defaults carry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Raise the declared minimums in pubspec.yaml to what our deps already
require: Flutter >=3.35.0 / Dart >=3.9.0 (was 3.19.0 / 3.5.0). alchemist
0.12 needs Flutter 3.32; Dart 3.9 first ships in Flutter 3.35, so 3.35 is
the binding floor. Pin the exact build toolchain in .fvmrc (Flutter
3.44.1).
Moving to the Dart 3.9 language level switches `dart format` to the new
"tall" style and enables two new lints. This commit is the resulting
mechanical churn, isolated from any behaviour change:
- whole-tree `dart format` reformat (tall style)
- `dart fix` for unnecessary_underscores + use_null_aware_elements
No runtime behaviour change; `make test` green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude_pane.dart had no widget-test harness, so the T-269 wiring (primary
rebind on workspace switch, host tab-reset) shipped uncovered — and the
moment any test mounts ClaudePane its 230 lines enter the coverage
denominator, which is what sank the gate earlier.
Add a real harness: a fake ClaudeSessionOrchestrator (no real `claude`
process) plus a connected fake IPC answering files.root, with spawn/respawn
driven inside tester.runAsync so the transcript-probe File I/O resolves.
Covers spawn-binds-to-repo, in-place rebind, same-repo no-op, send, /clear
(in place), /fork, /resume (picker open + cancel), mode cycle, draft
round-trip, init-event status, can_use_tool prompt swap, secondary spawn +
dispose-close, tap-to-focus, and the disconnected-daemon error path.
claude_pane.dart 25% -> 84%; session-host tab-reset covered too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>