@actiondock/core 2.20.0 → 2.21.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 (67) hide show
  1. package/dist/errors.d.ts +11 -1
  2. package/dist/errors.js +23 -0
  3. package/dist/execution/service.js +36 -5
  4. package/dist/host/host.d.ts +11 -0
  5. package/dist/host/host.js +258 -39
  6. package/dist/host/types.d.ts +10 -1
  7. package/dist/index.d.ts +8 -1
  8. package/dist/index.js +5 -1
  9. package/dist/invocation/types.d.ts +31 -0
  10. package/dist/invocation/types.js +10 -1
  11. package/dist/package/index.d.ts +1 -1
  12. package/dist/package/runtime.js +18 -4
  13. package/dist/platform/node.d.ts +6 -0
  14. package/dist/platform/node.js +59 -16
  15. package/dist/platform/types.d.ts +11 -0
  16. package/dist/process/command-executor.d.ts +35 -0
  17. package/dist/process/command-executor.js +326 -0
  18. package/dist/process/index.d.ts +1 -0
  19. package/dist/process/index.js +1 -0
  20. package/dist/process/process-driver.d.ts +19 -6
  21. package/dist/process/process-driver.js +223 -54
  22. package/dist/process/run-executor.js +101 -47
  23. package/dist/project/dependencies.d.ts +92 -0
  24. package/dist/project/dependencies.js +543 -0
  25. package/dist/project/index.d.ts +3 -0
  26. package/dist/project/index.js +3 -0
  27. package/dist/project/init.js +2 -2
  28. package/dist/project/loader.js +15 -30
  29. package/dist/project/package-manager.d.ts +40 -0
  30. package/dist/project/package-manager.js +224 -0
  31. package/dist/project/preparation.d.ts +8 -0
  32. package/dist/project/preparation.js +130 -0
  33. package/dist/project/transactions.d.ts +5 -1
  34. package/dist/project/transactions.js +35 -75
  35. package/dist/runtime/context.d.ts +21 -2
  36. package/dist/runtime/context.js +42 -17
  37. package/dist/runtime/run-persistence.d.ts +1 -1
  38. package/dist/runtime/run-persistence.js +4 -3
  39. package/dist/security/config-cipher.d.ts +54 -0
  40. package/dist/security/config-cipher.js +190 -0
  41. package/dist/security/config-key.d.ts +35 -0
  42. package/dist/security/config-key.js +469 -0
  43. package/dist/server/routes/config.js +20 -1
  44. package/dist/service/factory.js +3 -0
  45. package/dist/service/types.d.ts +8 -0
  46. package/dist/storage/config-value.d.ts +33 -0
  47. package/dist/storage/config-value.js +102 -0
  48. package/dist/storage/file-lock.js +35 -21
  49. package/dist/storage/index.d.ts +24 -2
  50. package/dist/storage/index.js +65 -2
  51. package/dist/storage/migrations/index.d.ts +40 -0
  52. package/dist/storage/migrations/index.js +96 -0
  53. package/dist/storage/migrations/v2-to-v3.d.ts +16 -0
  54. package/dist/storage/migrations/v2-to-v3.js +116 -0
  55. package/dist/storage/sqlite-config.d.ts +62 -3
  56. package/dist/storage/sqlite-config.js +163 -9
  57. package/dist/storage/sqlite-driver.d.ts +5 -2
  58. package/dist/storage/sqlite-driver.js +24 -3
  59. package/dist/storage/sqlite-schema.d.ts +20 -2
  60. package/dist/storage/sqlite-schema.js +129 -132
  61. package/dist/storage/sqlite.d.ts +7 -2
  62. package/dist/storage/sqlite.js +64 -13
  63. package/dist/storage/types.d.ts +72 -4
  64. package/dist/storage/types.js +7 -2
  65. package/dist/version.d.ts +1 -1
  66. package/dist/version.js +1 -1
  67. package/package.json +2 -2
package/dist/errors.d.ts CHANGED
@@ -71,6 +71,10 @@ export declare const CAPABILITY_UNAVAILABLE = "CAPABILITY_UNAVAILABLE";
71
71
  export declare const STATE_KEY_NOT_FOUND = "STATE_KEY_NOT_FOUND";
72
72
  /** SQLite 数据库忙或锁定 */
73
73
  export declare const STORAGE_BUSY = "STORAGE_BUSY";
74
+ /** 所需生产依赖未就绪且当前环境不允许或不满足自动首装条件 */
75
+ export declare const DEPENDENCIES_NOT_READY = "DEPENDENCIES_NOT_READY";
76
+ /** 自动依赖安装进程启动失败、超时或非零退出 */
77
+ export declare const DEPENDENCY_INSTALL_FAILED = "DEPENDENCY_INSTALL_FAILED";
74
78
  /** 工程事务锁被其他活跃进程持有 */
75
79
  export declare const PROJECT_BUSY = "PROJECT_BUSY";
76
80
  /** 工程存在未恢复的崩溃事务,需要执行恢复流程 */
@@ -300,6 +304,12 @@ export declare const PROJECT_CONFIG_NOT_FOUND = "PROJECT_CONFIG_NOT_FOUND";
300
304
  export declare const MANIFEST_LOAD_FAILED = "MANIFEST_LOAD_FAILED";
301
305
  /** 配置项数据值不符合声明的模式规范 */
302
306
  export declare const CONFIG_VALUE_INVALID = "CONFIG_VALUE_INVALID";
307
+ /** 配置加密密钥缺失、无效或与存储域身份不符等加密配置错误 */
308
+ export declare const CONFIG_ENCRYPTION_ERROR = "CONFIG_ENCRYPTION_ERROR";
309
+ /** 配置密文解密失败(信封格式非法、版本未知、密钥身份不符或认证校验失败) */
310
+ export declare const CONFIG_DECRYPT_FAILED = "CONFIG_DECRYPT_FAILED";
311
+ /** 配置加密与解密错误按结构化错误码识别,跨模块副本也不得吞没或覆盖。 */
312
+ export declare function isConfigEncryptionError(error: unknown): boolean;
303
313
  /** 动作标识符不符合命名规范 */
304
314
  export declare const INVALID_ACTION_ID = "INVALID_ACTION_ID";
305
315
  /** 规程标识符不符合命名规范 */
@@ -350,7 +360,7 @@ export declare function resolveExecutionHint(targetRef: string, error?: {
350
360
  /**
351
361
  * 集中定义的标准结构化错误码联合类型。
352
362
  */
353
- export type ErrorCode = typeof PACKAGE_NOT_FOUND | typeof ACTION_PACKAGE_VERSION_CONFLICT | typeof ACTION_NOT_FOUND | typeof ACTION_LOAD_FAILED | typeof ACTION_FAILED | typeof ACTION_TIMEOUT | typeof ACTION_CANCELLED | typeof INPUT_VALIDATION_FAILED | typeof OUTPUT_VALIDATION_FAILED | typeof INPUT_NOT_JSON | typeof OUTPUT_NOT_JSON | typeof ACTION_SUBRUN_LIMIT | typeof ACTION_CALL_CYCLE | typeof UNDECLARED_ACTION_DEPENDENCY | typeof INVALID_ACTION_REF | typeof IDEMPOTENCY_CONFLICT | typeof EXECUTION_FAILED | typeof UNHANDLED_EXECUTION_ERROR | typeof RUN_REPOSITORY_UNAVAILABLE | typeof RUN_PERSISTENCE_FAILED | typeof STANDALONE_ASYNC_UNSUPPORTED | typeof INVOCATION_UNSUPPORTED | typeof HOST_PROCESS_EXITED | typeof EXECUTION_ABORTED | typeof TIMEOUT | typeof SERVICE_CLOSED | typeof NETWORK_ERROR | typeof INSECURE_TRANSPORT | typeof REMOTE_REQUEST_FAILED | typeof REMOTE_STREAM_UNAVAILABLE | typeof CAPABILITY_UNAVAILABLE | typeof STATE_KEY_NOT_FOUND | typeof STORAGE_BUSY | typeof PROJECT_BUSY | typeof PROJECT_RECOVERY_REQUIRED | typeof EVENT_BACKPRESSURE_LIMIT | typeof EVENT_CURSOR_EXPIRED | typeof PROCESS_OUTPUT_LIMIT | typeof PROCESS_SPAWN_ERROR | typeof PROCESS_CANCELLED | typeof PROCESS_TIMEOUT | typeof UNAUTHORIZED | typeof NOT_FOUND | typeof SERVER_ERROR | typeof BAD_REQUEST | typeof PACKAGE_FORBIDDEN | typeof ACTION_FORBIDDEN | typeof PLAYBOOK_NOT_FOUND | typeof REQUEST_TOO_LARGE | typeof ACTION_START_FAILED | typeof ACTION_EXECUTION_ERROR | typeof ACTIONS_LIST_ERROR | typeof PLAYBOOKS_LIST_ERROR | typeof RUNS_LIST_ERROR | typeof RUNS_CLEAR_ERROR | typeof INFO_ERROR | typeof PACKAGES_INFO_ERROR | typeof DOCTOR_ERROR | typeof RUN_INTERRUPTED | typeof CONFIG_ENV_ERROR | typeof CONFIG_LIST_ERROR | typeof CONFIG_SET_ERROR | typeof CONFIG_DELETE_ERROR | typeof STATE_LIST_ERROR | typeof STATE_CLEAR_ERROR | typeof STATE_KEY_ERROR | typeof DATA_DIR_IN_USE | typeof DATA_DIR_RECOVERY_REQUIRED | typeof UNSUPPORTED_STORAGE_SCHEMA | typeof STORED_ERROR_DECODE_FAILED | typeof PROCESS_FAILED | typeof GENERATED_TYPES_OUTDATED | typeof PROTOCOL_UNSUPPORTED | typeof SERVICE_RESULT_UNKNOWN | typeof MCP_TOOL_NAME_COLLISION | typeof ACCESS_DENIED | typeof UNSUPPORTED_CAPABILITY | typeof CONTROL_BUSY | typeof CONTROL_EXPIRED | typeof CONTROL_REVOKED | typeof PROCESS_QUARANTINED | typeof PROCESS_LOST | typeof REQUEST_CONFLICT | typeof INPUT_OUTCOME_UNKNOWN | typeof OUTPUT_GAP | typeof OUTPUT_UNAVAILABLE | typeof INVALID_CURSOR | typeof QUEUE_FULL | typeof QUOTA_EXCEEDED | typeof INPUT_CLOSED | typeof INVALID_FLAT_ARGUMENT | typeof INVALID_JSON | typeof INVALID_JSON_LITERAL | typeof INPUT_PATH_CONFLICT | typeof FLAT_INPUT_LIMIT_EXCEEDED | typeof INPUT_LIMIT_EXCEEDED | typeof INPUT_POLICY_VIOLATION | typeof INPUT_CONFLICT | typeof INPUT_FILE_NOT_FOUND | typeof INPUT_FILE_READ_FAILED | typeof INVALID_ARGUMENT | typeof PACKAGE_NOT_ALLOWED | typeof INVALID_PACKAGE_ID | typeof PATH_TRAVERSAL | typeof AMBIGUOUS_STATE_KEY | typeof RUN_NOT_FOUND | typeof RUN_ALREADY_FINISHED | typeof IPC_ERROR | typeof SERVICE_ERROR | typeof PROFILE_NOT_FOUND | typeof LOCKFILE_READ_FAILED | typeof LOCKFILE_CORRUPTED | typeof LOCKFILE_INVALID | typeof UNSUPPORTED_LOCKFILE_VERSION | typeof MANIFEST_READ_FAILED | typeof MANIFEST_CORRUPTED | typeof MANIFEST_INVALID | typeof UNSUPPORTED_MANIFEST_SCHEMA | typeof MANIFEST_NOT_FOUND | typeof MANIFEST_DIGEST_MISMATCH | typeof REGISTRY_CORRUPTED | typeof ASYNC_TRANSACTION_UNSUPPORTED | typeof STORAGE_CLOSED | typeof PACKAGE_ID_CONFLICT | typeof INVALID_PATH | typeof STORAGE_INIT_FAILED | typeof INVALID_STATE | typeof PROCESS_NOT_FOUND | typeof MODULE_RESOLVE_FAILED | typeof EXECUTION_CONCURRENCY_LIMIT | typeof PROJECT_CONFIG_NOT_FOUND | typeof MANIFEST_LOAD_FAILED | typeof CONFIG_VALUE_INVALID | typeof INVALID_ACTION_ID | typeof INVALID_PLAYBOOK_ID | typeof PROJECT_ROOT_NOT_FOUND;
363
+ export type ErrorCode = typeof PACKAGE_NOT_FOUND | typeof ACTION_PACKAGE_VERSION_CONFLICT | typeof ACTION_NOT_FOUND | typeof ACTION_LOAD_FAILED | typeof ACTION_FAILED | typeof ACTION_TIMEOUT | typeof ACTION_CANCELLED | typeof INPUT_VALIDATION_FAILED | typeof OUTPUT_VALIDATION_FAILED | typeof INPUT_NOT_JSON | typeof OUTPUT_NOT_JSON | typeof ACTION_SUBRUN_LIMIT | typeof ACTION_CALL_CYCLE | typeof UNDECLARED_ACTION_DEPENDENCY | typeof INVALID_ACTION_REF | typeof IDEMPOTENCY_CONFLICT | typeof EXECUTION_FAILED | typeof UNHANDLED_EXECUTION_ERROR | typeof RUN_REPOSITORY_UNAVAILABLE | typeof RUN_PERSISTENCE_FAILED | typeof STANDALONE_ASYNC_UNSUPPORTED | typeof INVOCATION_UNSUPPORTED | typeof HOST_PROCESS_EXITED | typeof EXECUTION_ABORTED | typeof TIMEOUT | typeof SERVICE_CLOSED | typeof NETWORK_ERROR | typeof INSECURE_TRANSPORT | typeof REMOTE_REQUEST_FAILED | typeof REMOTE_STREAM_UNAVAILABLE | typeof CAPABILITY_UNAVAILABLE | typeof STATE_KEY_NOT_FOUND | typeof STORAGE_BUSY | typeof DEPENDENCIES_NOT_READY | typeof DEPENDENCY_INSTALL_FAILED | typeof PROJECT_BUSY | typeof PROJECT_RECOVERY_REQUIRED | typeof EVENT_BACKPRESSURE_LIMIT | typeof EVENT_CURSOR_EXPIRED | typeof PROCESS_OUTPUT_LIMIT | typeof PROCESS_SPAWN_ERROR | typeof PROCESS_CANCELLED | typeof PROCESS_TIMEOUT | typeof UNAUTHORIZED | typeof NOT_FOUND | typeof SERVER_ERROR | typeof BAD_REQUEST | typeof PACKAGE_FORBIDDEN | typeof ACTION_FORBIDDEN | typeof PLAYBOOK_NOT_FOUND | typeof REQUEST_TOO_LARGE | typeof ACTION_START_FAILED | typeof ACTION_EXECUTION_ERROR | typeof ACTIONS_LIST_ERROR | typeof PLAYBOOKS_LIST_ERROR | typeof RUNS_LIST_ERROR | typeof RUNS_CLEAR_ERROR | typeof INFO_ERROR | typeof PACKAGES_INFO_ERROR | typeof DOCTOR_ERROR | typeof RUN_INTERRUPTED | typeof CONFIG_ENV_ERROR | typeof CONFIG_LIST_ERROR | typeof CONFIG_SET_ERROR | typeof CONFIG_DELETE_ERROR | typeof STATE_LIST_ERROR | typeof STATE_CLEAR_ERROR | typeof STATE_KEY_ERROR | typeof DATA_DIR_IN_USE | typeof DATA_DIR_RECOVERY_REQUIRED | typeof UNSUPPORTED_STORAGE_SCHEMA | typeof STORED_ERROR_DECODE_FAILED | typeof PROCESS_FAILED | typeof GENERATED_TYPES_OUTDATED | typeof PROTOCOL_UNSUPPORTED | typeof SERVICE_RESULT_UNKNOWN | typeof MCP_TOOL_NAME_COLLISION | typeof ACCESS_DENIED | typeof UNSUPPORTED_CAPABILITY | typeof CONTROL_BUSY | typeof CONTROL_EXPIRED | typeof CONTROL_REVOKED | typeof PROCESS_QUARANTINED | typeof PROCESS_LOST | typeof REQUEST_CONFLICT | typeof INPUT_OUTCOME_UNKNOWN | typeof OUTPUT_GAP | typeof OUTPUT_UNAVAILABLE | typeof INVALID_CURSOR | typeof QUEUE_FULL | typeof QUOTA_EXCEEDED | typeof INPUT_CLOSED | typeof INVALID_FLAT_ARGUMENT | typeof INVALID_JSON | typeof INVALID_JSON_LITERAL | typeof INPUT_PATH_CONFLICT | typeof FLAT_INPUT_LIMIT_EXCEEDED | typeof INPUT_LIMIT_EXCEEDED | typeof INPUT_POLICY_VIOLATION | typeof INPUT_CONFLICT | typeof INPUT_FILE_NOT_FOUND | typeof INPUT_FILE_READ_FAILED | typeof INVALID_ARGUMENT | typeof PACKAGE_NOT_ALLOWED | typeof INVALID_PACKAGE_ID | typeof PATH_TRAVERSAL | typeof AMBIGUOUS_STATE_KEY | typeof RUN_NOT_FOUND | typeof RUN_ALREADY_FINISHED | typeof IPC_ERROR | typeof SERVICE_ERROR | typeof PROFILE_NOT_FOUND | typeof LOCKFILE_READ_FAILED | typeof LOCKFILE_CORRUPTED | typeof LOCKFILE_INVALID | typeof UNSUPPORTED_LOCKFILE_VERSION | typeof MANIFEST_READ_FAILED | typeof MANIFEST_CORRUPTED | typeof MANIFEST_INVALID | typeof UNSUPPORTED_MANIFEST_SCHEMA | typeof MANIFEST_NOT_FOUND | typeof MANIFEST_DIGEST_MISMATCH | typeof REGISTRY_CORRUPTED | typeof ASYNC_TRANSACTION_UNSUPPORTED | typeof STORAGE_CLOSED | typeof PACKAGE_ID_CONFLICT | typeof INVALID_PATH | typeof STORAGE_INIT_FAILED | typeof INVALID_STATE | typeof PROCESS_NOT_FOUND | typeof MODULE_RESOLVE_FAILED | typeof EXECUTION_CONCURRENCY_LIMIT | typeof PROJECT_CONFIG_NOT_FOUND | typeof MANIFEST_LOAD_FAILED | typeof CONFIG_VALUE_INVALID | typeof CONFIG_ENCRYPTION_ERROR | typeof CONFIG_DECRYPT_FAILED | typeof INVALID_ACTION_ID | typeof INVALID_PLAYBOOK_ID | typeof PROJECT_ROOT_NOT_FOUND;
354
364
  /** 全仓统一错误码类型别名 */
355
365
  export type ActionDockErrorCode = ErrorCode;
356
366
  /**
package/dist/errors.js CHANGED
@@ -71,6 +71,10 @@ export const CAPABILITY_UNAVAILABLE = "CAPABILITY_UNAVAILABLE";
71
71
  export const STATE_KEY_NOT_FOUND = "STATE_KEY_NOT_FOUND";
72
72
  /** SQLite 数据库忙或锁定 */
73
73
  export const STORAGE_BUSY = "STORAGE_BUSY";
74
+ /** 所需生产依赖未就绪且当前环境不允许或不满足自动首装条件 */
75
+ export const DEPENDENCIES_NOT_READY = "DEPENDENCIES_NOT_READY";
76
+ /** 自动依赖安装进程启动失败、超时或非零退出 */
77
+ export const DEPENDENCY_INSTALL_FAILED = "DEPENDENCY_INSTALL_FAILED";
74
78
  /** 工程事务锁被其他活跃进程持有 */
75
79
  export const PROJECT_BUSY = "PROJECT_BUSY";
76
80
  /** 工程存在未恢复的崩溃事务,需要执行恢复流程 */
@@ -301,6 +305,19 @@ export const PROJECT_CONFIG_NOT_FOUND = "PROJECT_CONFIG_NOT_FOUND";
301
305
  export const MANIFEST_LOAD_FAILED = "MANIFEST_LOAD_FAILED";
302
306
  /** 配置项数据值不符合声明的模式规范 */
303
307
  export const CONFIG_VALUE_INVALID = "CONFIG_VALUE_INVALID";
308
+ /** 配置加密密钥缺失、无效或与存储域身份不符等加密配置错误 */
309
+ export const CONFIG_ENCRYPTION_ERROR = "CONFIG_ENCRYPTION_ERROR";
310
+ /** 配置密文解密失败(信封格式非法、版本未知、密钥身份不符或认证校验失败) */
311
+ export const CONFIG_DECRYPT_FAILED = "CONFIG_DECRYPT_FAILED";
312
+ const CONFIG_ENCRYPTION_ERROR_CODES = new Set([
313
+ CONFIG_ENCRYPTION_ERROR,
314
+ CONFIG_DECRYPT_FAILED,
315
+ ]);
316
+ /** 配置加密与解密错误按结构化错误码识别,跨模块副本也不得吞没或覆盖。 */
317
+ export function isConfigEncryptionError(error) {
318
+ const code = error?.code;
319
+ return typeof code === "string" && CONFIG_ENCRYPTION_ERROR_CODES.has(code);
320
+ }
304
321
  /** 动作标识符不符合命名规范 */
305
322
  export const INVALID_ACTION_ID = "INVALID_ACTION_ID";
306
323
  /** 规程标识符不符合命名规范 */
@@ -374,6 +391,12 @@ export function resolveExecutionHint(targetRef, error) {
374
391
  if (error.code === ACTION_TIMEOUT) {
375
392
  return "Tip: Increase timeout via '--timeout <duration>', or run in background via '--async' and track with 'ad runs show <runId>'.";
376
393
  }
394
+ if (error.code === DEPENDENCIES_NOT_READY) {
395
+ return error.hint || error.details?.hint || "Tip: Install missing dependencies using 'npm install' or 'bun install'.";
396
+ }
397
+ if (error.code === DEPENDENCY_INSTALL_FAILED) {
398
+ return error.hint || error.details?.hint || "Tip: Check error output above and try running the package manager manually.";
399
+ }
377
400
  if (error.hint && typeof error.hint === "string") {
378
401
  return error.hint;
379
402
  }
@@ -289,7 +289,18 @@ export class DefaultExecutionService {
289
289
  }
290
290
  }
291
291
  if (typeof context.timeoutMs === "number" && context.timeoutMs > 0) {
292
- finalizer.startTimeout(controller, context.timeoutMs);
292
+ if (context.deadline !== undefined) {
293
+ const remainingMs = Math.max(0, context.deadline - Date.now());
294
+ if (remainingMs <= 0) {
295
+ controller.abort(new ActionDockError(ACTION_TIMEOUT, `Action exceeded timeout of ${context.initialTimeoutMs ?? context.timeoutMs}ms`, { timeoutMs: context.initialTimeoutMs ?? context.timeoutMs }));
296
+ }
297
+ else {
298
+ finalizer.startTimeout(controller, remainingMs, context.initialTimeoutMs ?? context.timeoutMs);
299
+ }
300
+ }
301
+ else {
302
+ finalizer.startTimeout(controller, context.timeoutMs, context.initialTimeoutMs);
303
+ }
293
304
  }
294
305
  const effectiveOwner = context.owner ||
295
306
  createDefaultProcessOwner({
@@ -426,6 +437,8 @@ export class DefaultExecutionService {
426
437
  package: opts.package || this.identity,
427
438
  signal: opts.signal,
428
439
  timeoutMs: opts.timeoutMs,
440
+ initialTimeoutMs: opts.initialTimeoutMs,
441
+ deadline: opts.deadline,
429
442
  maxCallDepth: opts.maxCallDepth,
430
443
  config: opts.config || opts.configOverrides,
431
444
  tenantId: opts.tenantId || opts.owner?.tenantId,
@@ -483,20 +496,36 @@ export class DefaultExecutionService {
483
496
  };
484
497
  }
485
498
  catch (err) {
486
- return this.classifyExecutionError(err, runId, context.timeoutMs, controller, finalizer);
499
+ return this.classifyExecutionError(err, runId, context, controller, finalizer);
487
500
  }
488
501
  }
489
- classifyExecutionError(err, runId, timeoutMs, controller, finalizer) {
502
+ classifyExecutionError(err, runId, context, controller, finalizer) {
503
+ const timeoutBudget = context.initialTimeoutMs ?? context.timeoutMs;
490
504
  if (finalizer.isTimeout) {
491
505
  const error = {
492
506
  code: ACTION_TIMEOUT,
493
- message: `Action exceeded timeout of ${timeoutMs}ms`,
507
+ message: `Action exceeded timeout of ${timeoutBudget}ms`,
494
508
  };
495
509
  finalizer.finalize("timed_out", undefined, error);
496
510
  return { ok: false, runId, error: finalizer.persistError || error };
497
511
  }
498
512
  if (controller.signal.aborted) {
499
513
  const reason = controller.signal.reason;
514
+ const reasonCode = reason?.code;
515
+ const isTimeoutReason = reasonCode === ACTION_TIMEOUT ||
516
+ (reason?.details && reason.details.code === ACTION_TIMEOUT) ||
517
+ (reason instanceof Error && reason.message?.includes("exceeded timeout")) ||
518
+ (typeof reason === "string" && reason.includes("exceeded timeout"));
519
+ if (isTimeoutReason) {
520
+ const error = {
521
+ code: ACTION_TIMEOUT,
522
+ message: (reason instanceof Error && reason.message?.includes("exceeded timeout"))
523
+ ? reason.message
524
+ : `Action exceeded timeout of ${timeoutBudget}ms`,
525
+ };
526
+ finalizer.finalize("timed_out", undefined, error);
527
+ return { ok: false, runId, error: finalizer.persistError || error };
528
+ }
500
529
  const reasonMsg = reason instanceof Error
501
530
  ? reason.message
502
531
  : typeof reason === "string"
@@ -544,6 +573,8 @@ export class DefaultExecutionService {
544
573
  package: this.identity,
545
574
  signal: controller.signal,
546
575
  timeoutMs: context.timeoutMs,
576
+ initialTimeoutMs: context.initialTimeoutMs,
577
+ deadline: context.deadline,
547
578
  maxCallDepth: context.maxCallDepth,
548
579
  config: context.config,
549
580
  tenantId: effectiveOwner.tenantId,
@@ -564,7 +595,7 @@ export class DefaultExecutionService {
564
595
  const digestPayload = {
565
596
  input,
566
597
  config: context.config,
567
- timeoutMs: context.timeoutMs,
598
+ timeoutMs: context.initialTimeoutMs ?? context.timeoutMs,
568
599
  };
569
600
  const inputDigest = computeDigest(digestPayload);
570
601
  const provisionalRunId = crypto.randomUUID();
@@ -9,6 +9,7 @@ import type { StateEntry } from "../storage/types.js";
9
9
  export declare class DefaultActionDockHost implements ActionDockHost {
10
10
  readonly hostSessionId: string;
11
11
  readonly options: ActionDockHostOptions;
12
+ readonly allowInstall?: boolean;
12
13
  private runtimes;
13
14
  private extraRuntimes;
14
15
  private extraRuntimeScopes;
@@ -18,6 +19,10 @@ export declare class DefaultActionDockHost implements ActionDockHost {
18
19
  private maxSubRuns;
19
20
  private eventSink;
20
21
  private isClosed;
22
+ private readonly prepareLocalPackage?;
23
+ private readonly closeController;
24
+ private readonly activePreparations;
25
+ private closePromise?;
21
26
  private dataDirLock?;
22
27
  private failedLinkedPackages;
23
28
  private failedAutoLoad?;
@@ -113,6 +118,12 @@ export declare class DefaultActionDockHost implements ActionDockHost {
113
118
  describePlaybook(id: string): Promise<PlaybookSpec>;
114
119
  runAction(ref: ActionRef | string, input: JsonValue, options?: RunOptions): Promise<ExecutionResult>;
115
120
  startAction(ref: ActionRef | string, input: JsonValue, options?: RunOptions): Promise<ExecutionTicket>;
121
+ /**
122
+ * 私有本地 Action 调度中枢。
123
+ * 统一处理本地已授权目标的准备(若已注入 prepareLocalPackage 且存在物理包根路径)、
124
+ * 中断与关闭复核,并分发至目标 Runtime 的 startInvocation。
125
+ */
126
+ private dispatchLocalInvocation;
116
127
  getRun(runId: string, signal?: AbortSignal): Promise<RunRecord | undefined>;
117
128
  listRuns(query?: ListRunsOptions, signal?: AbortSignal): Promise<RunRecord[]>;
118
129
  countRuns(query?: ListRunsOptions, signal?: AbortSignal): Promise<number>;
package/dist/host/host.js CHANGED
@@ -8,7 +8,7 @@ import { hasPendingTransactions, isProjectLockHeld, recoverPendingTransactions }
8
8
  import { listLinkedPackages } from "../registry/registry.js";
9
9
  import { InMemoryEventSink } from "../runtime/events.js";
10
10
  import { snapshotRunOrigin } from "../runtime/run-origin.js";
11
- import { ActionDockError, ACTION_CANCELLED, ACTION_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_SUBRUN_LIMIT, EXECUTION_FAILED, INVALID_ACTION_REF, PACKAGE_ID_CONFLICT, PACKAGE_NOT_FOUND, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, SERVICE_CLOSED, UNDECLARED_ACTION_DEPENDENCY, } from "../errors.js";
11
+ import { ActionDockError, ACTION_CANCELLED, ACTION_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_SUBRUN_LIMIT, ACTION_TIMEOUT, EXECUTION_FAILED, INVALID_ACTION_REF, PACKAGE_ID_CONFLICT, PACKAGE_NOT_FOUND, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, SERVICE_CLOSED, UNDECLARED_ACTION_DEPENDENCY, } from "../errors.js";
12
12
  import { InvocationPolicy } from "../invocation/policy.js";
13
13
  import { loadActionBindings, resolveActionBinding, validateActionBindings, } from "../invocation/bindings.js";
14
14
  import { RemoteInvocationExecutor } from "../invocation/remote.js";
@@ -19,7 +19,7 @@ import { DefaultActionCatalog, DefaultPackageGraph, PackageGraphBuilder, resolve
19
19
  import { parseActionRef } from "../catalog/resolve-action.js";
20
20
  import { buildRuntimeError, collectPackageInfos, describeActionAcrossRuntimes, describeVisiblePlaybook, isRootCallVisible, listVisiblePlaybooks, packageNotFoundMessage, parseRefLoose, resolveProjectRoot, } from "./routing.js";
21
21
  import { CAPABILITY_UNAVAILABLE } from "../errors.js";
22
- import { createGlobalStorage, redactConfigValueAtPaths, redactConfigValueForDeclarations } from "../storage/index.js";
22
+ import { createGlobalStorage, redactConfigValueAtPaths, redactConfigValueForDeclarations, readConfigWithProtection, redactConfigValueWithProtection } from "../storage/index.js";
23
23
  import { filterByIntent } from "../utils/intent.js";
24
24
  import { assertConcreteConfigKey, isConfigTemplate, resolveConfigDeclaration, validateConfigValue, } from "../project/config-declarations.js";
25
25
  function isPackageRuntime(item) {
@@ -60,9 +60,20 @@ function withAbortSignal(op, signal) {
60
60
  });
61
61
  });
62
62
  }
63
+ function isActionTimeoutReason(reason) {
64
+ if (!reason)
65
+ return false;
66
+ const code = reason?.code;
67
+ if (code === ACTION_TIMEOUT)
68
+ return true;
69
+ if (reason?.details?.code === ACTION_TIMEOUT)
70
+ return true;
71
+ return false;
72
+ }
63
73
  export class DefaultActionDockHost {
64
74
  hostSessionId;
65
75
  options;
76
+ allowInstall;
66
77
  runtimes = new Map();
67
78
  extraRuntimes = new Set();
68
79
  extraRuntimeScopes = new Map();
@@ -72,6 +83,10 @@ export class DefaultActionDockHost {
72
83
  maxSubRuns;
73
84
  eventSink;
74
85
  isClosed = false;
86
+ prepareLocalPackage;
87
+ closeController = new AbortController();
88
+ activePreparations = new Set();
89
+ closePromise;
75
90
  dataDirLock;
76
91
  failedLinkedPackages = new Map();
77
92
  failedAutoLoad;
@@ -109,6 +124,8 @@ export class DefaultActionDockHost {
109
124
  }
110
125
  this.hostSessionId = randomUUID();
111
126
  this.options = options;
127
+ this.allowInstall = options.allowInstall;
128
+ this.prepareLocalPackage = options.prepareLocalPackage;
112
129
  this.remoteInvoker = new RemoteInvocationExecutor({ logger: options.logger });
113
130
  this.maxCallDepth = options.maxCallDepth ?? 16;
114
131
  this.maxSubRuns = options.maxSubRuns ?? 64;
@@ -469,7 +486,10 @@ export class DefaultActionDockHost {
469
486
  if (!this.recoverOrphans) {
470
487
  throw new ActionDockError(PROJECT_RECOVERY_REQUIRED, `PROJECT_RECOVERY_REQUIRED: Project at '${root}' has pending transactions requiring recovery. Run an owner command (e.g. 'ad add' or 'ad run') to complete recovery before querying.`);
471
488
  }
472
- await recoverPendingTransactions(root, { frozenInstall: true });
489
+ if (this.allowInstall === false || process.env.ACTIONDOCK_AUTO_INSTALL === "false") {
490
+ throw new ActionDockError(PROJECT_RECOVERY_REQUIRED, `PROJECT_RECOVERY_REQUIRED: Project at '${root}' has pending transactions requiring recovery install, but installation is disabled (--no-install or ACTIONDOCK_AUTO_INSTALL=false).`);
491
+ }
492
+ await recoverPendingTransactions(root, { frozenInstall: true, allowInstall: this.allowInstall });
473
493
  }
474
494
  try {
475
495
  const config = loadProjectConfig(root);
@@ -735,7 +755,7 @@ export class DefaultActionDockHost {
735
755
  owner: targetOwner,
736
756
  origin: context.origin,
737
757
  };
738
- const ticket = await targetRuntime.startInvocation(targetActionId, childInput, subInvocationContext);
758
+ const ticket = await this.dispatchLocalInvocation(targetRuntime, targetActionId, childInput, subInvocationContext);
739
759
  if (!ticket.result) {
740
760
  throw new ActionDockError(EXECUTION_FAILED, `Execution ticket for run '${ticket.runId}' has no result Promise`);
741
761
  }
@@ -971,8 +991,182 @@ export class DefaultActionDockHost {
971
991
  process: this.options.process,
972
992
  origin: effectiveOptions.origin,
973
993
  });
974
- // 调度目标包执行服务
975
- return targetRuntime.startInvocation(targetActionId, input, rootContext);
994
+ // 调度目标包执行服务(经由本地分发中枢统一准备与复核)
995
+ return this.dispatchLocalInvocation(targetRuntime, targetActionId, input, rootContext);
996
+ }
997
+ /**
998
+ * 私有本地 Action 调度中枢。
999
+ * 统一处理本地已授权目标的准备(若已注入 prepareLocalPackage 且存在物理包根路径)、
1000
+ * 中断与关闭复核,并分发至目标 Runtime 的 startInvocation。
1001
+ */
1002
+ async dispatchLocalInvocation(targetRuntime, targetActionId, input, context) {
1003
+ if (this.isClosed) {
1004
+ const runId = context.runId;
1005
+ const error = buildRuntimeError(SERVICE_CLOSED, "ActionDockHost is closed: new tasks rejected", { package: targetRuntime.packageId, action: targetActionId, phase: "dispatch" });
1006
+ return {
1007
+ runId,
1008
+ status: "failed",
1009
+ result: Promise.resolve({ ok: false, runId, error }),
1010
+ };
1011
+ }
1012
+ const isRootCall = !context.caller;
1013
+ const initialTimeoutMs = context.initialTimeoutMs ?? (typeof context.timeoutMs === "number" && context.timeoutMs > 0 ? context.timeoutMs : undefined);
1014
+ const deadline = context.deadline ?? (initialTimeoutMs !== undefined ? Date.now() + initialTimeoutMs : undefined);
1015
+ if (context.signal.aborted) {
1016
+ const runId = context.runId;
1017
+ const isTimeout = isActionTimeoutReason(context.signal.reason);
1018
+ const error = buildRuntimeError(isTimeout ? ACTION_TIMEOUT : ACTION_CANCELLED, isTimeout
1019
+ ? `Action exceeded timeout of ${initialTimeoutMs ?? context.timeoutMs}ms`
1020
+ : "Action execution was cancelled before starting", { package: targetRuntime.packageId, action: targetActionId, phase: "dispatch" });
1021
+ return {
1022
+ runId,
1023
+ status: "failed",
1024
+ result: Promise.resolve({ ok: false, runId, error }),
1025
+ };
1026
+ }
1027
+ let timeoutController;
1028
+ let prepTimeoutTimer;
1029
+ if (isRootCall && typeof context.timeoutMs === "number" && context.timeoutMs > 0) {
1030
+ timeoutController = new AbortController();
1031
+ const remaining = deadline ? Math.max(0, deadline - Date.now()) : context.timeoutMs;
1032
+ const timeoutReason = new ActionDockError(ACTION_TIMEOUT, `Action exceeded timeout of ${initialTimeoutMs ?? context.timeoutMs}ms`, { package: targetRuntime.packageId, action: targetActionId, phase: "preparation" });
1033
+ if (remaining <= 0) {
1034
+ timeoutController.abort(timeoutReason);
1035
+ }
1036
+ else {
1037
+ prepTimeoutTimer = setTimeout(() => {
1038
+ timeoutController?.abort(timeoutReason);
1039
+ }, remaining);
1040
+ }
1041
+ }
1042
+ const signalSources = [context.signal, this.closeController.signal];
1043
+ if (timeoutController) {
1044
+ signalSources.push(timeoutController.signal);
1045
+ }
1046
+ const mergedSignal = AbortSignal.any(signalSources);
1047
+ if (this.prepareLocalPackage && targetRuntime.packageRoot) {
1048
+ const prepPromise = Promise.resolve().then(() => this.prepareLocalPackage({
1049
+ identity: targetRuntime.identity,
1050
+ packageRoot: targetRuntime.packageRoot,
1051
+ }, {
1052
+ signal: mergedSignal,
1053
+ rootRunId: context.rootRunId,
1054
+ }));
1055
+ this.activePreparations.add(prepPromise);
1056
+ try {
1057
+ await prepPromise;
1058
+ }
1059
+ catch (err) {
1060
+ if (prepTimeoutTimer !== undefined) {
1061
+ clearTimeout(prepTimeoutTimer);
1062
+ prepTimeoutTimer = undefined;
1063
+ }
1064
+ const errCode = err?.code;
1065
+ const isClosed = this.closeController.signal.aborted;
1066
+ const isExternalCancelled = context.signal.aborted;
1067
+ const isTimeout = timeoutController?.signal.aborted ||
1068
+ isActionTimeoutReason(context.signal.reason) ||
1069
+ errCode === ACTION_TIMEOUT ||
1070
+ (err?.details && err.details.code === ACTION_TIMEOUT);
1071
+ let code;
1072
+ let message = err?.message || "Package preparation failed";
1073
+ if (isTimeout) {
1074
+ code = ACTION_TIMEOUT;
1075
+ message = (err?.message && err.message.includes("exceeded timeout"))
1076
+ ? err.message
1077
+ : `Action exceeded timeout of ${initialTimeoutMs ?? context.timeoutMs}ms`;
1078
+ }
1079
+ else if (err instanceof ActionDockError) {
1080
+ code = err.code;
1081
+ }
1082
+ else if (isClosed) {
1083
+ code = SERVICE_CLOSED;
1084
+ message = "ActionDockHost is closed: new tasks rejected";
1085
+ }
1086
+ else if (isExternalCancelled) {
1087
+ code = ACTION_CANCELLED;
1088
+ message = "Action execution was cancelled";
1089
+ }
1090
+ else if (err?.code && typeof err.code === "string") {
1091
+ code = err.code;
1092
+ }
1093
+ else {
1094
+ code = EXECUTION_FAILED;
1095
+ }
1096
+ const hint = err?.details?.hint ?? err?.hint;
1097
+ const details = {
1098
+ ...(err?.details || {}),
1099
+ package: targetRuntime.packageId,
1100
+ action: targetActionId,
1101
+ phase: "preparation",
1102
+ ...(hint ? { hint } : {}),
1103
+ };
1104
+ const runId = context.runId;
1105
+ const error = buildRuntimeError(code, message, details);
1106
+ return {
1107
+ runId,
1108
+ status: "failed",
1109
+ result: Promise.resolve({ ok: false, runId, error }),
1110
+ };
1111
+ }
1112
+ finally {
1113
+ this.activePreparations.delete(prepPromise);
1114
+ }
1115
+ }
1116
+ if (prepTimeoutTimer !== undefined) {
1117
+ clearTimeout(prepTimeoutTimer);
1118
+ prepTimeoutTimer = undefined;
1119
+ }
1120
+ // 准备后再次复核取消与 Host 关闭状态
1121
+ if (this.isClosed || this.closeController.signal.aborted) {
1122
+ const runId = context.runId;
1123
+ const error = buildRuntimeError(SERVICE_CLOSED, "ActionDockHost is closed: new tasks rejected", { package: targetRuntime.packageId, action: targetActionId, phase: "post-preparation" });
1124
+ return {
1125
+ runId,
1126
+ status: "failed",
1127
+ result: Promise.resolve({ ok: false, runId, error }),
1128
+ };
1129
+ }
1130
+ if (context.signal.aborted) {
1131
+ const runId = context.runId;
1132
+ const isTimeout = isActionTimeoutReason(context.signal.reason);
1133
+ const error = buildRuntimeError(isTimeout ? ACTION_TIMEOUT : ACTION_CANCELLED, isTimeout
1134
+ ? `Action exceeded timeout of ${initialTimeoutMs ?? context.timeoutMs}ms`
1135
+ : "Action execution was cancelled after preparation", { package: targetRuntime.packageId, action: targetActionId, phase: "post-preparation" });
1136
+ return {
1137
+ runId,
1138
+ status: "failed",
1139
+ result: Promise.resolve({ ok: false, runId, error }),
1140
+ };
1141
+ }
1142
+ const remainingBudget = deadline !== undefined
1143
+ ? Math.max(0, deadline - Date.now())
1144
+ : context.timeoutMs;
1145
+ if (deadline !== undefined && typeof remainingBudget === "number" && remainingBudget <= 0) {
1146
+ const runId = context.runId;
1147
+ const error = buildRuntimeError(ACTION_TIMEOUT, `Action exceeded timeout of ${initialTimeoutMs ?? context.timeoutMs}ms`, { package: targetRuntime.packageId, action: targetActionId, phase: "post-preparation" });
1148
+ return {
1149
+ runId,
1150
+ status: "failed",
1151
+ result: Promise.resolve({ ok: false, runId, error }),
1152
+ };
1153
+ }
1154
+ const effectiveContext = {
1155
+ ...context,
1156
+ signal: mergedSignal,
1157
+ initialTimeoutMs,
1158
+ deadline,
1159
+ timeoutMs: remainingBudget,
1160
+ };
1161
+ const ticket = await targetRuntime.startInvocation(targetActionId, input, effectiveContext);
1162
+ if (ticket.result) {
1163
+ ticket.result.finally(() => {
1164
+ if (prepTimeoutTimer !== undefined) {
1165
+ clearTimeout(prepTimeoutTimer);
1166
+ }
1167
+ });
1168
+ }
1169
+ return ticket;
976
1170
  }
977
1171
  async getRun(runId, signal) {
978
1172
  await this.ensureInitialized();
@@ -1342,16 +1536,23 @@ export class DefaultActionDockHost {
1342
1536
  }) ||
1343
1537
  Array.from(this.runtimes.values()).some((rt) => Boolean(rt.inMemory || rt.options?.inMemory));
1344
1538
  const inMemory = Boolean(this.options.inMemory || hasInMemoryPackage);
1539
+ // 旧版本全局库迁移的声明并集:取首次打开时已加载包的声明字典;
1540
+ // 后续 registerRuntime 带来的新声明属当前版本正常写入,不重跑历史迁移
1541
+ const configDeclarations = this.listRuntimes()
1542
+ .map((runtime) => runtime.projectConfig?.config)
1543
+ .filter((config) => Boolean(config && Object.keys(config).length > 0));
1345
1544
  this.globalStorage =
1346
1545
  this.options.platform?.storage?.createGlobalStorage?.({
1347
1546
  dataDir: this.options.dataDir,
1348
1547
  customHome: this.options.customHome,
1349
1548
  ...(inMemory ? { inMemory: true } : {}),
1549
+ configDeclarations,
1350
1550
  }) ??
1351
1551
  createGlobalStorage({
1352
1552
  dataDir: this.options.dataDir,
1353
1553
  customHome: this.options.customHome,
1354
1554
  inMemory,
1555
+ configDeclarations,
1355
1556
  });
1356
1557
  }
1357
1558
  return this.globalStorage;
@@ -1399,10 +1600,11 @@ export class DefaultActionDockHost {
1399
1600
  assertConcreteConfigKey(key);
1400
1601
  if (packageId === "global") {
1401
1602
  const globalStorage = this.getGlobalStorage();
1402
- const val = globalStorage.getConfig(key);
1403
- const configured = val !== undefined;
1603
+ // 原子快照配对读取值与历史保护声明,消除并发删除造成的值与规则错配
1604
+ const snapshot = readConfigWithProtection(globalStorage, key);
1605
+ const configured = snapshot !== undefined;
1404
1606
  const items = this.findAllDeclaredConfigItems(key);
1405
- const redacted = redactConfigValueForDeclarations(val, items);
1607
+ const redacted = redactConfigValueWithProtection(snapshot?.value, items, snapshot?.protection);
1406
1608
  return {
1407
1609
  key,
1408
1610
  configured,
@@ -1422,7 +1624,9 @@ export class DefaultActionDockHost {
1422
1624
  for (const item of items) {
1423
1625
  validateConfigValue(key, value, item);
1424
1626
  }
1425
- await this.getGlobalStorage().setConfig(key, value);
1627
+ // 传递全部已加载包各自解析出的有效声明并集(各包内具体键优先于模板),
1628
+ // 由存储统一判定整键加密并合并历史保护
1629
+ await this.getGlobalStorage().setConfig(key, value, items.length > 0 ? { declarations: items } : undefined);
1426
1630
  return;
1427
1631
  }
1428
1632
  await this.requireRuntime(packageId).setConfig(key, value);
@@ -1441,12 +1645,18 @@ export class DefaultActionDockHost {
1441
1645
  const globalStorage = this.getGlobalStorage();
1442
1646
  const all = globalStorage.listConfig();
1443
1647
  const views = [];
1444
- for (const [key, val] of Object.entries(all)) {
1648
+ for (const key of Object.keys(all)) {
1445
1649
  if (isConfigTemplate(key)) {
1446
1650
  continue;
1447
1651
  }
1652
+ // 同源原子重读:值与历史保护声明取自同一次读取;
1653
+ // 行在列键之后又被并发删除时跳过该项,不回退旧值
1654
+ const snapshot = readConfigWithProtection(globalStorage, key);
1655
+ if (snapshot === undefined) {
1656
+ continue;
1657
+ }
1448
1658
  const items = this.findAllDeclaredConfigItems(key);
1449
- const redacted = redactConfigValueForDeclarations(val, items);
1659
+ const redacted = redactConfigValueWithProtection(snapshot.value, items, snapshot.protection);
1450
1660
  views.push({
1451
1661
  key,
1452
1662
  configured: true,
@@ -1504,39 +1714,48 @@ export class DefaultActionDockHost {
1504
1714
  * 外部传入借用的 Runtime 实例生命周期完全由调用方负责管理,Host 关闭时仅解绑引用并清理自身内部实例。
1505
1715
  */
1506
1716
  async close(options) {
1717
+ if (this.closePromise)
1718
+ return this.closePromise;
1507
1719
  if (this.isClosed)
1508
1720
  return;
1509
- this.isClosed = true;
1510
- const allRuntimes = Array.from(this.runtimes.values());
1511
- await Promise.all(allRuntimes.map(async (runtime) => {
1721
+ this.closePromise = (async () => {
1722
+ this.isClosed = true;
1723
+ this.closeController.abort(new ActionDockError(SERVICE_CLOSED, "ActionDockHost is closing"));
1724
+ if (this.activePreparations.size > 0) {
1725
+ await Promise.allSettled(Array.from(this.activePreparations));
1726
+ }
1727
+ const allRuntimes = Array.from(this.runtimes.values());
1728
+ await Promise.all(allRuntimes.map(async (runtime) => {
1729
+ try {
1730
+ await runtime.close(options);
1731
+ }
1732
+ catch {
1733
+ // 忽略 Runtime 关闭异常,确保全部安全释放
1734
+ }
1735
+ }));
1736
+ const invocationCloseErrors = await this.closeInvocationTargets();
1737
+ this.runtimes.clear();
1738
+ this.actionBindings.clear();
1739
+ this.remoteTargets.clear();
1740
+ await this.remoteInvoker.close();
1512
1741
  try {
1513
- await runtime.close(options);
1742
+ this.globalStorage?.close();
1514
1743
  }
1515
1744
  catch {
1516
- // 忽略 Runtime 关闭异常,确保全部安全释放
1745
+ // 忽略全局存储关闭异常
1517
1746
  }
1518
- }));
1519
- const invocationCloseErrors = await this.closeInvocationTargets();
1520
- this.runtimes.clear();
1521
- this.actionBindings.clear();
1522
- this.remoteTargets.clear();
1523
- await this.remoteInvoker.close();
1524
- try {
1525
- this.globalStorage?.close();
1526
- }
1527
- catch {
1528
- // 忽略全局存储关闭异常
1529
- }
1530
- this.globalStorage = undefined;
1531
- try {
1532
- this.dataDirLock?.release();
1533
- }
1534
- catch {
1535
- // 忽略目录排他锁释放异常
1536
- }
1537
- this.dataDirLock = undefined;
1538
- if (invocationCloseErrors.length)
1539
- throw new AggregateError(invocationCloseErrors, "Invocation target cleanup failed");
1747
+ this.globalStorage = undefined;
1748
+ try {
1749
+ this.dataDirLock?.release();
1750
+ }
1751
+ catch {
1752
+ // 忽略目录排他锁释放异常
1753
+ }
1754
+ this.dataDirLock = undefined;
1755
+ if (invocationCloseErrors.length)
1756
+ throw new AggregateError(invocationCloseErrors, "Invocation target cleanup failed");
1757
+ })();
1758
+ return this.closePromise;
1540
1759
  }
1541
1760
  }
1542
1761
  /**
@@ -1,7 +1,7 @@
1
1
  import type { ActionRef, ExecutionEvent, ExecutionResult, JsonValue, Logger, ProcessAPI, RunRecord } from "@actiondock/sdk";
2
2
  import type { PackageInfo, PackageRuntime, PackageRuntimeOptions, ActionSpec, ActionSummary, ListActionsOptions, PlaybookSpec, PlaybookSummary } from "../package/types.js";
3
3
  import type { CancelResult, ExecutionTicket } from "../execution/types.js";
4
- import type { RunOptions } from "../invocation/types.js";
4
+ import type { LocalPackagePreparer, RunOptions } from "../invocation/types.js";
5
5
  import type { Clock } from "../storage/clock.js";
6
6
  import type { EventSink } from "../runtime/events.js";
7
7
  import type { RuntimePlatform } from "../platform/types.js";
@@ -60,6 +60,13 @@ export interface ActionDockHostOptions {
60
60
  eventSink?: EventSink;
61
61
  /** 是否开启配置与状态管理端口(默认 true) */
62
62
  enableManagement?: boolean;
63
+ /**
64
+ * 是否允许执行依赖自动安装(主要控制启动与事务恢复中的依赖安装;
65
+ * 本地包运行期准备由 prepareLocalPackage 注入器根据自身配置决定,Host 不约束外部注入函数副作用)。
66
+ */
67
+ allowInstall?: boolean;
68
+ /** 本地包准备协调器回调,在 Action 执行前准备本地包环境 */
69
+ prepareLocalPackage?: LocalPackagePreparer;
63
70
  }
64
71
  /**
65
72
  * ActionDock 多包宿主容器领域契约。
@@ -69,6 +76,8 @@ export interface ActionDockHostOptions {
69
76
  export interface ActionDockHost extends ActionDockService {
70
77
  /** 宿主容器初始化配置项(只读) */
71
78
  readonly options?: ActionDockHostOptions;
79
+ /** 是否允许依赖安装(主要控制启动与事务恢复) */
80
+ readonly allowInstall?: boolean;
72
81
  /** 统一事件订阅端口(兼作原生订阅函数与事件服务端口) */
73
82
  readonly events: HostEventsPort;
74
83
  /** 获取所有已注册包的元数据信息列表 */