@actiondock/core 2.5.1 → 2.7.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 (175) 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 +22 -0
  25. package/dist/errors.js +22 -0
  26. package/dist/execution/service.js +14 -9
  27. package/dist/export/templates.js +42 -30
  28. package/dist/host/host.d.ts +33 -0
  29. package/dist/host/host.js +222 -362
  30. package/dist/host/routing.d.ts +92 -0
  31. package/dist/host/routing.js +238 -0
  32. package/dist/host/types.d.ts +8 -0
  33. package/dist/index.d.ts +3 -1
  34. package/dist/index.js +3 -1
  35. package/dist/input/advice.d.ts +100 -0
  36. package/dist/input/advice.js +767 -0
  37. package/dist/input/describe.d.ts +106 -0
  38. package/dist/input/describe.js +176 -0
  39. package/dist/input/file-input.d.ts +54 -0
  40. package/dist/input/file-input.js +124 -0
  41. package/dist/input/flat-decode.d.ts +11 -0
  42. package/dist/input/flat-decode.js +13 -0
  43. package/dist/input/flat-errors.d.ts +72 -0
  44. package/dist/input/flat-errors.js +87 -0
  45. package/dist/input/flat-materializer.d.ts +29 -0
  46. package/dist/input/flat-materializer.js +179 -0
  47. package/dist/input/flat-parser.d.ts +50 -0
  48. package/dist/input/flat-parser.js +169 -0
  49. package/dist/input/flat-predicates.d.ts +34 -0
  50. package/dist/input/flat-predicates.js +50 -0
  51. package/dist/input/index.d.ts +14 -0
  52. package/dist/input/index.js +14 -0
  53. package/dist/input/input-resolver.d.ts +63 -0
  54. package/dist/input/input-resolver.js +254 -0
  55. package/dist/input/json-depth-scanner.d.ts +17 -0
  56. package/dist/input/json-depth-scanner.js +49 -0
  57. package/dist/input/metadata.d.ts +99 -0
  58. package/dist/input/metadata.js +99 -0
  59. package/dist/input/stdin-input.d.ts +26 -0
  60. package/dist/input/stdin-input.js +125 -0
  61. package/dist/input/utf8.d.ts +19 -0
  62. package/dist/input/utf8.js +36 -0
  63. package/dist/input/validation-mapper.d.ts +27 -0
  64. package/dist/input/validation-mapper.js +144 -0
  65. package/dist/ipc/host.js +38 -0
  66. package/dist/json/index.d.ts +1 -0
  67. package/dist/json/index.js +1 -0
  68. package/dist/json/value-validator.d.ts +88 -0
  69. package/dist/json/value-validator.js +330 -0
  70. package/dist/platform/types.d.ts +7 -0
  71. package/dist/process/control-arbiter.d.ts +107 -0
  72. package/dist/process/control-arbiter.js +251 -0
  73. package/dist/process/diagnostics-sink.d.ts +26 -0
  74. package/dist/process/diagnostics-sink.js +35 -0
  75. package/dist/process/index.d.ts +7 -0
  76. package/dist/process/index.js +7 -0
  77. package/dist/process/input-dispatcher.d.ts +88 -0
  78. package/dist/process/input-dispatcher.js +240 -0
  79. package/dist/process/managed-record.d.ts +78 -0
  80. package/dist/process/managed-record.js +1 -0
  81. package/dist/process/process-manager.d.ts +15 -56
  82. package/dist/process/process-manager.js +119 -707
  83. package/dist/process/reservation-table.d.ts +39 -0
  84. package/dist/process/reservation-table.js +80 -0
  85. package/dist/process/run-executor.d.ts +16 -0
  86. package/dist/process/run-executor.js +155 -0
  87. package/dist/process/terminal-output-cache.d.ts +66 -0
  88. package/dist/process/terminal-output-cache.js +132 -0
  89. package/dist/profile/client-actions.d.ts +56 -0
  90. package/dist/profile/client-actions.js +215 -0
  91. package/dist/profile/client-config.d.ts +20 -0
  92. package/dist/profile/client-config.js +51 -0
  93. package/dist/profile/client-health.d.ts +14 -0
  94. package/dist/profile/client-health.js +69 -0
  95. package/dist/profile/client-playbooks.d.ts +19 -0
  96. package/dist/profile/client-playbooks.js +36 -0
  97. package/dist/profile/client-query.d.ts +30 -0
  98. package/dist/profile/client-query.js +70 -0
  99. package/dist/profile/client-runs.d.ts +42 -0
  100. package/dist/profile/client-runs.js +58 -0
  101. package/dist/profile/client-state.d.ts +56 -0
  102. package/dist/profile/client-state.js +83 -0
  103. package/dist/profile/client-transport.d.ts +61 -0
  104. package/dist/profile/client-transport.js +156 -0
  105. package/dist/profile/client.d.ts +23 -142
  106. package/dist/profile/client.js +28 -588
  107. package/dist/profile/manager.js +1 -1
  108. package/dist/project/closure.d.ts +4 -4
  109. package/dist/project/closure.js +11 -5
  110. package/dist/project/init.js +2 -2
  111. package/dist/project/loader.d.ts +0 -25
  112. package/dist/project/loader.js +1 -154
  113. package/dist/project/transactions.d.ts +12 -14
  114. package/dist/project/transactions.js +60 -836
  115. package/dist/registry/registry.js +6 -4
  116. package/dist/registry/resolve.js +1 -1
  117. package/dist/runtime/action-registry.d.ts +45 -0
  118. package/dist/runtime/action-registry.js +108 -0
  119. package/dist/runtime/clock.d.ts +5 -17
  120. package/dist/runtime/clock.js +5 -15
  121. package/dist/runtime/context.d.ts +3 -0
  122. package/dist/runtime/context.js +17 -56
  123. package/dist/runtime/index.d.ts +3 -0
  124. package/dist/runtime/index.js +3 -0
  125. package/dist/runtime/package-runner-factory.d.ts +82 -0
  126. package/dist/runtime/package-runner-factory.js +186 -0
  127. package/dist/runtime/run-persistence.d.ts +85 -0
  128. package/dist/runtime/run-persistence.js +111 -0
  129. package/dist/runtime/runner.d.ts +25 -45
  130. package/dist/runtime/runner.js +130 -354
  131. package/dist/runtime/standalone.d.ts +25 -2
  132. package/dist/runtime/standalone.js +305 -87
  133. package/dist/schema/index.d.ts +1 -0
  134. package/dist/schema/index.js +1 -0
  135. package/dist/schema/validator.d.ts +10 -0
  136. package/dist/schema/validator.js +48 -54
  137. package/dist/server/dispatcher.d.ts +8 -3
  138. package/dist/server/dispatcher.js +16 -40
  139. package/dist/server/routes/runs.js +6 -4
  140. package/dist/server/security.d.ts +1 -7
  141. package/dist/server/security.js +2 -13
  142. package/dist/storage/clock.d.ts +25 -0
  143. package/dist/storage/clock.js +23 -0
  144. package/dist/storage/data-dir-lock.d.ts +3 -36
  145. package/dist/storage/data-dir-lock.js +48 -881
  146. package/dist/storage/index.d.ts +8 -1
  147. package/dist/storage/index.js +8 -2
  148. package/dist/storage/lazy.js +1 -0
  149. package/dist/storage/lock-core.d.ts +162 -0
  150. package/dist/storage/lock-core.js +885 -0
  151. package/dist/storage/sqlite.d.ts +19 -6
  152. package/dist/storage/sqlite.js +81 -89
  153. package/dist/storage/types.d.ts +27 -5
  154. package/dist/storage/types.js +21 -0
  155. package/dist/storage/utils.d.ts +22 -0
  156. package/dist/storage/utils.js +34 -0
  157. package/dist/target/local.d.ts +1 -0
  158. package/dist/target/local.js +23 -17
  159. package/dist/target/remote-errors.d.ts +21 -0
  160. package/dist/target/remote-errors.js +51 -0
  161. package/dist/target/remote-polling.d.ts +35 -0
  162. package/dist/target/remote-polling.js +104 -0
  163. package/dist/target/remote.d.ts +4 -0
  164. package/dist/target/remote.js +108 -301
  165. package/dist/target/sse-parser.d.ts +35 -0
  166. package/dist/target/sse-parser.js +163 -0
  167. package/dist/target/target.js +2 -0
  168. package/dist/target/types.d.ts +6 -0
  169. package/dist/utils/index.d.ts +1 -0
  170. package/dist/utils/index.js +1 -0
  171. package/dist/utils/net.d.ts +10 -0
  172. package/dist/utils/net.js +16 -0
  173. package/dist/version.d.ts +1 -1
  174. package/dist/version.js +1 -1
  175. package/package.json +2 -2
@@ -2,72 +2,30 @@ 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";
7
- import { validateSchema } from "../schema/validator.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
+ import { validateSchemaOnly } 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
- /**
12
- * 校验值是否为合法的 JSON 兼容结构,严禁 NaN、Infinity、循环引用及不可序列化类型。
13
- *
14
- * 环路检测采用「当前递归路径栈」语义:对象仅在自身子树遍历期间保持标记,
15
- * 子树遍历完成后立即移除,从而正确放行共享子对象的有向无环结构(如 {a: shared, b: shared})。
16
- */
17
- export function validateJsonValue(val, stack = new WeakSet()) {
18
- if (val === null || typeof val === "boolean" || typeof val === "string") {
19
- return { valid: true };
20
- }
21
- if (typeof val === "number") {
22
- if (!Number.isFinite(val) || Number.isNaN(val)) {
23
- return { valid: false, reason: `Number is non-finite or NaN (${val})` };
24
- }
25
- return { valid: true };
26
- }
27
- if (typeof val === "undefined" || typeof val === "function" || typeof val === "symbol" || typeof val === "bigint") {
28
- return { valid: false, reason: `Unsupported JSON type '${typeof val}'` };
29
- }
30
- if (typeof val === "object") {
31
- if (stack.has(val)) {
32
- return { valid: false, reason: "Circular reference detected in object structure" };
33
- }
34
- stack.add(val);
35
- try {
36
- if (Array.isArray(val)) {
37
- for (const item of val) {
38
- const res = validateJsonValue(item, stack);
39
- if (!res.valid)
40
- return res;
41
- }
42
- return { valid: true };
43
- }
44
- for (const v of Object.values(val)) {
45
- if (v !== undefined) {
46
- const res = validateJsonValue(v, stack);
47
- if (!res.valid)
48
- return res;
49
- }
50
- }
51
- return { valid: true };
52
- }
53
- finally {
54
- // 无论正常返回还是提前返回,均须将当前对象移出路径栈,避免祖先对象被误判为环路
55
- stack.delete(val);
56
- }
57
- }
58
- return { valid: true };
59
- }
60
- const anonymousRunnerActionIds = new WeakMap();
61
- let anonymousRunnerActionCounter = 0;
14
+ import { validateJsonValue, validateActionInputValue, assertJsonValue, } from "../json/value-validator.js";
15
+ // 复用统一迭代式 JsonValue 校验器,杜绝深层递归栈溢出,并保持既有导出兼容
16
+ export { validateJsonValue, validateActionInputValue, assertJsonValue, };
62
17
  /**
63
18
  * ActionDock 核心执行引擎(ActionRunner)。
64
19
  *
65
- * 职责:
66
- * - 负责 Action 执行的全生命周期管理(校验、隔离、跟踪、落库)。
67
- * - 入参 (inputSchema) 与出参 (outputSchema) 的 JSON Schema 严格校验。
68
- * - 嵌套 Action 相互调用的环路检测(Cycle Detection)。
69
- * - 超时 (Timeout) 与中断信号 (AbortSignal) 竞态控制。
70
- * - 自动记录并持久化 RunRecord 运行记录至 SQLite 存储。
20
+ * 本类为执行编排壳:负责 Action 执行的全生命周期编排(校验、深度与环路检测、
21
+ * 超时与取消竞态、子调用 uses 授权与错误分类),并将单一职责域委托至独立模块:
22
+ * - Action 注册与检索委托 `ActionRegistry`(action-registry.ts)
23
+ * - RunRecord 构造与落库委托 run-persistence.ts
24
+ * - 跨包 Runner 构建与缓存委托 `PackageRunnerFactory`(package-runner-factory.ts)
25
+ *
26
+ * 入参 (inputSchema) 与出参 (outputSchema) 的 JSON Schema 严格校验、嵌套 Action
27
+ * 相互调用的环路检测(Cycle Detection)、超时 (Timeout) 与中断信号 (AbortSignal)
28
+ * 竞态控制与运行记录持久化编排仍由本类承担。
71
29
  */
72
30
  export class ActionRunner {
73
31
  packageId;
@@ -78,19 +36,16 @@ export class ActionRunner {
78
36
  projectRoot;
79
37
  projectConfig;
80
38
  configOverrides;
81
- actions;
39
+ registry;
82
40
  clock;
83
41
  process;
84
42
  platform;
85
43
  maxCallDepth;
86
44
  maxSubRuns;
87
45
  activeSubRuns = 0;
88
- packageRunners = new Map();
89
- /** 跨包 Runner 构建中的 in-flight Promise:并发调用 await 同一构建任务,消除 check-then-act 竞态 */
90
- pendingPackageRunners = new Map();
46
+ packageFactory;
91
47
  actionResolver;
92
48
  getStorageForPackage;
93
- packageContextResolver;
94
49
  customHome;
95
50
  hostSessionId;
96
51
  constructor(options) {
@@ -101,7 +56,7 @@ export class ActionRunner {
101
56
  this.projectRoot = options.projectRoot;
102
57
  this.projectConfig = options.projectConfig;
103
58
  this.configOverrides = options.configOverrides || {};
104
- this.actions = options.actions || new Map();
59
+ this.registry = new ActionRegistry(options.actions);
105
60
  this.customHome = options.customHome;
106
61
  this.platform = options.platform;
107
62
  if (options.platform) {
@@ -110,6 +65,8 @@ export class ActionRunner {
110
65
  this.storage = options.storage ?? options.platform.storage.createStorage(this.packageId, {
111
66
  projectRoot: this.projectRoot,
112
67
  customHome: this.customHome,
68
+ // Runner 作为执行宿主组件属于持有者路径,打开时收割遗留孤儿运行
69
+ recoverOrphans: true,
113
70
  });
114
71
  this.globalStorage = options.globalStorage ?? options.platform.storage.createGlobalStorage({
115
72
  customHome: this.customHome,
@@ -128,34 +85,38 @@ export class ActionRunner {
128
85
  this.maxSubRuns = options.maxSubRuns ?? 64;
129
86
  this.actionResolver = options.actionResolver;
130
87
  this.getStorageForPackage = options.getStorageForPackage;
131
- this.packageContextResolver = options.packageContextResolver;
88
+ this.packageFactory = new PackageRunnerFactory({
89
+ globalStorage: this.globalStorage,
90
+ projectRoot: this.projectRoot,
91
+ process: this.process,
92
+ clock: this.clock,
93
+ platform: this.platform,
94
+ maxCallDepth: this.maxCallDepth,
95
+ maxSubRuns: this.maxSubRuns,
96
+ actionResolver: this.actionResolver,
97
+ getStorageForPackage: this.getStorageForPackage,
98
+ packageContextResolver: options.packageContextResolver,
99
+ customHome: this.customHome,
100
+ }, (opts) => new ActionRunner(opts));
101
+ }
102
+ /** 本地 Action 注册表底层映射(仅内部与跨包工厂委托使用) */
103
+ get actions() {
104
+ return this.registry.map;
132
105
  }
133
106
  getStorage() {
134
107
  return this.storage;
135
108
  }
136
109
  getPackageRunners() {
137
- return this.packageRunners;
110
+ return this.packageFactory.runners;
138
111
  }
139
112
  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
- }
113
+ this.registry.registerAction(idOrAction, actionDef);
153
114
  }
154
115
  /**
155
116
  * 根据 ID 检索注册的 Action。
156
117
  */
157
118
  getAction(id) {
158
- return this.actions.get(id);
119
+ return this.registry.getAction(id);
159
120
  }
160
121
  /**
161
122
  * 动态解析 Action(支持本地注册表、自定义解析器委托与已链接包目录索引检索)。
@@ -164,15 +125,13 @@ export class ActionRunner {
164
125
  * @returns 解析判别联合结果(found | not_found | load_failed)
165
126
  */
166
127
  async resolveAction(actionOrRef) {
167
- if (typeof actionOrRef === "object" &&
168
- "run" in actionOrRef &&
169
- typeof actionOrRef.run === "function") {
128
+ if (isActionDefinitionObject(actionOrRef)) {
170
129
  return { status: "found", action: actionOrRef };
171
130
  }
172
131
  const ref = actionOrRef;
173
132
  let parsed;
174
133
  try {
175
- parsed = ActionResolver.parseRef(ref);
134
+ parsed = parseActionRef(ref);
176
135
  }
177
136
  catch (err) {
178
137
  return { status: "not_found", reason: err.message };
@@ -180,18 +139,9 @@ export class ActionRunner {
180
139
  const targetActionId = parsed.actionId;
181
140
  const targetPackageId = parsed.packageId;
182
141
  // 本地 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
- }
142
+ const localMatched = findLocalAction(this.actions, parsed, this.packageId);
143
+ if (localMatched) {
144
+ return { status: "found", action: localMatched };
195
145
  }
196
146
  // 外部注入的自定义 actionResolver 调度
197
147
  if (this.actionResolver) {
@@ -246,7 +196,6 @@ export class ActionRunner {
246
196
  let actionsMap;
247
197
  try {
248
198
  actionsMap = await loadActions(resolved.projectRoot, config.actionsDir, {
249
- autoInstall: false,
250
199
  loader: this.platform?.modules,
251
200
  });
252
201
  }
@@ -276,13 +225,13 @@ export class ActionRunner {
276
225
  * 获取当前 Runner 已注册的所有 Action 列表。
277
226
  */
278
227
  listActions() {
279
- return Array.from(this.actions.values());
228
+ return this.registry.listActions();
280
229
  }
281
230
  /**
282
231
  * 注入或更新跨包运行上下文解析委托。
283
232
  */
284
233
  setPackageContextResolver(resolver) {
285
- this.packageContextResolver = resolver;
234
+ this.packageFactory.setPackageContextResolver(resolver);
286
235
  }
287
236
  /**
288
237
  * 跨包运行时解析与获取(确保跨包执行具备独立的配置、存储、状态与 Action 注册表)。
@@ -292,95 +241,7 @@ export class ActionRunner {
292
241
  * 并发方 await 同一构建任务,构建失败时移除该 Promise 以便后续重试。
293
242
  */
294
243
  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;
244
+ return this.packageFactory.resolveTargetPackageRunner(targetPackageId);
384
245
  }
385
246
  /**
386
247
  * 异步启动 Action 的执行并立即返回 ExecutionHandle 句柄。
@@ -393,15 +254,21 @@ export class ActionRunner {
393
254
  start(actionOrId, input = {}, options = {}) {
394
255
  const runCtx = this.prepareRunContext(actionOrId, input, options);
395
256
  const { runId, targetActionId, targetPackageId } = runCtx;
396
- // 输入参数 JSON 格式与合法性防御校验(拦截 NaN/Infinity/循环引用等非 JSON 类型)
397
- const inputCheck = validateJsonValue(input);
257
+ // 输入参数 JSON 格式与合法性防御校验(拦截 NaN/Infinity/循环引用等非 JSON 类型及非法原型键策略违规)
258
+ const inputCheck = validateActionInputValue(input);
398
259
  if (!inputCheck.valid) {
399
- const error = {
400
- code: INPUT_NOT_JSON,
401
- message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
402
- };
403
- // 安全记录 failed 状态(不可序列化的非法 input 严禁直接写入持久化存储)
404
- this.tryPersistInitialRun(runCtx, "failed", error);
260
+ const error = inputCheck.kind === "json-value"
261
+ ? {
262
+ code: INPUT_NOT_JSON,
263
+ message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
264
+ }
265
+ : {
266
+ code: INPUT_VALIDATION_FAILED,
267
+ message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
268
+ details: [inputCheck.reason],
269
+ };
270
+ // 安全记录 failed 状态(不可序列化或策略违规的非法 input 严禁直接写入持久化存储)
271
+ tryCreateRun(this.storage, buildInitialRunRecord(this.buildRunPersistenceInput(runCtx), "failed", error));
405
272
  return {
406
273
  runId,
407
274
  result: Promise.resolve({ ok: false, runId, error }),
@@ -409,8 +276,8 @@ export class ActionRunner {
409
276
  };
410
277
  }
411
278
  // 始终优先将执行尝试持久化到存储中(确保任意异常与终态都可追溯)
412
- this.persistInitialRun(runCtx, "running");
413
- const finalizer = this.createRunFinalizer(runCtx);
279
+ createRunOrThrow(this.storage, buildInitialRunRecord(this.buildRunPersistenceInput(runCtx), "running"));
280
+ const finalizer = createRunFinalizer(this.storage, runCtx.runId);
414
281
  // 调用嵌套深度限制检测 (Max Call Depth Check)
415
282
  const depthError = this.checkCallDepth(runCtx.callStack, targetActionId, runCtx.options);
416
283
  if (depthError) {
@@ -512,33 +379,24 @@ export class ActionRunner {
512
379
  let action;
513
380
  let targetActionId;
514
381
  let targetPackageId = this.packageId;
515
- if (typeof actionOrId === "object" &&
516
- "run" in actionOrId &&
517
- typeof actionOrId.run === "function") {
382
+ if (isActionDefinitionObject(actionOrId)) {
518
383
  action = actionOrId;
519
384
  const actObj = action;
520
385
  if (actObj.id) {
521
386
  targetActionId = actObj.id;
522
387
  }
523
388
  else {
524
- targetActionId = this.resolveAnonymousActionId(action);
389
+ targetActionId = resolveAnonymousActionId(this.actions, action);
525
390
  }
526
391
  this.actions.set(targetActionId, action);
527
392
  }
528
393
  else {
529
- const parsed = ActionResolver.parseRef(actionOrId);
394
+ const parsed = parseActionRef(actionOrId);
530
395
  targetActionId = parsed.actionId;
531
396
  if (parsed.packageId) {
532
397
  targetPackageId = parsed.packageId;
533
398
  }
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
- }
399
+ action = findLocalAction(this.actions, parsed, this.packageId);
542
400
  }
543
401
  return {
544
402
  runId,
@@ -554,128 +412,27 @@ export class ActionRunner {
554
412
  };
555
413
  }
556
414
  /**
557
- * 为匿名传入的 Action 定义对象解析或分配稳定标识。
558
- */
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)。
415
+ * 依据运行期上下文装配落库模块入参(RunRecord 构造领域契约)。
595
416
  */
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,
417
+ buildRunPersistenceInput(runCtx) {
418
+ const { options, targetPackageId, targetActionId, startedAt, input } = runCtx;
419
+ return {
420
+ runId: runCtx.runId,
615
421
  rootRunId: this.computeRootRunId(runCtx),
616
422
  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,
423
+ ownerId: options.ownerId,
424
+ hostSessionId: options.hostSessionId,
425
+ packageInstanceId: options.packageInstanceId,
426
+ generationId: options.generationId,
427
+ targetPackageId,
428
+ targetActionId,
625
429
  startedAt,
430
+ input,
431
+ runnerPackageId: this.packageId,
432
+ runnerPackageInstanceId: this.packageInstanceId,
433
+ runnerGenerationId: this.generationId,
434
+ runnerHostSessionId: this.hostSessionId,
626
435
  };
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
436
  }
680
437
  /**
681
438
  * 调用嵌套深度限制检测(超限时返回 ACTION_CALL_CYCLE 错误)。
@@ -719,9 +476,11 @@ export class ActionRunner {
719
476
  * 输入参数 JSON Schema 校验(若 action 已就绪,返回校验错误或 undefined)。
720
477
  */
721
478
  checkActionInputSchema(action, targetActionId, input) {
722
- const targetInputSchema = action?.inputSchema ?? this.projectConfig?.actions?.[targetActionId]?.inputSchema;
723
- if (targetInputSchema) {
724
- const val = validateSchema(targetInputSchema, input);
479
+ const targetInputSchema = action?.inputSchema !== undefined
480
+ ? action.inputSchema
481
+ : this.projectConfig?.actions?.[targetActionId]?.inputSchema;
482
+ if (targetInputSchema !== undefined) {
483
+ const val = validateSchemaOnly(targetInputSchema, input);
725
484
  if (!val.valid) {
726
485
  return {
727
486
  code: INPUT_VALIDATION_FAILED,
@@ -892,11 +651,16 @@ export class ActionRunner {
892
651
  const { runCtx, actionOrId, input, options, controller, finalizer, ctx } = args;
893
652
  const { runId, targetActionId } = runCtx;
894
653
  const abortPromise = new Promise((_, reject) => {
654
+ const rejectWithReason = () => reject(controller.signal.reason || new Error("Action execution was cancelled"));
895
655
  if (controller.signal.aborted) {
896
- reject(controller.signal.reason || new Error("Action execution was cancelled"));
656
+ rejectWithReason();
897
657
  }
898
658
  else {
899
- controller.signal.addEventListener("abort", () => reject(controller.signal.reason || new Error("Action execution was cancelled")), { once: true });
659
+ controller.signal.addEventListener("abort", rejectWithReason, { once: true });
660
+ // 成功与失败路径统一注销:finalize 时回收监听句柄,避免信号对象残留引用
661
+ finalizer.raceListenerRemovers.push(() => {
662
+ controller.signal.removeEventListener("abort", rejectWithReason);
663
+ });
900
664
  }
901
665
  });
902
666
  return (async () => {
@@ -927,26 +691,26 @@ export class ActionRunner {
927
691
  finalizer.finalize("failed", undefined, error);
928
692
  return { ok: false, runId, error };
929
693
  }
930
- const inputCheck = validateJsonValue(input);
694
+ const inputCheck = validateActionInputValue(input);
931
695
  if (!inputCheck.valid) {
932
- const error = {
933
- code: INPUT_NOT_JSON,
934
- message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
935
- };
696
+ const error = inputCheck.kind === "json-value"
697
+ ? {
698
+ code: INPUT_NOT_JSON,
699
+ message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
700
+ }
701
+ : {
702
+ code: INPUT_VALIDATION_FAILED,
703
+ message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
704
+ details: [inputCheck.reason],
705
+ };
936
706
  finalizer.finalize("failed", undefined, error);
937
707
  return { ok: false, runId, error };
938
708
  }
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
- }
709
+ // 复用统一校验入口:优先 action 声明 schema,缺失时回退 projectConfig 清单声明
710
+ const schemaError = this.checkActionInputSchema(currentAction, targetActionId, input);
711
+ if (schemaError) {
712
+ finalizer.finalize("failed", undefined, schemaError);
713
+ return { ok: false, runId, error: schemaError };
950
714
  }
951
715
  }
952
716
  const rawOutput = await Promise.race([
@@ -999,9 +763,11 @@ export class ActionRunner {
999
763
  * 输出结果 Schema 校验(返回校验错误或 undefined)。
1000
764
  */
1001
765
  checkActionOutputSchema(action, targetActionId, rawOutput) {
1002
- const targetOutputSchema = action?.outputSchema ?? this.projectConfig?.actions?.[targetActionId]?.outputSchema;
1003
- if (targetOutputSchema) {
1004
- const outVal = validateSchema(targetOutputSchema, rawOutput);
766
+ const targetOutputSchema = action?.outputSchema !== undefined
767
+ ? action.outputSchema
768
+ : this.projectConfig?.actions?.[targetActionId]?.outputSchema;
769
+ if (targetOutputSchema !== undefined) {
770
+ const outVal = validateSchemaOnly(targetOutputSchema, rawOutput);
1005
771
  if (!outVal.valid) {
1006
772
  return {
1007
773
  code: OUTPUT_VALIDATION_FAILED,
@@ -1052,6 +818,16 @@ export class ActionRunner {
1052
818
  error: finalizer.persistError || error,
1053
819
  };
1054
820
  }
821
+ /**
822
+ * 释放 Runner 持有的跨包资源:遍历关闭全部子包 Runner 及其独立创建的存储连接。
823
+ *
824
+ * 幂等且并发安全:重复调用直接复用首次 Promise;子包存储异常不阻断其余释放;
825
+ * 仅关闭由本 Runner 构建子包 Runner 时独立创建的存储,外部注入的存储(如经
826
+ * packageContextResolver 传入的目标包自身存储)生命周期归所有者管理,不在此误关。
827
+ */
828
+ async dispose() {
829
+ return this.packageFactory.dispose();
830
+ }
1055
831
  /**
1056
832
  * 同步等待方式执行指定 Action,直接返回 ExecutionResult 信封结果。
1057
833
  *