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

121 lines
13 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.
# tsconfig host/client 拆分实施细案(fw-react 起草 2026-07-22;只方案不实施)
> **终态修订(2026-07-22 收敛刀)**:用户裁定不设 tsconfig.host.json——根 tsconfig.json 恢复 host 聚合原职(含 exclude packages/client),新增文件只有 tsconfig.client.json;typecheck=`tsc -b tsconfig.json tsconfig.client.json`;verify-cordis-config 双聚合并列种子。下文 §1 的 host.json/根壳化描述为拆分期中间形态,已被此终态取代;其余结论(A 类切断/归属表/刀序)不变。
> 背景:client 与 host 对 cordis Context 的 declare merge 撞名(`sessions`:SessionStore vs SessionsService;`loader`:vendor Loader vs ClientLoader)。用户已拍板**多 tsconfig 拆分**方向;v3 §4.0 ClientContext 裁决作废待删。本文=可执行细案,执行者按刀序照做。
> 现状确诊(盘上核实):**撞名发生在两层**——①根聚合 program(tsconfig.json 的 include 把全仓 tests 聚成单 program,127 个 references 的 d.ts 同室,llm-retry/app-boot 两处红即此);②**client leaf program 内部也撞**(packages/client/runtime/src/index.ts:48 FIXME 实证:runtime 自己的 `sessions: SessionsService` merge 与经 connection→dsh-session index d.ts 链进来的 host merge 同 program)。**拆聚合只治①;②必须切类型链**(见 §4)。
## 1. 拆分后 tsconfig 文件清单
### 1.1 文件表
| 文件 | 性质 | 内容 |
|---|---|---|
| `tsconfig.base.json` | 不动 | paths/编译选项唯一源(两侧共用) |
| `tsconfig.host.json` | **新增**(= 现 tsconfig.json 改名收缩) | noEmit 聚合:include=现 root include 全部 + `"exclude": ["packages/client/**"]`;references=现 127 条**减去 12 条 packages/client/**(vendor/cordis 等保留——host 本来就用) |
| `tsconfig.client.json` | **新增** | noEmit 聚合:include=`packages/client/*/tests/**/*.ts` + `**/*.tsx`;compilerOptions 加 `"jsx": "react-jsx"`、lib 加 DOM;references=12 条 packages/client/*(传递闭包由各包自身 tsconfig references 供给,聚合不重复列 host leaf) |
| `tsconfig.json` | **保留为纯壳** | `{ "extends": "./tsconfig.base.json", "files": [], "references": [{host},{client}] }`——编辑器/工具的目录锚点;**不再有 include**(单 program 聚合就此消灭) |
| `tsconfig.vitest.json` | 改 extends | 见 §3 |
| `tsconfig.build.json` | 不动 | build 走各 leaf 项目自身 program,与聚合无关(但受 §4 A 类切断的益处:client leaf 不再见 host merge) |
要点:
- **12 条 client references 名单**(从现 tsconfig.json 平移):ui-slots/ui-primitives/web-react/connection/runtime/ui-layout/ui-sidebar/ui-conversation/ui-trajectory/ui-theme/i18n/web。
- client 聚合**不列** vendor/cordis、host leaf(llm/session/tools/brand/apiproxy/user-approval/user-interaction/invariants)——`tsc -b` 自动沿各包 references 建传递闭包;聚合只列直接被 include 文件 import 的项目根。若 client specs 直接 `import 'cordis'`(web-react specs 不用,ui-* client specs 会),再补 vendor/cordis 一条即可,执行时以报错为准。
- client 聚合首次纳管 `.tsx` specs(现 root include 只到 `.ts`,全部 tsx spec 至今零 tsc 检查)——预期首跑翻出存量 tsx 类型错,属还债,修在同刀。
- 编辑器行为不回退:VS Code 对 tests 文件本就取最近包级 tsconfig(不含 tests),现状=inferred project,拆后相同。
### 1.2 typecheck 两跑接线
```jsonc
// package.json
"typecheck": "tsc -b tsconfig.host.json tsconfig.client.json"
```
- `tsc -b` 原生支持多入口,两入口各成独立 build graph(共享 leaf 的 .tsbuildinfo,重复 leaf 只 build 一次);一条命令仍是一个 pnpm script。
- **scripts/run-gates.ts 零改动**:所有 `pnpmScript('typecheck', 'typecheck')` 调用点(ci-primary/node-compat/pre-push)走 pnpm script 间接层,脚本名不变即全链生效。
- `clean:build` 的 `*.tsbuildinfo` glob 已覆盖新聚合的增量文件,不用动。
## 2. 横跨面归属判定表
判定依据三条(按序适用):A=运行宿主(node→host / 浏览器→client);B=类型链是否需要见 client declare merge(需要→client);C=tests 归其 src 所在侧。
| 范围 | 归属 | 依据 |
|---|---|---|
| packages/client/* 12 包(src 经 references,tests 经 include) | client | A+B |
| vendor/*(cordis/cosmokit/schemastery…) | **两侧闭包共享 leaf**,tests(如有)归 host | cordis 是双方 peer;leaf project 单 build 无冲突(cordis 自身不 merge sessions/loader) |
| packages/host/apiproxy | **双侧闭包共享 leaf**;tests 归 host | 协议同构测试跑 node(InProcessApiClient);client 只经 /api、/client 两子路径吃类型 |
| packages/host/runtime、host/webserver | host | A(node 载体) |
| packages/hooks/* | host | A(Claude/Codex hook 桥全是 node 进程件) |
| packages/ui/*(acp/tui/jsonrpc/app-boot/commands/permission/tool-ask-user) | host | A(node 侧 UI 桥;「ui」前缀勿与 client/ui-* 混淆) |
| packages/ui/user-approval、user-interaction | **双侧闭包共享 leaf**(apiproxy api 引其 id/answer 类型);tests 归 host | B(§4 切断后 client 只见其 /types) |
| packages/core/session、llm/llm、core/tools、util/brand | 双侧闭包共享 leaf;tests 归 host | 同上(wire 词汇表供方) |
| packages/support/*(invariants/testkit/llm-replay/acp-snapshot/loader-smoke) | host;invariants 兼双侧闭包 leaf | client 各包 src/invariant.ts import dsh-invariants(其 cordis merge key 不撞 client,容忍,见 §4 C 类) |
| packages/web/*(web-search 能力座——**≠ packages/client/web**,命名雷区) | host | A |
| 其余全部 packages/*(core/bash/fs/lsp/skill/compact/context/subagent/workflow/todo/guard/cordis/session-persistence/sdk/examples…) | host | A |
| examples/*(根目录 cordis.yml leaves) | host | A(node 启动) |
| apps/cli | host | 对 client 的唯一触点是 `require.resolve('@deepseek-ai/dsh-client-web/dist/index.html')`(apps/cli/src/web.ts:33)——运行时资产路径,非类型依赖;执行时核实 apps/cli 是否本就在根 graph 内(现 references 未见其条目),不在则维持现状 |
| scripts/**、website/** | host | A(node 工具与站点构建;verify-client-closure 等脚本读 client 包是当数据读,非 import) |
**无「双跑」条目**:所有共享包都是 leaf project 单 build、两聚合各自引用,不存在同一 tests 进两个 program 的情形。
## 3. tsconfig.vitest.json 拆不拆:不拆,改挂靠
结论:**不拆**。该文件只是 vite-tsconfig-paths 的路径映射范围(文件头注释自证「never a tsc -b target」),vitest 只转译不 typecheck——**specs 的类型隔离由 §1 两聚合负责,不由 vitest 面负责**;运行时不存在 declare merge 冲突问题。
唯一必要改动:现 `"extends": "./tsconfig.json"` 在根壳化(files:[] 无 include)后仍能继承 paths(paths 住 base,经 extends 链传递),**但为了不依赖壳的形态,直接改 `"extends": "./tsconfig.base.json"`**,include 原样保留。验收:`pnpm exec vitest run packages/client/web-react/tests/store.spec.ts`(映射面探针)照过。
## 4. A 类残余:拆聚合后 client program 仍见 host declare 的链路与最小切断点
### 4.1 确诊链路
TS 规则:import 某模块即装载其文件级 module augmentation。client 侧现存两条进水管:
```
① connection/src/api.ts ──→ '@deepseek-ai/dsh-session' index ──→ declare module 'cordis' { sessions: SessionStore } ★撞 runtime 的 SessionsService
② connection ──→ '@deepseek-ai/dsh-host-apiproxy/{api,client}' ──→ apiproxy 内部 import session/llm/user-approval/user-interaction 的 index ──→ 同上循环
```
`loader` 撞名无 A 类残余:client 侧零 import @cordisjs/plugin-loader(执行时 grep 复核),拆聚合即断根。
C 类(容忍不切):dsh-invariants、dsh-llm/dsh-tools 等的 cordis merge key(invariants/llm/tools)不与 client key 撞——是类型宇宙污染但不报错;浏览器产物纯度另由 verify-client-closure 把守,本案不扩大。
### 4.2 最小切断点(精确到文件+行;原则=新增类型子路径出口,绕开 index 的 augmentation 装载)
**新增出口四件**(各包 package.json exports + tsconfig.base.json paths 各一条)。闭包纯度已盘上复核:三份 types/presentation 文件自身零 augmentation;**但 session/src/types.ts:2 与 tools/src/presentation.ts:8 都 import `'@deepseek-ai/dsh-llm'` 主入口**——llm index 的 merge key=`llm` 属 C 类不撞,A 类切断不受影响;将来要清 C 类时改这两行指 llm/types 即可(本案不做)。plugin-loader 已复核 client src 零 import,`loader` 撞名拆聚合即断根(§4.1 结论盘上证实):
| 包 | 新子路径 | 指向 | 供出类型 |
|---|---|---|---|
| dsh-session | `./types` | src/types.ts(已存在) | SessionId、SessionEvent 族 |
| dsh-llm | `./types` | src/types.ts(已存在) | ContentBlock、StreamChunk、CallId |
| dsh-tools | `./presentation` | src/presentation.ts(已存在) | ToolCallView、ToolResultView |
| dsh-user-approval / dsh-user-interaction | `./types` | **新建 src/types.ts**(现纯类型窝在 index.ts,先抽出、index 再 re-export——两包仅 index+invariant,抽取量小) | ApprovalRequestId/ApprovalOutcome;AskUserQuestionItem/AskUserQuestionAnswer |
**改写点清单**(import specifier 机械替换,逻辑零改):
client 侧:
- packages/client/connection/src/api.ts:12(dsh-tools→/presentation)、:20(dsh-session→/types)、:21(dsh-llm→/types)
- packages/client/connection/src/fixture.ts:8(llm)、:9(session)
- packages/client/runtime/src/sessions/:conversation.ts:6、partial.ts:5、session.ts:6-7、fold-adapter.ts:6(执行时以 `grep -rn "'@deepseek-ai/dsh-\(session\|llm\|tools\)'" packages/client/{connection,runtime}/src` 取全量,防列漏)
apiproxy 内部(②管的根治点):
- src/api/rpc.ts:11、events.ts:9-12、approvals.ts:8-9、approvals.schema.ts:8、sessions.ts:7-8、sessions.schema.ts:9、questions.ts:8、questions.schema.ts:8、events.schema.ts:8(同样以 grep 取全量)
**augmentation 靶点保持不动**:apiproxy src/api/sessions.ts:12 的 `declare module '@deepseek-ai/dsh-llm'`(ContentBlockMap 扩块)继续指向主入口不改——它是 host program 的合法 merge,client program 不装载该文件即不受影响;执行时以 host 聚合绿+client 聚合绿双验。
**验收探针(④专属)**:切断后在 client 聚合跑一个负样本文件(临时 spec:`const s: import('cordis').Context['sessions'] = null as never` 的类型应解析为 SessionsService 而非 union/error),加 `grep -rn "from '@deepseek-ai/dsh-session'" packages/client --include='*.ts'` 应零命中(/types 除外)。
## 5. 执行刀序(每刀独立绿、可停可续)
| 刀 | 内容 | 验收命令 |
|---|---|---|
| 刀1 | 四件类型子路径出口(session/llm/tools 加 exports+paths;user-approval/user-interaction 抽 types.ts+出口)。纯加法 | `pnpm run typecheck`(现单 program 仍绿)+ `pnpm run build`(exports 面有效) |
| 刀2 | apiproxy 内部 9 处 import 切 /types(②管根治) | `pnpm run typecheck` + `pnpm exec vitest run packages/host/apiproxy` |
| 刀3 | client 侧 connection/runtime 全量 import 切换(§4.2 清单+grep 兜底) | `pnpm exec tsc -b packages/client/runtime packages/client/connection` + 该两包 vitest |
| 刀4 | client/runtime 复原契约 merge:删 index.ts:48 FIXME、`sessions: SessionsService` 落 declare(rt-core 属地,**由 rt-core 执行**,本刀序仅排位) | `pnpm exec tsc -b packages/client/runtime` 绿(此刻 host merge 已不可见) |
| 刀5 | 两聚合落地:新增 tsconfig.host.json/tsconfig.client.json、根壳化 tsconfig.json、package.json typecheck 两跑、tsconfig.vitest.json 改 extends | `pnpm run typecheck`(两 program 各绿)+ llm-retry/app-boot 红消失复核 + vitest 映射探针 |
| 刀6 | tsx specs 还债(刀5 首跑翻出的存量 tsx 类型错,按包分派属地 owner 修) | `pnpm exec tsc -b tsconfig.client.json` 绿 |
| 刀7 | 文档收尾:v3 §4.0 ClientContext 裁决删除+architecture §2 分层表补两聚合一行(**main 属地**,契约文档只有 main 可改) | `pnpm run doc-sync`(免门禁期可缓) |
刀1-3 与刀5 可并行推进后合流(1-3 不动聚合、5 不动包内 import),但**刀4 必须在 3 之后、刀5 的 client 聚合绿依赖 3+4 齐**。风险位:①types.ts 闭包不纯(暗 import index)→ 执行刀1 时 grep 即知,若不纯先做同包内类型下沉小刀;②tsx 还债量未知 → 刀6 独立成刀就是为了不阻塞主链。
—— 以上。执行前待 main 审:§2 归属表(尤其 apps/cli 维持现状项)、§5 刀4 的 rt-core 排程。