@mzzsfy/dsh-rs-workflow 1.0.1 → 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/board.mjs +28 -14
- package/lib/driver/approve.mjs +3 -1
- package/lib/driver/index.mjs +47 -15
- package/lib/driver/runner.mjs +11 -5
- package/lib/driver/scheduler.mjs +2 -1
- package/lib/guard.mjs +67 -0
- package/lib/orchestrator.mjs +511 -428
- package/lib/storage.mjs +13 -4
- package/lib/store.mjs +28 -10
- package/lib/template.mjs +7 -6
- package/package.json +4 -2
- package/preset/rs-workflow/agent.cordis.yml +4 -0
- package/src/client.js +0 -1
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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 = `${
|
|
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
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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,
|
|
317
|
-
//
|
|
318
|
-
if (
|
|
319
|
-
|
|
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,
|
|
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.
|
|
4
|
+
"version": "1.0.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
"README.md"
|
|
35
35
|
],
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@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"
|
|
38
40
|
}
|
|
39
41
|
}
|
|
@@ -67,6 +67,10 @@
|
|
|
67
67
|
|
|
68
68
|
- id: tool-jobs
|
|
69
69
|
name: '@deepseek-ai/dsh-tool-jobs'
|
|
70
|
+
# 缺省连续唤醒预算 3 次且仅用户消息重置:纯自主编排每段 settle 耗 1 次,第 4 段起降级 inject 不唤醒,
|
|
71
|
+
# 编排静默停滞;放宽到 64 段(覆盖 spec 建议的 3~12 步多实例流程上限)
|
|
72
|
+
config:
|
|
73
|
+
maxConsecutiveWakes: 64
|
|
70
74
|
|
|
71
75
|
# ── skills ──────────────────────────────────────────────────────────────────
|
|
72
76
|
|
package/src/client.js
CHANGED