@actiondock/core 2.5.1 → 2.6.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 (134) hide show
  1. package/dist/app/app.d.ts +14 -1
  2. package/dist/app/app.js +88 -222
  3. package/dist/app/static-index.d.ts +39 -0
  4. package/dist/app/static-index.js +164 -0
  5. package/dist/app/types.d.ts +5 -0
  6. package/dist/catalog/action-index.js +6 -4
  7. package/dist/catalog/package-catalog.js +2 -1
  8. package/dist/doctor/checks/index.d.ts +5 -0
  9. package/dist/doctor/checks/index.js +5 -0
  10. package/dist/doctor/checks/project-locate.d.ts +6 -0
  11. package/dist/doctor/checks/project-locate.js +36 -0
  12. package/dist/doctor/checks/project.d.ts +6 -0
  13. package/dist/doctor/checks/project.js +383 -0
  14. package/dist/doctor/checks/registry.d.ts +13 -0
  15. package/dist/doctor/checks/registry.js +207 -0
  16. package/dist/doctor/checks/runtime.d.ts +13 -0
  17. package/dist/doctor/checks/runtime.js +90 -0
  18. package/dist/doctor/checks/storage.d.ts +5 -0
  19. package/dist/doctor/checks/storage.js +34 -0
  20. package/dist/doctor/context.d.ts +35 -0
  21. package/dist/doctor/context.js +11 -0
  22. package/dist/doctor/doctor.d.ts +5 -0
  23. package/dist/doctor/doctor.js +33 -602
  24. package/dist/errors.d.ts +2 -0
  25. package/dist/errors.js +2 -0
  26. package/dist/execution/service.js +14 -9
  27. package/dist/host/host.d.ts +33 -0
  28. package/dist/host/host.js +222 -362
  29. package/dist/host/routing.d.ts +92 -0
  30. package/dist/host/routing.js +238 -0
  31. package/dist/host/types.d.ts +8 -0
  32. package/dist/ipc/host.js +38 -0
  33. package/dist/platform/types.d.ts +7 -0
  34. package/dist/process/control-arbiter.d.ts +107 -0
  35. package/dist/process/control-arbiter.js +251 -0
  36. package/dist/process/diagnostics-sink.d.ts +26 -0
  37. package/dist/process/diagnostics-sink.js +35 -0
  38. package/dist/process/index.d.ts +7 -0
  39. package/dist/process/index.js +7 -0
  40. package/dist/process/input-dispatcher.d.ts +88 -0
  41. package/dist/process/input-dispatcher.js +240 -0
  42. package/dist/process/managed-record.d.ts +78 -0
  43. package/dist/process/managed-record.js +1 -0
  44. package/dist/process/process-manager.d.ts +15 -56
  45. package/dist/process/process-manager.js +119 -707
  46. package/dist/process/reservation-table.d.ts +39 -0
  47. package/dist/process/reservation-table.js +80 -0
  48. package/dist/process/run-executor.d.ts +16 -0
  49. package/dist/process/run-executor.js +155 -0
  50. package/dist/process/terminal-output-cache.d.ts +66 -0
  51. package/dist/process/terminal-output-cache.js +132 -0
  52. package/dist/profile/client-actions.d.ts +56 -0
  53. package/dist/profile/client-actions.js +215 -0
  54. package/dist/profile/client-config.d.ts +20 -0
  55. package/dist/profile/client-config.js +51 -0
  56. package/dist/profile/client-health.d.ts +14 -0
  57. package/dist/profile/client-health.js +68 -0
  58. package/dist/profile/client-playbooks.d.ts +19 -0
  59. package/dist/profile/client-playbooks.js +36 -0
  60. package/dist/profile/client-query.d.ts +30 -0
  61. package/dist/profile/client-query.js +70 -0
  62. package/dist/profile/client-runs.d.ts +42 -0
  63. package/dist/profile/client-runs.js +58 -0
  64. package/dist/profile/client-state.d.ts +56 -0
  65. package/dist/profile/client-state.js +83 -0
  66. package/dist/profile/client-transport.d.ts +61 -0
  67. package/dist/profile/client-transport.js +155 -0
  68. package/dist/profile/client.d.ts +23 -142
  69. package/dist/profile/client.js +28 -588
  70. package/dist/profile/manager.js +1 -1
  71. package/dist/project/closure.d.ts +4 -4
  72. package/dist/project/closure.js +11 -5
  73. package/dist/project/init.js +2 -2
  74. package/dist/project/loader.d.ts +0 -25
  75. package/dist/project/loader.js +1 -154
  76. package/dist/project/transactions.d.ts +12 -14
  77. package/dist/project/transactions.js +60 -836
  78. package/dist/registry/registry.js +6 -4
  79. package/dist/registry/resolve.js +1 -1
  80. package/dist/runtime/action-registry.d.ts +45 -0
  81. package/dist/runtime/action-registry.js +108 -0
  82. package/dist/runtime/clock.d.ts +5 -17
  83. package/dist/runtime/clock.js +5 -15
  84. package/dist/runtime/context.d.ts +3 -0
  85. package/dist/runtime/context.js +17 -56
  86. package/dist/runtime/index.d.ts +3 -0
  87. package/dist/runtime/index.js +3 -0
  88. package/dist/runtime/package-runner-factory.d.ts +82 -0
  89. package/dist/runtime/package-runner-factory.js +186 -0
  90. package/dist/runtime/run-persistence.d.ts +85 -0
  91. package/dist/runtime/run-persistence.js +111 -0
  92. package/dist/runtime/runner.d.ts +23 -33
  93. package/dist/runtime/runner.js +92 -286
  94. package/dist/runtime/standalone.d.ts +11 -1
  95. package/dist/runtime/standalone.js +137 -25
  96. package/dist/server/dispatcher.d.ts +8 -3
  97. package/dist/server/dispatcher.js +16 -40
  98. package/dist/server/routes/runs.js +6 -4
  99. package/dist/server/security.d.ts +1 -7
  100. package/dist/server/security.js +2 -13
  101. package/dist/storage/clock.d.ts +25 -0
  102. package/dist/storage/clock.js +23 -0
  103. package/dist/storage/data-dir-lock.d.ts +3 -36
  104. package/dist/storage/data-dir-lock.js +48 -881
  105. package/dist/storage/index.d.ts +8 -1
  106. package/dist/storage/index.js +8 -2
  107. package/dist/storage/lazy.js +1 -0
  108. package/dist/storage/lock-core.d.ts +162 -0
  109. package/dist/storage/lock-core.js +885 -0
  110. package/dist/storage/sqlite.d.ts +19 -6
  111. package/dist/storage/sqlite.js +81 -89
  112. package/dist/storage/types.d.ts +27 -5
  113. package/dist/storage/types.js +21 -0
  114. package/dist/storage/utils.d.ts +22 -0
  115. package/dist/storage/utils.js +34 -0
  116. package/dist/target/local.d.ts +1 -0
  117. package/dist/target/local.js +23 -17
  118. package/dist/target/remote-errors.d.ts +21 -0
  119. package/dist/target/remote-errors.js +51 -0
  120. package/dist/target/remote-polling.d.ts +35 -0
  121. package/dist/target/remote-polling.js +104 -0
  122. package/dist/target/remote.d.ts +4 -0
  123. package/dist/target/remote.js +108 -301
  124. package/dist/target/sse-parser.d.ts +35 -0
  125. package/dist/target/sse-parser.js +163 -0
  126. package/dist/target/target.js +2 -0
  127. package/dist/target/types.d.ts +6 -0
  128. package/dist/utils/index.d.ts +1 -0
  129. package/dist/utils/index.js +1 -0
  130. package/dist/utils/net.d.ts +10 -0
  131. package/dist/utils/net.js +16 -0
  132. package/dist/version.d.ts +1 -1
  133. package/dist/version.js +1 -1
  134. package/package.json +2 -2
@@ -2,10 +2,13 @@ import { existsSync } from "node:fs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { ActionResolver } from "../catalog/action-resolver.js";
4
4
  import { loadActions, loadProjectConfig } from "../project/loader.js";
5
- import { resolveActionProject, resolvePackageRoot } from "../registry/registry.js";
6
- import { ACTION_CALL_CYCLE, ACTION_CYCLE_DETECTED, ACTION_FAILED, ACTION_MAX_DEPTH_EXCEEDED, ACTION_NOT_FOUND, ACTION_SUBRUN_LIMIT, ACTION_TIMEOUT, describeActionLoadFailure, INPUT_NOT_JSON, MAX_SUBRUNS_REACHED, OUTPUT_NOT_JSON, RUN_PERSISTENCE_FAILED, RUN_REPOSITORY_UNAVAILABLE, UNDECLARED_ACTION_DEPENDENCY, PACKAGE_NOT_FOUND, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, ACTION_CANCELLED, } from "../errors.js";
5
+ import { resolveActionProject } from "../registry/registry.js";
6
+ import { ACTION_CALL_CYCLE, ACTION_CYCLE_DETECTED, ACTION_FAILED, ACTION_MAX_DEPTH_EXCEEDED, ACTION_NOT_FOUND, ACTION_SUBRUN_LIMIT, ACTION_TIMEOUT, describeActionLoadFailure, INPUT_NOT_JSON, MAX_SUBRUNS_REACHED, OUTPUT_NOT_JSON, ACTION_CANCELLED, PACKAGE_NOT_FOUND, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, UNDECLARED_ACTION_DEPENDENCY, } from "../errors.js";
7
7
  import { validateSchema } from "../schema/validator.js";
8
8
  import { createActionContext, StderrLogger } from "./context.js";
9
+ import { ActionRegistry, findLocalAction, isActionDefinitionObject, parseActionRef, resolveAnonymousActionId, } from "./action-registry.js";
10
+ import { buildInitialRunRecord, createRunFinalizer, createRunOrThrow, tryCreateRun, } from "./run-persistence.js";
11
+ import { PackageRunnerFactory } from "./package-runner-factory.js";
9
12
  // 错误码常量已收敛至 src/errors.ts 单一事实源,此处保留 re-export 以维持既有导入路径兼容。
10
13
  export { RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, MAX_SUBRUNS_REACHED, ACTION_CALL_CYCLE, ACTION_CYCLE_DETECTED, ACTION_MAX_DEPTH_EXCEEDED, } from "../errors.js";
11
14
  /**
@@ -57,17 +60,18 @@ export function validateJsonValue(val, stack = new WeakSet()) {
57
60
  }
58
61
  return { valid: true };
59
62
  }
60
- const anonymousRunnerActionIds = new WeakMap();
61
- let anonymousRunnerActionCounter = 0;
62
63
  /**
63
64
  * ActionDock 核心执行引擎(ActionRunner)。
64
65
  *
65
- * 职责:
66
- * - 负责 Action 执行的全生命周期管理(校验、隔离、跟踪、落库)。
67
- * - 入参 (inputSchema) 与出参 (outputSchema) 的 JSON Schema 严格校验。
68
- * - 嵌套 Action 相互调用的环路检测(Cycle Detection)。
69
- * - 超时 (Timeout) 与中断信号 (AbortSignal) 竞态控制。
70
- * - 自动记录并持久化 RunRecord 运行记录至 SQLite 存储。
66
+ * 本类为执行编排壳:负责 Action 执行的全生命周期编排(校验、深度与环路检测、
67
+ * 超时与取消竞态、子调用 uses 授权与错误分类),并将单一职责域委托至独立模块:
68
+ * - Action 注册与检索委托 `ActionRegistry`(action-registry.ts)
69
+ * - RunRecord 构造与落库委托 run-persistence.ts
70
+ * - 跨包 Runner 构建与缓存委托 `PackageRunnerFactory`(package-runner-factory.ts)
71
+ *
72
+ * 入参 (inputSchema) 与出参 (outputSchema) 的 JSON Schema 严格校验、嵌套 Action
73
+ * 相互调用的环路检测(Cycle Detection)、超时 (Timeout) 与中断信号 (AbortSignal)
74
+ * 竞态控制与运行记录持久化编排仍由本类承担。
71
75
  */
72
76
  export class ActionRunner {
73
77
  packageId;
@@ -78,19 +82,16 @@ export class ActionRunner {
78
82
  projectRoot;
79
83
  projectConfig;
80
84
  configOverrides;
81
- actions;
85
+ registry;
82
86
  clock;
83
87
  process;
84
88
  platform;
85
89
  maxCallDepth;
86
90
  maxSubRuns;
87
91
  activeSubRuns = 0;
88
- packageRunners = new Map();
89
- /** 跨包 Runner 构建中的 in-flight Promise:并发调用 await 同一构建任务,消除 check-then-act 竞态 */
90
- pendingPackageRunners = new Map();
92
+ packageFactory;
91
93
  actionResolver;
92
94
  getStorageForPackage;
93
- packageContextResolver;
94
95
  customHome;
95
96
  hostSessionId;
96
97
  constructor(options) {
@@ -101,7 +102,7 @@ export class ActionRunner {
101
102
  this.projectRoot = options.projectRoot;
102
103
  this.projectConfig = options.projectConfig;
103
104
  this.configOverrides = options.configOverrides || {};
104
- this.actions = options.actions || new Map();
105
+ this.registry = new ActionRegistry(options.actions);
105
106
  this.customHome = options.customHome;
106
107
  this.platform = options.platform;
107
108
  if (options.platform) {
@@ -110,6 +111,8 @@ export class ActionRunner {
110
111
  this.storage = options.storage ?? options.platform.storage.createStorage(this.packageId, {
111
112
  projectRoot: this.projectRoot,
112
113
  customHome: this.customHome,
114
+ // Runner 作为执行宿主组件属于持有者路径,打开时收割遗留孤儿运行
115
+ recoverOrphans: true,
113
116
  });
114
117
  this.globalStorage = options.globalStorage ?? options.platform.storage.createGlobalStorage({
115
118
  customHome: this.customHome,
@@ -128,34 +131,38 @@ export class ActionRunner {
128
131
  this.maxSubRuns = options.maxSubRuns ?? 64;
129
132
  this.actionResolver = options.actionResolver;
130
133
  this.getStorageForPackage = options.getStorageForPackage;
131
- this.packageContextResolver = options.packageContextResolver;
134
+ this.packageFactory = new PackageRunnerFactory({
135
+ globalStorage: this.globalStorage,
136
+ projectRoot: this.projectRoot,
137
+ process: this.process,
138
+ clock: this.clock,
139
+ platform: this.platform,
140
+ maxCallDepth: this.maxCallDepth,
141
+ maxSubRuns: this.maxSubRuns,
142
+ actionResolver: this.actionResolver,
143
+ getStorageForPackage: this.getStorageForPackage,
144
+ packageContextResolver: options.packageContextResolver,
145
+ customHome: this.customHome,
146
+ }, (opts) => new ActionRunner(opts));
147
+ }
148
+ /** 本地 Action 注册表底层映射(仅内部与跨包工厂委托使用) */
149
+ get actions() {
150
+ return this.registry.map;
132
151
  }
133
152
  getStorage() {
134
153
  return this.storage;
135
154
  }
136
155
  getPackageRunners() {
137
- return this.packageRunners;
156
+ return this.packageFactory.runners;
138
157
  }
139
158
  registerAction(idOrAction, actionDef) {
140
- if (typeof idOrAction === "string") {
141
- if (actionDef) {
142
- this.actions.set(idOrAction, actionDef);
143
- }
144
- }
145
- else {
146
- const actObj = idOrAction;
147
- const id = actObj.id || "anonymous-action";
148
- const act = actObj.action || (actObj.run ? actObj : undefined);
149
- if (act) {
150
- this.actions.set(id, act);
151
- }
152
- }
159
+ this.registry.registerAction(idOrAction, actionDef);
153
160
  }
154
161
  /**
155
162
  * 根据 ID 检索注册的 Action。
156
163
  */
157
164
  getAction(id) {
158
- return this.actions.get(id);
165
+ return this.registry.getAction(id);
159
166
  }
160
167
  /**
161
168
  * 动态解析 Action(支持本地注册表、自定义解析器委托与已链接包目录索引检索)。
@@ -164,15 +171,13 @@ export class ActionRunner {
164
171
  * @returns 解析判别联合结果(found | not_found | load_failed)
165
172
  */
166
173
  async resolveAction(actionOrRef) {
167
- if (typeof actionOrRef === "object" &&
168
- "run" in actionOrRef &&
169
- typeof actionOrRef.run === "function") {
174
+ if (isActionDefinitionObject(actionOrRef)) {
170
175
  return { status: "found", action: actionOrRef };
171
176
  }
172
177
  const ref = actionOrRef;
173
178
  let parsed;
174
179
  try {
175
- parsed = ActionResolver.parseRef(ref);
180
+ parsed = parseActionRef(ref);
176
181
  }
177
182
  catch (err) {
178
183
  return { status: "not_found", reason: err.message };
@@ -180,18 +185,9 @@ export class ActionRunner {
180
185
  const targetActionId = parsed.actionId;
181
186
  const targetPackageId = parsed.packageId;
182
187
  // 本地 actions 映射表优先检索
183
- if (targetPackageId && targetPackageId !== this.packageId) {
184
- if (this.actions.has(`${targetPackageId}/${targetActionId}`)) {
185
- return { status: "found", action: this.actions.get(`${targetPackageId}/${targetActionId}`) };
186
- }
187
- }
188
- else {
189
- if (this.actions.has(targetActionId)) {
190
- return { status: "found", action: this.actions.get(targetActionId) };
191
- }
192
- if (this.packageId && this.actions.has(`${this.packageId}/${targetActionId}`)) {
193
- return { status: "found", action: this.actions.get(`${this.packageId}/${targetActionId}`) };
194
- }
188
+ const localMatched = findLocalAction(this.actions, parsed, this.packageId);
189
+ if (localMatched) {
190
+ return { status: "found", action: localMatched };
195
191
  }
196
192
  // 外部注入的自定义 actionResolver 调度
197
193
  if (this.actionResolver) {
@@ -246,7 +242,6 @@ export class ActionRunner {
246
242
  let actionsMap;
247
243
  try {
248
244
  actionsMap = await loadActions(resolved.projectRoot, config.actionsDir, {
249
- autoInstall: false,
250
245
  loader: this.platform?.modules,
251
246
  });
252
247
  }
@@ -276,13 +271,13 @@ export class ActionRunner {
276
271
  * 获取当前 Runner 已注册的所有 Action 列表。
277
272
  */
278
273
  listActions() {
279
- return Array.from(this.actions.values());
274
+ return this.registry.listActions();
280
275
  }
281
276
  /**
282
277
  * 注入或更新跨包运行上下文解析委托。
283
278
  */
284
279
  setPackageContextResolver(resolver) {
285
- this.packageContextResolver = resolver;
280
+ this.packageFactory.setPackageContextResolver(resolver);
286
281
  }
287
282
  /**
288
283
  * 跨包运行时解析与获取(确保跨包执行具备独立的配置、存储、状态与 Action 注册表)。
@@ -292,95 +287,7 @@ export class ActionRunner {
292
287
  * 并发方 await 同一构建任务,构建失败时移除该 Promise 以便后续重试。
293
288
  */
294
289
  async resolveTargetPackageRunner(targetPackageId) {
295
- const cached = this.packageRunners.get(targetPackageId);
296
- if (cached) {
297
- return cached;
298
- }
299
- const inFlight = this.pendingPackageRunners.get(targetPackageId);
300
- if (inFlight) {
301
- return inFlight;
302
- }
303
- const building = this.buildTargetPackageRunner(targetPackageId).finally(() => {
304
- // 无论成败均移除 in-flight 记录:成功者已写入正式缓存,失败者允许重试
305
- this.pendingPackageRunners.delete(targetPackageId);
306
- });
307
- this.pendingPackageRunners.set(targetPackageId, building);
308
- return building;
309
- }
310
- /**
311
- * 实际构建跨包 Runner(仅由 resolveTargetPackageRunner 串行调度)。
312
- */
313
- async buildTargetPackageRunner(targetPackageId) {
314
- if (this.packageContextResolver) {
315
- const resolved = await this.packageContextResolver(targetPackageId);
316
- if (resolved) {
317
- const runner = new ActionRunner({
318
- packageId: targetPackageId,
319
- packageInstanceId: resolved.packageInstanceId || resolved.projectConfig?.packageInstanceId || targetPackageId,
320
- generationId: resolved.generationId || resolved.projectConfig?.generationId || "1",
321
- storage: resolved.storage,
322
- globalStorage: this.globalStorage,
323
- projectRoot: resolved.projectRoot,
324
- projectConfig: resolved.projectConfig,
325
- actions: resolved.actions,
326
- process: this.process,
327
- clock: this.clock,
328
- platform: this.platform,
329
- maxCallDepth: this.maxCallDepth,
330
- maxSubRuns: this.maxSubRuns,
331
- actionResolver: this.actionResolver,
332
- getStorageForPackage: this.getStorageForPackage,
333
- packageContextResolver: this.packageContextResolver,
334
- });
335
- this.packageRunners.set(targetPackageId, runner);
336
- return runner;
337
- }
338
- }
339
- const root = resolvePackageRoot(targetPackageId, this.projectRoot, this.customHome);
340
- if (root && existsSync(root)) {
341
- const config = loadProjectConfig(root);
342
- let storage;
343
- if (this.getStorageForPackage) {
344
- storage = this.getStorageForPackage(targetPackageId, root);
345
- }
346
- else if (this.platform) {
347
- storage = this.platform.storage.createStorage(targetPackageId, {
348
- projectRoot: root,
349
- customHome: this.customHome,
350
- });
351
- }
352
- else {
353
- // 回退分支与主路径共用 createStorage 单一事实源,确保 run 记录落在统一解析的库文件
354
- const { createStorage } = await import("../storage/index.js");
355
- storage = createStorage(targetPackageId, { projectRoot: root, customHome: this.customHome });
356
- }
357
- const actionsMap = await loadActions(root, config.actionsDir, {
358
- autoInstall: false,
359
- loader: this.platform?.modules,
360
- });
361
- const runner = new ActionRunner({
362
- packageId: targetPackageId,
363
- packageInstanceId: config.packageInstanceId || targetPackageId,
364
- generationId: config.generationId || "1",
365
- storage,
366
- globalStorage: this.globalStorage,
367
- projectRoot: root,
368
- projectConfig: config,
369
- actions: actionsMap,
370
- process: this.process,
371
- clock: this.clock,
372
- platform: this.platform,
373
- maxCallDepth: this.maxCallDepth,
374
- maxSubRuns: this.maxSubRuns,
375
- actionResolver: this.actionResolver,
376
- getStorageForPackage: this.getStorageForPackage,
377
- packageContextResolver: this.packageContextResolver,
378
- customHome: this.customHome,
379
- });
380
- this.packageRunners.set(targetPackageId, runner);
381
- return runner;
382
- }
383
- return undefined;
290
+ return this.packageFactory.resolveTargetPackageRunner(targetPackageId);
384
291
  }
385
292
  /**
386
293
  * 异步启动 Action 的执行并立即返回 ExecutionHandle 句柄。
@@ -401,7 +308,7 @@ export class ActionRunner {
401
308
  message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
402
309
  };
403
310
  // 安全记录 failed 状态(不可序列化的非法 input 严禁直接写入持久化存储)
404
- this.tryPersistInitialRun(runCtx, "failed", error);
311
+ tryCreateRun(this.storage, buildInitialRunRecord(this.buildRunPersistenceInput(runCtx), "failed", error));
405
312
  return {
406
313
  runId,
407
314
  result: Promise.resolve({ ok: false, runId, error }),
@@ -409,8 +316,8 @@ export class ActionRunner {
409
316
  };
410
317
  }
411
318
  // 始终优先将执行尝试持久化到存储中(确保任意异常与终态都可追溯)
412
- this.persistInitialRun(runCtx, "running");
413
- const finalizer = this.createRunFinalizer(runCtx);
319
+ createRunOrThrow(this.storage, buildInitialRunRecord(this.buildRunPersistenceInput(runCtx), "running"));
320
+ const finalizer = createRunFinalizer(this.storage, runCtx.runId);
414
321
  // 调用嵌套深度限制检测 (Max Call Depth Check)
415
322
  const depthError = this.checkCallDepth(runCtx.callStack, targetActionId, runCtx.options);
416
323
  if (depthError) {
@@ -512,33 +419,24 @@ export class ActionRunner {
512
419
  let action;
513
420
  let targetActionId;
514
421
  let targetPackageId = this.packageId;
515
- if (typeof actionOrId === "object" &&
516
- "run" in actionOrId &&
517
- typeof actionOrId.run === "function") {
422
+ if (isActionDefinitionObject(actionOrId)) {
518
423
  action = actionOrId;
519
424
  const actObj = action;
520
425
  if (actObj.id) {
521
426
  targetActionId = actObj.id;
522
427
  }
523
428
  else {
524
- targetActionId = this.resolveAnonymousActionId(action);
429
+ targetActionId = resolveAnonymousActionId(this.actions, action);
525
430
  }
526
431
  this.actions.set(targetActionId, action);
527
432
  }
528
433
  else {
529
- const parsed = ActionResolver.parseRef(actionOrId);
434
+ const parsed = parseActionRef(actionOrId);
530
435
  targetActionId = parsed.actionId;
531
436
  if (parsed.packageId) {
532
437
  targetPackageId = parsed.packageId;
533
438
  }
534
- if (parsed.packageId && parsed.packageId !== this.packageId) {
535
- action = this.actions.get(`${parsed.packageId}/${targetActionId}`);
536
- }
537
- else {
538
- action =
539
- this.actions.get(targetActionId) ||
540
- (this.packageId ? this.actions.get(`${this.packageId}/${targetActionId}`) : undefined);
541
- }
439
+ action = findLocalAction(this.actions, parsed, this.packageId);
542
440
  }
543
441
  return {
544
442
  runId,
@@ -554,128 +452,27 @@ export class ActionRunner {
554
452
  };
555
453
  }
556
454
  /**
557
- * 为匿名传入的 Action 定义对象解析或分配稳定标识。
455
+ * 依据运行期上下文装配落库模块入参(RunRecord 构造领域契约)。
558
456
  */
559
- resolveAnonymousActionId(action) {
560
- const actObj = action;
561
- let foundId;
562
- for (const [id, a] of this.actions) {
563
- if (a === action) {
564
- foundId = id;
565
- break;
566
- }
567
- }
568
- if (foundId) {
569
- return foundId;
570
- }
571
- let anonId = anonymousRunnerActionIds.get(action);
572
- if (!anonId) {
573
- anonymousRunnerActionCounter++;
574
- anonId = `anonymous-action-${anonymousRunnerActionCounter}`;
575
- anonymousRunnerActionIds.set(action, anonId);
576
- }
577
- try {
578
- actObj.id = anonId;
579
- }
580
- catch { }
581
- return anonId;
582
- }
583
- /**
584
- * 尝试将 failed 终态初始记录写入存储(非法输入场景,存储异常静默忽略)。
585
- */
586
- tryPersistInitialRun(runCtx, status, error) {
587
- const initialRun = this.buildInitialRunRecord(runCtx, status, error);
588
- try {
589
- this.storage.createRun(initialRun);
590
- }
591
- catch { }
592
- }
593
- /**
594
- * 构建 running 初始记录并强制写入存储(存储不可用时抛出 RUN_REPOSITORY_UNAVAILABLE)。
595
- */
596
- persistInitialRun(runCtx, status) {
597
- const initialRun = this.buildInitialRunRecord(runCtx, status);
598
- try {
599
- this.storage.createRun(initialRun);
600
- }
601
- catch (err) {
602
- const error = new Error(`RUN_REPOSITORY_UNAVAILABLE: Failed to initialize run record in repository: ${err?.message || String(err)}`);
603
- error.code = RUN_REPOSITORY_UNAVAILABLE;
604
- error.details = { originalError: err?.message };
605
- throw error;
606
- }
607
- }
608
- /**
609
- * 依据运行期上下文构造 RunRecord 初始记录。
610
- */
611
- buildInitialRunRecord(runCtx, status, error) {
612
- const { runId, options, targetPackageId, targetActionId, startedAt } = runCtx;
613
- const record = {
614
- id: runId,
457
+ buildRunPersistenceInput(runCtx) {
458
+ const { options, targetPackageId, targetActionId, startedAt, input } = runCtx;
459
+ return {
460
+ runId: runCtx.runId,
615
461
  rootRunId: this.computeRootRunId(runCtx),
616
462
  parentRunId: options.parentRunId,
617
- packageId: targetPackageId,
618
- packageInstanceId: options.packageInstanceId || (this.packageId === targetPackageId ? this.packageInstanceId : targetPackageId),
619
- actionId: targetActionId,
620
- generationId: options.generationId || (this.packageId === targetPackageId ? this.generationId : "1"),
621
- ownerId: options.ownerId || "local",
622
- hostSessionId: options.hostSessionId || this.hostSessionId,
623
- status,
624
- error,
463
+ ownerId: options.ownerId,
464
+ hostSessionId: options.hostSessionId,
465
+ packageInstanceId: options.packageInstanceId,
466
+ generationId: options.generationId,
467
+ targetPackageId,
468
+ targetActionId,
625
469
  startedAt,
470
+ input,
471
+ runnerPackageId: this.packageId,
472
+ runnerPackageInstanceId: this.packageInstanceId,
473
+ runnerGenerationId: this.generationId,
474
+ runnerHostSessionId: this.hostSessionId,
626
475
  };
627
- if (status === "running") {
628
- record.input = runCtx.input;
629
- }
630
- else {
631
- record.finishedAt = startedAt;
632
- }
633
- return record;
634
- }
635
- /**
636
- * 创建运行终态收敛器:封装终态去重、超时定时器清理与落库异常捕获。
637
- */
638
- createRunFinalizer(runCtx) {
639
- const finalizer = {
640
- finalized: false,
641
- isTimeout: false,
642
- persistError: undefined,
643
- timeoutTimer: undefined,
644
- controller: undefined,
645
- signal: undefined,
646
- onAbort: undefined,
647
- startTimeout: (controller, timeoutMs) => {
648
- finalizer.controller = controller;
649
- finalizer.timeoutTimer = setTimeout(() => {
650
- finalizer.isTimeout = true;
651
- finalizer.controller?.abort(new Error(`Action exceeded timeout of ${timeoutMs}ms`));
652
- }, timeoutMs);
653
- },
654
- finalize: (status, output, error) => {
655
- if (finalizer.timeoutTimer) {
656
- clearTimeout(finalizer.timeoutTimer);
657
- finalizer.timeoutTimer = undefined;
658
- }
659
- if (finalizer.signal && finalizer.onAbort) {
660
- finalizer.signal.removeEventListener("abort", finalizer.onAbort);
661
- finalizer.onAbort = undefined;
662
- }
663
- if (finalizer.finalized)
664
- return;
665
- finalizer.finalized = true;
666
- try {
667
- this.storage.updateRun(runCtx.runId, status, output, error);
668
- }
669
- catch (persistErr) {
670
- finalizer.persistError = {
671
- code: RUN_PERSISTENCE_FAILED,
672
- message: `RUN_PERSISTENCE_FAILED: Failed to persist run state: ${persistErr?.message || String(persistErr)}`,
673
- details: { originalError: persistErr?.message },
674
- };
675
- }
676
- },
677
- };
678
- return finalizer;
679
476
  }
680
477
  /**
681
478
  * 调用嵌套深度限制检测(超限时返回 ACTION_CALL_CYCLE 错误)。
@@ -892,11 +689,16 @@ export class ActionRunner {
892
689
  const { runCtx, actionOrId, input, options, controller, finalizer, ctx } = args;
893
690
  const { runId, targetActionId } = runCtx;
894
691
  const abortPromise = new Promise((_, reject) => {
692
+ const rejectWithReason = () => reject(controller.signal.reason || new Error("Action execution was cancelled"));
895
693
  if (controller.signal.aborted) {
896
- reject(controller.signal.reason || new Error("Action execution was cancelled"));
694
+ rejectWithReason();
897
695
  }
898
696
  else {
899
- controller.signal.addEventListener("abort", () => reject(controller.signal.reason || new Error("Action execution was cancelled")), { once: true });
697
+ controller.signal.addEventListener("abort", rejectWithReason, { once: true });
698
+ // 成功与失败路径统一注销:finalize 时回收监听句柄,避免信号对象残留引用
699
+ finalizer.raceListenerRemovers.push(() => {
700
+ controller.signal.removeEventListener("abort", rejectWithReason);
701
+ });
900
702
  }
901
703
  });
902
704
  return (async () => {
@@ -936,17 +738,11 @@ export class ActionRunner {
936
738
  finalizer.finalize("failed", undefined, error);
937
739
  return { ok: false, runId, error };
938
740
  }
939
- if (currentAction.inputSchema) {
940
- const val = validateSchema(currentAction.inputSchema, input);
941
- if (!val.valid) {
942
- const error = {
943
- code: INPUT_VALIDATION_FAILED,
944
- message: `Input schema validation failed for action '${targetActionId}'`,
945
- details: val.errors,
946
- };
947
- finalizer.finalize("failed", undefined, error);
948
- return { ok: false, runId, error };
949
- }
741
+ // 复用统一校验入口:优先 action 声明 schema,缺失时回退 projectConfig 清单声明
742
+ const schemaError = this.checkActionInputSchema(currentAction, targetActionId, input);
743
+ if (schemaError) {
744
+ finalizer.finalize("failed", undefined, schemaError);
745
+ return { ok: false, runId, error: schemaError };
950
746
  }
951
747
  }
952
748
  const rawOutput = await Promise.race([
@@ -1052,6 +848,16 @@ export class ActionRunner {
1052
848
  error: finalizer.persistError || error,
1053
849
  };
1054
850
  }
851
+ /**
852
+ * 释放 Runner 持有的跨包资源:遍历关闭全部子包 Runner 及其独立创建的存储连接。
853
+ *
854
+ * 幂等且并发安全:重复调用直接复用首次 Promise;子包存储异常不阻断其余释放;
855
+ * 仅关闭由本 Runner 构建子包 Runner 时独立创建的存储,外部注入的存储(如经
856
+ * packageContextResolver 传入的目标包自身存储)生命周期归所有者管理,不在此误关。
857
+ */
858
+ async dispose() {
859
+ return this.packageFactory.dispose();
860
+ }
1055
861
  /**
1056
862
  * 同步等待方式执行指定 Action,直接返回 ExecutionResult 信封结果。
1057
863
  *
@@ -1,4 +1,4 @@
1
- import type { ActionDefinition } from "@actiondock/sdk";
1
+ import { type ActionDefinition } from "@actiondock/sdk";
2
2
  import type { ActionSpec } from "../app/types.js";
3
3
  import type { ConfigItemDefinition } from "../project/types.js";
4
4
  import type { ActionDockTarget } from "../target/types.js";
@@ -71,6 +71,16 @@ export declare class StandaloneDispatcher {
71
71
  private handleRun;
72
72
  private handleConfig;
73
73
  private handleState;
74
+ /**
75
+ * 判定配置读取异常是否为目标层能力缺失(键未配置语义)而非内部故障。
76
+ */
77
+ private isTargetCapabilityError;
78
+ /**
79
+ * 解析携带命名空间前缀的状态键参数:委托 sdk 单一事实源 decodeStateKey。
80
+ *
81
+ * 解析失败(如存在多个未转义冒号的歧义键)时按纯键处理,交由存储层兜底。
82
+ */
83
+ private decodeStateKeyArg;
74
84
  private printHelp;
75
85
  }
76
86
  /**