Files
deepseek-harness/missions/tasks/20260721-1520-web-plugin-rfc/design-notes.md
imccyu 0681ac47de chore(gui): mission work logs
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
2026-07-22 21:30:30 +08:00

260 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# dsh Web 插件体系 RFC——细则版(自用)
> 2026-07-21。本文档是 [walkthrough.md](walkthrough.md) 的全量细则底稿:裁决出处、约束红线、边界语义、验收面、开放问题。给实现 owner 和未来的自己看;用户 review 走读版即可。裁决源=2026-07-21 主会话对话(用户逐项拍板),冲突以对话原话为准。
## 0. 裁决登记(时间序)
| # | 裁决 | 要点 |
|---|---|---|
| D1 | 浏览器侧不需要强对等,"有 cordis 插件就行" | 拍掉 Loader 1:1 镜像/reload 联动;保留送达层基建 |
| D2 | 需求单 P0–P2(原文见对话) | 尽量插件化;左侧按钮/statusline/tabs/tool卡/详情tab/Traj行为/主题单槽/i18n fallback |
| D3 | 统一 slot registry,不要 N 个 registry | slot scope 路径化(session.chat.toolview);嵌套声明(区域拥有者开子坑);三型语义(唯一/列表/按对象多实例) |
| D4 | 包导出注册函数而非组件堆;client 有自己的不对等 cordis ctx | ctx 词汇:slots/i18n/theme/… |
| D5 | 点对点保留:绑定与不绑定 agent scope 的方法要能在声明处分清 | m.scoped('agent') / m.root() |
| D6 | scope 不能只是 key——host 的 Agent scope 是 Context 对象,client 侧要"对等造一遍" | scope 树镜像重建;插件集不对等、scope 树对等 |
| D7 | session 走 SSE 的现状与"数据插件化"冲突 → 要么不动要么全走 | 用户点破半吊子桥 |
| D8 | 不纠结改动量,只论架构合理性 → 会话层信封协议全部收编 | **后续修正(D16)**:载体先还原为既有 SSE/POST,信封寄生其上;WS 收敛降为独立议题 |
| D9 | 两侧 session 范畴不对称是公理:host=过程,client=视窗(任意时机连上,要处理 history) | attach 状态机 |
| D10 | onSessionEvent 太 low level,消费面=物化投影;"history 全读"级别只有 session 一个 | 通道三级分类学 |
| D11 | React 数据管理希望整体在 Zustand → 划辖区:世界 vs 相机 | store 无运行时事实副本 |
| D12 | 聊天面板 props 怎么定 → 位置供数+薄容器;props=身份+展示参数 | hook 从 ctx 取数 |
| D13 | 渲染性能:更新频次/失效半径/重算面走查 | 五段管道、频率递减 |
| D14 | 多视图同订同一 session → 投影单例、订阅分道 | 惰性+保温、一致性边界=投影 |
| D15 | RFC 双文档:走读版给用户(图+类关系+示例代码+上下游链路)、细则版自用;主会话自己写不派 teammate | 本文档结构的由来 |
| D16 | RFC 里先不扯 SSE/POST 与 WebSocket 的关系——数据通道还原成以前的 | 信封/channel/epoch/seq/watermark 语义不变,寄生在既有 SSE mux + HTTP RPC 上;载体收敛(WS/Electron IPC)单独立项再议 |
| D17 | 信封定位确认:"只是为了区分以前的 session 流或别的通道,可以预留" | 信封=通道分发+幂等账本,不承载业务语义;通道谱系 v1=session 流+预留插件私有通道 |
| D18 | rpc-log 裁撤 | 不做通道成员;C 级 v1 空置留给将来 metrics 类。(rpclog 面板/badge 的存续另议——通道层先不给它位置) |
| D19 | scope 不放信封层(用户直觉"不应该放这一层",采纳) | attach 时用 (通道名, scopeKey) 解析出通道实例 id,信封只带实例 id;信封层不理解业务维度 |
| D20 | peer 整体暂缓("点对点那个 peer 我们先不说") | 本 RFC 不设计 peer;cordis-web 资产封存待另稿收编;预埋两处:通道谱系留位 + fork ctx 留挂点;动作类需求 v1 走通道 intent |
| D21 | SessionLog 是既有资产不重造;client session scope 必须做进 cordis 架构而非自立设施 | 图上标注"既有资产";scope-created 处理函数体=一次 cordis 插件应用,无平行 scope 管理器;"ClientRuntime"改述为 client cordis root ctx 本身 |
| D22 | sessionHub 与 per-session session service 两个东西没必要并立;投影清单不是框架筛选对象 | sessionHub 裁撤:会话列表=root 级列表通道上的一个投影(壳声明);镜像驱动降为运行时内部件。投影全部用户空间:框架只供 defineProjection 机制(reduce 调度/合批/快照/保温),类型定义权与 reduce 实现权归声明者,共享=import 同一 handle,按 handle 去重 |
| D23 | root service 应该只有 loader,其他都靠 loader 拉起来 | root 原生=bootstrap 最小集(carrier/mux 基建+loader,静态写死——鸡生蛋消解的既有裁决);workbench/slots/i18n/theme 全部改为 loader 拉起的 bundled plugin,与第三方同权 |
| D24 | workbench 更名 **router**("有 Router 知道各种 current 情况");slots/i18n/theme 认可;hooks 无级别、由 ctx 可见性管辖的解释认可 | router=当前选中/导航命令 service;另立 **ctx.sessions**(会话域入口)承接 session 相关消费面,细化中(见 §3.1a) |
| D25 | useService 暴露过分——全删重推:从 React 组件真实诉求出发重新设计 hooks | 组件诉求归并=订阅+动作两类。hook 收敛为两原语一糖:`useWatch(handle)`(唯一读)/ `useAction(handle)`(唯一写,返回 [invoke,pending])/ `useT()`。**useService 彻底删除**(service 是 apply() 世界的词汇,组件只持 handle——防逻辑漂进组件,红线 16);useProjection/useIntent/useWorkbench 都是前两者的用例。可见性=import 权+位置两道门 |
| D26 | 用户质疑"fork 架构哪来的"——盘 vendored 源码证实:本仓 cordis 无 ForkScope,只有 Fiber;host Agent Scope=dsh-scope mintScope(no-op 插件 Fiber+ctx.extend 打标+intercept) | 全文废弃"fork"词汇。多实例=不同 scope ctx 下多次 ctx.plugin() 得多个 Fiber;client session scope=mintScope 模式照搬,与 host 同架构。此前所有"fork"表述系我从上游旧版 cordis 记忆带入的错误词汇 |
| D27 | useWatch 需要澄清与既有"反模式"裁决的关系;hooks 必须严格守 React 高性能+多次运行幂等原则 | 之前被判反模式的是"订阅结果拷进 state/双份订阅簿记/在 render 里做副作用",不是订阅本身。useWatch=uSES 严格用法:getSnapshot 纯读+引用稳定(幂等,并发渲染安全),subscribe 引用稳定,selector 等值短路;useAction 的 invoke 引用稳定。红线沉淀为 hook 实现契约五条(见 §4.2a) |
| D28 | 确认:client scope=agent scope 同套 intercept 架构,session 精度插件守 host tool/system-prompt 同一范式(读自己 ctx 的 scope key 反查);追问 React 侧如何精确到一个 agent session | 缝合点=SlotOutlet:插件世界靠 ctx 继承,React 世界靠树位置继承。outlet 把"注册者在该 session scope 下的实例 ctx"放进内部 React context;hooks 取最近 Provider 的 ctx 解析 handle+填 scopeKey——组件不做反查、无 ctx 无路由。多实例=两子树两 Provider 两份累加器。跨 session 无隐式通道(读 root 列表投影或另开 outlet) |
| D29 | Provider 里放恒等的 scope 实例对象不会造成 React 反复刷新——确认;useWatch/useAction 是否太 low level?以现聊天/输入框通信范式校验 | 分层裁决:原语归框架(仅有的两条缝,守 §4.2a 幂等契约),**领域 hook 归 owner 插件**——useConversation 范式原样照搬:owner export handle 的同时 export 组合 hook(快照+引用恒定 ops 包,C3 纪律由 useAction 继承);签名去 sessionId 参数(scope 从树位置来);纯展示组件 props 契约一行不改。框架不设"钦定领域 hook 清单"(与 §3.1 投影所有权同构) |
| D30 | "过于声明式"纠偏:用户本义=client ctx 上挂 interface 自己实现 service;插件间调用应是 ctx.conversation.send(...) 这种形态 | 四层定稿:**ctx service(cordis 本义,declaration merging+provide/inject)=插件间 API 面**;channel/intent zod 声明只属 wire 层(跨线边界才出现,不是插件 API);领域 hook=owner 用原语把 service+投影拼给 React;组件只见 hook。scope 寻址循 host 范式:service 方法从调用方 ctx 读 scope key,scoped ctx 里 ctx.conversation.send() 自动打本 session;跨 session=ctx.sessions.get(key) 返回 **scoped ctx 视图**(get(id) 的答案)。新原语 usePluginCtx 仅限 hook 文件(组件里出现=lint 红线) |
| D31 | 追问 ctx.conversation 的获取链路与 get(key).conversation 可行性——要完整实例代码 | 机制核验(vendor/cordis + dsh-scope 源码):①service=root 单例 provide,scope 敏感——**不是每 session 一个实例**;②per-session 实例"拿到"它零动作:scope ctx=fiber.ctx.extend({kScope}),extend 链共享 registry/reflect,root service 子 scope 直接可见;ctx proxy 保证方法收到 caller 的 scoped ctx,scopeOf(this.ctx) 读 key(host scoped 路由同机制);③get(key).conversation 可行:get 返回真 Context(extend 链一员),service 可见且 scopeOf=该 key——"带 key 路由"改述为"换 ctx"。完整四段实例代码进 walkthrough §7.0 |
| D32 | 现 apiproxy 是 sessions.prompt(sessionId,content) 一体式;实际链路=scope 到 session→找绑定 agent→.steer——client 是否也要一套 agents 集合? | **不要**。host 的 sessions/agents=一实体两面(agents 按 sessionId 键控 1:1,enter 强制 agent.id===session.id;api-proxy agentFor 已证);client 的二象性已由 session 状态机表达(cold=无 agent/live=有/frozen=已终),再建 agents scope 树=两本账。session→agent 解析是 host 权威(agentFor 含 resume-on-prompt);client wire intent 只带 session scope key,永远不认识 agent;resume-on-prompt 的 client 观感=cold→live 迁移 |
| D33 | Scoped<Agent>/Scoped<Session> 两个路由面的寿命差异(agent carrier key=Agent 对象本身,resume=新路由身份)→ 用户拍板:**聊天对话按 session 精度,v1 不做 agent 级隔离** | wire 通道 v1 只有 session 绑定轴;挂 agent 寿命的 host 插件其 client 半边可能见 resume 前的 stale 状态——明知接受,记台账。预研过的两个启用方案存档:①通道 agent 绑定轴(实例随 agent 换代 chan-closed+新快照,epoch 兜)②session 状态机带 incarnation 纪元数(agent 面投影随纪元重置)。触发=第一个真实撞 stale 的插件 |
| D41 | wire 不改(用户自改 RPC 类型走 TS/zod 自动化,通道层另行处理);开发者不该接触 projection 映射;关切收窄为"指定 session 的组件的 props 与 hooks 传递" | defineProjection 从公开 API 除名(reduce 是 conversation 等 feed 消费者的私有实现手法);数据契约=Watchable 最小接口(getSnapshot/subscribe);Session 类原样(已是 Watchable)。三通道定稿:①位置=框架内建 SessionScopeGate(**开发者不写**,Layout/框架自带)Provider 恒等 Session 引用;②活数据=useSession(selector)(内部 useContext 取 Session ctx——今天 useConversation 的 selector 化,非换架构);③切片=props 递 owner 已物化 frozen 值。useSession 的暴露形态:导出给开发者 or 经 props.useSession 注入——两案待拍 |
| D42 | 确认三点:①SessionScopeGate 框架内建,开发者不写;②useSession 内部=useContext(SessionCtx),要给开发者一个获取途径;③**use hooks 可以绑在 props 上**(props.useSession(selector) 形态成立) | hooks-on-props 可行性确认:hook 规则约束的是调用时序稳定,不是函数来源——SlotOutlet 对某坑位的每次渲染,注入的 useSession 引用恒定(绑定该 scope 的 Session),组件无条件调用即安全;类型上 SlotMap[K]['props'] 自带 useSession 签名,强类型完整。优点:组件零 import 框架 hook 包(依赖更干净,测试直接传假 useSession);代价:props 不再是纯数据(白名单加"框架注入的 hook"一类,仍引用稳定不破 memo)。倾向:**两者并存**——props 注入为主推(插件组件零依赖),包导出为补(领域 hook 内部组合用) |
| D44 | 追问:①outlet 是否还要注 useSession;②register 的 key 是干什么的 | ①props 三源合并定稿:owner 供参(SlotMap 锁)+scope 标配注入(框架自动,useSession——owner 不写)+注册方私有注入(inject 工厂);register 类型要求 inject 恰好补齐组件 props 超出前两源的差集。②key 只属 keyed 坑,用途=**运行时按数据分发**(owner 拿 block.tool 当 entryKey 查注册表选组件,即"注册表驱动的 switch-case,case 可由第三方插件添加")+重复注册冲突检测;list 坑无 key 有 label/order(展示排序);single 无 key |
| D43 | 定稿:**完全外部注入模式**——组件所需一切经 props 注入,唯一约束=注入物引用稳定(不引发多余 rerender) | 三入口示例代码按此重写(walkthrough §6/§7):shared=interface+RPC 类型;client=service(持 store,命令式更新)+slot 注册;react=组件只吃 props(纯值+注入 hook),零框架 import。SlotOutlet 注入面={useSession, useAction 域函数, t}——全部构造一次缓存,per-(坑位×scope) 恒等 |
| D39 | 数据流转全景定稿(React 组件↔cordis service 对象↔SSE 通道的完整流向) | 三次形态转换:①JSON→类型化事件(zod,边界一次)②事件→可变 draft(投影 reduce 内部——immer/mutative 的唯一正确落点,实现细节非 API)③draft→不可变快照(finalize,结构共享=下游 memo 根基)。相等性协议表:mux=seq/epoch 数值;投影=引用即版本;useWatch=Object.is,selector 选对象声明 shallowEqual(深比较禁止,有此需求=去投影里物化切片);React.memo=shallow props(白名单保证够用);Zustand 同款语义。组件/容器里 map/filter 出新引用=打穿 memo 的事故源。zstd 压缩属 carrier 内部与数据流无关 |
| D40 | i18n/theme 简单做:纯字符串注册表;React 面标准化 | theme=token→值字典→CSS variables 到 :root,换肤 DOM 级联 **React 零渲染无需 Provider**;i18n=locale×namespace 映射表,React 面=标准 Provider+useT 查表(locale 切换整树重渲染,低频接受)。确认"比较标准化"——不发明新机制 |
| D38 | slot 骨架已定,但 props 数据流未定(class/Zustand/纯 JSON?) | props=跨插件 ABI,白名单四类:branded 身份/纯展示参数/**owner 已物化的不可变快照切片**(owner 本来持有+变化时本来就重渲染该条目)/罕见纯 UI 协调回调(引用稳定)。禁:class 实例、store、ctx、可订阅源。判据:props 传"定位你的已物化数据",不传"你自主需要的数据"(后者 hooks);owner 不许为子组件当数据泵。swarm 卡双通道样本:props 收 tool-call block,进度自己 useWatch |
| D37 | 嵌套注册最终形态(用户给出完整机制)+ register 直接挂 React 组件函数 | 单一 ctx.slots.register(key, Component, options?) API;**SlotMap declare merge**(typed-events 惯例复用):layout 挖顶层坑(SlotMap{sidebar,conversation}+define 落账),conversation inject[slots,layout] 后 register('conversation') 得完整类型推断+再 merge 挖 conversation.* 子坑,chat inject[slots,conversation] 再 register('conversation.views')——**inject 链即所有权链**(没 inject owner=装配 fail loud;卸载级联走 inject 原生语义)。register 第二参=组件函数本身,FC<SlotMap[K]['props']> 直接锁强类型;key/order 走第三参 options(条件类型:keyed 必填/single 禁传);register 到未 define 的 key=运行期 fail loud。D36 的 owner-registry 独立注册模型方案退场(被本形态吸收:owner 权威改由 inject+declare 表达) |
| D36 | 页面形态定盘:Layout 顶层两坑 sidebar+conversation;projects 不设 service(从 sessions 算出)而是 sidebar 区块插件;**嵌套 slot 一律走 owner 独立注册模型**——conversation.chat 经 conversation 的 registry 注册,不直接上全局 slots | 全局 ctx.slots 只管顶层坑+简单注册(key 字符串寻址,字面量类型查表锁契约);声明子 slot 的插件必须提供注册模型(registerView/registerToolView 这类 service 方法,返回 disposer);寻址链=所有权链(conversation→全局,conversation.chat→conversation registry,conversation.chat.tool→chat-view registry);defineSlot handle 方案退场,换 key+owner-registry 两级模型;输入框/视图切换器=conversation 骨架自有 UI 不开坑 |
| D35 | intent 声明太声明式/不 TS 化;conversation 应统管对话架构,滚动视图呈现形态由子插件注入 | ①跨线契约改 **TS interface 优先**:shared=interface(方法签名即契约)+一行 channel<I>(名字/级别/绑定轴);node=implements 类+serve(缺方法=编译错);client service 内部=channel.remote(ctx) 类型化代理;zod 退为框架内部边界校验(构建期从 interface 推导或运行时反射,复杂载荷才手写覆盖)——builder 链退场。②对话区所有权三层:壳给坑→conversation 插件统管(输入框/statusline/视图切换=骨架)→聊天/甘特图/Traj/Waterfall=注入 conversation.views(list slot)的子插件,各自可再开坑(聊天开 toolview);"session tabs"需求收敛为 conversation.views |
| D34 | apiproxy 是否可简化——不再需要 server 侧把 session/agent/steer/resume 封装成领域 RPC? | 对。apiproxy 从"领域 API 总汇"降为**通用通道路由层**(信封收发/attach/watermark,零领域词汇)。现 RpcMethodMap 领域方法逐个去向:events 流→session A 级通道;history→A 级 attach backfill(不再是独立 RPC);list→列表 B 级通道;prompt/cancel→conversation 插件 shared 声明的 intent;approvals/questions→respond 域 B 级通道;describe→config B 级通道。**agentFor/resume-on-prompt 不消失**——搬进 conversation 插件 node 半边 serve 实现,host 权威不变,只是从中心 api-proxy.ts 挪到 owner 插件。四象限 carrier 缝保留为传输层。净效果:新领域=新插件自带声明,中心契约不再随领域膨胀 |
前置已验收资产(cordis-web 线,worktree-cordis-web,ACCEPTANCE 12/12):本稿收编其中的**装载/构建面**——tsdown client bundle 预设、DSHClientProxy.loadPlugin、类型宇宙隔离(verify-client-closure);**peer 面**(builder/五件套/hold/echo 双向)随 D20 整体封存,见 §6。
VS Code 调研报告(missions/tasks/20260721-1330-vscode-extension-research/report.md)六节,本稿吸收的结论在各节标 [VSC-x]。
## 1. 信封协议(会话层)
### 1.1 分层
```
scope 语义层 通道 intent/事件 / 投影增量 /(将来 peer 调用,D20)/ slot 无关(slot 是纯 client 概念,不过线)
会话层 channel × epoch × seq;attach/watermark;背压;结构化错误;取消
传输层 SseHttpCarrier(现状既有通道,v1 载体)| InProcessCarrier | (future: WS / Electron IPC)
```
- v1 载体=既有 SSE 下行 + HTTP RPC 上行,不动(D16)。信封语义(channel/epoch/seq/watermark)寄生在既有 mux 帧格式上:下行 event 帧走 SSE 流,intent/receipt 走既有 HTTP RPC 面。
- 会话层要抽显式的动机不变:重连补帧目前与 EventSource 行为耦合(baseline-replay 稳定 rpcId),抽出后语义归会话层、载体归 carrier,将来换载体不动上层——但"换不换、何时换"不在本 RFC 范围。
- carrier 缝沿用四象限先例:接口/实现/消费三包分立;InProcess 保 fixture/单测路径(fixture runtime 不走网络)。
- [VSC-4] 单一契约文件两侧共同编译(extHost.protocol.ts 模式)→ 我们的 shared 通道声明即此物;改协议必双侧报错。
### 1.2 信封字段细则
```
{ chan: ChanId // 通道实例 id(branded);attach 时由 (通道名, scopeKey?) 解析而来(D19)
epoch: number // attach 会话代;client mint(attach receipt 里 host 回填确认)
seq: number // 通道实例内单调,host 侧 mint;intent 无 seq(用 rpcId)
kind: 'intent'|'receipt'|'event'
rpcId?: RpcId // intent/receipt 对;发起方 mint、应答方回填(红线 15 原样)
payload: unknown } // 通道 schema 双端 parse(zod)
```
- **scope 不在信封层(D19)**:信封只做分发与去重,不理解业务维度。scope 出现在 attach 的解析输入里(`attach(通道名, scopeKey?, fromWatermark?) → chan 实例 id + 应答`),host 侧维护 (通道名×scopeKey)→实例 的表;此后帧上只有实例 id。scope dispose → 该 scope 名下全部通道实例关闭(下发 chan-closed)。
- 通道名命名空间:官方通道与插件通道同一空间,插件通道强制前缀 pluginId/(编译期 route-reservation 断言沿用 R2 模式)。
- 官方 RpcMethodMap 与通道 payload 的关系沿用既有裁决:插件 payload 对官方契约 opaque 透传,不进官方编译期锁。
- 错误:结构化错误码闭集(含 `no-such-chan`、`no-such-scope`(attach 时)、`stale-epoch`(丢弃语义,仅计数))。closed union + assertNever。
- 取消:intent 可携 cancel 帧(rpcId 引用);[VSC-4] 取消独立报文先例。
- 背压:沿用既有 SSE res.write→drain 流控(R2 加固)+ host 侧每通道发送队列上限;超限策略按级别——A 级不丢(流控),B 级折叠(新快照替换未发旧快照),C 级丢尾。**这是 B 级"快照即全部"语义的直接红利。**
### 1.3 通道三级分类学
判据一句话:关心"过程"→A;关心"现状"→B;调试回看→C。
- **A 级封闭**:只有 session 事件流。新增 A 级=宣称存在 session log 外不可推导的过程性事实,与 Model-visible⟺logged 几乎矛盾 → 架构评审门。
- B 级快照义务:host 侧 per-scope 实例随时可供快照 = 双半边插件契约义务(进 defineChannel 的类型:必须提供 snapshot())。
- C 级 ring:容量是通道声明字段;"刷新丢旧"写进 JSDoc 为契约行为。v1 无成员(rpc-log 已裁撤,D18),级别定义保留给将来 metrics 类。
- attach 动词统一:`attach(通道名, scopeKey?, fromWatermark?)`;应答形态按级别 backfill-paged / snapshot / recent-N。session 的 history 分页=A 级 backfill 的展开形态,不是独立机制。
### 1.4 幂等定律(全系统唯一一套)
- snapshot + watermark + epoch。seq ≤ watermark 丢;epoch 旧丢。框架在 mux 层统一做,插件作者零自制去重。
- 刷新恢复:boot → sessions list(B 级)→ 重建 session scope → 视图挂载惰性 attach 各通道。
- fork 重建(如插件 reload):epoch+1,重走 snapshot。
- 在途 intent 丢失(刷新):无需处理;receipt 超时=unary 30s 既有兜底。
### 1.5 持久化等级
- session 事件帧:进 log,可重放(既有)。
- B/C 级帧(含将来的 peer 帧):呈现面控制流,不进 log(规矩 18)。同管道不同存续等级,帧上不标——由通道身份决定。
- 重放视图:A 级投影从 log 重算;B/C 级在回放态不存在 → 插件卡片必须可在"无 scope"形态渲染(documented-default 回退,三型卡梯子复用)。
## 2. scope 镜像
### 2.1 结构
- client root 下按 session 建子 scope(cordis 原生);Session class = scope ctx service,1:1 同生共死。**禁止第二账本**:不允许任何"scope 树 ↔ 对象层"对账表存在。
- 镜像事件:scope-created{key, meta} / scope-disposed{key},走 B 级 hub 通道的增量。
- mount 轴声明:client 半边 manifest 字段 `mount: 'root' | 'per-session'`。显式声明,不从用法推断(explicit>implicit)。
- **词汇修正(D26)**:本仓 vendored cordis 无 ForkScope(那是上游旧版词汇),只有 Fiber。多实例机制=在不同 scope ctx 下多次 `ctx.plugin(clientHalf, config)`,每次得一个 Fiber。scope 本身=dsh-scope mintScope 模式(no-op 插件 Fiber 当载体+ctx.extend 打 kScope 标+intercept 随 extend 继承),与 host 侧 Agent Scope 同一架构,client 侧照搬。
- 实例 ctx 已绑 scope:ctx 上可解析 session service;插件自己声明的 scoped 通道 attach 时自动带本 scope 的 key(将来 peer 挂点同此,D20)。
- dispose 链:host scope dispose → 事件 → client scope dispose → 实例 Fiber dispose → slot 注册/订阅/在途调用(reject no-such-scope)全收。**顺序**:先冻结投影(frozen 快照保留供 UI 显示終局)再收实例——UI 不闪空。
### 2.2 client session 状态机
```
cold → attaching(backfill) → live → frozen
↑ 断线 → reattach(fromWatermark) —— 对消费者不可见(投影不清空)
```
- backfilling 期间投影已可用(先给 history 前缀,water 线后缝 live);缝合由 session service 保证连续(去重靠 seq)。
- frozen:host 结束/杀死。既有"中断部分冻结"语义是其特例。frozen 后 intents reject(`session-ended`),投影只读。
- cold session(纯 log 无 live):投影全部从 history 构建;证明投影本体=log 投影,live 只是加速器。
### 2.4 agent 化身差异(v1 不做,D33 存档)
host 两路由面:session carrier key=scope key(跨 resume 恒定);agent carrier key=Agent 对象本身(resume=新身份,agent 面状态天然不跨代)。v1 client 一律 session 精度,stale 风险记台账。启用方案(想清待用):
- 方案 A 通道 agent 绑定轴:`scope:'agent'` 的通道,host 侧 agent 换代→旧通道实例 chan-closed→新实例新快照;client 投影收 closed 重置。epoch 机制原样兜住乱序。
- 方案 B incarnation 纪元:session 状态机 live 态带 incarnation(cold→live +1,源=agent 生命周期事件);agent 面投影声明"随纪元重置",session 面投影跨纪元累积;插件 agent 精度状态用 (sessionKey,incarnation) 键控。
- 择案倾向:A 更契约化(差异写在通道声明处,编译期可见),B 更轻(不动通道层)。真触发时按当事插件形态择一。
### 2.3 边界语义
- 镜像滞后:fork 收 disposed 即冻结,后续 UI 转 stale 态。
- props.sessionId ≡ 所在 scope:SlotOutlet 从同一 fork 记录取 ctx+id,同源不变量,进框架断言(dev 模式 throw)。
- 跨 scope 调用:从 client 运行时查目标 fork 再调,显式;不做隐式路由。
## 3. client ctx 词汇表
root 原生(bootstrap): carrier/mux/loader(D23)
root 上由 bundled plugin 注册: workbench / slots / i18n / theme
scope: session(事件feed+intents+attach 机,框架唯一的 scope service)/ 投影(用户空间,D22) / (插件自注册 service) /(将来 peer,D20)
- workbench [VSC-git activeRepository 模式]:读+订阅+命令(openSession/openTab);实现内读写 Zustand;插件唯一门。"无命中保持旧值+可 pin"语义抄 scmViewService。
- i18n:locale×namespace;fallback 链 de→en 声明式;插件 namespace=pluginId;t() 同步查(字典 attach 时全量到位,B 级)。
- theme:single 槽;token 空间由壳预注册([VSC-3] StatusBarItem 只准主题色 id 的推广);插件组件禁 hardcode 色值(lint 面);应用=写 CSS variables 到 :root。
- loader:清单=host 声明(config 同源、重启生效——既有裁决沿用);bundle 分发端点/DSHClientProxy 全复用已验收链。
- 类型宇宙:client 词汇表接口全部在 client-only 包声明;verify-client-closure 覆盖面扩到新包。
## 4. 投影(materialized views)
### 4.1 契约
```ts
defineProjection('conversation', {
from: sessionChannel, // 原料通道
init: (backfill) => State, // attach/backfill 构建
reduce: (state, event) => void, // 可变累加器(内部)
snapshot: (state) => Snapshot, // 不可变快照+结构共享(uSES 正确性前提)
retain?: 'scope' | 'refcount', // 默认 scope 保温
})
```
- reduce 一次性:唯一一份计算,视图皆订阅者。
- 通知合批:raf 级 bump(红线 17 Notifier 纪律的新家);等值短路(status 没变不 bump)。
- 投影全部用户空间(D22):conversation/status/timeline/usage 只是第一方插件恰好声明的四个,不是框架 API 面;声明者持类型与 reduce 实现权,共享=import handle,按 handle 在 scope 上去重。
- onEvent 生流逃生门:JSDoc 标注"自负 reduce/顺序/重放责任";review 时按红旗对待。
- 一致性边界=投影;跨投影 ≤1 帧撕裂接受(台账 T2);投影内原子由快照整体替换保证。
- 惰性+保温:首订阅时建(含必要 backfill),scope 活着不拆;retain:'refcount' 留给重投影。
### 4.2a hook 实现契约(D27,组件侧幂等五条)
1. `getSnapshot` 纯读且引用稳定:数据没变必须返回同一引用(uSES 正确性前提,否则并发渲染下 tearing/死循环);快照生成在投影 bump 时,不在 render 时。
2. `subscribe` 引用稳定(useCallback/模块级),render N 次不重订。
3. selector 在 uSES 的 selector 位(带等值比较),不在组件体里二次加工——加工=每次 render 新引用=失效放大。
4. `useAction` 返回的 invoke 引用稳定;pending 是它内部的 uSES 源,不是组件 setState。
5. render 体内零副作用零解析:ctx/handle 解析全部在包裹层或 hook 内部 memo,组件体可以被 React 并发模式任意重放。
违反任一条=review 打回;Profiler 计数验收(§10)是这五条的行为面证据。
### 4.2 渲染性能红线(进验收)
- ctx 注入的 React context 引用稳定,数据不走 render 树。
- 投影快照结构共享:流式期间历史 turn 引用不变。
- markdown 增量:已闭合块 memo,只 parse 尾部未闭合块。
- 滚动跟随:raf 直接 DOM,不经 React state。
- 列表铁律:父订成员身份(id 序列),子订成员内容。
- 禁止订阅结果拷进 state/store。
- **Profiler 计数验收**(playwright + React Profiler API):流式期间 statusline 渲染次数=0、邻居 row=0、历史 MessageItem=0;发送链路各组件渲染次数上限表。
## 5. slot 系统细则
- handle=值:defineSlot 返回 phantom-typed handle;注册递 handle;字符串路径只是调试名。嵌套=export/import 图,无中心名册;壳只 export 第一层。
- 三型状态机:single(冲突 fail loud/config 指定赢家)、list(order 字段,稳定排序)、keyed(key 必填,重 key fail loud)。类型状态机模式抄 peer builder(broadcast 无 output 方法先例)。
- 纯数据 slot:props 位换 value schema(zod);同一注册表、同一 disposer 纪律。
- SlotOutlet 包裹层:ErrorBoundary(必需品,[VSC-5] 放弃进程隔离的对价)+ ctx Provider(注册者插件 ctx × 所在 scope ctx 两层)+ 身份 props。全部稳定引用,注册表变化才重建。
- 插件卸载:fork disposer → 注册撤销 → outlet 下一次渲染少一项;正在显示的组件卸载由 React 正常 unmount。
- 声明先于行为 [VSC-2 contributes 哲学]:数据性贡献(槽位/token/图标/路由)注册即可消费,行为性贡献(组件/回调)待 apply 完成;两者都走 ctx.effect。
## 6. peer——暂缓(D20),封存备忘
本 RFC 不设计 peer。以下为暂缓时点的备忘,供将来另稿收编时起步,**不是本稿承诺**:
- 封存资产(worktree-cordis-web,ACCEPTANCE 12/12):builder 词汇(input/output 状态机、broadcast 限无 output、serve 第二参来源 proxy)、hold 语义、PeerError 闭集、HostPeer/Gateway/Proxy 五件套。
- 届时的收编方向(本轮讨论已对齐但未定稿):peer 帧=插件私有通道的一种;绑定轴 m.scoped('agent')/m.root();Gateway 路由 (pluginId, scopeKey)→实例;scoped serve 类型随 scope 注入变化;组件侧消费=useAction(peer 动作 handle)(D25 之后无独立 peer hook);乐观态=observed intent [VSC-4.5]。
- 本稿预埋(见 walkthrough §6):通道谱系留位+fork ctx 留挂点;动作类需求 v1 走通道 intent。
## 7. Zustand 细则
- 单 store 分 slices:nav(选中 session/tab)、layout(面板/尺寸)、prefs(主题选择/语言选择,persist localStorage)、drafts(keyed by sessionId)。
- prune 桥:scope dispose effect → store.prune(sessionId);单向,ctx 通知 store 执行;store 永不反向查询 ctx。
- workbench service=插件与 store 之间唯一的膜;插件 API 面无 Zustand 类型(呈现库可换性)。
- 插件内部私用 Zustand 随意(bundle 私事)。
- 红线 14 新表述:store 无运行时事实副本;lint/review 检查点:store slice 里出现 SessionId 索引的**内容**数据(非观看态)即红旗。
## 8. 迁移地图(现状→目标,给实现排期用)
| 现状资产 | 去向 |
|---|---|
| connection.ts/SSE mux/重连补帧 | **保留为 v1 载体**;其上抽 ChannelMux 会话层(attach/watermark/epoch 显式化);背压/R1/R2 加固原样 |
| web-api-client/api.ts 四象限 | intents 收编为信封 kind;InProcess carrier 保留测试路径;AbstractApiClient 类层次并入 carrier 缝 |
| apiproxy RpcMethodMap 领域方法(sessions.prompt/cancel/history/list、approvals、questions、describe) | 解体(D34):通用层只剩信封路由+attach;领域方法化为各 owner 插件的通道/intent 声明与 node 半边 serve;agentFor/resume-on-prompt 平移进 conversation node 半边 |
| Session OOP 对象层/store.ts | Session→scope service;投影从现有 reduce 逻辑拆出(conversation 雏形=现聊天数据流);Notifier→投影 bump |
| RpcLog | 裁撤(D18):不入通道谱系;现有面板/badge 的去留在壳清理时另拍 |
| 三个 shell registry+toolCardRegistry | 统一 slots service;现有注册点改 defineSlot handle;git mv 保历史 |
| respond/approvals 设计(五刀已归档) | pending approvals=B 级通道范例;PendingInteractionRegistry 即其 host 侧快照源;实施窗口与本 RFC 合流 |
| cordis-web worktree | loader/bundle/类型隔离收编;peer 面封存不动(D20,待另稿) |
| fixture.ts | 造 InProcess carrier 的假 host;历史脚本/重放钩子平移 |
| jsdom/playwright 验收面 | 快照面换新协议帧;Profiler 计数断言新增 |
分期(粗):M1 会话层显式化(既有 SSE/POST 上抽 ChannelMux)+session 通道跑通(对象层换底,UI 无感);M2 scope 镜像+统一 slot+hook 库(壳自迁移,内置=bundled 插件);M3 插件 loader 收编+第一个双半边插件(swarm 或 respond)+theme/i18n 注册表。每期门禁全绿交付。
## 9. 开放问题(下轮 review 待拍)
1. 载体收敛(要不要在某期换单条 WS,或 Electron IPC 直连):本 RFC 明确出局(D16),单独立项;carrier 缝已显式,届时不动会话层以上。
2. hub 通道与 scope 镜像事件是否合并为一个 B 级通道:倾向合并(session 列表项=scope 存在性同源)。
3. defineProjection 给第三方开放的时机:M2 还是 M3(API 稳定前第一方 bundled 插件先用,机制同一套——D22 之下没有"官方投影"只有"先用者")。
4. workbench 命令面词汇表(openSession/openTab 之外还有什么进 v1)。
5. i18n 字典的 bundle 形态:随插件 dist.js 内联 vs 独立 chunk 按 locale 拉(倾向内联,量小)。
6. Electron 线(plan.md 原始诉求):会话层与载体解耦后 renderer=WebUI 天然成立;main 侧 carrier 形态留到 Electron 立项(并入开放问题 1 的载体议题)。
## 10. 验收面规划
- 协议单测:信封 codec、watermark/epoch 丢弃矩阵、三级 attach 应答形态。
- InProcess e2e:双 cordis 运行时同进程,scope 镜像生命周期(创建/断线 reattach/dispose 冻结)。
- 浏览器验收(playwright):9a/9b/9c 三链路+Profiler 计数表+断线刷新恢复+双视图同订。
- 类型负样本:single slot 双注册、keyed 漏 key、通道命名冲突、scoped 通道从 root ctx attach——各一个 expect-error 编译用例。
- 插件样板:双半边示范包(per-session mount+B 级通道+statusline slot)作为验收载体与文档范例(echo 的 peer 面封存,示范包不含 peer)。