@mzzsfy/dsh-rs-workflow 1.0.0 → 1.0.2

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/lib/storage.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  // storage — rs-workflow 自有文件存储(不经宿主 settings 服务,settings.yaml 不承载本插件配置)
2
2
  // 布局:<dataDir>/v5/{templates,config}.json;dataDir(DSH_RS_WORKFLOW_DATA_DIR 覆写)
3
3
  import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
4
- import { homedir, tmpdir } from 'node:os'
4
+ import { homedir } from 'node:os'
5
5
  import { dirname, join } from 'node:path'
6
6
 
7
7
  export function dataDir() {
@@ -14,18 +14,27 @@ function v5File(name) {
14
14
  }
15
15
 
16
16
  export function loadJson(name, fallback) {
17
+ let raw
17
18
  try {
18
- return JSON.parse(readFileSync(v5File(name), 'utf8'))
19
+ raw = readFileSync(v5File(name), 'utf8')
19
20
  } catch {
21
+ // 缺失即首启:回默认;存在但读失败同路,下次 save 重建
22
+ return fallback
23
+ }
24
+ try {
25
+ return JSON.parse(raw)
26
+ } catch (e) {
27
+ // 损坏不可静默吞成默认值:留痕后回退,否则损坏文件被下一次 save 无声覆盖
28
+ console.error(`[rsww] ${name} 解析失败,回退默认值:${e?.message ?? e}`)
20
29
  return fallback
21
30
  }
22
31
  }
23
32
 
24
- // 写临时文件后原子改名,断电不产生半截 JSON
33
+ // 写同目录临时文件后原子改名:跨卷 tmpdir 会触发 EXDEV;同盘保证 rename 原子性
25
34
  export function saveJson(name, data) {
26
35
  const target = v5File(name)
27
36
  mkdirSync(dirname(target), { recursive: true })
28
- const tmp = `${join(tmpdir(), 'rsww')}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`
37
+ const tmp = `${target}.${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`
29
38
  try {
30
39
  writeFileSync(tmp, JSON.stringify(data, null, 2))
31
40
  renameSync(tmp, target)
package/lib/store.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  // run-store(v5):每 run 全量 JSON + index.json 索引;LRU 容量收敛;零截断
2
2
  // namespace 子目录 v5/(旧数据物理隔离);record 增 plan/warnings/controls[].by(见 data-design.md)
3
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs'
4
- import { homedir, tmpdir } from 'node:os'
4
+ import { homedir } from 'node:os'
5
5
  import { join } from 'node:path'
6
6
 
7
7
  export const KEEP_RUNS = 200
@@ -31,9 +31,14 @@ export function createStore({ dir = defaultDataDir(), logger = console, keepRuns
31
31
  const warn = (message) => logger.warn?.(`[rsww-store] ${message}`)
32
32
 
33
33
  const writeAtomic = (path, text) => {
34
- const tmp = join(tmpdir(), `rsww-${Date.now()}-${Math.random().toString(36).slice(2)}`)
35
- writeFileSync(tmp, text, 'utf8')
36
- renameSync(tmp, path)
34
+ // 临时文件落目标同目录:跨卷 tmpdir rename 触发 EXDEV;同盘保证原子性
35
+ const tmp = `${path}.${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`
36
+ try {
37
+ writeFileSync(tmp, text, 'utf8')
38
+ renameSync(tmp, path)
39
+ } finally {
40
+ rmSync(tmp, { force: true })
41
+ }
37
42
  }
38
43
 
39
44
  const persistRun = (record) => writeAtomic(join(runsDir, `${record.runId}.json`), JSON.stringify(record, null, 2))
@@ -54,7 +59,7 @@ export function createStore({ dir = defaultDataDir(), logger = console, keepRuns
54
59
  const at = Date.now()
55
60
  record.status = 'cancelled'
56
61
  record.finishedAt = at
57
- if (!record.summary) record.summary = '进程重启,运行中断,可在会话页签断点续跑'
62
+ if (!record.summary) record.summary = '进程重启,运行已自动取消,可在会话页签断点续跑'
58
63
  record.controls = [...(record.controls ?? []), { at, kind: 'cancel', text: '进程重启,自动收敛' }]
59
64
  persistRun(record)
60
65
  const entry = index.find((e) => e.runId === record.runId)
@@ -101,13 +106,17 @@ export function createStore({ dir = defaultDataDir(), logger = console, keepRuns
101
106
  warn(`运行记录 ${name} 读取失败,跳过:${e.message}`)
102
107
  }
103
108
  }
104
- // 对账:runs/ 文件集合为权威修齐 index(缺行补、幽灵行删、收敛改写行落盘),仅内存修正
109
+ // 对账:runs/ 文件集合为权威修齐 index(缺行补、幽灵行删、已存行内容刷新),仅内存修正
105
110
  let repaired = settledCount > 0
106
111
  const fileIds = new Set(records.keys())
107
112
  for (const record of records.values()) {
108
- if (!index.some((e) => e.runId === record.runId)) {
113
+ const entry = index.find((e) => e.runId === record.runId)
114
+ if (entry === undefined) {
109
115
  index.push(indexEntryOf(record))
110
116
  repaired = true
117
+ } else {
118
+ // finish 先写 run 文件后写 index,中断会留旧行;以文件记录刷新,消除失真窗口
119
+ Object.assign(entry, indexEntryOf(record))
111
120
  }
112
121
  }
113
122
  for (let i = index.length - 1; i >= 0; i--) {
@@ -154,9 +163,18 @@ export function createStore({ dir = defaultDataDir(), logger = console, keepRuns
154
163
  state: state ?? emptyState(), controls: [], stepsTrace: {}, queued: [],
155
164
  }
156
165
  records.set(id, record)
157
- index.unshift(indexEntryOf(record))
158
- persistRun(record)
159
- persistIndex()
166
+ const entry = indexEntryOf(record)
167
+ index.unshift(entry)
168
+ try {
169
+ persistRun(record)
170
+ persistIndex()
171
+ } catch (e) {
172
+ // 落盘失败须回滚内存,否则同 id 重试恒撞"runId 已存在"且列表含幽灵行
173
+ records.delete(id)
174
+ const at = index.indexOf(entry)
175
+ if (at >= 0) index.splice(at, 1)
176
+ throw e
177
+ }
160
178
  evictOverCapacity()
161
179
  return record
162
180
  },
package/lib/template.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // 模板静态校验器:DSL v5 唯一权威(board 保存校验唯一入口);v5 新增顶层 autoApprove 键
1
+ // 模板静态校验器:DSL v5 唯一权威(board 保存校验唯一入口);v5 新增顶层 autoApprove 键
2
2
 
3
3
  export const SLOT_KEYS = ['planner', 'executor', 'reviewer', 'executor-loop', 'reviewer-approve', 'executor-escalate']
4
4
  const TOP_FIELDS = new Set(['id', 'label', 'description', 'inputs', 'steps', 'autoApprove'])
@@ -313,12 +313,13 @@ export function validateTemplateSet(list) {
313
313
  }
314
314
  if (outs.size > 0) tmplEdges.set(t.id, outs)
315
315
  }
316
- const depthOf = (id, seen) => {
317
- // 环即拒绝:深度无穷,不限于此路径是否重复经过
318
- if (seen.has(id)) return Infinity
319
- seen.add(id)
316
+ const depthOf = (id, path) => {
317
+ // 仅同路径重访即环(深度无穷);菱形(不同路径汇合同一子流程)是合法 DAG,回溯时摘除路径标记
318
+ if (path.has(id)) return Infinity
319
+ path.add(id)
320
320
  let d = 1
321
- for (const n of tmplEdges.get(id) ?? []) d = Math.max(d, 1 + depthOf(n, seen))
321
+ for (const n of tmplEdges.get(id) ?? []) d = Math.max(d, 1 + depthOf(n, path))
322
+ path.delete(id)
322
323
  return d
323
324
  }
324
325
  for (const id of tmplEdges.keys()) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mzzsfy/dsh-rs-workflow",
3
3
  "description": "若水工作流设置面:流程模板管理与工作位/预算配置(模板编辑/校验/配置读写)。编排运行时按 v5 设计(docs/rsww-v5/)另行实现。",
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -28,10 +28,14 @@
28
28
  "files": [
29
29
  "lib",
30
30
  "src",
31
+ "engine",
32
+ "preset",
31
33
  "cordis.patch.yml",
32
34
  "README.md"
33
35
  ],
34
36
  "peerDependencies": {
35
- "@deepseek-ai/schemastery": "^3.18.1"
37
+ "@deepseek-ai/schemastery": "^3.18.1",
38
+ "@deepseek-ai/dsh-llm": ">=0.1.2-rc.1",
39
+ "@deepseek-ai/dsh-tools": ">=0.1.5-rc.2"
36
40
  }
37
41
  }
@@ -0,0 +1,181 @@
1
+ # The `rs-workflow` agent preset: 若水工作流(v5,主循环协作编排)。
2
+ #
3
+ # 本文件是释放骨架源:release 生成器把它作为唯一源生成 rs-<id> 模式组合
4
+ # (运行时仅改写 orchestrator 行的 templateId 锚定),不在包内被宿主装载。
5
+ #
6
+ # 基底是 `standard` 全功能编码代理行集,差异:
7
+ # 1. persona 换成流程模式纪律 v5(人机接口+受约束规划者,任务交付由编排子代理承担);
8
+ # 2. delegation 组内 rs-workflow-orchestrator 工具行与 template-tool 行;
9
+ # 3. 无 report 行(trace 事件 + run-store 取代);plan-mode 组移除(规划在编排内)。
10
+
11
+ # ── identity ────────────────────────────────────────────────────────────────
12
+
13
+ - id: persona
14
+ name: '@deepseek-ai/dsh-persona'
15
+ config:
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}}.
36
+
37
+ - id: agent-instructions
38
+ name: '@deepseek-ai/dsh-agent-instructions'
39
+ config:
40
+ maxBytes: 65536
41
+
42
+ # ── shell ───────────────────────────────────────────────────────────────────
43
+
44
+ # 注:不可禁本组合 shell/fs 工具行——宿主 workflow worker 的段子代理继承组合行集,
45
+ # 禁行使段子代理无法落盘交付物(实测证实)。主循环不写文件由 persona 判据约束,
46
+ # 物理隔离需宿主提供 worker 行集覆写能力(外部依赖)。
47
+
48
+ - id: tool-bash
49
+ name: '@deepseek-ai/dsh-tool-bash'
50
+ disabled: !!js process.platform === 'win32'
51
+
52
+ - id: tool-pwsh
53
+ name: '@deepseek-ai/dsh-tool-pwsh'
54
+ disabled: !!js process.platform !== 'win32'
55
+
56
+ # ── filesystem ──────────────────────────────────────────────────────────────
57
+
58
+ - id: tool-fs
59
+ name: '@deepseek-ai/dsh-tool-fs'
60
+
61
+ - id: tool-fs-search
62
+ name: '@deepseek-ai/dsh-tool-fs-search'
63
+ config:
64
+ sampleOverCapGlobResults: false
65
+
66
+ # ── background jobs ────────────────────────────────────────────────────────
67
+
68
+ - id: tool-jobs
69
+ name: '@deepseek-ai/dsh-tool-jobs'
70
+ # 缺省连续唤醒预算 3 次且仅用户消息重置:纯自主编排每段 settle 耗 1 次,第 4 段起降级 inject 不唤醒,
71
+ # 编排静默停滞;放宽到 64 段(覆盖 spec 建议的 3~12 步多实例流程上限)
72
+ config:
73
+ maxConsecutiveWakes: 64
74
+
75
+ # ── skills ──────────────────────────────────────────────────────────────────
76
+
77
+ - id: skill-filesystem
78
+ name: '@deepseek-ai/dsh-skill-filesystem'
79
+
80
+ - id: tool-skill
81
+ name: '@deepseek-ai/dsh-tool-skill'
82
+
83
+ # ── web ─────────────────────────────────────────────────────────────────────
84
+
85
+ - id: tool-web
86
+ name: '@deepseek-ai/dsh-tool-web'
87
+ config:
88
+ fetch: false
89
+ searchTimeoutMs: 60000
90
+
91
+ # ── compaction ──────────────────────────────────────────────────────────────
92
+
93
+ # 编排子代理上下文较长,保留 standard 的压缩组。
94
+ - id: compaction
95
+ name: cordis:group
96
+ group: true
97
+ isolate:
98
+ compaction: true
99
+ toolResultPruner: true
100
+ config:
101
+ - id: compaction-basic
102
+ name: '@deepseek-ai/dsh-compaction-basic'
103
+
104
+ - id: command-compact
105
+ name: '@deepseek-ai/dsh-command-compact'
106
+
107
+ - id: tool-result-pruner
108
+ name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
109
+ config:
110
+ thresholdChars: 8192
111
+ headChars: 4096
112
+ tailChars: 1024
113
+
114
+ # ── delegation and workflows ────────────────────────────────────────────────
115
+
116
+ # workflowEngine 是预设私有服务(preset 组合发布全局服务会被宿主拒绝 mount),
117
+ # 所有触达它的行共享一个 entry-local isolate realm。
118
+ # orchestrator 行注册 rs_workflow_* 六工具(含裁决回写 rs_workflow_verdict)并经 jobs 服务拉起分段 continuable job;
119
+ # 控制队列与 store 为普通模块单例,host/agent 行经同一 realpath 解析共享实例。
120
+ - id: delegation
121
+ name: cordis:group
122
+ group: true
123
+ isolate:
124
+ workflowEngine: true
125
+ config:
126
+ - id: tool-subagent-control
127
+ name: '@deepseek-ai/dsh-tool-subagent-control'
128
+
129
+ - id: tool-subagent-list-agents
130
+ name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
131
+
132
+ - id: tool-subagent
133
+ name: '@deepseek-ai/dsh-tool-subagent'
134
+ config:
135
+ provider: spawn
136
+ toolName: subagent
137
+ backgroundMode: continuable
138
+
139
+ - id: tool-subagent-fork
140
+ name: '@deepseek-ai/dsh-tool-subagent'
141
+ config:
142
+ provider: fork
143
+ toolName: subagent_fork
144
+ backgroundMode: continuable
145
+
146
+ - id: workflow-worker-thread
147
+ name: '@deepseek-ai/dsh-workflow-worker-thread'
148
+ config:
149
+ provider: spawn
150
+
151
+ - id: tool-workflow
152
+ name: '@deepseek-ai/dsh-tool-workflow'
153
+
154
+ - id: tool-ralph
155
+ name: '@deepseek-ai/dsh-tool-ralph'
156
+ config:
157
+ subagentProvider: spawn
158
+ maxRounds: 64
159
+
160
+ # orchestrator 工具行:模板 id 由组合名承载(rs-<id> → <id>),释放生成器按组合名写入
161
+ - id: rs-workflow-orchestrator
162
+ name: '@mzzsfy/dsh-rs-workflow'
163
+ config:
164
+ role: orchestrator
165
+ templateId: TPL_ANCHOR
166
+
167
+ # 模板编辑工具行:rs_workflow_template(AI 按口述逻辑生成/修改流程模板)
168
+ - id: rs-workflow-template-tool
169
+ name: '@mzzsfy/dsh-rs-workflow'
170
+ config:
171
+ role: template-tool
172
+
173
+ # ── remaining model-facing rows ─────────────────────────────────────────────
174
+
175
+ - id: tool-ask-user
176
+ name: '@deepseek-ai/dsh-tool-ask-user'
177
+
178
+ - id: tool-todo
179
+ name: '@deepseek-ai/dsh-tool-todo'
180
+ config:
181
+ allowParallelInProgress: true
@@ -0,0 +1,5 @@
1
+ name: 若水·流程编排 v5
2
+ description: >-
3
+ 若水工作流 v5:简单请求直接答复,复杂请求经结构化规划进入流程编排;
4
+ 主模型作为人机接口与受约束规划者,任务交付由引擎子代理分段完成;
5
+ 审批可代审可转呈,运行中消息注入纠偏,取消即时生效,产出与重跑/续跑在会话页签全量可查。
package/src/client.js CHANGED
@@ -1260,7 +1260,6 @@ window.__ModuleLoader__.load({
1260
1260
  unsubscribe()
1261
1261
  if (timer) clearInterval(timer)
1262
1262
  if (disposeView) disposeView()
1263
- if (disposeChip) disposeChip()
1264
1263
  }
1265
1264
  }, 'rs-workflow: session-aware flow view')
1266
1265
  },