@mzzsfy/dsh-rs-workflow 0.2.4 → 1.0.0
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 +14 -49
- package/cordis.patch.yml +8 -17
- package/lib/board.mjs +355 -0
- package/lib/driver/approve.mjs +126 -0
- package/lib/driver/control.mjs +46 -0
- package/lib/driver/index.mjs +454 -0
- package/lib/driver/prompts.mjs +102 -0
- package/lib/driver/runner.mjs +173 -0
- package/lib/driver/scheduler.mjs +159 -0
- package/lib/index.js +30 -213
- package/lib/orchestrator.mjs +428 -0
- package/lib/planner-gate.mjs +195 -0
- package/lib/release.mjs +183 -0
- package/lib/settings-schema.mjs +73 -0
- package/lib/spec.mjs +161 -0
- package/lib/storage.mjs +35 -0
- package/lib/store.mjs +255 -0
- package/lib/template-tool.mjs +161 -0
- package/lib/template.mjs +341 -0
- package/package.json +8 -6
- package/src/client.js +1269 -0
- package/lib/preset-sync.mjs +0 -227
- package/preset/rs-workflow/agent.cordis.yml +0 -196
- package/preset/rs-workflow/preset.yml +0 -5
- package/preset/rs-workflow/skills/rs-workflow/SKILL.md +0 -110
- package/preset/rs-workflow/skills/rs-workflow/references/engine.js +0 -1446
- package/preset/rs-workflow/skills/rs-workflow/references/templates.md +0 -104
- package/preset/rs-workflow/skills/rs-workflow/slots.json5 +0 -53
package/lib/preset-sync.mjs
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* preset-sync — 把包内 preset/rs-workflow 幂等同步到用户 preset 根。
|
|
3
|
-
*
|
|
4
|
-
* 职责:
|
|
5
|
-
* - sync:递归拷贝包内 preset/rs-workflow → <dsh-home>/.agent-presets/rs-workflow,
|
|
6
|
-
* 并写入来源标记(marker),供排查"选择器里 broken 的 preset 来自哪个包"。
|
|
7
|
-
* - 所有权防线:目标目录存在但 marker 缺失或归属他人时拒绝覆盖(可能是用户手工
|
|
8
|
-
* 安装或本地定制的同名 preset),告警后原样保留。
|
|
9
|
-
* - 仅在插件 apply 时运行:dsh 每次启动同步一次,升级包后重启即更新。
|
|
10
|
-
* 卸载场景受 pnpm 限制(依赖的 preuninstall 脚本一律不执行,实验证实),
|
|
11
|
-
* 无法自动删除,残留 preset 因 tool 行 import 失败在选择器显示 broken;
|
|
12
|
-
* 手动清理命令见包 README。
|
|
13
|
-
*/
|
|
14
|
-
import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
|
15
|
-
import { homedir } from 'node:os'
|
|
16
|
-
import { dirname, join, resolve } from 'node:path'
|
|
17
|
-
import { fileURLToPath } from 'node:url'
|
|
18
|
-
|
|
19
|
-
const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
20
|
-
const PRESET_SRC = join(PKG_ROOT, 'preset', 'rs-workflow')
|
|
21
|
-
const USER_PRESET_DIR = '.agent-presets'
|
|
22
|
-
const PRESET_ID = 'rs-workflow'
|
|
23
|
-
const MARKER_NAME = '.dsh-rs-workflow-source.json'
|
|
24
|
-
const PACKAGE_NAME = '@mzzsfy/dsh-rs-workflow'
|
|
25
|
-
// slots.json5 是文档引导的用户后备编辑点:rewrite 前若其内容异于包内模板,
|
|
26
|
-
// 备份到 home 根的该文件名,重写后恢复,升级不再静默吞掉手工定制
|
|
27
|
-
const SLOTS_REL = join('skills', 'rs-workflow', 'slots.json5')
|
|
28
|
-
const USER_SLOTS_BACKUP = 'rs-workflow.slots.user.json5'
|
|
29
|
-
// 完整性清单:任一缺失即视为残缺,走重写自愈(与 PRESET_SRC 产物对齐)
|
|
30
|
-
const MANAGED_FILES = [
|
|
31
|
-
'preset.yml',
|
|
32
|
-
'agent.cordis.yml',
|
|
33
|
-
join('skills', 'rs-workflow', 'SKILL.md'),
|
|
34
|
-
SLOTS_REL,
|
|
35
|
-
join('skills', 'rs-workflow', 'references', 'engine.js'),
|
|
36
|
-
join('skills', 'rs-workflow', 'references', 'templates.md'),
|
|
37
|
-
]
|
|
38
|
-
|
|
39
|
-
/** dsh home:CLI 配置层可显式指定,插件环境只见 $DSH_HOME;空串视同未设 */
|
|
40
|
-
function dshHome() {
|
|
41
|
-
const fromEnv = process.env.DSH_HOME
|
|
42
|
-
return fromEnv && fromEnv.trim().length > 0 ? resolve(fromEnv) : join(homedir(), '.dsh')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** 释放目标绝对路径(诊断用:home 错位时可直接从日志/测试定位) */
|
|
46
|
-
export function presetDest() {
|
|
47
|
-
return join(dshHome(), USER_PRESET_DIR, PRESET_ID)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 版本读取容错:package.json 恰逢包管理器替换窗口或损坏时不让包整体加载失败,
|
|
51
|
-
// marker 的 version 字段恒有确定语义(缺失/非字符串归一 unknown)
|
|
52
|
-
let PKG_VERSION = 'unknown'
|
|
53
|
-
try {
|
|
54
|
-
const parsed = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf8')).version
|
|
55
|
-
if (typeof parsed === 'string' && parsed.length > 0) PKG_VERSION = parsed
|
|
56
|
-
} catch { /* 保留 unknown */ }
|
|
57
|
-
|
|
58
|
-
/** 源目录内容指纹:文件名集合 + 逐文件 size 与 mtime 的聚合。粒度足够感知
|
|
59
|
-
* 同版本内容改动与产物残缺,不引入 hash 依赖 */
|
|
60
|
-
function sourceFingerprint() {
|
|
61
|
-
const parts = []
|
|
62
|
-
const walk = (dir, rel) => {
|
|
63
|
-
for (const name of readdirSync(dir)) {
|
|
64
|
-
const full = join(dir, name)
|
|
65
|
-
const relPath = rel ? `${rel}/${name}` : name
|
|
66
|
-
const st = statSync(full)
|
|
67
|
-
if (st.isDirectory()) walk(full, relPath)
|
|
68
|
-
else parts.push(`${relPath}:${st.size}:${st.mtimeMs}`)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
walk(PRESET_SRC, '')
|
|
72
|
-
return parts.sort().join('|')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** 读 marker;缺失或损坏返回 null(损坏与缺失同义:无法证明归属) */
|
|
76
|
-
function readMarker(dest) {
|
|
77
|
-
const markerPath = join(dest, MARKER_NAME)
|
|
78
|
-
if (!existsSync(markerPath)) return null
|
|
79
|
-
try {
|
|
80
|
-
return JSON.parse(readFileSync(markerPath, 'utf8'))
|
|
81
|
-
} catch {
|
|
82
|
-
return null
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** 完整性校验:受管文件任一缺失即残缺 */
|
|
87
|
-
function isComplete(dest) {
|
|
88
|
-
return MANAGED_FILES.every((rel) => existsSync(join(dest, rel)))
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** 同步释放;返回 'created' | 'updated' | 'unchanged' | 'skipped-foreign' */
|
|
92
|
-
export function syncPreset() {
|
|
93
|
-
if (!existsSync(PRESET_SRC)) throw new Error(`包内 preset 缺失: ${PRESET_SRC}`)
|
|
94
|
-
const dest = presetDest()
|
|
95
|
-
// staging 残留清理与快慢路径无关:硬崩溃后仅 rewrite 清理会让残留长期滞留
|
|
96
|
-
cleanStaleStaging(dirname(dest))
|
|
97
|
-
if (!existsSync(dest)) return rewrite(dest, false)
|
|
98
|
-
const marker = readMarker(dest)
|
|
99
|
-
if (marker === null || marker.package !== PACKAGE_NAME) return 'skipped-foreign'
|
|
100
|
-
// 内容指纹一致即视为最新:双副本 root 交替不再触发整目录重写,
|
|
101
|
-
// 仅 marker.root 归属不同时原地改写 marker 一个文件
|
|
102
|
-
if (marker.fingerprint === sourceFingerprint() && isComplete(dest)) {
|
|
103
|
-
if (marker.root !== PKG_ROOT) {
|
|
104
|
-
writeFileSync(join(dest, MARKER_NAME), JSON.stringify({ ...marker, root: PKG_ROOT }, null, 2) + '\n')
|
|
105
|
-
}
|
|
106
|
-
return 'unchanged'
|
|
107
|
-
}
|
|
108
|
-
return rewrite(dest, true)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** 已确认归属本包后的重写。换入式原子替换:旧目录先 rename 到备份名,新目录
|
|
112
|
-
* rename 入位成功后才删备份;换入失败时尽力还原,还原也失败则把旧副本改名为
|
|
113
|
-
* orphan 前缀(不匹配清理规则,永不被自动删除)并告警,绝不静默销毁。 */
|
|
114
|
-
function rewrite(dest, existed) {
|
|
115
|
-
mkdirSync(dirname(dest), { recursive: true })
|
|
116
|
-
const staging = mkdtempSync(join(dirname(dest), '.rs-workflow-staging-'))
|
|
117
|
-
const backup = join(dirname(dest), `.rs-workflow-old-${Date.now()}`)
|
|
118
|
-
let orphan = null
|
|
119
|
-
try {
|
|
120
|
-
cpSync(PRESET_SRC, join(staging, 'out'), { recursive: true })
|
|
121
|
-
writeFileSync(join(staging, 'out', MARKER_NAME), JSON.stringify({
|
|
122
|
-
package: PACKAGE_NAME,
|
|
123
|
-
version: PKG_VERSION,
|
|
124
|
-
root: PKG_ROOT,
|
|
125
|
-
fingerprint: sourceFingerprint(),
|
|
126
|
-
}, null, 2) + '\n')
|
|
127
|
-
backupUserSlots(dest)
|
|
128
|
-
restoreUserSlots(join(staging, 'out'))
|
|
129
|
-
const hasDest = existsSync(dest)
|
|
130
|
-
if (hasDest) renameSync(dest, backup)
|
|
131
|
-
try {
|
|
132
|
-
renameSync(join(staging, 'out'), dest)
|
|
133
|
-
} catch (error) {
|
|
134
|
-
if (hasDest) {
|
|
135
|
-
try {
|
|
136
|
-
renameSync(backup, dest)
|
|
137
|
-
} catch (restoreError) {
|
|
138
|
-
// dest 缺失且旧副本留存:改用清理规则不匹配的 orphan 前缀,保留待人工处置
|
|
139
|
-
orphan = join(dirname(backup), `.rs-workflow-orphan-${Date.now()}`)
|
|
140
|
-
try {
|
|
141
|
-
renameSync(backup, orphan)
|
|
142
|
-
} catch {
|
|
143
|
-
orphan = backup
|
|
144
|
-
}
|
|
145
|
-
console.warn(`[rs-workflow] preset 换入失败且还原失败,旧目录保留在 ${orphan}(不会被自动清理,需人工处置)`)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
throw error
|
|
149
|
-
}
|
|
150
|
-
} finally {
|
|
151
|
-
rmSync(staging, { recursive: true, force: true })
|
|
152
|
-
if (orphan === null) rmSync(backup, { recursive: true, force: true })
|
|
153
|
-
}
|
|
154
|
-
return existed ? 'updated' : 'created'
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/** 用户改过的 slots.json5 在重写前备份;已回退到模板内容时清除旧备份,防陈旧定制复活 */
|
|
158
|
-
function backupUserSlots(dest) {
|
|
159
|
-
const userSlots = join(dest, SLOTS_REL)
|
|
160
|
-
const template = join(PRESET_SRC, SLOTS_REL)
|
|
161
|
-
const backupPath = join(dshHome(), USER_SLOTS_BACKUP)
|
|
162
|
-
if (!existsSync(userSlots)) return
|
|
163
|
-
let userText
|
|
164
|
-
try {
|
|
165
|
-
userText = readFileSync(userSlots, 'utf8')
|
|
166
|
-
} catch {
|
|
167
|
-
return
|
|
168
|
-
}
|
|
169
|
-
let templateText = ''
|
|
170
|
-
try {
|
|
171
|
-
templateText = readFileSync(template, 'utf8')
|
|
172
|
-
} catch { /* 模板不可读视同定制, 保留现有备份 */ }
|
|
173
|
-
if (userText === templateText && templateText !== '') {
|
|
174
|
-
rmSync(backupPath, { force: true })
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
writeFileSync(backupPath, userText)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/** 待换入目录的 slots 若为模板内容且 home 有备份,写入用户定制;
|
|
181
|
-
* 恢复随换入原子完成,dest 不再出现"模板+待恢复"中间态(崩溃窗口与复活窗口同消) */
|
|
182
|
-
function restoreUserSlots(stagingOut) {
|
|
183
|
-
const backupPath = join(dshHome(), USER_SLOTS_BACKUP)
|
|
184
|
-
if (!existsSync(backupPath)) return
|
|
185
|
-
const userSlots = join(stagingOut, SLOTS_REL)
|
|
186
|
-
if (!existsSync(userSlots)) return
|
|
187
|
-
let userText
|
|
188
|
-
try {
|
|
189
|
-
userText = readFileSync(userSlots, 'utf8')
|
|
190
|
-
} catch {
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
let templateText = ''
|
|
194
|
-
try {
|
|
195
|
-
templateText = readFileSync(join(PRESET_SRC, SLOTS_REL), 'utf8')
|
|
196
|
-
} catch { /* 模板不可读时无法判定, 不动作 */ }
|
|
197
|
-
if (templateText !== '' && userText === templateText) {
|
|
198
|
-
writeFileSync(userSlots, readFileSync(backupPath, 'utf8'))
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/** 清理硬崩溃残留的 staging/备份目录(前缀为本包独占,直接删安全);避免预设扫描把它们当 broken preset 展示 */
|
|
203
|
-
function cleanStaleStaging(parentDir) {
|
|
204
|
-
let entries = []
|
|
205
|
-
try {
|
|
206
|
-
entries = readdirSync(parentDir)
|
|
207
|
-
} catch {
|
|
208
|
-
return
|
|
209
|
-
}
|
|
210
|
-
for (const name of entries) {
|
|
211
|
-
if (name.startsWith('.rs-workflow-staging-') || name.startsWith('.rs-workflow-old-')) {
|
|
212
|
-
rmSync(join(parentDir, name), { recursive: true, force: true })
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/** 删除本包释放的 preset(仅供维护脚本/手工调用,插件生命周期内不触发)。
|
|
218
|
-
* 返回三态:'removed' 已删 | 'missing' 目录不存在 | 'foreign' 外来目录拒绝删除 */
|
|
219
|
-
export function removePreset() {
|
|
220
|
-
const dest = presetDest()
|
|
221
|
-
if (!existsSync(dest)) return 'missing'
|
|
222
|
-
const marker = readMarker(dest)
|
|
223
|
-
if (marker === null || marker.package !== PACKAGE_NAME) return 'foreign'
|
|
224
|
-
rmSync(dest, { recursive: true, force: true })
|
|
225
|
-
cleanStaleStaging(dirname(dest))
|
|
226
|
-
return 'removed'
|
|
227
|
-
}
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
# The `rs-workflow` agent preset: 若水工作流。
|
|
2
|
-
#
|
|
3
|
-
# 迁移自 rs-tui (C:\Users\yuanhao\Desktop\jzjy\rscli) 的 rs-workflow 多模型协作工作流引擎。
|
|
4
|
-
# 基底是 `standard` 全功能编码代理(本文件行集与出厂 standard 保持同构),
|
|
5
|
-
# 差异有四处:
|
|
6
|
-
# 1. persona 换成若水工作流纪律(非平凡需求走 workflow 编排);
|
|
7
|
-
# 2. skill-filesystem 追加预设自带技能目录 skills/(rs-workflow 协议技能随预设分发);
|
|
8
|
-
# 3. 追加 GUI 配置读取工具行 rs-workflow-config(@mzzsfy/dsh-rs-workflow
|
|
9
|
-
# 的 tool 角色,裸包名从组合 baseUrl 上溯解析到 profile node_modules;
|
|
10
|
-
# GUI 表单注册在同包 settings 角色、bundle patch 行上;本文件整体由该包
|
|
11
|
-
# 的 preset-sync 角色行释放到用户预设根,随插件安装/升级自动同步);
|
|
12
|
-
# 4. 其余行保持 standard 原样 —— 工具面不变,模式差异只由 persona + 技能承载。
|
|
13
|
-
#
|
|
14
|
-
# 平面规则与 standard 一致:本文件是 AGENT-PLANE 组合;服务行必须在 isolate realm 后面。
|
|
15
|
-
|
|
16
|
-
# ── identity ────────────────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
- id: persona
|
|
19
|
-
name: '@deepseek-ai/dsh-persona'
|
|
20
|
-
config:
|
|
21
|
-
text: |-
|
|
22
|
-
你是"若水工作流"编码 Agent,由 {{model}} 模型驱动,工作目录 {{cwd}}。
|
|
23
|
-
本模式迁移自 rs-tui 的多模型协作工作流引擎:executor 执行、reviewer 审批、planner 规划分诊,模型间分工与相互制约。
|
|
24
|
-
|
|
25
|
-
核心纪律:
|
|
26
|
-
- 非平凡编码需求(多步骤、多文件、质量敏感或有回归风险)必须先加载 rs-workflow 技能,按其协议用 workflow 工具启动编排:分诊与规划 → 执行与审批 → 终审,必要时升级重规划。
|
|
27
|
-
- 琐碎请求(问答、解释、单点小改)直接处理,不空转流程(off 态)。用户点名模板(lite / plan-final / step-review / multi-plan)时锁定该模板。
|
|
28
|
-
- 各角色的模型由工作位(slot)配置决定:细分位缺失降级基础位,再降级会话默认模型;配置以 GUI 设置页 rs-workflow 段为准(启动编排前用 rs_workflow_config 工具读取,改后即时生效),工具不可用时回退技能目录的 slots.json5。
|
|
29
|
-
- 工作流结束后必须向用户完整汇报:模板、难度分诊、任务结果、审批结论、变更文件;blocked 时说明原因并给出建议。
|
|
30
|
-
|
|
31
|
-
- id: agent-instructions
|
|
32
|
-
name: '@deepseek-ai/dsh-agent-instructions'
|
|
33
|
-
config:
|
|
34
|
-
maxBytes: 65536
|
|
35
|
-
|
|
36
|
-
# ── shell ───────────────────────────────────────────────────────────────────
|
|
37
|
-
|
|
38
|
-
# 与 standard 相同:两个 shell 工具消费 HOST 侧注册表,执行器也在 host 平面。
|
|
39
|
-
- id: tool-bash
|
|
40
|
-
name: '@deepseek-ai/dsh-tool-bash'
|
|
41
|
-
disabled: !!js process.platform === 'win32'
|
|
42
|
-
|
|
43
|
-
- id: tool-pwsh
|
|
44
|
-
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
45
|
-
disabled: !!js process.platform !== 'win32'
|
|
46
|
-
|
|
47
|
-
# ── filesystem ──────────────────────────────────────────────────────────────
|
|
48
|
-
|
|
49
|
-
- id: tool-fs
|
|
50
|
-
name: '@deepseek-ai/dsh-tool-fs'
|
|
51
|
-
|
|
52
|
-
- id: tool-fs-search
|
|
53
|
-
name: '@deepseek-ai/dsh-tool-fs-search'
|
|
54
|
-
config:
|
|
55
|
-
sampleOverCapGlobResults: false
|
|
56
|
-
|
|
57
|
-
# ── background jobs ────────────────────────────────────────────────────────
|
|
58
|
-
|
|
59
|
-
- id: tool-jobs
|
|
60
|
-
name: '@deepseek-ai/dsh-tool-jobs'
|
|
61
|
-
|
|
62
|
-
# ── skills ──────────────────────────────────────────────────────────────────
|
|
63
|
-
|
|
64
|
-
# 与 cordis 出厂预设相同的手法:customSkillDirs 指向预设自己的 skills/ 目录,
|
|
65
|
-
# baseUrl 即预设目录本身,因此 rs-workflow 协议技能随预设安装、复制、迁移。
|
|
66
|
-
- id: skill-filesystem
|
|
67
|
-
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
68
|
-
config:
|
|
69
|
-
customSkillDirs:
|
|
70
|
-
- !!js "process.getBuiltinModule('node:url').fileURLToPath(new URL('skills/', baseUrl))"
|
|
71
|
-
|
|
72
|
-
- id: tool-skill
|
|
73
|
-
name: '@deepseek-ai/dsh-tool-skill'
|
|
74
|
-
|
|
75
|
-
# 若水工作流的 GUI 配置读取工具(rs_workflow_config):主代理启动编排前调用。
|
|
76
|
-
# 包本体由 `dsh plugin add @mzzsfy/dsh-rs-workflow` 安装进 profile node_modules;
|
|
77
|
-
# 本 preset 文件由同包的 preset-sync 角色行在宿主启动时释放/同步到用户预设根。
|
|
78
|
-
# 预设行的裸包名从组合 baseUrl(profile 目录)上溯解析;settings 表单的注册
|
|
79
|
-
# 在同包 bundle patch 行上(见包内 cordis.patch.yml)。
|
|
80
|
-
- id: rs-workflow-config
|
|
81
|
-
name: '@mzzsfy/dsh-rs-workflow'
|
|
82
|
-
config:
|
|
83
|
-
role: tool
|
|
84
|
-
|
|
85
|
-
# ── goals ───────────────────────────────────────────────────────────────────
|
|
86
|
-
|
|
87
|
-
- id: tool-goal
|
|
88
|
-
name: '@deepseek-ai/dsh-tool-goal'
|
|
89
|
-
|
|
90
|
-
# ── plan mode ───────────────────────────────────────────────────────────────
|
|
91
|
-
|
|
92
|
-
# 保留 standard 的计划模式:用户仍可把会话切到 plan 模式做前置规划,
|
|
93
|
-
# 与工作流不冲突(工作流自身的规划发生在编排内部)。
|
|
94
|
-
- id: planning
|
|
95
|
-
name: cordis:group
|
|
96
|
-
group: true
|
|
97
|
-
isolate:
|
|
98
|
-
planMode: true
|
|
99
|
-
config:
|
|
100
|
-
- id: plan-mode
|
|
101
|
-
name: '@deepseek-ai/dsh-plan-mode'
|
|
102
|
-
config:
|
|
103
|
-
section: |
|
|
104
|
-
You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
|
105
|
-
|
|
106
|
-
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
|
107
|
-
|
|
108
|
-
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
|
109
|
-
|
|
110
|
-
Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
|
|
111
|
-
|
|
112
|
-
Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
|
|
113
|
-
|
|
114
|
-
When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
|
|
115
|
-
|
|
116
|
-
# ── compaction ──────────────────────────────────────────────────────────────
|
|
117
|
-
|
|
118
|
-
- id: compaction
|
|
119
|
-
name: cordis:group
|
|
120
|
-
group: true
|
|
121
|
-
isolate:
|
|
122
|
-
compaction: true
|
|
123
|
-
toolResultPruner: true
|
|
124
|
-
config:
|
|
125
|
-
- id: compaction-basic
|
|
126
|
-
name: '@deepseek-ai/dsh-compaction-basic'
|
|
127
|
-
|
|
128
|
-
- id: command-compact
|
|
129
|
-
name: '@deepseek-ai/dsh-command-compact'
|
|
130
|
-
|
|
131
|
-
- id: tool-result-pruner
|
|
132
|
-
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
133
|
-
config:
|
|
134
|
-
thresholdChars: 8192
|
|
135
|
-
headChars: 4096
|
|
136
|
-
tailChars: 1024
|
|
137
|
-
|
|
138
|
-
# ── delegation and workflows ────────────────────────────────────────────────
|
|
139
|
-
|
|
140
|
-
# 与 standard 相同:subagents 注册表留在 HOST;workflowEngine 是预设私有服务,
|
|
141
|
-
# 所有触达它的行共享一个 entry-local realm。
|
|
142
|
-
- id: delegation
|
|
143
|
-
name: cordis:group
|
|
144
|
-
group: true
|
|
145
|
-
isolate:
|
|
146
|
-
workflowEngine: true
|
|
147
|
-
config:
|
|
148
|
-
- id: tool-subagent-control
|
|
149
|
-
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
150
|
-
|
|
151
|
-
- id: tool-subagent-list-agents
|
|
152
|
-
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
|
153
|
-
|
|
154
|
-
- id: tool-subagent
|
|
155
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
156
|
-
config:
|
|
157
|
-
provider: spawn
|
|
158
|
-
toolName: subagent
|
|
159
|
-
backgroundMode: continuable
|
|
160
|
-
|
|
161
|
-
- id: tool-subagent-fork
|
|
162
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
163
|
-
config:
|
|
164
|
-
provider: fork
|
|
165
|
-
toolName: subagent_fork
|
|
166
|
-
backgroundMode: continuable
|
|
167
|
-
|
|
168
|
-
- id: workflow-worker-thread
|
|
169
|
-
name: '@deepseek-ai/dsh-workflow-worker-thread'
|
|
170
|
-
config:
|
|
171
|
-
provider: spawn
|
|
172
|
-
|
|
173
|
-
- id: tool-workflow
|
|
174
|
-
name: '@deepseek-ai/dsh-tool-workflow'
|
|
175
|
-
|
|
176
|
-
- id: tool-ralph
|
|
177
|
-
name: '@deepseek-ai/dsh-tool-ralph'
|
|
178
|
-
config:
|
|
179
|
-
subagentProvider: spawn
|
|
180
|
-
maxRounds: 64
|
|
181
|
-
|
|
182
|
-
# ── remaining model-facing rows ─────────────────────────────────────────────
|
|
183
|
-
|
|
184
|
-
- id: tool-ask-user
|
|
185
|
-
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
186
|
-
|
|
187
|
-
- id: tool-todo
|
|
188
|
-
name: '@deepseek-ai/dsh-tool-todo'
|
|
189
|
-
config:
|
|
190
|
-
allowParallelInProgress: true
|
|
191
|
-
|
|
192
|
-
- id: tool-web
|
|
193
|
-
name: '@deepseek-ai/dsh-tool-web'
|
|
194
|
-
config:
|
|
195
|
-
fetch: false
|
|
196
|
-
searchTimeoutMs: 60000
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: rs-workflow
|
|
3
|
-
description: 若水多模型协作工作流。非平凡编码需求(多步骤/多文件/质量敏感/有回归风险)必须先加载本技能,按协议用 workflow 工具启动编排(planner 分诊规划 / executor 执行 / reviewer 审批,16 工作位,4 模板,预算化审批循环,升级重规划)。琐碎问答或用户明说"直接做"时不要加载。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# 若水工作流 (rs-workflow)
|
|
7
|
-
|
|
8
|
-
迁移自 rs-tui 的多模型协作工作流引擎。三角色分工:**planner** 规划分诊、**executor** 执行、**reviewer** 审批;引擎按模板蓝图调度 DAG 节点,审批循环与升级重规划由脚本保证。
|
|
9
|
-
|
|
10
|
-
## 0. 职责边界(必读)
|
|
11
|
-
|
|
12
|
-
你是主代理(leader),**不亲自执行工作流内的任务**。你只负责四件事:
|
|
13
|
-
|
|
14
|
-
1. 判定是否进入工作流;
|
|
15
|
-
2. 调用 rs_workflow_config 工具读取工作位(slot)与工作流默认配置;
|
|
16
|
-
3. 用 workflow 工具启动编排脚本;
|
|
17
|
-
4. 向用户汇报结果。
|
|
18
|
-
|
|
19
|
-
执行、审批、规划全部由编排内的子代理完成。你不在工作流运行期间并行改动代码。
|
|
20
|
-
|
|
21
|
-
## 1. 进入判定
|
|
22
|
-
|
|
23
|
-
- **直接处理(off 态)**:纯问答/解释;用户明说"直接做/别走流程";单文件、无风险的小改。
|
|
24
|
-
- **进入工作流**:其余编码需求——多文件、多步骤、质量敏感、有回归风险、需要先规划的任务。
|
|
25
|
-
- 用户点名模板(lite / plan-final / step-review / multi-plan)→ 作为 `lockedTemplate` 传入;未点名 → 由 planner 分诊选模板。
|
|
26
|
-
- 用户想改角色模型/默认模板/预算 → 让用户在 GUI 设置页的 rs-workflow 段修改(改完即时生效);GUI 不可用时才改 slots.json5。
|
|
27
|
-
|
|
28
|
-
## 2. 启动步骤(严格按序)
|
|
29
|
-
|
|
30
|
-
1. **读工作流配置**:调用 `rs_workflow_config` 工具,取回 `{ slots, workflow, budgets, source }`。它反映 GUI 设置页 rs-workflow 段的当前配置(3 基础+13 细分工作位模型绑定、默认模板、任务上限、预算)。工具不可用或 `source=fallback`(设置服务不可用)时,回退读本技能目录下的 `slots.json5`:JSON5 解析后**取其 `slots` 属性**作为下面的 `slots`(空字符串 = 未配置);该后备文件只含 slots,此时 `defaultTemplate`/`limits`/`budgets` 一并省略,由引擎缺省值兜底。
|
|
31
|
-
2. **勘察仓库(可选)**:把与需求相关的要点(目录结构、相关文件、构建/测试命令)写进 `contextNotes`,不超过 30 行;已经熟悉仓库可传空字符串。
|
|
32
|
-
3. **读引擎脚本**:读本技能 Base directory 下 `references/engine.js` 的**全文**,作为 workflow 的 `script` 参数原样传入——不要改写、不要截断、不要"优化"。
|
|
33
|
-
4. **调用 workflow 工具**,三个参数:
|
|
34
|
-
- `meta`:`{ name: "rs-workflow", description: "<一句话需求>", phases: [{title: "分诊与规划"}, {title: "执行与审批"}, {title: "升级重规划"}, {title: "汇总"}] }`
|
|
35
|
-
- `script`:engine.js 全文
|
|
36
|
-
- `args`:`{ request: <用户需求原话>, contextNotes: <第2步要点或空串>, slots: <第1步 slots,16 键>, lockedTemplate: <见下>, defaultTemplate: <见下>, limits: <见下>, budgets: <见下>, prefix: <见下> }`
|
|
37
|
-
- `lockedTemplate`:仅用户点名了模板 → 传点名的;未点名省略,由引擎分诊定模板。
|
|
38
|
-
- `defaultTemplate`:第 1 步 `workflow.defaultTemplate` 原值(`auto` 也照传,引擎自行兜底 multi-plan)。
|
|
39
|
-
- `limits`:第 1 步 `workflow.maxTasks` 存在时传 `limits: { maxTasks: <值> }`。
|
|
40
|
-
- `budgets`:第 1 步 `budgets` 原样透传(审批/重问预算,字段见 §6)。
|
|
41
|
-
- 断点续跑(见 §4):`prefix: <上轮已完成任务数组>`,元素取上轮返回 `tasks[]` 中 `status="done"` 且 `type="task"` 的 `{ id, description, output: <summary>, changedFiles }`(引擎返回的 tasks 字段名是 `summary`,映射为 prefix 元素的 `output`);仅 lite / plan-final / step-review 生效,multi-plan 会忽略并记日志。
|
|
42
|
-
5. 工作流在前台运行到结束才返回,等待期间不做其他改动。
|
|
43
|
-
|
|
44
|
-
## 3. 汇报(工作流返回后,必做)
|
|
45
|
-
|
|
46
|
-
用中文向用户汇报,依次包含:
|
|
47
|
-
|
|
48
|
-
- 模板与难度分诊(complexity / risk / scope 三信号 + planner 理由;模板来源注明:引用返回 `templateSource` 字段——`locked`(用户/配置锁定)、`declared`(planner 声明采纳)、`matrix`(引擎矩阵裁定)、`default-fallback`(无信号兜底));
|
|
49
|
-
- 每个任务的结果与执行摘要;
|
|
50
|
-
- 审批结论(计划审 / 逐步审 / 子计划审 / 终审 / 交叉终审链),逐项附:审批证据要点(reviewer 实际执行的检查与结果)、范围核查结果(是否发现申报清单外且不属于其他任务申报范围的越界文件)、审批者故障披露(引用返回 `reviews[].reviewerFault: true` 的条目——含义是引擎已把该审批按拒绝处理:审批者不可用或 APPROVED 缺证据预算耗尽折算,交付型可原样重交、计划型转计划重规划;leader 只披露,不自行重交);multi-plan 模板按蓝图返回 `reviews`:大纲审(pr)→ 各子计划单元内逐步审(r-*)与子计划审(sr)→ 交叉终审串行链 xr1/xr2(均挂 `reviewer-cross` 位,xr1 审正确性、xr2 审边界与安全,xr2 依赖 xr1 通过,任一拒绝回跳最后一个已完成 task 重做后重挂);blocked 时的返回中未运行到的审批 `verdict` 为 `UNREVIEWED`,如实报"未运行到",不冒充通过或驳回;
|
|
51
|
-
- 变更文件清单;
|
|
52
|
-
- 升级重规划次数、换模型重做情况(slot 配了候选数组/rotation 时)、本次是否以 prefix 续跑及续跑任务数;blocked 时原因与建议(锁定更重的模板重跑 / 缩小需求范围 / 补充信息)。
|
|
53
|
-
|
|
54
|
-
汇报前运行一次仓库验证(构建/测试)确认工作区真实状态,再交用户验收。
|
|
55
|
-
|
|
56
|
-
## 4. 故障处置
|
|
57
|
-
|
|
58
|
-
- workflow 调用报错(脚本被杀 / caps 超限):读错误信息;保存上一次返回的 `tasks` 与 `reviews`(必要时含 `blocked`),把 `tasks[]` 中 `status="done"` 且 `type="task"` 的条目按 §2 第 4 步的 prefix 契约整理为数组重新调用 workflow 续跑——不缩 maxTasks、不重做已完成工作;取不回上轮结果时降级为主代理直接实现,并向用户声明"工作流引擎故障,已降级直发"。
|
|
59
|
-
- 返回 `ok: false` 且 `blocked`:如实汇报,不粉饰;可按上一条以 prefix 续跑剩余工作,或缩小需求范围重跑。
|
|
60
|
-
|
|
61
|
-
## 5. 工作位(slot)降级链
|
|
62
|
-
|
|
63
|
-
3 基础位:`planner` / `executor` / `reviewer`。13 细分位未配置时降级到同域基础位,基础位未配置时降级到会话默认模型(细分位 → 基础位 → 会话默认模型):
|
|
64
|
-
|
|
65
|
-
| 细分位 | 降级到 | 用途 |
|
|
66
|
-
|---|---|---|
|
|
67
|
-
| `planner-triage` | `planner` | 首次分诊:分析需求选模板拆任务(建议快而便宜的模型) |
|
|
68
|
-
| `planner-command` | `planner` | 总规划:制定整体方案 / 拆子计划大纲;计划被拒后的计划重写 |
|
|
69
|
-
| `planner-subplan` | `planner` | 子计划细化:子计划内的计划 |
|
|
70
|
-
| `planner-escalate` | `planner` | 升级重规划:连续拒绝超阈后的尾段重拆 / 返工重规划 |
|
|
71
|
-
| `reviewer-plan` | `reviewer` | 计划审批:审批计划文本(计划审 / 大纲审,中等推理即可) |
|
|
72
|
-
| `reviewer-task` | `reviewer` | 任务审批:审批单个任务执行结果 |
|
|
73
|
-
| `reviewer-subplan` | `reviewer` | 子计划交付审批:审批整个子计划交付 |
|
|
74
|
-
| `reviewer-final` | `reviewer` | 单终审:末尾终审全部交付(建议最强推理的模型) |
|
|
75
|
-
| `reviewer-cross` | `reviewer` | 交叉终审:多视角交叉终审链(建议最强推理的模型) |
|
|
76
|
-
| `executor-task` | `executor` | 任务首次执行 |
|
|
77
|
-
| `executor-enhance` | `executor` | 被拒重做:携带 REJECTED 理由修改重交(可配数组实现换模型重做) |
|
|
78
|
-
| `executor-retry` | `executor` | 失败重试:自报失败 / 无凭证后的重试 |
|
|
79
|
-
| `executor-escalate` | `executor` | 升级后执行:升级重规划产出的新任务 |
|
|
80
|
-
|
|
81
|
-
配好后引擎按节点语境自动选位:任务首执行=`executor-task`、被拒重做=`executor-enhance`、失败重试=`executor-retry`、升级新任务=`executor-escalate`、计划审=`reviewer-plan`、任务审=`reviewer-task`、子计划审=`reviewer-subplan`、终审=`reviewer-final`、交叉终审=`reviewer-cross`、分诊=`planner-triage`、计划重写=`planner-command`、子计划细化=`planner-subplan`、升级重规划=`planner-escalate`。
|
|
82
|
-
|
|
83
|
-
值格式:`"provider/model"` 字符串、候选数组 `["a/m1", "b/m2"]`、`{ rotation: ["a/m1", "b/m2"] }`(前三态与 GUI 设置 schema 一致)或 `{ provider, model }` 对象(引擎兼容形态,GUI 配置路径不可产出):单候选/rotation 内失效时依次故障转移,被拒重做与审批重问从下一候选换模型(节点级游标轮换)。必须是当前部署模型路由里真实存在的目标。适配红线:reviewer 弱于 executor = 审批形同虚设。配置来源优先级:GUI 设置页 rs-workflow 段(`rs_workflow_config` 工具读取,即时生效)> 本技能目录 `slots.json5`(后备)> 会话默认模型。
|
|
84
|
-
|
|
85
|
-
## 6. 模板与阈值(细节见 references/templates.md)
|
|
86
|
-
|
|
87
|
-
| 模板 | 形态 |
|
|
88
|
-
|---|---|
|
|
89
|
-
| lite | 单发终审:单任务直干,末尾终审一次 |
|
|
90
|
-
| plan-final | 计划审 + 终审:前置总计划(pr 计划审通过才执行),末尾终审 |
|
|
91
|
-
| step-review | 计划审 + 逐步审:前置总计划(pr),每任务执行完即审,通过才放行下一步 |
|
|
92
|
-
| multi-plan | 大纲审 + 子计划审 + 交叉终审链:总规划拆子计划大纲(pr 大纲审),子计划单元逐个细化执行(子计划内逐步审 + 子计划交付审),末尾交叉终审串行链 xr1(正确性)→ xr2(边界与安全) |
|
|
93
|
-
|
|
94
|
-
阈值(budgets 四字段,GUI/slots.json5 可配,clamp [1,10],缺省 2/2/3/3):`reviewRejectBeforeEscalate`=2(交付型审批对象连续被拒或失败达此值 → 升级)、`planRejectBeforeBlocked`=2(计划型审批对象连续被拒达此值 → 升级)、`emptyOutputRetryLimit`=3(审批缺验证证据时的重问上限,超限视为拒绝)、`reportNudgeLimit`=3(executor 返回 completed 但交接摘要空白时的补救追问上限);升级重规划累计 `ESCALATION_LIMIT`=**2** 次 → blocked(固定,不可配)。审批通过清零规则:交付类审批(逐步审/子计划审/终审/交叉终审)通过清零升级账,计划审批通过只放行不清零。审批 fail-closed:审批者不可用视为拒绝(交付型可原样重交,计划审/大纲审带此原因转计划重规划),APPROVED 必须附验证证据。
|
|
95
|
-
|
|
96
|
-
分诊口径:缺失信号按 low/small 降级(planner 未标注的信号视为不存在);三信号全缺 → 按 `defaultTemplate` 兜底(`auto`/缺省 → multi-plan);planner 可在计划中声明模板,合法才采纳,非法仍落引擎矩阵兜底。
|
|
97
|
-
|
|
98
|
-
拆解与审批基准:planner 拆解时每任务可带 acceptance(可独立验证的验收判据)与 files(预期触达文件,作范围核查申报基线),任务描述禁止占位措辞(TBD/"适当处理"/"同任务 N"式描述视为计划缺陷);任务审批基准为可判定清单——只审本任务改动,每条判据须带可核证据(测试名/命令输出/file:line),不确定写明;审批结论可带 severity(critical/important/minor),仅丰富报告、不作通过门控(APPROVED/REJECTED + evidence 契约不变),critical 问题必须进 reasons;带 fixNote 的复审只判定驳回点是否解决与是否引入新问题,不扩大审查范围。
|
|
99
|
-
|
|
100
|
-
## 7. 庞大需求分阶段编排
|
|
101
|
-
|
|
102
|
-
单次 workflow 有规模上限(全局任务预算 `maxTasks` / 代理数 caps),超大需求不硬塞单次编排,改用分阶段协议:
|
|
103
|
-
|
|
104
|
-
- **触发**:分诊 `scope=large` 且拆解触达全局任务预算 `maxTasks`、上轮 blocked 且建议缩小范围,或用户明说"分阶段做"。
|
|
105
|
-
- **动作**:
|
|
106
|
-
1. leader 先产出阶段大纲(把需求划分为若干可独立验收的 phase,每个 phase 一个明确目标与验收标准);用 goal 工具(`create_goal`)把总目标固化为持久目标,由 goal 轮次自动续跑推进;
|
|
107
|
-
2. 逐 phase 调用 workflow(§2 流程,`request` 写该 phase 的目标与验收标准,`contextNotes` 带上仓库要点与上一 phase 的产物摘要);
|
|
108
|
-
3. 每个 phase 返回后向用户简报该 phase 结果,goal 轮次自动续跑下一 phase;
|
|
109
|
-
4. 某 phase blocked → 用 goal 工具如实标记 blocked(`update_goal`,附原因),带原因与建议,不缩小总目标范围。
|
|
110
|
-
- **注意**:多 phase 共享同一工作区,phase 划分必须文件域互斥或串行依赖明确;每 phase 用什么模板由该 phase 的分诊矩阵裁定,用户可点名。
|