dsh-auto-flow 0.1.2 → 0.1.3

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/README.md CHANGED
@@ -4,26 +4,86 @@ DeepSeek Harness 插件 Bundle(Host 半 + Client 半同包),一个**确定
4
4
 
5
5
  - **@Remote 严格模式**(`AutoFlowService`)——Host 服务,浏览器经 `ctx.remote.autoFlow` 调用,双向 zod 校验 + 完整类型,契约由 `pnpm typert:gen` 生成(`lib/typert.*`,提交进 git)。
6
6
  - **工作流编辑器**:React Flow 画布 + 节点系统,画布经 `loadFlow`/`saveFlow` 落 domain storage。
7
- - 编排节点:表单(数据源)/ 运行脚本(playwright-ag)/ 人工审批(屏障)。
8
- - 值传递节点:文本模板 / AI 模型 / 图片识别 / 执行命令 / HTTP 请求 / 条件分支 / 输出,
9
- 支持 `{{input.*}}`、`{{steps.<节点id>}}`、`{{current}}` 插值与 true/false 分支剪枝。
10
- - **执行引擎**:按层级并发调度脚本节点(同层独立脚本并行),审批节点是串行人工屏障;脚本经 playwright-ag 运行,产物落 `<workspaceDir>/<runId>/<index>-<script>/`,人工在审批面板对产物做增删改查后放行/驳回。
11
- - **可运行脚本目录**:脚本清单(展示名 / 描述 / 参数 / 产物装配规则)由 playwright-ag 脚本自描述、经 `listScripts` 下发;本插件对具体业务脚本零感知。
12
-
13
- > English TL;DR: A dsh plugin bundle implementing a deterministic workflow editor + engine — strict `@Remote` RPC, a React Flow canvas with a node system (form / script / approval), concurrent script execution with serial human-approval gates, and workspace artifact CRUD. Scripts run via the playwright-ag plugin.
7
+ - 编排节点:表单(数据源)/ 运行脚本(ag)/ 运行本地脚本 / 人工审批(屏障)。
8
+ - 值传递节点:文本模板 / AI 模型 / AI 智能体 / 调用工作流 / 图片识别 / 执行命令 / HTTP 请求 /
9
+ 条件分支 / 展开列表 / 输出,支持 `{{input.<字段>}}`、`{{item.<字段>}}`、`{{节点名.<字段>}}`
10
+ 插值与分支剪枝。`item` 是**当前条目**(`each` 模式逐条、`all` 模式取输入口首条),`节点名`
11
+ 指**已执行的上游节点**;按 id 寻址的旧写法 `{{steps.<节点id>}}` 已废弃(写它会被校验拒绝)。
12
+ - **分支排除是一等语义**:条件节点(以及声明了「走错误分支」的节点)把出口选在哪个句柄是
13
+ **显式事实**;未选中分支的整棵下游**从不执行**(不是「跑了拿到空值」),步骤上带
14
+ `excludedBy`(谁排除的、可追溯到来源节点)。排除是**推导**出来的标记,来源改选后随之下落
15
+ —— 不是一标即定局。
16
+ - **引用静态闭合**:运行前逐条校验引用 —— 节点必须存在、必须是本节点的**上游**(跨分支引用报错)、
17
+ 字段必须落在该节点**声明的产出字段域**里(形状不可静态确定的节点按 `unknown` 容错,只跳过字段级)。
18
+ 断引用在设计期报错,而不是运行期静默拿到空串/null。
19
+ - 节点定义是**单一注册表**(`shared/workflow/src/node-defs/`):类型、展示名、参数、模板字段、
20
+ 运行前必填、产出字段域、执行槽位都写在一处,宿主校验与画布表单共读同一份;加一种值节点只写一个
21
+ 定义文件 + 在注册表里加一行。
22
+ - 节点级**错误策略**(停止整轮 / 失败继续 / 走错误分支 / 默认值兜底)与**重试**(次数夹取到上界,
23
+ 确定性失败不重试)在属性面板设置;`attempts[]` 逐次留存输入与错误。
24
+ - **item 流**:节点按**执行模式**跑 —— `all` 整批进整批出,`each` 对输入逐条各跑一遍
25
+ (条目级并发 / 失败上限 / 条目出错策略都是节点级设置);「一个数组字段拆成一批条目」用
26
+ **展开列表节点**(`expand`,照 n8n 的 Split Out)。原先的**迭代节点已被它取代**(不再有
27
+ 循环体出口,`types.ts` 的 `NodePortDataType` 里也没有 `'body'` 了)。
28
+ - **执行引擎**:就绪队列调度(节点的入边全部结算即可起跑,完成即向下游传播),脚本节点受并发上限
29
+ 限流,审批节点是串行人工屏障;脚本经 ag 运行,产物落 `<workspaceDir>/<runId>/<index>-<script>/`,
30
+ 人工在审批面板对产物做增删改查后放行/驳回。引擎本体在 `shared/workflow-engine/`(不认识 dsh、
31
+ 不认识具体节点类型:按定义声明的 `runtime` / `branchByValue` / `runOutput` 调度)。
32
+ - **可运行脚本目录**:脚本清单(展示名 / 描述 / 参数 / 产物装配规则)由 ag 脚本自描述、经 `listScripts` 下发;本插件对具体业务脚本零感知。
33
+
34
+ > English TL;DR: A dsh plugin bundle implementing a deterministic workflow editor + engine — strict `@Remote` RPC, a React Flow canvas with a node system (form / script / approval), concurrent script execution with serial human-approval gates, and workspace artifact CRUD. Scripts run via the ag plugin.
14
35
 
15
36
  ## 能力清单
16
37
 
17
- | 能力 | 位置 |
18
- | ------------------------------ | -------------------------------------------------------------------------- |
19
- | `@Remote` 严格模式 | `src/host/service.ts` 的 `loadFlow` / `saveFlow` / `runFlow` 等 |
20
- | 执行引擎(并发 + 审批 + 取消) | `src/host/engine.ts` + `src/host/service.ts` 的 `cancelRun` |
21
- | 环检测 | `src/workflow/pipeline.ts` 的 `detectCycle` |
22
- | 脚本目录(playwright-ag) | `src/host/service.ts` 的 `listScripts` + `src/client/.../scriptCatalog.ts` |
23
- | 插件 Config + 设置 UI | `src/host/config.ts`(workspaceDir)+ `features/settings` |
24
- | 持久化(domain storage) | `src/host/domain.ts`(flows / runs) |
25
- | 客户端 UI 槽位(slots) | `src/client/features/*`(`conversation.input.dock` 等) |
26
- | 多语言 locale | `src/client/features/*/locales.ts` + `locales.ts` 聚合 |
38
+ | 能力 | 位置 |
39
+ | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
40
+ | `@Remote` 严格模式 | `src/host/service.ts` 的 `loadFlow` / `saveFlow` / `runFlow` 等 |
41
+ | 执行引擎(就绪队列 + 重试 + 审批 + 取消) | `shared/workflow-engine/src/engine.ts`(通用)+ `src/host/engine/`(适配:宿主能力面与各节点执行器) |
42
+ | 执行期错误归一(errno→人话 + 有界投影) | `shared/workflow-engine/src/errors.ts`(`describeError` / `boundErrorText`;写进记录的错误全走这里) |
43
+ | 执行记录(序号 / 取值来源 / 执行区间) | `shared/workflow/src/types.ts` 的 `RunStep` + `shared/workflow-engine/src/engine.ts`(三处时间戳盖章) |
44
+ | 运行日志格式版本与迁移 | `src/host/features/runs/record.ts`(唯一迁移入口)+ `store.ts`(唯一调用点,旧版本不外传) |
45
+ | 断点续跑(暂停实体 + 进度快照) | `src/host/features/runs/record.ts` + `store.ts`(`runs/inflight/`)+ `coordinator.ts` 的 `resumeRun` |
46
+ | 两级超时 | 节点级:审批节点 `timeoutMs`(`shared/workflow/src/approval.ts`);全局:`approvalTimeoutMs` 配置 |
47
+ | 节点定义单一注册表 | `shared/workflow/src/node-defs/`(定义)+ `src/host/engine/nodes/value.ts`(执行器注册表) |
48
+ | 环检测 | `shared/workflow/src/graph.ts` 的 `detectCycle`(经 `pipeline.ts` 转出) |
49
+ | 脚本目录(ag) | `src/host/service.ts` 的 `listScripts` + 前端工程的 `src/workflow/scriptCatalog.ts` |
50
+ | 前端工程 | 仓库同级目录 `../dsh-auto-flow-editor-web`(它自己的依赖、构建、测试与命令见那边的 README) |
51
+ | 插件 Config + 设置 UI | `src/host/config.ts`(workspaceDir)+ `src/client/features/settings/` |
52
+ | 持久化(domain storage) | flows:`src/host/features/flows/domain.ts`;runs:`src/host/features/runs/store.ts` |
53
+ | 客户端 UI 槽位(slots) | 声明:`src/client/contract/slots.ts`;注册:`src/client/apply.ts`(设置行 + 会话内入口:整页浮层) |
54
+ | 客户端 UI 栈 | 宿主半边:dsh 原生 primitives(无组件库出口、无主题桥);前端工程:它自己的 `src/ui/`(antd 出口 + 画布 token 覆盖) |
55
+ | 多语言 locale | `src/client/features/*/locales.ts` + `src/client/locales.ts` 聚合(画布的文案在前端工程的 `src/workflow/locales.ts`) |
56
+ | 画布领域类型(React Flow 泛型绑定) | 前端工程的 `src/workflow/flowchart/canvasTypes.ts`(`CanvasNode` / `CanvasEdge`,四处泛型入口的同一套词汇) |
57
+ | 连线判据(句柄级三条规则) | 前端工程的 `src/workflow/flowchart/connectionRules.ts`(`checkConnection`,画布与测试读同一份) |
58
+ | 画布初值与运行状态 value(引用稳定) | `flowchart/useCanvasSeed.ts`(种子只算一次)+ `workflow/runStatus.tsx` 的 `useRunStatusValue` |
59
+
60
+ ## 结构
61
+
62
+ | 目录 | 内容 |
63
+ | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
64
+ | `src/types.ts` | 本包的对外契约登记(错误码 / 事件名)与共享词汇的转出(浏览器安全,不含 `node:` 依赖) |
65
+ | `shared/workflow/` | 工作流的**纯领域逻辑**:节点定义单一注册表(`node-defs/`)、图与拓扑、模板插值、payload 装配、表单 schema、运行前校验。零依赖宿主概念,宿主与浏览器同时引用 |
66
+ | `shared/workflow-engine/` | 通用图引擎:就绪队列调度、节点级重试与错误策略、执行预算、生命周期事件、注入面契约。**不认识 dsh、不认识具体节点类型** |
67
+ | `src/host/engine/` | 引擎适配层:宿主能力面 `capabilities.ts`、装配 `create-engine.ts`、各节点执行器与执行器注册表 `nodes/`、运行作用域 `context.ts` |
68
+ | `src/host/features/` | 按能力分组的宿主功能:`flows/`(定义与 `/workflow` 命令行)、`runs/`(启动 / 协调 / 运行日志)、`scripts/`、`agent/`、`llm/` |
69
+ | `src/host/utils/` | 宿主侧无状态小工具(`flow-inputs.ts` / `workspace.ts`) |
70
+ | `src/client/` | 浏览器半:`ui/`(antd 主题桥)、`contract/`(槽位声明 `slots.ts`)、`features/`(画布 / 设计器 / 设置) |
71
+
72
+ ## 两层 UI:宿主半边用 dsh 原生,应用单元自决
73
+
74
+ - **宿主半边**(设置行 + 会话内入口/整页浮层)用宿主的 `@deepseek-ai/dsh-client-ui-primitives`:
75
+ 装到宿主页面里的界面就用宿主自己的组件,不引第三方组件库,也不需要主题桥。
76
+ - **前端工程**(仓库同级目录,见下面「应用单元」一节)UI 自决:继续用 **antd 6**,出口在它自己的
77
+ `src/ui/`(`export * from 'antd'` + 它自己的 `DswAntdProvider`)。图标按需走深路径
78
+ `@ant-design/icons/<图标名>` 导入(走包根那个全量 barrel 会 tree-shake 不掉,整个图标库全进产物)。
79
+
80
+ **保留自绘**:React Flow 的节点/连线/主题(`flow-theme.css`、各 `*.module.css`)与
81
+ `designer-controls.tsx` 的自绘图标(plus / control)—— antd 没有图组件。
82
+
83
+ **下拉卡片为什么不是 antd `Dropdown`**:画布内的下拉要「跟随画布」——触发器随画布缩放/平移时
84
+ 卡片必须同步;而 antd `Dropdown` 的弹出层自带一套 rc-trigger 定位,会和跟随画布互相打架。
85
+ 所以用 `node-system/CanvasMenu.tsx`:antd `Menu` + 自管 portal(外壳带 `data-canvas-menu`,
86
+ `useCanvasFollow` 按它查找卡片)+ 自管外部点击 / Esc。契约是**我们自己的**,不依赖第三方 DOM。
27
87
 
28
88
  ## 开发
29
89
 
@@ -31,12 +91,137 @@ DeepSeek Harness 插件 Bundle(Host 半 + Client 半同包),一个**确定
31
91
  pnpm install # 安装全部工作区依赖
32
92
  pnpm typert:gen # 首次必跑:生成 @Remote 契约(lib/typert.*),之后改动签名时重跑
33
93
  pnpm dev # 开发:tsdown --watch 双段(含首次构建)
34
- pnpm typecheck # 三程序检查:Host + Client + 默认程序(tsconfig.json,均不 emit)
94
+ pnpm lint # oxlint 全仓静态检查
95
+ pnpm format # oxfmt 全仓格式化(`format:check` 只检查不写)
96
+ pnpm typecheck # 全仓 typecheck(Host + Client + 默认三程序,均不 emit;依赖 lib/typert.*)
35
97
  pnpm test # 单测:Host 半 + 浏览器半 jsdom(需先跑过 typert:gen)
36
98
  pnpm build # 一次性构建产物(发布用)
37
- pnpm check # 一键门禁:typecheck + test + build(发布前跑)
99
+ pnpm check:fast # 开发中的门禁:只验改动面(所选包的 typecheck + 测试 + 改动文件的 lint/format)
100
+ pnpm check # 阶段边界门禁:lint + format + 4 个派生闸门 + 全仓 typecheck + test(**不含构建**)
101
+ pnpm verify # 交付前:check + build + 构建面守卫 + 冒烟(真实装载 lib/)
102
+ ```
103
+
104
+ 各步耗时不一(取决于机器、改动面与是否冷缓存),故本文不记耗时 —— 每一步都会自报墙钟。
105
+
106
+ ### 应用单元
107
+
108
+ 本插件有**一个应用单元** `editor`(工作流编辑器,会话里那颗「工作流」),跑在插件进程起的自有 origin 上
109
+ (`http://127.0.0.1:<单元端口>`,端口与两半的来源写在 `src/app-units.ts` 的 `APP_UNITS` 登记表里,形态见
110
+ `packages/shared/app-unit/README.md`),宿主那一侧只把带进程令牌的地址 iframe 进来。
111
+
112
+ **声明只有一份**(门禁、工具与运行时读的都是它):
113
+
114
+ ```ts
115
+ export const APP_UNITS = {
116
+ editor: { port: 5173, web: 'artifact', back: 'plugin' },
117
+ } satisfies AppUnits
118
+ ```
119
+
120
+ 下表解释三个字段:
121
+
122
+ | 字段 | 本插件 | 含义 |
123
+ | ------ | ------------ | ------------------------------------------------------------ |
124
+ | `port` | `5173` | 单元服务端口(单元 origin 的一部分) |
125
+ | `web` | `'artifact'` | 前端来自**外部工程**的产物(收进落点 `units/editor/dist`) |
126
+ | `back` | `'plugin'` | 后端是**本插件里的代码**(`…/app-unit/bff.ts` 的 Hono 应用) |
127
+
128
+ 两条都钉住了实现(声明与实现对不上时挂载**响亮失败**,不会静默换一条来源):
129
+
130
+ - 后端那一半的取数面整体依赖宿主能力(列工作流、跑运行、列工作空间目录、列 LLM 路由),搬不进外部
131
+ 进程,故是 `'plugin'`;
132
+ - 前端那一半来自**外部工程**,故是 `'artifact'`。
133
+
134
+ 再加一个单元时只有两处要改:登记表里加一项,外加它自己的 BFF(`back` 是 `'plugin'` 时)。`back: 'none'`
135
+ 那一档留给**自带取数的外部站点**:开发态它的 `/api` 由单元反代透传给该工程的 dev server,产物态它打的
136
+ 是自己打包进去的地址,与本插件无关。`pnpm new:app` 会把这些骨架铺好(登记表与入口清单的哨兵对之间各插
137
+ 一项)。
138
+
139
+ **登记表里几个单元,会话输入区就有几个入口**(清单见 `src/client/dock-units.ts`):眼下是 `editor` →
140
+ 那颗「工作流」。"哪个入口指向哪个单元"写在入口清单里,那里用的是**单元名的字面量** —— 客户端半不许
141
+ import 单元常量(地址只能从 Remote 取,守卫见 `tests/client/apply.spec.tsx`),故"清单与登记表对得上"
142
+ 这件事由同一个 spec 里的一条断言守着。
143
+
144
+ **挂载只有一处**:`src/host/features/app-unit/mount.ts` 里那一次 `mountUnits(connCtx, deps)`
145
+ (每包一份;通用挂载 `mountAppUnits` 按登记表把全部单元各挂一份,加单元不用改它)。
146
+ `src/host/service.ts` 的 `registerAppUnits` 把结果存下来供 `unitUrl` 回答"面板该往哪儿加载"。
147
+
148
+ 前端工程住在**仓库同级目录** `../dsh-auto-flow-editor-web`(它自己的 README 有它那一套命令):UI 栈
149
+ (React / antd / React Flow / Monaco)、构建工具(Vite)、测试与共享词汇的引用都在它自己身上 ——
150
+ 本包(宿主半边)不为它背依赖,也不复制它的源码。
151
+
152
+ **开发态是两条命令**(各在自己的目录里跑):
153
+
154
+ ```sh
155
+ # 1) 前端工程目录里:起它自己的 dev server(端口是它自己的事,本仓不读它的配置)
156
+ pnpm dev
157
+
158
+ # 2) 本仓根:把那个地址告诉宿主(写进插件包根的 .unit-local.json,按单元分节;改完要重启 dsh)
159
+ pnpm unit:dev dsh-auto-flow editor --url http://127.0.0.1:5200 # 地址由我们指定
160
+ pnpm unit:dev dsh-auto-flow # 不带单元时只报告各单元现在的档位
161
+ pnpm unit:dev dsh-auto-flow editor --api-url http://127.0.0.1:<后端 dev 地址> # 后端也反代时
162
+ pnpm unit:dev dsh-auto-flow --clear # 全部单元回到产物
163
+ ```
164
+
165
+ 改画布源码只热更新:不重建插件、不重启 dsh。
166
+
167
+ **一个容易踩的坑**:`--clear` 之后(或从来没写过 `.unit-local.json`)页面走的是**收进来的产物**,不是
168
+ dev server —— 那时改源码当然"没有热更新"(你看到的还是上一次构建的那一版)。判断办法就是看一眼
169
+ `.unit-local.json` 在不在。两者并存也没关系:**反代开着就优先走反代**,产物只在这条反代没开时才被服务。
170
+
171
+ **另一个曾经容易踩的坑(现在不会了)**:那条反代开着、而 dev server 已经停了(`Ctrl+C` 掉了
172
+ `pnpm dev`)时,单元会**回落到落点产物**并在响应头 `x-app-unit-source: artifact` 与宿主日志里标出来源
173
+ —— 页面照旧能开,而"我看到的到底是哪一份"一眼可辨(走 dev server 时是 `dev`)。落点里也没有产物时才是
174
+ 502 + 原因。要彻底回到产物就 `--clear` 再重启 dsh。
175
+
176
+ **发布态**:产物由那一边构建、本仓收进来 ——
177
+
178
+ ```sh
179
+ pnpm unit:collect dsh-auto-flow # 工程位置已记住(.unit-local.json 的 project)
180
+ pnpm unit:collect dsh-auto-flow editor --from ../dsh-auto-flow-editor-web # 换一台机器时
181
+ ```
182
+
183
+ 它先在那个工程里跑它的 `check`(**跑不过就停**),再 `pnpm build`,然后把 `dist/` 拷进
184
+ `units/editor/dist` 并按发布档自检(`--no-check` / `--no-build` 可分别跳过)。第一次给 `--from` 之后
185
+ 它会记住工程位置,下次不必再给。
186
+
187
+ **外部工程单元的命令**(对方工程**零改动**,故地址与工程位置都由本仓这边记住;下面把单元名写成
188
+ `<单元>`、工程目录写成 `<外部工程目录>`):
189
+
190
+ ```sh
191
+ # 开发:先起它自己的 dev server(端口是它自己的事,本仓不读它的配置)
192
+ pnpm unit:dev dsh-auto-flow <单元> --url http://127.0.0.1:5200
193
+
194
+ # 产物:在它的根目录跑它的构建脚本,把产物收进 units/<单元>/dist
195
+ pnpm unit:collect dsh-auto-flow <单元> --from <外部工程目录> \
196
+ --build <对方的构建脚本> --dist <对方的产物目录> --no-check
38
197
  ```
39
198
 
199
+ `--build` / `--dist` 把锚点钉在对方的某个站点上(对方是 monorepo 时,根 `build` 常常会构建全部站点,
200
+ 故这两个参数值得显式给);默认**不跑**对方工程的任何脚本(它有自己的质量闸 —— circular / dep / type /
201
+ cspell,与"收产物"无关),`--no-check` 可跳过它。
202
+
203
+ **已知摩擦:没有**。对方的清单里不写任何 dsh 的东西(端口事实在对方那边),本仓也**不去对方的仓库里读**:
204
+ 地址一律由这边给一次(`--url`),工程位置由 `unit:collect --from` 记进 `.unit-local.json` 的 `project`。
205
+ 不带单元的 `pnpm unit:dev dsh-auto-flow` 现在只**报告**各单元的档位(早先它会在"从对方清单推 dev 端口"这一步
206
+ 抛错,那条推导已随 `dsh.devPort` 一起删除)。
207
+
208
+ **注意:本机运行也读这个落点**。插件在挂载时按登记表找 `<插件包根>/units/editor/dist`:它不在、而
209
+ `.unit-local.json` 里也没有这个单元的 `devUrl` 时,单元**响亮失败** —— 面板上给出原因与两条处置命令
210
+ (`pnpm unit:collect dsh-auto-flow editor` / `pnpm unit:dev dsh-auto-flow editor --url <它的 dev 地址>`),
211
+ 而不是打开一片空白。所以本机要么先把产物收进来(收完**重启 dsh**:挂载时读一次),要么用上面的开发态
212
+ 反代顶上。
213
+
214
+ **契约守卫住在哪**:线上形状与取数面在前端工程侧声明,本包 `src/host/features/app-unit/bff.ts` 是另一
215
+ 半 —— 两侧"同形"由前端工程里那三条守卫钉住(`tests/{bff-contract.spec.ts,type-drift.ts,host-message-contract.spec.ts}`;
216
+ 它们 `link:` 本包源码,故同时看得见两边)。本仓侧不再留它们的副本:那些断言必须同时看见两棵树,而本仓
217
+ 此刻看不见那棵。`unit:collect` 默认跑一次那边的 `check`,就是"发出去的一对同属一个契约"的验证时刻。
218
+
219
+ 两个容易踩的点:`pnpm --filter dsh-auto-flow run build` **不会**建单元产物(它是独立的包)——
220
+ 产物由那个前端包自己的 `vite build` 出;而根目录 `pnpm dev` 因为 `-r` 会把单元的前端 dev server
221
+ 一起拉起来(正是开发时想要的)。单元的前端测试住在前端包自己的 `tests/` 里,由它的 `vite.config.ts`
222
+ 的 `test` 段跑(前端**只有一份**配置);本包的 vitest 配置把 `apps/<插件包名>-<单元名>-web/` 排除在
223
+ 收集之外(不重复跑)。
224
+
40
225
  ### 热更新边界
41
226
 
42
227
  日常开发就是根目录 `pnpm dev`(`tsdown --watch` 同时 watch 两半):
@@ -49,10 +234,14 @@ pnpm check # 一键门禁:typecheck + test + build(发布前跑
49
234
 
50
235
  只想 watch 单面时才用 `pnpm dev:client` / `pnpm dev:host`。
51
236
 
237
+ 热替换只保证「页面里的客户端半跟上产物」,**不保证两半配套**:宿主半不热更(改完要重启 dsh),而重启后
238
+ 页面里仍是重启前注入的那一份客户端半。所以**改完任一半都要重新加载页面**(`Ctrl+Shift+R`)—— 只重启 dsh
239
+ 会留下「宿主已换新、页面还是旧客户端半」的错配,浮层给出的提示会与实际不符。
240
+
52
241
  本地试装到 dsh:
53
242
 
54
243
  ```sh
55
- dsh plugin --profile web add "<workspace>/packages/dsh-auto-flow"
244
+ dsh plugin --profile web add "<workspace>/packages/business/dsh-auto-flow"
56
245
  ```
57
246
 
58
247
  ## @Remote 契约维护
@@ -78,7 +267,7 @@ GitHub 安装免构建。
78
267
 
79
268
  - **结构 / 命名 / 依赖分层**:Host 半 default-export `class X extends TypertRemoteService`,Client 半 `inject` / `apply`;`dsh.bundle.patch` + `dsh.client` 双声明同包;exports / files 分层对齐官方插件。
80
269
  - **两类 schema**:插件 `Config` 用 schemastery(`z`),持久化记录用 zod。
81
- - **注册即 effect**:每个贡献走 `ctx.effect` / 注册表返回的 disposer;可选 seam 用 `ctx.inject([...], cb)`;可选服务(playwright-ag / storageDomain)用 `ctx.get(...)`。
270
+ - **注册即 effect**:每个贡献走 `ctx.effect` / 注册表返回的 disposer;可选 seam 用 `ctx.inject([...], cb)`;可选服务(ag / storageDomain)用 `ctx.get(...)`。
82
271
  - **持久化只进 domain storage**:运行快照落 domain storage(与 session 日志隔离);不追加自定义会话事件。
83
272
 
84
273
  ## 发布
@@ -96,25 +285,45 @@ pnpm publish:npm # prepublishOnly 自动 typert:gen + test + build
96
285
  GitHub(monorepo 仓库,按包打 tag):
97
286
 
98
287
  ```sh
99
- git add -f packages/dsh-auto-flow/lib # 提交产物,GitHub 安装免构建
288
+ git add -f packages/business/dsh-auto-flow/lib # 提交产物,GitHub 安装免构建
100
289
  git tag dsh-auto-flow@v0.1.0
101
290
  git push --tags
102
291
  ```
103
292
 
104
293
  ## 安装(用户侧)
105
294
 
295
+ 本包是 bundle(`package.json` 的 `dsh.bundle.patch`),装法跟着**运行形态**走:
296
+
297
+ | 运行形态 | 安装入口 |
298
+ | ------------------------------ | ---------------------------------------------------------- |
299
+ | 独立 `dsh web` | `dsh plugin --profile web add "<spec>"`(卸载用 `remove`) |
300
+ | 桌面客户端(DeepSeek Harness) | 客户端侧栏 **插件** → **添加插件** → 填同一个 `<spec>` |
301
+
302
+ `desktop` 是客户端保留的 profile,外部 `dsh`(含 npm 全局安装的那个)一律被拒绝:
303
+
304
+ ```
305
+ error: profile "desktop" is managed exclusively by the Electron application
306
+ ```
307
+
308
+ 这不是配置问题,换 profile 也不解决——桌面客户端只能用客户端自己的入口安装。命令行只在完全退出
309
+ 客户端、且改用客户端自带的 `dsh` 时才可用。
310
+
311
+ `<spec>` 可写包名、git 地址、`.tgz` 或本地目录。本地目录的写法两种入口不同:`dsh plugin` 相对/
312
+ 绝对路径都行(相对路径按你执行命令时的目录解析),**客户端插件页只能填绝对路径**(Host 解析 spec
313
+ 时强制 `isAbsolute`,因为浏览器侧没有可解释的 cwd)。
314
+
106
315
  命令里的包名/git 地址统一用**双引号**包住。
107
316
 
108
317
  ```sh
109
318
  dsh plugin --profile web add "dsh-auto-flow" # npm
110
319
  dsh plugin --profile web remove "dsh-auto-flow" # npm 卸载
111
320
  dsh plugin --profile web add "github:you/<repo>#dsh-auto-flow@v0.1.0" # GitHub(monorepo 子包)
112
- dsh plugin --profile web add "../<workspace>/packages/dsh-auto-flow" # 本地目录
321
+ dsh plugin --profile web add "<workspace>/packages/business/dsh-auto-flow" # 本地目录
113
322
  ```
114
323
 
115
324
  ## 常见问题
116
325
 
117
326
  - **为什么把 `lib/` 提交进 git?** dsh 经 GitHub 安装插件时不触发构建,提交产物让安装开箱即用。
118
327
  - **首次 `pnpm build` / `pnpm test` 报找不到 `<包名>/remote`?** 先运行 `pnpm typert:gen`。
119
- - **运行工作流报 `playwright-missing`?** 未安装 playwright-ag 插件;装 `plugin_playwright_ag` 后重试。
328
+ - **运行工作流报 `ag-missing`?** 未安装 ag 插件;装 `plugin_ag` 后重试。
120
329
  - **运行报 `cycle-detected`?** 画布里存在环,检查连线后重跑。