Files
deepseek-harness/missions/tasks/20260721-1520-web-plugin-rfc/api-contracts.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

33 KiB
Raw Blame History

P-I API 契约终版 v3(冻结;teammate 照抄实现)

本文件即冻结契约本体——实现中发现契约错 → SendMessage 主会话仲裁改本文件并广播;不许自行改接口。配套:dispatch.md(任务分派)、plugins.md(业务+布局规则)、architecture.md(机制讲解)、figma-analysis/(视觉)。services.md 已并入本版作废。 包名前缀 @deepseek-ai/dsh-client-<dir>(下文省略)。ESM 源码、strict TS、类型 import 一律 import type。

0. 包清单、依赖图、装载链

0.1 包(12 个)

packages/client/
  ui-slots/        SlotCore 纯注册表 + SlotMap 类型 + ScopedSlots 双形态类型
  ui-primitives/   纯 React 原子件:State四色点/ic_ds_图标族/按钮胶囊菜单/markdown族(零 cordis 依赖)
  web-react/       store(zustand 引擎)+ObservableSnapshot+bindSnapshotSelector+SessionProvider+outlet 实现+useInvoke
  connection/      wire 类型封装+网络连接(现文件原样搬家)
  runtime/         cordis 启动+SlotsService 包装+sessions(scope 树)+loader(渐进式)
  ui-layout/       layout 插件:三栏 AppFrame+拖拽+ctx.layout service(壳级观看态总归口:导航+面板)
  ui-sidebar/      sidebar 插件(树列表全量)
  ui-conversation/ conversation 骨架+chat 视图+toolviews+details 极简+bash toolview 样例
  ui-trajectory/   trajectory+waterfall 占位视图(独立 teammate,不要求效果)
  ui-theme/        theme 注册表+CSS vars
  i18n/            i18n 注册表+t 绑定
  web/             入口壳:global 挂载+boot+AppRoot+vite/构建管线

0.2 依赖方向(import 面)

ui-slots ←─ web-react ←─ runtime ←─┬─ ui-layout ←──── web
    ↑           ↑           ↑      ├─ ui-sidebar
ui-primitives ──┘       connection └─ ui-conversation ←─ ui-trajectory
(ui-* 均可用 ui-slots 类型/web-react hooks/ui-primitives 组件;ui-theme、i18n 零依赖旁路)

0.3 装载链(v4 修订:默认等全量加载完再画真 UI)

GET / → host webserver 渲染 index.html,注入 window.__DSH_BOOT__(§9.2)
  → web 壳: 挂 ctx.loader(loader 机件由壳静态持有,不能经自己装载)
  → createRoot 渲染 <AppRoot> 的 **boot loading 页**(纯壳组件,不依赖任何插件)
  → loader.start(): 读 __DSH_BOOT__,immediately 组并行拉取+组内 inject 拓扑 apply
      (connection/runtime(slots/sessions)/ui-theme/i18n) → 全组就位后
  → 其余插件按 inject 拓扑逐个拉 bundle(layout 等)
  → await loader.settled() → AppRoot 切换渲染真 UI(此刻全部注册已到位,一次成型)

默认=等所有插件加载完才开始画实际 UI(loading 页→整 UI 一次切换);Suspense/渐进点亮/局部依赖加载全部后置(台账)——loader 的渐进能力(status/单插件失败标记)保留,只是 AppRoot 不利用它做渐进渲染。单插件 load 失败:P-I 直接在 loading 页显示错误(fail loud),不做部分可用。 插件(含基建四包)全部经 bundle loader 动态加载(CSS 随插件注入+隔离 §9.1)——生产模式不可重打包主入口,壳静态内置的只有 loader 机件与纯库。dev 与 prod 同链:插件改动=tsdown watch 重编译+刷新,无 HMR(后置)。

双入口包形态(每个 UI 插件包)——UI 插件本身就是 host 插件;client 产物靠声明发现,不靠 apply 里调注册:

package.json:
  "dshClient": { "inject": ["ui-layout"], "platform": "web", "immediately": true? }   ← 声明位
  "exports": { ".": ..., "./client": "./dist/client.js" }   ← client 产物走标准 exports,不进 dshClient
"."(node 半边)  : cordis host 插件。纯 UI 插件的 apply 可以是空函数——
                  存在的意义=让插件出现在 host cordis.yml/Loader 里(装载与生命周期跟 host 走);
                  有 host 逻辑的插件(将来 approvals/swarm)在此加码
"./client"      : 浏览器半边(tsdown 闭包 bundle,§9.1)
"./shared"      : 仅当有跨线契约时出现(P-I 无)

dshClient 字段语义(2026-07-21 修订 immediately):inject: string[]=client 半边的装载依赖(与 cordis inject 同名同念——按它拓扑排序);platform: 'web'=目标平台(暂一插件一平台,将来 electron 等再扩枚举);immediately: true=先行装载组——仍是标准动态 bundle(有 url,与其余插件同构),loader.start() 先并行拉取该组、按组内 inject 拓扑 apply,全组就位后才开始装载其余插件(layout 等)。 声明范围(八包):connection/runtime/ui-theme/i18n 带 immediately: true(基建;runtime 的 client 半边供 slots/sessions;loader 机件例外——装载器不能经自己装载,由 web 壳静态持有并在 boot 时挂 ctx.loader,代码家仍在 runtime 包);ui-layout/ui-sidebar/ui-conversation/ui-trajectory 不带。纯库 ui-slots/ui-primitives/web-react 与壳 web 不声明。host cordis.yml 全列八包(config 同源:装/卸=改 yml 重启)。

发现机制(HostWebPluginRegistry,归 webserver 属地/rt-core 实施):订阅 host Loader 的插件加载面(实施时对照 vendor/loader 实际事件名,无则 registry 遍历+update 事件),对每个已加载插件解析其 package.json:有 dshClient 且 platform 匹配 → 进 web 插件表(id=包名;client 产物路径=解析该包 exports["./client"]);插件卸载=移出表。webserver 由此表生成 DSH_BOOT 与 /plugins//client.js。apply 零仪式——插件作者只写 package.json 声明;config 同源落地=装/卸 UI 插件就是改 host cordis.yml 重启。类型宇宙隔离照旧:client 编译单不得见 node 半边 merge,verify-client-closure 扩到 12 包(T0 接)。

1. ui-slots

export interface SlotMap {}                        // owner declare merge;条目={ kind; scope; props }
export type SlotKind = 'single' | 'list' | 'keyed'
export type SlotScope = 'root' | 'session'
export interface SlotEntryDef { kind: SlotKind; scope: SlotScope; props: object }

// inject 装配语境(apply 世界专用,永不进 props)
export interface SessionBinding {
  readonly sessionId: string                       // web-react re-export 收窄为 SessionId
  readonly session: SessionAccess                  // { useSelector },web-react 收窄
  readonly ctx: unknown                            // 该 session 的 scoped cordis ctx(runtime 充实)
}
export interface RootBinding { readonly ctx: unknown }
export interface SessionAccess { readonly useSelector: unknown }
export type InjectFactory<E extends SlotEntryDef> =
  (b: E['scope'] extends 'session' ? SessionBinding : RootBinding) => Record<string, unknown>

export interface SlotSpec<E extends SlotEntryDef> { kind: E['kind']; scope: E['scope'] }
export type SlotOptions<E extends SlotEntryDef> =
  E['kind'] extends 'keyed' ? { key: string; inject?: InjectFactory<E> }
  : E['kind'] extends 'list' ? { id: string; order?: number; label?: string; inject?: InjectFactory<E> }
  : { inject?: InjectFactory<E> }

// 纯注册表核心(零 cordis;事件发射归 runtime 的 Service 包装)
export interface SlotEntry<E extends SlotEntryDef> { component: FC<E['props']>; options: SlotOptions<E> }
export class SlotCore {
  define<K extends keyof SlotMap & string>(key: K, spec: SlotSpec<SlotMap[K]>): () => void
  register<K extends keyof SlotMap & string>(
    key: K, component: FC<SlotMap[K]['props']>, options?: SlotOptions<SlotMap[K]>): () => void
  entries<K extends keyof SlotMap & string>(key: K): readonly SlotEntry<SlotMap[K]>[]
  spec(key: string): SlotSpec<SlotEntryDef> | undefined
  subscribe(key: string, fn: () => void): () => void   // 微任务合批
  getVersion(key: string): number
  onMutate(fn: (key: string) => void): () => void      // Service 包装的事件桥挂点
}

// 渲染注入面类型(实现住 web-react;P-I 单形态——Suspense/局部依赖加载后置,接口预留位见台账)。
// ★交付形态=props 注入:owner 组件的 props 类型里带 `slots: ScopedSlots<'a'|'b'>`(白名单泛型),
//   由注册处 inject 工厂/框架标配注入下发——组件不 import 任何渲染器,能开哪些坑在签名上强类型可见。
export interface ScopedSlots<K extends keyof SlotMap & string> {
  renderSlot<Key extends K>(key: Key, props: OwnerProps<SlotMap[Key]>, opts?: RenderOpts): ReactNode
}
export interface RenderOpts { entryKey?: string; only?: string; fallback?: ReactNode }
export type OwnerProps<E extends SlotEntryDef> = /* E['props'] 去掉 inject 键与标配注入键 */

行为规格:define 前 register=throw;single 重复 register=throw;keyed 重 key=throw;register/define 返回 disposer。零依赖(React 仅 import type)。

2. web-react

// ── 子路径 web-react/store —— zustand 为引擎 ──
export interface ObservableSnapshot<T> { getSnapshot(): T; subscribe(fn: () => void): () => void }
export interface SnapshotStore<T> extends ObservableSnapshot<T> {
  update(mutator: (draft: T) => void): void        // immer 中间件
  set(next: T): void
  readonly useSelector: SnapshotSelectorHook<T>
}
export type SnapshotSelectorHook<T> = <S>(sel: (s: T) => S, eq?: (a: S, b: S) => boolean) => S
export function createSnapshotStore<T>(init: T, opts?: { flush?: 'raf' | 'sync'; persist?: { name: string } }): SnapshotStore<T>
//   = zustand vanilla + immer + subscribeWithSelector + 自研 rafFlush 中间件 + persist(opt-in) + dev freeze
//   flush 缺省='sync'(受控输入要同 tick 回响;帧驱动 store 由创建方显式传 'raf'——Notifier 双通道纪律的 store 面)
export function shallowEqual(a: unknown, b: unknown): boolean   // re-export zustand/shallow

// ── 主入口(React 面) ──
export function bindSnapshotSelector<T>(w: ObservableSnapshot<T>): SnapshotSelectorHook<T>
export type UseSession = SnapshotSelectorHook<ConversationSnapshot>
export interface SessionBinding { sessionId: SessionId; session: { useSelector: UseSession }; ctx: Context }

// SessionProvider(依赖倒置,不 import runtime;2026-07-21 仲裁修订:两坑渲染归装配方)
export interface SessionProviderDeps {
  useCurrent: () => SessionId | undefined
  resolveBinding: (id: SessionId) => SessionBinding | undefined
  renderBody: (id: SessionId) => ReactNode   // 装配方(web 壳)闭包自己的 scopedSlots 渲染 conversation+details 两坑——坑归 layout own,Provider 不认识坑名
}
export function createSessionProvider(deps: SessionProviderDeps): FC<{ renderEmpty?: () => ReactNode }>
//   订 current → resolveBinding(恒等引用) → <BindingContext value key={id}> → renderBody(id)

// RootBinding 供给通道(2026-07-21 仲裁新增):root 坑 inject 工厂需要 b.ctx(如 sidebar 拿 layout/sessions),
//   由壳顶部挂一次 <RootBindingProvider value={rootBinding}>;SlotOutlet 渲染 root 坑 inject 时读取,缺失=throw
export const RootBindingProvider: FC<{ value: RootBinding; children?: ReactNode }>

// ScopedSlots 工厂(outlet 实现;不全局导出组件——一切经注入面下发,白名单收窄)
export function scopedSlots<K extends keyof SlotMap & string>(core: SlotCore, ...keys: K[]): ScopedSlots<K>
//   renderSlot: 订注册表(subscribe+getVersion 走 uSES),按 kind 渲染,每 entry 包 ErrorBoundary,
//     合并 props={...标配注入(session 坑=useSession), ...cachedInject(binding), ...ownerProps}
//     inject 缓存: session 坑 per-(entry×session) WeakMap;root 坑 per-entry

export function useInvoke(fn: () => Promise<unknown>): [invoke: () => void, pending: boolean]

白名单纪律:组件能开哪些坑写在它的 props 类型上(slots: ScopedSlots<'a'|'b'>)——来源=自己 define 的坑+owner 显式转授;越权=编译错。

3. connection —— 接口零变化搬家

现 web-runtime wire 消费层原样改名搬家:IApiClient、connect/ConnectionHandle(SSE 双流+重连+since)、MuxFrame/HostFrame、createFixtureApi。契约=现状导出面(rt-core 搬家时附实际清单于本节尾)。不 import cordis。

3.1 附注:apiproxy 纯度原则(2026-07-21 裁决;rt-core 对账清单的判据)

apiproxy 只准含两种代码:①窄形↔全形协议胶水;②wire 契约明文承诺的读投影(如分页边界对齐)。判据:换个消费面(ACP/headless)还需要吗? 需要→下沉 host 能力层;只有浏览器需要→上移 client;只有这条 wire 契约需要→留下。

存量三类判决:

存量 判决
agentFor(resume-on-prompt)、summarizeCold(cold 列表合并) 下沉 host 能力位——P-I 原样使用,rt-core 对账清单标 TODO(P-II) 下沉
paginate 留(wire 契约的读投影)
viewFor / backscanArgs(ToolEventView 呈现计算) P-I 随 toolviews 迁移整体删除(呈现归 client)

语义边界:隐式 resume 保留(cold→live 迁移是 host 内政,in-flight 表去重),永不隐式 create。可议项:history 的隐式 resume 应改纯持久化读(看历史不该拉起 agent)——记入 rt-core 对账清单,P-I 不改。

3.2 附录:实际导出清单(rt-core 对账 2026-07-21;本节尾附清单=契约授权动作)

搬家后接口零变化实测(export * 已改精确清单)。§3 正文「connect/ConnectionHandle」为旧稿措辞,现状实体=ConnectionController/ConnectionSinks(对账认现状)。

来源文件 值导出 类型导出
api.ts RpcId、AbstractApiClient、resultOf、transportError ApiProxy/SessionsApi/SessionSummary/HostApi/EventsApi/MuxFrame/HostFrame/ApprovalResponsePayload/QuestionResponsePayload/HistoryEntry/ToolEventView/ToolCallView/ToolResultView/RpcRequest/RpcResponse/RpcResult/RpcError/RpcErrorCode/ClientRequest/ServerResponse/ServerRequest/ClientResponse/RpcMessage/RpcReceipt/IApiClient/SessionId/SessionEvent/ContentBlock/StreamChunk
connection.ts ConnectionController ConnectionConfig/ConnectionSinks/ConnectionState
web-api-client.ts WebApiClient —
fixture.ts FixtureApiClient、createFixtureApi —

溶解项(不入 connection 导出面):events.ts(WEB_EVENTS/WebEventName)随死码退役刀删除(web-cordis pre-provision,零消费者,2026-07-22);intents.ts 整文件死——rpc-log 五 intent+pingHost 随 D18/RpcLog 族退役,refreshSessions/createSession 职责归 runtime SessionsService;store.ts(ConnectionSlice 观看态)不属 wire 层,connection 状态可见性归 runtime 侧挂账。ToolEventView/ToolCallView/ToolResultView 的 re-export 随 §3.1 viewFor 删除刀同步移除(toolviews 迁移触发,届时更新本表)。

4. runtime

4.0 类型宇宙隔离(2026-07-22 终裁——host/client 双 TS program)

实证:client 包 declare module 'cordis' 与 host 侧同名键撞车(sessions 撞 core/session 的 SessionStore、loader 撞 vendor/loader)。ClientContext extends Context 方案已作废(extends 消不掉父类——若 program 仍装载 host declare,继承照样撞)。

终裁(用户拍板)=多 tsconfig 拆分,已全案落地(细案与执行记录=同目录 clientcontext-audit.md):①typecheck 拆两 program——tsc -b tsconfig.json tsconfig.client.json(根=host 聚合,client 独立聚合);②A 类类型链切断——session/llm/tools/approval/interaction 出纯类型子路径(./types、./presentation、llm/brand),connection/apiproxy/runtime 的 import 全量切换,client program 不再装载 host 的 cordis augmentation;③client 包照常 declare module 'cordis' 声明自家服务键(sessions: SessionsService/loader: ClientLoader/layout/conversation/toolviews/theme/i18n)——双 program 下与 host 声明永不同室。ui-theme/i18n 对 runtime 的 type-only devDep 豁免保留(§0.2 零依赖旁路指零运行时依赖)。

// ── slots Service 包装(真 cordis Service,直接 ctx.emit) ──
declare module 'cordis' { interface Events { 'slots/changed'(key: string): void } }
declare module 'cordis' { interface Context { slots: SlotsService } }
export class SlotsService extends Service {        // static provide='slots'
  private core = new SlotCore()                    // core.onMutate(k => this.ctx.emit('slots/changed', k))
  // define/register/entries/spec/subscribe/getVersion 代理 core
}

// ── sessions(含 scope 树,P-I 就做) ──
declare module 'cordis' { interface Context { sessions: SessionsService } }
export interface SessionSummary { id: SessionId; title: string; cwd?: string; parentId?: SessionId; running: boolean; updatedAt: number }
export interface SessionListState { ids: SessionId[]; byId: Record<SessionId, SessionSummary> }
export class SessionsService {
  readonly list: SnapshotStore<SessionListState>   // list RPC 快照+host 流增量;重连重拉
  readonly manager: SessionManager                 // 现有类原样
  create(opts: { cwd?: string }): Promise<SessionId>
  scope(id: SessionId): Context | undefined        // scoped ctx 视图(用完即弃)
  binding(id: SessionId): SessionBinding | undefined   // SessionProvider.resolveBinding 供给
  ancestry(id: SessionId): SessionSummary[]        // 面包屑供数:沿 parentId 回溯(list store 内查,含自身,根在前)
}
// scope 树:dsh-scope mintScope 模式照搬(no-op 插件 Fiber+extend({[kScope]:id}));
//   生命周期=观看驱动(首次解析惰性建;列表 removed 且无人观看才拆;host 死≠scope 死,frozen 保留)
export function scopeOf(ctx: Context): SessionId | undefined

// ── Session 类改造(仅加法两行) ──
class Session implements ObservableSnapshot<ConversationSnapshot> {
  readonly useSelector: UseSession                 // = bindSnapshotSelector(this)
}

// ── loader(渐进式;机件壳静态持有——runtime 出 `./loader` 专用子路径供壳 import,
//    壳 bundle 不得捎带 runtime 整个 client 半边(双实例禁令)) ──
// 工厂签名(2026-07-22 仲裁,ui-shell/rt-core 接缝):
//   export function createClientLoader(opts: { ctx: Context; modules: Record<string, unknown> }): ClientLoader
//   —— modules=壳播种的纯库实体表(seed.ts 形状);loader 拥有该表并在装载后回登记 bundle 导出面
//   export interface ClientLoader { requireModule(spec: string): unknown }  // 追加成员:壳装配面取
//   已装载模块导出(如 layout 的 CenterColumn),查无=throw——与递给 factory 的 require 同一实现
declare module 'cordis' { interface Context { loader: ClientLoader } }
export interface BootPluginEntry { id: string; url: string; inject: string[]; immediately?: boolean }
export interface ClientLoader {
  start(): void                                    // 读 __DSH_BOOT__:immediately 组并行拉+组内 inject 拓扑 apply,全组就位后其余按拓扑装,不阻塞
  load(id: string): Promise<void>                  // script 注入→DSHClientProxy 单槽 handoff→ctx.plugin(apply)→style 归属登记
  unload(id: string): Promise<void>                // P-I 空实现(throw not-implemented)——HMR 立项时补(Fiber dispose→级联收回→移 style)
  settled(): Promise<void>                         // 全部到位——AppRoot 等它后才画真 UI(见 §0.3 v4 修订)
  readonly status: SnapshotStore<Record<string, 'loading' | 'active' | 'failed'>>
}

5. ui-layout(单插件:AppFrame + ctx.layout service)

declare module '@deepseek-ai/dsh-client-ui-slots' { interface SlotMap {
  sidebar:      { kind: 'single'; scope: 'root';    props: SidebarRootProps }
  conversation: { kind: 'single'; scope: 'session'; props: ConvRootProps }
  details:      { kind: 'single'; scope: 'session'; props: DetailsProps }
  'conversation.empty': { kind: 'single'; scope: 'root'; props: {} }
  // ↑ 2026-07-22 仲裁(EmptyState 挂载点缺口):无选中会话时 SessionProvider.renderEmpty
  //   由壳 renderSlot('conversation.empty') 渲染——EmptyState 归 ui-conversation 包,
  //   经注册表进坑(inject 工厂供 startSession/cwd 派生);壳不得经 requireModule 点名
  //   插件导出件(防幽灵耦合)。转场同组件裁决不变:EmptyState 与常态 InputBar 共用输入组件。

declare module 'cordis' { interface Context { layout: LayoutService } }
export interface NavState { sessionId?: SessionId; viewFor: Record<SessionId, ViewId> }
export interface PanelState { open: boolean; width: number }
export class LayoutService {                       // 壳级观看态总归口(zustand+persist)
  readonly current: SnapshotStore<NavState>
  readonly sidebar: SnapshotStore<PanelState>      // 默认 300,[240,420]
  readonly details: SnapshotStore<PanelState>      // 默认 360,[300,520];P-I 全局不随 session
  open(id: SessionId): void                        // 校验存在于 sessions.list
  openView(sessionId: SessionId, view: ViewId): void
  toggleSidebar(): void; setSidebarWidth(px: number): void
  openDetails(): void; closeDetails(): void; setDetailsWidth(px: number): void
}
export type ViewId = keyof ConversationViewMap & string
// prune: 订 sessions.list 的 removed → 清 viewFor 等 keyed 条目(单向);current.sessionId 指向 removed 会话时同刀置 undefined(回落 EmptyState)
// 让步链第4步 auto-close details=派生 0 宽,不写 details.open 的 persist 态(窗口回宽自动恢复)

// AppFrame: 三栏 grid+两条拖拽把手(pointer+raf 节流)+让步链(中栏 min640→clamp details→clamp sidebar→auto close details)
//   details 收起=0 宽不 unmount;各坑一律 renderSlot;
//   AppRoot(web 壳)第一帧的 boot 骨架被 layout 插件到达后替换

6. ui-sidebar

register 进 sidebar 坑(整栏独占)。SidebarRoot 结构照 figma(Logo 行+折叠钮/NewSession/Search/区头+Group-by 菜单/树列表/Foot Settings 占位)。 treeStore=插件自建 SnapshotStore:订 sessions.list → 物化派生(cwd 分组→project 节点;parentId→session 树;排序)+expandedIds+query。组件订 treeStore,不在 render 里派生。 注入面(inject from RootBinding):{ useTree: treeStore.useSelector, actions: { open, create, toggleSidebar } }(经 b.ctx.layout/b.ctx.sessions 绑定)。 状态点:P-I 数据源两态(running→ongoing 环/无);四色组件齐(ui-primitives State 件),done/error/琥珀数据源记台账。

7. ui-conversation

// ── ViewMap(强类型视图注册) ──
export interface ConversationViewMap {}            // 本包 merge {chat};ui-trajectory merge {trajectory,waterfall}
// merge 目标路径(2026-07-22 对账,§6b 双入口后类型真身在 client 半边):
//   declare module '@deepseek-ai/dsh-client-ui-conversation/client'(对根路径 merge 挂不到真身)
declare module '@deepseek-ai/dsh-client-ui-conversation' { interface ConversationViewMap { chat: {} } }
export interface ViewEntry<Id extends ViewId = ViewId> {
  id: Id; label: string; order?: number
  component: FC<ConvViewProps>
  chrome?: { header?: FC<ChromeProps>; footer?: FC<ChromeProps> }   // 统计条挂点(chat 用 footer)
}
export interface ChromeProps { sessionId: SessionId; useSession: UseSession }

// ── ConversationService(scope 寻址,无 sessionId 参数) ──
declare module 'cordis' { interface Context { conversation: ConversationService } }
export interface SelectionTarget { turnSeq: number; stepSeq?: number; callId?: CallId; toolName?: string }
export class ConversationService {
  // 方法从 caller ctx 读 scope(scopeOf);root ctx 调用=throw。跨 session: ctx.sessions.scope(id)!.conversation.xxx
  send(text: string, mode: 'queue' | 'steer'): Promise<void>
  cancel(): Promise<void>
  readonly selection: SnapshotStore<SelectionTarget | null>   // per-scope 实例账(挂 scope 树)
  openDetails(target: SelectionTarget): void       // 写 selection + ctx.layout.openDetails()(编排;布局态归 layout)
  registerView<Id extends ViewId>(entry: ViewEntry<Id>): () => void
  readonly drafts: SnapshotStore<string>           // per-scope;persist keyed by sessionId
}
export interface ConvViewProps {
  sessionId: SessionId; useSession: UseSession
  useSelection: SnapshotSelectorHook<SelectionTarget | null>
  actions: { openDetails(t: SelectionTarget): void; loadOlder(): void }
  slots: ScopedSlots<never>                        // chat P-I 无子坑转授(toolview 走具名注册表)
}

// ── ToolViewRegistry(agent/session 维度 P-I 生效) ──
declare module 'cordis' { interface Context { toolviews: ToolViewRegistry } }
export interface ToolViewProps {
  callId: CallId; toolName: string; block: ToolCallBlock   // frozen 切片
  useSession: UseSession
  actions: { openDetails(): void }                 // 已绑本 call
  t: Translate
}
export class ToolViewRegistry {
  register(tool: string, component: FC<ToolViewProps>,
           opts?: { scope?: (sessionId: SessionId) => boolean; inject?: InjectFactory<any> }): () => void
  resolve(tool: string, sessionId: SessionId): ResolvedToolView | undefined
  // 解析序:scope 匹配(注册序后者优先) > 无 filter 全局(同样后者优先——后装插件可覆写默认渲染) > undefined(渲染方 fallback GenericToolCard)
}
// 验收样例:bash 专属行注册两份(全局一份+某 scope filter 一份)证明差异渲染链路

// 组件归属: 骨架=ConversationRoot(Header面包屑+按钮排+ViewSwitcher+composer)/InputBar 平移/EmptyState 同组件转场
//   chat=ChatView/MessageItem/AssistantMarkdown 平移改注入取数/ToolRow 5形态/GenericToolCard/统计行(chrome.footer)
//   details=DetailsPanel 极简(关闭钮+选中 call 的 args/result 展示)

// 模型/effort 选择器: P-I 只显示(host describe 供数)不可改——写回窗口等后端能力,记台账
// ── 空态首发链(先创建再 send;figma NEW SESSION 屏)──
// 无选中会话时 conversation 坑渲染 EmptyState:居中大输入框+左上角 project(cwd)下拉
//   (选项=sessions.list 派生的 cwd 集合+"新目录"输入;figma New Session 按钮的隐藏下拉箭头即此)
// 提交: conversation.startSession({ cwd?, text, mode }) —— root 语境专用方法(不吃 scope):
//   ① await ctx.sessions.create({cwd}) ② ctx.layout.open(id)(SessionProvider 随之建 scope)
//   ③ 经新 scope 的 conversation.send(text) —— 三连由 service 内部编排,组件一次调用
// EmptyState 的输入框与常态 InputBar 是同一组件(空态转场裁决§0.1-8),仅 onSend 绑定不同

8. ui-trajectory / ui-theme / i18n / ui-primitives

  • ui-trajectory:merge ConversationViewMap{trajectory,waterfall}+registerView 两条+占位实现(span 粗糙即可,不要求效果);chrome.header 挂统计条(第二客户实证)。不挂任何 ctx service——判断:它只消费(registerView+useSession 派生渲染),无人需要调用它;插件≠必有 service,纯消费型插件零 declare merge(这也是"最小插件"样板)。
  • ui-theme:ThemeService { register(id, tokens): ()=>void; apply(id): void; current(): string }。token 体系=用户提供的 cssdesign/ 两份文件(design-platform.css + gradient-shadow-text.css),统一用这套 --dsw-* 变量,不沿用现有代码任何色值(figma 报告 token 表仅对照参考)。结构:static 色阶层(--dsw-static-*)+ alias 语义层(--dsw-alias-*)+ 渐变/阴影/blur/字体;暗色=body[data-ds-dark-theme] 属性整段覆写——apply(id) 的实现=切换该 body 属性。两份 CSS 由 web 壳作为 base 样式表直接引入(成品变量表,不经 register 转译);ThemeService 管"选哪套"+将来第三方主题(=覆写同名 --dsw-alias-*)。全体组件只准用 --dsw-* 变量,禁 hardcode 色值/阴影/渐变(lint 面)。单占冲突 throw。
  • i18n:I18nService { register(ns, locale, dict): ()=>void; bind(ns): Translate; locale: SnapshotStore<string> };Translate=(key, params?)=>string;zh/en 空结构起步。
  • ui-primitives:纯 React 组件(零 cordis):<StateDot state>四色件/ic_ds_* SVG 图标族(figma-flows 供数)/Button/Pill/Menu/Input 原子件/markdown 渲染族(现 components/ 迁入)。全部只吃 props+token vars。

9. 构建、分发、装载(bundle loader 全链)

9.1 插件构建(每个 UI 插件包)——闭包+依赖注入,无 global

bundle 编译为闭包工厂:external 依赖不挂全局,作为初始化入参注入(require 形态,但解析源=cordis 依赖注入实体):

// dist/client.js 的产物形状(tsdown banner/footer 包装):
window.DSHClientProxy.loadPlugin({
  id: 'ui-sidebar',
  factory: (require) => {           // require: (spec: string) => unknown
    const React = require('react')                          // ← loader 从模块表供给
    const { createSnapshotStore } = require('@deepseek-ai/dsh-client-web-react')
    ...bundle 主体(tsdown external→require 调用)...
    return { apply, ...其余导出 }   // 模块导出面:apply=cordis 插件本体;整面登记进模块表供后装者 require
  },
})
  • 模块表归 client loader(机件由壳静态持有,§0.3):壳 boot 时播种纯库实体 { react, react-dom, cordis, ui-slots, web-react, ui-primitives };插件 bundle 的 factory 返回其模块导出面(含 apply),loader 装载成功后以包名登记进模块表——inject 拓扑保证后装者可 require 先装者(如 ui-sidebar require runtime 的 scopeOf、i18n 的消费面)。loader 在 load 时把绑定了模块表的 require 递给 factory;要什么查什么,查无=fail loud(明确报缺依赖)。无 window.DSH.* 命名空间、无 import map;将来换机制只动 loader 的 require 实现。
  • zod 等非共享依赖照旧内联。
  • CSS 随插件:CSS Modules 产物内联进 client.js(执行时注入 <style data-plugin="<id>">);隔离=CSS Modules 哈希+插件 id 归属;unload 时 loader 统一移除;插件包禁 :global 越界与裸元素选择器(token 变量除外)。
  • dev 工作流:tsdown --watch 重编译+手动刷新,无 HMR(后置)。

9.2 分发与 boot 注入(host 侧改动=webPlugins 注册表+收编已验收端点+HTML 注入)

  • HostWebPluginRegistry(host 侧,webserver 属地):订阅 Loader 加载面+读 package.json dshClient 声明(client 产物取 exports["./client"])构建 web 插件表(§0.3 发现机制);声明式,无 serve() 调用面。
  • GET /plugins/<id>/client.js(cordis-web B5 已验收端点收编,文件源=该表)。
  • GET / 渲染 index.html 时注入:
window.__DSH_BOOT__ = {
  plugins: [{ id, url: '/plugins/<id>/client.js', inject: [...], immediately?: true }, ...],
  // = HostWebPluginRegistry(host Loader 实际加载 ∩ 带 dshClient 声明);
  //   immediately=true 组先并行装载(组内 inject 拓扑),全组就位后其余按 inject 拓扑
}

零往返:HTML 到手即知拉什么。host 插件集变化=重启生效(config 同源既有裁决)。fixture server 注入同一协议。 P-I config 同源达标口径(2026-07-21 仲裁):现 dsh web 链无 Loader 实例——host/runtime 装配层挂 @cordisjs/plugin-loader 并 loader.create×八包(内存树)即达标;yml 文件形态后置(届时装/卸=改文件重启)。HostWebPluginRegistry 订阅面:vendor/loader 无「加载完成」单事件,走 registry 遍历 entries()+订 internal/plugin 去抖重扫(实勘结论,rt-core)。

9.3 web 壳(M8)

  • 无 global 挂载:共享依赖经 loader 的模块表+require 注入(§9.1);壳只负责把这些实体在 boot 时递给 loader 构造模块表。
  • AppRoot:第一帧 boot 骨架(logo/loading);layout 插件到达后被顶层坑替换;SessionProvider/scopedSlots 装配在此闭合(deps 注入)。
  • 构建管线:vite 管壳自身;插件包各自 tsdown;dsh web serve 壳 dist+插件 dist+注入端。归 ui-shell(构建模板)+rt-core(serve/注入)协调。

10. P-I 台账(明知偏差)

# 偏差 触发
1 侧栏状态点仅 running/无 两态有数据 P-II approvals+通知
2 details 极简(无三段/步进/深链) P-II/III
3 trajectory/waterfall 占位不要求效果 P-III 实装
4 scope filter 仅函数形态 第三方声明式需求
5 loader 无完整性校验(第一方产物) 第三方插件
6 无 HMR(dev=重编译+刷新) 工作流优化期
6b 无渐进渲染/Suspense(等 settled 一次成型;单插件失败=loading 页 fail loud) 局部依赖加载立项(renderSuspenseSlot 设计已议,届时恢复)
7 全纯 client 插件 已撤销:UI 插件=双入口(node 半边 serve 注册+client 半边);./shared 仍零实例(P-I 无跨线契约需求,首个私有 wire 插件激活封存设计) —

11. 现有代码迁移映射

现文件 去向
web-runtime/src/{connection,api,events,intents,web-api-client,fixture}.ts → connection/(git mv,导出面不变)
web-runtime/src/session/* → runtime/src/sessions/(git mv;Session 两行加法)
web-runtime/src/boot.ts → runtime/src/kernel/ 改造
web-runtime/src/{store,rpc-log}.ts store 评估并入 sessions;rpc-log 删除(D18)
web-ui/src/sessiontabs/conversation/* → ui-conversation/(git mv;容器改注入取数;PendingCard 保留现状挂 chat 流)
web-ui/src/components/* → ui-primitives/(markdown 族等,git mv)
web-ui/src/shell/+leftmenu/ AppShell/SessionsPanel 参考重写;三 registry/RpcLog 族删除
apps/web → web/ 包(vite/index.html;最小 diff 自选)