@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.
@@ -0,0 +1,341 @@
1
+ // 模板静态校验器:DSL v5 唯一权威(board 保存校验唯一入口);v5 新增顶层 autoApprove 键
2
+
3
+ export const SLOT_KEYS = ['planner', 'executor', 'reviewer', 'executor-loop', 'reviewer-approve', 'executor-escalate']
4
+ const TOP_FIELDS = new Set(['id', 'label', 'description', 'inputs', 'steps', 'autoApprove'])
5
+ const STEP_FIELDS = new Set(['id', 'label', 'slot', 'prompt', 'load', 'outputs', 'listOutputs', 'after', 'maxFail', 'for_each', 'mode', 'type', 'flow', 'input', 'target', 'rounds', 'onExhausted'])
6
+ const ID_RE = /^[a-z][a-z0-9-]*$/
7
+ const INPUT_NAME_RE = /^[a-z][a-zA-Z0-9]*$/
8
+ // 保留步骤 id:{input.x} 占位符以入参命名空间优先解析,同 id 步骤产出会被遮蔽
9
+ const RESERVED_STEP_IDS = new Set(['input'])
10
+ const PLACEHOLDER_RE = /\{([^{}]+)\}/g
11
+ const LOAD_PREFIX_RE = /^(skill|doc):/
12
+ const MAX_ROUND = 10
13
+ const MAX_NEST_DEPTH = 3
14
+
15
+ const VERDICT_SCHEMA = {
16
+ type: 'object',
17
+ required: ['verdict', 'comments'],
18
+ additionalProperties: false,
19
+ properties: { verdict: { type: 'string', enum: ['APPROVED', 'REJECTED'] }, comments: { type: 'string' } },
20
+ }
21
+
22
+ const err = (target, message) => ({ target, message })
23
+ const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v)
24
+ const placeholdersOf = (text) => [...String(text).matchAll(PLACEHOLDER_RE)].map((m) => m[1])
25
+
26
+ // JSON 解析;失败抛错,调用方经 parseErrorLine 提取行号
27
+ export function parseTemplate(text) {
28
+ return JSON.parse(text)
29
+ }
30
+
31
+ export function parseErrorLine(message) {
32
+ const text = String(message)
33
+ // V8 JSON.parse 形态 "(line 1 column 3)" 与 "3:5" 位置形态双兼容
34
+ const v8 = text.match(/\bline (\d+)\b/)
35
+ if (v8) return Number(v8[1])
36
+ const m = text.match(/(?:^|\s)(\d+):(\d+)(?:\s|$|\))/)
37
+ return m ? Number(m[1]) : null
38
+ }
39
+
40
+ // 单模板校验:结构/字段/引用/环/审批与升级规则;返回 errors[](空=通过)
41
+ export function validateTemplate(t) {
42
+ const errors = []
43
+ if (!isObj(t)) return [err('json', '模板必须是对象')]
44
+ for (const k of Object.keys(t)) if (!TOP_FIELDS.has(k)) errors.push(err(`top:${k}`, `未知顶层字段 ${k}`))
45
+ if (typeof t.id !== 'string' || !ID_RE.test(t.id)) errors.push(err('top:id', 'id 必填且须匹配 ^[a-z][a-z0-9-]*$'))
46
+ if (typeof t.label !== 'string' || t.label.trim() === '') errors.push(err('top:label', 'label 必填且为非空字符串'))
47
+ if (typeof t.description !== 'string' || t.description.trim() === '') errors.push(err('top:description', 'description 必填且为非空字符串(分诊目录/保底 brief 依据)'))
48
+ if (t.autoApprove !== undefined && typeof t.autoApprove !== 'boolean') errors.push(err('top:autoApprove', 'autoApprove 须为布尔(缺省 false;true = 审批由主循环代审)'))
49
+
50
+ const declaredInputs = new Set()
51
+ if (t.inputs !== undefined) {
52
+ if (!isObj(t.inputs)) errors.push(err('top:inputs', 'inputs 须为对象(name: 说明)'))
53
+ else {
54
+ for (const [k, v] of Object.entries(t.inputs)) {
55
+ if (!INPUT_NAME_RE.test(k)) errors.push(err('top:inputs', `入参名 ${k} 须匹配 ${INPUT_NAME_RE.source}`))
56
+ if (typeof v !== 'string') errors.push(err('top:inputs', `入参 ${k} 的说明须为字符串`))
57
+ declaredInputs.add(k)
58
+ }
59
+ }
60
+ }
61
+
62
+ if (!Array.isArray(t.steps) || t.steps.length === 0) {
63
+ errors.push(err('top:steps', 'steps 必填且为非空数组'))
64
+ return errors
65
+ }
66
+
67
+ const byId = new Map()
68
+ for (const s of t.steps) {
69
+ if (isObj(s) && typeof s.id === 'string') {
70
+ if (byId.has(s.id)) errors.push(err('top:steps', `步骤 id 重复:${s.id}`))
71
+ byId.set(s.id, s)
72
+ }
73
+ }
74
+
75
+ // escalate-only 集:被任一 approve 步 onExhausted 指向的步骤;记其审批对象(target)用于产出引用禁令
76
+ const escalateTargetOf = new Map()
77
+ for (const s of t.steps) {
78
+ if (isObj(s) && s.type === 'approve' && typeof s.target === 'string' && typeof s.onExhausted === 'string' && s.onExhausted !== 'blocked' && byId.has(s.onExhausted)) {
79
+ escalateTargetOf.set(s.onExhausted, s.target)
80
+ }
81
+ }
82
+
83
+ const order = []
84
+ for (const s of t.steps) {
85
+ const sid = isObj(s) && typeof s.id === 'string' ? s.id : `#${order.length}`
86
+ order.push(sid)
87
+ if (!isObj(s)) {
88
+ errors.push(err('top:steps', `步骤 ${sid} 必须是对象`))
89
+ continue
90
+ }
91
+ if (typeof s.id !== 'string' || !ID_RE.test(s.id)) {
92
+ errors.push(err('top:steps', `步骤 id 必填且须匹配 ${ID_RE.source}`))
93
+ continue
94
+ }
95
+ const target = `step:${s.id}`
96
+ if (RESERVED_STEP_IDS.has(s.id)) errors.push(err(target, `步骤 id ${s.id} 为保留字,与子流程入参命名空间冲突`))
97
+ for (const k of Object.keys(s)) if (!STEP_FIELDS.has(k)) errors.push(err(target, `未知步骤字段 ${k}`))
98
+ const type = s.type === undefined ? 'ai' : s.type
99
+ if (!['ai', 'flow', 'approve'].includes(type)) errors.push(err(target, `type 仅支持 ai/flow/approve,得到 ${type}`))
100
+
101
+ if (s.slot !== undefined) {
102
+ if (!SLOT_KEYS.includes(s.slot)) errors.push(err(target, `slot 仅六键:${SLOT_KEYS.join('/')}`))
103
+ if (type === 'approve' && s.slot !== 'reviewer-approve') errors.push(err(target, 'approve 步骤 slot 固定 reviewer-approve,不可声明其他值'))
104
+ }
105
+ if (s.label !== undefined && typeof s.label !== 'string') errors.push(err(target, 'label 须为字符串'))
106
+ if (s.maxFail !== undefined && (!Number.isInteger(s.maxFail) || s.maxFail < 1 || s.maxFail > MAX_ROUND)) errors.push(err(target, `maxFail 须为 [1,${MAX_ROUND}] 整数`))
107
+
108
+ if (s.outputs !== undefined) {
109
+ if (!isObj(s.outputs)) errors.push(err(target, 'outputs 须为对象(字段名: 说明)'))
110
+ else for (const [k, v] of Object.entries(s.outputs)) {
111
+ if (k.trim() === '') errors.push(err(target, 'outputs 字段名不可为空'))
112
+ if (typeof v !== 'string') errors.push(err(target, `outputs.${k} 的说明须为字符串`))
113
+ }
114
+ }
115
+ const outputNames = new Set(isObj(s.outputs) ? Object.keys(s.outputs) : [])
116
+ if (s.listOutputs !== undefined) {
117
+ if (!Array.isArray(s.listOutputs)) errors.push(err(target, 'listOutputs 须为字符串数组'))
118
+ else for (const n of s.listOutputs) {
119
+ if (!outputNames.has(n)) errors.push(err(target, `listOutputs 项 ${n} 未在本步 outputs 中声明`))
120
+ }
121
+ }
122
+ if (s.load !== undefined) {
123
+ if (!Array.isArray(s.load) || s.load.some((v) => typeof v !== 'string' || !LOAD_PREFIX_RE.test(v))) {
124
+ errors.push(err(target, 'load 须为字符串数组且每项带 skill:/doc: 前缀'))
125
+ }
126
+ }
127
+ if (s.after !== undefined) {
128
+ if (!Array.isArray(s.after) || s.after.some((v) => typeof v !== 'string')) errors.push(err(target, 'after 须为步骤 id 数组'))
129
+ else for (const dep of s.after) {
130
+ if (dep === s.id) errors.push(err(target, 'after 不可引用自身'))
131
+ else if (!byId.has(dep)) errors.push(err(target, `after 引用不存在的步骤 ${dep}`))
132
+ }
133
+ }
134
+ if (s.for_each !== undefined) {
135
+ if (typeof s.for_each !== 'string' || !/^[a-z][a-z0-9-]*\.[a-zA-Z0-9]+$/.test(s.for_each)) {
136
+ errors.push(err(target, 'for_each 须为 "<步骤id>.<产出名>" 形态'))
137
+ } else {
138
+ const [srcId, srcOut] = s.for_each.split('.')
139
+ const src = byId.get(srcId)
140
+ if (!src) errors.push(err(target, `for_each 数据源步骤 ${srcId} 不存在`))
141
+ else if (!(Array.isArray(src.listOutputs) && src.listOutputs.includes(srcOut))) {
142
+ errors.push(err(target, `for_each 数据源 ${s.for_each} 未在该步骤 listOutputs 中声明`))
143
+ }
144
+ }
145
+ }
146
+ if (s.mode !== undefined) {
147
+ if (!['sequential', 'parallel'].includes(s.mode)) errors.push(err(target, 'mode 仅支持 sequential/parallel'))
148
+ if (s.for_each === undefined) errors.push(err(target, 'mode 仅在声明 for_each 时有效'))
149
+ }
150
+ // sequential 为缺省(spec §8),不必显式声明;校验器无副作用,仅接受缺省
151
+
152
+ if (type === 'ai') {
153
+ if (typeof s.prompt !== 'string' || s.prompt.trim() === '') errors.push(err(target, '普通步骤 prompt 必填'))
154
+ if (s.flow !== undefined || s.input !== undefined || s.target !== undefined || s.rounds !== undefined || s.onExhausted !== undefined) {
155
+ errors.push(err(target, '普通步骤不可声明 flow/input/target/rounds/onExhausted'))
156
+ }
157
+ if (!isObj(s.outputs) || outputNames.size === 0) errors.push(err(target, '普通步骤必须以 outputs 声明产出契约'))
158
+ }
159
+ if (type === 'approve') {
160
+ if (typeof s.prompt !== 'string' || s.prompt.trim() === '') errors.push(err(target, 'approve 步骤 prompt(审批口径)必填'))
161
+ if (s.outputs !== undefined || s.listOutputs !== undefined) errors.push(err(target, 'approve 步骤裁决契约由引擎生成,不可声明 outputs/listOutputs'))
162
+ if (s.for_each !== undefined || s.mode !== undefined) errors.push(err(target, 'approve 步骤不支持 for_each/mode'))
163
+ if (typeof s.target !== 'string' || !byId.has(s.target)) errors.push(err(target, 'approve 步骤 target 必填且须指向存在步骤'))
164
+ else if (s.target === s.id) errors.push(err(target, 'approve 步骤 target 不可为自身'))
165
+ if (s.rounds !== undefined && (!Number.isInteger(s.rounds) || s.rounds < 1 || s.rounds > MAX_ROUND)) errors.push(err(target, `rounds 须为 [1,${MAX_ROUND}] 整数`))
166
+ if (s.onExhausted !== undefined && s.onExhausted !== 'blocked' && !byId.has(s.onExhausted)) {
167
+ errors.push(err(target, `onExhausted 须为 "blocked" 或存在步骤 id,得到 ${s.onExhausted}`))
168
+ }
169
+ }
170
+ if (type === 'flow') {
171
+ if (s.prompt !== undefined) errors.push(err(target, 'flow 步骤不派发子代理,不可声明 prompt'))
172
+ if (s.outputs !== undefined || s.listOutputs !== undefined) errors.push(err(target, 'flow 步骤产出由子流程扁平挂载,不可声明 outputs/listOutputs'))
173
+ if (s.load !== undefined || s.for_each !== undefined || s.mode !== undefined) errors.push(err(target, 'flow 步骤不支持 load/for_each/mode'))
174
+ if (typeof s.flow !== 'string' || s.flow.trim() === '') errors.push(err(target, 'flow 步骤 flow 必填(模板 id 或 "{步骤id.产出名}" 动态路由)'))
175
+ else if (s.flow.trim() === t.id) errors.push(err(target, '子流程路由不可指向自身'))
176
+ if (s.input !== undefined) {
177
+ if (!isObj(s.input)) errors.push(err(target, 'input 须为对象'))
178
+ else for (const [k, v] of Object.entries(s.input)) {
179
+ if (typeof v !== 'string' || !/^\{[^{}]+\}$/.test(v)) errors.push(err(target, `input.${k} 须为单占位符引用 "{来源}"`))
180
+ }
181
+ }
182
+ }
183
+ }
184
+
185
+ // 占位符来源校验(prompt/flow/input 中 {引用} 的存在性与合法性)
186
+ const refOk = (s, ph, target) => {
187
+ if (ph === 'request') return true
188
+ if (ph === 'item' || ph === 'item.index') {
189
+ if (s.for_each === undefined) errors.push(err(target, `占位符 {${ph}} 仅在 for_each 步骤内可用`))
190
+ return
191
+ }
192
+ if (ph.startsWith('input.')) {
193
+ if (!declaredInputs.has(ph.slice(6))) errors.push(err(target, `占位符 {${ph}} 引用未声明的入参`))
194
+ return
195
+ }
196
+ const dot = ph.indexOf('.')
197
+ if (dot <= 0) {
198
+ errors.push(err(target, `占位符 {${ph}} 无合法来源(request/input.<name>/<步骤>.<产出>/item)`))
199
+ return
200
+ }
201
+ const refId = ph.slice(0, dot)
202
+ const refOut = ph.slice(dot + 1)
203
+ const ref = byId.get(refId)
204
+ if (!ref) {
205
+ errors.push(err(target, `占位符 {${ph}} 引用不存在的步骤`))
206
+ return
207
+ }
208
+ if (ref.type === 'approve') {
209
+ errors.push(err(target, `占位符 {${ph}} 不可引用审批步骤产出(裁决不进产出账)`))
210
+ return
211
+ }
212
+ const refOuts = isObj(ref.outputs) ? Object.keys(ref.outputs) : []
213
+ if (!refOuts.includes(refOut)) {
214
+ errors.push(err(target, `占位符 {${ph}} 的产出未在步骤 ${refId} outputs 中声明`))
215
+ return
216
+ }
217
+ if (refId === s.id && !(s.for_each !== undefined && s.mode === 'sequential')) {
218
+ errors.push(err(target, '自引用产出仅 sequential 循环实例可用(首实例解析空串)'))
219
+ }
220
+ const forbid = escalateTargetOf.get(s.id)
221
+ if (forbid !== undefined && refId === forbid) {
222
+ errors.push(err(target, `升级步占位符 {${ph}} 禁止引用审批对象产出(耗尽时已清空),只可引用其上游产出`))
223
+ }
224
+ }
225
+ for (const s of t.steps) {
226
+ if (!isObj(s) || typeof s.id !== 'string') continue
227
+ const target = `step:${s.id}`
228
+ if (typeof s.prompt === 'string') for (const ph of placeholdersOf(s.prompt)) refOk(s, ph, target)
229
+ if (type_flow(s)) {
230
+ const m = /^\{([^{}]+)\}$/.exec(String(s.flow).trim())
231
+ if (m) {
232
+ const ph = m[1]
233
+ if (!ph.includes('.') || ph.startsWith('input.') || ph === 'request' || ph === 'item') {
234
+ errors.push(err(target, `flow 动态路由 {${ph}} 须引用 "<步骤id>.<产出名>"`))
235
+ } else refOk(s, ph, target)
236
+ }
237
+ if (isObj(s.input)) for (const v of Object.values(s.input)) {
238
+ for (const ph of placeholdersOf(v)) refOk(s, ph, target)
239
+ }
240
+ }
241
+ }
242
+
243
+ // escalate-only 规则:不参与依赖图(不可声明 after),且不可被其他步骤 after 引用
244
+ for (const s of t.steps) {
245
+ if (!isObj(s) || typeof s.id !== 'string') continue
246
+ if (escalateTargetOf.has(s.id) && s.after !== undefined) {
247
+ errors.push(err(`step:${s.id}`, '升级步由引擎脱离常规调度图,不可声明 after'))
248
+ }
249
+ }
250
+ for (const s of t.steps) {
251
+ if (!isObj(s) || !Array.isArray(s.after)) continue
252
+ for (const dep of s.after) {
253
+ if (escalateTargetOf.has(dep)) errors.push(err(`step:${s.id}`, `after 不可引用升级步 ${dep}(其唯一触发边为审批耗尽)`))
254
+ }
255
+ }
256
+
257
+ // 环检测:边 = 显式 after,缺省 = 文档序前一步(escalate-only 无缺省边)
258
+ const depsOf = new Map()
259
+ t.steps.forEach((s, i) => {
260
+ if (!isObj(s) || typeof s.id !== 'string') return
261
+ let deps
262
+ if (Array.isArray(s.after)) deps = s.after
263
+ else if (escalateTargetOf.has(s.id) || i === 0) deps = []
264
+ else deps = [order[i - 1]]
265
+ depsOf.set(s.id, deps.filter((d) => byId.has(d) && d !== s.id))
266
+ })
267
+ const state = new Map()
268
+ const visit = (id, stack) => {
269
+ const st = state.get(id)
270
+ if (st === 'done') return
271
+ if (st === 'visiting') {
272
+ const cycle = [...stack.slice(stack.indexOf(id)), id].join(' -> ')
273
+ errors.push(err('top:steps', `依赖环:${cycle}`))
274
+ return
275
+ }
276
+ state.set(id, 'visiting')
277
+ for (const d of depsOf.get(id) ?? []) visit(d, [...stack, id])
278
+ state.set(id, 'done')
279
+ }
280
+ for (const id of depsOf.keys()) visit(id, [])
281
+
282
+ return errors
283
+ }
284
+
285
+ const type_flow = (s) => (s.type === undefined ? false : s.type === 'flow')
286
+
287
+ // 集合校验:各模板单独校验 + 字面子流程引用存在性 + 嵌套深度 ≤3;损坏成员跳过不阻塞其余
288
+ export function validateTemplateSet(list) {
289
+ const errors = []
290
+ const ok = []
291
+ for (const t of Array.isArray(list) ? list : []) {
292
+ const es = validateTemplate(t)
293
+ errors.push(...es)
294
+ if (es.length === 0) ok.push(t)
295
+ }
296
+ const ids = new Set(ok.map((t) => t.id))
297
+ const parsedById = new Map(ok.map((t) => [t.id, t]))
298
+ const tmplEdges = new Map()
299
+ for (const t of ok) {
300
+ const outs = new Set()
301
+ for (const s of t.steps) {
302
+ if (s.type !== 'flow' || typeof s.flow !== 'string') continue
303
+ const literal = s.flow.trim()
304
+ if (/^\{[^{}]+\}$/.test(literal)) continue
305
+ // 归属本模板(top:id 前缀):释放权威点按 id 过滤集合校验错误
306
+ if (!ids.has(literal)) errors.push(err('top:id', `${t.id} 步骤 ${s.id} 的子流程 ${literal} 不存在于模板集合`))
307
+ else {
308
+ if (literal !== t.id) outs.add(literal)
309
+ // 审批只在顶层可达:子模板含审批步则任何 flow 引用它的模板无法完成该子流程
310
+ const sub = parsedById.get(literal)
311
+ if (sub?.steps?.some((x) => x.type === 'approve')) errors.push(err('top:id', `${t.id} 引用的子流程 ${literal} 含审批步:审批仅支持顶层剧本`))
312
+ }
313
+ }
314
+ if (outs.size > 0) tmplEdges.set(t.id, outs)
315
+ }
316
+ const depthOf = (id, seen) => {
317
+ // 环即拒绝:深度无穷,不限于此路径是否重复经过
318
+ if (seen.has(id)) return Infinity
319
+ seen.add(id)
320
+ let d = 1
321
+ for (const n of tmplEdges.get(id) ?? []) d = Math.max(d, 1 + depthOf(n, seen))
322
+ return d
323
+ }
324
+ for (const id of tmplEdges.keys()) {
325
+ if (depthOf(id, new Set()) > MAX_NEST_DEPTH) errors.push(err(`top:id`, `${id} 子流程嵌套深度超过 ${MAX_NEST_DEPTH}`))
326
+ }
327
+ return errors
328
+ }
329
+
330
+ // 产出契约 → structured_output schema(宿主 schema 子集内);approve 步为固定裁决契约
331
+ export function buildSchema(step) {
332
+ if (step.type === 'approve') return VERDICT_SCHEMA
333
+ const properties = {}
334
+ const required = []
335
+ const listSet = new Set(Array.isArray(step.listOutputs) ? step.listOutputs : [])
336
+ for (const name of Object.keys(isObj(step.outputs) ? step.outputs : {})) {
337
+ required.push(name)
338
+ properties[name] = listSet.has(name) ? { type: 'array', items: { type: 'string' } } : { type: 'string' }
339
+ }
340
+ return { type: 'object', required, additionalProperties: false, properties }
341
+ }
package/package.json CHANGED
@@ -1,16 +1,20 @@
1
1
  {
2
2
  "name": "@mzzsfy/dsh-rs-workflow",
3
- "description": "若水工作流一体化:安装即得 rs-workflow agent 模式(协议技能、编排引擎、模式组合),含设置表单与工作流配置工具",
4
- "version": "0.2.4",
3
+ "description": "若水工作流设置面:流程模板管理与工作位/预算配置(模板编辑/校验/配置读写)。编排运行时按 v5 设计(docs/rsww-v5/)另行实现。",
4
+ "version": "1.0.1",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
8
8
  ".": "./lib/index.js",
9
+ "./client": "./src/client.js",
9
10
  "./package.json": "./package.json"
10
11
  },
11
12
  "dsh": {
12
13
  "bundle": {
13
14
  "patch": "./cordis.patch.yml"
15
+ },
16
+ "client": {
17
+ "platform": "web"
14
18
  }
15
19
  },
16
20
  "scripts": {
@@ -18,18 +22,18 @@
18
22
  },
19
23
  "license": "MIT",
20
24
  "repository": {
21
- "type": "git",
25
+ "type": "object",
22
26
  "url": "git+https://github.com/mzzsfy/dsh-plugin.git"
23
27
  },
24
28
  "files": [
25
29
  "lib",
30
+ "src",
31
+ "engine",
26
32
  "preset",
27
33
  "cordis.patch.yml",
28
34
  "README.md"
29
35
  ],
30
36
  "peerDependencies": {
31
- "@deepseek-ai/dsh-settings": ">=0.1.2-alpha.2",
32
- "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
33
37
  "@deepseek-ai/schemastery": "^3.18.1"
34
38
  }
35
39
  }
@@ -1,32 +1,38 @@
1
- # The `rs-workflow` agent preset: 若水工作流。
1
+ # The `rs-workflow` agent preset: 若水工作流(v5,主循环协作编排)。
2
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 + 技能承载。
3
+ # 本文件是释放骨架源:release 生成器把它作为唯一源生成 rs-<id> 模式组合
4
+ # (运行时仅改写 orchestrator 行的 templateId 锚定),不在包内被宿主装载。
13
5
  #
14
- # 平面规则与 standard 一致:本文件是 AGENT-PLANE 组合;服务行必须在 isolate realm 后面。
6
+ # 基底是 `standard` 全功能编码代理行集,差异:
7
+ # 1. persona 换成流程模式纪律 v5(人机接口+受约束规划者,任务交付由编排子代理承担);
8
+ # 2. delegation 组内 rs-workflow-orchestrator 工具行与 template-tool 行;
9
+ # 3. 无 report 行(trace 事件 + run-store 取代);plan-mode 组移除(规划在编排内)。
15
10
 
16
11
  # ── identity ────────────────────────────────────────────────────────────────
17
12
 
18
13
  - id: persona
19
14
  name: '@deepseek-ai/dsh-persona'
20
15
  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 时说明原因并给出建议。
16
+ prefix: |-
17
+ 你是"若水工作流"会话代理,由 {{model}} 模型驱动。你负责人机协作与编排调度;
18
+ 任务交付由流程引擎接管下的子代理完成,你不直接产出交付物。
19
+ 职责判据:
20
+ 1. 纯会话内请求(问答、解释、讨论,不产生文件)→ 直接答复,不起编排;
21
+ 2. 凡需新建或修改交付物文件(代码、页面、脚本、文档、配置,无论大小与步骤数,例如"写个贪吃蛇html")
22
+ → 必须调用 rs_workflow_start,提交结构化规划(steps 各步要点与验收口径);禁止以"一句话能答、
23
+ 改动小"为由绕开编排自行产出文件;此通道无豁免:规划被拒按 errors 修正重交,不许改道自行完成;
24
+ 3. 编排运行中你只被段结束通知或用户消息唤醒:任一唤醒轮先 rs_workflow_status 检查现役 run——
25
+ status=running 且无活跃段 job(待拉起)或 status=paused 且 awaitingResume=true(页签已发恢复)
26
+ 时,即 rs_workflow_resume 补拉;waiting_approval 一律不补拉,轮到裁决:按模板 autoApprove 决定
27
+ 代审或 ask_user 转呈真人,裁决经 rs_workflow_verdict 回写(autoApprove 代审 by 缺省,转呈真人
28
+ by=user,意见必填),裁决受理后 rs_workflow_resume 拉起下一段;
29
+ 终态则汇报(完成经 deliverables 交付,汇报必须引用 runId 供页签核验;失败/阻塞说明卡点与建议);
30
+ 任何交付说明若无对应 run 记录即为违规——禁止把自行产出的文件当交付物汇报;
31
+ 用户中途纠偏经 rs_workflow_message 注入,下一步骤边界进入编排;
32
+ 4. 你不代替子代理执行编排内步骤,不伪造产出,不跳过引擎直接交付;
33
+ 5. 引擎不可用或编排启动失败时,不得自行完成文件类请求——向用户说明故障与已尝试的调用,
34
+ 等待用户处置;文件交付只有 rs_workflow_ 通道,没有自行兜底。
35
+ suffix: Your working directory is {{cwd}}.
30
36
 
31
37
  - id: agent-instructions
32
38
  name: '@deepseek-ai/dsh-agent-instructions'
@@ -35,7 +41,10 @@
35
41
 
36
42
  # ── shell ───────────────────────────────────────────────────────────────────
37
43
 
38
- # 与 standard 相同:两个 shell 工具消费 HOST 侧注册表,执行器也在 host 平面。
44
+ # 注:不可禁本组合 shell/fs 工具行——宿主 workflow worker 的段子代理继承组合行集,
45
+ # 禁行使段子代理无法落盘交付物(实测证实)。主循环不写文件由 persona 判据约束,
46
+ # 物理隔离需宿主提供 worker 行集覆写能力(外部依赖)。
47
+
39
48
  - id: tool-bash
40
49
  name: '@deepseek-ai/dsh-tool-bash'
41
50
  disabled: !!js process.platform === 'win32'
@@ -61,60 +70,23 @@
61
70
 
62
71
  # ── skills ──────────────────────────────────────────────────────────────────
63
72
 
64
- # 与 cordis 出厂预设相同的手法:customSkillDirs 指向预设自己的 skills/ 目录,
65
- # baseUrl 即预设目录本身,因此 rs-workflow 协议技能随预设安装、复制、迁移。
66
73
  - id: skill-filesystem
67
74
  name: '@deepseek-ai/dsh-skill-filesystem'
68
- config:
69
- customSkillDirs:
70
- - !!js "process.getBuiltinModule('node:url').fileURLToPath(new URL('skills/', baseUrl))"
71
75
 
72
76
  - id: tool-skill
73
77
  name: '@deepseek-ai/dsh-tool-skill'
74
78
 
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'
79
+ # ── web ─────────────────────────────────────────────────────────────────────
89
80
 
90
- # ── plan mode ───────────────────────────────────────────────────────────────
91
-
92
- # 保留 standard 的计划模式:用户仍可把会话切到 plan 模式做前置规划,
93
- # 与工作流不冲突(工作流自身的规划发生在编排内部)。
94
- - id: planning
95
- name: cordis:group
96
- group: true
97
- isolate:
98
- planMode: true
81
+ - id: tool-web
82
+ name: '@deepseek-ai/dsh-tool-web'
99
83
  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.
84
+ fetch: false
85
+ searchTimeoutMs: 60000
115
86
 
116
87
  # ── compaction ──────────────────────────────────────────────────────────────
117
88
 
89
+ # 编排子代理上下文较长,保留 standard 的压缩组。
118
90
  - id: compaction
119
91
  name: cordis:group
120
92
  group: true
@@ -137,8 +109,10 @@
137
109
 
138
110
  # ── delegation and workflows ────────────────────────────────────────────────
139
111
 
140
- # 与 standard 相同:subagents 注册表留在 HOST;workflowEngine 是预设私有服务,
141
- # 所有触达它的行共享一个 entry-local realm。
112
+ # workflowEngine 是预设私有服务(preset 组合发布全局服务会被宿主拒绝 mount),
113
+ # 所有触达它的行共享一个 entry-local isolate realm。
114
+ # orchestrator 行注册 rs_workflow_* 六工具(含裁决回写 rs_workflow_verdict)并经 jobs 服务拉起分段 continuable job;
115
+ # 控制队列与 store 为普通模块单例,host/agent 行经同一 realpath 解析共享实例。
142
116
  - id: delegation
143
117
  name: cordis:group
144
118
  group: true
@@ -179,6 +153,19 @@
179
153
  subagentProvider: spawn
180
154
  maxRounds: 64
181
155
 
156
+ # orchestrator 工具行:模板 id 由组合名承载(rs-<id> → <id>),释放生成器按组合名写入
157
+ - id: rs-workflow-orchestrator
158
+ name: '@mzzsfy/dsh-rs-workflow'
159
+ config:
160
+ role: orchestrator
161
+ templateId: TPL_ANCHOR
162
+
163
+ # 模板编辑工具行:rs_workflow_template(AI 按口述逻辑生成/修改流程模板)
164
+ - id: rs-workflow-template-tool
165
+ name: '@mzzsfy/dsh-rs-workflow'
166
+ config:
167
+ role: template-tool
168
+
182
169
  # ── remaining model-facing rows ─────────────────────────────────────────────
183
170
 
184
171
  - id: tool-ask-user
@@ -188,9 +175,3 @@
188
175
  name: '@deepseek-ai/dsh-tool-todo'
189
176
  config:
190
177
  allowParallelInProgress: true
191
-
192
- - id: tool-web
193
- name: '@deepseek-ai/dsh-tool-web'
194
- config:
195
- fetch: false
196
- searchTimeoutMs: 60000
@@ -1,5 +1,5 @@
1
- name: 若水工作流
1
+ name: 若水·流程编排 v5
2
2
  description: >-
3
- rs-tui 多模型协作工作流迁移版:planner/executor/reviewer 三角色子代理分工,
4
- 难度分诊四模板(lite / plan-final / step-review / multi-plan),
5
- 审批循环、升级重规划与工作位模型降级链;非平凡编码需求自动走工作流编排。
3
+ 若水工作流 v5:简单请求直接答复,复杂请求经结构化规划进入流程编排;
4
+ 主模型作为人机接口与受约束规划者,任务交付由引擎子代理分段完成;
5
+ 审批可代审可转呈,运行中消息注入纠偏,取消即时生效,产出与重跑/续跑在会话页签全量可查。