@actiondock/core 2.10.0 → 2.11.0

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.
Files changed (252) hide show
  1. package/README.md +1 -2
  2. package/dist/catalog/action-catalog.js +3 -33
  3. package/dist/catalog/discovery.d.ts +0 -3
  4. package/dist/catalog/discovery.js +4 -4
  5. package/dist/catalog/graph.d.ts +0 -4
  6. package/dist/catalog/graph.js +8 -14
  7. package/dist/{profile/client-actions.d.ts → client/actions.d.ts} +1 -1
  8. package/dist/{profile/client-actions.js → client/actions.js} +2 -8
  9. package/dist/{profile/client-config.d.ts → client/config.d.ts} +1 -1
  10. package/dist/{profile/client-config.js → client/config.js} +1 -1
  11. package/dist/client/dispatcher.d.ts +4 -0
  12. package/dist/client/dispatcher.js +21 -0
  13. package/dist/client/health.d.ts +38 -0
  14. package/dist/{profile/client-health.js → client/health.js} +9 -6
  15. package/dist/client/index.d.ts +16 -0
  16. package/dist/client/index.js +20 -0
  17. package/dist/{profile/client-playbooks.d.ts → client/playbooks.d.ts} +1 -1
  18. package/dist/{profile/client-playbooks.js → client/playbooks.js} +1 -1
  19. package/dist/{profile/client-runs.d.ts → client/runs.d.ts} +4 -2
  20. package/dist/{profile/client-runs.js → client/runs.js} +7 -1
  21. package/dist/{profile/client-state.d.ts → client/state.d.ts} +1 -1
  22. package/dist/{profile/client-state.js → client/state.js} +1 -1
  23. package/dist/{profile/client-transport.d.ts → client/transport.d.ts} +29 -10
  24. package/dist/{profile/client-transport.js → client/transport.js} +125 -35
  25. package/dist/doctor/checks/index.d.ts +1 -1
  26. package/dist/doctor/checks/index.js +1 -1
  27. package/dist/doctor/checks/project.js +20 -88
  28. package/dist/doctor/checks/runtime.d.ts +0 -4
  29. package/dist/doctor/checks/runtime.js +0 -28
  30. package/dist/doctor/doctor.js +1 -2
  31. package/dist/errors.d.ts +53 -3
  32. package/dist/errors.js +53 -3
  33. package/dist/execution/service.d.ts +55 -37
  34. package/dist/execution/service.js +601 -212
  35. package/dist/execution/types.d.ts +24 -5
  36. package/dist/host/host.d.ts +42 -22
  37. package/dist/host/host.js +562 -149
  38. package/dist/host/routing.js +6 -6
  39. package/dist/host/types.d.ts +25 -13
  40. package/dist/index.d.ts +9 -4
  41. package/dist/index.js +5 -2
  42. package/dist/input/advice.d.ts +0 -44
  43. package/dist/input/advice.js +7 -141
  44. package/dist/input/flat-materializer.js +1 -1
  45. package/dist/input/flat-parser.js +1 -1
  46. package/dist/input/index.d.ts +0 -1
  47. package/dist/input/index.js +0 -1
  48. package/dist/input/input-resolver.js +1 -1
  49. package/dist/input/validation-mapper.d.ts +1 -1
  50. package/dist/ipc/diagnostic.d.ts +8 -21
  51. package/dist/ipc/diagnostic.js +24 -57
  52. package/dist/ipc/host.js +21 -7
  53. package/dist/ipc/service.d.ts +14 -0
  54. package/dist/ipc/service.js +132 -26
  55. package/dist/ipc/types.d.ts +0 -4
  56. package/dist/package/index.d.ts +4 -5
  57. package/dist/package/index.js +1 -2
  58. package/dist/package/runtime.d.ts +94 -23
  59. package/dist/package/runtime.js +628 -274
  60. package/dist/package/static-index.d.ts +3 -6
  61. package/dist/package/static-index.js +11 -40
  62. package/dist/package/types.d.ts +19 -4
  63. package/dist/platform/index.d.ts +2 -1
  64. package/dist/platform/index.js +2 -1
  65. package/dist/platform/loader-hook.d.ts +32 -0
  66. package/dist/platform/loader-hook.js +90 -0
  67. package/dist/{node → platform}/module-loader.d.ts +13 -0
  68. package/dist/{node → platform}/module-loader.js +51 -4
  69. package/dist/platform/node.d.ts +19 -8
  70. package/dist/platform/node.js +50 -20
  71. package/dist/platform/types.d.ts +7 -47
  72. package/dist/process/context-process.d.ts +3 -18
  73. package/dist/process/context-process.js +3 -44
  74. package/dist/process/control.d.ts +21 -0
  75. package/dist/process/control.js +34 -0
  76. package/dist/process/converters.d.ts +23 -0
  77. package/dist/process/converters.js +92 -0
  78. package/dist/process/cursor.js +2 -22
  79. package/dist/process/{diagnostics-sink.d.ts → diagnostics.d.ts} +0 -7
  80. package/dist/process/{diagnostics-sink.js → diagnostics.js} +6 -8
  81. package/dist/process/index.d.ts +1 -3
  82. package/dist/process/index.js +1 -3
  83. package/dist/process/input-dispatcher.d.ts +4 -13
  84. package/dist/process/input-dispatcher.js +48 -69
  85. package/dist/process/managed-record.d.ts +3 -31
  86. package/dist/process/metadata-store.d.ts +0 -41
  87. package/dist/process/metadata-store.js +2 -341
  88. package/dist/process/process-driver.js +2 -2
  89. package/dist/process/process-manager.d.ts +8 -50
  90. package/dist/process/process-manager.js +28 -417
  91. package/dist/process/quotas.d.ts +14 -0
  92. package/dist/process/quotas.js +29 -0
  93. package/dist/process/run-executor.js +0 -1
  94. package/dist/process/terminal-output-cache.d.ts +20 -34
  95. package/dist/process/terminal-output-cache.js +43 -64
  96. package/dist/profile/index.d.ts +1 -2
  97. package/dist/profile/index.js +1 -2
  98. package/dist/profile/manager.d.ts +2 -4
  99. package/dist/profile/manager.js +10 -40
  100. package/dist/profile/types.d.ts +1 -17
  101. package/dist/project/digest.d.ts +5 -1
  102. package/dist/project/digest.js +81 -219
  103. package/dist/project/index.d.ts +4 -5
  104. package/dist/project/index.js +4 -5
  105. package/dist/project/init.js +2 -2
  106. package/dist/project/loader.d.ts +3 -3
  107. package/dist/project/loader.js +72 -50
  108. package/dist/project/lockfile.js +6 -5
  109. package/dist/project/manifest.js +5 -4
  110. package/dist/project/transactions.d.ts +4 -42
  111. package/dist/project/transactions.js +83 -138
  112. package/dist/project/types-generator.js +2 -1
  113. package/dist/project/types.d.ts +2 -0
  114. package/dist/registry/index.d.ts +0 -1
  115. package/dist/registry/index.js +0 -1
  116. package/dist/registry/lock.d.ts +2 -11
  117. package/dist/registry/lock.js +14 -183
  118. package/dist/registry/registry.d.ts +1 -6
  119. package/dist/registry/registry.js +5 -17
  120. package/dist/runtime/action-registry.d.ts +1 -5
  121. package/dist/runtime/action-registry.js +0 -12
  122. package/dist/runtime/context.js +2 -2
  123. package/dist/runtime/events.d.ts +11 -50
  124. package/dist/runtime/events.js +88 -368
  125. package/dist/runtime/index.d.ts +0 -4
  126. package/dist/runtime/index.js +0 -4
  127. package/dist/runtime/run-persistence.d.ts +2 -0
  128. package/dist/runtime/run-persistence.js +2 -1
  129. package/dist/runtime/standalone-commands.d.ts +18 -0
  130. package/dist/runtime/standalone-commands.js +466 -0
  131. package/dist/runtime/standalone-help.d.ts +20 -0
  132. package/dist/runtime/standalone-help.js +72 -0
  133. package/dist/runtime/standalone.d.ts +1 -44
  134. package/dist/runtime/standalone.js +32 -573
  135. package/dist/schema/validator.d.ts +2 -2
  136. package/dist/schema/validator.js +2 -9
  137. package/dist/server/index.d.ts +0 -2
  138. package/dist/server/index.js +0 -2
  139. package/dist/server/routes/actions.js +1 -1
  140. package/dist/server/routes/info.js +1 -1
  141. package/dist/server/routes/playbooks.js +1 -1
  142. package/dist/server/routes/runs.js +123 -16
  143. package/dist/server/routes/state.js +43 -18
  144. package/dist/server/security.d.ts +1 -1
  145. package/dist/server/security.js +2 -2
  146. package/dist/server/server.js +3 -7
  147. package/dist/service/factory.js +4 -3
  148. package/dist/service/index.d.ts +1 -1
  149. package/dist/service/index.js +1 -1
  150. package/dist/service/remote-errors.d.ts +1 -1
  151. package/dist/service/remote-errors.js +6 -6
  152. package/dist/service/remote-polling.d.ts +3 -0
  153. package/dist/service/remote-polling.js +10 -5
  154. package/dist/service/remote.d.ts +5 -1
  155. package/dist/service/remote.js +108 -17
  156. package/dist/service/sse-stream.js +2 -2
  157. package/dist/service/types.d.ts +22 -19
  158. package/dist/service/types.js +1 -24
  159. package/dist/storage/clock.d.ts +1 -1
  160. package/dist/storage/clock.js +1 -1
  161. package/dist/storage/data-dir-lock.d.ts +10 -22
  162. package/dist/storage/data-dir-lock.js +28 -56
  163. package/dist/storage/file-lock.d.ts +64 -0
  164. package/dist/storage/file-lock.js +447 -0
  165. package/dist/storage/index.d.ts +2 -2
  166. package/dist/storage/index.js +5 -4
  167. package/dist/storage/run-liveness.d.ts +48 -0
  168. package/dist/storage/run-liveness.js +44 -0
  169. package/dist/storage/sqlite-config.d.ts +16 -0
  170. package/dist/storage/sqlite-config.js +59 -0
  171. package/dist/storage/sqlite-driver.d.ts +3 -1
  172. package/dist/storage/sqlite-driver.js +14 -4
  173. package/dist/storage/sqlite-events.d.ts +13 -0
  174. package/dist/storage/sqlite-events.js +48 -0
  175. package/dist/storage/sqlite-runs.d.ts +107 -0
  176. package/dist/storage/sqlite-runs.js +622 -0
  177. package/dist/storage/sqlite-schema.d.ts +5 -0
  178. package/dist/storage/sqlite-schema.js +143 -0
  179. package/dist/storage/sqlite-state.d.ts +33 -0
  180. package/dist/storage/sqlite-state.js +270 -0
  181. package/dist/storage/sqlite.d.ts +30 -57
  182. package/dist/storage/sqlite.js +84 -741
  183. package/dist/storage/types.d.ts +35 -2
  184. package/dist/utils/index.d.ts +18 -14
  185. package/dist/utils/index.js +37 -29
  186. package/dist/utils/semver.d.ts +18 -0
  187. package/dist/utils/semver.js +19 -0
  188. package/dist/utils/warning.d.ts +1 -0
  189. package/dist/utils/warning.js +52 -0
  190. package/dist/{json/value-validator.d.ts → value-validator.d.ts} +9 -0
  191. package/dist/value-validator.js +330 -0
  192. package/dist/version.d.ts +1 -1
  193. package/dist/version.js +1 -1
  194. package/package.json +10 -12
  195. package/dist/execution/index.d.ts +0 -2
  196. package/dist/execution/index.js +0 -2
  197. package/dist/filter/index.d.ts +0 -1
  198. package/dist/filter/index.js +0 -1
  199. package/dist/graph/index.d.ts +0 -4
  200. package/dist/graph/index.js +0 -4
  201. package/dist/input/metadata.d.ts +0 -99
  202. package/dist/input/metadata.js +0 -99
  203. package/dist/invocation/index.d.ts +0 -2
  204. package/dist/invocation/index.js +0 -2
  205. package/dist/ipc/index.d.ts +0 -4
  206. package/dist/ipc/index.js +0 -4
  207. package/dist/json/index.d.ts +0 -1
  208. package/dist/json/index.js +0 -1
  209. package/dist/json/value-validator.js +0 -330
  210. package/dist/node/index.d.ts +0 -1
  211. package/dist/node/index.js +0 -1
  212. package/dist/platform/node-fs.d.ts +0 -37
  213. package/dist/platform/node-fs.js +0 -81
  214. package/dist/process/control-arbiter.d.ts +0 -108
  215. package/dist/process/control-arbiter.js +0 -250
  216. package/dist/process/process-executor.d.ts +0 -47
  217. package/dist/process/process-executor.js +0 -296
  218. package/dist/profile/client-health.d.ts +0 -14
  219. package/dist/profile/client-query.d.ts +0 -30
  220. package/dist/profile/client-query.js +0 -69
  221. package/dist/profile/client.d.ts +0 -26
  222. package/dist/profile/client.js +0 -31
  223. package/dist/project/closure.d.ts +0 -25
  224. package/dist/project/closure.js +0 -74
  225. package/dist/registry/store.d.ts +0 -42
  226. package/dist/registry/store.js +0 -43
  227. package/dist/runtime/clock.d.ts +0 -7
  228. package/dist/runtime/clock.js +0 -7
  229. package/dist/runtime/module-loader.d.ts +0 -2
  230. package/dist/runtime/module-loader.js +0 -1
  231. package/dist/runtime/process.d.ts +0 -8
  232. package/dist/runtime/process.js +0 -1
  233. package/dist/runtime/runner.d.ts +0 -234
  234. package/dist/runtime/runner.js +0 -609
  235. package/dist/schema/index.d.ts +0 -1
  236. package/dist/schema/index.js +0 -1
  237. package/dist/server/dispatcher.d.ts +0 -25
  238. package/dist/server/dispatcher.js +0 -54
  239. package/dist/service/local.d.ts +0 -27
  240. package/dist/service/local.js +0 -141
  241. package/dist/storage/driver.d.ts +0 -10
  242. package/dist/storage/driver.js +0 -10
  243. package/dist/storage/lazy.d.ts +0 -8
  244. package/dist/storage/lazy.js +0 -45
  245. package/dist/storage/lock-core.d.ts +0 -158
  246. package/dist/storage/lock-core.js +0 -872
  247. package/dist/utils/net.d.ts +0 -10
  248. package/dist/utils/net.js +0 -16
  249. package/dist/utils/process.d.ts +0 -10
  250. package/dist/utils/process.js +0 -21
  251. /package/dist/{filter → utils}/intent.d.ts +0 -0
  252. /package/dist/{filter → utils}/intent.js +0 -0
@@ -1,7 +1,4 @@
1
- import { randomUUID } from "node:crypto";
2
- import { readFile, mkdir, rename, rm, stat, utimes, writeFile } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { isProcessAlive } from "../utils/process.js";
1
+ import { withFileLock } from "../storage/file-lock.js";
5
2
  /**
6
3
  * 锁目录最近一次心跳(创建或续期)后超过该时长且持有进程已死亡,
7
4
  * 才视为持有进程异常退出的残留锁。
@@ -18,99 +15,12 @@ export const REGISTRY_LOCK_ACQUIRE_TIMEOUT_MS = 15000;
18
15
  export const REGISTRY_LOCK_RETRY_DELAY_MS = 25;
19
16
  /**
20
17
  * 持锁期间的心跳续期间隔。持有者定期 touch 锁目录刷新 mtime,
21
- * 使长耗时持锁操作(如 linkPackage 的深度扫描)不会仅因耗时超过
22
- * stale 阈值而被等待方误判为残留锁抢占。
18
+ * 使长耗时持锁操作不会仅因耗时超过 stale 阈值而被等待方误判为残留锁抢占。
23
19
  */
24
20
  export const REGISTRY_LOCK_HEARTBEAT_MS = 3000;
25
- /**
26
- * 锁目录内的持有者元数据文件名。
27
- */
28
- const LOCK_METADATA_FILE = "metadata.json";
29
- function sleep(ms) {
30
- return new Promise((r) => setTimeout(r, ms));
31
- }
32
- /**
33
- * 读取锁目录中的持有者元数据;缺失、不可读或结构非法时返回 undefined。
34
- * 元数据信息缺失时陈旧判定退化为仅看 mtime 年龄(与历史行为兼容)。
35
- */
36
- async function readLockMetadata(lockDir) {
37
- let raw;
38
- try {
39
- raw = await readFile(join(lockDir, LOCK_METADATA_FILE), "utf-8");
40
- }
41
- catch {
42
- return undefined;
43
- }
44
- try {
45
- const parsed = JSON.parse(raw);
46
- if (parsed &&
47
- typeof parsed === "object" &&
48
- typeof parsed.pid === "number" &&
49
- typeof parsed.token === "string") {
50
- return {
51
- pid: parsed.pid,
52
- token: parsed.token,
53
- createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : "",
54
- };
55
- }
56
- }
57
- catch {
58
- // 损坏的元数据视同缺失
59
- }
60
- return undefined;
61
- }
62
- /**
63
- * 本进程当前持有的注册表锁目录集合。
64
- * 用于区分「同进程在途持锁」(不可回收,需等待)与「同进程历史泄漏锁」
65
- * (释放阶段 rm 失败遗留,pid 存活复核下仍应可回收),
66
- * 避免存活 pid 保守判定把自身泄漏锁变成永久死锁。
67
- */
68
- const activeLockDirs = new Set();
69
- /**
70
- * 判定锁目录是否为可回收的残留锁。
71
- *
72
- * 判定语义(保守优先,保护并发正确性):
73
- * - mtime 未超龄:一律不回收(正常持锁或心跳间隔内的活跃锁)。
74
- * - mtime 超龄 且元数据含存活 pid:不回收(慢磁盘下长扫描的持锁者仍活着;
75
- * 同进程在途持锁同样不回收,交由释放后重试获取)。
76
- * - mtime 超龄 且元数据 pid 已死:回收。
77
- * - mtime 超龄 且元数据缺失/非法:仅看 mtime 维持历史行为回收
78
- * (兼容旧版本无元数据锁目录与竞态窗口内的半初始化锁目录)。
79
- */
80
- async function isStaleLock(lockDir, staleMs = REGISTRY_LOCK_STALE_MS) {
81
- let lockAgeMs;
82
- try {
83
- const info = await stat(lockDir);
84
- lockAgeMs = Date.now() - info.mtimeMs;
85
- }
86
- catch (err) {
87
- // ENOENT:持有者恰好在两次探测之间释放,交由调用方立即重试获取
88
- throw err;
89
- }
90
- if (lockAgeMs <= staleMs) {
91
- return false;
92
- }
93
- const metadata = await readLockMetadata(lockDir);
94
- if (metadata === undefined) {
95
- return true;
96
- }
97
- if (metadata.pid === process.pid) {
98
- // 同进程:仅在途持锁不可回收,历史泄漏锁(不在活跃集合)可回收
99
- return !activeLockDirs.has(lockDir);
100
- }
101
- return !isProcessAlive(metadata.pid);
102
- }
103
21
  /**
104
22
  * 异步注册表锁(防止多进程并发读写导致 registry.json 损坏)。
105
- *
106
- * 获取方式:fs/promises mkdir 的原子性保证同一时刻仅一个进程能创建锁目录,
107
- * 创建成功后立即写入含 pid 与随机 token 的 metadata.json。
108
- * 冲突处理:EEXIST 时按保守语义判定残留(见 isStaleLock):mtime 超龄且
109
- * pid 已死才抢占——抢占仍通过原子 rename 到隔离名实现,rename 仅有一个
110
- * 进程能成功,失败方回到等待重试,消除「检查后删除再重建」的竞态窗口;
111
- * 未过期的锁通过 sleep 让出事件循环后重试。
112
- * 心跳续期:持锁期间以固定间隔 touch 锁目录刷新 mtime,使 linkPackage 等
113
- * 长耗时持锁操作不会仅因耗时被误判残留;fn 完成后停止心跳并删除锁目录。
23
+ * 基于统一轻量文件锁原语实现。
114
24
  *
115
25
  * @param filePath 注册表文件路径,锁目录为其同级 `${filePath}.lock`
116
26
  * @param fn 持锁期间执行的操作(同步或异步)
@@ -119,95 +29,16 @@ async function isStaleLock(lockDir, staleMs = REGISTRY_LOCK_STALE_MS) {
119
29
  * @throws 超过 acquireTimeoutMs 仍未获取时抛出描述性错误
120
30
  */
121
31
  export async function withRegistryLock(filePath, fn, options) {
122
- const staleMs = options?.staleMs ?? REGISTRY_LOCK_STALE_MS;
123
- const acquireTimeoutMs = options?.acquireTimeoutMs ?? REGISTRY_LOCK_ACQUIRE_TIMEOUT_MS;
124
- const retryDelayMs = options?.retryDelayMs ?? REGISTRY_LOCK_RETRY_DELAY_MS;
125
- const heartbeatMs = options?.heartbeatMs ?? REGISTRY_LOCK_HEARTBEAT_MS;
126
32
  const lockDir = `${filePath}.lock`;
127
- const deadline = Date.now() + acquireTimeoutMs;
128
- while (true) {
129
- try {
130
- // 原子获取:目录创建成功即持有锁,无需二次确认
131
- await mkdir(lockDir);
132
- activeLockDirs.add(lockDir);
133
- break;
134
- }
135
- catch (err) {
136
- if (err.code !== "EEXIST") {
137
- throw err;
138
- }
139
- }
140
- // 锁被占用,检查是否为可回收的残留锁
141
- let stale;
142
- try {
143
- stale = await isStaleLock(lockDir, staleMs);
144
- }
145
- catch (err) {
146
- if (err.code === "ENOENT") {
147
- // 持有者恰好在两次探测之间释放,立即重试获取
148
- continue;
149
- }
150
- throw err;
151
- }
152
- if (stale) {
153
- // 原子抢占:rename 到含进程号与时间戳的隔离名,仅一个进程能成功;
154
- // 失败方说明已被其他等待者抢先处理,回到等待重试即可
155
- const quarantine = `${lockDir}.stale.${process.pid}.${Date.now()}`;
156
- try {
157
- await rename(lockDir, quarantine);
158
- await rm(quarantine, { recursive: true, force: true });
159
- continue;
160
- }
161
- catch (err) {
162
- if (err.code === "ENOENT") {
163
- // 其他等待者已抢先回收该残留锁
164
- continue;
165
- }
166
- // 其他原因的 rename 失败(如瞬时权限问题)不视为致命,退回等待重试
167
- }
168
- }
169
- if (Date.now() > deadline) {
170
- throw new Error(`Failed to acquire registry lock '${lockDir}' within ${acquireTimeoutMs}ms. ` +
171
- `The lock may be held by another ActionDock process; retry after it exits or remove the stale lock directory manually.`);
172
- }
173
- await sleep(retryDelayMs);
174
- }
175
- // 持有者元数据写入失败不阻断持锁(退化为仅 mtime 判定),但保证可观测
176
- try {
177
- const metadata = {
178
- pid: process.pid,
179
- token: randomUUID(),
180
- createdAt: new Date().toISOString(),
181
- };
182
- await writeFile(join(lockDir, LOCK_METADATA_FILE), JSON.stringify(metadata, null, 2), "utf-8");
183
- }
184
- catch (err) {
185
- const reason = err instanceof Error ? err.message : String(err);
186
- console.warn(`[Registry] Failed to write lock metadata in '${lockDir}': ${reason}`);
187
- }
188
- // 心跳续期:定期 touch 锁目录刷新 mtime,防止长耗时持锁操作被误判残留。
189
- // 单次 touch 失败仅告警不中断(mtime 未刷新仅会提前暴露为陈旧候选,
190
- // 由 pid 存活复核兜底,不会破坏互斥)。
191
- const heartbeat = setInterval(() => {
192
- const now = new Date();
193
- utimes(lockDir, now, now).catch((err) => {
194
- const reason = err instanceof Error ? err.message : String(err);
195
- console.warn(`[Registry] Failed to refresh lock heartbeat for '${lockDir}': ${reason}`);
196
- });
197
- }, heartbeatMs);
198
- heartbeat.unref?.();
199
- try {
200
- return await fn();
201
- }
202
- finally {
203
- clearInterval(heartbeat);
204
- activeLockDirs.delete(lockDir);
205
- try {
206
- await rm(lockDir, { recursive: true, force: true });
207
- }
208
- catch {
209
- // 释放失败可忽略:进程未清理的锁目录会随时间超过 stale 阈值,
210
- // 被后续获取方的原子 rename 抢占回收,不会造成永久死锁
211
- }
212
- }
33
+ const acquireTimeoutMs = options?.acquireTimeoutMs ?? REGISTRY_LOCK_ACQUIRE_TIMEOUT_MS;
34
+ const lockOptions = {
35
+ staleMs: options?.staleMs ?? REGISTRY_LOCK_STALE_MS,
36
+ acquireTimeoutMs,
37
+ retryDelayMs: options?.retryDelayMs ?? REGISTRY_LOCK_RETRY_DELAY_MS,
38
+ heartbeatMs: options?.heartbeatMs ?? REGISTRY_LOCK_HEARTBEAT_MS,
39
+ mtimeFirst: true,
40
+ createLockError: () => new Error(`Failed to acquire registry lock '${lockDir}' within ${acquireTimeoutMs}ms. ` +
41
+ `The lock may be held by another ActionDock process; retry after it exits or remove the stale lock directory manually.`),
42
+ };
43
+ return await withFileLock(lockDir, fn, lockOptions);
213
44
  }
@@ -21,12 +21,7 @@ export declare function loadRegistry(customHome?: string): GlobalRegistryData;
21
21
  */
22
22
  export declare function loadRegistryAsync(customHome?: string): Promise<GlobalRegistryData>;
23
23
  /**
24
- * 原子写入注册表:临时文件写盘后 rename,持锁执行,避免并发撕裂。
25
- */
26
- export declare function saveRegistry(data: GlobalRegistryData, customHome?: string): Promise<void>;
27
- /**
28
- * 在已持有注册表锁的上下文中直接原子落盘(供复合读改写流程使用,
29
- * 避免与 saveRegistry 的锁重入死等);调用方必须已持有对应锁。
24
+ * 在已持有注册表锁的上下文中直接原子落盘(供复合读改写流程使用);调用方必须已持有对应锁。
30
25
  */
31
26
  export declare function writeRegistryLocked(data: GlobalRegistryData, customHome?: string): Promise<void>;
32
27
  export declare function linkPackage(targetPath?: string, customHome?: string, options?: {
@@ -3,6 +3,7 @@ import { readFile, rename, writeFile } from "node:fs/promises";
3
3
  import { basename, dirname, join, resolve, sep } from "node:path";
4
4
  import { findProjectRoot, loadProjectConfig } from "../project/loader.js";
5
5
  import { getActionDockHome, getPackageSlug } from "../utils/index.js";
6
+ import { ActionDockError, MANIFEST_NOT_FOUND, REGISTRY_CORRUPTED } from "../errors.js";
6
7
  import { withRegistryLock } from "./lock.js";
7
8
  import { buildLinkedPackageEntry, discoverProjectConfigs, discoverProjects, } from "./scan.js";
8
9
  /**
@@ -32,12 +33,12 @@ export function parseRegistryContent(raw, filePath) {
32
33
  parsed = JSON.parse(raw);
33
34
  }
34
35
  catch (err) {
35
- throw new Error(`Registry file '${filePath}' is corrupted (invalid JSON: ${err.message}). ` +
36
+ throw new ActionDockError(REGISTRY_CORRUPTED, `Registry file '${filePath}' is corrupted (invalid JSON: ${err.message}). ` +
36
37
  `The original data has been preserved as '${filePath}.corrupt' and can be restored manually. ` +
37
38
  `Fix or restore the file, or remove it to start with an empty registry.`);
38
39
  }
39
40
  if (!parsed || typeof parsed !== "object") {
40
- throw new Error(`Registry file '${filePath}' is corrupted (top-level value is not an object). ` +
41
+ throw new ActionDockError(REGISTRY_CORRUPTED, `Registry file '${filePath}' is corrupted (top-level value is not an object). ` +
41
42
  `The original data has been preserved as '${filePath}.corrupt' and can be restored manually. ` +
42
43
  `Fix or restore the file, or remove it to start with an empty registry.`);
43
44
  }
@@ -177,20 +178,7 @@ function buildUnifiedPayload(data) {
177
178
  }
178
179
  let tmpFileSeq = 0;
179
180
  /**
180
- * 原子写入注册表:临时文件写盘后 rename,持锁执行,避免并发撕裂。
181
- */
182
- export async function saveRegistry(data, customHome) {
183
- const filePath = getRegistryFilePath(customHome);
184
- ensureRegistryDir(filePath);
185
- const unifiedData = buildUnifiedPayload(data);
186
- const payload = JSON.stringify(unifiedData, null, 2) + "\n";
187
- await withRegistryLock(filePath, async () => {
188
- await writeRegistryPayloadLocked(filePath, payload);
189
- });
190
- }
191
- /**
192
- * 在已持有注册表锁的上下文中直接原子落盘(供复合读改写流程使用,
193
- * 避免与 saveRegistry 的锁重入死等);调用方必须已持有对应锁。
181
+ * 在已持有注册表锁的上下文中直接原子落盘(供复合读改写流程使用);调用方必须已持有对应锁。
194
182
  */
195
183
  export async function writeRegistryLocked(data, customHome) {
196
184
  const filePath = getRegistryFilePath(customHome);
@@ -298,7 +286,7 @@ export async function linkPackage(targetPath = process.cwd(), customHome, option
298
286
  };
299
287
  });
300
288
  }
301
- throw new Error(`Cannot link: actiondock.json not found in '${absPath}' or its subdirectories`);
289
+ throw new ActionDockError(MANIFEST_NOT_FOUND, `Cannot link: actiondock.json not found in '${absPath}' or its subdirectories`);
302
290
  }
303
291
  export async function unlinkPackage(identifier = process.cwd(), customHome) {
304
292
  const filePath = getRegistryFilePath(customHome);
@@ -1,4 +1,4 @@
1
- import type { ActionDefinition, ActionRef } from "@actiondock/sdk";
1
+ import type { ActionDefinition } from "@actiondock/sdk";
2
2
  /**
3
3
  * 为匿名传入的 Action 定义对象解析或分配稳定标识。
4
4
  */
@@ -28,10 +28,6 @@ export declare class ActionRegistry {
28
28
  */
29
29
  listActions(): ActionDefinition[];
30
30
  }
31
- /**
32
- * 本地注册表阶梯检索:跨包引用查 "pkg/action",本包引用依次查短标识与限定标识。
33
- */
34
- export declare function findLocalAction(actions: Map<string, ActionDefinition>, parsed: ActionRef, currentPackageId: string): ActionDefinition | undefined;
35
31
  /**
36
32
  * 判定传入对象是否为可执行的 Action 定义对象(含 run 函数)。
37
33
  *
@@ -76,18 +76,6 @@ export class ActionRegistry {
76
76
  return Array.from(this.actions.values());
77
77
  }
78
78
  }
79
- /**
80
- * 本地注册表阶梯检索:跨包引用查 "pkg/action",本包引用依次查短标识与限定标识。
81
- */
82
- export function findLocalAction(actions, parsed, currentPackageId) {
83
- const targetActionId = parsed.actionId;
84
- const targetPackageId = parsed.packageId;
85
- if (targetPackageId && targetPackageId !== currentPackageId) {
86
- return actions.get(`${targetPackageId}/${targetActionId}`);
87
- }
88
- return (actions.get(targetActionId) ||
89
- (currentPackageId ? actions.get(`${currentPackageId}/${targetActionId}`) : undefined));
90
- }
91
79
  /**
92
80
  * 判定传入对象是否为可执行的 Action 定义对象(含 run 函数)。
93
81
  *
@@ -1,7 +1,7 @@
1
1
  import { MemoryProcessDriver, ProcessManager } from "../process/index.js";
2
2
  import { createDefaultProcessOwner } from "../invocation/types.js";
3
3
  import { resolveEnvValue } from "./env.js";
4
- import { ActionDockError, INVALID_ACTION_REF } from "../errors.js";
4
+ import { ActionDockError, INVALID_ACTION_REF, INVALID_STATE } from "../errors.js";
5
5
  /**
6
6
  * 生产级配置解析器实现。
7
7
  * 严格践行 5 层配置解析优先级链:
@@ -177,7 +177,7 @@ export function createActionContext(options) {
177
177
  if (options.onActionInvoke) {
178
178
  return (await options.onActionInvoke(action, input, currentRunId));
179
179
  }
180
- throw new Error("ActionInvoker not configured with an invocation delegate");
180
+ throw new ActionDockError(INVALID_STATE, "ActionInvoker not configured with an invocation delegate");
181
181
  };
182
182
  const invokerFn = (ref, input) => invoke(ref, input);
183
183
  const invoker = Object.assign(invokerFn, { invoke });
@@ -1,7 +1,10 @@
1
1
  import type { ExecutionEvent } from "@actiondock/sdk";
2
2
  export interface EventSinkSubscribeOptions {
3
+ /** 起始游标位置(序号或事件标识) */
3
4
  after?: number | string;
5
+ /** 外部取消信号 */
4
6
  signal?: AbortSignal;
7
+ /** 订阅者事件队列上限(默认 1000) */
5
8
  maxQueueSize?: number;
6
9
  }
7
10
  export interface EventSink {
@@ -11,83 +14,41 @@ export interface EventSink {
11
14
  close?(): void;
12
15
  }
13
16
  export interface InMemoryEventSinkOptions {
14
- /** 单个运行最大缓冲事件数(默认 1024) */
17
+ /** 单个运行最大缓冲事件数(默认 1000) */
15
18
  maxEventsPerRun?: number;
16
- /** 单个运行最大缓冲字节数(默认 1024 * 1024 = 1MiB) */
17
- maxBytesPerRun?: number;
18
19
  /** 全局最大缓存运行数(默认 500) */
19
20
  maxRuns?: number;
20
- /** 慢订阅者事件队列背压有界上限(默认 1024) */
21
+ /** 订阅者事件队列上限(默认 1000) */
21
22
  maxSubscriberQueueSize?: number;
22
23
  }
23
24
  /**
24
- * 进程内有界事件缓冲区实现。
25
+ * 进程内轻量发布订阅事件广播器。
25
26
  * 遵循设计规范:
26
- * - 单运行双配额保护:上限 1024 条事件或 1MiB 字节数,优先淘汰日志与进度,保护终态。
27
+ * - 轻量发布订阅:保留订阅、退订、按 runId 过滤与事件分发。
28
+ * - 纯净广播:事件原样广播,绝不进行字节估算,绝不篡改用户执行结果。
29
+ * - 有界缓冲队列:简单条数上限(如 1000 条),队列满时丢弃最旧并计数,不切断通道。
27
30
  * - 全局有界回收:限制最大运行数(默认 500),溢出时优先淘汰已完成运行。
28
- * - 原子订阅衔接:先注册实时队列再回放历史快照,去重并杜绝并发丢事件窗口。
29
- * - 严格生命周期清理:迭代退出或中断时彻底注销监听器与 AbortSignal 事件。
30
- * - 慢订阅者背压保护:队列超出上限时推送终止事件并切断通道,主运行不受阻。
31
- * - 游标断点续传:支持全局持久化 eventId 与序列号,过期游标返回 EVENT_CURSOR_EXPIRED。
31
+ * - 错误隔离保护:单个订阅者监听器异常不扩散,保障系统整体稳定性。
32
32
  */
33
33
  export declare class InMemoryEventSink implements EventSink {
34
34
  private runs;
35
35
  private listeners;
36
- private wakeups;
37
36
  private evictions;
38
37
  private evictedRuns;
39
38
  private globalEventSeq;
40
39
  private maxEventsPerRun;
41
- private maxBytesPerRun;
42
40
  private maxRuns;
43
41
  private maxSubscriberQueueSize;
44
42
  constructor(options?: InMemoryEventSinkOptions);
45
43
  emit(event: ExecutionEvent): void;
46
44
  subscribe(runId: string, options?: EventSinkSubscribeOptions): AsyncIterable<ExecutionEvent>;
47
- /**
48
- * 断言请求游标仍处于保留窗口内(过期则抛出 EVENT_CURSOR_EXPIRED 错误)。
49
- */
50
- private assertCursorRetained;
51
- /**
52
- * 记录已交付事件并推进游标(已交付序号与最后确认游标)。
53
- */
54
- private trackDeliveredEvent;
55
- /**
56
- * 终止谓词:finish 事件或背压终止事件交付后订阅应立即结束。
57
- */
58
- private isTerminalDelivery;
59
- /**
60
- * 从实时队列取出下一条应交付的事件(跳过已交付序号),并同步推进游标。
61
- */
62
- private takeNextDeliverable;
63
- /**
64
- * 挂起等待下一条实时事件、通道终止或运行淘汰唤醒(严格注销监听避免泄漏)。
65
- */
66
- private waitForNextEvent;
67
- /**
68
- * 手动修剪已终态运行中早于指定序列号的历史事件(用于模拟过期清理)。
69
- */
70
- pruneEvents(runId: string, upToSequence: number): void;
71
45
  clear(runId: string): void;
72
- /**
73
- * 获取指定运行的事件统计信息(调试与测试用)。
74
- */
75
46
  getRunStats(runId: string): {
76
47
  count: number;
77
- bytes: number;
78
48
  isTerminal: boolean;
49
+ droppedCount: number;
79
50
  } | undefined;
80
- /**
81
- * 获取全局缓存的运行数。
82
- */
83
51
  getRunCount(): number;
84
- /**
85
- * 淘汰最久未活跃的运行记录。
86
- * 优先淘汰已终态运行;若无终态运行则淘汰最旧活动运行,严格保证 runs.size <= maxRuns。
87
- */
88
52
  private evictOldestRun;
89
- /**
90
- * 关闭事件接收器并唤醒所有活跃订阅者退出。
91
- */
92
53
  close(): void;
93
54
  }