chore(gui): mission work logs — cordis design finalization, tool-card wire archive, incident records chore: missions chore: missions chore(gui): mission ledger — batch-2 answers, parallel dispatch state, jsdom coverage re-scope chore(gui): ledger — night-mode standing orders (self-commit small, no push, 5-min refresh) chore(gui): ledger — jsdom batches 2-4 landed (233 green), coverage probe next chore(gui): ledger — web-ui coverage probe 65%, four-tier fill plan approved chore(gui): ledger — cordis-impl B1 state after third API drop, decisions on file chore(gui): ledger — 01:32 patrol snapshot (jsdom tier-1 landed, coverage-fixer probed) chore(gui): ledger — 01:37 patrol (peer src trio landed, coverage-fixer still silent) chore(gui): ledger — 01:42 patrol (jsdom tier-2 landed, peer committed x2, coverage-fixer 2nd probe) chore(gui): ledger — coverage diagnosis complete (6-file gap list), web-ui at 91.4% chore(gui): ledger — 01:46 patrol (B1 done, jsdom tier-3 landed, coverage fix batch running) chore(gui): ledger — 01:51 patrol (jsdom tails x2 landed, B2 underway) chore(gui): ledger — 01:56 patrol (gateway.ts 337 lines, checkpoint T-7min) chore(gui): ledger — hold/pending split ruling, coverage-fixer externalize-or-restart ultimatum chore(gui): ledger — 02:01 patrol (B2 done, jsdom final arms, coverage ultimatum pending) chore(gui): ledger — 02:03 checkpoint executed (fixer2 respawn, four lanes released, three owners cold-started) chore(gui): ledger — all six lanes acked, type isolation first live proof (client closure clean) chore(gui): ledger — 02:08 patrol (all seven lanes active, wire carrier assembled) docs(gui): respond-design task checkpoint — apiproxy wire-layer recon done chore(gui): ledger — P0-2 contributor AGENTS.md landed (dd28a5019) docs(gui): respond-design checkpoint 2 — host-side recon (stub respond, frame types, approval seam, ACP answerer precedent) docs(gui): OOP debt inventory — seven territories, 2 real debts (createApiProxy, createFixtureApi), rest ruled keep-as-is docs(gui): disambiguation note on the archived i18n design task chore(gui): ledger — 02:12 patrol (exclude removed, mixed-knife incident under reconciliation) chore(gui): ledger — 02:15 wave (jsdom mission closed, OOP audit done, B3 isolation proof, mixed-knife resolved) chore(gui): ledger — 02:17 patrol (attribution reversal filed, arch-session probed) chore(gui): ledger — 02:22 patrol (B4 done, B5+B6 merged batch, arch-session deadline set) docs(gui): respond-design checkpoint 3 — client-side recon (pending map, PendingCard onRespond stub, AbstractApiClient.respond ready, bootHost missing approval mounts) docs(gui): peer carrier territory review — 2 fixes (SSE cancel leak, route-reservation guard), 1 ruling ask (RPC-log visibility), compliance ledger chore(gui): ledger — 02:27 (arch-shell respawn, territory review verdicts routed, ask-deny finding flagged) docs(gui): P1-5 respond design page complete — pending registry, wire answerer, client state machine, first-wins arbitration chore(gui): ledger — 02:31 patrol (respond design complete, B5 wire smoke green, shell knife 1 underway) docs(gui): respond design — add §0 status warning (web host ask defaults to deny), mount-behavior delta, no-timeout ruling with Config discipline chore(gui): ledger — 02:42 patrol (respond line closed pending review, B7 last piece underway) docs(gui): respond design contract review — direction pass, 2 doc fixes (settle-order contradiction, answering-state race), A/B/C compliance ledger docs(gui): respond design — contract review fixes (R1 verify-before-delete arbitration, R2 answering+resolved-frame transition, ask dual-source wording, rejected-is-ok-value note) chore(gui): ledger — 02:46 patrol (respond line final, two user decisions distilled, arch-shell deadline) docs(gui): respond review addendum — contract-gap ruling: approve plan A (ApprovalRequest.id), wire unchanged, drop plan B backscan chore(gui): ledger — cordis B7 summit: real-browser 10/10 green, user acceptance criterion proven docs(gui): respond design — contract gap #4 approved as plan A (ApprovalRequest.id), backscan fallback retired, blade 0 prepended docs(gui): respond design — final polish (owner-approval vs user-go-ahead wording, implementation handoff notes) chore(gui): ledger — 02:51 (shell-exec third respawn with operational script, respond line 4-knife final) chore(gui): ledger — 02:53 wave (respond five-knife true final, B7 closed 12/12, client.ts green) chore(gui): ledger — 02:56 patrol (cordis closeout bounced pending R1/R2/N1, shell-exec first sign of life) chore(gui): ledger — 03:01 patrol (R1/R2/N1 remediation in flight across four files) chore(gui): ledger — cordis line officially closed and archived, verified on disk (24 knives, 12/12, reviews closed) chore(gui): ledger — 03:06 patrol (shell-exec final window, lowered first-knife bar) chore(gui): ledger — 03:11 patrol (shell line iced-broken: three registries on disk) chore(gui): ledger — 03:15 patrol (quiet window, both active lanes within threshold) chore(gui): ledger — 03:20 patrol (shell five files up, api-proxy plan reported) chore(gui): ledger — 03:25 patrol (shell migration in flight with history-preserving moves, webserver green) chore(gui): ledger — 03:30 patrol (shell knife-1 in verification, api-proxy patching) chore(gui): ledger — shell knife 1 accepted (694cecc53), knife 2 released chore(gui): ledger — 03:40 patrol (knife 2 pre-move stage, cold-list spec appears) chore(gui): ledger — 03:45 patrol (rpclog moves staged, api-proxy two specs in flight) chore(gui): ledger — 03:54 patrol (knife-2 code done, coverage full-run final check) docs(gui): coverage-fixer task ledger — fixer2 takeover, per-file fix log, isolated reportsDirectory pitfall chore(gui): ledger — coverage lane closed and accepted (a19f069a5), the PR #443 CI fix knife chore(gui): ledger — 04:04 patrol (knife-2 calibration, sole active lane) chore(gui): ledger — shell knife 2 accepted (f8fb77b95), knife 3 released as final night task chore(gui): ledger — 04:19 patrol (knife-3 past half: callback chain through, ToolCallDetail up) chore(gui): ledger — night closeout summary: seven lanes closed, wake-up decision sheet chore: missions chore: missions chore: missions chore(gui): mission-local browser/probe verify scripts under missions/scripts/ The six acceptance/probe scripts move here as mission-side working material (headers and relative imports adjusted for the new location): carrier-errors, rpclog-panel, session, session-real, webserver-backpressure, webserver-hardening. chore(gui): verify-relocate mission log chore(gui): verify-relocate mission log — R1 guard addendum chore(gui): gates-continue mission log — CI-equivalent sequence all green chore(gui): VS Code 扩展体系双边设计调研报告 chore(gui): 调研追加 4.5 节——git 扩展数据面与 scope 绑定 docs(gui): web plugin system RFC — walkthrough + design notes docs(gui): RFC — restore existing SSE/POST as the v1 transport; envelope rides on it (D16) docs(gui): RFC — envelope demoted to chan-dispatch, scope out of envelope, rpc-log cut, peer deferred, scope tree is native cordis (D17-D21) docs(gui): RFC — hooks re-derived from component needs: useWatch/useAction only, useService removed; sessionHub cut, projections user-space, router rename, loader-only root (D22-D25) docs(gui): RFC — drop stale fork vocabulary (vendored cordis has Fiber only; scope = mintScope pattern), hook idempotence contract (D26-D27) docs(gui): RFC — session precision seam: plugins read scope key (host paradigm), React gets it from tree position via SlotOutlet (D28) docs(gui): RFC — domain hooks owned by plugins over framework primitives; useConversation paradigm carried over (D29) docs(gui): RFC — ctx services are the inter-plugin API (cordis proper); declarations are wire-only; get(id) returns scoped ctx (D30) docs(gui): RFC — full ctx.conversation walkthrough: root-singleton scope-sensitive service, caller-ctx scope key, get(key) as scoped ctx (D31) docs(gui): RFC — no client-side agents collection: session state machine already expresses the duality; agent resolution stays host authority (D32) docs(gui): RFC — v1 stays session-precision, no agent-level isolation; incarnation/agent-axis designs archived in ledger (D33) docs(gui): RFC walkthrough — full rewrite to final state (D16-D33 consolidated), end-to-end chain restored docs(gui): RFC — apiproxy demoted to generic channel routing; domain RPCs dissolve into owner plugins (D34) docs(gui): RFC — TS-interface-first wire contract (zod internal), conversation owns the dialogue frame with pluggable views (D35) docs(gui): RFC — page skeleton (sidebar+conversation), projects as plugin not service, nested slots via owner registries (D36) docs(gui): RFC — SlotMap declaration-merging slot model: single register API, inject-as-ownership, FC-typed registration, typed outlets (D37) docs(gui): RFC — slot props whitelist: identity, display params, materialized snapshot slices, stable UI callbacks (D38) docs(gui): RFC — end-to-end data flow: three transforms, equality protocol table, immer placement; i18n/theme kept standard (D39-D40) docs(gui): RFC — full external-injection model: props carry values + stable injected hooks; shared/client/react example rewritten (D41-D43) docs(gui): RFC final trio — modules.md (agent implementation spec), architecture.md (human walkthrough), plugins.md (business plugin inventory) docs(gui): RFC — props three-source merge (scope-standard useSession auto-injected); keyed key vs list id disambiguated (D44) docs(gui): RFC — inject comment says what it is (the React-facing props bundle); SessionHandle rename; snapshot-production story unified on buildSnapshot docs(gui): RFC architecture — full React component tree walkthrough: props three sources, slot vs plain children, hook taxonomy per node docs(gui): RFC — module map finalized (ui-slots/web-react/connection/runtime/ui-*/web); slots onChange replaced by cordis events; toolcall dimension; detail sidebar default-collapsed with toolName-keyed routing docs(gui): RFC plugins — openDetail relay chain: toolcard calls chat-view injected action, chat-view relays to conversation sidebar chore(gui): progress ledger — full archive rewrite: RFC outcome digest, open gaps, dispatch plan, cold-start entry docs(gui): RFC grill pass 1 — SlotScope axis (root/session) on declares, Gate dependency inversion, inject handle by scope, W5 acceptance list, gantt relay chain fixed docs(gui): figma analysis — sidebar/projects/sessions 区域交互视觉理解报告 docs(gui): figma 解析报告 — details 面板/多视图 tabs/未来功能区盘点 + slot 需求清单 docs(gui): figma 对话主区解析报告 — 消息流/tool calls 变体/审批接管输入框/Header tabs/视觉 token docs(gui): plugins.md rewritten from figma analysis — three-column layout, full slot reservation table, selection channel, composer-takeover approvals, phased scope docs(gui): layout dynamics ruled (drag+collapse both rails, details yields first, composer swap-panel, same-component transition); toolviews promoted to named scope-aware registry docs(gui): P-I scope locked (details minimal, dual theme, chat-view, custom toolview sample); teammate dispatch plan — 6 owners by package, dependency-driven waves, contract arbitration docs(gui): P-I api-contracts (full inter-package API spec) + dispatch plan (T0 skeleton knife, 7-dev roster, task briefs, milestones) docs(gui): api-contracts v2 — scope tree in P-I, bundle loader + per-plugin CSS isolation in P-I, agent-scoped toolviews live, zustand engine, renames (SessionProvider/ObservableSnapshot/SessionBinding), router owns all shell view-state docs(gui): services roster + progressive loading (no blocking loadAll), SlotsService as real cordis Service, renderSlot/renderSuspenseSlot duo, ui-traj teammate docs(gui): loading-chain gaps ruled — dev=rebundle no HMR, ui-primitives package, externals on globals (no import map), host injects __DSH_BOOT__ into HTML (zero round-trip) docs(gui): api-contracts v3 + dispatch v2 final — 12 packages, services merged in, progressive loader, global externals, __DSH_BOOT__ injection, 8-dev roster with convo split and ui-traj docs(gui): v3 amendments — router renamed ctx.layout, ui-trajectory has no service (pure consumer sample), wait-for-settled loading (no Suspense in P-I, ledger 6b) chore(gui): progress — pre-compact final state: v3 revision chain, 8-dev roster, T0 procedure, doc authority order docs(gui): authority banners — modules/architecture get v3 term-mapping headers, walkthrough marked as archived process doc docs(gui): cssdesign token set is THE theme source (--dsw-* variables, data-ds-dark-theme switch); recorded in contracts + progress docs(gui): architecture.md full v3 rewrite — loading chain, 12-package map, service roster, slot/inject/toolviews, data flow, component tree, perf model, all current docs(gui): contracts — UI plugins are dual-entry host plugins (node half serves client asset via ctx.webPlugins; __DSH_BOOT__ derives from it; client-closure gate back in scope) docs(gui): contracts — closure-factory bundles with DI require (no globals), package.json dshWeb declarative discovery (no serve ritual), create-then-send empty state with project picker, ancestry() for breadcrumb, unload stubbed until HMR, props.renderSlot confirmed docs(gui): dshClient declaration (inject/platform/immediately, exports./client), closure-DI require loading — synced across contracts/dispatch/modules/architecture/walkthrough chore(gui): progress — record final loading-chain rulings (dshClient declaration, closure-DI require, startSession) before compact docs(gui): architecture.md — developer-facing whole-web architecture on master baseline 6b16a67cb: what exists, what is new, no process narrative docs(gui): architecture.md — self-contained whole-web architecture: absorbs still-valid substance from the branch RFCs (host layering, four-quadrant RPC, object layer, testing tiers) under the new plugin system as the override chore(gui): progress — final pre-compact snapshot: contracts digest, apiproxy purity ruling, T0 procedure with first-action list docs(gui): api-contracts v3 §3.1 — apiproxy purity principle with three-way existing-code verdicts docs(gui): api-contracts v3 — immediately reinterpreted as static-infra group (8-package dshClient scope, boot manifest reconciliation) docs(gui): api-contracts v3 — immediately corrected to early-load dynamic group (prod shell must not rebundle); loader shell-held; bundles register their export surface into module table docs(gui): architecture — align with immediately=early-load dynamic group ruling; loader shell-held; module-table registration of loaded bundles docs(gui): T0 checklist — 12-package skeleton table, 4-cut sequence, mv/attic/rewire rules (pre-drafted, awaiting go) docs(gui): t0-checklist — pin figma-flows findings (missing font-family base vars, three alias vars behind upstream) docs(gui): dispatch v2.1 — drop cordis-web salvage wording, two-wave staffing, loader/immediately boundary updates docs(gui): progress + t0-checklist ledger — T0 landed, staffing status, execution accounting docs(gui): api-contracts v3 — arbitration round 1: renderBody deps, RootBindingProvider, flush default sync, prune current, loader subpath, config-source P-I bar docs(gui): v3 §3.2 connection 导出清单附录(rt-core 对账)+ rt-core 实现计划档案 docs(gui): progress — T1 milestone, arbitration round 1 ledger, fw-react timeout escalation docs(gui): progress rolling update — per-line battlefield state at 00:2x, mailbox-vs-contract lesson, small-batch discipline reinforced docs(gui): fw-react notes — v3 §2 complete, seven knives, T1/T2 follow-ups docs(fw-slots): archive — four packages landed, open tails logged docs(gui): progress — framework layer complete (web-react five, fw-slots four packages), T2 gated on rt-core runtime knife only docs: api-contracts docs: style-spec docs docs(gui): tsconfig convergence ruling — no host.json, root resumes host-aggregate duty, typecheck = root + client aggregates docs(gui): missions 根三份 07-18 世代档案加「已被取代」头注——指向 web-plugin-rfc 现行权威并注明新旧对应 missions docs(gui): progress rewritten for post-closeout state — wave ledger, architecture finale, teammate roster with handover notes, pending-user-command queue
23 KiB
core 能力面 × 契约 v1.3 覆盖度盘点
2026-07-19 起盘(分批落盘中)。方法:逐包读 core 源码 types/service 面(file:line 为证),对照 design.md v1.3 标注四态:已覆盖 / 有意不做(引拍板)/ 接缝已留(说在哪)/ 漏判(需新裁决,汇总见文末清单)。 背景:rpc-compare 发现契约漏 subagent 谱系,根因是当初只按 UI 需求反查 core、未做系统盘点;本文件补这道工序。
1. session 域(packages/core/session)
1.1 SessionEventMap 全类型表(types.ts:180-252)
| 事件 | core 事实 | 契约状态 |
|---|---|---|
turn/start / turn/end |
types.ts:187/193,turn 边界 + TurnTrigger/TurnEndReason(merge-extensible,types.ts:79-124) | 已覆盖——mux session/event + history 纯透传(design §3.3 透传纪律) |
step/start / step/end |
types.ts:195/197 | 已覆盖(同上透传) |
user/message |
types.ts:199 | 已覆盖(透传;client fold 消费) |
prompt/blocked |
types.ts:204,veto 的 durable 记录 | 已覆盖(透传)。UI 是否渲染是 client fold 决策,不是契约缺口 |
context/message |
types.ts:212-217,含 meta(模型不可见 durable JSON) |
已覆盖(透传) |
assistant/chunk |
types.ts:219,token 级 | 已覆盖——「token 流即事件流」(design §3.3) |
assistant/message |
types.ts:226,含 usage?: TokenUsage |
已覆盖(透传)。usage/token 统计随之免费到达 client,无需独立统计接口(§7 LLM 层回引此行) |
tool/call / tool/result |
types.ts:232/242;result 带 meta?: unknown(tool 私有 presentation 载荷) |
已覆盖(透传);render intent(presentCall/presentResult)有意不做——design §3.3 tool presentation 拍板「先透传,additive 附件帧留座」 |
steering/message |
types.ts:244 | 已覆盖(透传) |
todo/write |
types.ts:246,全量快照、last-write-wins、log-only | 已覆盖(透传);client fold 照 last-write-wins 折即可,无需独立 todo 接口(§6 回引此行) |
request/header |
types.ts:251,EpochHeader 快照(config/system/tools/messagePrefix) | 已覆盖(透传)。UI 可从中读 provider/model 现值变化 |
| merge-extensible 扩展键 | types.ts:180 map 声明 | 已覆盖——design §3.3:client fold 对未知 type documented-default,schema 留「合法信封+未知类型」分支 |
1.2 Session/SessionStore 服务面(index.ts)
| 能力 | core 事实 | 契约状态 |
|---|---|---|
session/created / session/disposed 事件 |
index.ts:47/57 | 已覆盖——HostFrame host/session-added/removed(design §3.3) |
session/event 追加流 |
index.ts:69 | 已覆盖——mux 的源 |
session/flush 检查点 |
index.ts:79 | 有意不做(不对客暴露)——durability 是 host 内部事务,client 只见已落地事件 |
store.get/list |
index.ts:818/826(live only) | 已覆盖——sessions.list(live+冷合并的持久化清单,v1 mtime 三字段拍板) |
SessionHeader.parentSession + seedLength |
types.ts:47/52;fork 时写入 index.ts:854-855 | 漏判 →【L1】——谱系在 core 持久化面存在,契约 SessionSummary/HostFrame 均未携带 |
SessionHeader.cwd / createdAt |
types.ts:43/45 | 漏判 →【L2】——create 收 cwd 入参但 list/describe 均不回吐;createdAt 被 v1「mtime 即 updatedAt」拍板部分覆盖但非同一语义 |
SessionStore.fork() |
index.ts:843-857;SessionForkSource index.ts:546;错误码 SessionForkErrorCode index.ts:556-562(turn/end 边界约束 :890-895) | 漏判 →【L3】——core 有完整 fork 原语(opencode 也有 POST /session/:id/fork 对照),契约无 session.fork 方法 |
repair(interruptedTurnClosers) |
index.ts:25 导出;repair.ts;persistence 加载时闭合 crash 孤儿 turn(TurnEndReason interrupted,types.ts:120) |
已覆盖(间接)——修复产物就是 turn/end interrupted 事件,随 history 透传到达;修复动作本身是 host 内部行为,无需接口 |
surface(foldSurface/SurfaceOp/replace) |
index.ts:26-27 导出;types.ts:262-309(SurfaceOp append/replace,compaction 用) | 已覆盖——design §5「优先复用 core foldSurface」;replace 语义随事件透传,client fold 天然处理 compaction |
deriveMessages/requestHeader 折叠 |
index.ts:469/432 | 有意不做(server 不代折)——「历史=事件重放,client 单一 fold」拍板(README 拍板表) |
2. agent 域(packages/core/agent)
| 能力 | core 事实 | 契约状态 |
|---|---|---|
agent/created / agent/disposed |
types.ts:147/156(注册/注销时 emit) | 已覆盖——HostFrame added/removed 的 agent 侧对应(HostFrame 语义=「session 出现/消失」,v1 agent 与 session 同生命周期) |
agent/status(idle⇄running→disposed) |
types.ts:165;AgentStatus types.ts:47 | 已覆盖——HostFrame host/session-status running 布尔。三态压两态是拍板(冷 session 拍板:attach 不暴露,disposed 即 removed) |
agent/queued(入箱通知) |
types.ts:175 | 有意不做——prompt() 返回 accepted 即达意;入箱细节属 host 内部(CQRS:渲染靠 session 事件) |
| send/steer/cancel 原语 | types.ts:103/110/127 | 已覆盖——prompt(mode: queue/steer)、cancel 1:1 映射(design §3.1) |
inject(注入上下文不跑模型) |
types.ts:119 | 漏判 →【L4】——core 第三条输入原语,契约只映射了 send/steer;UI 场景(如「贴文件给 agent 但不触发回复」)v1 是否需要待裁决 |
whenIdle() |
types.ts:130 | 有意不做——client 由 host/session-status 事件驱动,不需要 promise 面 |
AgentRegistry.create/resume(CreateAgentOptions:sessionId/meta{cwd,parentSession,seedLength}/seed/agentOptions/setup) |
index.ts:44-90、:352/:371 | 已覆盖(部分)——sessions.create 走此路(cwd 已在契约入参);meta.parentSession/seed 是 fork/spawn 用的,与【L3】同源,client 侧 v1 不透出 |
AgentRegistry.get/list/roots/isOwnedBy |
index.ts:530/550/560+/543 | list/get 已覆盖(sessions.list + running);roots()/isOwnedBy(运行时归属树)漏判 →【L1】共同体——UI 要画 subagent 树需要谱系,见 L1 处置提案 |
initiator scope(withInitiator/initiator()) |
index.ts:288/:258;RFC 2026-07-15-agent-initiator-scope | 有意不做——进程内 AsyncLocalStorage 机制,本质不可序列化,不属 wire 契约;UI 需要的「谁创建了谁」由持久谱系(L1)承担 |
| 扩展 seam 事件(pre-step/prompt-submit/request/session-prefix/step-result/post-step/request-error/turn-continuation/turn-stop、agent/error) | types.ts:204-311 | 有意不做(对客)——插件扩展 seam,是 host 进程内 waterfall/serial 钩子;durable 后果已进 session log 透传(如 prompt/blocked、turn/end error)。agent/error 的无 turn 位置失败 →【L5】边缘:live-only 诊断无 session 事件时 client 不可见,待裁决是否要 stream/error 级 host 通知 |
3. subagent 包组(packages/subagent/*:spawn/fork/inprocess/subprocess/acp + tool-subagent)
| 能力 | core 事实 | 契约状态 |
|---|---|---|
| 子 agent 创建(spawn=白纸 / fork=继承 turn 前缀) | subagent/src/types.ts:52-101(StartRequest:parent 必填、读 parent.session.header 拿 cwd+stamp parentSession,:56-61);SubagentRun.id=子 session id、parentSession 记录 parent(:148-154) |
对 UI 的可观测面 = 普通 session:子 agent 就是 registry 里一个 live agent + store 里一个 session,mux/hostEvents 天然看得见。缺的只是谱系标注 →【L1】(host/session-added 无 parentSessionId,UI 无法区分「用户开的」和「agent spawn 的」) |
| 运行时 run 面(result promise/dispose/sendMessage?/resume?) | types.ts:148-185 | 有意不做——run 生命周期属 parent agent 的工具调用(tool/call task → tool/result),已随事件透传;client 不直接操纵子 run |
| stopReason / structured output | types.ts:109-141 | 已覆盖(间接)——结果进 parent 的 tool/result 透传 |
| ACP/subprocess 远程子 agent | subagent-acp、subagent-subprocess | 同上——远程 run 无本地 session;parent 侧 tool 事件已覆盖其可观测面;v1 不做远程子会话浏览(不做清单精神,未明文 → 盘点顺手补进 §6 不做清单措辞即可,不算漏判) |
4. ctx.tasks 后台任务(packages/tasks/tasks)
| 能力 | core 事实 | 契约状态 |
|---|---|---|
tasks.list/get/wait(TaskSnapshot:kind/label/status/detail/output/startedAt/finishedAt) |
index.ts:153/167/226/326 | 漏判 →【L6】——运行时全局后台任务注册表(bash 后台、subagent run 等挂在这),UI「后台任务列表」是常见诉求;但 v1 UI 范围未含此面板,处置建议偏「留接缝」 |
onTaskDone 完成通知 |
index.ts:283 | 同【L6】——若做任务面板需 HostFrame 或独立流;不做面板则无需 |
| 任务归属(owner: Agent、session-scoped 授权) | index.ts:44-48(TrackedTask.owner)、list(caller) 过滤 | 同【L6】附注:core 已有按 agent 过滤语义,接口若做可直接映射 |
5. 审批与问答(packages/ui/user-approval、user-interaction)
| 能力 | core 事实 | 契约状态 |
|---|---|---|
approval/request waterfall(待决问题推给 answerer 链) |
user-approval/src/index.ts:23-32;ApprovalOutcome :91(allowed-once/rejected/cancelled/unavailable,fail-closed) | 有意不做(v1)——design §6 不做清单明文「审批/问答域」。结构性事实需记录:这是 client→server 反向要答案的面,纯 mux 单向流装不下,将来要么复用 unary(poll/answer 方法对)要么加双向帧——接缝形态建议在拍板时一并定 |
approval/asked / approval/decided session 审计事件 |
index.ts:35-60(log-only,merge into SessionEventMap) | 已覆盖——merge-extensible 事件随 mux 透传(design §3.3 未知类型分支),UI 已可"看到"审批发生过;缺的只是"参与决定"(上行) |
approval/policy(ask/never,session 内覆写) |
index.ts:108 + SessionEventMap merge | 已覆盖(事件透传);改 policy 的命令面归审批域一并 v2 |
ctx.userInteraction.ask(AskUserQuestionRequest/Answer,单 provider 注册制) |
user-interaction/src/index.ts:43-71(registerProvider 单占 :96-107) | 有意不做(v1)——同上不做清单。附注:单 provider 语义 ⇒ Web client 接管问答时要经 host 侧代理 provider 中转(provider 在 host 进程注册、答案从 wire 上取),这决定将来接缝在 impl 不在契约新增语义 |
6. workflow / todo / skill / compact 可观测面速查
| 包 | core 事实 | 契约状态 |
|---|---|---|
| todo(packages/todo) | 唯一持久面 = todo/write session 事件(types.ts:246) |
已覆盖——透传 + client fold last-write-wins(§1.1 已列) |
| compact(packages/compact) | 产物 = surface replace 事件 + context/message(SurfaceOp types.ts:292-294) |
已覆盖——透传;client fold 处理 replace 即正确渲染压缩后视图(design §5 foldSurface 复用) |
| skill(packages/skill) | 装载产物 = context/message(skill 内容注入)+ tool/call 事件 |
已覆盖(透传);skill 目录浏览/管理面 v1 无 UI 诉求,有意不做(catalog 工具是模型面不是 client 面) |
| workflow(packages/workflow) | worker-thread 引擎;对 session 的可观测面 = 其 tool/call、tool/result + 子 agent session(同 §3) | 已覆盖(间接);workflow 进度独立流 v1 不做,与 L6 任务面板同性质 |
| guard(packages/guard) | loop-hygiene 插件,干预结果落 session 事件(steering/turn-stop) | 已覆盖(透传,无独立面) |
7. LLM 层(packages/llm)
| 能力 | core 事实 | 契约状态 |
|---|---|---|
| usage/token 统计 | assistant/message.usage?: TokenUsage(session types.ts:226,与消息同travel);request/header 里 config |
已覆盖——透传即达(§1.1 已列);聚合统计(session 累计 token)是 client fold 的算术,不需要 server 接口 |
| adapter 注册面(provider 现值) | LlmService 注册表;AgentOptions.provider/model(agent types.ts:21-26) | 已覆盖——host.describe 的 provider/model 现值(20:02 拍板);adapter 列表枚举(UI 下拉「可用 provider 有哪些」)漏判 →【L7】:describe 只给现值不给候选集,「模型切换」在不做清单但「枚举可选项」是它的读前提,处置建议留接缝 |
| 模型切换(运行中改 provider/model) | agent/request waterfall 可换 config | 有意不做——design §6 不做清单明文 |
漏判清单(已裁决,2026-07-19 21:13:L1/L2/L5 合入 design.md v1.5;L3/L4/L6/L7 类型预留 design §8;审批/问答提案整体采纳并入 §3.4)
| # | 缺口 | core 事实(file:line) | 建议处置 | 一句话理由 |
|---|---|---|---|---|
| L1 | subagent/fork 谱系不可见(已知条目):host/session-added 与 SessionSummary 均无 parent 信息,UI 无法画子 agent 树、无法区分用户开的还是 agent spawn 的 | SessionHeader.parentSession/seedLength(session types.ts:47/52);fork 时写入(session index.ts:854);spawn 时 stamp(subagent types.ts:56-61 REQUIRED parent) | 进 v1 契约。补法:① host/session-added 帧加可选 parentSessionId?: SessionId(从 session.header.parentSession 读,无谱系时缺省);② SessionSummary 同补 parentSessionId?(冷 session 列表也要能画树;jsonl 后端从持久化 header 读)。运行时归属(registry owner/roots,agent index.ts:543/560)不透出——durable 谱系已够 UI 用,运行时树是进程内概念 |
字段 core 已持久化、读取零成本;缺它 UI 树状视图无法做,且 additive 可选字段不破坏现契约 |
| L2 | session 元数据有入无出:create 收 cwd 但 list/history 均不回吐;createdAt 同 |
SessionHeader.cwd/createdAt(session types.ts:43/45) | 进 v1 契约(顺手):SessionSummary 加 cwd?: string。createdAt 不加——v1「mtime=updatedAt」拍板已覆盖排序诉求,再加是第二时间语义 |
多 session 不同 cwd 时列表页无法标注工作目录;一字段事,与 L1 同一次 SessionSummary 改动 |
| L3 | fork 无契约方法:core 有完整原语+类型化错误码,opencode 有同款端点 | SessionStore.fork(session index.ts:843-857);SessionForkErrorCode :556-562;turn/end 边界 :890-895 | 留接缝:v1 不加 session.fork(UI 无 fork 按钮诉求);接缝=将来 RpcMethodMap 加 'session.fork' 一行 + SessionForkErrorCode 并入 RpcErrorCode 按域扩展,零结构变化 |
形态 B 下加方法是纯 additive;现在加则要陪审 UI 交互(fork 点选择、边界约束提示)不值 v1 |
| L4 | agent.inject 第三输入原语无映射:prompt 只有 queue/steer |
Agent.inject(agent types.ts:119);idle 时一次性 turn 语义 types.ts:84-89 | 留接缝:prompt 的 mode union 将来加 'inject' 即可(merge 进闭合 union + impl 分发)。v1 Web UI 无「注入不触发回复」交互 |
三原语中 inject 是插件/自动化面(文件变更通知等 host 内部已在用);人机 UI 场景未出现,union 扩展零迁移 |
| L5 | 无 turn 位置的 live 失败 client 不可见:agent/error 在 session log 无对应事件时(如 flush 失败、驱动崩溃)UI 只能看到 session 卡死 |
agent/error emit(agent types.ts:311,"even when the error has no in-turn position") | 进 v1 契约(轻量):HostFrame 加 { type: 'host/agent-error'; sessionId; message: string }——只做诊断展示不做恢复语义 |
不加则「agent 停了但 UI 永远转圈」无解释渠道;一帧类型,Frame union additive |
| L6 | 后台任务注册表无接口:bash 后台/长任务在 ctx.tasks,UI 任务面板无数据源 | tasks.list/get/wait/onTaskDone(tasks index.ts:153/167/226/283);TaskSnapshot :326 | 留接缝:任务面板不在 v1 UI 范围;接缝=将来新 tasks 域(一域一文件 + RpcMethodMap 数行 + 完成通知并入 hostEvents 或独立流)。设计已天然支持新域(design §1「新域=新文件对+根接口一字段」) |
v1 UI 无此面板;域级 additive 是本契约的标准扩展路径,无需预留字段 |
| L7 | provider 候选集不可枚举:describe 给现值,UI「切换模型」下拉无数据源 | LlmService adapter 注册表;AgentOptions.provider/model(agent types.ts:21-26) | 留接缝:模型切换整域在不做清单,枚举是其读前提,一起进将来的 provider 域;不单独提前 | 只读枚举脱离切换动作无用户价值;避免半个域 |
| — | 审批/问答:形态已拍板(2026-07-19 21:0x,mux 下行帧 + HTTP unary 上行),协议提案见下节,随本清单一并裁决 | 见下节逐条 file:line | 本轮定契约形状,实现可排后 | — |
审批/问答域协议提案(已采纳并入 design.md §3.4;21:19 修订:QuestionAskId 取消、问题标识复用 RpcId——下文为原提案存档,以 design.md 为准)
已定:请求下行 = mux 控制帧(稳定 id + session 锚点);回答上行 = 普通 unary(respond 带 id 回传)。不做双向帧/双工通道——SSE 本就是 server→client,回送有 HTTP。
A. 方法与帧
// RpcMethodMap 增两行(key 按域单数 convention)
'approval.respond': ApprovalApi['respond']
'question.respond': QuestionApi['respond']
export interface ApprovalApi {
/** 回答一个待决审批。outcome 只收 client 可给的子集(cancelled/unavailable 是 host 侧结局)。 */
respond(input: { sessionId: SessionId; id: ApprovalRequestId; outcome: 'allowed-once' | 'rejected' }):
Promise<RpcResponse<{ accepted: true }>>
}
export interface QuestionApi {
/** 整批回答一次 ask(core 事实:一次 ask 多题一个 answer,user-interaction index.ts:63-67)。 */
respond(input: { sessionId: SessionId; id: QuestionAskId; answer: AskUserQuestionAnswer }):
Promise<RpcResponse<{ accepted: true }>>
}
// MuxFrame 增四帧(Frame 族,session 锚点)
| { type: 'approval/requested'; sessionId: SessionId; id: ApprovalRequestId; toolName: string; callId?: CallId; reason?: string }
| { type: 'approval/resolved'; sessionId: SessionId; id: ApprovalRequestId; outcome: ApprovalOutcome }
| { type: 'question/requested'; sessionId: SessionId; id: QuestionAskId; questions: AskUserQuestionItem[] }
| { type: 'question/resolved'; sessionId: SessionId; id: QuestionAskId; outcome: 'answered' | 'cancelled' }
- requested 载荷 = core 类型透传:审批帧字段即 ApprovalRequest 去 agent(换 sessionId 锚)去 signal(user-approval index.ts:190-212);问题帧直接透传
AskUserQuestionItem[](user-interaction index.ts:29-41,model 自带题内 id)。 - resolved 帧是收敛面:多 client 同看一 session 时,别人答掉/超时取消/policy 决掉,观察方靠 resolved 撤卡片;自己答成功也等 resolved 帧统一收敛(respond 的 accepted 只表示受理)。
B. id 纪律(按现行纪律推导)
- 审批:复用 core
ApprovalRequestId(已 brand,user-approval index.ts:76)——SessionId 同款先例:type-only import、id 全部源自 server(requested 帧),client 只回传。 - 问答:host 造
QuestionAskId(api 层新 brand:Branded<'question-ask-id'>)——core 事实:user-interaction 无 request 级 id(AskUserQuestionRequest 只有题内 model 自给的 string id,index.ts:29-33),host 代理 provider 受理 ask() 时 mint UUID。与 cursor 占位不同(那是未实现故不 brand),此 id 实装即进签名,按「opaque 跨界 id 必 brand」仓规上 brand。
C. 竞争语义
- 先到先赢,host 内存 pending 表是唯一裁判:一个 id 只被 settle 一次。竞争方:client respond vs
signalabort(tool 取消/step 中止 → cancelled,user-approval index.ts:206-211)vs 另一 client respond。policy'never'在 answerer 链之前解决(index.ts:100-108),requested 帧根本不发——天然对齐。core 无审批超时(signal 是唯一撤回通道),不发明。 - 迟到/重复回答:RpcErrorDetailsMap 增两码——
'approval-not-pending': { id: ApprovalRequestId }、'question-not-pending': { id: QuestionAskId }(分域两码不合一:details 类型不同,且按域扩展是既定纪律)。
D. 刷新恢复(推荐:subscribed 基线重放)
推荐:client 重开 mux 后,host 在每个 session 的 session/subscribed 帧之后立即重放该 session 仍 pending 的 */requested 帧(来源=host 内存 pending 表)。理由:单一事实源(全走 mux),与 lastSeq 补缝流程同构,client 无需第二条 bootstrap 路径做 join。不推荐 host.describe 带 pending 列表:跨 session 聚合 + 与流竞态,两处真相。
不能从 history 推 pending(审批虽有 approval/asked/decided 审计事件,但 crash 后 asked-without-decided 是永久悬案——pending 真相只在 host 内存,重放帧无此歧义)。
E. core 事实对齐(盘点批3 + 本批补核)
| core 事实 | file:line | 提案对齐 |
|---|---|---|
| approval 走 policy → answerer waterfall 链,fail-closed unavailable | user-approval index.ts:23-32、:100-108 | host 侧注册一个「wire answerer」进链:收 approval/request → 发 requested 帧 → 等 respond/abort → 返回 outcome。链上仍可有其他 answerer(组合语义不变) |
approval/asked/decided 已是 session 审计事件(log-only) |
index.ts:35-60 | 保持透传不动;帧是 live 控制面、事件是 durable 审计,职责分离不算重复造 DTO |
| ApprovalOutcome 四值闭合 union | index.ts:91 | resolved 帧透传全集;respond 入参窄化为 client 可给二值 |
| userInteraction 单 provider 注册制 | user-interaction index.ts:96-107 | host 代理 provider 是唯一注册者(盘点 §5 已注),多 web client 竞争在 wire 层由 pending 表裁决,不违单 provider |
| ask() 不落 session 日志 | user-interaction 全文无 SessionEventMap merge(本批 grep 核实) | 问答无审计事件可依赖 → requested/resolved 帧是问答唯一可观测面,D 的内存重放是唯一恢复路径(自洽) |
| 一次 ask 多题、整批回答 | index.ts:43-67 | respond 收整个 AskUserQuestionAnswer,不拆单题方法 |
汇总(裁决后):L1/L2/L5 已合入 v1.5(SessionSummary.parentSessionId?/cwd?、session-added 帧 parentSessionId?、host/agent-error 帧);L3/L4/L6/L7 类型已预留(design §8 完整签名,不进 map——fail loud 优于 not-implemented 兜底);审批/问答提案(下节)整体采纳并入 design §3.4。本文件转为盘点存档,后续契约变更以 design.md 为准。