feat(web): dock the state-grouped fleet beside the conductor's chat - #325
Merged
Conversation
P5.2, the centerpiece of the front-doors work (docs/conductor-frontends-design.md §4, §12). Focusing the conductor now renders its ordinary chat with the fleet board docked in a right rail. Classification lives in `lib/fleet-lanes.ts` as pure functions — the status vocabulary (§6) and the triage lanes (§4) ARE the design decision here, so they belong somewhere a test can reach without a reactive root. `FleetRail.tsx` is the renderer over `state/fleet.ts`. Two rules from §6 drive the vocabulary and are easy to get wrong: **`awaiting` outranks `working`.** A node that needs a human must never hide behind one that is merely busy, so it is checked before the task's own status and sorts first. A worker wedged on an approval reads as `awaiting` even while its task still says `running` — the dispatch status describes the QUEUE, the session status describes the AGENT, and it is the agent that is stuck on a person. **`disconnected` is not `failed`.** A dropped runner is a transport event; the dispatcher reconciles it. Colouring it red trains the operator to ignore red, so it renders quiet grey. A claimed spawn that has not created its worker yet is `queued`, not `disconnected` — otherwise every healthy dispatch flashes a fault for a moment. "Ready to review" is deliberately narrow: a finished task that produced a digest, i.e. something a human can actually read. §4 envisages this lane eventually carrying worktree diffs and sequenced merge (P5.4); until then, a review lane holding nothing readable would be worse than none. Empty lanes are omitted — four permanent headers over one running task is chrome, not information. Per §3.B the rail links out and never embeds a second copy of a session's chat: every node is a real session, so clicking one focuses its ordinary cockpit. "Take over" is the existing attach flow reached from a node. A task with no session to drill into renders as a plain row rather than a button that would do nothing. `SessionBody` is extracted so the conductor branch docks a rail beside the SAME cockpit every agent gets, rather than reimplementing it. Verified against a live conductor, not just fixtures: a real board of 5 tasks classified correctly — the two tasks blocked by the old daemon-collision bug landed in "Needs you" with their real error, the three successful dispatches in "Ready to review" with digests, and needsYouCount matched agg.blockedTasks. That run also confirmed a finished task has NO worker session (the dispatcher tears it down after the digest), so the session join returns null and the row falls back to the task id — handled, and the reason `done` is classified before the null-session branch. 16 lane tests; 215 web tests total; typecheck, lint and build clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 6, 2026
rsharath
approved these changes
Sep 6, 2026
saucam
added a commit
that referenced
this pull request
Sep 6, 2026
conductor-design §3 specifies that the fleet READ verbs — fleet_list, fleet_find, fleet_summary, fleet_recall, fleet_tasks, machine_map — "run silently". They did not. `isSafeTool` knew the memory and blackboard mounts but not the fleet, so in guarded mode every one of them raised an approval prompt. Found by driving a real conductor rather than reading the code: asking it to resolve a session reference produced an approval request for `fleet_find`, which is a read. An assistant that asks permission to look something up is not an assistant, and this is the first friction anyone meets in the new conductor pane (#325). The daemon already put these verbs in the provider's `allowedTools`, so the SDK warned it would auto-approve them — but codeoid's own gate is consulted independently and did not recognise them, which is why the two disagreed. Follows the established shape exactly: match the namespace prefix, then require the suffix to be a known read verb. Never a bare prefix match — an over-broad match here is a prompt bypass, which is why a look-alike segment (`mcp__evil_codeoid_fleet__…`) is asserted to gain nothing. The read list is `FLEET_READ_TOOLS` from the shared protocol package, so the send half cannot leak in by someone editing one of two copies. Send verbs remain hard-gated BEFORE this function is consulted (`isFleetSendTool` in Session#shouldAutoApprove, checked ahead of any mode logic); this is defence in depth, not the fence. Verified live, both directions. With the fix, fleet_list / fleet_find / machine_map go straight to `executing` with zero prompts; fleet_spawn still lands in `waiting_confirmation` and waits for the owner. The R3 invariant is intact. Also lists the bare (non-`mcp__`) namespacing so a mounted fleet (#245) does not silently regress to prompting on every read. 10 tool-safety tests; 2452 daemon tests; typecheck and lint clean. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
P5.2 — the centerpiece of the front-doors work (conductor-frontends-design.md §4, §12). Focusing the conductor now renders its ordinary chat with the fleet board docked in a right rail.
Shape
Classification lives in
lib/fleet-lanes.tsas pure functions — the status vocabulary (§6) and triage lanes (§4) are the design decision here, so they belong somewhere a test can reach without a reactive root.FleetRail.tsxrenders overstate/fleet.ts(#324).Two rules from §6 that are easy to get wrong
awaitingoutranksworking. A node needing a human must never hide behind one that's merely busy, so it's checked before the task's own status and sorts first. A worker wedged on an approval reads asawaitingeven while its task still saysrunning— the dispatch status describes the queue, the session status describes the agent, and it's the agent that's stuck on a person.disconnectedis notfailed. A dropped runner is a transport event the dispatcher reconciles. Colouring it red trains the operator to ignore red, so it renders quiet grey. Relatedly: a claimed spawn that hasn't created its worker yet isqueued, notdisconnected— otherwise every healthy dispatch flashes a fault for a moment.Two deliberate narrowings
"Ready to review" means a finished task with a digest — something a human can actually read. §4 envisages this lane eventually carrying worktree diffs and sequenced merge (P5.4); until then a review lane holding nothing readable would be worse than none.
Empty lanes are omitted. Four permanent headers over one running task is chrome, not information.
§3.B — the rail links out, never embeds
Every node is a real session, so clicking one focuses its ordinary cockpit; "take over" is the existing attach flow reached from a node. A task with no session to drill into renders as a plain row rather than a button that would do nothing.
SessionBodyis extracted so the conductor branch docks a rail beside the same cockpit every agent gets, rather than reimplementing it.Verified against a live conductor
Not just fixtures — I drove a real dispatch and classified the resulting board:
The two tasks blocked by the old daemon-collision bug (#319) land in Needs you with their real error; the three successful dispatches land in Ready to review;
needsYouCountmatchesagg.blockedTasks.That run also surfaced something worth knowing: a finished task has no worker session — the dispatcher tears it down after the digest — so the session join returns null and the row falls back to the task id. Handled, and it's why
doneis classified before the null-session branch.Verification
16 lane tests, 215 web tests total, typecheck / lint / build clean.
Component rendering itself still isn't locally testable (the Node <20.19 jsdom gap), but CI runs those suites.
Next
P5.1's remaining piece is the Conductor ⇄ Sessions toggle (§3.A); P5.3 is the cross-session "Needs you" inbox, which this rail's
needsYouCountalready feeds.🤖 Generated with Claude Code