@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
@@ -200,7 +200,16 @@ export interface RuntimeStorage {
200
200
  actionId?: string;
201
201
  status?: string;
202
202
  limit?: number;
203
+ offset?: number;
203
204
  }): RunRecord[];
205
+ /** 统计符合条件的运行记录总数 */
206
+ countRuns?(options?: {
207
+ actionId?: string;
208
+ status?: string;
209
+ requestIds?: string[];
210
+ }): number;
211
+ /** 查询单条运行记录并补齐幂等请求标识关联(详情路径专用) */
212
+ getRunWithRequestId?(id: string): RunRecord | null;
204
213
  clearRuns(options?: {
205
214
  actionId?: string;
206
215
  status?: string;
@@ -210,14 +219,38 @@ export interface RuntimeStorage {
210
219
  /** 按保留策略清理过期及超额的终态运行记录(基于时间与数量策略) */
211
220
  cleanExpiredRuns?(policy?: RunsRetentionPolicy): number;
212
221
  /**
213
- * 收敛死亡会话遗留的非终态运行任务(含无会话标识的遗留非终态记录),
222
+ * 收敛死亡宿主遗留的非终态运行任务(含无会话标识的遗留非终态记录),
214
223
  * 统一收敛为 interrupted。历史别名 recoverRunningRuns 已合并至本方法。
224
+ *
225
+ * 判定依据是宿主存活而不是会话归属:仅收割进程探测失败或心跳过期的记录,
226
+ * 无法确认死亡时保守保留,避免误杀并发进程的在途任务。
215
227
  */
216
- recoverDeadSessionRuns?(currentHostSessionId?: string): number | Promise<number>;
228
+ recoverDeadSessionRuns?(currentHostSessionId?: string, options?: {
229
+ probe?: import("./run-liveness.js").ProcessLivenessProbe;
230
+ }): number | Promise<number>;
231
+ /** 刷新在途运行记录的心跳时间戳(供执行宿主周期性调用,支撑存活判定) */
232
+ touchRunHeartbeat?(runIds: string[]): number;
217
233
  /** 确保底层存储与 Schema 初始化完成 */
218
234
  ensureInitialized?(): Promise<void>;
219
235
  checkAndRecordIdempotency?(record: IdempotencyRecord): IdempotencyCheckResult;
220
236
  getIdempotencyRecord?(ownerId: string, actionRef: string, requestId: string): IdempotencyRecord | undefined;
237
+ /**
238
+ * 按客户端幂等请求标识批量反查运行记录(仅限当前包范围)。
239
+ *
240
+ * 不按 ownerId 隔离:查询方(CLI 旁观视图)与写入方(后台派工进程)分属
241
+ * 不同宿主会话,ownerId 天然不一致;requestId 本身即跨进程全局唯一的外部
242
+ * 事实源,包维度隔离已提供足够边界。
243
+ */
244
+ listRunsByRequestIds?(requestIds: string[]): RunRecord[];
245
+ /** 按客户端幂等请求标识反查关联运行记录总数 */
246
+ countRunsByRequestIds?(requestIds: string[], options?: {
247
+ actionId?: string;
248
+ status?: string;
249
+ }): number;
250
+ /** 反查运行记录关联的幂等请求标识映射(仅限当前包范围,无关联时不含对应键) */
251
+ getRunRequestIds?(runIds: string[]): Record<string, string>;
252
+ appendEvent?(eventType: string, payload: unknown): void;
253
+ queryEvents?(eventType?: string): any[];
221
254
  /** 关闭底层 SQLite 数据库连接并释放句柄 */
222
255
  close(): void | Promise<void>;
223
256
  }
@@ -1,20 +1,23 @@
1
- export { isLoopbackHost } from "./net.js";
2
- export { isProcessAlive } from "./process.js";
3
1
  /**
4
- * 语义化版本元数据结构。
2
+ * 检查指定的主机地址是否为本地回环接口(Loopback Host)。
3
+ * 支持 127.0.0.1, localhost, ::1 等形式。
4
+ *
5
+ * @param host 主机名或 IP 字符串
5
6
  */
6
- export interface SemVer {
7
- major: number;
8
- minor: number;
9
- patch: number;
10
- prerelease?: string;
11
- }
7
+ export declare function isLoopbackHost(host: string): boolean;
12
8
  /**
13
- * 解析基础 SemVer 版本字符串(单一事实源)。
14
- * 仅接受可选 v/= 前缀的严格三段式语义化版本,可携带预发布后缀;
15
- * 其余形态(两段式、非数值等)返回 null,由调用方自行兜底。
9
+ * 检查目标进程是否处于存活状态(跨域通用谓词单一事实源)。
10
+ *
11
+ * 基于 process.kill(pid, 0) 探测:无异常视为存活;ESRCH(进程不存在)
12
+ * 视为已死;其他异常(如 EPERM,权限不足但进程存在)保守视为存活,
13
+ * 避免权限受限环境下误判他人持有进程死亡而抢占其锁。
14
+ *
15
+ * @param pid 待检测的进程标识符
16
16
  */
17
- export declare function parseSemVer(v: string): SemVer | null;
17
+ export declare function isProcessAlive(pid: number): boolean;
18
+ /** 转引独立的语义化版本纯函数,保持既有公共导出面。 */
19
+ export type { SemVer } from "./semver.js";
20
+ export { parseSemVer } from "./semver.js";
18
21
  /**
19
22
  * 目录遍历条目描述。
20
23
  */
@@ -53,7 +56,7 @@ export interface TraverseDirectoryOptions {
53
56
  */
54
57
  export declare function traverseDirectory(root: string, options?: TraverseDirectoryOptions): TraverseDirectoryEntry[];
55
58
  /**
56
- * 跨运行时安全查找可执行文件绝对物理路径。
59
+ * 安全查找可执行文件绝对物理路径。
57
60
  *
58
61
  * 注意:本函数为同步实现,内部同步遍历 PATH 逐个探测物理文件存在性;
59
62
  * 当前全部调用方(doctor 体检与测试 CLI 桥)均为同步链路,serve 链路未使用本函数,
@@ -101,3 +104,4 @@ export declare function assertPathWithinRoot(rootDir: string, targetPath: string
101
104
  * Backwards-compatible alias for assertPathWithinRoot targeting project roots.
102
105
  */
103
106
  export declare function assertWithinProjectRoot(projectRoot: string, subDir: string, fieldName: string): void;
107
+ export { suppressExperimentalWarnings } from "./warning.js";
@@ -1,25 +1,42 @@
1
1
  import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
- import { ActionDockError, INVALID_PACKAGE_ID, PATH_TRAVERSAL } from "../errors.js";
5
- export { isLoopbackHost } from "./net.js";
6
- export { isProcessAlive } from "./process.js";
4
+ import { ActionDockError, INVALID_ARGUMENT, INVALID_PACKAGE_ID, PATH_TRAVERSAL } from "../errors.js";
7
5
  /**
8
- * 解析基础 SemVer 版本字符串(单一事实源)。
9
- * 仅接受可选 v/= 前缀的严格三段式语义化版本,可携带预发布后缀;
10
- * 其余形态(两段式、非数值等)返回 null,由调用方自行兜底。
6
+ * 检查指定的主机地址是否为本地回环接口(Loopback Host)。
7
+ * 支持 127.0.0.1, localhost, ::1 等形式。
8
+ *
9
+ * @param host 主机名或 IP 字符串
11
10
  */
12
- export function parseSemVer(v) {
13
- const match = v.trim().replace(/^[v=]/, "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
14
- if (!match)
15
- return null;
16
- return {
17
- major: parseInt(match[1], 10),
18
- minor: parseInt(match[2], 10),
19
- patch: parseInt(match[3], 10),
20
- prerelease: match[4],
21
- };
11
+ export function isLoopbackHost(host) {
12
+ const trimmed = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
13
+ return (trimmed === "127.0.0.1" ||
14
+ trimmed === "::1" ||
15
+ trimmed === "localhost" ||
16
+ trimmed === "0:0:0:0:0:0:0:1");
17
+ }
18
+ /**
19
+ * 检查目标进程是否处于存活状态(跨域通用谓词单一事实源)。
20
+ *
21
+ * 基于 process.kill(pid, 0) 探测:无异常视为存活;ESRCH(进程不存在)
22
+ * 视为已死;其他异常(如 EPERM,权限不足但进程存在)保守视为存活,
23
+ * 避免权限受限环境下误判他人持有进程死亡而抢占其锁。
24
+ *
25
+ * @param pid 待检测的进程标识符
26
+ */
27
+ export function isProcessAlive(pid) {
28
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) {
29
+ return false;
30
+ }
31
+ try {
32
+ process.kill(pid, 0);
33
+ return true;
34
+ }
35
+ catch (err) {
36
+ return err?.code !== "ESRCH";
37
+ }
22
38
  }
39
+ export { parseSemVer } from "./semver.js";
23
40
  /**
24
41
  * 递归遍历目录的单一事实源实现(同步)。
25
42
  *
@@ -98,23 +115,13 @@ export function traverseDirectory(root, options = {}) {
98
115
  return results;
99
116
  }
100
117
  /**
101
- * 跨运行时安全查找可执行文件绝对物理路径。
118
+ * 安全查找可执行文件绝对物理路径。
102
119
  *
103
120
  * 注意:本函数为同步实现,内部同步遍历 PATH 逐个探测物理文件存在性;
104
121
  * 当前全部调用方(doctor 体检与测试 CLI 桥)均为同步链路,serve 链路未使用本函数,
105
122
  * 因此暂不提供异步版本。若后续异步链路需要,应另行新增异步实现而非改造本函数。
106
123
  */
107
124
  export function findExecutable(command) {
108
- if (typeof globalThis.Bun !== "undefined" && typeof globalThis.Bun.which === "function") {
109
- try {
110
- const bPath = globalThis.Bun.which(command);
111
- if (bPath)
112
- return bPath;
113
- }
114
- catch {
115
- // ignore
116
- }
117
- }
118
125
  const hasPathSep = command.includes("/") || command.includes("\\");
119
126
  if (hasPathSep) {
120
127
  return existsSync(command) ? command : null;
@@ -150,7 +157,7 @@ export function parseDuration(input) {
150
157
  }
151
158
  const match = str.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/i);
152
159
  if (!match) {
153
- throw new Error(`Invalid duration format: '${input}'. Supported formats: 500ms, 30s, 5m, 1h`);
160
+ throw new ActionDockError(INVALID_ARGUMENT, `Invalid duration format: '${input}'. Supported formats: 500ms, 30s, 5m, 1h`);
154
161
  }
155
162
  const val = parseFloat(match[1]);
156
163
  const unit = match[2].toLowerCase();
@@ -270,7 +277,8 @@ export function assertPathWithinRoot(rootDir, targetPath, fieldName = "path") {
270
277
  */
271
278
  export function assertWithinProjectRoot(projectRoot, subDir, fieldName) {
272
279
  if (isAbsolute(subDir)) {
273
- throw new Error(`'${fieldName}' cannot be an absolute path: ${subDir}`);
280
+ throw new ActionDockError(INVALID_ARGUMENT, `'${fieldName}' cannot be an absolute path: ${subDir}`);
274
281
  }
275
282
  assertPathWithinRoot(projectRoot, subDir, fieldName);
276
283
  }
284
+ export { suppressExperimentalWarnings } from "./warning.js";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 语义化版本元数据结构。
3
+ */
4
+ export interface SemVer {
5
+ major: number;
6
+ minor: number;
7
+ patch: number;
8
+ prerelease?: string;
9
+ }
10
+ /**
11
+ * 解析基础 SemVer 版本字符串(单一事实源)。
12
+ * 仅接受可选 v/= 前缀的严格三段式语义化版本,可携带预发布后缀;
13
+ * 其余形态(两段式、非数值等)返回 null,由调用方自行兜底。
14
+ *
15
+ * 本模块不依赖任何 Node 内建与 core 领域模块,可在无 loader 的原生
16
+ * Node 直接以 .ts 形态导入(工程脚本域复用此处单一事实源)。
17
+ */
18
+ export declare function parseSemVer(v: string): SemVer | null;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 解析基础 SemVer 版本字符串(单一事实源)。
3
+ * 仅接受可选 v/= 前缀的严格三段式语义化版本,可携带预发布后缀;
4
+ * 其余形态(两段式、非数值等)返回 null,由调用方自行兜底。
5
+ *
6
+ * 本模块不依赖任何 Node 内建与 core 领域模块,可在无 loader 的原生
7
+ * Node 直接以 .ts 形态导入(工程脚本域复用此处单一事实源)。
8
+ */
9
+ export function parseSemVer(v) {
10
+ const match = v.trim().replace(/^[v=]/, "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
11
+ if (!match)
12
+ return null;
13
+ return {
14
+ major: parseInt(match[1], 10),
15
+ minor: parseInt(match[2], 10),
16
+ patch: parseInt(match[3], 10),
17
+ prerelease: match[4],
18
+ };
19
+ }
@@ -0,0 +1 @@
1
+ export declare function suppressExperimentalWarnings(): void;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * 全局禁用 Node.js 所有的 ExperimentalWarning 实验性特性警告。
3
+ * 跨所有 Node.js 运行版本,不区分具体特性(如 SQLite、类型擦除等),
4
+ * 在进程级别统一静默全部实验性警告,同时完整保留废弃警告与运行期异常。
5
+ *
6
+ * 逃生通道:当环境变量 ACTIONDOCK_SILENCE_WARNINGS 显式设置为 "0" 时,放弃静默以供深度调试排查。
7
+ */
8
+ const kExperimentalWarningSuppressed = Symbol.for("actiondock.experimental_warning_suppressed");
9
+ export function suppressExperimentalWarnings() {
10
+ // 逃生通道:支持开发者显式禁用静默逻辑,确保排障透明度
11
+ if (process.env.ACTIONDOCK_SILENCE_WARNINGS === "0") {
12
+ return;
13
+ }
14
+ const globalObj = globalThis;
15
+ if (globalObj[kExperimentalWarningSuppressed]) {
16
+ return;
17
+ }
18
+ globalObj[kExperimentalWarningSuppressed] = true;
19
+ // 1. 拦截 process.emitWarning 源头
20
+ const originalEmitWarning = process.emitWarning;
21
+ if (typeof originalEmitWarning === "function") {
22
+ process.emitWarning = function (warning, ...args) {
23
+ if (typeof warning === "string") {
24
+ const type = typeof args[0] === "string" ? args[0] : args[0]?.type || args[1];
25
+ if (type === "ExperimentalWarning") {
26
+ return;
27
+ }
28
+ }
29
+ else if (warning) {
30
+ const warnObj = warning;
31
+ if (warnObj.name === "ExperimentalWarning" || warnObj.type === "ExperimentalWarning") {
32
+ return;
33
+ }
34
+ }
35
+ return Reflect.apply(originalEmitWarning, process, [warning, ...args]);
36
+ };
37
+ }
38
+ // 2. 包装进程级 warning 事件分发器,作为双层防护
39
+ const originalListeners = process.listeners("warning");
40
+ process.removeAllListeners("warning");
41
+ process.on("warning", (warning) => {
42
+ const warnObj = warning;
43
+ if (warnObj.name === "ExperimentalWarning" || warnObj.type === "ExperimentalWarning") {
44
+ return;
45
+ }
46
+ for (const listener of originalListeners) {
47
+ listener.call(process, warning);
48
+ }
49
+ });
50
+ }
51
+ // 模块被引入时立即自执行
52
+ suppressExperimentalWarnings();
@@ -79,6 +79,15 @@ export declare function validateJsonValue(value: unknown, options?: ValidateJson
79
79
  * @returns 校验结果对象
80
80
  */
81
81
  export declare function validateActionInputValue(value: unknown, options?: ValidateActionInputOptions): ActionInputValidationResult;
82
+ /**
83
+ * 递归检查数据中是否包含危险的原型污染属性键名(__proto__、constructor、prototype)。
84
+ * 针对环路对象与有向无环图(DAG)保证安全遍历且具有深度上限防御。
85
+ *
86
+ * @param data 待检查的目标数据
87
+ * @param maxDepth 最大允许嵌套深度(默认 256)
88
+ * @returns 若存在危险键名则返回 true,否则返回 false
89
+ */
90
+ export declare function hasDangerousKeys(data: unknown, maxDepth?: number): boolean;
82
91
  /**
83
92
  * 断言值必须为合法的 Canonical JSON 兼容结构,若非法则抛出 TypeError。
84
93
  *
@@ -0,0 +1,330 @@
1
+ import { isForbiddenActionInputPropertyName } from "./input/flat-predicates.js";
2
+ /** 默认 JSON 最大嵌套深度限制(防止恶意超深结构) */
3
+ export const DEFAULT_MAX_JSON_DEPTH = 256;
4
+ /** 最大安全递归调用栈深度,防止超大自定义 maxDepth 导致栈溢出 */
5
+ const MAX_SAFE_RECURSION_DEPTH = 2048;
6
+ /**
7
+ * 按照 RFC 6901 转义单个 JSON Pointer 路径段。
8
+ *
9
+ * @param segment 路径段(属性名或数组索引)
10
+ * @returns 转义后的路径段
11
+ */
12
+ export function escapeJsonPointerSegment(segment) {
13
+ return String(segment).replace(/~/g, "~0").replace(/\//g, "~1");
14
+ }
15
+ /**
16
+ * 将路径段追加到基础 RFC 6901 JSON Pointer 路径后。
17
+ *
18
+ * @param basePath 基础路径
19
+ * @param segment 待追加的路径段
20
+ * @returns 拼接后的 JSON Pointer
21
+ */
22
+ export function appendJsonPointer(basePath, segment) {
23
+ return `${basePath}/${escapeJsonPointerSegment(segment)}`;
24
+ }
25
+ function walkJsonValue(val, depth, path, maxDepth, ancestors, checkForbidden) {
26
+ if (val === null || typeof val === "boolean" || typeof val === "string") {
27
+ return { valid: true };
28
+ }
29
+ if (typeof val === "number") {
30
+ if (!Number.isFinite(val) || Number.isNaN(val)) {
31
+ return {
32
+ valid: false,
33
+ kind: "json-value",
34
+ code: "NON_FINITE_NUMBER",
35
+ reason: `Number is non-finite or NaN (${val})`,
36
+ path,
37
+ };
38
+ }
39
+ return { valid: true };
40
+ }
41
+ if (typeof val === "undefined") {
42
+ return {
43
+ valid: false,
44
+ kind: "json-value",
45
+ code: "UNDEFINED_JSON_VALUE",
46
+ reason: "Undefined value is not allowed in JSON",
47
+ path,
48
+ };
49
+ }
50
+ if (typeof val === "function" ||
51
+ typeof val === "symbol" ||
52
+ typeof val === "bigint") {
53
+ return {
54
+ valid: false,
55
+ kind: "json-value",
56
+ code: "UNSUPPORTED_JSON_TYPE",
57
+ reason: `Unsupported JSON type '${typeof val}'`,
58
+ path,
59
+ };
60
+ }
61
+ if (typeof val === "object") {
62
+ if (depth > maxDepth || depth > MAX_SAFE_RECURSION_DEPTH) {
63
+ return {
64
+ valid: false,
65
+ kind: "json-value",
66
+ code: "MAX_JSON_DEPTH",
67
+ reason: `Max JSON depth limit (${maxDepth}) exceeded`,
68
+ path,
69
+ };
70
+ }
71
+ if (ancestors.has(val)) {
72
+ return {
73
+ valid: false,
74
+ kind: "json-value",
75
+ code: "CIRCULAR_REFERENCE",
76
+ reason: "Circular reference detected in object structure",
77
+ path,
78
+ };
79
+ }
80
+ try {
81
+ if (Array.isArray(val)) {
82
+ if (Object.getPrototypeOf(val) !== Array.prototype) {
83
+ return {
84
+ valid: false,
85
+ kind: "json-value",
86
+ code: "INVALID_JSON_OBJECT",
87
+ reason: "Array prototype must be Array.prototype",
88
+ path,
89
+ };
90
+ }
91
+ // 稀疏数组(存在 hole)不是合法 JSON 结构
92
+ for (let i = 0; i < val.length; i++) {
93
+ if (!(i in val)) {
94
+ return {
95
+ valid: false,
96
+ kind: "json-value",
97
+ code: "INVALID_JSON_OBJECT",
98
+ reason: `Sparse array (hole at index ${i}) is not allowed in JSON`,
99
+ path: appendJsonPointer(path, i),
100
+ };
101
+ }
102
+ }
103
+ // 数组只允许索引元素,拒绝额外字符串属性或 Symbol 属性(忽略不可枚举的内建属性如 length)
104
+ const extraKeys = Reflect.ownKeys(val).filter((k) => {
105
+ if (typeof k === "symbol")
106
+ return true;
107
+ if (!/^\d+$/.test(k) || Number(k) >= val.length) {
108
+ const desc = Object.getOwnPropertyDescriptor(val, k);
109
+ // 不可枚举属性(如数组内建 length)不视为额外属性
110
+ return desc?.enumerable === true;
111
+ }
112
+ return false;
113
+ });
114
+ if (extraKeys.length > 0) {
115
+ return {
116
+ valid: false,
117
+ kind: "json-value",
118
+ code: "INVALID_JSON_OBJECT",
119
+ reason: "Arrays must not carry extra properties beyond indexed elements",
120
+ path,
121
+ };
122
+ }
123
+ ancestors.add(val);
124
+ try {
125
+ for (let i = 0; i < val.length; i++) {
126
+ const res = walkJsonValue(val[i], depth + 1, appendJsonPointer(path, i), maxDepth, ancestors, checkForbidden);
127
+ if (!res.valid)
128
+ return res;
129
+ }
130
+ }
131
+ finally {
132
+ ancestors.delete(val);
133
+ }
134
+ return { valid: true };
135
+ }
136
+ const proto = Object.getPrototypeOf(val);
137
+ if (proto !== Object.prototype && proto !== null) {
138
+ return {
139
+ valid: false,
140
+ kind: "json-value",
141
+ code: "INVALID_JSON_OBJECT",
142
+ reason: "Object prototype must be Object.prototype or null",
143
+ path,
144
+ };
145
+ }
146
+ const ownKeys = Reflect.ownKeys(val);
147
+ if (checkForbidden) {
148
+ for (let i = 0; i < ownKeys.length; i++) {
149
+ const key = ownKeys[i];
150
+ if (typeof key === "string" && isForbiddenActionInputPropertyName(key)) {
151
+ return {
152
+ valid: false,
153
+ kind: "input-policy",
154
+ code: "FORBIDDEN_PROPERTY",
155
+ property: key,
156
+ path: appendJsonPointer(path, key),
157
+ reason: `Forbidden property "${key}" is not allowed in Action input`,
158
+ };
159
+ }
160
+ }
161
+ }
162
+ ancestors.add(val);
163
+ try {
164
+ for (let i = 0; i < ownKeys.length; i++) {
165
+ const key = ownKeys[i];
166
+ if (typeof key !== "string") {
167
+ return {
168
+ valid: false,
169
+ kind: "json-value",
170
+ code: "INVALID_JSON_OBJECT",
171
+ reason: "Symbol-keyed properties are not allowed in JSON object",
172
+ path: appendJsonPointer(path, String(key)),
173
+ };
174
+ }
175
+ const propPath = appendJsonPointer(path, key);
176
+ const desc = Object.getOwnPropertyDescriptor(val, key);
177
+ if (!desc)
178
+ continue;
179
+ if (desc.get !== undefined || desc.set !== undefined) {
180
+ return {
181
+ valid: false,
182
+ kind: "json-value",
183
+ code: "INVALID_JSON_OBJECT",
184
+ reason: `Accessor properties (getters/setters) are not allowed for key "${key}"`,
185
+ path: propPath,
186
+ };
187
+ }
188
+ if (!desc.enumerable) {
189
+ return {
190
+ valid: false,
191
+ kind: "json-value",
192
+ code: "INVALID_JSON_OBJECT",
193
+ reason: `Non-enumerable properties are not allowed for key "${key}"`,
194
+ path: propPath,
195
+ };
196
+ }
197
+ if (desc.value === undefined) {
198
+ return {
199
+ valid: false,
200
+ kind: "json-value",
201
+ code: "UNDEFINED_JSON_VALUE",
202
+ reason: `Undefined value is not allowed for key "${key}"`,
203
+ path: propPath,
204
+ };
205
+ }
206
+ const res = walkJsonValue(desc.value, depth + 1, propPath, maxDepth, ancestors, checkForbidden);
207
+ if (!res.valid)
208
+ return res;
209
+ }
210
+ }
211
+ finally {
212
+ ancestors.delete(val);
213
+ }
214
+ return { valid: true };
215
+ }
216
+ catch (err) {
217
+ return {
218
+ valid: false,
219
+ kind: "json-value",
220
+ code: "INVALID_JSON_OBJECT",
221
+ reason: `Failed to inspect object: ${err instanceof Error ? err.message : String(err)}`,
222
+ path,
223
+ };
224
+ }
225
+ }
226
+ return { valid: true };
227
+ }
228
+ /**
229
+ * 校验值是否为合法的 Canonical JSON 兼容结构。
230
+ *
231
+ * @param value 待校验的目标值
232
+ * @param options 校验配置选项
233
+ * @returns 校验结果对象
234
+ */
235
+ export function validateJsonValue(value, options) {
236
+ const maxDepth = options?.maxDepth ?? DEFAULT_MAX_JSON_DEPTH;
237
+ const res = walkJsonValue(value, 0, "", maxDepth, new Set(), false);
238
+ if (res.valid) {
239
+ return { valid: true };
240
+ }
241
+ if (res.kind !== "input-policy") {
242
+ return {
243
+ valid: false,
244
+ code: res.code,
245
+ reason: res.reason,
246
+ path: res.path,
247
+ };
248
+ }
249
+ return {
250
+ valid: false,
251
+ code: "INVALID_JSON_OBJECT",
252
+ reason: res.reason,
253
+ path: res.path,
254
+ };
255
+ }
256
+ /**
257
+ * 校验 Action 输入值是否满足 Canonical JsonValue 规范且符合 ActionDock 安全策略。
258
+ * 在遍历过程中递归检查是否存在禁止属性(__proto__、constructor、prototype)。
259
+ *
260
+ * @param value 待校验的 Action 输入值
261
+ * @param options 校验配置选项
262
+ * @returns 校验结果对象
263
+ */
264
+ export function validateActionInputValue(value, options) {
265
+ const maxDepth = options?.maxDepth ?? DEFAULT_MAX_JSON_DEPTH;
266
+ return walkJsonValue(value, 0, "", maxDepth, new Set(), true);
267
+ }
268
+ /**
269
+ * 递归检查数据中是否包含危险的原型污染属性键名(__proto__、constructor、prototype)。
270
+ * 针对环路对象与有向无环图(DAG)保证安全遍历且具有深度上限防御。
271
+ *
272
+ * @param data 待检查的目标数据
273
+ * @param maxDepth 最大允许嵌套深度(默认 256)
274
+ * @returns 若存在危险键名则返回 true,否则返回 false
275
+ */
276
+ export function hasDangerousKeys(data, maxDepth = DEFAULT_MAX_JSON_DEPTH) {
277
+ const visited = new Set();
278
+ function scan(val, depth) {
279
+ if (!val || typeof val !== "object" || depth > maxDepth) {
280
+ return false;
281
+ }
282
+ if (visited.has(val)) {
283
+ return false;
284
+ }
285
+ visited.add(val);
286
+ try {
287
+ if (Array.isArray(val)) {
288
+ for (let i = 0; i < val.length; i++) {
289
+ if (scan(val[i], depth + 1))
290
+ return true;
291
+ }
292
+ return false;
293
+ }
294
+ const keys = Reflect.ownKeys(val);
295
+ for (let i = 0; i < keys.length; i++) {
296
+ const k = keys[i];
297
+ if (typeof k === "string" && isForbiddenActionInputPropertyName(k)) {
298
+ return true;
299
+ }
300
+ }
301
+ for (let i = 0; i < keys.length; i++) {
302
+ const k = keys[i];
303
+ if (typeof k === "string") {
304
+ const desc = Object.getOwnPropertyDescriptor(val, k);
305
+ if (desc && desc.value !== undefined) {
306
+ if (scan(desc.value, depth + 1))
307
+ return true;
308
+ }
309
+ }
310
+ }
311
+ }
312
+ catch {
313
+ return false;
314
+ }
315
+ return false;
316
+ }
317
+ return scan(data, 0);
318
+ }
319
+ /**
320
+ * 断言值必须为合法的 Canonical JSON 兼容结构,若非法则抛出 TypeError。
321
+ *
322
+ * @param value 待断言的目标值
323
+ * @param options 校验配置选项
324
+ */
325
+ export function assertJsonValue(value, options) {
326
+ const result = validateJsonValue(value, options);
327
+ if (!result.valid) {
328
+ throw new TypeError(`Invalid JSON value: ${result.reason}`);
329
+ }
330
+ }
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * ActionDock 核心版本号单一事实源。
3
3
  */
4
- export declare const ACTIONDOCK_VERSION = "2.10.0";
4
+ export declare const ACTIONDOCK_VERSION = "2.11.0";
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * ActionDock 核心版本号单一事实源。
3
3
  */
4
- export const ACTIONDOCK_VERSION = "2.10.0";
4
+ export const ACTIONDOCK_VERSION = "2.11.0";