@actiondock/core 2.10.0 → 2.12.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
@@ -18,15 +18,8 @@ const validatorCache = new WeakMap();
18
18
  /**
19
19
  * 递归检查数据中是否包含危险的原型污染属性键名(__proto__、constructor、prototype)。
20
20
  */
21
- import { validateActionInputValue } from "../json/value-validator.js";
22
- /**
23
- * 递归检查数据中是否包含危险的原型污染属性键名(__proto__、constructor、prototype)。
24
- * 内部委托 validateActionInputValue 执行迭代遍历,确保环路安全、有向无环图安全且无调用栈溢出风险。
25
- */
26
- export function hasDangerousKeys(data) {
27
- const result = validateActionInputValue(data);
28
- return !result.valid && result.kind === "input-policy";
29
- }
21
+ import { hasDangerousKeys } from "../value-validator.js";
22
+ export { hasDangerousKeys };
30
23
  /**
31
24
  * 内部纯 Schema 校验器。
32
25
  * 仅处理布尔 Schema 与 Ajv 规则校验,严禁执行 dangerous-key 遍历。
@@ -5,7 +5,6 @@ export * from "./body.js";
5
5
  export * from "./routes/index.js";
6
6
  export * from "./server.js";
7
7
  export * from "./mcp-endpoint.js";
8
- export * from "./dispatcher.js";
9
8
  export * from "./http-server.js";
10
9
  export { serveParentIpc } from "../ipc/host.js";
11
10
  export { IpcActionDockService } from "../ipc/service.js";
@@ -13,6 +12,5 @@ export type { IpcServiceOptions } from "../ipc/types.js";
13
12
  export { parseDuration } from "../utils/index.js";
14
13
  export type { ActionDockHost, ActionDockHostOptions, } from "../host/types.js";
15
14
  export { createActionDockHost, DefaultActionDockHost, } from "../host/host.js";
16
- export { LocalActionDockService, } from "../service/local.js";
17
15
  export { RemoteActionDockService, } from "../service/remote.js";
18
16
  export { ExitCode, StandaloneDispatcher, type StandaloneDispatcherOptions, type InvocationControl, runStandaloneProcess, } from "../runtime/standalone.js";
@@ -5,13 +5,11 @@ export * from "./body.js";
5
5
  export * from "./routes/index.js";
6
6
  export * from "./server.js";
7
7
  export * from "./mcp-endpoint.js";
8
- export * from "./dispatcher.js";
9
8
  export * from "./http-server.js";
10
9
  export { serveParentIpc } from "../ipc/host.js";
11
10
  export { IpcActionDockService } from "../ipc/service.js";
12
11
  export { parseDuration } from "../utils/index.js";
13
12
  export { createActionDockHost, DefaultActionDockHost, } from "../host/host.js";
14
- export { LocalActionDockService, } from "../service/local.js";
15
13
  export { RemoteActionDockService, } from "../service/remote.js";
16
14
  // 独立进程分发器与退出控制
17
15
  export { ExitCode, StandaloneDispatcher, runStandaloneProcess, } from "../runtime/standalone.js";
@@ -1,5 +1,5 @@
1
1
  import { parseActionRef } from "../../catalog/resolve-action.js";
2
- import { filterByIntent } from "../../filter/index.js";
2
+ import { filterByIntent } from "../../utils/intent.js";
3
3
  import { ACTION_EXECUTION_ERROR, ACTION_FORBIDDEN, ACTION_NOT_FOUND, ACTION_START_FAILED, ACTION_TIMEOUT, ACTIONS_LIST_ERROR, IDEMPOTENCY_CONFLICT, INPUT_VALIDATION_FAILED, INVALID_JSON, PACKAGE_FORBIDDEN, PACKAGE_NOT_FOUND, REQUEST_TOO_LARGE, } from "../../errors.js";
4
4
  import { InvalidJsonError, readJsonBody, RequestTooLargeError } from "../body.js";
5
5
  import { filterActionsByPolicy, getSubPath, isActionAllowedByPolicy, isPackageAllowedByPolicy, jsonResponse, } from "./common.js";
@@ -1,4 +1,4 @@
1
- import { filterWithFallbackInfo } from "../../filter/index.js";
1
+ import { filterWithFallbackInfo } from "../../utils/intent.js";
2
2
  import { INFO_ERROR, PACKAGE_NOT_ALLOWED, PACKAGE_NOT_FOUND, PACKAGES_INFO_ERROR } from "../../errors.js";
3
3
  import { ACTIONDOCK_VERSION } from "../../version.js";
4
4
  import { sanitizeConfigDefinitions } from "../../storage/index.js";
@@ -1,4 +1,4 @@
1
- import { filterByIntent } from "../../filter/index.js";
1
+ import { filterByIntent } from "../../utils/intent.js";
2
2
  import { PACKAGE_NOT_ALLOWED, PLAYBOOK_NOT_FOUND, PLAYBOOKS_LIST_ERROR } from "../../errors.js";
3
3
  import { assertPackageAllowed, getSubPath, isPackageAllowedByPolicy, jsonResponse } from "./common.js";
4
4
  /**
@@ -1,5 +1,6 @@
1
- import { ACTION_FORBIDDEN, CAPABILITY_UNAVAILABLE, EVENT_BACKPRESSURE_LIMIT, EVENT_CURSOR_EXPIRED, EXECUTION_FAILED, PACKAGE_FORBIDDEN, RUN_ALREADY_FINISHED, RUN_INTERRUPTED, RUN_NOT_FOUND, RUNS_CLEAR_ERROR, RUNS_LIST_ERROR, } from "../../errors.js";
1
+ import { ACTION_FORBIDDEN, CAPABILITY_UNAVAILABLE, EVENT_BACKPRESSURE_LIMIT, EVENT_CURSOR_EXPIRED, EXECUTION_FAILED, INVALID_ARGUMENT, PACKAGE_FORBIDDEN, RUN_ALREADY_FINISHED, RUN_INTERRUPTED, RUN_NOT_FOUND, RUNS_CLEAR_ERROR, RUNS_LIST_ERROR, } from "../../errors.js";
2
2
  import { isTerminalRunStatus } from "../../storage/types.js";
3
+ import { createAbortError } from "../../ipc/service.js";
3
4
  import { parseDuration } from "../../utils/index.js";
4
5
  import { readJsonBody } from "../body.js";
5
6
  import { getSubPath, isActionAllowedByPolicy, isPackageAllowedByPolicy, jsonResponse } from "./common.js";
@@ -16,11 +17,36 @@ export async function handleRunsRoutes(ctx) {
16
17
  const actionId = url.searchParams.get("actionId") || undefined;
17
18
  const packageId = url.searchParams.get("packageId") || undefined;
18
19
  const intent = url.searchParams.get("intent") || undefined;
19
- // limit 边界防护:非法或超界值回退默认 50,并夹紧到 1 至 500 区间
20
- const parsedLimit = parseInt(url.searchParams.get("limit") || "50", 10);
21
- const limit = Number.isFinite(parsedLimit) && parsedLimit > 0
22
- ? Math.min(parsedLimit, 500)
23
- : 50;
20
+ // requestId 反查:支持 requestId 与 requestIds 重复查询参数,将每一个参数值作为原样字符串保存
21
+ const requestIds = [
22
+ ...url.searchParams.getAll("requestId"),
23
+ ...url.searchParams.getAll("requestIds"),
24
+ ].filter((id) => id.length > 0);
25
+ // offset 校验:非负安全整数,默认为 0,上限 100000 防止滥用
26
+ const rawOffset = url.searchParams.get("offset");
27
+ let offset = 0;
28
+ if (rawOffset !== null && rawOffset !== "") {
29
+ const parsedOffset = Number(rawOffset);
30
+ if (!Number.isSafeInteger(parsedOffset) || parsedOffset < 0 || parsedOffset > 100_000) {
31
+ return jsonResponse({
32
+ ok: false,
33
+ error: {
34
+ code: INVALID_ARGUMENT,
35
+ message: `Invalid offset '${rawOffset}': must be a non-negative safe integer <= 100000`,
36
+ },
37
+ }, 400, corsHeaders);
38
+ }
39
+ offset = parsedOffset;
40
+ }
41
+ // limit 边界防护:未提供保持默认值 50,提供则夹紧在 1 至 500 区间
42
+ const rawLimit = url.searchParams.get("limit");
43
+ let limit = 50;
44
+ if (rawLimit !== null) {
45
+ const parsedLimit = parseInt(rawLimit, 10);
46
+ if (Number.isFinite(parsedLimit)) {
47
+ limit = Math.min(Math.max(parsedLimit, 1), 500);
48
+ }
49
+ }
24
50
  if (packageId && !isPackageAllowedByPolicy(packageId, policy)) {
25
51
  return jsonResponse({
26
52
  ok: false,
@@ -39,23 +65,104 @@ export async function handleRunsRoutes(ctx) {
39
65
  },
40
66
  }, 403, corsHeaders);
41
67
  }
42
- let allRuns = await service.runs.list({
68
+ const queryOptions = {
43
69
  actionId,
44
70
  status,
45
71
  packageId,
46
72
  intent,
47
- limit,
48
- });
49
- if (policy.packageAllowlist && policy.packageAllowlist.length > 0) {
50
- allRuns = allRuns.filter((r) => isPackageAllowedByPolicy(r.packageId, policy));
73
+ requestIds: requestIds.length > 0 ? requestIds : undefined,
74
+ signal: req.signal,
75
+ packageAllowlist: policy.packageAllowlist && policy.packageAllowlist.length > 0
76
+ ? policy.packageAllowlist
77
+ : undefined,
78
+ };
79
+ const hasPolicyFilter = Boolean((policy.packageAllowlist && policy.packageAllowlist.length > 0) ||
80
+ (policy.actionAllowlist && policy.actionAllowlist.length > 0));
81
+ let runs;
82
+ let total;
83
+ if (hasPolicyFilter) {
84
+ const BATCH_SIZE = 100;
85
+ let cursorOffset = 0;
86
+ let matchedCount = 0;
87
+ const pagedRuns = [];
88
+ while (true) {
89
+ if (req.signal?.aborted) {
90
+ throw createAbortError(req.signal.reason);
91
+ }
92
+ const batch = await service.runs.list({
93
+ ...queryOptions,
94
+ limit: BATCH_SIZE,
95
+ offset: cursorOffset,
96
+ signal: req.signal,
97
+ }, { signal: req.signal });
98
+ if (req.signal?.aborted) {
99
+ throw createAbortError(req.signal.reason);
100
+ }
101
+ if (batch.length === 0) {
102
+ break;
103
+ }
104
+ for (const run of batch) {
105
+ const packageAllowed = isPackageAllowedByPolicy(run.packageId, policy);
106
+ const actionAllowed = isActionAllowedByPolicy({ packageId: run.packageId, actionId: run.actionId }, policy);
107
+ if (packageAllowed && actionAllowed) {
108
+ if (matchedCount >= offset && pagedRuns.length < limit) {
109
+ pagedRuns.push(run);
110
+ }
111
+ matchedCount++;
112
+ }
113
+ }
114
+ cursorOffset += batch.length;
115
+ if (batch.length < BATCH_SIZE) {
116
+ break;
117
+ }
118
+ }
119
+ runs = pagedRuns;
120
+ total = matchedCount;
121
+ }
122
+ else {
123
+ // 当 hasPolicyFilter 为 false 时:下推 limit 与 offset 到底层查询
124
+ if (req.signal?.aborted) {
125
+ throw createAbortError(req.signal.reason);
126
+ }
127
+ runs = await service.runs.list({
128
+ ...queryOptions,
129
+ limit,
130
+ offset,
131
+ signal: req.signal,
132
+ }, { signal: req.signal });
133
+ if (req.signal?.aborted) {
134
+ throw createAbortError(req.signal.reason);
135
+ }
136
+ // 统计总数 total 的可靠处理:
137
+ if (typeof service.runs.count === "function") {
138
+ total = await service.runs.count({
139
+ ...queryOptions,
140
+ signal: req.signal,
141
+ }, { signal: req.signal });
142
+ }
143
+ else if (runs.length === 0 && offset === 0) {
144
+ total = 0;
145
+ }
146
+ else if (runs.length > 0 && runs.length < limit) {
147
+ total = offset + runs.length;
148
+ }
149
+ else {
150
+ total = undefined;
151
+ }
51
152
  }
52
- if (policy.actionAllowlist && policy.actionAllowlist.length > 0) {
53
- allRuns = allRuns.filter((r) => isActionAllowedByPolicy({ packageId: r.packageId, actionId: r.actionId }, policy));
153
+ const responseData = {
154
+ ok: true,
155
+ items: runs,
156
+ };
157
+ if (typeof total === "number") {
158
+ responseData.total = total;
54
159
  }
55
- const sliced = allRuns.slice(0, limit);
56
- return jsonResponse({ ok: true, total: allRuns.length, items: sliced }, 200, corsHeaders);
160
+ return jsonResponse(responseData, 200, corsHeaders);
57
161
  }
58
162
  catch (err) {
163
+ if (req.signal?.aborted || err?.name === "AbortError") {
164
+ throw err;
165
+ }
59
166
  return jsonResponse({ ok: false, error: { code: RUNS_LIST_ERROR, message: err.message } }, 500, corsHeaders);
60
167
  }
61
168
  }
@@ -310,7 +417,7 @@ export async function handleRunsRoutes(ctx) {
310
417
  const runShowMatch = subpath.match(/^\/runs\/([^/]+)$/);
311
418
  if (runShowMatch && req.method === "GET") {
312
419
  const runId = decodeURIComponent(runShowMatch[1]);
313
- const run = await service.runs.get(runId);
420
+ const run = await service.runs.get(runId, { signal: req.signal });
314
421
  if (!run) {
315
422
  return jsonResponse({
316
423
  ok: false,
@@ -38,7 +38,8 @@ export async function handleStateRoutes(ctx) {
38
38
  if (subpath === "/state" && req.method === "GET") {
39
39
  try {
40
40
  const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
41
- const actionParam = url.searchParams.get("action") || url.searchParams.get("actionId") || "";
41
+ const rawAction = url.searchParams.get("action") ?? url.searchParams.get("actionId");
42
+ const actionParam = rawAction !== null ? rawAction : undefined;
42
43
  const nsParam = url.searchParams.get("namespace") ?? undefined;
43
44
  const prefix = url.searchParams.get("prefix") || "";
44
45
  const pkgs = await service.discovery.listPackages();
@@ -68,8 +69,13 @@ export async function handleStateRoutes(ctx) {
68
69
  try {
69
70
  const body = await readJsonBody(req, { maxBytes: options.maxBodyBytes }).catch(() => ({}));
70
71
  const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || body.package || undefined;
71
- const actionParam = url.searchParams.get("action") || url.searchParams.get("actionId") || body.action || body.actionId || "";
72
- const baseNs = body.namespace ?? (url.searchParams.get("namespace") || undefined);
72
+ const rawAction = url.searchParams.get("action") ??
73
+ url.searchParams.get("actionId") ??
74
+ body.action ??
75
+ body.actionId;
76
+ const actionParam = rawAction !== null && rawAction !== undefined ? rawAction : undefined;
77
+ const rawNs = body.namespace ?? url.searchParams.get("namespace");
78
+ const baseNs = rawNs !== null && rawNs !== undefined ? rawNs : undefined;
73
79
  const pkgs = await service.discovery.listPackages();
74
80
  const targetPackageId = resolveTargetPackageId(pkgs, pkgParam, policy);
75
81
  const clearedCount = await service.management.state.clear(targetPackageId, actionParam, {
@@ -99,13 +105,21 @@ export async function handleStateRoutes(ctx) {
99
105
  try {
100
106
  const key = decodeURIComponent(stateKeyMatch[1]);
101
107
  const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
102
- const actionParam = url.searchParams.get("action") || url.searchParams.get("actionId") || "";
103
- const nsParam = url.searchParams.get("namespace") || undefined;
108
+ const rawAction = url.searchParams.get("action") ?? url.searchParams.get("actionId");
109
+ const actionParam = rawAction !== null ? rawAction : undefined;
110
+ const nsParam = url.searchParams.get("namespace") ?? undefined;
104
111
  const pkgs = await service.discovery.listPackages();
105
112
  const targetPackageId = resolveTargetPackageId(pkgs, pkgParam, policy);
106
113
  if (req.method === "GET") {
107
- const entry = await service.management.state.get(targetPackageId, actionParam, key, {
108
- namespace: nsParam,
114
+ let actualKey = key;
115
+ let ns = nsParam;
116
+ if (actionParam === undefined && nsParam === undefined) {
117
+ const decoded = decodeStateKey(key);
118
+ ns = decoded.namespace;
119
+ actualKey = decoded.key;
120
+ }
121
+ const entry = await service.management.state.get(targetPackageId, actionParam ?? "", actualKey, {
122
+ namespace: ns,
109
123
  detail: true,
110
124
  });
111
125
  if (!entry || (typeof entry === "object" && entry.value === undefined)) {
@@ -113,12 +127,12 @@ export async function handleStateRoutes(ctx) {
113
127
  }
114
128
  const data = typeof entry === "object" && "value" in entry
115
129
  ? entry
116
- : { key, namespace: nsParam || "", value: entry, expiresAt: undefined };
130
+ : { key: actualKey, namespace: ns ?? "", value: entry, expiresAt: undefined };
117
131
  return jsonResponse({
118
132
  ok: true,
119
133
  packageId: targetPackageId,
120
- key: data.key || key,
121
- namespace: data.namespace ?? nsParam ?? "",
134
+ key: data.key ?? actualKey,
135
+ namespace: data.namespace ?? ns ?? "",
122
136
  value: data.value,
123
137
  expiresAt: data.expiresAt,
124
138
  }, 200, corsHeaders);
@@ -127,29 +141,40 @@ export async function handleStateRoutes(ctx) {
127
141
  const body = await readJsonBody(req, { maxBytes: options.maxBodyBytes });
128
142
  const val = body.value !== undefined ? body.value : body;
129
143
  const ttl = typeof body.ttl === "number" ? body.ttl : undefined;
130
- const bodyAction = body.action || body.actionId || actionParam;
131
- const explicitNs = body.namespace || nsParam;
144
+ const rawActionFromReq = url.searchParams.get("action") ??
145
+ url.searchParams.get("actionId") ??
146
+ body.action ??
147
+ body.actionId;
148
+ const bodyAction = rawActionFromReq !== null && rawActionFromReq !== undefined ? rawActionFromReq : undefined;
149
+ const explicitNs = body.namespace ?? nsParam;
132
150
  let actualKey = key;
133
151
  let ns = explicitNs;
134
- if (!bodyAction && !explicitNs) {
152
+ if (bodyAction === undefined && explicitNs === undefined) {
135
153
  const decoded = decodeStateKey(key);
136
154
  ns = decoded.namespace;
137
155
  actualKey = decoded.key;
138
156
  }
139
- await service.management.state.set(targetPackageId, bodyAction, actualKey, val, {
157
+ await service.management.state.set(targetPackageId, bodyAction ?? "", actualKey, val, {
140
158
  namespace: ns,
141
159
  ttl,
142
160
  });
143
- return jsonResponse({ ok: true, packageId: targetPackageId, key: actualKey, namespace: ns || bodyAction || "", message: "updated" }, 200, corsHeaders);
161
+ return jsonResponse({ ok: true, packageId: targetPackageId, key: actualKey, namespace: ns ?? bodyAction ?? "", message: "updated" }, 200, corsHeaders);
144
162
  }
145
163
  if (req.method === "DELETE") {
146
- const deleted = await service.management.state.delete(targetPackageId, actionParam, key, {
147
- namespace: nsParam,
164
+ let actualKey = key;
165
+ let ns = nsParam;
166
+ if (actionParam === undefined && nsParam === undefined) {
167
+ const decoded = decodeStateKey(key);
168
+ ns = decoded.namespace;
169
+ actualKey = decoded.key;
170
+ }
171
+ const deleted = await service.management.state.delete(targetPackageId, actionParam ?? "", actualKey, {
172
+ namespace: ns,
148
173
  });
149
174
  if (!deleted) {
150
175
  return jsonResponse({ ok: false, error: { code: STATE_KEY_NOT_FOUND, message: `State key '${key}' not found` } }, 404, corsHeaders);
151
176
  }
152
- return jsonResponse({ ok: true, packageId: targetPackageId, key, deleted: true }, 200, corsHeaders);
177
+ return jsonResponse({ ok: true, packageId: targetPackageId, key: actualKey, deleted: true }, 200, corsHeaders);
153
178
  }
154
179
  }
155
180
  catch (err) {
@@ -1,4 +1,4 @@
1
- export { isLoopbackHost } from "../utils/net.js";
1
+ export { isLoopbackHost } from "../utils/index.js";
2
2
  /**
3
3
  * 恒定时间字符串比较(Constant-time comparison)。
4
4
  * 使用底层的 `crypto.timingSafeEqual` 防范时序攻击(Timing Attack)。
@@ -1,6 +1,6 @@
1
1
  import { timingSafeEqual } from "node:crypto";
2
- // isLoopbackHost 已上移至 utils/net.ts(通用谓词单一事实源),此处仅保留 re-export 兼容旧引用路径
3
- export { isLoopbackHost } from "../utils/net.js";
2
+ // isLoopbackHost 已上移至 utils(通用谓词单一事实源),此处仅保留 re-export 兼容旧引用路径
3
+ export { isLoopbackHost } from "../utils/index.js";
4
4
  /**
5
5
  * 恒定时间字符串比较(Constant-time comparison)。
6
6
  * 使用底层的 `crypto.timingSafeEqual` 防范时序攻击(Timing Attack)。
@@ -1,9 +1,8 @@
1
1
  import { posix, resolve } from "node:path";
2
2
  import { NodeHttpServer } from "./http-server.js";
3
3
  import { createActionDock } from "../service/factory.js";
4
- import { NOT_FOUND, UNAUTHORIZED } from "../errors.js";
4
+ import { ActionDockError, ACCESS_DENIED, NOT_FOUND, SERVICE_ERROR, UNAUTHORIZED } from "../errors.js";
5
5
  import { parseActionRef } from "../catalog/resolve-action.js";
6
- import { ensureDependencyClosure } from "../project/closure.js";
7
6
  import { findProjectRoot } from "../project/loader.js";
8
7
  import { listLinkedPackages, resolvePackageRoot } from "../registry/registry.js";
9
8
  import { handleActionsRoutes, handleConfigRoutes, handleDoctorRoute, handleHealthRoute, handleInfoRoute, handlePlaybooksRoutes, handleRunsRoutes, handleStateRoutes, jsonResponse, } from "./routes/index.js";
@@ -57,7 +56,7 @@ export async function startActionDockServer(options = {}) {
57
56
  defaultView.policy.token ||
58
57
  Array.from(views.values()).some((v) => Boolean(v.policy.token)));
59
58
  if (!isLoopbackHost(host) && !hasTokenConfigured && !options.allowInsecureNoAuth) {
60
- throw new Error("Authentication token is required when binding to a non-loopback address. Use --allow-insecure-no-auth to override.");
59
+ throw new ActionDockError(ACCESS_DENIED, "Authentication token is required when binding to a non-loopback address. Use --allow-insecure-no-auth to override.");
61
60
  }
62
61
  let serviceInstance = options.service;
63
62
  // 若未传 service,服务端统一通过 createActionDock() 实例化,不再接受或处理外部传入的 Host 实体
@@ -74,7 +73,7 @@ export async function startActionDockServer(options = {}) {
74
73
  });
75
74
  }
76
75
  if (!serviceInstance) {
77
- throw new Error("Failed to initialize ActionDockService for server");
76
+ throw new ActionDockError(SERVICE_ERROR, "Failed to initialize ActionDockService for server");
78
77
  }
79
78
  // 2. 聚合所有 views 声明的依赖包与动作,确保依赖闭包满足
80
79
  const roots = [];
@@ -124,9 +123,6 @@ export async function startActionDockServer(options = {}) {
124
123
  catch { }
125
124
  }
126
125
  }
127
- if (roots.length > 0) {
128
- await ensureDependencyClosure(roots, { customHome });
129
- }
130
126
  // 3. 为每个启用 MCP 的视图独立装配 MCP 处理器
131
127
  if (options.enableMcp !== false) {
132
128
  for (const v of views.values()) {
@@ -1,5 +1,4 @@
1
1
  import { createActionDockHost } from "../host/host.js";
2
- import { LocalActionDockService } from "./local.js";
3
2
  import { RemoteActionDockService } from "./remote.js";
4
3
  import { ActionDockError, INVALID_ARGUMENT, NOT_FOUND } from "../errors.js";
5
4
  /**
@@ -21,16 +20,18 @@ export async function createActionDock(options = {}) {
21
20
  process: options.runtimeOptions.process,
22
21
  logger: options.runtimeOptions.logger,
23
22
  recoverOrphans: options.recoverOrphans,
23
+ enableManagement: options.enableManagement,
24
24
  });
25
- return new LocalActionDockService(host, { enableManagement: options.enableManagement });
25
+ return host;
26
26
  }
27
27
  const host = await createActionDockHost({
28
28
  scanLinkedPackages: options.scanLinkedPackages ?? true,
29
29
  ...options,
30
30
  recoverOrphans: options.recoverOrphans ?? true,
31
+ enableManagement: options.enableManagement,
31
32
  ...(options.hostOptions || {}),
32
33
  });
33
- return new LocalActionDockService(host, { enableManagement: options.enableManagement });
34
+ return host;
34
35
  }
35
36
  /**
36
37
  * 工厂函数:连接远端 ActionDock 服务实例。
@@ -1,4 +1,4 @@
1
1
  export * from "./types.js";
2
- export * from "./local.js";
3
2
  export * from "./remote.js";
4
3
  export * from "./factory.js";
4
+ export { DefaultActionDockHost, DefaultActionDockHost as DefaultActionDockService } from "../host/host.js";
@@ -1,4 +1,4 @@
1
1
  export * from "./types.js";
2
- export * from "./local.js";
3
2
  export * from "./remote.js";
4
3
  export * from "./factory.js";
4
+ export { DefaultActionDockHost, DefaultActionDockHost as DefaultActionDockService } from "../host/host.js";
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 远端通信错误翻译层。
3
3
  *
4
- * 职责单一:将 profile/client 传输层抛出的底层异常翻译为统一的
4
+ * 职责单一:将 client 传输层抛出的底层异常翻译为统一的
5
5
  * 结构化异常,供 RemoteActionDockService 各操作边界复用;
6
6
  * 优先读取传输层透传的结构化错误码,无法识别时原样透传。
7
7
  */
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * 远端通信错误翻译层。
3
3
  *
4
- * 职责单一:将 profile/client 传输层抛出的底层异常翻译为统一的
4
+ * 职责单一:将 client 传输层抛出的底层异常翻译为统一的
5
5
  * 结构化异常,供 RemoteActionDockService 各操作边界复用;
6
6
  * 优先读取传输层透传的结构化错误码,无法识别时原样透传。
7
7
  */
8
- import { CAPABILITY_UNAVAILABLE, STATE_KEY_NOT_FOUND } from "../errors.js";
9
- import { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, ServiceError, } from "./types.js";
8
+ import { ActionDockError, CAPABILITY_UNAVAILABLE, STATE_KEY_NOT_FOUND } from "../errors.js";
9
+ import { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, } from "./types.js";
10
10
  /**
11
11
  * 将远端通信异常包装翻译为统一的异常。
12
12
  *
@@ -16,16 +16,16 @@ export function wrapRemoteError(err) {
16
16
  const code = err?.code || "";
17
17
  if (code === "CAPABILITY_UNAVAILABLE" ||
18
18
  code === "TARGET_CAPABILITY_UNAVAILABLE") {
19
- throw new ServiceError(CAPABILITY_UNAVAILABLE, `CAPABILITY_UNAVAILABLE: Management APIs are not enabled on remote service`, { originalMessage: err?.message });
19
+ throw new ActionDockError(CAPABILITY_UNAVAILABLE, `CAPABILITY_UNAVAILABLE: Management APIs are not enabled on remote service`, { originalMessage: err?.message });
20
20
  }
21
21
  if (code === "PROTOCOL_UNSUPPORTED" ||
22
22
  code === "TARGET_PROTOCOL_UNSUPPORTED") {
23
- throw new ServiceError(PROTOCOL_UNSUPPORTED, `PROTOCOL_UNSUPPORTED: ${err?.message || ""}`, { originalMessage: err?.message });
23
+ throw new ActionDockError(PROTOCOL_UNSUPPORTED, `PROTOCOL_UNSUPPORTED: ${err?.message || ""}`, { originalMessage: err?.message });
24
24
  }
25
25
  if (code === "SERVICE_RESULT_UNKNOWN" ||
26
26
  code === "TARGET_RESULT_UNKNOWN" ||
27
27
  code === "RESULT_UNKNOWN") {
28
- throw new ServiceError(SERVICE_RESULT_UNKNOWN, `SERVICE_RESULT_UNKNOWN: ${err?.message || ""}`, { originalMessage: err?.message });
28
+ throw new ActionDockError(SERVICE_RESULT_UNKNOWN, `SERVICE_RESULT_UNKNOWN: ${err?.message || ""}`, { originalMessage: err?.message });
29
29
  }
30
30
  throw err;
31
31
  }
@@ -6,6 +6,7 @@
6
6
  * ExecutionResult 结构。不感知 SSE 通道与门面生命周期。
7
7
  */
8
8
  import type { ExecutionResult, RunRecord } from "@actiondock/sdk";
9
+ import { type Clock } from "../storage/clock.js";
9
10
  /**
10
11
  * 轮询依赖上下文:由 RemoteActionDockService 门面注入自身能力,
11
12
  * 保持本模块与门面实现解耦。
@@ -17,6 +18,8 @@ export interface RunPollingContext {
17
18
  isClosed(): boolean;
18
19
  /** 查询远端运行详情(键不存在时返回 undefined) */
19
20
  getRun(runId: string): Promise<RunRecord | undefined>;
21
+ /** 可选注入时钟抽象(缺省使用系统时钟) */
22
+ clock?: Clock;
20
23
  }
21
24
  /**
22
25
  * 将终态运行记录翻译为统一 ExecutionResult 结构。
@@ -6,8 +6,11 @@
6
6
  * ExecutionResult 结构。不感知 SSE 通道与门面生命周期。
7
7
  */
8
8
  import { ACTION_CANCELLED, EXECUTION_FAILED, RUN_INTERRUPTED, TIMEOUT } from "../errors.js";
9
+ import { SystemClock } from "../storage/clock.js";
9
10
  import { isTerminalRunStatus } from "../storage/types.js";
10
11
  import { SERVICE_CLOSED } from "./types.js";
12
+ /** 模块级默认时钟实例 */
13
+ const defaultClock = new SystemClock();
11
14
  /**
12
15
  * 将终态运行记录翻译为统一 ExecutionResult 结构。
13
16
  */
@@ -44,12 +47,14 @@ export function formatTerminalRunResult(run, runId) {
44
47
  * @param startTime 等待起点时间戳(缺省当前时间)
45
48
  * @param totalMaxWaitMs 已消耗等待预算对应的总上限(SSE 阶段已等待时传入)
46
49
  */
47
- export async function pollRunCompletion(ctx, runId, signal, timeoutMs, startTime = Date.now(), totalMaxWaitMs) {
50
+ export async function pollRunCompletion(ctx, runId, signal, timeoutMs, startTime, totalMaxWaitMs) {
51
+ const clock = ctx.clock ?? defaultClock;
52
+ const effectiveStartTime = startTime ?? clock.monotonic();
48
53
  const maxWaitMs = totalMaxWaitMs ?? Math.max(ctx.baseTimeoutMs, timeoutMs ?? 0);
49
- const remainingWaitMs = Math.max(0, maxWaitMs - (Date.now() - startTime));
54
+ const remainingWaitMs = Math.max(0, maxWaitMs - (clock.monotonic() - effectiveStartTime));
50
55
  let delayMs = Math.min(150, Math.max(10, Math.floor((remainingWaitMs || maxWaitMs) / 4)));
51
56
  const maxDelayMs = 2000;
52
- while (Date.now() - startTime < maxWaitMs) {
57
+ while (clock.monotonic() - effectiveStartTime < maxWaitMs) {
53
58
  if (ctx.isClosed()) {
54
59
  return {
55
60
  ok: false,
@@ -89,10 +94,10 @@ export async function pollRunCompletion(ctx, runId, signal, timeoutMs, startTime
89
94
  }
90
95
  throw err;
91
96
  }
92
- await new Promise((resolve) => setTimeout(resolve, delayMs));
97
+ await clock.sleep(delayMs);
93
98
  delayMs = Math.min(delayMs * 2, maxDelayMs);
94
99
  }
95
- const waitedMs = Date.now() - startTime;
100
+ const waitedMs = Math.round(clock.monotonic() - effectiveStartTime);
96
101
  return {
97
102
  ok: false,
98
103
  runId,
@@ -1,4 +1,5 @@
1
1
  import type { PackageInfo } from "../package/types.js";
2
+ import { type Clock } from "../storage/clock.js";
2
3
  import { type RemoteServiceOptions } from "./types.js";
3
4
  import type { ActionDockService, ConfigPort, ConnectActionDockOptions, DiscoveryPort, EventsPort, ExecutionPort, RunsPort, StatePort } from "./types.js";
4
5
  /**
@@ -14,6 +15,7 @@ export declare class RemoteActionDockService implements ActionDockService {
14
15
  readonly allowInsecureHttp?: boolean;
15
16
  readonly insecure?: boolean;
16
17
  readonly dispatcher?: unknown;
18
+ readonly clock: Clock;
17
19
  private isClosed;
18
20
  readonly discovery: DiscoveryPort;
19
21
  readonly execution: ExecutionPort;
@@ -23,7 +25,9 @@ export declare class RemoteActionDockService implements ActionDockService {
23
25
  config: ConfigPort;
24
26
  state: StatePort;
25
27
  };
26
- constructor(options: ConnectActionDockOptions | RemoteServiceOptions);
28
+ constructor(options: (ConnectActionDockOptions | RemoteServiceOptions) & {
29
+ clock?: Clock;
30
+ });
27
31
  private assertNotClosed;
28
32
  info(): Promise<PackageInfo[]>;
29
33
  private waitForRunCompletion;