@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
@@ -1,11 +1,13 @@
1
1
  import { parseActionRef } from "../catalog/resolve-action.js";
2
2
  import { ActionDockError, ACTION_CANCELLED, CAPABILITY_UNAVAILABLE, INVALID_ARGUMENT, TIMEOUT, } from "../errors.js";
3
- import { cancelRemoteRun, clearRemoteRuns, cleanExpiredRemoteRuns, clearRemoteState, deleteRemoteConfig, deleteRemoteStateKey, executeRemoteAction, fetchRemoteActionShow, fetchRemoteActions, fetchRemoteConfig, fetchRemoteInfo, fetchRemotePlaybookShow, fetchRemotePlaybooks, fetchRemoteRun, fetchRemoteRuns, fetchRemoteStateList, getRemoteStateKey, setRemoteConfig, setRemoteStateKey, } from "../profile/client.js";
3
+ import { createAbortError } from "../ipc/service.js";
4
+ import { cancelRemoteRun, clearRemoteRuns, cleanExpiredRemoteRuns, clearRemoteState, deleteRemoteConfig, deleteRemoteStateKey, executeRemoteAction, fetchRemoteActionShow, fetchRemoteActions, fetchRemoteConfig, fetchRemoteInfo, fetchRemotePlaybookShow, fetchRemotePlaybooks, fetchRemoteRun, fetchRemoteRuns, fetchRemoteStateList, getRemoteStateKey, setRemoteConfig, setRemoteStateKey, } from "../client/index.js";
4
5
  import { isRemoteStateKeyNotFound, wrapRemoteError } from "./remote-errors.js";
5
6
  import { formatTerminalRunResult, pollRunCompletion } from "./remote-polling.js";
6
7
  import { streamRemoteEvents } from "./sse-stream.js";
8
+ import { SystemClock } from "../storage/clock.js";
7
9
  import { isTerminalRunStatus } from "../storage/types.js";
8
- import { ACTIONDOCK_PROTOCOL_VERSION, PROTOCOL_UNSUPPORTED, SERVICE_CLOSED, ServiceError, } from "./types.js";
10
+ import { ACTIONDOCK_PROTOCOL_VERSION, PROTOCOL_UNSUPPORTED, SERVICE_CLOSED, } from "./types.js";
9
11
  /**
10
12
  * 远程 ActionDockService 端口实现。
11
13
  * 封装与远端 ActionDock 服务的 HTTP / SSE 协议交互,
@@ -19,6 +21,7 @@ export class RemoteActionDockService {
19
21
  allowInsecureHttp;
20
22
  insecure;
21
23
  dispatcher;
24
+ clock;
22
25
  isClosed = false;
23
26
  discovery;
24
27
  execution;
@@ -36,6 +39,7 @@ export class RemoteActionDockService {
36
39
  this.allowInsecureHttp = options.allowInsecureHttp;
37
40
  this.insecure = options.insecure;
38
41
  this.dispatcher = options.dispatcher;
42
+ this.clock = options.clock ?? new SystemClock();
39
43
  const self = this;
40
44
  this.discovery = {
41
45
  async listPackages() {
@@ -56,7 +60,7 @@ export class RemoteActionDockService {
56
60
  const [remoteMajor] = protocolVersion.split(".");
57
61
  const [currentMajor] = ACTIONDOCK_PROTOCOL_VERSION.split(".");
58
62
  if (remoteMajor !== currentMajor) {
59
- throw new ServiceError(PROTOCOL_UNSUPPORTED, `PROTOCOL_UNSUPPORTED: Remote server protocol version '${protocolVersion}' is incompatible with expected '${ACTIONDOCK_PROTOCOL_VERSION}'`);
63
+ throw new ActionDockError(PROTOCOL_UNSUPPORTED, `PROTOCOL_UNSUPPORTED: Remote server protocol version '${protocolVersion}' is incompatible with expected '${ACTIONDOCK_PROTOCOL_VERSION}'`);
60
64
  }
61
65
  }
62
66
  if (Array.isArray(raw)) {
@@ -224,35 +228,121 @@ export class RemoteActionDockService {
224
228
  },
225
229
  };
226
230
  this.runs = {
227
- async list(query) {
231
+ async list(query, options) {
228
232
  self.assertNotClosed();
233
+ const effectiveSignal = options?.signal ?? query?.signal;
234
+ try {
235
+ const SERVER_MAX_PAGE_SIZE = 500;
236
+ const SERVER_MAX_OFFSET = 100_000;
237
+ if (query?.offset !== undefined && (query.offset < 0 || query.offset > SERVER_MAX_OFFSET || !Number.isSafeInteger(query.offset))) {
238
+ throw new ActionDockError(INVALID_ARGUMENT, `Invalid offset '${query.offset}': must be a non-negative safe integer <= ${SERVER_MAX_OFFSET}`);
239
+ }
240
+ const requestedLimit = query?.limit;
241
+ let offset = query?.offset ?? 0;
242
+ const allItems = [];
243
+ while (true) {
244
+ if (effectiveSignal?.aborted) {
245
+ throw createAbortError(effectiveSignal.reason);
246
+ }
247
+ if (offset > SERVER_MAX_OFFSET) {
248
+ throw new ActionDockError(INVALID_ARGUMENT, `Invalid offset '${offset}': must be a non-negative safe integer <= ${SERVER_MAX_OFFSET}`);
249
+ }
250
+ let batchLimit = SERVER_MAX_PAGE_SIZE;
251
+ if (requestedLimit !== undefined) {
252
+ const remaining = requestedLimit - allItems.length;
253
+ if (remaining <= 0) {
254
+ break;
255
+ }
256
+ batchLimit = Math.min(remaining, SERVER_MAX_PAGE_SIZE);
257
+ }
258
+ const res = await fetchRemoteRuns(self.serverUrl, self.token, {
259
+ packageId: query?.packageId,
260
+ actionId: query?.actionId,
261
+ status: query?.status,
262
+ intent: query?.intent,
263
+ limit: batchLimit,
264
+ offset,
265
+ requestIds: query?.requestIds,
266
+ allowInsecureHttp: self.allowInsecureHttp,
267
+ insecure: self.insecure,
268
+ dispatcher: self.dispatcher,
269
+ signal: effectiveSignal,
270
+ });
271
+ if (effectiveSignal?.aborted) {
272
+ throw createAbortError(effectiveSignal.reason);
273
+ }
274
+ const items = res.items || [];
275
+ if (items.length === 0) {
276
+ break;
277
+ }
278
+ allItems.push(...items);
279
+ offset += items.length;
280
+ if (requestedLimit !== undefined && allItems.length >= requestedLimit) {
281
+ break;
282
+ }
283
+ if (typeof res.total === "number" && offset >= res.total) {
284
+ break;
285
+ }
286
+ if (items.length < batchLimit) {
287
+ break;
288
+ }
289
+ if (offset > SERVER_MAX_OFFSET) {
290
+ throw new ActionDockError(INVALID_ARGUMENT, `Invalid offset '${offset}': must be a non-negative safe integer <= ${SERVER_MAX_OFFSET}`);
291
+ }
292
+ }
293
+ return allItems;
294
+ }
295
+ catch (err) {
296
+ if (effectiveSignal?.aborted || err?.name === "AbortError") {
297
+ throw createAbortError(effectiveSignal?.reason ?? err);
298
+ }
299
+ wrapRemoteError(err);
300
+ }
301
+ },
302
+ async count(query, options) {
303
+ self.assertNotClosed();
304
+ const effectiveSignal = options?.signal ?? query?.signal;
229
305
  try {
230
306
  const res = await fetchRemoteRuns(self.serverUrl, self.token, {
231
307
  packageId: query?.packageId,
232
308
  actionId: query?.actionId,
233
309
  status: query?.status,
234
310
  intent: query?.intent,
235
- limit: query?.limit,
311
+ limit: 1,
312
+ offset: 0,
313
+ requestIds: query?.requestIds,
236
314
  allowInsecureHttp: self.allowInsecureHttp,
237
315
  insecure: self.insecure,
238
316
  dispatcher: self.dispatcher,
317
+ signal: effectiveSignal,
239
318
  });
240
- return res.items || [];
319
+ if (typeof res.total === "number") {
320
+ return res.total;
321
+ }
322
+ const all = await self.runs.list({ ...query, limit: undefined, offset: undefined }, options);
323
+ return all.length;
241
324
  }
242
325
  catch (err) {
326
+ if (effectiveSignal?.aborted || err?.name === "AbortError") {
327
+ throw createAbortError(effectiveSignal?.reason ?? err);
328
+ }
243
329
  wrapRemoteError(err);
244
330
  }
245
331
  },
246
- async get(runId) {
332
+ async get(runId, options) {
247
333
  self.assertNotClosed();
248
334
  try {
249
335
  return await fetchRemoteRun(self.serverUrl, runId, self.token, {
250
336
  allowInsecureHttp: self.allowInsecureHttp,
251
337
  insecure: self.insecure,
252
338
  dispatcher: self.dispatcher,
339
+ signal: options?.signal,
253
340
  });
254
341
  }
255
342
  catch (err) {
343
+ if (options?.signal?.aborted || err?.name === "AbortError") {
344
+ throw createAbortError(options?.signal?.reason ?? err);
345
+ }
256
346
  const code = String(err?.code || "");
257
347
  if (code === "RUN_NOT_FOUND" || code === "NOT_FOUND" || err?.status === 404) {
258
348
  return undefined;
@@ -426,7 +516,7 @@ export class RemoteActionDockService {
426
516
  try {
427
517
  const res = await getRemoteStateKey(self.serverUrl, key, self.token, {
428
518
  package: packageId || undefined,
429
- action: actionId || undefined,
519
+ action: actionId ?? undefined,
430
520
  namespace: opts?.namespace,
431
521
  allowInsecureHttp: self.allowInsecureHttp,
432
522
  insecure: self.insecure,
@@ -451,7 +541,7 @@ export class RemoteActionDockService {
451
541
  try {
452
542
  await setRemoteStateKey(self.serverUrl, key, value, self.token, {
453
543
  package: packageId || undefined,
454
- action: actionId || undefined,
544
+ action: actionId ?? undefined,
455
545
  namespace: opts?.namespace,
456
546
  ttl: opts?.ttl,
457
547
  allowInsecureHttp: self.allowInsecureHttp,
@@ -468,7 +558,7 @@ export class RemoteActionDockService {
468
558
  try {
469
559
  const res = await deleteRemoteStateKey(self.serverUrl, key, self.token, {
470
560
  package: packageId || undefined,
471
- action: actionId || undefined,
561
+ action: actionId ?? undefined,
472
562
  namespace: opts?.namespace,
473
563
  allowInsecureHttp: self.allowInsecureHttp,
474
564
  insecure: self.insecure,
@@ -488,7 +578,7 @@ export class RemoteActionDockService {
488
578
  try {
489
579
  const res = await fetchRemoteStateList(self.serverUrl, self.token, {
490
580
  package: packageId || undefined,
491
- action: actionId || undefined,
581
+ action: actionId ?? undefined,
492
582
  namespace: opts?.namespace,
493
583
  prefix: opts?.prefix,
494
584
  allowInsecureHttp: self.allowInsecureHttp,
@@ -506,7 +596,7 @@ export class RemoteActionDockService {
506
596
  try {
507
597
  const res = await clearRemoteState(self.serverUrl, self.token, {
508
598
  package: packageId || undefined,
509
- action: actionId || undefined,
599
+ action: actionId ?? undefined,
510
600
  namespace: opts?.namespace,
511
601
  prefix: opts?.prefix,
512
602
  all: opts?.all,
@@ -522,7 +612,7 @@ export class RemoteActionDockService {
522
612
  },
523
613
  async listEntries(_packageId, _opts) {
524
614
  self.assertNotClosed();
525
- throw new ServiceError(CAPABILITY_UNAVAILABLE, "CAPABILITY_UNAVAILABLE: listStateEntries is not supported on remote service");
615
+ throw new ActionDockError(CAPABILITY_UNAVAILABLE, "CAPABILITY_UNAVAILABLE: listStateEntries is not supported on remote service");
526
616
  },
527
617
  },
528
618
  };
@@ -530,14 +620,14 @@ export class RemoteActionDockService {
530
620
  }
531
621
  assertNotClosed() {
532
622
  if (this.isClosed) {
533
- throw new ServiceError(SERVICE_CLOSED, "RemoteActionDockService is closed");
623
+ throw new ActionDockError(SERVICE_CLOSED, "RemoteActionDockService is closed");
534
624
  }
535
625
  }
536
626
  async info() {
537
627
  return this.discovery.listPackages();
538
628
  }
539
629
  async waitForRunCompletion(runId, signal, timeoutMs) {
540
- const startTime = Date.now();
630
+ const startTime = this.clock.monotonic();
541
631
  const maxWaitMs = Math.max(this.baseTimeoutMs, timeoutMs ?? 0);
542
632
  // 服务已关闭时的统一失败结果
543
633
  const closedResult = () => ({
@@ -607,7 +697,7 @@ export class RemoteActionDockService {
607
697
  if (signal?.aborted) {
608
698
  return cancelledResult();
609
699
  }
610
- const remainingWaitMs = Math.max(0, maxWaitMs - (Date.now() - startTime));
700
+ const remainingWaitMs = Math.max(0, maxWaitMs - (this.clock.monotonic() - startTime));
611
701
  if (remainingWaitMs <= 0) {
612
702
  if (this.isClosed) {
613
703
  return closedResult();
@@ -624,7 +714,7 @@ export class RemoteActionDockService {
624
714
  }
625
715
  throw err;
626
716
  }
627
- const waitedMs = Date.now() - startTime;
717
+ const waitedMs = Math.round(this.clock.monotonic() - startTime);
628
718
  return {
629
719
  ok: false,
630
720
  runId,
@@ -638,6 +728,7 @@ export class RemoteActionDockService {
638
728
  baseTimeoutMs: this.baseTimeoutMs,
639
729
  isClosed: () => this.isClosed,
640
730
  getRun: (id) => this.runs.get(id),
731
+ clock: this.clock,
641
732
  }, runId, signal, remainingWaitMs, startTime, maxWaitMs);
642
733
  }
643
734
  async close(_options) {
@@ -1,7 +1,7 @@
1
1
  import { ActionDockError, EVENT_CURSOR_EXPIRED, REMOTE_STREAM_UNAVAILABLE } from "../errors.js";
2
- import { assertSecureTransport } from "../profile/client.js";
2
+ import { assertSecureTransport } from "../client/index.js";
3
3
  import { normalizeServerUrl } from "../profile/manager.js";
4
- import { getInsecureDispatcher } from "../server/dispatcher.js";
4
+ import { getInsecureDispatcher } from "../client/dispatcher.js";
5
5
  import { parseSseMessages } from "./sse-parser.js";
6
6
  /**
7
7
  * 将 SSE 消息载荷解码为统一执行事件。
@@ -4,7 +4,7 @@ import type { CancelResult, ExecutionTicket } from "../execution/types.js";
4
4
  import type { ActionDockHostOptions } from "../host/types.js";
5
5
  import type { RuntimePlatform } from "../platform/types.js";
6
6
  import type { StateEntry } from "../storage/types.js";
7
- import { ActionDockError, type ErrorCode, PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED } from "../errors.js";
7
+ import { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED } from "../errors.js";
8
8
  /**
9
9
  * 公开运行控制选项(RunOptions)。
10
10
  * 面向外部调用方的公开契约,彻底剥离内部 Lineage 字段,严禁污染内部运行时状态。
@@ -27,20 +27,6 @@ export declare const ACTIONDOCK_PROTOCOL_VERSION = "2.0";
27
27
  * 服务端/通信协议错误码常量(单一事实源引用自 errors.ts)。
28
28
  */
29
29
  export { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED };
30
- /**
31
- * 结构化服务通信异常类。
32
- */
33
- export declare class ServiceError extends ActionDockError {
34
- readonly details?: Record<string, unknown>;
35
- constructor(code: ErrorCode, message: string, details?: Record<string, unknown>);
36
- }
37
- /**
38
- * 服务关闭超时异常类。
39
- */
40
- export declare class CloseTimeoutError extends Error {
41
- readonly runIds?: string[];
42
- constructor(message?: string, runIds?: string[]);
43
- }
44
30
  /**
45
31
  * 配置项安全视图契约。
46
32
  * 屏蔽敏感配置原文,仅暴露是否已配置、是否为秘密及实际解析来源。
@@ -86,8 +72,23 @@ export interface ListRunsOptions {
86
72
  status?: string;
87
73
  /** 模糊意图检索模式 */
88
74
  intent?: string;
75
+ /** 幂等请求标识集合(经 idempotency_keys 反查关联运行记录) */
76
+ requestIds?: string[];
77
+ /** 偏移游标条数(跳过的记录数) */
78
+ offset?: number;
89
79
  /** 最大返回记录条数限制 */
90
80
  limit?: number;
81
+ /** 取消信号,用于中止读取请求 */
82
+ signal?: AbortSignal;
83
+ /** 包白名单过滤集合(可选) */
84
+ packageAllowlist?: string[];
85
+ }
86
+ /**
87
+ * 运行记录读取控制选项。
88
+ */
89
+ export interface RunReadOptions {
90
+ /** 取消信号,用于中止读取请求 */
91
+ signal?: AbortSignal;
91
92
  }
92
93
  /**
93
94
  * 远程服务连接配置选项。
@@ -160,9 +161,11 @@ export interface EventsPort {
160
161
  */
161
162
  export interface RunsPort {
162
163
  /** 查询任务执行记录列表 */
163
- list(query?: ListRunsOptions): Promise<RunRecord[]>;
164
+ list(query?: ListRunsOptions, options?: RunReadOptions): Promise<RunRecord[]>;
165
+ /** 统计符合条件的任务执行记录总数 */
166
+ count?(query?: ListRunsOptions, options?: RunReadOptions): Promise<number>;
164
167
  /** 查询指定运行标识的记录详情 */
165
- get(runId: string): Promise<RunRecord | undefined>;
168
+ get(runId: string, options?: RunReadOptions): Promise<RunRecord | undefined>;
166
169
  /** 取消指定在运行的任务 */
167
170
  cancel(runId: string, reason?: string): Promise<CancelResult>;
168
171
  /** 清空历史任务运行记录 */
@@ -200,9 +203,9 @@ export interface StatePort {
200
203
  /** 删除指定包指定动作的作用域状态项 */
201
204
  delete(packageId: string, actionId: string, key: string, options?: StateScopeOptions): Promise<boolean>;
202
205
  /** 列出指定包指定动作在存储中的状态键名列表 */
203
- list(packageId: string, actionId: string, options?: StateScopeOptions): Promise<string[]>;
206
+ list(packageId: string, actionId?: string, options?: StateScopeOptions): Promise<string[]>;
204
207
  /** 清空指定包指定动作在存储中的状态条目 */
205
- clear(packageId: string, actionId: string, options?: StateScopeOptions): Promise<number>;
208
+ clear(packageId: string, actionId?: string, options?: StateScopeOptions): Promise<number>;
206
209
  /** 列出指定包在存储中的状态条目(含完整元数据) */
207
210
  listEntries?(packageId: string, options?: any): Promise<StateEntry[]>;
208
211
  }
@@ -1,4 +1,4 @@
1
- import { ActionDockError, PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED, } from "../errors.js";
1
+ import { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED, } from "../errors.js";
2
2
  /**
3
3
  * 统一协议版本常量。
4
4
  */
@@ -7,26 +7,3 @@ export const ACTIONDOCK_PROTOCOL_VERSION = "2.0";
7
7
  * 服务端/通信协议错误码常量(单一事实源引用自 errors.ts)。
8
8
  */
9
9
  export { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED };
10
- /**
11
- * 结构化服务通信异常类。
12
- */
13
- export class ServiceError extends ActionDockError {
14
- details;
15
- constructor(code, message, details) {
16
- super(code, message, details);
17
- this.name = "ServiceError";
18
- this.details = details;
19
- Object.setPrototypeOf(this, ServiceError.prototype);
20
- }
21
- }
22
- /**
23
- * 服务关闭超时异常类。
24
- */
25
- export class CloseTimeoutError extends Error {
26
- runIds;
27
- constructor(message = "Service close operation timed out", runIds) {
28
- super(message);
29
- this.name = "CloseTimeoutError";
30
- this.runIds = runIds;
31
- }
32
- }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 统一时间与时钟基础契约。
3
3
  *
4
- * 单一事实源:Clock 接口与 SystemClock 实现定义于此,runtime/clock.ts 与
4
+ * 单一事实源:Clock 接口与 SystemClock 实现定义于此,
5
5
  * storage/types.ts 一律从本文件引用或转发,禁止重复实现。
6
6
  */
7
7
  /**
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 统一时间与时钟基础契约。
3
3
  *
4
- * 单一事实源:Clock 接口与 SystemClock 实现定义于此,runtime/clock.ts 与
4
+ * 单一事实源:Clock 接口与 SystemClock 实现定义于此,
5
5
  * storage/types.ts 一律从本文件引用或转发,禁止重复实现。
6
6
  */
7
7
  /**
@@ -1,4 +1,5 @@
1
- import { cleanStaleQuarantines, isProcessAlive, parseQuarantineTimestamp, safeRemoveStaleReclaimGuard, safeRollbackLock } from "./lock-core.js";
1
+ import { isProcessAlive } from "../utils/index.js";
2
+ import { type FileLockHandle } from "./file-lock.js";
2
3
  /**
3
4
  * 数据目录排他锁元数据契约。
4
5
  */
@@ -9,7 +10,7 @@ export interface DataDirLockInfo {
9
10
  hostname: string;
10
11
  /** 宿主会话令牌 */
11
12
  sessionToken: string;
12
- /** 内部排他锁令牌(用于防护伪造 sessionToken 的所有权隔离) */
13
+ /** 内部排他锁令牌 */
13
14
  lockToken?: string;
14
15
  /** 创建时间戳(ISO 8601 格式) */
15
16
  createdAt: string;
@@ -18,12 +19,11 @@ export interface DataDirLockInfo {
18
19
  /** 宿主会话标识 */
19
20
  hostSessionId?: string;
20
21
  }
21
- export { isProcessAlive, parseQuarantineTimestamp, cleanStaleQuarantines, safeRemoveStaleReclaimGuard, safeRollbackLock, };
22
+ export { isProcessAlive };
22
23
  /**
23
- * 安全释放主锁。
24
- * 仅当锁目录中持有当前 lockToken / sessionToken 时才删除,杜绝删除他人新锁。
24
+ * 安全释放主锁(兼容导出)。
25
25
  */
26
- export declare function safeReleaseLock(lockPath: string, expectedSessionToken: string, expectedLockToken?: string): void;
26
+ export declare function safeReleaseLock(lockPath: string, expectedSessionToken?: string, expectedLockToken?: string): void;
27
27
  /**
28
28
  * 数据目录排他文件锁管理器。
29
29
  * 负责在数据目录下维护 .actiondock.data.lock 排他文件锁,记录宿主进程与子进程运行状态,
@@ -32,8 +32,9 @@ export declare function safeReleaseLock(lockPath: string, expectedSessionToken:
32
32
  export declare class DataDirLock {
33
33
  private readonly lockDirPath;
34
34
  private readonly info;
35
+ private readonly handle?;
35
36
  private released;
36
- constructor(lockDirPath: string, info: DataDirLockInfo);
37
+ constructor(lockDirPath: string, info: DataDirLockInfo, handle?: FileLockHandle);
37
38
  /**
38
39
  * 获取当前排他锁元数据信息。
39
40
  */
@@ -52,7 +53,6 @@ export declare class DataDirLock {
52
53
  unregisterChildPid(pid: number): void;
53
54
  /**
54
55
  * 将当前锁元数据刷新持久化至磁盘文件。
55
- * 先写入临时文件再通过 renameSync 原子替换,消除 truncate 空文件窗口。
56
56
  */
57
57
  private flush;
58
58
  /**
@@ -60,20 +60,8 @@ export declare class DataDirLock {
60
60
  */
61
61
  release(): void;
62
62
  /**
63
- * 尝试获取指定数据目录的排他锁。
64
- *
65
- * 仲裁规则由目录锁内核(lock-core)统一承载:
66
- * - 引入所有竞争者均遵守的原子 reclaim guard 机制(lockDirPath.reclaim),每个 guard 具备全局唯一 guardToken。
67
- * - 基于原子目录创建 mkdirSync(lockDirPath, { mode: 0o700 }) 确立所有权,并在其下存放 metadata.json。
68
- * - 兼容已有常规文件锁(如老版本或测试 mock 场景)。
69
- * - 若检测到 reclaim guard 存在且处于宽限期内或持有者存活,必须等待,禁止抢先创建主锁。
70
- * - 若锁目录已存在,通过宽限期机制防止将并发写入中的元数据误判为锁死亡。
71
- * - 若主进程仍处于存活状态,抛出 DATA_DIR_IN_USE 错误拒绝并发启动。
72
- * - 若主进程已退出但仍有子进程存活,抛出 DATA_DIR_RECOVERY_REQUIRED 错误。
73
- * - 当识别到主锁为陈旧锁时,竞争者必须先原子竞争获取 reclaim guard。
74
- * - 仅成功获取 reclaim guard 的唯一胜利者获准执行:复核主锁陈旧性 -> 验证并清理陈旧主锁 -> 原子创建新主锁并写入自身元数据 -> 清理 reclaim guard。
75
- * - 竞争失败者等待并 continue 重试;若 reclaim guard 持有者意外崩溃,其他竞争者在超过宽限期且 PID 已死后通过 safeRemoveStaleReclaimGuard 核对 guardToken 并清理。
76
- * - 严禁在获取新主锁遇到 EEXIST 时盲目 rmSync;后检发现他人活跃 guard 时,仅通过 safeRollbackLock 核对自身 sessionToken 回滚自身锁。
63
+ * 获取指定数据目录的排他锁。
64
+ * 基于统一轻量文件锁原语实现,非阻塞且支持原子抢占与崩溃恢复。
77
65
  *
78
66
  * @param dataDir 目标数据存储目录物理绝对路径
79
67
  * @param options 锁配置参数
@@ -1,17 +1,17 @@
1
1
  import { existsSync, mkdirSync, renameSync, statSync, writeFileSync } from "node:fs";
2
2
  import { hostname } from "node:os";
3
- import { dirname, join } from "node:path";
3
+ import { join } from "node:path";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { DATA_DIR_IN_USE, DATA_DIR_RECOVERY_REQUIRED } from "../errors.js";
6
- import { acquireDirectoryLock, cleanStaleQuarantines, isProcessAlive, parseQuarantineTimestamp, safeReleaseLock as coreSafeReleaseLock, safeRemoveStaleReclaimGuard, safeRollbackLock, } from "./lock-core.js";
6
+ import { isProcessAlive } from "../utils/index.js";
7
+ import { acquireFileLockSync, safeReleaseFileLockSync, } from "./file-lock.js";
7
8
  const LOCK_DIR_NAME = ".actiondock.data.lock";
8
- export { isProcessAlive, parseQuarantineTimestamp, cleanStaleQuarantines, safeRemoveStaleReclaimGuard, safeRollbackLock, };
9
+ export { isProcessAlive };
9
10
  /**
10
- * 安全释放主锁。
11
- * 仅当锁目录中持有当前 lockToken / sessionToken 时才删除,杜绝删除他人新锁。
11
+ * 安全释放主锁(兼容导出)。
12
12
  */
13
13
  export function safeReleaseLock(lockPath, expectedSessionToken, expectedLockToken) {
14
- coreSafeReleaseLock(lockPath, expectedSessionToken, expectedLockToken);
14
+ safeReleaseFileLockSync(lockPath, expectedLockToken, expectedSessionToken);
15
15
  }
16
16
  /**
17
17
  * 数据目录排他文件锁管理器。
@@ -21,10 +21,12 @@ export function safeReleaseLock(lockPath, expectedSessionToken, expectedLockToke
21
21
  export class DataDirLock {
22
22
  lockDirPath;
23
23
  info;
24
+ handle;
24
25
  released = false;
25
- constructor(lockDirPath, info) {
26
+ constructor(lockDirPath, info, handle) {
26
27
  this.lockDirPath = lockDirPath;
27
28
  this.info = info;
29
+ this.handle = handle;
28
30
  }
29
31
  /**
30
32
  * 获取当前排他锁元数据信息。
@@ -65,7 +67,6 @@ export class DataDirLock {
65
67
  }
66
68
  /**
67
69
  * 将当前锁元数据刷新持久化至磁盘文件。
68
- * 先写入临时文件再通过 renameSync 原子替换,消除 truncate 空文件窗口。
69
70
  */
70
71
  flush() {
71
72
  try {
@@ -91,9 +92,6 @@ export class DataDirLock {
91
92
  }
92
93
  }
93
94
  catch (err) {
94
- // 元数据刷新失败不可中断宿主启动,但绝不能静默吞没:childPids 是
95
- // DATA_DIR_RECOVERY_REQUIRED 判定的唯一依据,丢失会使恢复决策退化为直接接管。
96
- // 输出单行告警携带原因,保证降级事件可观测。
97
95
  const reason = err instanceof Error ? err.message : String(err);
98
96
  console.warn(`[actiondock] data dir lock metadata flush failed (lock='${this.lockDirPath}'): ${reason}`);
99
97
  }
@@ -105,30 +103,21 @@ export class DataDirLock {
105
103
  if (this.released)
106
104
  return;
107
105
  this.released = true;
108
- coreSafeReleaseLock(this.lockDirPath, this.info.sessionToken, this.info.lockToken);
109
- cleanStaleQuarantines(dirname(this.lockDirPath), LOCK_DIR_NAME);
106
+ if (this.handle) {
107
+ this.handle.release();
108
+ }
109
+ else {
110
+ safeReleaseFileLockSync(this.lockDirPath, this.info.lockToken, this.info.sessionToken);
111
+ }
110
112
  }
111
113
  /**
112
- * 尝试获取指定数据目录的排他锁。
113
- *
114
- * 仲裁规则由目录锁内核(lock-core)统一承载:
115
- * - 引入所有竞争者均遵守的原子 reclaim guard 机制(lockDirPath.reclaim),每个 guard 具备全局唯一 guardToken。
116
- * - 基于原子目录创建 mkdirSync(lockDirPath, { mode: 0o700 }) 确立所有权,并在其下存放 metadata.json。
117
- * - 兼容已有常规文件锁(如老版本或测试 mock 场景)。
118
- * - 若检测到 reclaim guard 存在且处于宽限期内或持有者存活,必须等待,禁止抢先创建主锁。
119
- * - 若锁目录已存在,通过宽限期机制防止将并发写入中的元数据误判为锁死亡。
120
- * - 若主进程仍处于存活状态,抛出 DATA_DIR_IN_USE 错误拒绝并发启动。
121
- * - 若主进程已退出但仍有子进程存活,抛出 DATA_DIR_RECOVERY_REQUIRED 错误。
122
- * - 当识别到主锁为陈旧锁时,竞争者必须先原子竞争获取 reclaim guard。
123
- * - 仅成功获取 reclaim guard 的唯一胜利者获准执行:复核主锁陈旧性 -> 验证并清理陈旧主锁 -> 原子创建新主锁并写入自身元数据 -> 清理 reclaim guard。
124
- * - 竞争失败者等待并 continue 重试;若 reclaim guard 持有者意外崩溃,其他竞争者在超过宽限期且 PID 已死后通过 safeRemoveStaleReclaimGuard 核对 guardToken 并清理。
125
- * - 严禁在获取新主锁遇到 EEXIST 时盲目 rmSync;后检发现他人活跃 guard 时,仅通过 safeRollbackLock 核对自身 sessionToken 回滚自身锁。
114
+ * 获取指定数据目录的排他锁。
115
+ * 基于统一轻量文件锁原语实现,非阻塞且支持原子抢占与崩溃恢复。
126
116
  *
127
117
  * @param dataDir 目标数据存储目录物理绝对路径
128
118
  * @param options 锁配置参数
129
119
  */
130
120
  static acquire(dataDir, options = {}) {
131
- const timeoutMs = options.acquireTimeoutMs ?? 5000;
132
121
  if (!existsSync(dataDir)) {
133
122
  mkdirSync(dataDir, { recursive: true, mode: 0o700 });
134
123
  }
@@ -144,45 +133,28 @@ export class DataDirLock {
144
133
  childPids: [],
145
134
  hostSessionId: options.hostSessionId,
146
135
  };
147
- const content = JSON.stringify(newLockInfo, null, 2);
148
- return acquireDirectoryLock({
149
- lockPath: lockDirPath,
150
- parentDir: dataDir,
151
- basePrefix: LOCK_DIR_NAME,
152
- metadataContent: content,
153
- sessionToken: token,
154
- lockToken,
155
- acquireTimeoutMs: options.acquireTimeoutMs,
156
- createLockError(message) {
157
- const timeoutErr = new Error(message);
158
- timeoutErr.code = DATA_DIR_IN_USE;
159
- return timeoutErr;
160
- },
161
- messages: {
162
- timeoutWaitingReclaimGuard: (holderPid) => `DATA_DIR_IN_USE: Timeout waiting for active reclaim guard (PID ${holderPid ?? "unknown"}) on data directory '${dataDir}' after ${timeoutMs}ms`,
163
- timeoutAcquireBlockedByGuard: (holderPid) => `DATA_DIR_IN_USE: Timeout acquiring lock on data directory '${dataDir}' due to active reclaim guard (PID ${holderPid}) after ${timeoutMs}ms`,
164
- timeoutAcquire: () => `DATA_DIR_IN_USE: Timeout acquiring lock on data directory '${dataDir}' after ${timeoutMs}ms`,
165
- timeoutGracePeriod: () => `DATA_DIR_IN_USE: Timeout waiting for lock grace period on data directory '${dataDir}' after ${timeoutMs}ms`,
166
- timeoutGuardContention: () => `DATA_DIR_IN_USE: Timeout contending for reclaim guard on data directory '${dataDir}' after ${timeoutMs}ms`,
167
- timeoutReclaimStale: () => `DATA_DIR_IN_USE: Timeout reclaiming stale lock on data directory '${dataDir}' after ${timeoutMs}ms`,
168
- timeoutCreate: () => `DATA_DIR_IN_USE: Timeout creating primary lock on data directory '${dataDir}' after ${timeoutMs}ms`,
136
+ const handle = acquireFileLockSync(lockDirPath, {
137
+ metadata: newLockInfo,
138
+ createLockError() {
139
+ const inUseErr = new Error(`DATA_DIR_IN_USE: Data directory '${dataDir}' is in use by another active Host process`);
140
+ inUseErr.code = DATA_DIR_IN_USE;
141
+ return inUseErr;
169
142
  },
170
- assertStaleHolderReclaimable(info) {
143
+ assertHolderReclaimable(info) {
171
144
  const holderPid = info.pid;
172
- if (isProcessAlive(holderPid)) {
145
+ if (holderPid && isProcessAlive(holderPid)) {
173
146
  const inUseErr = new Error(`DATA_DIR_IN_USE: Data directory '${dataDir}' is in use by another active Host process (PID ${holderPid})`);
174
147
  inUseErr.code = DATA_DIR_IN_USE;
175
148
  throw inUseErr;
176
149
  }
177
- // 主进程已死亡,检查关联子进程存活状态
178
- const activeChildren = (info.childPids || []).filter((childPid) => isProcessAlive(childPid));
150
+ const activeChildren = info.childPids?.filter((childPid) => isProcessAlive(childPid)) ?? [];
179
151
  if (activeChildren.length > 0) {
180
- const recoveryErr = new Error(`DATA_DIR_RECOVERY_REQUIRED: Data directory recovery required for '${dataDir}': previous host (PID ${holderPid}) exited but child processes (${activeChildren.join(", ")}) are still running`);
152
+ const recoveryErr = new Error(`DATA_DIR_RECOVERY_REQUIRED: Data directory recovery required for '${dataDir}': previous host (PID ${holderPid ?? "unknown"}) exited but child processes (${activeChildren.join(", ")}) are still running`);
181
153
  recoveryErr.code = DATA_DIR_RECOVERY_REQUIRED;
182
154
  throw recoveryErr;
183
155
  }
184
156
  },
185
- onAcquired: () => new DataDirLock(lockDirPath, newLockInfo),
186
157
  });
158
+ return new DataDirLock(lockDirPath, newLockInfo, handle);
187
159
  }
188
160
  }