feat(tui): subagent operation surface — notification visibility, list, human input (#37 #38 #39) - #43
yixiao5428 wants to merge 31 commits into
Conversation
#38 #39) Subplan agentmgmt-1-tui-subagent-surface: three commits on one branch, ordered #39 (notification visibility) -> #38 (subagent list via down-chain) -> #37 (human -> subagent input). TUI only. Key decisions: notification identified via 2-field part metadata (kind/summary, I2); history-exhausted state via history.atLive() + callback (I3); identity resolution stays single-owner in AgentInbox.deliver via sender-kind union, enforced at the schema layer by a parts-only HTTP payload (I1). Baseline for all line refs: dev @ bf8ec3d. Co-Authored-By: Claude <noreply@anthropic.com>
Step 0 of workflow §6.2: 10-section expectations extracted from the subplan contract doc, before any implementation. Notes the no-framework substitution for property-based invariants (example-based, INV-1..5) and marks the dedicated contract_audit script N/A pending a second subplan. Co-Authored-By: Claude <noreply@anthropic.com>
…ice (#39) Completion/failure notifications were synthetic-only text parts, so the UserMessage renderer filtered them into invisibility — the one subagent message users most need to see was the only one they could not. inject() now attaches {kind: "agent_notification", summary} metadata to the notice part; the TUI renders one muted line from it and never parses the model-facing envelope text (INV-2). Parts without the metadata — every pre-feature transcript — render exactly as before (Step P13). The four existing synthetic filters are untouched: a notice is not user input, so exclusion from undo aggregation and history recall stands. Co-Authored-By: Claude <noreply@anthropic.com>
Subagent navigation was a blind cycle: one key jumped to the first child, left/right cycled siblings with no overview. The chain now falls through naturally — down moves the cursor, then walks prompt history, then lands on the live input, and one more down opens the list. history.move() cannot express "history exhausted" (at the bottom it returns the live item, truthy, so its !item branch is dead going down), so the store gains atLive(). prompt.history.next hands the keystroke to the new onHistoryNextAtBottom callback only when the line is empty and the cursor is on the live input; unconsumed, it reaches the same no-op move() always produced there. With no subagents the keystroke stays with the editor. The list is the view's subtree minus the root and the current session, direct children first, grandchildren indented; rows show the instance name (or agent type, or the title's @type suffix) with type and live status. Membership is a pure exported function tested without rendering; the dialog is a thin DialogSelect wrapper whose pick reuses enterChild. Co-Authored-By: Claude <noreply@anthropic.com>
…idelity Three real-app tests drive the actual keymap and headless renderer through the down chain (list opens at exhausted history; stays with the editor without subagents or with text in the input). They boot the full app and navigate into the session route, but the stubbed projection is not yet faithful enough for the view to paint, so they are skipped with the exact blocker and a verified seeding recipe (real server + bun:sqlite child rows) documented in place — flip the skips once the view paints. Also records the compensating verification: the real-server projection was confirmed field-for-field against what subagentListMembers consumes. Co-Authored-By: Claude <noreply@anthropic.com>
…mt-1 Behavioral closure for what automation cannot reach (live keystrokes, visual layout): per-commit checklists — notification visibility (#39), down-chain and list contents (#38), human-to-subagent input and identity non-rewrite (#37, post-implementation) — each with expected outcomes and a result table to backfill. Referenced from the audit Step 5 record. Co-Authored-By: Claude <noreply@anthropic.com>
…e items Visual truncation of the notice line (#39), in-dialog key handling and existing navigation-key regressions (#38), selector hiding, mid-run message delivery, file parts through the new payload, and the permission-hoisting known limitation (#37). Also scopes the checklist away from #35's known outstanding issues. Co-Authored-By: Claude <noreply@anthropic.com>
2.6: the cursor-end guard only jumps to the end when already on the last visual line; on a multi-line input elsewhere, down moves a line down. 3.3: the list shows type and status but not model — model identity is observed on the assistant message meta line instead. Co-Authored-By: Claude <noreply@anthropic.com>
Subagent sessions were read-only in the TUI: visible() demanded a root session. Opening the input naively would route the globally selected agent/model into createUserMessage, whose setAgentModel write-back would persist them over the subagent's own identity — the same P0 class #35 fixed for agent_send. The inbox now takes a tagged InboxMessage: "agent" carries agent_send and stop traffic through the existing render(); "user" carries the human's parts behind a fixed [Message from user] header. Both flow through one identity resolution — the target's own agent/model/variant, "default" folded away — so the rule that a delivered message never rewrites the target keeps exactly one owner (I1). The new endpoint accepts parts and nothing else: a pick, not an omit, because omitting from PromptPayload would still admit noReply, which persists a message without ever running the loop. The TUI wires the takeover through a Prompt prop that reroutes submission ahead of the shell/slash/prompt branches and hides the agent/model readout; the route injects it only for child sessions and calls the regenerated sdk.client.session.agentMessage. Old transcripts and the root-session path are unchanged. Co-Authored-By: Claude <noreply@anthropic.com>
Independent subagent audit over the full range diff: 0 critical, 0 unresolved; I1/I2/I3 verified line-by-line. Dispositions applied: design doc amended where the contract lagged the implementation (404 channel and routing query on the new endpoint, the dialog-stack guard, the list row fallback and description format, takeover delegating to the route with the editor-selection consequence); handler ops comment corrected (inline construction, die branch unreachable); user-path test gains the missing sessionID assertion. Test gaps that stay open (atLive, trigger conditions, render branch, HTTP layer) are recorded in decisions.md with their tracking path. Co-Authored-By: Claude <noreply@anthropic.com>
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
|
The following comment was made by an LLM, it may be inaccurate: |
…p wording Smoke of the published linux-x64 asset: sha256 matches, version string correct, embedded rg runs, /project and /agent 200, agent-message end-to-end 204 with the message persisted as a non-synthetic [Message from user] part. One correction the smoke surfaced: excess payload keys are stripped by Effect Schema defaults, not rejected — noReply cannot reach the inbox, but the mechanism is stripping, so D14 records the distinction (missing parts still 400s). Also logged the stale-server pitfall: first probe hit a Sep 17 zombie opencode holding the port, which masqueraded as the new build. Co-Authored-By: Claude <noreply@anthropic.com>
…erequisites Co-Authored-By: Claude <noreply@anthropic.com>
…onsolidation) Co-Authored-By: Claude <noreply@anthropic.com>
…l Task -> Agent) Co-Authored-By: Claude <noreply@anthropic.com>
Verification findings P1+P2 from the beta's manual pass. P1: the list becomes the single navigation surface. Members are the whole tree — Main (the root), every subagent, and the session being viewed, with the current one preselected via DialogSelect's `current` and tagged in its description; the root renders as "Main". The SubagentFooter loses its Parent/Prev/Next buttons (info line stays) and the up/left/right/ <leader>down bindings go back to the editor, along with the hidden commands and their handlers. The transcript hint now reads "<down> view agents". P2: subagent tool rows no longer label themselves "Task" — the tool was removed in #35 and the word must not resurface; the agent type leads the title, background keeps its marker. Commit 2's exclusion of the root and the "no subagents, no UI" behavior are overturned by verified usage feedback; design doc, expectations (INV-4) and the manual checklist §2 are amended accordingly. Co-Authored-By: Claude <noreply@anthropic.com>
From a grandchild, `parentID ?? id` yields the middle node, so the list shrank to "current + parent" and the parent wore the Main label — the two-level idiom rode along when the list went whole-tree. treeRoot() walks parentID to the parentless ancestor (cycle-safe, falls back to the seat itself when the chain is missing from the projection) and the dialog receives that as its root. Regression tests cover every seat, the broken chain, and unknown sessions. Co-Authored-By: Claude <noreply@anthropic.com>
The Agents list's only entry point was a gesture with no visible sign: down at the exhausted history. A muted one-line hint now sits above the textarea in the input box — key display follows the prompt.history.next binding — so the gesture is discoverable before it is needed. Co-Authored-By: Claude <noreply@anthropic.com>
The hint now gates on exactly the trigger condition — empty input at the live history position — so it hides while typing and while browsing history. The row keeps its height, so text toggling never shifts the layout. Co-Authored-By: Claude <noreply@anthropic.com>
The key renders bright against the muted rest (staying muted through a leader chord), matching the transcript hint's emphasis pattern. Co-Authored-By: Claude <noreply@anthropic.com>
…ters Verification finding P3. Each subagent row now carries its delegation blurb inline after the name (parsed from the session title's "(@type subagent)" suffix — the description the model gave when delegating, no new data), and at the far right a footer slot with token spend and elapsed time: live from creation while the agent runs, frozen to its work duration once done, ticking once per second while the dialog is open. The Main row shows its meters too. Three pure helpers (subagentDescription, formatListTokens, formatListElapsed) are exported and tested without rendering — the regex caught its own missing paren on the normal case. Co-Authored-By: Claude <noreply@anthropic.com>
…the dialog Verification finding P4. A panel now sits below the input with the same row anatomy as the down dialog — Main, blurbs, type/status/current, live token and elapsed meters — through one shared formatAgentRow owner. Rows hover-highlight and click-to-jump; the meters tick once per second while it is on screen. The panel appears from two tree members up and truncates past five rows with a tail pointing at the dialog, which stays the keyboard and filter surface. Verification finding P3 rode along earlier in this round: delegation blurbs parsed from session titles plus the same meters, all behind three tested pure helpers. Co-Authored-By: Claude <noreply@anthropic.com>
Verification findings P5, four items: The panel container aligns to the transcript text column on the left and the input's inner right edge on the right. The session being viewed is highlighted — a primary-colored title behind a leading dot, matching the dialog's selection — and the "current" text tag is gone from the row builder, highlighting belonging to each surface. The input's top-right "down view agents" hint is deleted; the persistent panel replaced it. The token readout converges on one convention: a shared contextUsage helper (last scoring assistant message, percent of the model's context limit) is now the single owner for the footer, the dialog and the panel — the footer refactored onto it with its display unchanged, and the panel's session-aggregate path with its "tok" unit is deleted. Two numbers that look alike but meant different things was the whole bug. Co-Authored-By: Claude <noreply@anthropic.com>
Both regressions came from eyeballing instead of reading the frame: the panel guessed 5/3 padding where the input is actually left-border plus 2/2 inner padding, so the panel now mirrors that frame exactly and the two edges coincide. And the current marker's dot sat in front of the indented title, poking the label two columns out of line — the indent moves out of formatAgentRow's title into its own field, and both surfaces render it in a fixed-width gutter the dot shares, so no row ever shifts. Co-Authored-By: Claude <noreply@anthropic.com>
Third alignment regression, same root cause each time: the panel hand-rolled its row layout while the dialog shipped a second one, and hand-built padding never matches by construction. The dialog's actual structure — scrollbox padding 1/1, rows with conditional padding 1/3, gap 1, and an Option whose title carries its own paddingLeft — is now the only row renderer: DialogSelect.Option is exported and the panel renders it inside the same row container, with the indent and the current dot sharing the gutter. The panel's indentation is the dialog's by construction. Co-Authored-By: Claude <noreply@anthropic.com>
The route container already insets the bottom stack by 2 on each side, and the input's bottom hints line ends exactly at that container edge — but the panel stacked paddingRight 1 + 3 on top of it, leaving the meters four columns short of the "commands" hint. Right padding goes to zero at both panel levels; the left keeps the dialog's 1 + 1 so the gutter column is untouched. Co-Authored-By: Claude <noreply@anthropic.com>
Three refinements from verification: the dot is gone (the primary title already marks the current session), the leftmost glyphs align with the directory path on the hints line above (both at container+1 — Option's built-in paddingLeft of 3 cannot reach that column, so the panel stops reusing the dialog's row renderer and becomes a deliberately compact layout of its own), and a line of air separates the panel from the hints line. The design doc's single-renderer rule narrows accordingly: identical look must not have two renderers; a deliberately different look may. Co-Authored-By: Claude <noreply@anthropic.com>
…ecklist Co-Authored-By: Claude <noreply@anthropic.com>
Verification finding P6. The current-row dot is gone — the primary-colored title carries the highlight — and the rows lose their gutter, which lands the labels on the Filter "F" column (container 4) and the meters on the esc-tail column (width - 4). Option gains two additive options (dot, pl) so the built-in marker and the row's left padding are overridable per dialog; every other dialog keeps its behavior by default. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
… measured test The measurement test located the real cause of three alignment rounds: opentui ignores paddingLeft on <text> elements, so every padding-based calculation landed three columns off. The dialog's rows now carry the alignment as literal content spacing (3 spaces + tree indent + label), which always renders, and a new test renders the real DialogSelect and asserts the label column equals the Filter "F" column and the meters share one right edge — alignment is pinned by measurement, not arithmetic. Co-Authored-By: Claude <noreply@anthropic.com>
…spacing; measured test" This reverts commit 34879ab.
|
审过了。实现和流程都很扎实,有两处需要先处理才能合。 必须处理1. CI 确定性失败 ——
根因是本分支删掉了四个键位绑定且未新增任何绑定: - session_child_first <leader>down
- session_child_cycle right
- session_child_cycle_reverse left
- session_parent up那句底栏提示是从 两个平台的同一个分片同时失败,说明是确定性的,不是 Windows 抖动。 需要决定:让提示指向新的入口(历史翻完处的 2. 与 刚合的 #45(空仓库 unborn HEAD)动了同一批文件。 只有 实现上做得好的几处#37 的身份陷阱不但避开了,还避得比 issue 里建议的更彻底。 issue 里指出的风险是:TUI 的提交路径带的是 这里的做法是新加服务端端点复用
载荷类型里根本没有 agent/model 字段,不变量由类型强制而非靠约束,这比"记得别传"可靠得多。顺带把输入框上方的 agent/model 显示也隐藏了,理由("它不再描述提交会发生什么")是对的。消息扩成 #38 的 hook 正是 issue 里指出的那个"目前不存在的状态"。 #39 保留 流程完整:§6 v2 子计划文档 + 一处不阻塞的观察四个绑定的删除是有意的(注释追溯到验证发现 P1:导航收敛到 Agents 列表)。收敛入口本身合理;顺带记一笔:「按 |
Subplan
agentmgmt-1-tui-subagent-surface: the three TUI gaps after #35, one branch, three commits. Closes #37, closes #38, closes #39 on merge.Commits
Process artifacts
Verification
🤖 Generated with Claude Code