dsh-card-dispatch 0.0.0-stage → 0.1.1
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/CHANGELOG.md +41 -0
- package/LICENSE +21 -0
- package/README.md +276 -2
- package/cordis.patch.yml +25 -0
- package/icon.svg +21 -0
- package/index.js +88 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +57 -4
- package/src/catalog-source.js +122 -0
- package/src/client/index.js +155 -0
- package/src/config-store.js +49 -0
- package/src/config.js +229 -0
- package/src/console.html +1056 -0
- package/src/criteria-llm.js +79 -0
- package/src/criteria.js +102 -0
- package/src/gates.js +134 -0
- package/src/human-channel.js +900 -0
- package/src/import.js +193 -0
- package/src/model-call.js +106 -0
- package/src/models.js +249 -0
- package/src/parse-llm.js +96 -0
- package/src/prompts-llm.js +104 -0
- package/src/rework-store.js +69 -0
- package/src/selfcheck.js +117 -0
- package/src/split.js +549 -0
- package/src/verdict.js +133 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 更新日志
|
|
2
|
+
|
|
3
|
+
发布在 npm:<https://www.npmjs.com/package/dsh-card-dispatch>(0.1.0 之前还发布过旧名
|
|
4
|
+
[`dsh-unattended-dev`](https://www.npmjs.com/package/dsh-unattended-dev))。日期用 npm 记录的 UTC 时间。
|
|
5
|
+
|
|
6
|
+
## 0.1.1 — 2026-10-25
|
|
7
|
+
|
|
8
|
+
### 修复
|
|
9
|
+
|
|
10
|
+
- **预算 / 并发闸真的会拦了**:运行态由页面从看板 `/state` 汇总后随 `/split` 传入;拿不到快照时回
|
|
11
|
+
`E_STATE_UNKNOWN` 拒绝开跑,不再用 `0` 冒充「今天还没跑过、也没人在跑」——以前闸门永远放行就是因为它。
|
|
12
|
+
- **打回上限(R3)刷新不再归零**:打回次数按 `rootId` 落到 `<dataDir>/rework.json`;写盘失败返回 500
|
|
13
|
+
且**不给**重跑信封(失败关闭,不能假装限流生效)。
|
|
14
|
+
- **父卡 prompt 不再自相矛盾**:父卡自己干活时不再说「父卡不产出报告」;没有验收卡时不再提「让它执行」。
|
|
15
|
+
- **`/split` 不再接受请求体里的 `permission` / `agentPreset`**:权限只认配置;`permission: null` 表示
|
|
16
|
+
「不指定,沿用看板默认」。
|
|
17
|
+
|
|
18
|
+
### 变更
|
|
19
|
+
|
|
20
|
+
- **验收卡随父卡自动运行**:交付门槛开着就生成,它是父卡的子卡,运行父卡会一起带起来。父卡始终以 Lead
|
|
21
|
+
会话运行,验收卡是同一会话里的 teammate(智能体团队没勾也只算 1 条会话)。删除 `pending[]` 与页面上的
|
|
22
|
+
「运行验收卡」;返工后由页面单独重排验收卡。
|
|
23
|
+
- **拆卡幂等**:卡 id 与 `requestId` 由输入 hash 决定,同一份输入 10 分钟内重试不会多建一棵树;换了提示词
|
|
24
|
+
或超过窗口才是一批新卡。
|
|
25
|
+
- 判据条数、单条长度、重复编号在服务端收敛。
|
|
26
|
+
- HTTP 栅栏:回环地址 + `sec-fetch-site` + `Origin`/`Host` 一致 + `content-type: application/json` + `nosniff`。
|
|
27
|
+
- README / HANDOFF 校正到当前实现;新增本更新日志。
|
|
28
|
+
|
|
29
|
+
### 测试
|
|
30
|
+
|
|
31
|
+
- 196/196(`node test\run-all.mjs`,17 个文件);新增 `http.test.js`(同源 / JSON / nosniff 栅栏)与
|
|
32
|
+
`model-call.test.js`(调用超时与重试边界)。
|
|
33
|
+
|
|
34
|
+
## 0.1.0 — 2026-10-24
|
|
35
|
+
|
|
36
|
+
首次公开发布(`dsh-card-dispatch`,旧名 `dsh-unattended-dev@0.1.0` 同日发布):需求解析 / 按判据拆分 /
|
|
37
|
+
看板 action 信封 + 人工通道。
|
|
38
|
+
|
|
39
|
+
## 0.0.0-stage — 2026-10-24
|
|
40
|
+
|
|
41
|
+
占位预发布,用于验证 npm 发布链路。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 往事似风如雾
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,277 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-card-dispatch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/dsh-card-dispatch)
|
|
4
|
+
|
|
5
|
+
DSH Host 插件:**把需求拆成任务看板卡片**。就这一件事。
|
|
6
|
+
|
|
7
|
+
```powershell
|
|
8
|
+
npm i dsh-card-dispatch # 装进 DSH profile 的 node_modules
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
更新日志:[CHANGELOG.md](CHANGELOG.md);当前发布版本见上面的 npm 徽章。
|
|
12
|
+
|
|
13
|
+
- 拆分:`src/split.js`(纯函数,判据自动补 `C-01`,按判据拆父卡 + 并行子卡,prompt 自包含)
|
|
14
|
+
- 填写:控制台页面直接调看板的 `POST /api/task-board/action`
|
|
15
|
+
- 执行:**不归本插件**。开会话、钉工作区、预设/权限、跑、结算,全部是看板的事
|
|
16
|
+
|
|
17
|
+
人类入口:`/api/card-dispatch/`(面板里的「派卡台」)。
|
|
18
|
+
|
|
19
|
+
## 1. 为什么只做这两件事
|
|
20
|
+
|
|
21
|
+
这个插件曾经有 6000 行:事件台账、二十行迁移授权表、24 个模型工具、看门狗、
|
|
22
|
+
自动开会话、预算核算、拓扑审计……结果是「什么都能干,但哪一步都不确定」。
|
|
23
|
+
|
|
24
|
+
现在按一条线切:
|
|
25
|
+
|
|
26
|
+
| 谁 | 负责 |
|
|
27
|
+
|---|---|
|
|
28
|
+
| 平台(DSH) | 会话、权限、计费、工作区、模型通道 |
|
|
29
|
+
| 看板插件 | 卡片的真源、运行、子卡关系、运行记录 |
|
|
30
|
+
| 本插件 | 闸门(能不能开跑)、投影(需求 → 卡片)、人入口(一块能看懂的表单) |
|
|
31
|
+
|
|
32
|
+
**平台已经保证的事不重复实现**:不自己起会话、不自己记台账、不自己算并发——
|
|
33
|
+
看板的 `maxParallelRuns`、`sessionDefaultPermission`、`ExecutionRecord` 就是答案。
|
|
34
|
+
重复一份只会得到两份不一致的真相。
|
|
35
|
+
|
|
36
|
+
## 2. 安装与用法
|
|
37
|
+
|
|
38
|
+
**安装**(两步都要做,然后重启一次 DSH):
|
|
39
|
+
|
|
40
|
+
1. 装进 profile 的依赖里:
|
|
41
|
+
|
|
42
|
+
```powershell
|
|
43
|
+
cd $env:USERPROFILE\.dsh\profiles\desktop
|
|
44
|
+
npm i dsh-card-dispatch
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
2. 在 profile 的 `cordis.patch.yml` 里加一个 insert 块:
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
- insert:
|
|
51
|
+
- id: card-dispatch
|
|
52
|
+
name: 'dsh-card-dispatch'
|
|
53
|
+
config:
|
|
54
|
+
enabled: false # 总开关:先关着,进面板自己开
|
|
55
|
+
permission: workspace-write
|
|
56
|
+
dataDir: .card-dispatch
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**必须是 insert 块**:只写 `- id: card-dispatch` + config 不生效,宿主会报
|
|
60
|
+
`patch: entry "card-dispatch" not found` 并跳过。
|
|
61
|
+
|
|
62
|
+
本地改代码时用链接安装(不进 npm 仓库,改完刷新页面即可):
|
|
63
|
+
|
|
64
|
+
```powershell
|
|
65
|
+
cd $env:USERPROFILE\.dsh\profiles\desktop
|
|
66
|
+
npm i "dsh-card-dispatch@link:<仓库路径>/plugins/card-dispatch"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**用法**:
|
|
70
|
+
1. 左侧边栏「派卡台」→ 面板(左栏填需求;右栏一列装「工作区 / 模型 / 怎么跑 / 开关 / 体检」;下面整条是预览);
|
|
71
|
+
**模型在右栏**:它是全局设置(解析并拆分、生成判据草稿都用它)。换一个就写回 `models.draftRoute`,
|
|
72
|
+
刷新或重启后仍是它;下拉候选来自宿主设置里已添加的模型。
|
|
73
|
+
2. 填 **需求标题**、描述(可选)、**验收判据**(一行一条);
|
|
74
|
+
需求写在文档里的话,用「从文档导入」选 `.md` / `.txt` / `.json`,标题、描述、判据会自动填好;
|
|
75
|
+
手头只有一段话(聊天记录、验收邮件、会议纪要)就直接贴进描述,点 **「AI 解析并拆分」**:
|
|
76
|
+
- 第一步(模型)把原文整理成 标题 + 描述(给谁用 / 要什么 / 明确不要什么)+ 3~8 条可判定的判据;
|
|
77
|
+
- 第二步(规则)按判据拆成「父卡 + 子卡 + 验收卡」,拆几张是算出来的,不由模型决定;
|
|
78
|
+
- 第三步(模型)**按需求给每张卡写提示词开头**(目标 / 范围 / 不要做什么),
|
|
79
|
+
判据清单和「完成要交证据」那些硬要求仍由系统确定性地拼在后面;
|
|
80
|
+
- 预览里能逐张看到提示词,点「看解析用的提示词」能看到两段提示词原文;
|
|
81
|
+
- 标题只在空着时填、判据只追加不覆盖;改过表单后这一轮写的提示词会被丢掉(回落到模板);
|
|
82
|
+
- 模型不可用时按格式规则解析(认 `# 标题`、`- [ ] 勾选项`、`## 验收判据` 列表),并明说走了哪条路;
|
|
83
|
+
3. 判据不想手写就点「生成草稿」:**用右栏选的模型写**,模型不可用或关掉 `models.useModel` 时退回规则提炼。
|
|
84
|
+
两条路都是草稿,改完再用。
|
|
85
|
+
4. 点「看拆分」先看预览:几张卡、每张卡会收到什么 prompt、这次会不会开跑。
|
|
86
|
+
5. 点「写入看板」(右下)建卡;勾了「写入后立即运行」才会真的开会话(**这一步才计费**)。
|
|
87
|
+
6. 验收驳回后回来点「按结论处理」:读 `out/<根卡 id>/verdict.json`,按打回上限给出 pass / rework / escalate。
|
|
88
|
+
|
|
89
|
+
## 3. 路由(十二条)
|
|
90
|
+
|
|
91
|
+
都以 `/api/card-dispatch` 为前缀,`kind: prefix`,页面每次请求从磁盘读(改 HTML 不用重启,改路由要)。
|
|
92
|
+
|
|
93
|
+
| 方法 + 路径 | 作用 |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `GET /` | 控制台页面(`src/console.html`) |
|
|
96
|
+
| `GET /view` | 页面要的状态快照:能力清单、看板基址、当前配置 |
|
|
97
|
+
| `GET /models` | 宿主设置里已添加的模型(只读扫描 `cordis.patch.yml`),给下拉用 |
|
|
98
|
+
| `POST /split` | 需求 → 卡片计划(`cards[]` + `envelopes[]` + `warnings[]`),**不写任何东西**;同一份输入 10 分钟内幂等 |
|
|
99
|
+
| `POST /plan` | 解析 + 算拆几张卡 + 给每张卡写提示词(两次模型调用);回 `intros` 给页面,写卡时原样带回 `/split` |
|
|
100
|
+
| `POST /parse` | 整段需求 → 标题 / 描述 / 判据(只整理字段,不拆分) |
|
|
101
|
+
| `POST /criteria` | 判据草稿:模型优先,失败退回规则提炼 |
|
|
102
|
+
| `POST /import` | 文档正文 → 标题 / 描述 / 判据(正文由浏览器读成文本发来,插件不碰磁盘路径) |
|
|
103
|
+
| `POST /rework` | 读 `verdict.json`,按 `gates.maxReworkRounds` 给 pass / rework / escalate;打回次数由服务端落盘 |
|
|
104
|
+
| `GET /config` | 声明表 + 当前值(页面据此回填控件) |
|
|
105
|
+
| `POST /config` | 归一化后落盘成完整配置;不认识的键回 `rejected`,不会被悄悄丢掉 |
|
|
106
|
+
| `GET /status` | 配置、开关、体检结论 |
|
|
107
|
+
|
|
108
|
+
`POST` 一律要求人标记:请求头 `x-card-dispatch-human: true` 或 body 里 `human: true`。
|
|
109
|
+
没有这个标记就是 400——**这条路只能人走**,模型和脚本不该替人按「开跑」。
|
|
110
|
+
|
|
111
|
+
除人标记外还有三道同源栅栏(人标记不是鉴权,别的页面照样能带):
|
|
112
|
+
来源必须是回环地址;`sec-fetch-site` 不能是 `cross-site` / `same-site`;带了 `Origin` 就必须与 `Host` 一致;
|
|
113
|
+
`POST` 必须声明 `content-type: application/json`(`text/plain` 的简单请求不触发预检,正是跨站伪造最常用的形状)。
|
|
114
|
+
命令行调用要同时带标记与 JSON 头。
|
|
115
|
+
|
|
116
|
+
`/split` 的幂等窗口是 10 分钟:同一份输入(标题、描述、判据、跑法、提示词)重复提交会被当成重试,
|
|
117
|
+
卡 id 与 requestId 都不变,看板不会建出第二批;要故意新建一批,改一下标题/判据,或等窗口过去。
|
|
118
|
+
|
|
119
|
+
## 4. 配置
|
|
120
|
+
|
|
121
|
+
配置只有一份真源:`src/config.js` 的声明表。面板、`/config` 路由、`cordis.patch.yml` 都按它收敛;
|
|
122
|
+
旧版键(`budgetPerDay`、`autoProvision.permission`)仍然认得,非法值退回缺省并记一条 note。
|
|
123
|
+
|
|
124
|
+
| 键 | 缺省 | 作用 | 插件能真拦吗 |
|
|
125
|
+
|---|---|---|---|
|
|
126
|
+
| `enabled` | `false` | 总开关;关掉只拆卡、不返回运行信封 | 能 |
|
|
127
|
+
| `permission` | `workspace-write` | 卡片权限预设(写进每个 create 信封;`null` = 不指定,沿用看板默认;请求体不能覆盖它) | 能 |
|
|
128
|
+
| `dataDir` | `.card-dispatch` | 插件的数据目录(配置、结论文件) | — |
|
|
129
|
+
| `gates.requirePlanApproval` | `true` | 没批准的拆分不给运行信封 | 能(「批准」= 人点「写入并运行」) |
|
|
130
|
+
| `gates.requireEvidence` | `true` | 没有 commit / 报告不算完成;验收卡随父卡自动运行(团队开着时在同一条会话里) | 能 |
|
|
131
|
+
| `gates.budgetPerDay` | `200` | 每日运行次数上限 | 能,需调用方给 `runsToday` |
|
|
132
|
+
| `gates.maxParallelRuns` | `3` | 同时在跑的会话上限 | 能,需调用方给 `runningSessions` |
|
|
133
|
+
| `gates.maxReworkRounds` | `2` | 打回上限,第 3 次升级给人(基线 R3) | 能 |
|
|
134
|
+
| `models.useModel` | `true` | 解析与判据草稿用不用模型;关掉只用规则(点一次才计费) | 是 |
|
|
135
|
+
| `models.draftRoute` | 空 | 全局模型:解析并拆分、判据草稿、写卡提示词都用它(右栏下拉写的就是这一项),留空 = 宿主默认 | 是 |
|
|
136
|
+
| `org.departments` | `dev/verify/integrate` | 部门名(写进卡片描述,供人读) | 只记录 |
|
|
137
|
+
| `execution.driver` | `task-board` | 执行层驱动(只有看板一条路) | 枚举校验 |
|
|
138
|
+
| `execution.teamRun` | `true` | 智能体团队:开了才按判据拆子卡(父卡带 `teamRun: true`,看板只起一个 Lead 会话,子卡由它派 teammate);关掉不拆子卡;交付门槛开着时父卡仍以 Lead 会话运行(只带验收卡) | 能(决定拆不拆 + 写进 create 信封) |
|
|
139
|
+
| `audit.orgChart` | `true` | 拓扑稽查:事后核对有没有人绕开主管 | 只检查,不拦 |
|
|
140
|
+
| `org.depthRequired` | `2` | 主管带人所需的委派深度 | **不能**,只体检提示 |
|
|
141
|
+
|
|
142
|
+
**优先级:`<dataDir>/config.json` 优先于部署配置。** 面板保存的是归一化后的**完整**配置,
|
|
143
|
+
所以不会出现半份配置的中间态;想回到部署值就删掉这个文件(`/status` 会给出路径)。
|
|
144
|
+
|
|
145
|
+
三个闸都是纯函数(`src/gates.js`):同样的输入永远同样答案,因此能逐条单测,也能在页面上先算给人看。
|
|
146
|
+
`/split` 用 `evaluateStart` 决定给不给运行信封——**写卡不花钱,运行才花钱**,闸门只卡后者。
|
|
147
|
+
|
|
148
|
+
## 5. 看板信封的字段约束(踩过就回不来)
|
|
149
|
+
|
|
150
|
+
看板对 action 做 `exactKeys` 校验,**多一个字段就是 400**:
|
|
151
|
+
|
|
152
|
+
- `create` 只认 `{kind, title, description, prompt, workspaceId?, mode?, permission?, parentId?, teamRun?}`;
|
|
153
|
+
`null` 也要整个省掉,不能写 `workspaceId: null`。
|
|
154
|
+
- `teamRun` 只写在**父卡**上。子卡再带一次,看板会给子卡也起一个 Lead,等于开了两套班子。
|
|
155
|
+
- `run` 是 `{kind:'run', taskId}`;`delete` 拒删**有子卡**的卡(`task has subtasks; detach or delete them first`),
|
|
156
|
+
也拒删正在跑的卡;要整棵子树消失用 `archive`(可恢复)。
|
|
157
|
+
- 插件进程调 `/state` 是 403:看板有信任栅栏(`sec-fetch-site` / `Origin` / `dsh-auth-` cookie)。
|
|
158
|
+
所以插件只**生成信封**,真正的请求由**页面**同源发出(浏览器的标记天然通过)。
|
|
159
|
+
|
|
160
|
+
## 6. 拆分策略
|
|
161
|
+
|
|
162
|
+
| 情况 | 产出 | 会话数 |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| 判据 0 条 | 1 张父卡 + 警告「执行者不知道做到什么算完」 | 1 |
|
|
165
|
+
| 判据 1 条 | 1 张父卡(不拆——拆多了全是计费) | 1 |
|
|
166
|
+
| **没开智能体团队、也没开交付门槛** | 不拆子卡,判据全写进一张卡 | 1 |
|
|
167
|
+
| 判据 ≥2 条 + 开了团队 + 勾了并行 | 父卡 + 每判据组 1 张子卡(子卡由父卡的 Lead 在**同一个**会话里派成 teammate) | **1** |
|
|
168
|
+
| 判据 ≥2 条但没勾并行 | 1 张父卡,全部判据写进去 | 1 |
|
|
169
|
+
| 交付门槛开着(`gates.requireEvidence`) | 上面任一种 + **1 张验收卡** | **1**(父卡以 Lead 会话运行,验收卡是同一会话里的 teammate;团队没勾也不额外开会话) |
|
|
170
|
+
|
|
171
|
+
**卡数 ≠ 会话数**。只要走 Lead 模式(开了团队,或团队没开但开了交付门槛),父卡 + 子卡 + 验收卡
|
|
172
|
+
都在同一个 Lead 会话里跑,只算 1 条会话;只有团队和门槛都关、只有一张父卡时,才是单卡单会话。
|
|
173
|
+
验收卡可能比开发卡先启动,它会在会话里等开发报告,再给出 PASS/REJECT。
|
|
174
|
+
预览里的警告会把这次到底开几条会话直接写出来。
|
|
175
|
+
|
|
176
|
+
拆不拆子卡只看**跑法**:团队开着才拆。页面跟着联动——团队没勾时
|
|
177
|
+
「按判据拆成并行子卡」直接禁用;勾一下就存盘并重算预览,不用先点「保存」才发现会话条数变了。
|
|
178
|
+
其余开关(总开关、计划闸、交付门槛、拓扑稽查、预算 / 并发 / 打回)也一样:改完立刻存盘;
|
|
179
|
+
已经出过预览的会就地重算,不必再点一次「保存」。
|
|
180
|
+
|
|
181
|
+
**子卡是并行的**:看板没有「卡片间依赖」字段。所以每张子卡的 prompt 自带它那组判据
|
|
182
|
+
和「怎么算完」,不靠顺序;要严格串行就别勾并行。
|
|
183
|
+
|
|
184
|
+
**验收卡为什么是另一张卡**:基线 §3.3 要求验证独立于开发——同一张卡自己说「我做完了」
|
|
185
|
+
不算验收。所以门槛开着时,插件会把交付要求写进每张开发卡(报告路径写死成
|
|
186
|
+
`out/<该卡 id>/report.md`,附 commit sha),再生成一张只读证据的验收卡:
|
|
187
|
+
结论只能是 PASS 或 REJECT,REJECT 必须带 `{criteriaId, expected, actual, evidencePath}`。
|
|
188
|
+
打回上限按 `gates.maxReworkRounds` 写进验收 prompt;走 Lead 模式时父卡就是 Lead,负责收尾。
|
|
189
|
+
平台排不出「先开发、后验收」的顺序(看板没有依赖字段),所以顺序靠验收卡自己等报告:
|
|
190
|
+
父卡自己就是干活的那张时,prompt 不会再写「父卡不产出报告」,也不会提一张并不存在的验收卡。
|
|
191
|
+
验收卡和团队开关无关:门槛开着就有,并且**随父卡一起运行**——它是父卡的子卡,运行父卡会把子卡
|
|
192
|
+
一起带起来,所以不再需要手动点「运行验收卡」。代价是它可能比开发卡先启动,因此验收 prompt 里
|
|
193
|
+
写死了等待规则:开始先等 30 秒;报告缺失或比该卡最近一次执行开始时间还旧时,每 60 秒再看一次;
|
|
194
|
+
开发卡已结束但仍缺本轮报告 → 立即 REJECT;最多等 120 分钟。开发卡的 prompt 同时要求开工前
|
|
195
|
+
删掉上一轮的 `report.md`,所以复验不会把旧报告当新证据。返工只重跑单张开发卡、不会带起验收卡,
|
|
196
|
+
页面会在重跑信封之后再单独排一次验收卡。
|
|
197
|
+
走 Lead 模式时它只是 Lead 会话里的一个 teammate,不额外开一条会话——所以团队没勾、门槛开着时,
|
|
198
|
+
父卡 + 验收卡也只算 1 条会话。
|
|
199
|
+
|
|
200
|
+
## 7. 已验证 / 未验证
|
|
201
|
+
|
|
202
|
+
已验证(真机,2026-10-22,在运行中的看板页里直接发请求):
|
|
203
|
+
|
|
204
|
+
| 动作 | 结果 |
|
|
205
|
+
|---|---|
|
|
206
|
+
| `GET /api/task-board/state` | 200,`sessionDefaultPermission: workspace-write` |
|
|
207
|
+
| `create` 单卡 | 200,卡片出现 `status: todo` |
|
|
208
|
+
| `create` 父卡 + 子卡(`parentId`) | 200,子卡正确挂到父卡下 |
|
|
209
|
+
| `delete` | 200,卡片移除,看板回到干净状态 |
|
|
210
|
+
| `delete` 有子卡的父卡 | 400 `task has subtasks; detach or delete them first`(所以要按子树删/归档) |
|
|
211
|
+
|
|
212
|
+
**AI 解析并拆分**(2026-10-24,真机点过一次,用的是宿主里配的 `deepseek-v4.1-flash`):
|
|
213
|
+
一段「做一个可视化计算器…」的原文,返回 5 条判据,页面自动拆成 6 张卡(父卡 + 4 子卡 + 验收卡),
|
|
214
|
+
预览里逐张列出提示词。`/plan` 的两段提示词(解析、写卡)在同一轮里各调一次模型。
|
|
215
|
+
模型不可用或关掉 `models.useModel` 时退回「格式规则解析 + 模板提示词」,并在提示里说明。
|
|
216
|
+
|
|
217
|
+
**未验证**:`run` 的会话侧效果(真的开出一个会话并跑完)——那会真开会话计费,
|
|
218
|
+
没有替你跑。第一次点运行时请留意看板是否解析出工作区与预设。
|
|
219
|
+
`teamRun` 写进 create 信封后看板是否真的只起一个 Lead、把子卡派成 teammate,同样没跑过;
|
|
220
|
+
交付门槛开着、团队没勾时「父卡当 Lead + 验收卡做 teammate」这条路径也没跑过;
|
|
221
|
+
看板自己的 `teamRunAvailable` 与「Agent Teams 服务缺失就拒绝运行」两道检查仍在它那边。
|
|
222
|
+
|
|
223
|
+
**交付门槛现在到哪一步**:证据要求已写进每张开发卡的 prompt,验收卡独立存在、只给
|
|
224
|
+
PASS/REJECT 并把结论落到 `out/<根卡 id>/verdict.json`——「证据从哪来、谁来验、结论放哪」都解决了。
|
|
225
|
+
验收卡现在随父卡自动启动,不再需要人点「运行验收卡」;「等报告」是 prompt 级约束,未真机跑过。
|
|
226
|
+
|
|
227
|
+
「打回之后谁重跑」现在有一个**人触发**的答案:`POST /rework` 读那个文件,按
|
|
228
|
+
`gates.maxReworkRounds` 给出三种结局——`pass` 收尾、`rework` 返回重跑被拒卡片的 run 信封、
|
|
229
|
+
`escalate` 停下来交给人(基线 R3)。没有轮询、没有定时器:人点一下才跑一次判定。
|
|
230
|
+
|
|
231
|
+
为什么判定靠文件而不是看板状态:`GET /api/task-board/state` 走看板自己的信任栅栏,
|
|
232
|
+
插件进程没有那张 cookie,**实测 403**;而且 `ExecutionRecord.result` 只记「执行成没成」,
|
|
233
|
+
不记「验收判 PASS 还是 REJECT」。落盘的文件三方都能读,也才符合基线 P3。
|
|
234
|
+
|
|
235
|
+
**还没做**:worktree 隔离、模型调用缓存、`skills/card-dispatch-org/SKILL.md` 的安装。
|
|
236
|
+
模型调用已加一次重试(只重试非超时失败)。预算与并发闸已经接上真实运行态:页面从看板 `/state` 的 executions 算「今天跑了几次、几个会话在跑」,
|
|
237
|
+
随 `/split` 传给服务端;拿不到快照就回 `E_STATE_UNKNOWN` 拒绝放行,不再拿 0 冒充。
|
|
238
|
+
注意:看板每张卡只保留最近 20 条 executions,单卡一天跑超过 20 次时预算数会少算。
|
|
239
|
+
|
|
240
|
+
## 8. 测试
|
|
241
|
+
|
|
242
|
+
```powershell
|
|
243
|
+
node test\run-all.mjs # 196 个用例,全绿
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
十七个测试文件:`config`(收敛/兼容/合并)、`gates`(四个闸的判定顺序与理由)、
|
|
247
|
+
`selfcheck`(体检判据)、`split`(拆分、信封、智能体团队、提示词覆盖、幂等)、`status`(所有路由 + 打回次数落盘)、
|
|
248
|
+
`verdict`(结论文件契约)、`import`(文档解析)、`criteria`(规则提炼)、`models`(模型清单扫描)、
|
|
249
|
+
`model-call`(调用超时 / 重试边界)、`criteria-llm`(判据草稿的模型通道)、`parse-llm`(整段需求解析)、
|
|
250
|
+
`prompts-llm`(按需求写每张卡的提示词)、`human-channel`(路由 + 两道门 + 依赖透传 + 闸门降级)、
|
|
251
|
+
`console`(页面 id / 看板基址 / 旧机制痕迹清零)、`http`(同源 / JSON / nosniff 栅栏)、
|
|
252
|
+
`index`(导出形状 / 旧配置兼容 / apply 只挂路由 / 端到端打一次 /models)。
|
|
253
|
+
|
|
254
|
+
模型相关的用例全部用假的 `llm` 服务:**不在测试里花真钱**。真模型的验证靠人点一次、
|
|
255
|
+
看页面回的 `source: 'model'` 和实际文案。
|
|
256
|
+
|
|
257
|
+
## 9. 文件
|
|
258
|
+
|
|
259
|
+
| 文件 | 行数 | 作用 |
|
|
260
|
+
|---|---|---|
|
|
261
|
+
| `index.js` | ~90 | cordis 入口:`apply` 只注册路由,注入 `models` 依赖 |
|
|
262
|
+
| `src/human-channel.js` | ~830 | 十二条路由、两道门(人标记 + 同源/JSON)、闸门与配置 |
|
|
263
|
+
| `src/split.js` | ~470 | 需求 → 卡片计划(唯一有「业务判断」的地方,纯函数;同输入幂等) |
|
|
264
|
+
| `src/gates.js` | ~110 | 四个闸:计划、运行态、预算、并发(+ 打回次数判定) |
|
|
265
|
+
| `src/rework-store.js` | ~70 | 打回次数按 `rootId` 落盘(`<dataDir>/rework.json`) |
|
|
266
|
+
| `src/config.js` | ~230 | 声明表 + 归一化 + 兼容旧键 |
|
|
267
|
+
| `src/config-store.js` | ~90 | 配置读写(`<dataDir>/config.json` 优先) |
|
|
268
|
+
| `src/selfcheck.js` | ~200 | 体检:看板在不在、模型清单、权限、委派深度 |
|
|
269
|
+
| `src/catalog-source.js` | ~70 | 从宿主的 `cordis.patch.yml` 扫出可选模型 |
|
|
270
|
+
| `src/model-call.js` | ~120 | 模型调用与 JSON 抽取(一次点击一次调用,45 秒超时) |
|
|
271
|
+
| `src/criteria.js` | ~90 | 规则提炼判据(无模型时的退路) |
|
|
272
|
+
| `src/criteria-llm.js` | ~125 | 判据草稿的模型通道 |
|
|
273
|
+
| `src/parse-llm.js` | ~120 | 整段需求 → 标题 / 描述 / 判据:提示词 + 回复解析 |
|
|
274
|
+
| `src/prompts-llm.js` | ~140 | 按需求给每张卡写提示词开头(结构不归它管) |
|
|
275
|
+
| `src/import.js` | ~150 | `.md` / `.txt` / `.json` → 标题 / 描述 / 判据 |
|
|
276
|
+
| `src/verdict.js` | ~120 | `verdict.json` 的读写与形状校验 |
|
|
277
|
+
| `src/console.html` | ~1010 | 控制台页面(单文件,无构建) |
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
- insert:
|
|
2
|
+
- id: card-dispatch
|
|
3
|
+
name: 'dsh-card-dispatch'
|
|
4
|
+
config:
|
|
5
|
+
# 总开关:false 时只做「拆分 + 填卡」,不开任何会话、不产生费用。
|
|
6
|
+
enabled: false
|
|
7
|
+
# 新卡片钉住的权限预设;null 表示不改动(沿用看板默认)。
|
|
8
|
+
permission: workspace-write
|
|
9
|
+
# 数据目录(相对宿主 cwd;桌面端 cwd 是 profile 目录)。
|
|
10
|
+
# 面板改的开关会落到 <dataDir>/config.json,且**文件优先于本文件**。
|
|
11
|
+
dataDir: .card-dispatch
|
|
12
|
+
gates:
|
|
13
|
+
requirePlanApproval: true # DAG 未经人确认不开跑(计划闸)
|
|
14
|
+
budgetPerDay: 200 # 每日运行次数预算(D10)
|
|
15
|
+
maxParallelRuns: 3 # 同时在跑的会话上限
|
|
16
|
+
maxReworkRounds: 2 # 打回上限,第 3 次升级给人(基线 R3)
|
|
17
|
+
requireEvidence: true # 没有 commit / 报告不算完成(基线 P4)
|
|
18
|
+
org:
|
|
19
|
+
departments: [dev, verify, integrate] # 基线 §12:第一版三个职能
|
|
20
|
+
depthRequired: 2 # 主管要带人所需的委派深度(插件改不了,只体检)
|
|
21
|
+
execution:
|
|
22
|
+
driver: task-board # 执行层驱动:任务看板(D-15)
|
|
23
|
+
teamRun: true # 父卡起 Lead、子卡起 teammate
|
|
24
|
+
audit:
|
|
25
|
+
orgChart: true # 拓扑事后稽查(人员越权在平台上已不可能)
|
package/icon.svg
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Unattended Dev Ledger">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="ud-bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#3b6fd4"/>
|
|
5
|
+
<stop offset="1" stop-color="#1e3a7a"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect x="4" y="4" width="56" height="56" rx="14" fill="url(#ud-bg)"/>
|
|
9
|
+
<g fill="none" stroke="#ffffff" stroke-width="2.4" stroke-linecap="round">
|
|
10
|
+
<path d="M18 20h28"/>
|
|
11
|
+
<path d="M18 32h28"/>
|
|
12
|
+
<path d="M18 44h18"/>
|
|
13
|
+
</g>
|
|
14
|
+
<g fill="#8ec5ff">
|
|
15
|
+
<circle cx="24" cy="20" r="3.2"/>
|
|
16
|
+
<circle cx="33" cy="32" r="3.2"/>
|
|
17
|
+
<circle cx="21" cy="44" r="3.2"/>
|
|
18
|
+
</g>
|
|
19
|
+
<circle cx="47" cy="43" r="7.5" fill="none" stroke="#ffffff" stroke-width="2.6"/>
|
|
20
|
+
<path d="M52.4 48.4 57 53" stroke="#ffffff" stroke-width="2.6" stroke-linecap="round"/>
|
|
21
|
+
</svg>
|
package/index.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 插件入口:**控制面**(配置 / 闸门 / 体检),执行面交给任务看板。
|
|
3
|
+
*
|
|
4
|
+
* 历史(偏差 D-14 / D-15):这里曾经装着台账、20 行迁移规则、24 个模型工具、看门狗、
|
|
5
|
+
* 自动调度和自动开会话。真机验证后确认两件事:
|
|
6
|
+
*
|
|
7
|
+
* 1. 宿主不授权插件直调会话控制器(`sessionController` 是网关客户端,缺 peer 抛
|
|
8
|
+
* `throwIfAborted` / `truncatedAt`),自己开会话这条路走不通;
|
|
9
|
+
* 2. 任务看板(`@linxin666/dsh-client-ui-task-board`)已经把「建卡 → 钉工作区/预设/
|
|
10
|
+
* 权限 → 开会话 → 跑 → 结算」做通了,且暴露 `POST /api/task-board/action`。
|
|
11
|
+
*
|
|
12
|
+
* 所以本插件的定位是:**平台没有的那三件事**——闸门、体检、人入口;执行一律外移。
|
|
13
|
+
* 反过来,平台已经保证的事(谁能给谁发消息、谁指挥谁、委派深度、计费)**不写**,
|
|
14
|
+
* 写进插件只会造成「以为拦住了」的错觉。
|
|
15
|
+
*
|
|
16
|
+
* 关于 inject:一个都不声明。`webServer` 由 human-channel 用 `ctx.inject(['webServer'], …)`
|
|
17
|
+
* 惰性获取——纯 CLI profile 里没有 webServer,硬依赖会让插件装不上(偏差 D-9)。
|
|
18
|
+
* 同样不导出 `export const Config`(有意避开 schemastery 依赖,偏差 D-7)。
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { registerHumanChannel, HUMAN_ROUTE_PREFIX } from './src/human-channel.js'
|
|
22
|
+
import { normalizeConfig, normalizeConfigDetailed } from './src/config.js'
|
|
23
|
+
import { createConfigStore } from './src/config-store.js'
|
|
24
|
+
import { loadCatalog } from './src/catalog-source.js'
|
|
25
|
+
|
|
26
|
+
export const name = 'card-dispatch'
|
|
27
|
+
|
|
28
|
+
// 测试与面板共用同一份收敛逻辑,避免「插件里一套、面板里一套」
|
|
29
|
+
export { normalizeConfig, normalizeConfigDetailed }
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 取配置:**面板保存的文件优先于部署配置**。
|
|
33
|
+
*
|
|
34
|
+
* 理由:面板上的开关是「人此刻的意图」,重启后不该悄悄回退;而部署配置是安装时的
|
|
35
|
+
* 意图。文件里存的永远是归一化后的**完整**配置,所以不会出现半份配置的中间态。
|
|
36
|
+
* 想让部署配置重新生效,删掉 `dataDir/config.json` 即可(面板会把路径给出来)。
|
|
37
|
+
*
|
|
38
|
+
* @returns {{config: object, notes: string[], source: 'file'|'deploy', store: object, error: string|null}}
|
|
39
|
+
*/
|
|
40
|
+
function resolveConfig(rawConfig) {
|
|
41
|
+
const deployed = normalizeConfigDetailed(rawConfig)
|
|
42
|
+
const store = createConfigStore({ dir: deployed.config.dataDir })
|
|
43
|
+
const loaded = store.load()
|
|
44
|
+
const hasFile = loaded.error === null && Object.keys(loaded.config).length > 0
|
|
45
|
+
if (!hasFile) {
|
|
46
|
+
return { config: deployed.config, notes: deployed.notes, source: 'deploy', store, error: loaded.error }
|
|
47
|
+
}
|
|
48
|
+
const fromFile = normalizeConfigDetailed(loaded.config)
|
|
49
|
+
return { config: fromFile.config, notes: fromFile.notes, source: 'file', store, error: null }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function apply(ctx, rawConfig = {}) {
|
|
53
|
+
const { config, notes, source, store, error } = resolveConfig(rawConfig)
|
|
54
|
+
const logger = typeof ctx.get === 'function' ? ctx.get('logger') : null
|
|
55
|
+
|
|
56
|
+
logger?.info?.(
|
|
57
|
+
`[card-dispatch] 已装载:控制台 ${HUMAN_ROUTE_PREFIX}(需求拆分 → 任务看板);` +
|
|
58
|
+
`enabled=${config.enabled};配置来源=${source === 'file' ? store.file : '部署配置'}`,
|
|
59
|
+
)
|
|
60
|
+
if (error !== null) logger?.warn?.(`[card-dispatch] ${error}(已退回部署配置)`)
|
|
61
|
+
for (const note of notes) logger?.warn?.(`[card-dispatch] ${note}`)
|
|
62
|
+
|
|
63
|
+
const dispose = registerHumanChannel(ctx, {
|
|
64
|
+
config,
|
|
65
|
+
store,
|
|
66
|
+
// 插件读不到部署的子智能体深度,也探测不了看板;两者都交给 /status 的体检去问人。
|
|
67
|
+
facts: () => ({ subagentDepthMax: null, boardReachable: null, boardTeamRun: null }),
|
|
68
|
+
/* 模型接口用宿主已经配好的那份:`llm` 是官方服务,模型清单读 profile 的
|
|
69
|
+
cordis.patch.yml。拿不到就返回 null,页面自动退回规则提炼。 */
|
|
70
|
+
models: {
|
|
71
|
+
llm: () => {
|
|
72
|
+
try {
|
|
73
|
+
return typeof ctx.get === 'function' ? ctx.get('llm') ?? null : null
|
|
74
|
+
} catch {
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
catalog: () => loadCatalog(),
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
return () => {
|
|
82
|
+
try {
|
|
83
|
+
dispose?.()
|
|
84
|
+
} catch {
|
|
85
|
+
// 卸载失败不影响宿主回收 fiber
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-card-dispatch",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "派卡台开发的上游:把需求拆成任务看板卡片(执行交给 dsh-client-ui-task-board)",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"dsh",
|
|
8
|
+
"deepseek-harness",
|
|
9
|
+
"dsh-plugin",
|
|
10
|
+
"task-board",
|
|
11
|
+
"card-dispatch",
|
|
12
|
+
"requirement-split",
|
|
13
|
+
"prompt"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "yangchaoran",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://gitee.com/past-events-sifenruwu/dsh-card-dispatch.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://gitee.com/past-events-sifenruwu/dsh-card-dispatch#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://gitee.com/past-events-sifenruwu/dsh-card-dispatch/issues"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": "./index.js",
|
|
30
|
+
"./client": "./src/client/index.js",
|
|
31
|
+
"./locale/*.json": "./locale/*.json",
|
|
32
|
+
"./icon": "./icon.svg"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"index.js",
|
|
36
|
+
"src",
|
|
37
|
+
"cordis.patch.yml",
|
|
38
|
+
"locale/*.json",
|
|
39
|
+
"icon.svg",
|
|
40
|
+
"README.md",
|
|
41
|
+
"CHANGELOG.md"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"test": "node test/run-all.mjs",
|
|
45
|
+
"test:node": "node --test test/"
|
|
46
|
+
},
|
|
47
|
+
"dsh": {
|
|
48
|
+
"bundle": {
|
|
49
|
+
"patch": "./cordis.patch.yml"
|
|
50
|
+
},
|
|
51
|
+
"client": {
|
|
52
|
+
"platform": "web",
|
|
53
|
+
"immediately": true,
|
|
54
|
+
"inject": [
|
|
55
|
+
"@deepseek-ai/dsh-client-ui-slots"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|