better-dsh 0.2.2-c → 0.2.3-c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/50_test-reports/2026-09-06-write/345/267/245/345/205/267sandbox/345/215/207/347/272/247/351/200/217/344/274/240bug/345/244/215/345/217/221/345/217/212/346/214/202/350/265/267-/344/272/213/344/273/266/346/212/245/345/221/212.md +133 -0
- package/docs/50_test-reports/2026-09-08-hashline-edit-E_RANGE_UNVERIFIED/350/267/250/350/275/256/344/274/232/350/257/235/351/224/256/345/244/261/346/225/210-/350/257/212/346/226/255/346/212/245/345/221/212.md +226 -0
- package/docs/50_test-reports/upstream-dsh-0.1.3-alpha.2-local-test-report.md +44 -0
- package/docs/50_test-reports/upstream-dsh-0.1.3-alpha.2-report.md +110 -0
- package/docs/50_test-reports/upstream-dsh-0.1.5-rc.2-local-test-report.md +79 -0
- package/docs/50_test-reports/v0.2.3b-hashline-content-locator/345/256/236/346/265/213/346/212/245/345/221/212.md +73 -0
- package/docs/upstream-dsh-0.1.5-rc.2-report.md +156 -0
- package/lib/client/index.js +60 -51
- package/lib/index.d.ts +2 -3
- package/lib/index.js +1203 -1278
- package/package.json +2 -2
- package/docs/50_test-reports/v0.1.8d_artifacts/README.md +0 -138
- package/docs/50_test-reports/v0.1.8d_artifacts/code-mode-repl-only.observation.md +0 -74
- package/docs/50_test-reports/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.jsonl +0 -3890
- package/docs/50_test-reports/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.w-sample-0435.jsonl +0 -544
- package/docs/50_test-reports/v0.1.8d_artifacts/functions.json +0 -592
- package/docs/50_test-reports/v0.1.8d_artifacts/skills-catalog.snapshot.md +0 -30
- package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.output-schemas.json +0 -1236
- package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.python.txt +0 -592
- package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.typescript.txt +0 -516
- package/docs/50_test-reports/v0.1.8d_artifacts/wire-vs-transcription.diff.md +0 -54
- package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/Cargo.lock +0 -7
- package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/Cargo.toml +0 -6
- package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/src/bin/messy.rs +0 -8
- package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/src/main.rs +0 -4
- package/docs/50_test-reports/v0.2.0b_artifacts/hashline-probe.md +0 -5
- package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/Cargo.lock +0 -7
- package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/Cargo.toml +0 -7
- package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/build.rs +0 -4
- package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/src/main.rs +0 -13
- package/docs/60_exploration-and-research/01-cordis-runtime/bun-compile-cordis-runtime-bootstrap-research.md +0 -536
- package/docs/60_exploration-and-research/01-cordis-runtime/cordis-customization-and-override-mechanics.md +0 -418
- package/docs/60_exploration-and-research/01-cordis-runtime/cordis-research.md +0 -350
- package/docs/60_exploration-and-research/01-cordis-runtime/dsh-cordis-hotplug-mcp-patch-research.md +0 -265
- package/docs/60_exploration-and-research/02-dsh-webui/dsh-web-profile-package-map.md +0 -186
- package/docs/60_exploration-and-research/02-dsh-webui/dsh-web-ui-slot-system-research.md +0 -310
- package/docs/60_exploration-and-research/02-dsh-webui/dsh-webui-backend-data-inventory.md +0 -633
- package/docs/60_exploration-and-research/02-dsh-webui/dsh-webui-strip-boundary-research.md +0 -300
- package/docs/60_exploration-and-research/02-dsh-webui/dsh-webui-wire-appendix.md +0 -3729
- package/docs/60_exploration-and-research/02-dsh-webui/web-frontend-composability-research.md +0 -191
- package/docs/60_exploration-and-research/02-dsh-webui/webui-wire-data/capture-live-turn.json +0 -1
- package/docs/60_exploration-and-research/02-dsh-webui/webui-wire-data/observed-endpoints.json +0 -224
- package/docs/60_exploration-and-research/02-dsh-webui/webui-wire-data/remote-inventory.json +0 -110954
- package/docs/60_exploration-and-research/02-dsh-webui/webui-wire-data/served-index-sample.html +0 -47
- package/docs/60_exploration-and-research/02-dsh-webui/webui-wire-data/session-events.json +0 -9411
- package/docs/60_exploration-and-research/02-dsh-webui/webui-wire-data/ws-frame-examples.json +0 -20
- package/docs/60_exploration-and-research/03-mobile-ios/dsh-mobile-spa-ios-input-experience-research.md +0 -160
- package/docs/60_exploration-and-research/03-mobile-ios/ios-chat-app-bridge-research.md +0 -324
- package/docs/60_exploration-and-research/04-session-storage/alpha5-compaction-jsonl-mapping.md +0 -1532
- package/docs/60_exploration-and-research/04-session-storage/alpha5-compaction-sample/episode-failed.json +0 -46
- package/docs/60_exploration-and-research/04-session-storage/alpha5-compaction-sample/episode1.json +0 -1124
- package/docs/60_exploration-and-research/04-session-storage/alpha5-compaction-sample/episode2.json +0 -1240
- package/docs/60_exploration-and-research/05-dashr-dev/plugin-development.md +0 -148
- package/docs/60_exploration-and-research/05-dashr-dev/upstream-alignment.md +0 -102
- package/docs/60_exploration-and-research/README.md +0 -87
- package/docs/60_exploration-and-research/bun-compile-cordis-runtime-bootstrap-research.md +0 -348
- package/docs/60_exploration-and-research/cordis-research.md +0 -350
- package/docs/60_exploration-and-research/dsh-mobile-spa-ios-input-experience-research.md +0 -160
- package/docs/60_exploration-and-research/dsh-web-profile-package-map.md +0 -186
- package/docs/60_exploration-and-research/dsh-web-ui-slot-system-research.md +0 -310
- package/docs/60_exploration-and-research/dsh-webui-strip-boundary-research.md +0 -300
- package/docs/60_exploration-and-research/ios-chat-app-bridge-research.md +0 -324
- package/docs/60_exploration-and-research/web-frontend-composability-research.md +0 -191
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
# DSH Web Profile 包分类测绘
|
|
2
|
-
|
|
3
|
-
> 记录:2026-08-31 · 一手核验:`~/.dsh/profiles/node_modules/@deepseek-ai/`(220 包,dsh-alpha
|
|
4
|
-
> 4.x 全量)+ `~/workspaces/dsh-alpha/packages/` 源码(client 各包 src import 逐文件 grep)。
|
|
5
|
-
> 范围:Web Profile(含 headless-only 包一并列出,但标注为"非 Web")。
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 1. 一句话结论
|
|
10
|
-
|
|
11
|
-
**DSH Web 的依赖不是一条干净的"核心 → 中间层 → UI"三层管道,而是"双轨":**
|
|
12
|
-
|
|
13
|
-
- **运行时数据**走干净的管道:`核心(service) → 中间层(api-controller RPC) → UI`。
|
|
14
|
-
- **类型**却是**双轨旁路**:UI 层**跳过中间层,直接 import 底层核心的类型**
|
|
15
|
-
(`dsh-session/types`、`dsh-llm`、`dsh-agent`、`dsh-scope`)。
|
|
16
|
-
|
|
17
|
-
**量化证据**:40 个 `dsh-client-ui-*` 包里,**17 个直接 import 底层核心**(session/llm/agent/scope),
|
|
18
|
-
共约 70 个文件;其中 `ui-conversation`、`ui-chat` 各 14 个文件。这是"UI 直接 import 底层"的铁证。
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## 2. 数据流验证(你问的核心问题)
|
|
23
|
-
|
|
24
|
-
### 2.1 UI 直接 import 底层核心(跳过中间层)
|
|
25
|
-
|
|
26
|
-
```
|
|
27
|
-
ui-conversation: 14 文件
|
|
28
|
-
ui-chat: 14 文件
|
|
29
|
-
ui-workspace: 5 文件
|
|
30
|
-
ui-workflow-run: 3 文件
|
|
31
|
-
ui-trajectory: 3 文件
|
|
32
|
-
ui-subagent: 3 文件
|
|
33
|
-
ui-input-trigger: 3 文件
|
|
34
|
-
ui-message-feedback: 2 文件
|
|
35
|
-
ui-goal: 2 文件
|
|
36
|
-
ui-commands: 2 文件
|
|
37
|
-
+ 7 个包各 1 文件(session / skill / plan / model-selection / deliverables / approval / user-questions)
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
具体例子(`ui-session/src/client/index.ts`):
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
import type { SessionId } from '@deepseek-ai/dsh-session/types' // 直接 import 底层
|
|
44
|
-
import type {} from '@deepseek-ai/dsh-api-session-controller/client' // 同时 import 中间层
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### 2.2 判定
|
|
48
|
-
|
|
49
|
-
**是,UI 层既走中间层(拿运行时数据),又跳过中间层(拿类型)。** 两条轨:
|
|
50
|
-
|
|
51
|
-
| 轨 | 路径 | 内容 |
|
|
52
|
-
|---|---|---|
|
|
53
|
-
| 数据轨 | 核心 service → api-controller → RPC → UI | 运行时数据(会话列表、transcript、模型) |
|
|
54
|
-
| 类型轨 | 核心 types → UI **直接 import** | `SessionId`/`SessionEvent`/`ContentBlock` 等形状 |
|
|
55
|
-
|
|
56
|
-
**这正是"UI 类型耦合在底层、不在中间层"的机制层证据**——你之前问"alias dsh-session 能不能
|
|
57
|
-
只服务前端 100 行",答案是**不能**,因为前端 17 个包、约 70 个文件直接 import 了 dsh-session/
|
|
58
|
-
dsh-llm 的类型,alias 必须覆盖这些。
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## 3. 四层分类清单(220 包)
|
|
63
|
-
|
|
64
|
-
### ① Cordis 框架底层(15)
|
|
65
|
-
|
|
66
|
-
| 组 | 包 |
|
|
67
|
-
|---|---|
|
|
68
|
-
| 框架核心 | `cordis` |
|
|
69
|
-
| 框架插件 | `cordis-plugin-loader` `cordis-plugin-include` `cordis-plugin-group` `cordis-plugin-timer` `cordis-plugin-hmr` |
|
|
70
|
-
| 框架配套 | `cosmokit` `schemastery` `node-addon-landlock-run` |
|
|
71
|
-
| 共享基础 | `dsh-invariants` `dsh-brand` `dsh-timeout` `dsh-atomic-write` `dsh-util-crypto` `dsh-util-workspace-path` |
|
|
72
|
-
|
|
73
|
-
### ② DSH 核心底层(约 140,含 headless)
|
|
74
|
-
|
|
75
|
-
| 组 | 包 |
|
|
76
|
-
|---|---|
|
|
77
|
-
| 入口/自举 | `dsh` `dsh-base` `dsh-app-boot` `dsh-home-paths` `dsh-launch-environment` |
|
|
78
|
-
| host 基建 | `dsh-host-webserver` `dsh-host-frontend-static` `dsh-host-directory-picker`(+auto/browse/native) `dsh-host-plugin-inventory` |
|
|
79
|
-
| **Session(17)** | `dsh-session` `dsh-session-persistence` `dsh-session-persistence-jsonl` `dsh-session-projection` `dsh-session-projection-cache` `dsh-session-query` `dsh-session-query-sqlite` `dsh-session-title` `dsh-session-title-first-prompt-llm` `dsh-session-title-llm` `dsh-session-log-deepseek` `dsh-session-log-export` `dsh-session-stats` `dsh-session-telemetry` `dsh-session-telemetry-otel` `dsh-session-checkpoint-policy` `dsh-session-reference` |
|
|
80
|
-
| **LLM(5)** | `dsh-llm` `dsh-llm-deepseek` `dsh-llm-pi-ai` `dsh-llm-retry` `dsh-deepseek-llm-api-extensions` |
|
|
81
|
-
| **Agent(7)** | `dsh-agent` `dsh-agent-loop` `dsh-agent-presets` `dsh-agent-default-model` `dsh-agent-instructions` `dsh-agent-tool-presentation` `dsh-agent-spine-demo` |
|
|
82
|
-
| scope/workspace | `dsh-scope` `dsh-workspace` |
|
|
83
|
-
| **Tools(20)** | `dsh-tools` `dsh-tool-{ask-user,bash,bash-persistent,call-timeout-policy,cordis,fs,fs-search,goal,jobs,pwsh,pwsh-persistent,ralph,skill,str-replace-editor,subagent,subagent-control,subagent-report,todo,web,workflow}` |
|
|
84
|
-
| 命令/技能 | `dsh-commands` `dsh-command-{compact,feedback,goal}` `dsh-skill` `dsh-skill-{badge,filesystem}` |
|
|
85
|
-
| 子代理/子进程 | `dsh-subagent` `dsh-subagent-{fork-in-process,in-process-driver,spawn-in-process}` `dsh-subprocess` `dsh-subprocess-local` |
|
|
86
|
-
| shell/sandbox/fs | `dsh-shell` `dsh-shell-env` `dsh-bash-{local,sandbox}` `dsh-pwsh-{local,sandbox}` `dsh-sandbox` `dsh-sandbox-{local,policy,windows-acl}` `dsh-fs` `dsh-fs-{local,observation-policy,sandbox}` `dsh-win32-process` |
|
|
87
|
-
| code-runtime | `dsh-code-runtime` `dsh-code-runtime-worker-thread` |
|
|
88
|
-
| 存储/spill/压缩 | `dsh-storage` `dsh-storage-{domain,json}` `dsh-spill` `dsh-spill-{local,policy}` `dsh-compaction` `dsh-compaction-{basic,tool-result-pruner}` |
|
|
89
|
-
| jobs/attachment/credential | `dsh-jobs` `dsh-jobs-local` `dsh-attachment` `dsh-attachment-local` `dsh-credentials` `dsh-credentials-local` |
|
|
90
|
-
| 权限/审批 | `dsh-permission-presets` `dsh-authorization` `dsh-user-approval` `dsh-user-questions` |
|
|
91
|
-
| 消息/引用 | `dsh-message-feedback` `dsh-file-reference` `dsh-file-reference-local` |
|
|
92
|
-
| goal/plan/persona/system | `dsh-goal` `dsh-goal-round-driver` `dsh-plan-mode` `dsh-persona` `dsh-system-prompt` `dsh-schedule` `dsh-time-context` `dsh-tmux-context` |
|
|
93
|
-
| hooks | `dsh-hook-protocol` `dsh-hooks-claude-code` `dsh-hooks-codex` |
|
|
94
|
-
| MCP/杂项 | `dsh-mcp-client` `dsh-token-meter` `dsh-output-retention` `dsh-native-command` `dsh-repeat-tool-reminder` `dsh-settings` `dsh-settings-file` `dsh-workflow` `dsh-workflow-worker-thread` `dsh-plugin-package-inventory-deepseek` |
|
|
95
|
-
| *headless-only* | `dsh-headless` `dsh-cmdline` `dsh-terminal` `dsh-terminal-bash` |
|
|
96
|
-
|
|
97
|
-
### ③ DSH 中间层(API/RPC 转接,约 16)
|
|
98
|
-
|
|
99
|
-
| 组 | 包 |
|
|
100
|
-
|---|---|
|
|
101
|
-
| API BFF | `dsh-api-gateway` `dsh-api-remotes` `dsh-api-session-controller` `dsh-api-settings-controller` `dsh-api-workspace-controller` |
|
|
102
|
-
| RPC 机制 | `dsh-typert-protocol` `dsh-typert-registry` `dsh-typert-loader` |
|
|
103
|
-
| RPC 传输 | `dsh-client-connection` |
|
|
104
|
-
| 双半 runner | `dsh-cordis-host-runner` `dsh-cordis-client-runner` |
|
|
105
|
-
| ACP | `dsh-acp` `dsh-acp-app` |
|
|
106
|
-
| SDK | `dsh-sdk-app` `dsh-sdk-jsonrpc-server` `dsh-sdk-minimal` `dsh-sdk-protocol` |
|
|
107
|
-
|
|
108
|
-
### ④ DSH UI 层(约 50)
|
|
109
|
-
|
|
110
|
-
| 组 | 包 |
|
|
111
|
-
|---|---|
|
|
112
|
-
| **UI 组件(40)** | `dsh-client-ui-{agent-preset,approval,attachment,brand-official,chat,commands,conversation,cordis,deliverables,directory-picker-browse,directory-picker-native,goal,input-trigger,jobs,layout,message-feedback,model-selection,permission-presets,plan,reference,renderer,session,settings,settings-general,settings-models,settings-plugin-inventory,settings-plugins,sidebar,skill,subagent,theme,tool,trajectory,user-questions,workflow-run,workspace}`(40 个) |
|
|
113
|
-
| client 基建 | `dsh-client-modules` `dsh-client-locale` `dsh-client-hmr` `dsh-client-store` |
|
|
114
|
-
| web 组装 | `dsh-web` `dsh-web-app` `dsh-web-frontend` |
|
|
115
|
-
| web 工具 | `dsh-web-fetch-http` `dsh-web-search-deepseek` `dsh-webhook` `dsh-webhook-github` |
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## 4. Mermaid 分层图
|
|
120
|
-
|
|
121
|
-
```mermaid
|
|
122
|
-
flowchart TD
|
|
123
|
-
subgraph L1["① Cordis 框架底层"]
|
|
124
|
-
cordis["cordis"]
|
|
125
|
-
cp["cordis-plugin-loader / include / group / timer / hmr"]
|
|
126
|
-
base["cosmokit · schemastery · node-addon-landlock-run"]
|
|
127
|
-
shared["dsh-invariants · dsh-brand · dsh-timeout · dsh-atomic-write · dsh-util-*"]
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
subgraph L2["② DSH 核心底层"]
|
|
131
|
-
boot["dsh · dsh-base · dsh-app-boot · dsh-home-paths"]
|
|
132
|
-
host["dsh-host-webserver · host-frontend-static · host-directory-picker · host-plugin-inventory"]
|
|
133
|
-
session["dsh-session · session-persistence · session-projection · session-query · session-title · session-log · session-stats · session-telemetry"]
|
|
134
|
-
llm["dsh-llm · llm-deepseek · llm-pi-ai · llm-retry · deepseek-llm-api-extensions"]
|
|
135
|
-
agent["dsh-agent · agent-loop · agent-presets · agent-default-model · agent-instructions"]
|
|
136
|
-
scope["dsh-scope · dsh-workspace"]
|
|
137
|
-
tools["dsh-tools · dsh-tool-*(20)· dsh-command-* · dsh-skill-* · dsh-subagent-*"]
|
|
138
|
-
runtime["dsh-subprocess · shell · bash · pwsh · sandbox · fs · code-runtime · storage · spill · compaction · jobs · attachment · credentials"]
|
|
139
|
-
policy["dsh-permission-presets · authorization · user-approval · hook-protocol · mcp-client"]
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
subgraph L3["③ DSH 中间层(API/RPC 转接)"]
|
|
143
|
-
api["dsh-api-gateway · api-remotes · api-session-controller · api-settings-controller · api-workspace-controller"]
|
|
144
|
-
rpc["dsh-typert-protocol · typert-registry · typert-loader · client-connection"]
|
|
145
|
-
runner["dsh-cordis-host-runner · cordis-client-runner"]
|
|
146
|
-
ext["dsh-acp · acp-app · dsh-sdk-app · sdk-jsonrpc-server · sdk-protocol"]
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
subgraph L4["④ DSH UI 层"]
|
|
150
|
-
renderer["dsh-client-ui-renderer(SlotRegistry + React mount)"]
|
|
151
|
-
shell["dsh-client-ui-layout · sidebar · theme"]
|
|
152
|
-
chat["dsh-client-ui-chat · session · conversation · trajectory · message-feedback"]
|
|
153
|
-
model["dsh-client-ui-model-selection · agent-preset · permission-presets"]
|
|
154
|
-
ws["dsh-client-ui-workspace · goal · plan · jobs · workflow-run · deliverables"]
|
|
155
|
-
misc["dsh-client-ui-* 其余(approval · attachment · commands · reference · skill · subagent · tool · user-questions · input-trigger · settings-* · directory-picker-* · cordis · brand-official)"]
|
|
156
|
-
infra["dsh-client-modules · client-locale · client-hmr · client-store"]
|
|
157
|
-
web["dsh-web · dsh-web-app · dsh-web-frontend"]
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
L2 -->|"运行时数据(service → RPC)"| L3
|
|
161
|
-
L3 -->|"运行时数据(RPC → 浏览器)"| L4
|
|
162
|
-
L2 -.->|"类型直连:SessionId / SessionEvent / ContentBlock(跳过中间层,17 个 UI 包约 70 文件)"| L4
|
|
163
|
-
L1 --> L2
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## 5. 关键发现汇总
|
|
169
|
-
|
|
170
|
-
1. **不是干净三层管道**:数据走 `核心→中间层→UI`,但**类型走 `核心→UI` 旁路**。
|
|
171
|
-
2. **类型耦合在底层,不在中间层**:`dsh-client-ui-session` 直接 `import { SessionId } from
|
|
172
|
-
'dsh-session/types'`,`ui-chat`/`ui-conversation` 各 14 文件直接 import dsh-session/dsh-llm。
|
|
173
|
-
3. **对桥接层的意义**:要斩断 dsh-session,代价不在 api-controller(中间层),而在 UI 层那
|
|
174
|
-
~70 个文件的类型 import——这才是真正的耦合点。
|
|
175
|
-
4. **中间层(api-controller)是运行时数据的分发器**,不是类型隔离层;类型隔离在 UI 层直接
|
|
176
|
-
import 底层时就破了。
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
## 来源
|
|
181
|
-
|
|
182
|
-
- `~/.dsh/profiles/node_modules/@deepseek-ai/`:220 包清单(`ls`)+ 各包 `package.json` 的
|
|
183
|
-
`description`/`peerDependencies`/`dependencies`。
|
|
184
|
-
- `~/workspaces/dsh-alpha/packages/client/`:`ui-*/src` 逐文件 grep `import ... from
|
|
185
|
-
'@deepseek-ai/dsh-(session|llm|agent|scope)'` 与 `'@deepseek-ai/dsh-api-*-controller/client'`。
|
|
186
|
-
- 关联:`dsh-web-ui-slot-system-research.md`(UI 插槽机制)、`cordis-research.md`(Cordis 服务端)。
|
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
# dsh Web UI 前端插件系统研究
|
|
2
|
-
|
|
3
|
-
> 记录:2026-08-31 · 一手核验:`~/workspaces/dsh-alpha/packages/client/` 源码 +
|
|
4
|
-
> `packages/extensions/cordis-client-runner/` + vendored `@deepseek-ai/*` 4.x 编译产物
|
|
5
|
-
> (`~/.dsh/profiles/node_modules/@deepseek-ai/`,220 包)。
|
|
6
|
-
> 本文是 `cordis-research.md`(Cordis 服务端侧)与 `web-frontend-composability-research.md`
|
|
7
|
-
> (Web 生态通用对照)的**dsh 实测侧**:回答"dsh 的 Web UI 到底是怎么把插件穿透到前端、又
|
|
8
|
-
> 怎么在 React 上渲染出来的"。
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 1. 一句话结论
|
|
13
|
-
|
|
14
|
-
**dsh 的 Web UI 不是"React 组件库",而是把 Cordis 原封不动地搬进了浏览器:每个
|
|
15
|
-
`dsh-client-ui-*` 包是"双半插件"(服务端空 `apply()` + 浏览器端真 `apply(ctx)`),组合靠一套
|
|
16
|
-
框架无关的**插槽注册表(SlotRegistry)**——一个靠 TypeScript `declare module` 声明合并出来的
|
|
17
|
-
类型化 `SlotMap` 定义所有插槽契约,React 只是挂在注册表上的**一个可替换渲染后端**,状态用
|
|
18
|
-
框架无关的 observable 喂给 `useSyncExternalStore`。**
|
|
19
|
-
|
|
20
|
-
判定:这与服务端 Cordis 是**同一哲学的两个投影**——服务端用 `inject`/`provide` 组合 *服务*,
|
|
21
|
-
前端用 `register`/`renderSlot` 组合 *组件*;两者都是"声明式 + 控制反转 + 免重启装卸"。
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## 2. 双半插件(dual-half):一个包,两个编译目标
|
|
26
|
-
|
|
27
|
-
每个 `dsh-client-ui-*` 插件有两个入口文件,编译成两个目标:
|
|
28
|
-
|
|
29
|
-
**host 半**(服务端,空函数——只为让 Cordis Loader 有东西可加载):
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
// packages/client/ui-sidebar/src/index.ts
|
|
33
|
-
/** Host loader entry for the browser-only sidebar plugin. */
|
|
34
|
-
/** Provides no host-side behavior. */
|
|
35
|
-
export function apply(): void {}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**browser 半**(浏览器里真正干活):
|
|
39
|
-
|
|
40
|
-
```ts
|
|
41
|
-
// packages/client/ui-sidebar/src/client/index.ts
|
|
42
|
-
export const inject = ['slots', 'layout', 'uiWorkspace', 'locale']
|
|
43
|
-
|
|
44
|
-
export function apply(ctx: ClientContext): void {
|
|
45
|
-
const workspaceNavigation = ctx.get('uiWorkspace')
|
|
46
|
-
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-sidebar: dictionaries')
|
|
47
|
-
const injectProps = (): SidebarRootInjected => ({
|
|
48
|
-
startSession: (workspaceId) => workspaceNavigation.startSession(workspaceId),
|
|
49
|
-
toggleSidebar: () => ctx.layout.toggleSidebar(),
|
|
50
|
-
})
|
|
51
|
-
ctx.effect(
|
|
52
|
-
() => ctx.slots.register({ name: 'sidebar', children: {...}, inject: injectProps }, SidebarRoot),
|
|
53
|
-
'ui-sidebar: slot registration',
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
关键点:`client/index.ts` 的 `apply(ctx)` 里有 `inject` 数组(`['slots', 'layout', ...]`)和
|
|
59
|
-
`ctx.effect(...)`——**和服务端 Cordis 插件完全一样的写法**。它跑在浏览器里,是因为下面第 8 节
|
|
60
|
-
的 `dsh-cordis-client-runner` 在浏览器里又起了一套 Cordis。
|
|
61
|
-
|
|
62
|
-
判定:服务端"组合服务"和前端"组合 UI"用的是**同一套心智模型**,只是换了个宿主(Node 进程 →
|
|
63
|
-
浏览器页面)。
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
## 3. 组合 = Slot:框架无关的类型化插槽注册表
|
|
68
|
-
|
|
69
|
-
前端的组合**不是** React 的 props 钻孔,也不是 React Context,而是一个**纯插槽注册表**
|
|
70
|
-
(`SlotCore`,无 cordis、无 React):
|
|
71
|
-
|
|
72
|
-
```ts
|
|
73
|
-
// packages/client/ui-slots/src/index.ts
|
|
74
|
-
export interface SlotMap {} // 空接口,靠各插件 declare module 合并
|
|
75
|
-
export interface LocaleNamespaceMap {}
|
|
76
|
-
export type SlotKind = 'single' | 'list' | 'keyed' | 'chain'
|
|
77
|
-
export type SlotScope = 'root' | 'session-maybe' | 'session'
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
每个插件通过 **TypeScript 声明合并** 往 `SlotMap` 上"合并"进自己的插槽名,于是**类型层面就知道
|
|
81
|
-
每个插槽的契约**:
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
// packages/client/ui-sidebar/src/client/contract/slots.ts
|
|
85
|
-
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
86
|
-
interface SlotMap {
|
|
87
|
-
'sidebar.brand.mark': ...
|
|
88
|
-
'sidebar.brand.name': ...
|
|
89
|
-
'sidebar.workspaces': ...
|
|
90
|
-
'sidebar.settings': ...
|
|
91
|
-
'sidebar.footer.action': ...
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
这与服务端 Cordis 的 `declare module '@deepseek-ai/cordis' { interface Context { slots: ... } }`
|
|
97
|
-
是**同一个机制**(`ui-renderer` 就是用它把 `ctx.slots`、`ctx.uiRenderer` 合并进 cordis Context)。
|
|
98
|
-
|
|
99
|
-
组合是**一棵树**:owner 注册时声明 `children`(子槽),occupant 填充别人的槽:
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
-
root(内置,唯一的 ctx 级槽)
|
|
103
|
-
└─ layout 区域(ui-layout 拥有,声明 'sidebar' 等区域槽)
|
|
104
|
-
└─ sidebar 壳(ui-sidebar 注册)
|
|
105
|
-
├─ sidebar.workspaces ← ui-workspace 填充
|
|
106
|
-
├─ sidebar.settings ← ui-settings 填充
|
|
107
|
-
├─ sidebar.brand.mark ← ui-brand 填充
|
|
108
|
-
└─ sidebar.footer.action(list 槽,多插件追加)
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
判定:**"谁声明槽"和"谁填槽"是两个正交的插件集**,靠 `SlotMap` 的类型合并保证编译期契约匹配
|
|
112
|
-
——这是它比"字符串 key + `any` props"的普通插件系统(如 VS Code views)高明的地方。
|
|
113
|
-
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
## 4. 插槽的两个轴:基数(kind)与作用域(scope)
|
|
117
|
-
|
|
118
|
-
```ts
|
|
119
|
-
type SlotKind = 'single' | 'list' | 'keyed' | 'chain' // 单占位 / 有序列表 / 按 key 分发 / 选择器路由链
|
|
120
|
-
type SlotScope = 'root' | 'session-maybe' | 'session' // 全局 / 可选当前会话 / 严格绑定会话
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
- **kind** 决定一个槽能容纳几个 occupant、怎么分发(`single` 一个、`list` 按顺序叠加、`keyed`
|
|
124
|
-
按 key 分发、`chain` 用选择器决定路由)。
|
|
125
|
-
- **scope** 决定 occupant 是否/如何拿到当前 Session(`session` 槽会被注入 `sessionId` +
|
|
126
|
-
`SessionProvider` 座,`session-maybe` 在无会话时收到 `undefined`,`root` 不接触会话)。
|
|
127
|
-
|
|
128
|
-
判定:基数轴覆盖了 UI 扩展点的四种真实形态;作用域轴把"会话数据怎么流进 UI"做成了类型级约束
|
|
129
|
-
(`PropsRuntime` 按 scope 收窄),而不是靠运行时约定。
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## 5. React 渲染:整个 App = 一个 `root` 槽
|
|
134
|
-
|
|
135
|
-
这是最反直觉的一步——**整个 React 树只有一个入口**:
|
|
136
|
-
|
|
137
|
-
```tsx
|
|
138
|
-
// packages/client/ui-renderer/src/client/app.tsx
|
|
139
|
-
export function buildRenderApp(deps): () => ReactNode {
|
|
140
|
-
const { ctx } = deps
|
|
141
|
-
return () => ctx.slots.renderSlot('root', {}) // ← 整个 app 就这一句
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
```ts
|
|
146
|
-
// packages/client/ui-renderer/src/client/index.ts
|
|
147
|
-
export function apply(ctx: Context): void {
|
|
148
|
-
const slots = new SlotRegistry(ctx)
|
|
149
|
-
slots.install(createSlotRenderer()) // ← 把 React 渲染器装进插槽注册表
|
|
150
|
-
ctx.reflect.provide('uiRenderer', {
|
|
151
|
-
mount: (container) => {
|
|
152
|
-
const root = mountApp(container, buildRenderApp({ ctx })) // createRoot / hydrateRoot
|
|
153
|
-
return () => root.unmount()
|
|
154
|
-
},
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
`SlotRegistry` 是 cordis 侧对 `SlotCore` 的包装;`createSlotRenderer()` 是**唯一一个 React 绑定**。
|
|
160
|
-
React 挂载后只渲染 `renderSlot('root')`,渲染器递归展开插槽树、找到每个槽的 occupant 渲染出来。
|
|
161
|
-
|
|
162
|
-
还有一段 boot 水合细节(`mountApp`):如果容器里已有 kernel 预渲染的 `[data-dsh-boot]` DOM,就走
|
|
163
|
-
`hydrateRoot` 保活;否则 `createRoot` + `flushSync` 首帧。这是"框架无关的 boot DOM"→"React 接管"的
|
|
164
|
-
交接。
|
|
165
|
-
|
|
166
|
-
判定:**插槽注册表和状态都是框架中立的;React 只是"当前安装的那个渲染器"。** 理论上换一个
|
|
167
|
-
渲染器(如 preact / solid)只换 `createSlotRenderer()`,插槽树和业务插件一行不用改。
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
## 6. 每个插槽组件的 props 是"组合"出来的
|
|
172
|
-
|
|
173
|
-
一个 slot 组件收到的 props 是五份的交集(`ComposedProps`):
|
|
174
|
-
|
|
175
|
-
| 份额 | 类型 | 来源 |
|
|
176
|
-
|---|---|---|
|
|
177
|
-
| **owner 分享** | `PropsRuntime`(`OwnerOf` + `KeyPropsOf` + scope 标准件) | 父级 `renderSlot` 调用点传的数据 |
|
|
178
|
-
| **render 分享** | `PropsRenderSlots<S>` | 子插槽的 `renderSlot` 函数,**静态收窄到声明的 `children`** |
|
|
179
|
-
| **store hooks** | `PropsStore` | 从 observable 源合成的 `use<Name>` 选择器 hook |
|
|
180
|
-
| **locale** | `PropsLocale` | `t` 翻译函数(按命名空间收窄 key) |
|
|
181
|
-
| **inject** | `InjectFace` | owner 注入的 `inject` 工厂(`hooks` 成员被转成 hook) |
|
|
182
|
-
|
|
183
|
-
侧栏的 `injectProps` 就是第 5 份的实例:把 `startSession`/`toggleSidebar` 通过 `inject` 工厂塞给
|
|
184
|
-
`SidebarRoot`,而 `SidebarRoot` 自己只消费 `PropsRenderSlots<'sidebar.brand.mark' | ...>` 去渲染
|
|
185
|
-
它声明的 5 个子槽。
|
|
186
|
-
|
|
187
|
-
判定:props 的每一份都有**编译期来源**(类型系统里能追到"谁给了什么"),不是运行时拼出来的
|
|
188
|
-
黑盒。这是它能"有求必应"又"不越权"的根基——owner 只拿到自己声明的 children 的 `renderSlot`,
|
|
189
|
-
occupant 只拿到 owner 声明的 `owner`/`inject` 分享。
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## 7. 状态 = 框架无关的 observable → useSyncExternalStore
|
|
194
|
-
|
|
195
|
-
业务状态**不依赖 React**,是 `HostObservable<T>`(`getSnapshot` + `subscribe`)。React 绑定用
|
|
196
|
-
`useSyncExternalStore` 把它变成 hook:
|
|
197
|
-
|
|
198
|
-
```ts
|
|
199
|
-
// packages/client/ui-renderer/src/client/bindings.tsx
|
|
200
|
-
export function observableHook<T>(source: HostObservable<T>): SnapshotSelectorHook<T> { ... }
|
|
201
|
-
export function maybeObservableHook<T>(source: HostObservable<T> | undefined) { ... } // 缺席保持 Hook 调用顺序稳定
|
|
202
|
-
export function keyedObservableHook(source: KeyedStandardSource | undefined) { ... }
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
一个 store 的 snapshot 变成组件里注入的 `use<Name>(selector)` hook(`standardHookPropName` 把源名
|
|
206
|
-
`foo` 转成 `useFoo`)。`use-sync-external-store` 是为了在 React 18 里拿到并发安全的订阅。
|
|
207
|
-
|
|
208
|
-
判定:**状态层是框架中立的,React 是纯渲染绑定。** 这和第 5 节同构——插槽和状态都跟框架解耦,
|
|
209
|
-
所以"换渲染器"不是说说而已。
|
|
210
|
-
|
|
211
|
-
---
|
|
212
|
-
|
|
213
|
-
## 8. 浏览器里的 Cordis + RPC 传输
|
|
214
|
-
|
|
215
|
-
`dsh-cordis-client-runner`(`packages/extensions/cordis-client-runner/src/client/`)在**浏览器里再
|
|
216
|
-
起一套完整 Cordis**,逐个调每个 UI 插件的 `client/index.ts` 的 `apply(ctx)`。它带齐了 cordis 的
|
|
217
|
-
运行时件:`runtime.ts`、`providers.ts`、`evaluator.ts`、`guard.ts`、`timer.ts`、`slot-catalog.ts`、
|
|
218
|
-
`orchestrator.ts`(页面侧的 run 编排 / 审批 / 面板手势)。
|
|
219
|
-
|
|
220
|
-
浏览器 → 服务端的通信是 **WebSocket 上的 Typert RPC**:
|
|
221
|
-
|
|
222
|
-
| 包 | 角色 |
|
|
223
|
-
|---|---|
|
|
224
|
-
| `dsh-client-connection` | 认证 RPC 传输(`ws`),生命周期管理 |
|
|
225
|
-
| `dsh-api-gateway` | Typert Remote Host 分发器(`ws` + schemastery 校验) |
|
|
226
|
-
| `dsh-api-remotes` | 客户端 RPC 桩(BFF 组装,会话/工作区/设置等面) |
|
|
227
|
-
| `dsh-api-*-controller` | 服务端 BFF 后端(session/workspace/settings) |
|
|
228
|
-
|
|
229
|
-
判定:浏览器里的 Cordis 服务通过 `inject` 拿到的是**远程能力的本地代理**(`dsh-api-remotes` 桩),
|
|
230
|
-
而非直接 import 服务端实现。这条 RPC 边界就是"前端插件"和"后端服务"的分界——也是我们桥接层
|
|
231
|
-
(omp-webui)要替换数据源的那条缝。
|
|
232
|
-
|
|
233
|
-
---
|
|
234
|
-
|
|
235
|
-
## 9. 完整链路(一张图)
|
|
236
|
-
|
|
237
|
-
```mermaid
|
|
238
|
-
flowchart TD
|
|
239
|
-
subgraph Browser["浏览器(dsh-cordis-client-runner 再起一套 cordis)"]
|
|
240
|
-
R["ui-renderer<br/>SlotRegistry + createSlotRenderer + mount"]
|
|
241
|
-
L["ui-layout<br/>拥有 root 布局树,声明 'sidebar' 等区域槽"]
|
|
242
|
-
S["ui-sidebar<br/>注册 'sidebar' 槽 + 5 个子槽"]
|
|
243
|
-
W["ui-workspace / ui-settings<br/>填 sidebar.workspaces / sidebar.settings"]
|
|
244
|
-
C["ui-chat / ui-session / ui-conversation<br/>聊天区"]
|
|
245
|
-
T["client-connection(WS)+ api-remotes(RPC 桩)"]
|
|
246
|
-
R -->|"ctx.slots.renderSlot('root')"| L
|
|
247
|
-
L --> S --> W
|
|
248
|
-
end
|
|
249
|
-
subgraph Host["宿主(Node cordis)"]
|
|
250
|
-
G["dsh-api-gateway(typert 分发)"]
|
|
251
|
-
A["api-session/workspace/settings controller"]
|
|
252
|
-
D["dsh-session / dsh-llm / dsh-agent(核心,供数据)"]
|
|
253
|
-
G --> A --> D
|
|
254
|
-
end
|
|
255
|
-
T -->|"WebSocket Typert RPC"| G
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
---
|
|
259
|
-
|
|
260
|
-
## 10. 方法论提炼:为什么这么设计
|
|
261
|
-
|
|
262
|
-
把 dsh 前端这套机制抽象成三条方法论,与 Cordis 服务端一一对应:
|
|
263
|
-
|
|
264
|
-
1. **组合 = 类型化的注册表,不是字符串约定。** 服务端 `declare module '@deepseek-ai/cordis'`
|
|
265
|
-
合并 `Context`;前端 `declare module '@deepseek-ai/dsh-client-ui-slots'` 合并 `SlotMap`。插槽
|
|
266
|
-
契约是编译期检查的,不是文档口头约定。
|
|
267
|
-
2. **框架中立的内核 + 可替换的渲染后端。** `SlotCore`(无 cordis/React)+ observable(无 React)
|
|
268
|
-
是内核;`createSlotRenderer()` 是唯一 React 绑定。对应 Cordis 的"框架内核 + 插件树"。
|
|
269
|
-
3. **双半部署。** 一个插件包编译成 host 半(空 `apply()`)+ browser 半(真逻辑),由 cordis 的
|
|
270
|
-
`cordis-client-runner` 在两端各自加载。对应 Cordis 的"同一插件在两个运行时里各有一半"。
|
|
271
|
-
|
|
272
|
-
判定:这套东西的难点不在"能组合"(Web Components / MF 都能),而在**"依赖驱动的、反应式的、
|
|
273
|
-
类型安全的组合"**——`web-frontend-composability-research.md` §5 说 Web 生态缺的"反应式依赖激活",
|
|
274
|
-
dsh 前端是用 **cordis 本身**补上的(浏览器里再跑一个 cordis),而不是在 DOM 层重造。
|
|
275
|
-
|
|
276
|
-
---
|
|
277
|
-
|
|
278
|
-
## 11. 对 omp-webui 桥接层的意义
|
|
279
|
-
|
|
280
|
-
这份研究直接回答了我们前几轮纠结的问题:
|
|
281
|
-
|
|
282
|
-
- **"能不能只复用 UI、自己写 HTTP 服务器?"** —— 不能干净地做。UI 插件是 dual-half,它的
|
|
283
|
-
browser 半靠 `dsh-cordis-client-runner` 跑在浏览器 cordis 里、通过 `dsh-api-remotes` 的 RPC 桩
|
|
284
|
-
调服务端;UI 的"前端组件"和"后端服务"不是 REST 边界,是 Typert RPC 边界。要复用 UI 就得
|
|
285
|
-
把服务端 RPC 那半也搭起来。
|
|
286
|
-
- **"我们的桥接层到底替换了哪一段?"** —— 替换的是**数据源服务**(`dsh-session-persistence`
|
|
287
|
-
→ OMP 适配器、`dsh-agent-loop` → OMP provider、`dsh-agent-presets` → 单模式 roster),即第 8 节
|
|
288
|
-
RPC 链**服务端**那一侧的几个 provider。**前端插槽树、渲染器、RPC 协议一行没动。**
|
|
289
|
-
- **"YAML 组合"的设想本来就存在。** 服务端是 `cordis.patch.yml`(定义加载哪些插件),前端是
|
|
290
|
-
`SlotMap` + `register`/`renderSlot`(定义 UI 怎么组合)。我们要加 UI 面板,正确做法是**注册一个
|
|
291
|
-
槽的 occupant**(`ctx.slots.register(...)`),而不是去改渲染器或重写插槽机制。
|
|
292
|
-
|
|
293
|
-
---
|
|
294
|
-
|
|
295
|
-
## 来源
|
|
296
|
-
|
|
297
|
-
- dsh-alpha 源码(`~/workspaces/dsh-alpha/packages/`):
|
|
298
|
-
- `client/ui-sidebar/src/index.ts`(host 半空 `apply()`)、`client/ui-sidebar/src/client/index.ts`
|
|
299
|
-
(browser 半 `ctx.slots.register`)、`client/ui-sidebar/src/client/contract/slots.ts`(SlotMap 合并)
|
|
300
|
-
- `client/ui-slots/src/index.ts`(`SlotMap`/`SlotKind`/`SlotScope`/`SlotCore`)、
|
|
301
|
-
`client/ui-slots/src/renderer.ts`(框架中立契约 `SlotRendererHost`/`SlotRenderer`)
|
|
302
|
-
- `client/ui-renderer/src/client/app.tsx`(`renderSlot('root')`)、
|
|
303
|
-
`client/ui-renderer/src/client/index.ts`(`SlotRegistry` + `createSlotRenderer` + `mount`)、
|
|
304
|
-
`client/ui-renderer/src/client/bindings.tsx`(observable → `useSyncExternalStore`)
|
|
305
|
-
- `extensions/cordis-client-runner/src/client/orchestrator.ts`(浏览器 cordis 编排)
|
|
306
|
-
- vendored 编译产物:`~/.dsh/profiles/node_modules/@deepseek-ai/{dsh-web-app,dsh-web-frontend,
|
|
307
|
-
dsh-cordis-client-runner,dsh-client-connection,dsh-api-gateway,dsh-api-remotes,dsh-client-ui-renderer,
|
|
308
|
-
dsh-typert-protocol}/package.json`(description + peerDependencies 取证的依赖图)
|
|
309
|
-
- 关联研究:`cordis-research.md`(Cordis 服务端时空可组合性)、`web-frontend-composability-research.md`
|
|
310
|
-
(Web 生态通用对照,本文是其 dsh 实测侧)
|