@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/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.1",
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
@@ -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
  },