@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.
- package/dist/errors.d.ts +11 -1
- package/dist/errors.js +23 -0
- package/dist/execution/service.js +36 -5
- package/dist/host/host.d.ts +11 -0
- package/dist/host/host.js +258 -39
- package/dist/host/types.d.ts +10 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +5 -1
- package/dist/invocation/types.d.ts +31 -0
- package/dist/invocation/types.js +10 -1
- package/dist/package/index.d.ts +1 -1
- package/dist/package/runtime.js +18 -4
- package/dist/platform/node.d.ts +6 -0
- package/dist/platform/node.js +59 -16
- package/dist/platform/types.d.ts +11 -0
- package/dist/process/command-executor.d.ts +35 -0
- package/dist/process/command-executor.js +326 -0
- package/dist/process/index.d.ts +1 -0
- package/dist/process/index.js +1 -0
- package/dist/process/process-driver.d.ts +19 -6
- package/dist/process/process-driver.js +223 -54
- package/dist/process/run-executor.js +101 -47
- package/dist/project/dependencies.d.ts +92 -0
- package/dist/project/dependencies.js +543 -0
- package/dist/project/index.d.ts +3 -0
- package/dist/project/index.js +3 -0
- package/dist/project/init.js +2 -2
- package/dist/project/loader.js +15 -30
- package/dist/project/package-manager.d.ts +40 -0
- package/dist/project/package-manager.js +224 -0
- package/dist/project/preparation.d.ts +8 -0
- package/dist/project/preparation.js +130 -0
- package/dist/project/transactions.d.ts +5 -1
- package/dist/project/transactions.js +35 -75
- package/dist/runtime/context.d.ts +21 -2
- package/dist/runtime/context.js +42 -17
- package/dist/runtime/run-persistence.d.ts +1 -1
- package/dist/runtime/run-persistence.js +4 -3
- package/dist/security/config-cipher.d.ts +54 -0
- package/dist/security/config-cipher.js +190 -0
- package/dist/security/config-key.d.ts +35 -0
- package/dist/security/config-key.js +469 -0
- package/dist/server/routes/config.js +20 -1
- package/dist/service/factory.js +3 -0
- package/dist/service/types.d.ts +8 -0
- package/dist/storage/config-value.d.ts +33 -0
- package/dist/storage/config-value.js +102 -0
- package/dist/storage/file-lock.js +35 -21
- package/dist/storage/index.d.ts +24 -2
- package/dist/storage/index.js +65 -2
- package/dist/storage/migrations/index.d.ts +40 -0
- package/dist/storage/migrations/index.js +96 -0
- package/dist/storage/migrations/v2-to-v3.d.ts +16 -0
- package/dist/storage/migrations/v2-to-v3.js +116 -0
- package/dist/storage/sqlite-config.d.ts +62 -3
- package/dist/storage/sqlite-config.js +163 -9
- package/dist/storage/sqlite-driver.d.ts +5 -2
- package/dist/storage/sqlite-driver.js +24 -3
- package/dist/storage/sqlite-schema.d.ts +20 -2
- package/dist/storage/sqlite-schema.js +129 -132
- package/dist/storage/sqlite.d.ts +7 -2
- package/dist/storage/sqlite.js +64 -13
- package/dist/storage/types.d.ts +72 -4
- package/dist/storage/types.js +7 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- 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
|
-
|
|
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
|
|
499
|
+
return this.classifyExecutionError(err, runId, context, controller, finalizer);
|
|
487
500
|
}
|
|
488
501
|
}
|
|
489
|
-
classifyExecutionError(err, runId,
|
|
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 ${
|
|
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();
|
package/dist/host/host.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1403
|
-
const
|
|
1603
|
+
// 原子快照配对读取值与历史保护声明,消除并发删除造成的值与规则错配
|
|
1604
|
+
const snapshot = readConfigWithProtection(globalStorage, key);
|
|
1605
|
+
const configured = snapshot !== undefined;
|
|
1404
1606
|
const items = this.findAllDeclaredConfigItems(key);
|
|
1405
|
-
const redacted =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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.
|
|
1510
|
-
|
|
1511
|
-
|
|
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
|
-
|
|
1742
|
+
this.globalStorage?.close();
|
|
1514
1743
|
}
|
|
1515
1744
|
catch {
|
|
1516
|
-
//
|
|
1745
|
+
// 忽略全局存储关闭异常
|
|
1517
1746
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
this.
|
|
1526
|
-
|
|
1527
|
-
|
|
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
|
/**
|
package/dist/host/types.d.ts
CHANGED
|
@@ -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
|
/** 获取所有已注册包的元数据信息列表 */
|