@actiondock/core 2.6.0 → 2.8.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 (268) hide show
  1. package/README.md +61 -68
  2. package/dist/catalog/action-catalog.d.ts +56 -0
  3. package/dist/catalog/action-catalog.js +202 -0
  4. package/dist/catalog/discovery.d.ts +60 -0
  5. package/dist/catalog/discovery.js +119 -0
  6. package/dist/catalog/graph.d.ts +190 -0
  7. package/dist/catalog/graph.js +589 -0
  8. package/dist/catalog/index.d.ts +5 -5
  9. package/dist/catalog/index.js +5 -5
  10. package/dist/catalog/resolve-action.d.ts +43 -0
  11. package/dist/catalog/resolve-action.js +119 -0
  12. package/dist/catalog/resolve-playbook.d.ts +26 -0
  13. package/dist/catalog/resolve-playbook.js +114 -0
  14. package/dist/doctor/checks/project.js +27 -8
  15. package/dist/doctor/checks/registry.js +2 -2
  16. package/dist/doctor/checks/runtime.js +34 -1
  17. package/dist/errors.d.ts +122 -10
  18. package/dist/errors.js +123 -10
  19. package/dist/execution/service.d.ts +18 -16
  20. package/dist/execution/service.js +183 -252
  21. package/dist/execution/types.d.ts +25 -69
  22. package/dist/execution/types.js +2 -1
  23. package/dist/filter/intent.d.ts +8 -9
  24. package/dist/filter/intent.js +49 -10
  25. package/dist/graph/index.d.ts +4 -0
  26. package/dist/graph/index.js +4 -0
  27. package/dist/host/host.d.ts +74 -32
  28. package/dist/host/host.js +585 -336
  29. package/dist/host/routing.d.ts +15 -48
  30. package/dist/host/routing.js +90 -153
  31. package/dist/host/types.d.ts +50 -17
  32. package/dist/index.d.ts +21 -22
  33. package/dist/index.js +19 -21
  34. package/dist/input/advice.d.ts +104 -0
  35. package/dist/input/advice.js +716 -0
  36. package/dist/input/describe.d.ts +106 -0
  37. package/dist/input/describe.js +176 -0
  38. package/dist/input/file-input.d.ts +54 -0
  39. package/dist/input/file-input.js +124 -0
  40. package/dist/input/flat-decode.d.ts +11 -0
  41. package/dist/input/flat-decode.js +13 -0
  42. package/dist/input/flat-errors.d.ts +71 -0
  43. package/dist/input/flat-errors.js +87 -0
  44. package/dist/input/flat-materializer.d.ts +29 -0
  45. package/dist/input/flat-materializer.js +177 -0
  46. package/dist/input/flat-parser.d.ts +50 -0
  47. package/dist/input/flat-parser.js +169 -0
  48. package/dist/input/flat-predicates.d.ts +34 -0
  49. package/dist/input/flat-predicates.js +50 -0
  50. package/dist/input/index.d.ts +14 -0
  51. package/dist/input/index.js +14 -0
  52. package/dist/input/input-resolver.d.ts +63 -0
  53. package/dist/input/input-resolver.js +254 -0
  54. package/dist/input/json-depth-scanner.d.ts +17 -0
  55. package/dist/input/json-depth-scanner.js +49 -0
  56. package/dist/input/metadata.d.ts +99 -0
  57. package/dist/input/metadata.js +99 -0
  58. package/dist/input/stdin-input.d.ts +26 -0
  59. package/dist/input/stdin-input.js +125 -0
  60. package/dist/input/utf8.d.ts +19 -0
  61. package/dist/input/utf8.js +36 -0
  62. package/dist/input/validation-mapper.d.ts +27 -0
  63. package/dist/input/validation-mapper.js +144 -0
  64. package/dist/invocation/index.d.ts +2 -0
  65. package/dist/invocation/index.js +2 -0
  66. package/dist/invocation/policy.d.ts +157 -0
  67. package/dist/invocation/policy.js +231 -0
  68. package/dist/invocation/types.d.ts +172 -0
  69. package/dist/invocation/types.js +97 -0
  70. package/dist/ipc/diagnostic.d.ts +0 -12
  71. package/dist/ipc/diagnostic.js +0 -21
  72. package/dist/ipc/host.d.ts +4 -4
  73. package/dist/ipc/host.js +44 -47
  74. package/dist/ipc/index.d.ts +1 -1
  75. package/dist/ipc/index.js +1 -1
  76. package/dist/ipc/service.d.ts +40 -0
  77. package/dist/ipc/service.js +349 -0
  78. package/dist/ipc/types.d.ts +21 -15
  79. package/dist/json/index.d.ts +1 -0
  80. package/dist/json/index.js +1 -0
  81. package/dist/json/value-validator.d.ts +88 -0
  82. package/dist/json/value-validator.js +330 -0
  83. package/dist/node/index.d.ts +1 -0
  84. package/dist/node/index.js +1 -0
  85. package/dist/node/module-loader.d.ts +63 -0
  86. package/dist/node/module-loader.js +112 -0
  87. package/dist/package/index.d.ts +23 -0
  88. package/dist/package/index.js +18 -0
  89. package/dist/{app/app.d.ts → package/runtime.d.ts} +41 -20
  90. package/dist/{app/app.js → package/runtime.js} +140 -95
  91. package/dist/{app → package}/static-index.js +58 -41
  92. package/dist/package/storage-view.d.ts +13 -0
  93. package/dist/package/storage-view.js +22 -0
  94. package/dist/{app → package}/types.d.ts +76 -57
  95. package/dist/package/types.js +22 -0
  96. package/dist/platform/index.d.ts +1 -1
  97. package/dist/platform/index.js +1 -1
  98. package/dist/platform/node.d.ts +48 -0
  99. package/dist/platform/node.js +96 -0
  100. package/dist/platform/types.d.ts +0 -9
  101. package/dist/process/control-arbiter.d.ts +2 -1
  102. package/dist/process/control-arbiter.js +1 -2
  103. package/dist/process/driver.d.ts +9 -8
  104. package/dist/process/driver.js +11 -22
  105. package/dist/process/index.d.ts +2 -0
  106. package/dist/process/index.js +2 -0
  107. package/dist/process/managed-record.d.ts +2 -2
  108. package/dist/process/process-driver.d.ts +109 -0
  109. package/dist/process/process-driver.js +650 -0
  110. package/dist/process/process-executor.d.ts +47 -0
  111. package/dist/process/process-executor.js +296 -0
  112. package/dist/process/process-manager.js +48 -26
  113. package/dist/process/run-executor.js +12 -12
  114. package/dist/profile/client-actions.js +2 -2
  115. package/dist/profile/client-health.js +3 -2
  116. package/dist/profile/client-query.js +5 -6
  117. package/dist/profile/client-transport.d.ts +19 -17
  118. package/dist/profile/client-transport.js +48 -80
  119. package/dist/profile/client.d.ts +2 -2
  120. package/dist/profile/client.js +1 -1
  121. package/dist/profile/index.d.ts +1 -0
  122. package/dist/profile/index.js +1 -0
  123. package/dist/profile/manager.d.ts +5 -0
  124. package/dist/profile/manager.js +41 -6
  125. package/dist/project/closure.js +2 -2
  126. package/dist/project/index.d.ts +8 -1
  127. package/dist/project/index.js +10 -1
  128. package/dist/project/init.js +2 -2
  129. package/dist/project/loader.d.ts +1 -1
  130. package/dist/project/loader.js +26 -29
  131. package/dist/project/transactions.js +5 -10
  132. package/dist/registry/index.d.ts +3 -1
  133. package/dist/registry/index.js +2 -1
  134. package/dist/registry/lock.d.ts +28 -7
  135. package/dist/registry/lock.js +136 -16
  136. package/dist/registry/registry.d.ts +1 -5
  137. package/dist/registry/registry.js +1 -53
  138. package/dist/registry/scan.d.ts +0 -15
  139. package/dist/registry/scan.js +0 -59
  140. package/dist/registry/store.d.ts +42 -0
  141. package/dist/registry/store.js +43 -0
  142. package/dist/registry/types.d.ts +0 -24
  143. package/dist/runtime/action-collection.d.ts +1 -1
  144. package/dist/runtime/action-registry.d.ts +0 -4
  145. package/dist/runtime/action-registry.js +0 -7
  146. package/dist/runtime/context.js +5 -11
  147. package/dist/runtime/events.js +3 -6
  148. package/dist/runtime/identity.d.ts +22 -0
  149. package/dist/runtime/identity.js +10 -0
  150. package/dist/runtime/index.d.ts +1 -2
  151. package/dist/runtime/index.js +1 -2
  152. package/dist/runtime/module-loader.d.ts +2 -34
  153. package/dist/runtime/module-loader.js +1 -16
  154. package/dist/runtime/process.d.ts +6 -24
  155. package/dist/runtime/process.js +1 -118
  156. package/dist/runtime/run-persistence.js +2 -5
  157. package/dist/runtime/runner.d.ts +33 -103
  158. package/dist/runtime/runner.js +142 -404
  159. package/dist/runtime/standalone.d.ts +37 -9
  160. package/dist/runtime/standalone.js +195 -136
  161. package/dist/schema/index.d.ts +1 -0
  162. package/dist/schema/index.js +1 -0
  163. package/dist/schema/validator.d.ts +10 -0
  164. package/dist/schema/validator.js +48 -54
  165. package/dist/server/body.d.ts +16 -8
  166. package/dist/server/body.js +33 -16
  167. package/dist/server/dispatcher.d.ts +6 -10
  168. package/dist/server/dispatcher.js +24 -32
  169. package/dist/server/http-server.d.ts +87 -0
  170. package/dist/server/http-server.js +304 -0
  171. package/dist/server/index.d.ts +11 -0
  172. package/dist/server/index.js +10 -0
  173. package/dist/server/mcp-endpoint.d.ts +61 -0
  174. package/dist/server/mcp-endpoint.js +118 -0
  175. package/dist/server/routes/actions.js +34 -28
  176. package/dist/server/routes/common.d.ts +10 -14
  177. package/dist/server/routes/common.js +32 -56
  178. package/dist/server/routes/config.js +54 -64
  179. package/dist/server/routes/doctor.d.ts +1 -1
  180. package/dist/server/routes/doctor.js +10 -18
  181. package/dist/server/routes/health.d.ts +1 -1
  182. package/dist/server/routes/health.js +2 -3
  183. package/dist/server/routes/info.d.ts +2 -2
  184. package/dist/server/routes/info.js +12 -13
  185. package/dist/server/routes/playbooks.js +16 -29
  186. package/dist/server/routes/runs.js +95 -117
  187. package/dist/server/routes/state.js +63 -62
  188. package/dist/server/server.d.ts +2 -2
  189. package/dist/server/server.js +43 -227
  190. package/dist/server/types.d.ts +8 -19
  191. package/dist/service/factory.d.ts +12 -0
  192. package/dist/service/factory.js +85 -0
  193. package/dist/{target → service}/index.d.ts +1 -1
  194. package/dist/{target → service}/index.js +1 -1
  195. package/dist/service/local.d.ts +27 -0
  196. package/dist/service/local.js +138 -0
  197. package/dist/{target → service}/remote-errors.d.ts +3 -6
  198. package/dist/service/remote-errors.js +38 -0
  199. package/dist/{target → service}/remote-polling.d.ts +2 -2
  200. package/dist/{target → service}/remote-polling.js +8 -8
  201. package/dist/service/remote.d.ts +34 -0
  202. package/dist/service/remote.js +634 -0
  203. package/dist/service/sse-stream.d.ts +24 -0
  204. package/dist/service/sse-stream.js +139 -0
  205. package/dist/service/types.d.ts +272 -0
  206. package/dist/service/types.js +32 -0
  207. package/dist/storage/data-dir-lock.js +10 -5
  208. package/dist/storage/driver.d.ts +2 -1
  209. package/dist/storage/driver.js +3 -52
  210. package/dist/storage/index.d.ts +1 -0
  211. package/dist/storage/index.js +1 -0
  212. package/dist/storage/lock-core.d.ts +2 -6
  213. package/dist/storage/lock-core.js +4 -17
  214. package/dist/storage/params.d.ts +2 -8
  215. package/dist/storage/params.js +2 -8
  216. package/dist/storage/sqlite-driver.d.ts +40 -0
  217. package/dist/storage/sqlite-driver.js +116 -0
  218. package/dist/storage/sqlite.js +21 -6
  219. package/dist/storage/types.d.ts +4 -4
  220. package/dist/storage/types.js +2 -2
  221. package/dist/utils/index.d.ts +53 -0
  222. package/dist/utils/index.js +99 -4
  223. package/dist/utils/process.d.ts +10 -0
  224. package/dist/utils/process.js +21 -0
  225. package/dist/version.d.ts +1 -1
  226. package/dist/version.js +1 -1
  227. package/package.json +41 -3
  228. package/dist/app/index.d.ts +0 -2
  229. package/dist/app/index.js +0 -2
  230. package/dist/app/types.js +0 -1
  231. package/dist/catalog/action-index.d.ts +0 -9
  232. package/dist/catalog/action-index.js +0 -95
  233. package/dist/catalog/action-resolver.d.ts +0 -20
  234. package/dist/catalog/action-resolver.js +0 -95
  235. package/dist/catalog/location-registry.d.ts +0 -15
  236. package/dist/catalog/location-registry.js +0 -138
  237. package/dist/catalog/package-catalog.d.ts +0 -7
  238. package/dist/catalog/package-catalog.js +0 -77
  239. package/dist/catalog/types.d.ts +0 -68
  240. package/dist/catalog/types.js +0 -1
  241. package/dist/export/index.d.ts +0 -1
  242. package/dist/export/index.js +0 -1
  243. package/dist/export/templates.d.ts +0 -71
  244. package/dist/export/templates.js +0 -623
  245. package/dist/ipc/target.d.ts +0 -74
  246. package/dist/ipc/target.js +0 -365
  247. package/dist/platform/default.d.ts +0 -38
  248. package/dist/platform/default.js +0 -78
  249. package/dist/project/resolver.d.ts +0 -116
  250. package/dist/project/resolver.js +0 -376
  251. package/dist/registry/resolve.d.ts +0 -62
  252. package/dist/registry/resolve.js +0 -195
  253. package/dist/runtime/execution-manager.d.ts +0 -48
  254. package/dist/runtime/execution-manager.js +0 -66
  255. package/dist/runtime/package-runner-factory.d.ts +0 -82
  256. package/dist/runtime/package-runner-factory.js +0 -186
  257. package/dist/target/local.d.ts +0 -53
  258. package/dist/target/local.js +0 -349
  259. package/dist/target/remote-errors.js +0 -51
  260. package/dist/target/remote.d.ts +0 -86
  261. package/dist/target/remote.js +0 -818
  262. package/dist/target/target.d.ts +0 -6
  263. package/dist/target/target.js +0 -37
  264. package/dist/target/types.d.ts +0 -261
  265. package/dist/target/types.js +0 -35
  266. /package/dist/{app → package}/static-index.d.ts +0 -0
  267. /package/dist/{target → service}/sse-parser.d.ts +0 -0
  268. /package/dist/{target → service}/sse-parser.js +0 -0
@@ -1,18 +1,26 @@
1
1
  /**
2
2
  * 远端客户端传输层。
3
3
  *
4
- * 职责单一聚焦:鉴权头拼装、明文传输安全校验、insecure dispatcher 注入
5
- * 与 v2 -> v1 协议回退,作为全部远端端点函数的传输样板单一事实源。
4
+ * 职责单一聚焦:鉴权头拼装、明文传输安全校验与 insecure dispatcher 注入,
5
+ * 作为全部远端端点函数的传输样板单一事实源。
6
6
  */
7
7
  /**
8
- * 校验在携带认证 Token 时传输层协议是否安全。
9
- * 若请求携带认证 Token 且目标为非本地回环的明文 http://,默认报错拒绝。
10
- * 可通过 allowInsecureHttp 选项、insecure 选项、环境变量或命令行参数豁免。
8
+ * 传输层安全豁免控制选项。
11
9
  */
12
- export declare function assertSecureTransport(serverUrl: string, token?: string, allowInsecureHttpOrOptions?: boolean | {
10
+ export interface SecureTransportOptions {
11
+ /** 是否允许向非回环地址发送明文 HTTP 请求 */
13
12
  allowInsecureHttp?: boolean;
13
+ /** 是否跳过 TLS 证书合法性校验 */
14
14
  insecure?: boolean;
15
- }, insecureArg?: boolean): void;
15
+ }
16
+ /**
17
+ * 校验在携带认证 Token 时传输层协议与目标地址是否安全。
18
+ * 若请求携带认证 Token 且目标为非本地回环的明文 http://,默认报错拒绝。
19
+ * 可通过 allowInsecureHttp 选项、insecure 选项、环境变量或命令行参数豁免明文限制。
20
+ * 目标 URL 无法解析时无论豁免与否均 fail-closed 抛 INVALID_ARGUMENT,
21
+ * 杜绝携凭据请求发往不可控的畸形地址。
22
+ */
23
+ export declare function assertSecureTransport(serverUrl: string, token?: string, options?: SecureTransportOptions): void;
16
24
  /**
17
25
  * 构造携带鉴权信息的标准请求头。
18
26
  */
@@ -43,19 +51,13 @@ export interface RemoteFetchInit {
43
51
  * 构建绑定传输上下文(Token、安全选项与调度器)的远端请求函数。
44
52
  *
45
53
  * 单一事实源收敛所有 fetchRemoteXxx 端点的传输样板:
46
- * 调用方仅需声明 path 与查询参数,鉴权头拼装、明文传输校验、
47
- * insecure dispatcher 注入与 v2 -> v1 协议回退均在此统一处理。
54
+ * 调用方仅需声明 path 与查询参数,鉴权头拼装、明文传输校验与
55
+ * insecure dispatcher 注入均在此统一处理。
48
56
  */
49
57
  export declare function createRemoteFetch(serverUrl: string, token?: string, options?: RemoteClientRequestOptions): (path: string, init?: RemoteFetchInit) => Promise<Response>;
50
58
  /**
51
- * 携带协议回退的远端请求单一入口:先打 /api/v2/ 路由,404 时改打 /api/v1/ 路由。
52
- *
53
- * v1 回退请求若网络失败则沿用 v2 响应,保留原始状态与错误体供上层透传。
59
+ * 远端请求单一入口:执行目标协议路由请求。
54
60
  */
55
- export declare function fetchWithProtocolFallback(base: string, path: string, init: RequestInit & {
61
+ export declare function fetchRemoteRoute(base: string, path: string, init: RequestInit & {
56
62
  dispatcher?: any;
57
63
  }): Promise<Response>;
58
- /**
59
- * 列出指定路由的全部协议版本候选 URL(按优先级排序,含原始路由自身)。
60
- */
61
- export declare function listProtocolRouteCandidates(base: string, path: string): string[];
@@ -1,31 +1,20 @@
1
1
  import { isLoopbackHost } from "../utils/net.js";
2
2
  import { normalizeServerUrl } from "./manager.js";
3
3
  import { getInsecureDispatcher } from "../server/dispatcher.js";
4
+ import { ActionDockError, INSECURE_TRANSPORT, INVALID_ARGUMENT } from "../errors.js";
4
5
  /**
5
- * 远端客户端传输层。
6
- *
7
- * 职责单一聚焦:鉴权头拼装、明文传输安全校验、insecure dispatcher 注入
8
- * 与 v2 -> v1 协议回退,作为全部远端端点函数的传输样板单一事实源。
9
- */
10
- /**
11
- * 校验在携带认证 Token 时传输层协议是否安全。
6
+ * 校验在携带认证 Token 时传输层协议与目标地址是否安全。
12
7
  * 若请求携带认证 Token 且目标为非本地回环的明文 http://,默认报错拒绝。
13
- * 可通过 allowInsecureHttp 选项、insecure 选项、环境变量或命令行参数豁免。
8
+ * 可通过 allowInsecureHttp 选项、insecure 选项、环境变量或命令行参数豁免明文限制。
9
+ * 目标 URL 无法解析时无论豁免与否均 fail-closed 抛 INVALID_ARGUMENT,
10
+ * 杜绝携凭据请求发往不可控的畸形地址。
14
11
  */
15
- export function assertSecureTransport(serverUrl, token, allowInsecureHttpOrOptions, insecureArg) {
12
+ export function assertSecureTransport(serverUrl, token, options) {
16
13
  if (!token || !token.trim()) {
17
14
  return;
18
15
  }
19
- let allowInsecureHttp = false;
20
- let insecure = false;
21
- if (typeof allowInsecureHttpOrOptions === "object" && allowInsecureHttpOrOptions !== null) {
22
- allowInsecureHttp = Boolean(allowInsecureHttpOrOptions.allowInsecureHttp);
23
- insecure = Boolean(allowInsecureHttpOrOptions.insecure);
24
- }
25
- else {
26
- allowInsecureHttp = Boolean(allowInsecureHttpOrOptions);
27
- insecure = Boolean(insecureArg);
28
- }
16
+ const allowInsecureHttp = Boolean(options?.allowInsecureHttp);
17
+ const insecure = Boolean(options?.insecure);
29
18
  const allow = allowInsecureHttp ||
30
19
  insecure ||
31
20
  (typeof process !== "undefined" &&
@@ -33,25 +22,44 @@ export function assertSecureTransport(serverUrl, token, allowInsecureHttpOrOptio
33
22
  process.env?.ACTIONDOCK_ALLOW_INSECURE_HTTP === "1" ||
34
23
  process.env?.ACTIONDOCK_INSECURE === "true" ||
35
24
  process.env?.ACTIONDOCK_INSECURE === "1"));
36
- if (allow) {
37
- return;
38
- }
39
25
  const base = normalizeServerUrl(serverUrl);
40
- if (base.startsWith("http://")) {
26
+ // 目标地址可解析性前置校验(fail-closed):携带 token 时若目标 URL 无法解析,
27
+ // 严禁继续放行——后续请求会携凭据发往不可控的畸形目标。错误信息仅携带
28
+ // 脱敏地址与解析原因。allow 豁免仅作用于明文传输策略,不豁免地址合法性。
29
+ if (base.startsWith("http://") || base.startsWith("https://")) {
30
+ let parsed;
41
31
  try {
42
- const parsed = new URL(base);
43
- if (!isLoopbackHost(parsed.hostname)) {
44
- const err = new Error(`Insecure HTTP connection with authentication token to non-loopback host '${parsed.hostname}' is prohibited. Use HTTPS or pass --allow-insecure-http to override.`);
45
- err.code = "INSECURE_TRANSPORT";
46
- throw err;
47
- }
32
+ parsed = new URL(base);
48
33
  }
49
- catch (e) {
50
- if (e.code === "INSECURE_TRANSPORT") {
51
- throw e;
52
- }
34
+ catch (err) {
35
+ const reason = err instanceof Error ? err.message : String(err);
36
+ throw new ActionDockError(INVALID_ARGUMENT, `Invalid server URL '${redactUrl(base)}' with authentication token: URL parse failed (${reason}). Fix the profile server URL before sending credentials.`);
37
+ }
38
+ // 明文传输策略:仅非回环明文 http 需要豁免,https 不受限制
39
+ if (!allow && base.startsWith("http://") && !isLoopbackHost(parsed.hostname)) {
40
+ throw new ActionDockError(INSECURE_TRANSPORT, `Insecure HTTP connection with authentication token to non-loopback host '${parsed.hostname}' is prohibited. Use HTTPS or pass --allow-insecure-http to override.`);
53
41
  }
54
42
  }
43
+ // 非 http/https 协议形态(如 local):交给后续请求层处理,此处无从校验
44
+ }
45
+ /**
46
+ * 脱敏 URL:移除 userinfo 凭据与查询串后再返回;仅保留协议、主机与路径。
47
+ */
48
+ function redactUrl(url) {
49
+ let candidate = url;
50
+ try {
51
+ const u = new URL(candidate);
52
+ u.username = "";
53
+ u.password = "";
54
+ u.search = "";
55
+ return u.toString();
56
+ }
57
+ catch {
58
+ // 解析失败的畸形地址:以正则剥离 userinfo 形态后截断长度,
59
+ // 避免原始串携带敏感片段外泄到错误信息与日志
60
+ candidate = candidate.replace(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)[^\/@]+@/, "$1");
61
+ return candidate.length > 128 ? `${candidate.slice(0, 128)}...` : candidate;
62
+ }
55
63
  }
56
64
  /**
57
65
  * 构造携带鉴权信息的标准请求头。
@@ -69,8 +77,8 @@ export function buildHeaders(token) {
69
77
  * 构建绑定传输上下文(Token、安全选项与调度器)的远端请求函数。
70
78
  *
71
79
  * 单一事实源收敛所有 fetchRemoteXxx 端点的传输样板:
72
- * 调用方仅需声明 path 与查询参数,鉴权头拼装、明文传输校验、
73
- * insecure dispatcher 注入与 v2 -> v1 协议回退均在此统一处理。
80
+ * 调用方仅需声明 path 与查询参数,鉴权头拼装、明文传输校验与
81
+ * insecure dispatcher 注入均在此统一处理。
74
82
  */
75
83
  export function createRemoteFetch(serverUrl, token, options) {
76
84
  assertSecureTransport(serverUrl, token, {
@@ -101,55 +109,15 @@ export function createRemoteFetch(serverUrl, token, options) {
101
109
  }
102
110
  else if (options?.insecure) {
103
111
  fetchInit.dispatcher = getInsecureDispatcher();
112
+ fetchInit.tls = { rejectUnauthorized: false };
104
113
  }
105
- return fetchWithProtocolFallback(base, path, fetchInit);
114
+ return fetchRemoteRoute(base, path, fetchInit);
106
115
  };
107
116
  }
108
- /** v2 优先、v1 兼容回退的协议版本优先级列表。 */
109
- const PROTOCOL_PREFERENCE = ["v2", "v1"];
110
117
  /**
111
- * 判定响应是否应当触发下一优先级协议重试。
112
- * 仅 404(路由不存在)回退;其余状态(如 401、403、500)原样透传。
118
+ * 远端请求单一入口:执行目标协议路由请求。
113
119
  */
114
- function shouldFallbackToNextProtocol(res) {
115
- return res.status === 404;
116
- }
117
- /**
118
- * 携带协议回退的远端请求单一入口:先打 /api/v2/ 路由,404 时改打 /api/v1/ 路由。
119
- *
120
- * v1 回退请求若网络失败则沿用 v2 响应,保留原始状态与错误体供上层透传。
121
- */
122
- export async function fetchWithProtocolFallback(base, path, init) {
120
+ export async function fetchRemoteRoute(base, path, init) {
123
121
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
124
- const isVersioned = /^\/api\/v\d+\//.test(normalizedPath);
125
- let res = await fetch(`${base}${normalizedPath}`, init);
126
- if (!isVersioned || !shouldFallbackToNextProtocol(res)) {
127
- return res;
128
- }
129
- const candidates = listProtocolRouteCandidates(base, normalizedPath);
130
- // 首个候选即当前已返回 404 的路由,从次优先级继续尝试
131
- for (let i = 1; i < candidates.length; i++) {
132
- try {
133
- const fallbackRes = await fetch(candidates[i], init);
134
- if (!shouldFallbackToNextProtocol(fallbackRes)) {
135
- return fallbackRes;
136
- }
137
- }
138
- catch {
139
- // 回退请求网络异常时保持既有 v2 响应,由上层统一处理
140
- }
141
- }
142
- return res;
143
- }
144
- /**
145
- * 列出指定路由的全部协议版本候选 URL(按优先级排序,含原始路由自身)。
146
- */
147
- export function listProtocolRouteCandidates(base, path) {
148
- const normalizedPath = path.startsWith("/") ? path : `/${path}`;
149
- const versionMatch = normalizedPath.match(/^\/api\/(v\d+)\/(.*)$/);
150
- if (!versionMatch) {
151
- return [`${base}${normalizedPath}`];
152
- }
153
- const rest = versionMatch[2];
154
- return PROTOCOL_PREFERENCE.map((version) => `${base}/api/${version}/${rest}`);
122
+ return fetch(`${base}${normalizedPath}`, init);
155
123
  }
@@ -15,8 +15,8 @@
15
15
  * - client-state:状态存储管理
16
16
  * - client-config:配置管理
17
17
  */
18
- export { assertSecureTransport, createRemoteFetch, fetchWithProtocolFallback, listProtocolRouteCandidates, } from "./client-transport.js";
19
- export type { RemoteClientRequestOptions, RemoteFetchInit, } from "./client-transport.js";
18
+ export { assertSecureTransport, createRemoteFetch, fetchRemoteRoute, } from "./client-transport.js";
19
+ export type { RemoteClientRequestOptions, RemoteFetchInit, SecureTransportOptions, } from "./client-transport.js";
20
20
  export { checkRemoteHealth } from "./client-health.js";
21
21
  export { executeRemoteAction, fetchRemoteActionShow, fetchRemoteActions, fetchRemoteDoctor, fetchRemoteInfo, } from "./client-actions.js";
22
22
  export type { RemoteExecuteOptions, RemoteExecutionResult } from "./client-actions.js";
@@ -16,7 +16,7 @@
16
16
  * - client-config:配置管理
17
17
  */
18
18
  // 传输层与通用类型
19
- export { assertSecureTransport, createRemoteFetch, fetchWithProtocolFallback, listProtocolRouteCandidates, } from "./client-transport.js";
19
+ export { assertSecureTransport, createRemoteFetch, fetchRemoteRoute, } from "./client-transport.js";
20
20
  // 健康检查
21
21
  export { checkRemoteHealth } from "./client-health.js";
22
22
  // 执行与 Action 查询
@@ -1,3 +1,4 @@
1
1
  export * from "./types.js";
2
2
  export * from "./manager.js";
3
3
  export * from "./client.js";
4
+ export { toSnakeUpperCase, resolveEnvValue } from "../runtime/env.js";
@@ -1,3 +1,4 @@
1
1
  export * from "./types.js";
2
2
  export * from "./manager.js";
3
3
  export * from "./client.js";
4
+ export { toSnakeUpperCase, resolveEnvValue } from "../runtime/env.js";
@@ -13,6 +13,11 @@ export declare function normalizeServerUrl(url: string): string;
13
13
  export declare function getProfilesFilePath(customHome?: string): string;
14
14
  /**
15
15
  * 加载并读取 profiles.json 配置文件。
16
+ *
17
+ * 损坏语义与注册表(registry.ts)对齐:JSON 损坏或结构非法时将原文件原子留档为
18
+ * `profiles.json.corrupt` 并抛出带恢复指引的异常,绝不静默返回默认配置——否则
19
+ * 后续任意一次 saveProfiles 会用默认配置覆写原文件,导致用户全部远端环境配置
20
+ * 永久丢失。文件不存在时仍走默认初始化。
16
21
  */
17
22
  export declare function loadProfiles(customHome?: string): ProfilesConfig;
18
23
  /**
@@ -1,8 +1,9 @@
1
- import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { toSnakeUpperCase } from "../runtime/env.js";
4
4
  import { isLoopbackHost } from "../utils/net.js";
5
5
  import { getActionDockHome } from "../utils/index.js";
6
+ import { ActionDockError, INVALID_ARGUMENT } from "../errors.js";
6
7
  const PROFILE_NAME_REGEX = /^[a-zA-Z0-9_\-\.]+$/;
7
8
  /**
8
9
  * 默认初始 Profiles 配置(预设 local 本地环境)。
@@ -56,22 +57,56 @@ export function getProfilesFilePath(customHome) {
56
57
  }
57
58
  /**
58
59
  * 加载并读取 profiles.json 配置文件。
60
+ *
61
+ * 损坏语义与注册表(registry.ts)对齐:JSON 损坏或结构非法时将原文件原子留档为
62
+ * `profiles.json.corrupt` 并抛出带恢复指引的异常,绝不静默返回默认配置——否则
63
+ * 后续任意一次 saveProfiles 会用默认配置覆写原文件,导致用户全部远端环境配置
64
+ * 永久丢失。文件不存在时仍走默认初始化。
59
65
  */
60
66
  export function loadProfiles(customHome) {
61
67
  const filePath = getProfilesFilePath(customHome);
62
68
  if (!existsSync(filePath)) {
63
69
  return structuredClone(DEFAULT_PROFILES_CONFIG);
64
70
  }
71
+ let raw;
65
72
  try {
66
- const raw = readFileSync(filePath, "utf-8");
67
- const parsed = JSON.parse(raw);
68
- if (!parsed || typeof parsed !== "object" || !parsed.profiles) {
73
+ raw = readFileSync(filePath, "utf-8");
74
+ }
75
+ catch (err) {
76
+ if (err.code === "ENOENT") {
69
77
  return structuredClone(DEFAULT_PROFILES_CONFIG);
70
78
  }
71
- return parsed;
79
+ throw err;
80
+ }
81
+ let parsed;
82
+ try {
83
+ parsed = JSON.parse(raw);
84
+ }
85
+ catch (err) {
86
+ archiveCorruptProfiles(filePath);
87
+ throw new ActionDockError(INVALID_ARGUMENT, `Profiles file '${filePath}' is corrupted (invalid JSON: ${err.message}). ` +
88
+ `The original data has been preserved as '${filePath}.corrupt'. ` +
89
+ `Restore it manually or remove it to start with the default profiles.`);
90
+ }
91
+ if (!parsed || typeof parsed !== "object" || !parsed.profiles) {
92
+ archiveCorruptProfiles(filePath);
93
+ throw new ActionDockError(INVALID_ARGUMENT, `Profiles file '${filePath}' is corrupted (missing or invalid 'profiles' field). ` +
94
+ `The original data has been preserved as '${filePath}.corrupt'. ` +
95
+ `Restore it manually or remove it to start with the default profiles.`);
96
+ }
97
+ return parsed;
98
+ }
99
+ /**
100
+ * 将损坏的 profiles 文件原子留档为 `${filePath}.corrupt`,
101
+ * 后续 saveProfiles 的直接写入不会覆盖留档(文件名不同)。
102
+ */
103
+ function archiveCorruptProfiles(filePath) {
104
+ try {
105
+ renameSync(filePath, `${filePath}.corrupt`);
72
106
  }
73
107
  catch {
74
- return structuredClone(DEFAULT_PROFILES_CONFIG);
108
+ // 留档失败(如文件被其他进程先行移动)时仅放弃留档:错误仍会向上抛出,
109
+ // 不会出现静默清空
75
110
  }
76
111
  }
77
112
  /**
@@ -1,5 +1,5 @@
1
1
  import { resolve } from "node:path";
2
- import { ActionResolver } from "../catalog/action-resolver.js";
2
+ import { parseActionRef } from "../catalog/resolve-action.js";
3
3
  import { resolvePackageRoot } from "../registry/registry.js";
4
4
  import { loadManifest } from "./manifest.js";
5
5
  /**
@@ -52,7 +52,7 @@ export async function ensureDependencyClosure(roots, opts = {}) {
52
52
  continue;
53
53
  let parsed;
54
54
  try {
55
- parsed = ActionResolver.parseRef(rawRef);
55
+ parsed = parseActionRef(rawRef);
56
56
  }
57
57
  catch {
58
58
  warnings.push(`uses 声明 '${rawRef}' 未在注册表中解析到包`);
@@ -4,7 +4,14 @@ export * from "./init.js";
4
4
  export * from "./loader.js";
5
5
  export * from "./lockfile.js";
6
6
  export * from "./manifest.js";
7
- export * from "./resolver.js";
8
7
  export { SNAPSHOT_TRACKED_FILES, type TransactionFileRecord, type TransactionMetadata, type ProjectTransaction, isPidAlive, isProjectLockHeld, acquireProjectLock, runFrozenInstall, hasPendingTransactions, beginTransaction, recoverPendingTransactions, } from "./transactions.js";
9
8
  export * from "./types.js";
10
9
  export * from "./types-generator.js";
10
+ export { assertPathWithinRoot, isPathOutsideBoundary, getPackageSlug, parseDuration, traverseDirectory, type TraverseDirectoryEntry, type TraverseDirectoryOptions, } from "../utils/index.js";
11
+ export { isSecretConfigKey, maskSecretValue, sanitizeConfigDefinitions, } from "../storage/mask.js";
12
+ export { runDoctorChecks, } from "../doctor/doctor.js";
13
+ export { resolveActionInput, buildActionInputAdvice, formatActionDetail, buildActionDescribePayload, mapInputValidationFailure, stripBom, readStdinBounded, type ResolveActionInputOptions, type ReadStdinBoundedOptions, } from "../input/index.js";
14
+ export { validateActionInputValue } from "../json/value-validator.js";
15
+ export { parseJson } from "../input/input-resolver.js";
16
+ export { InputError, FlatInputError, } from "../input/flat-errors.js";
17
+ export { filterWithFallbackInfo, compileIntentRegex, matchIntent, type Extractor, } from "../filter/intent.js";
@@ -4,7 +4,16 @@ export * from "./init.js";
4
4
  export * from "./loader.js";
5
5
  export * from "./lockfile.js";
6
6
  export * from "./manifest.js";
7
- export * from "./resolver.js";
8
7
  export { SNAPSHOT_TRACKED_FILES, isPidAlive, isProjectLockHeld, acquireProjectLock, runFrozenInstall, hasPendingTransactions, beginTransaction, recoverPendingTransactions, } from "./transactions.js";
9
8
  export * from "./types.js";
10
9
  export * from "./types-generator.js";
10
+ export { assertPathWithinRoot, isPathOutsideBoundary, getPackageSlug, parseDuration, traverseDirectory, } from "../utils/index.js";
11
+ export { isSecretConfigKey, maskSecretValue, sanitizeConfigDefinitions, } from "../storage/mask.js";
12
+ export { runDoctorChecks, } from "../doctor/doctor.js";
13
+ // 动作入参解析、校验与格式化(含 BOM 剥离与标准输入有界读取的单一事实源转引)
14
+ export { resolveActionInput, buildActionInputAdvice, formatActionDetail, buildActionDescribePayload, mapInputValidationFailure, stripBom, readStdinBounded, } from "../input/index.js";
15
+ export { validateActionInputValue } from "../json/value-validator.js";
16
+ export { parseJson } from "../input/input-resolver.js";
17
+ export { InputError, FlatInputError, } from "../input/flat-errors.js";
18
+ // 意图过滤与查询
19
+ export { filterWithFallbackInfo, compileIntentRegex, matchIntent, } from "../filter/intent.js";
@@ -86,10 +86,10 @@ export function initProject(targetDir, options = {}) {
86
86
  node: ">=24.12.0",
87
87
  },
88
88
  dependencies: {
89
- "@actiondock/sdk": "^2.6.0",
89
+ "@actiondock/sdk": "^2.8.0",
90
90
  },
91
91
  devDependencies: {
92
- "@actiondock/testing": "^2.6.0",
92
+ "@actiondock/testing": "^2.8.0",
93
93
  "@types/node": "^22.13.0",
94
94
  "tsx": "^4.19.0",
95
95
  "typescript": "^5.7.0",
@@ -1,5 +1,5 @@
1
1
  import type { ActionDefinition } from "@actiondock/sdk";
2
- import { type ModuleLoader } from "../runtime/module-loader.js";
2
+ import { type ModuleLoader } from "../node/module-loader.js";
3
3
  import type { ActionDockManifest, PlaybookDefinition, PlaybookManifestEntry, ProjectConfig } from "./types.js";
4
4
  /**
5
5
  * 从指定目录开始向上逐级递归查找包含 `actiondock.json` 的项目根目录。
@@ -1,7 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
3
  import { basename, dirname, join, resolve } from "node:path";
4
- import { DefaultModuleLoader } from "../runtime/module-loader.js";
4
+ import { NodeModuleLoader, unwrapDefaultExport } from "../node/module-loader.js";
5
5
  import { ACTION_LOAD_FAILED, isMissingModuleError } from "../errors.js";
6
6
  import { loadManifest, ACTION_ID_REGEX, PLAYBOOK_ID_REGEX } from "./manifest.js";
7
7
  /**
@@ -36,7 +36,7 @@ export function findProjectRoot(cwd) {
36
36
  }
37
37
  return null;
38
38
  }
39
- import { PACKAGE_ID_REGEX, assertPathWithinRoot, assertWithinProjectRoot, } from "../utils/index.js";
39
+ import { PACKAGE_ID_REGEX, assertPathWithinRoot, assertWithinProjectRoot, traverseDirectory, } from "../utils/index.js";
40
40
  /**
41
41
  * 加载并校验指定目录下的 `actiondock.json` 配置文件。
42
42
  *
@@ -126,32 +126,29 @@ export function getInstallCommand(projectRoot) {
126
126
  return ["npm", "install"];
127
127
  }
128
128
  /**
129
- * 递归扫描指定目录下的特定后缀文件(自动排除测试文件 *.test.ts, *.spec.ts 和 *.d.ts)。
129
+ * 测试与类型声明文件后缀常量(与 builder 的 isIgnoredPath 保持同一语义口径)。
130
+ */
131
+ const TEST_FILE_SUFFIXES = [
132
+ ".test.ts",
133
+ ".test.js",
134
+ ".test.tsx",
135
+ ".test.jsx",
136
+ ".spec.ts",
137
+ ".spec.js",
138
+ ".spec.tsx",
139
+ ".spec.jsx",
140
+ ".d.ts",
141
+ ];
142
+ /**
143
+ * 递归扫描指定目录下的特定后缀文件(自动排除测试文件与类型声明文件)。
144
+ * 目录遍历统一复用 core utils 的 traverseDirectory 单一事实源,
145
+ * 自带软链接越界与循环防护。
130
146
  */
131
147
  function scanFiles(dir, extension) {
132
- if (!existsSync(dir))
133
- return [];
134
- const results = [];
135
- function walk(current) {
136
- const entries = readdirSync(current);
137
- for (const entry of entries) {
138
- const fullPath = join(current, entry);
139
- const stat = statSync(fullPath);
140
- if (stat.isDirectory()) {
141
- walk(fullPath);
142
- }
143
- else if (stat.isFile() && fullPath.endsWith(extension)) {
144
- // 排除测试与类型声明文件
145
- if (!fullPath.endsWith(".test.ts") &&
146
- !fullPath.endsWith(".spec.ts") &&
147
- !fullPath.endsWith(".d.ts")) {
148
- results.push(fullPath);
149
- }
150
- }
151
- }
152
- }
153
- walk(dir);
154
- return results;
148
+ return traverseDirectory(dir)
149
+ .filter((entry) => entry.relPath.endsWith(extension) &&
150
+ !TEST_FILE_SUFFIXES.some((suffix) => entry.relPath.endsWith(suffix)))
151
+ .map((entry) => entry.fullPath);
155
152
  }
156
153
  /**
157
154
  * 发现并检索项目 actions 目录下的所有 Action 源码文件(.ts)。
@@ -175,7 +172,7 @@ export function discoverActionFiles(projectRoot, actionsDir = "actions") {
175
172
  */
176
173
  export async function loadActions(projectRoot, _actionsDir = "actions", options = {}) {
177
174
  const actions = new Map();
178
- const loader = options.loader || new DefaultModuleLoader();
175
+ const loader = options.loader || new NodeModuleLoader();
179
176
  let manifest = null;
180
177
  try {
181
178
  manifest = loadManifest(projectRoot);
@@ -222,7 +219,7 @@ export async function loadActions(projectRoot, _actionsDir = "actions", options
222
219
  };
223
220
  throw error;
224
221
  }
225
- const exported = imported?.default ?? imported?.action ?? imported;
222
+ const exported = unwrapDefaultExport(imported);
226
223
  let runFn;
227
224
  if (typeof exported === "function") {
228
225
  runFn = exported;
@@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
3
3
  import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync, } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { acquireDirectoryLock, checkReclaimGuard, cleanStaleQuarantines, isProcessAlive, parseQuarantineTimestamp as parseQuarantineTimestampCore, readLockWithGracePeriod, safeReleaseLock as coreSafeReleaseLock, safeRemoveStaleReclaimGuard, safeRollbackLock, } from "../storage/lock-core.js";
6
+ import { ActionDockError, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED } from "../errors.js";
6
7
  import { LOCKFILE_NAME } from "./lockfile.js";
7
8
  import { MANIFEST_FILE_NAME } from "./manifest.js";
8
9
  export const SNAPSHOT_TRACKED_FILES = [
@@ -138,9 +139,7 @@ export function acquireProjectLock(projectRoot, options = {}) {
138
139
  lockToken,
139
140
  acquireTimeoutMs: options.acquireTimeoutMs,
140
141
  createLockError(message) {
141
- const busyErr = new Error(message);
142
- busyErr.code = "PROJECT_BUSY";
143
- return busyErr;
142
+ return new ActionDockError(PROJECT_BUSY, message);
144
143
  },
145
144
  messages: {
146
145
  timeoutWaitingReclaimGuard: (holderPid) => `PROJECT_BUSY: Timeout waiting for active reclaim guard (PID ${holderPid ?? "unknown"}) on project '${projectRoot}' after ${timeoutMs}ms`,
@@ -154,9 +153,7 @@ export function acquireProjectLock(projectRoot, options = {}) {
154
153
  assertStaleHolderReclaimable(info) {
155
154
  const holderPid = info.pid;
156
155
  if (isProcessAlive(holderPid)) {
157
- const busyErr = new Error(`PROJECT_BUSY: Project modification lock is held by PID ${holderPid}. Another command is running in ${projectRoot}.`);
158
- busyErr.code = "PROJECT_BUSY";
159
- throw busyErr;
156
+ throw new ActionDockError(PROJECT_BUSY, `PROJECT_BUSY: Project modification lock is held by PID ${holderPid}. Another command is running in ${projectRoot}.`);
160
157
  }
161
158
  },
162
159
  onAcquired: () => true,
@@ -223,9 +220,7 @@ export function runFrozenInstall(projectRoot) {
223
220
  });
224
221
  if (proc.status !== 0) {
225
222
  const errorMsg = proc.stderr?.toString() || proc.stdout?.toString() || "Unknown error";
226
- const err = new Error(`PROJECT_RECOVERY_REQUIRED: Frozen install failed during recovery in ${projectRoot}: ${errorMsg}`);
227
- err.code = "PROJECT_RECOVERY_REQUIRED";
228
- throw err;
223
+ throw new ActionDockError(PROJECT_RECOVERY_REQUIRED, `PROJECT_RECOVERY_REQUIRED: Frozen install failed during recovery in ${projectRoot}: ${errorMsg}`);
229
224
  }
230
225
  }
231
226
  /**
@@ -366,7 +361,7 @@ export async function recoverPendingTransactions(projectRoot, options) {
366
361
  releaseLock = acquireProjectLock(projectRoot);
367
362
  }
368
363
  catch (err) {
369
- if (err?.code === "PROJECT_BUSY" || err?.message?.includes("PROJECT_BUSY")) {
364
+ if (err?.code === "PROJECT_BUSY") {
370
365
  // 仅当项目锁被其他活跃进程占用时,安全退出并返回空数组
371
366
  return [];
372
367
  }
@@ -1,5 +1,7 @@
1
1
  export * from "./lock.js";
2
+ export type { RegistryLockOptions } from "./lock.js";
2
3
  export * from "./registry.js";
3
- export * from "./resolve.js";
4
4
  export * from "./scan.js";
5
+ export * from "./store.js";
5
6
  export * from "./types.js";
7
+ export { getActionDockHome, getPackageSlug, } from "../utils/index.js";
@@ -1,5 +1,6 @@
1
1
  export * from "./lock.js";
2
2
  export * from "./registry.js";
3
- export * from "./resolve.js";
4
3
  export * from "./scan.js";
4
+ export * from "./store.js";
5
5
  export * from "./types.js";
6
+ export { getActionDockHome, getPackageSlug, } from "../utils/index.js";
@@ -1,5 +1,6 @@
1
1
  /**
2
- * 锁目录创建后超过该时长仍未释放,视为持有进程异常退出的残留锁。
2
+ * 锁目录最近一次心跳(创建或续期)后超过该时长且持有进程已死亡,
3
+ * 才视为持有进程异常退出的残留锁。
3
4
  */
4
5
  export declare const REGISTRY_LOCK_STALE_MS = 10000;
5
6
  /**
@@ -11,17 +12,37 @@ export declare const REGISTRY_LOCK_ACQUIRE_TIMEOUT_MS = 15000;
11
12
  * 两次获取尝试之间的退避间隔,休眠让出事件循环,避免忙等阻塞。
12
13
  */
13
14
  export declare const REGISTRY_LOCK_RETRY_DELAY_MS = 25;
15
+ /**
16
+ * 持锁期间的心跳续期间隔。持有者定期 touch 锁目录刷新 mtime,
17
+ * 使长耗时持锁操作(如 linkPackage 的深度扫描)不会仅因耗时超过
18
+ * stale 阈值而被等待方误判为残留锁抢占。
19
+ */
20
+ export declare const REGISTRY_LOCK_HEARTBEAT_MS = 3000;
21
+ /**
22
+ * 锁配置选项。
23
+ */
24
+ export interface RegistryLockOptions {
25
+ staleMs?: number;
26
+ acquireTimeoutMs?: number;
27
+ retryDelayMs?: number;
28
+ heartbeatMs?: number;
29
+ }
14
30
  /**
15
31
  * 异步注册表锁(防止多进程并发读写导致 registry.json 损坏)。
16
32
  *
17
- * 获取方式:fs/promises mkdir 的原子性保证同一时刻仅一个进程能创建锁目录。
18
- * 冲突处理:EEXIST 时 stat 检查锁目录年龄,超过 stale 阈值则先原子 rename 到
19
- * 隔离名再删除——rename 仅有一个进程能成功,失败方回到等待重试,消除
20
- * 「stat 检查后删除再重建」的竞态窗口;未过期的锁通过 sleep 让出事件循环后重试。
33
+ * 获取方式:fs/promises mkdir 的原子性保证同一时刻仅一个进程能创建锁目录,
34
+ * 创建成功后立即写入含 pid 与随机 token 的 metadata.json。
35
+ * 冲突处理:EEXIST 时按保守语义判定残留(见 isStaleLock):mtime 超龄且
36
+ * pid 已死才抢占——抢占仍通过原子 rename 到隔离名实现,rename 仅有一个
37
+ * 进程能成功,失败方回到等待重试,消除「检查后删除再重建」的竞态窗口;
38
+ * 未过期的锁通过 sleep 让出事件循环后重试。
39
+ * 心跳续期:持锁期间以固定间隔 touch 锁目录刷新 mtime,使 linkPackage 等
40
+ * 长耗时持锁操作不会仅因耗时被误判残留;fn 完成后停止心跳并删除锁目录。
21
41
  *
22
42
  * @param filePath 注册表文件路径,锁目录为其同级 `${filePath}.lock`
23
43
  * @param fn 持锁期间执行的操作(同步或异步)
44
+ * @param options 可选锁超时与重试配置
24
45
  * @returns fn 的返回值
25
- * @throws 超过 REGISTRY_LOCK_ACQUIRE_TIMEOUT_MS 仍未获取时抛出描述性错误
46
+ * @throws 超过 acquireTimeoutMs 仍未获取时抛出描述性错误
26
47
  */
27
- export declare function withRegistryLock<T>(filePath: string, fn: () => T | Promise<T>): Promise<T>;
48
+ export declare function withRegistryLock<T>(filePath: string, fn: () => T | Promise<T>, options?: RegistryLockOptions): Promise<T>;