0681ac47de
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
171 lines
23 KiB
Markdown
171 lines
23 KiB
Markdown
# 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. 方法与帧
|
||
|
||
```ts
|
||
// 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 `signal` abort(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 为准。
|