@mzzsfy/dsh-rs-workflow 0.2.4 → 1.0.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/README.md +14 -49
- package/cordis.patch.yml +8 -17
- package/engine/flow-exec.js +23 -0
- 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 +9 -5
- package/preset/rs-workflow/agent.cordis.yml +54 -73
- package/preset/rs-workflow/preset.yml +4 -4
- package/src/client.js +1269 -0
- package/lib/preset-sync.mjs +0 -227
- 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,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 的分诊矩阵裁定,用户可点名。
|