@actiondock/core 2.6.0 → 2.8.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 (268) hide show
  1. package/README.md +61 -68
  2. package/dist/catalog/action-catalog.d.ts +56 -0
  3. package/dist/catalog/action-catalog.js +202 -0
  4. package/dist/catalog/discovery.d.ts +60 -0
  5. package/dist/catalog/discovery.js +119 -0
  6. package/dist/catalog/graph.d.ts +190 -0
  7. package/dist/catalog/graph.js +589 -0
  8. package/dist/catalog/index.d.ts +5 -5
  9. package/dist/catalog/index.js +5 -5
  10. package/dist/catalog/resolve-action.d.ts +43 -0
  11. package/dist/catalog/resolve-action.js +119 -0
  12. package/dist/catalog/resolve-playbook.d.ts +26 -0
  13. package/dist/catalog/resolve-playbook.js +114 -0
  14. package/dist/doctor/checks/project.js +27 -8
  15. package/dist/doctor/checks/registry.js +2 -2
  16. package/dist/doctor/checks/runtime.js +34 -1
  17. package/dist/errors.d.ts +122 -10
  18. package/dist/errors.js +123 -10
  19. package/dist/execution/service.d.ts +18 -16
  20. package/dist/execution/service.js +183 -252
  21. package/dist/execution/types.d.ts +25 -69
  22. package/dist/execution/types.js +2 -1
  23. package/dist/filter/intent.d.ts +8 -9
  24. package/dist/filter/intent.js +49 -10
  25. package/dist/graph/index.d.ts +4 -0
  26. package/dist/graph/index.js +4 -0
  27. package/dist/host/host.d.ts +74 -32
  28. package/dist/host/host.js +585 -336
  29. package/dist/host/routing.d.ts +15 -48
  30. package/dist/host/routing.js +90 -153
  31. package/dist/host/types.d.ts +50 -17
  32. package/dist/index.d.ts +21 -22
  33. package/dist/index.js +19 -21
  34. package/dist/input/advice.d.ts +104 -0
  35. package/dist/input/advice.js +716 -0
  36. package/dist/input/describe.d.ts +106 -0
  37. package/dist/input/describe.js +176 -0
  38. package/dist/input/file-input.d.ts +54 -0
  39. package/dist/input/file-input.js +124 -0
  40. package/dist/input/flat-decode.d.ts +11 -0
  41. package/dist/input/flat-decode.js +13 -0
  42. package/dist/input/flat-errors.d.ts +71 -0
  43. package/dist/input/flat-errors.js +87 -0
  44. package/dist/input/flat-materializer.d.ts +29 -0
  45. package/dist/input/flat-materializer.js +177 -0
  46. package/dist/input/flat-parser.d.ts +50 -0
  47. package/dist/input/flat-parser.js +169 -0
  48. package/dist/input/flat-predicates.d.ts +34 -0
  49. package/dist/input/flat-predicates.js +50 -0
  50. package/dist/input/index.d.ts +14 -0
  51. package/dist/input/index.js +14 -0
  52. package/dist/input/input-resolver.d.ts +63 -0
  53. package/dist/input/input-resolver.js +254 -0
  54. package/dist/input/json-depth-scanner.d.ts +17 -0
  55. package/dist/input/json-depth-scanner.js +49 -0
  56. package/dist/input/metadata.d.ts +99 -0
  57. package/dist/input/metadata.js +99 -0
  58. package/dist/input/stdin-input.d.ts +26 -0
  59. package/dist/input/stdin-input.js +125 -0
  60. package/dist/input/utf8.d.ts +19 -0
  61. package/dist/input/utf8.js +36 -0
  62. package/dist/input/validation-mapper.d.ts +27 -0
  63. package/dist/input/validation-mapper.js +144 -0
  64. package/dist/invocation/index.d.ts +2 -0
  65. package/dist/invocation/index.js +2 -0
  66. package/dist/invocation/policy.d.ts +157 -0
  67. package/dist/invocation/policy.js +231 -0
  68. package/dist/invocation/types.d.ts +172 -0
  69. package/dist/invocation/types.js +97 -0
  70. package/dist/ipc/diagnostic.d.ts +0 -12
  71. package/dist/ipc/diagnostic.js +0 -21
  72. package/dist/ipc/host.d.ts +4 -4
  73. package/dist/ipc/host.js +44 -47
  74. package/dist/ipc/index.d.ts +1 -1
  75. package/dist/ipc/index.js +1 -1
  76. package/dist/ipc/service.d.ts +40 -0
  77. package/dist/ipc/service.js +349 -0
  78. package/dist/ipc/types.d.ts +21 -15
  79. package/dist/json/index.d.ts +1 -0
  80. package/dist/json/index.js +1 -0
  81. package/dist/json/value-validator.d.ts +88 -0
  82. package/dist/json/value-validator.js +330 -0
  83. package/dist/node/index.d.ts +1 -0
  84. package/dist/node/index.js +1 -0
  85. package/dist/node/module-loader.d.ts +63 -0
  86. package/dist/node/module-loader.js +112 -0
  87. package/dist/package/index.d.ts +23 -0
  88. package/dist/package/index.js +18 -0
  89. package/dist/{app/app.d.ts → package/runtime.d.ts} +41 -20
  90. package/dist/{app/app.js → package/runtime.js} +140 -95
  91. package/dist/{app → package}/static-index.js +58 -41
  92. package/dist/package/storage-view.d.ts +13 -0
  93. package/dist/package/storage-view.js +22 -0
  94. package/dist/{app → package}/types.d.ts +76 -57
  95. package/dist/package/types.js +22 -0
  96. package/dist/platform/index.d.ts +1 -1
  97. package/dist/platform/index.js +1 -1
  98. package/dist/platform/node.d.ts +48 -0
  99. package/dist/platform/node.js +96 -0
  100. package/dist/platform/types.d.ts +0 -9
  101. package/dist/process/control-arbiter.d.ts +2 -1
  102. package/dist/process/control-arbiter.js +1 -2
  103. package/dist/process/driver.d.ts +9 -8
  104. package/dist/process/driver.js +11 -22
  105. package/dist/process/index.d.ts +2 -0
  106. package/dist/process/index.js +2 -0
  107. package/dist/process/managed-record.d.ts +2 -2
  108. package/dist/process/process-driver.d.ts +109 -0
  109. package/dist/process/process-driver.js +650 -0
  110. package/dist/process/process-executor.d.ts +47 -0
  111. package/dist/process/process-executor.js +296 -0
  112. package/dist/process/process-manager.js +48 -26
  113. package/dist/process/run-executor.js +12 -12
  114. package/dist/profile/client-actions.js +2 -2
  115. package/dist/profile/client-health.js +3 -2
  116. package/dist/profile/client-query.js +5 -6
  117. package/dist/profile/client-transport.d.ts +19 -17
  118. package/dist/profile/client-transport.js +48 -80
  119. package/dist/profile/client.d.ts +2 -2
  120. package/dist/profile/client.js +1 -1
  121. package/dist/profile/index.d.ts +1 -0
  122. package/dist/profile/index.js +1 -0
  123. package/dist/profile/manager.d.ts +5 -0
  124. package/dist/profile/manager.js +41 -6
  125. package/dist/project/closure.js +2 -2
  126. package/dist/project/index.d.ts +8 -1
  127. package/dist/project/index.js +10 -1
  128. package/dist/project/init.js +2 -2
  129. package/dist/project/loader.d.ts +1 -1
  130. package/dist/project/loader.js +26 -29
  131. package/dist/project/transactions.js +5 -10
  132. package/dist/registry/index.d.ts +3 -1
  133. package/dist/registry/index.js +2 -1
  134. package/dist/registry/lock.d.ts +28 -7
  135. package/dist/registry/lock.js +136 -16
  136. package/dist/registry/registry.d.ts +1 -5
  137. package/dist/registry/registry.js +1 -53
  138. package/dist/registry/scan.d.ts +0 -15
  139. package/dist/registry/scan.js +0 -59
  140. package/dist/registry/store.d.ts +42 -0
  141. package/dist/registry/store.js +43 -0
  142. package/dist/registry/types.d.ts +0 -24
  143. package/dist/runtime/action-collection.d.ts +1 -1
  144. package/dist/runtime/action-registry.d.ts +0 -4
  145. package/dist/runtime/action-registry.js +0 -7
  146. package/dist/runtime/context.js +5 -11
  147. package/dist/runtime/events.js +3 -6
  148. package/dist/runtime/identity.d.ts +22 -0
  149. package/dist/runtime/identity.js +10 -0
  150. package/dist/runtime/index.d.ts +1 -2
  151. package/dist/runtime/index.js +1 -2
  152. package/dist/runtime/module-loader.d.ts +2 -34
  153. package/dist/runtime/module-loader.js +1 -16
  154. package/dist/runtime/process.d.ts +6 -24
  155. package/dist/runtime/process.js +1 -118
  156. package/dist/runtime/run-persistence.js +2 -5
  157. package/dist/runtime/runner.d.ts +33 -103
  158. package/dist/runtime/runner.js +142 -404
  159. package/dist/runtime/standalone.d.ts +37 -9
  160. package/dist/runtime/standalone.js +195 -136
  161. package/dist/schema/index.d.ts +1 -0
  162. package/dist/schema/index.js +1 -0
  163. package/dist/schema/validator.d.ts +10 -0
  164. package/dist/schema/validator.js +48 -54
  165. package/dist/server/body.d.ts +16 -8
  166. package/dist/server/body.js +33 -16
  167. package/dist/server/dispatcher.d.ts +6 -10
  168. package/dist/server/dispatcher.js +24 -32
  169. package/dist/server/http-server.d.ts +87 -0
  170. package/dist/server/http-server.js +304 -0
  171. package/dist/server/index.d.ts +11 -0
  172. package/dist/server/index.js +10 -0
  173. package/dist/server/mcp-endpoint.d.ts +61 -0
  174. package/dist/server/mcp-endpoint.js +118 -0
  175. package/dist/server/routes/actions.js +34 -28
  176. package/dist/server/routes/common.d.ts +10 -14
  177. package/dist/server/routes/common.js +32 -56
  178. package/dist/server/routes/config.js +54 -64
  179. package/dist/server/routes/doctor.d.ts +1 -1
  180. package/dist/server/routes/doctor.js +10 -18
  181. package/dist/server/routes/health.d.ts +1 -1
  182. package/dist/server/routes/health.js +2 -3
  183. package/dist/server/routes/info.d.ts +2 -2
  184. package/dist/server/routes/info.js +12 -13
  185. package/dist/server/routes/playbooks.js +16 -29
  186. package/dist/server/routes/runs.js +95 -117
  187. package/dist/server/routes/state.js +63 -62
  188. package/dist/server/server.d.ts +2 -2
  189. package/dist/server/server.js +43 -227
  190. package/dist/server/types.d.ts +8 -19
  191. package/dist/service/factory.d.ts +12 -0
  192. package/dist/service/factory.js +85 -0
  193. package/dist/{target → service}/index.d.ts +1 -1
  194. package/dist/{target → service}/index.js +1 -1
  195. package/dist/service/local.d.ts +27 -0
  196. package/dist/service/local.js +138 -0
  197. package/dist/{target → service}/remote-errors.d.ts +3 -6
  198. package/dist/service/remote-errors.js +38 -0
  199. package/dist/{target → service}/remote-polling.d.ts +2 -2
  200. package/dist/{target → service}/remote-polling.js +8 -8
  201. package/dist/service/remote.d.ts +34 -0
  202. package/dist/service/remote.js +634 -0
  203. package/dist/service/sse-stream.d.ts +24 -0
  204. package/dist/service/sse-stream.js +139 -0
  205. package/dist/service/types.d.ts +272 -0
  206. package/dist/service/types.js +32 -0
  207. package/dist/storage/data-dir-lock.js +10 -5
  208. package/dist/storage/driver.d.ts +2 -1
  209. package/dist/storage/driver.js +3 -52
  210. package/dist/storage/index.d.ts +1 -0
  211. package/dist/storage/index.js +1 -0
  212. package/dist/storage/lock-core.d.ts +2 -6
  213. package/dist/storage/lock-core.js +4 -17
  214. package/dist/storage/params.d.ts +2 -8
  215. package/dist/storage/params.js +2 -8
  216. package/dist/storage/sqlite-driver.d.ts +40 -0
  217. package/dist/storage/sqlite-driver.js +116 -0
  218. package/dist/storage/sqlite.js +21 -6
  219. package/dist/storage/types.d.ts +4 -4
  220. package/dist/storage/types.js +2 -2
  221. package/dist/utils/index.d.ts +53 -0
  222. package/dist/utils/index.js +99 -4
  223. package/dist/utils/process.d.ts +10 -0
  224. package/dist/utils/process.js +21 -0
  225. package/dist/version.d.ts +1 -1
  226. package/dist/version.js +1 -1
  227. package/package.json +41 -3
  228. package/dist/app/index.d.ts +0 -2
  229. package/dist/app/index.js +0 -2
  230. package/dist/app/types.js +0 -1
  231. package/dist/catalog/action-index.d.ts +0 -9
  232. package/dist/catalog/action-index.js +0 -95
  233. package/dist/catalog/action-resolver.d.ts +0 -20
  234. package/dist/catalog/action-resolver.js +0 -95
  235. package/dist/catalog/location-registry.d.ts +0 -15
  236. package/dist/catalog/location-registry.js +0 -138
  237. package/dist/catalog/package-catalog.d.ts +0 -7
  238. package/dist/catalog/package-catalog.js +0 -77
  239. package/dist/catalog/types.d.ts +0 -68
  240. package/dist/catalog/types.js +0 -1
  241. package/dist/export/index.d.ts +0 -1
  242. package/dist/export/index.js +0 -1
  243. package/dist/export/templates.d.ts +0 -71
  244. package/dist/export/templates.js +0 -623
  245. package/dist/ipc/target.d.ts +0 -74
  246. package/dist/ipc/target.js +0 -365
  247. package/dist/platform/default.d.ts +0 -38
  248. package/dist/platform/default.js +0 -78
  249. package/dist/project/resolver.d.ts +0 -116
  250. package/dist/project/resolver.js +0 -376
  251. package/dist/registry/resolve.d.ts +0 -62
  252. package/dist/registry/resolve.js +0 -195
  253. package/dist/runtime/execution-manager.d.ts +0 -48
  254. package/dist/runtime/execution-manager.js +0 -66
  255. package/dist/runtime/package-runner-factory.d.ts +0 -82
  256. package/dist/runtime/package-runner-factory.js +0 -186
  257. package/dist/target/local.d.ts +0 -53
  258. package/dist/target/local.js +0 -349
  259. package/dist/target/remote-errors.js +0 -51
  260. package/dist/target/remote.d.ts +0 -86
  261. package/dist/target/remote.js +0 -818
  262. package/dist/target/target.d.ts +0 -6
  263. package/dist/target/target.js +0 -37
  264. package/dist/target/types.d.ts +0 -261
  265. package/dist/target/types.js +0 -35
  266. /package/dist/{app → package}/static-index.d.ts +0 -0
  267. /package/dist/{target → service}/sse-parser.d.ts +0 -0
  268. /package/dist/{target → service}/sse-parser.js +0 -0
@@ -1,79 +1,47 @@
1
- import { existsSync } from "node:fs";
2
- import { randomUUID } from "node:crypto";
3
- import { ActionResolver } from "../catalog/action-resolver.js";
4
- import { loadActions, loadProjectConfig } from "../project/loader.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 { validateSchema } from "../schema/validator.js";
1
+ import { parseActionRef } from "../catalog/resolve-action.js";
2
+ import { ACTION_FAILED, ACTION_NOT_FOUND, ACTION_TIMEOUT, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_CANCELLED, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, INVOCATION_UNSUPPORTED, ActionDockError, } from "../errors.js";
3
+ import { validateSchemaOnly } from "../schema/validator.js";
4
+ import { validateActionInputValue } from "../json/value-validator.js";
8
5
  import { createActionContext, StderrLogger } from "./context.js";
9
- import { ActionRegistry, findLocalAction, isActionDefinitionObject, parseActionRef, resolveAnonymousActionId, } from "./action-registry.js";
6
+ import { createDefaultProcessOwner } from "../invocation/types.js";
7
+ import { ActionRegistry, findLocalAction, isActionDefinitionObject, resolveAnonymousActionId, } from "./action-registry.js";
10
8
  import { buildInitialRunRecord, createRunFinalizer, createRunOrThrow, tryCreateRun, } from "./run-persistence.js";
11
- import { PackageRunnerFactory } from "./package-runner-factory.js";
12
- // 错误码常量已收敛至 src/errors.ts 单一事实源,此处保留 re-export 以维持既有导入路径兼容。
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";
9
+ import { validateJsonValue } from "../json/value-validator.js";
14
10
  /**
15
- * 校验值是否为合法的 JSON 兼容结构,严禁 NaN、Infinity、循环引用及不可序列化类型。
11
+ * 将输入值校验结果映射为标准运行时错误(单一事实源)。
16
12
  *
17
- * 环路检测采用「当前递归路径栈」语义:对象仅在自身子树遍历期间保持标记,
18
- * 子树遍历完成后立即移除,从而正确放行共享子对象的有向无环结构(如 {a: shared, b: shared})。
13
+ * json-value 分支映射 INPUT_NOT_JSON(非 JSON 类型值),input-policy 分支映射
14
+ * INPUT_VALIDATION_FAILED(策略违规)并携带 details 数组;start 同步路径与
15
+ * raceExecutionAndAbort 延迟解析路径共用,确保错误码与 details 结构一致。
19
16
  */
20
- export function validateJsonValue(val, stack = new WeakSet()) {
21
- if (val === null || typeof val === "boolean" || typeof val === "string") {
22
- return { valid: true };
23
- }
24
- if (typeof val === "number") {
25
- if (!Number.isFinite(val) || Number.isNaN(val)) {
26
- return { valid: false, reason: `Number is non-finite or NaN (${val})` };
27
- }
28
- return { valid: true };
29
- }
30
- if (typeof val === "undefined" || typeof val === "function" || typeof val === "symbol" || typeof val === "bigint") {
31
- return { valid: false, reason: `Unsupported JSON type '${typeof val}'` };
32
- }
33
- if (typeof val === "object") {
34
- if (stack.has(val)) {
35
- return { valid: false, reason: "Circular reference detected in object structure" };
36
- }
37
- stack.add(val);
38
- try {
39
- if (Array.isArray(val)) {
40
- for (const item of val) {
41
- const res = validateJsonValue(item, stack);
42
- if (!res.valid)
43
- return res;
44
- }
45
- return { valid: true };
46
- }
47
- for (const v of Object.values(val)) {
48
- if (v !== undefined) {
49
- const res = validateJsonValue(v, stack);
50
- if (!res.valid)
51
- return res;
52
- }
53
- }
54
- return { valid: true };
55
- }
56
- finally {
57
- // 无论正常返回还是提前返回,均须将当前对象移出路径栈,避免祖先对象被误判为环路
58
- stack.delete(val);
59
- }
17
+ function buildInputValidationError(targetActionId, check) {
18
+ if (check.kind === "json-value") {
19
+ return {
20
+ code: INPUT_NOT_JSON,
21
+ message: `Input validation failed for action '${targetActionId}': ${check.reason}`,
22
+ };
60
23
  }
61
- return { valid: true };
24
+ return {
25
+ code: INPUT_VALIDATION_FAILED,
26
+ message: `Input validation failed for action '${targetActionId}': ${check.reason}`,
27
+ details: [check.reason],
28
+ };
62
29
  }
63
30
  /**
64
31
  * ActionDock 核心执行引擎(ActionRunner)。
65
32
  *
66
33
  * 本类为执行编排壳:负责 Action 执行的全生命周期编排(校验、深度与环路检测、
67
34
  * 超时与取消竞态、子调用 uses 授权与错误分类),并将单一职责域委托至独立模块:
68
- * - Action 注册与检索委托 `ActionRegistry`(action-registry.ts)
35
+ * - Action 注册与检索委托 ActionRegistry(action-registry.ts)
69
36
  * - RunRecord 构造与落库委托 run-persistence.ts
70
- * - 跨包 Runner 构建与缓存委托 `PackageRunnerFactory`(package-runner-factory.ts)
37
+ * - 跨包动作调用委托注入的 ActionInvoker 并重返目标包执行服务
71
38
  *
72
39
  * 入参 (inputSchema) 与出参 (outputSchema) 的 JSON Schema 严格校验、嵌套 Action
73
40
  * 相互调用的环路检测(Cycle Detection)、超时 (Timeout) 与中断信号 (AbortSignal)
74
41
  * 竞态控制与运行记录持久化编排仍由本类承担。
75
42
  */
76
43
  export class ActionRunner {
44
+ identity;
77
45
  packageId;
78
46
  packageInstanceId;
79
47
  generationId;
@@ -85,76 +53,41 @@ export class ActionRunner {
85
53
  registry;
86
54
  clock;
87
55
  process;
88
- platform;
89
- maxCallDepth;
90
- maxSubRuns;
91
- activeSubRuns = 0;
92
- packageFactory;
93
- actionResolver;
94
- getStorageForPackage;
95
56
  customHome;
57
+ actionResolver;
96
58
  hostSessionId;
59
+ actionInvoker;
97
60
  constructor(options) {
98
- this.packageId = options.packageId;
99
- this.packageInstanceId = options.packageInstanceId || options.packageId;
100
- this.generationId = options.generationId || "1";
61
+ if (!options.identity) {
62
+ throw new Error("ActionRunner requires 'identity' PackageIdentity option");
63
+ }
64
+ this.identity = options.identity;
65
+ this.packageId = this.identity.id;
66
+ this.packageInstanceId = this.identity.instanceId;
67
+ this.generationId = this.identity.generation;
101
68
  this.hostSessionId = options.hostSessionId;
102
69
  this.projectRoot = options.projectRoot;
103
70
  this.projectConfig = options.projectConfig;
104
71
  this.configOverrides = options.configOverrides || {};
105
72
  this.registry = new ActionRegistry(options.actions);
106
73
  this.customHome = options.customHome;
107
- this.platform = options.platform;
108
- if (options.platform) {
109
- this.clock = options.platform.clock;
110
- this.process = options.platform.process;
111
- this.storage = options.storage ?? options.platform.storage.createStorage(this.packageId, {
112
- projectRoot: this.projectRoot,
113
- customHome: this.customHome,
114
- // Runner 作为执行宿主组件属于持有者路径,打开时收割遗留孤儿运行
115
- recoverOrphans: true,
116
- });
117
- this.globalStorage = options.globalStorage ?? options.platform.storage.createGlobalStorage({
118
- customHome: this.customHome,
119
- });
120
- }
121
- else {
122
- if (!options.storage) {
123
- throw new Error("ActionRunner requires either 'storage' or 'platform' option");
124
- }
125
- this.storage = options.storage;
126
- this.globalStorage = options.globalStorage;
127
- this.process = options.process;
128
- this.clock = options.clock;
129
- }
130
- this.maxCallDepth = options.maxCallDepth ?? 16;
131
- this.maxSubRuns = options.maxSubRuns ?? 64;
74
+ this.actionInvoker = options.actionInvoker;
132
75
  this.actionResolver = options.actionResolver;
133
- this.getStorageForPackage = options.getStorageForPackage;
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));
76
+ if (!options.storage) {
77
+ throw new Error("ActionRunner requires 'storage' option");
78
+ }
79
+ this.storage = options.storage;
80
+ this.globalStorage = options.globalStorage;
81
+ this.clock = options.clock;
82
+ this.process = options.process;
147
83
  }
148
- /** 本地 Action 注册表底层映射(仅内部与跨包工厂委托使用) */
84
+ /** 本地 Action 注册表底层映射 */
149
85
  get actions() {
150
86
  return this.registry.map;
151
87
  }
152
88
  getStorage() {
153
89
  return this.storage;
154
90
  }
155
- getPackageRunners() {
156
- return this.packageFactory.runners;
157
- }
158
91
  registerAction(idOrAction, actionDef) {
159
92
  this.registry.registerAction(idOrAction, actionDef);
160
93
  }
@@ -164,11 +97,17 @@ export class ActionRunner {
164
97
  getAction(id) {
165
98
  return this.registry.getAction(id);
166
99
  }
100
+ /**
101
+ * 设置子任务动作调用委托器。
102
+ */
103
+ setActionInvoker(invoker) {
104
+ this.actionInvoker = invoker;
105
+ }
167
106
  /**
168
107
  * 动态解析 Action(支持本地注册表、自定义解析器委托与已链接包目录索引检索)。
169
108
  *
170
109
  * @param actionOrRef Action 定义对象、引用或标识符
171
- * @returns 解析判别联合结果(found | not_found | load_failed)
110
+ * @returns 解析判别联合结果(found | not_found)
172
111
  */
173
112
  async resolveAction(actionOrRef) {
174
113
  if (isActionDefinitionObject(actionOrRef)) {
@@ -184,24 +123,26 @@ export class ActionRunner {
184
123
  }
185
124
  const targetActionId = parsed.actionId;
186
125
  const targetPackageId = parsed.packageId;
126
+ // 铁律:ActionRunner 仅限执行当前包局部 Action,任何跨包请求直接拒绝,禁止跨包解析和执行
127
+ if (targetPackageId && targetPackageId !== this.packageId) {
128
+ return {
129
+ status: "not_found",
130
+ reason: `Cross-package action '${targetPackageId}/${targetActionId}' cannot be resolved by ActionRunner of package '${this.packageId}'`,
131
+ };
132
+ }
187
133
  // 本地 actions 映射表优先检索
188
134
  const localMatched = findLocalAction(this.actions, parsed, this.packageId);
189
135
  if (localMatched) {
190
136
  return { status: "found", action: localMatched };
191
137
  }
192
- // 外部注入的自定义 actionResolver 调度
138
+ // 外部注入的局部 actionResolver 调度
193
139
  if (this.actionResolver) {
194
140
  try {
195
- const customResolved = await this.actionResolver(ref, this.packageId);
141
+ const customResolved = await this.actionResolver(targetActionId);
196
142
  if (customResolved) {
197
- if (targetPackageId && targetPackageId !== this.packageId) {
198
- this.actions.set(`${targetPackageId}/${targetActionId}`, customResolved);
199
- }
200
- else {
201
- this.actions.set(targetActionId, customResolved);
202
- if (this.packageId) {
203
- this.actions.set(`${this.packageId}/${targetActionId}`, customResolved);
204
- }
143
+ this.actions.set(targetActionId, customResolved);
144
+ if (this.packageId) {
145
+ this.actions.set(`${this.packageId}/${targetActionId}`, customResolved);
205
146
  }
206
147
  return { status: "found", action: customResolved };
207
148
  }
@@ -210,61 +151,10 @@ export class ActionRunner {
210
151
  return { status: "not_found", reason: err.message };
211
152
  }
212
153
  }
213
- // 基于全局链接注册表与目录索引的动态寻址与按需加载
214
- const identifier = targetPackageId
215
- ? `${targetPackageId}/${targetActionId}`
216
- : targetActionId;
217
- let resolved;
218
- try {
219
- resolved = await resolveActionProject(identifier, this.projectRoot, this.customHome);
220
- }
221
- catch (err) {
222
- return { status: "not_found", reason: err.message };
223
- }
224
- if (!resolved || !existsSync(resolved.projectRoot)) {
225
- return {
226
- status: "not_found",
227
- reason: `Project root not found for action '${identifier}'`,
228
- };
229
- }
230
- let config;
231
- try {
232
- config = loadProjectConfig(resolved.projectRoot);
233
- }
234
- catch (err) {
235
- return {
236
- status: "load_failed",
237
- error: err instanceof Error ? err : new Error(String(err)),
238
- packageId: resolved.packageId,
239
- projectRoot: resolved.projectRoot,
240
- };
241
- }
242
- let actionsMap;
243
- try {
244
- actionsMap = await loadActions(resolved.projectRoot, config.actionsDir, {
245
- loader: this.platform?.modules,
246
- });
247
- }
248
- catch (err) {
249
- return {
250
- status: "load_failed",
251
- error: err instanceof Error ? err : new Error(String(err)),
252
- packageId: resolved.packageId,
253
- projectRoot: resolved.projectRoot,
254
- };
255
- }
256
- const matched = actionsMap.get(resolved.actionId);
257
- if (matched) {
258
- this.actions.set(`${resolved.packageId}/${resolved.actionId}`, matched);
259
- // 仅当目标包就是当前项目时才注册短标识符,避免跨包动态载入污染全局短标识符
260
- if (!targetPackageId || resolved.packageId === this.packageId) {
261
- this.actions.set(resolved.actionId, matched);
262
- }
263
- return { status: "found", action: matched };
264
- }
154
+ // 铁律 4:Runner 不找 Package:只能执行自己所属 Package 的 Action
265
155
  return {
266
156
  status: "not_found",
267
- reason: `Action '${resolved.actionId}' not found in package '${resolved.packageId}' (${resolved.projectRoot})`,
157
+ reason: `Action '${targetActionId}' not found in package '${this.packageId}'`,
268
158
  };
269
159
  }
270
160
  /**
@@ -273,22 +163,6 @@ export class ActionRunner {
273
163
  listActions() {
274
164
  return this.registry.listActions();
275
165
  }
276
- /**
277
- * 注入或更新跨包运行上下文解析委托。
278
- */
279
- setPackageContextResolver(resolver) {
280
- this.packageFactory.setPackageContextResolver(resolver);
281
- }
282
- /**
283
- * 跨包运行时解析与获取(确保跨包执行具备独立的配置、存储、状态与 Action 注册表)。
284
- *
285
- * 缓存 miss 后存在长异步窗口(加载配置、扫描 Action、打开存储),并发调用会重复构建
286
- * Runner、重复打开 SQLite 连接且子任务限流计数分裂;故以 in-flight Promise 去重,
287
- * 并发方 await 同一构建任务,构建失败时移除该 Promise 以便后续重试。
288
- */
289
- async resolveTargetPackageRunner(targetPackageId) {
290
- return this.packageFactory.resolveTargetPackageRunner(targetPackageId);
291
- }
292
166
  /**
293
167
  * 异步启动 Action 的执行并立即返回 ExecutionHandle 句柄。
294
168
  *
@@ -300,14 +174,11 @@ export class ActionRunner {
300
174
  start(actionOrId, input = {}, options = {}) {
301
175
  const runCtx = this.prepareRunContext(actionOrId, input, options);
302
176
  const { runId, targetActionId, targetPackageId } = runCtx;
303
- // 输入参数 JSON 格式与合法性防御校验(拦截 NaN/Infinity/循环引用等非 JSON 类型)
304
- const inputCheck = validateJsonValue(input);
177
+ // 输入参数 JSON 格式与合法性防御校验(拦截 NaN/Infinity/循环引用等非 JSON 类型及非法原型键策略违规)
178
+ const inputCheck = validateActionInputValue(input);
305
179
  if (!inputCheck.valid) {
306
- const error = {
307
- code: INPUT_NOT_JSON,
308
- message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
309
- };
310
- // 安全记录 failed 状态(不可序列化的非法 input 严禁直接写入持久化存储)
180
+ const error = buildInputValidationError(targetActionId, inputCheck);
181
+ // 安全记录 failed 状态(不可序列化或策略违规的非法 input 严禁直接写入持久化存储)
311
182
  tryCreateRun(this.storage, buildInitialRunRecord(this.buildRunPersistenceInput(runCtx), "failed", error));
312
183
  return {
313
184
  runId,
@@ -318,27 +189,15 @@ export class ActionRunner {
318
189
  // 始终优先将执行尝试持久化到存储中(确保任意异常与终态都可追溯)
319
190
  createRunOrThrow(this.storage, buildInitialRunRecord(this.buildRunPersistenceInput(runCtx), "running"));
320
191
  const finalizer = createRunFinalizer(this.storage, runCtx.runId);
321
- // 调用嵌套深度限制检测 (Max Call Depth Check)
322
- const depthError = this.checkCallDepth(runCtx.callStack, targetActionId, runCtx.options);
323
- if (depthError) {
324
- finalizer.finalize("failed", undefined, depthError);
325
- return {
326
- runId,
327
- result: Promise.resolve({ ok: false, runId, error: depthError }),
328
- cancel: () => false,
329
- };
330
- }
331
- // 环路死锁检测 (Cycle Detection)
332
- const cycle = this.computeCallKey(runCtx.callStack, targetActionId, targetPackageId);
333
- if (cycle.error) {
334
- finalizer.finalize("failed", undefined, cycle.error);
335
- return {
336
- runId,
337
- result: Promise.resolve({ ok: false, runId, error: cycle.error }),
338
- cancel: () => false,
339
- };
192
+ // 记录调用栈快照(供子任务追溯,若已处于调用栈顶则不重复追加)
193
+ const callKey = targetPackageId ? `${targetPackageId}/${targetActionId}` : targetActionId;
194
+ const lastStackItem = runCtx.callStack[runCtx.callStack.length - 1];
195
+ const isAlreadyAtTop = lastStackItem === callKey ||
196
+ (targetActionId && lastStackItem === targetActionId) ||
197
+ (targetPackageId && lastStackItem === `${targetPackageId}/${targetActionId}`);
198
+ if (!isAlreadyAtTop) {
199
+ runCtx.callStack.push(callKey);
340
200
  }
341
- runCtx.callStack.push(cycle.callKey);
342
201
  // 输入参数 JSON Schema 校验(若 action 已就绪)
343
202
  const schemaError = this.checkActionInputSchema(runCtx.action, targetActionId, input);
344
203
  if (schemaError) {
@@ -408,9 +267,9 @@ export class ActionRunner {
408
267
  * 预解析执行目标并装配运行期共享上下文对象(runId、目标标识、时钟源与调用栈快照)。
409
268
  */
410
269
  prepareRunContext(actionOrId, input, options) {
411
- const runId = options.runId || randomUUID();
412
- const effectiveClock = options.platform?.clock ?? this.clock;
413
- const effectiveProcess = options.process || options.platform?.process || this.process;
270
+ const runId = options.runId || crypto.randomUUID();
271
+ const effectiveClock = options.clock ?? this.clock;
272
+ const effectiveProcess = options.process || this.process;
414
273
  const startedAt = effectiveClock?.now().toISOString() ||
415
274
  (typeof this.storage.clock?.now === "function"
416
275
  ? this.storage.clock.now().toISOString()
@@ -474,51 +333,15 @@ export class ActionRunner {
474
333
  runnerHostSessionId: this.hostSessionId,
475
334
  };
476
335
  }
477
- /**
478
- * 调用嵌套深度限制检测(超限时返回 ACTION_CALL_CYCLE 错误)。
479
- */
480
- checkCallDepth(callStack, targetActionId, options) {
481
- // 执行级 options.maxCallDepth 可覆盖构造级默认值,保留原覆盖契约
482
- const maxDepth = options.maxCallDepth ?? this.maxCallDepth;
483
- if (callStack.length >= maxDepth) {
484
- return {
485
- code: ACTION_CALL_CYCLE,
486
- message: `Maximum call depth of ${maxDepth} exceeded: ${callStack.join(" -> ")} -> ${targetActionId}`,
487
- details: { alias: ACTION_MAX_DEPTH_EXCEEDED, reason: "depth_exceeded", maxDepth, callStack: [...callStack] },
488
- };
489
- }
490
- return undefined;
491
- }
492
- /**
493
- * 计算调用键并执行环路死锁检测(命中时返回携带错误的调用键结果)。
494
- */
495
- computeCallKey(callStack, targetActionId, targetPackageId) {
496
- const isExternal = Boolean(targetPackageId && targetPackageId !== this.packageId);
497
- const callKey = isExternal
498
- ? `${targetPackageId}/${targetActionId}`
499
- : targetActionId;
500
- const hasCycle = isExternal
501
- ? callStack.includes(callKey)
502
- : (callStack.includes(callKey) || (this.packageId ? callStack.includes(`${this.packageId}/${targetActionId}`) : false));
503
- if (hasCycle) {
504
- return {
505
- callKey,
506
- error: {
507
- code: ACTION_CALL_CYCLE,
508
- message: `Cycle detected in action invocation: ${callStack.join(" -> ")} -> ${callKey}`,
509
- details: { alias: ACTION_CYCLE_DETECTED, reason: "cycle_detected", callStack: [...callStack], target: callKey },
510
- },
511
- };
512
- }
513
- return { callKey };
514
- }
515
336
  /**
516
337
  * 输入参数 JSON Schema 校验(若 action 已就绪,返回校验错误或 undefined)。
517
338
  */
518
339
  checkActionInputSchema(action, targetActionId, input) {
519
- const targetInputSchema = action?.inputSchema ?? this.projectConfig?.actions?.[targetActionId]?.inputSchema;
520
- if (targetInputSchema) {
521
- const val = validateSchema(targetInputSchema, input);
340
+ const targetInputSchema = action?.inputSchema !== undefined
341
+ ? action.inputSchema
342
+ : this.projectConfig?.actions?.[targetActionId]?.inputSchema;
343
+ if (targetInputSchema !== undefined) {
344
+ const val = validateSchemaOnly(targetInputSchema, input);
522
345
  if (!val.valid) {
523
346
  return {
524
347
  code: INPUT_VALIDATION_FAILED,
@@ -535,12 +358,13 @@ export class ActionRunner {
535
358
  buildActionContext(args) {
536
359
  const { runCtx, controller, rootRunId } = args;
537
360
  const { options, targetPackageId, targetActionId, effectiveProcess } = runCtx;
538
- const effectiveOwner = options.owner || {
539
- tenantId: options.tenantId || "default",
540
- principalId: options.principalId || options.ownerId || "default",
541
- packageInstanceId: options.packageInstanceId || (this.packageId === targetPackageId ? this.packageInstanceId : targetPackageId),
542
- generationId: options.generationId || (this.packageId === targetPackageId ? this.generationId : "1"),
543
- };
361
+ const effectiveOwner = options.owner ||
362
+ createDefaultProcessOwner({
363
+ tenantId: options.tenantId,
364
+ principalId: options.principalId || options.ownerId,
365
+ packageInstanceId: options.packageInstanceId || this.packageInstanceId,
366
+ generationId: options.generationId || this.generationId,
367
+ });
544
368
  return createActionContext({
545
369
  actionId: targetActionId,
546
370
  storage: this.storage,
@@ -566,121 +390,51 @@ export class ActionRunner {
566
390
  });
567
391
  }
568
392
  /**
569
- * 子 Action 调用委托:并发上限守卫、uses 跨包声明校验与子执行派发。
393
+ * 子 Action 调用委托:
394
+ * 必须委托 Host 动作调用器(走 Host 执行主链与 InvocationPolicy 鉴权);
395
+ * 缺少 actionInvoker 时直接抛出 INVOCATION_UNSUPPORTED,严禁跨包或同包无约束执行逃逸。
570
396
  */
571
397
  async invokeChildAction(args) {
572
398
  const { runCtx, controller, rootRunId, childAction, childInput, parentRunId } = args;
573
- const { options, targetActionId, effectiveProcess, callStack } = runCtx;
574
- if (this.activeSubRuns >= this.maxSubRuns) {
575
- const err = new Error(`Maximum concurrent sub-runs (${this.maxSubRuns}) reached`);
576
- err.code = ACTION_SUBRUN_LIMIT;
577
- err.details = { alias: MAX_SUBRUNS_REACHED, limit: this.maxSubRuns };
578
- throw err;
579
- }
580
- let childPackageId = this.packageId;
581
- const parsed = ActionResolver.parseRef(childAction);
582
- if (parsed.packageId) {
583
- childPackageId = parsed.packageId;
584
- }
585
- const childActionId = parsed.actionId;
586
- const effectiveParentOwner = options.owner || {
587
- tenantId: options.tenantId || "default",
588
- principalId: options.principalId || options.ownerId || "default",
589
- packageInstanceId: options.packageInstanceId || this.packageInstanceId || this.packageId,
590
- generationId: options.generationId || this.generationId || "1",
591
- };
592
- const isSamePackage = !childPackageId || childPackageId === this.packageId;
593
- this.activeSubRuns++;
594
- try {
595
- const runnerToUse = await this.resolveChildRunner(childPackageId, childActionId);
596
- if (runnerToUse !== this) {
597
- this.assertDeclaredUses(runCtx.action, targetActionId, childPackageId, childActionId);
598
- }
599
- const childPackageInstanceId = isSamePackage
600
- ? effectiveParentOwner.packageInstanceId
601
- : (runnerToUse !== this ? runnerToUse.packageInstanceId : childPackageId);
602
- const childGenerationId = isSamePackage
603
- ? effectiveParentOwner.generationId
604
- : (runnerToUse !== this ? runnerToUse.generationId : "1");
605
- const childOwner = {
606
- tenantId: effectiveParentOwner.tenantId,
607
- principalId: effectiveParentOwner.principalId,
608
- packageInstanceId: childPackageInstanceId,
609
- generationId: childGenerationId,
610
- };
611
- const childResult = await runnerToUse.execute(childAction, childInput, {
612
- rootRunId,
613
- parentRunId,
614
- callStack,
615
- signal: controller.signal,
616
- process: effectiveProcess,
617
- platform: options.platform || this.platform,
618
- progress: options.progress,
619
- logger: options.logger,
620
- configOverrides: options.configOverrides,
621
- maxCallDepth: options.maxCallDepth ?? this.maxCallDepth,
622
- tenantId: childOwner.tenantId,
623
- principalId: childOwner.principalId,
624
- ownerId: childOwner.principalId,
625
- packageInstanceId: childOwner.packageInstanceId,
626
- generationId: childOwner.generationId,
627
- owner: childOwner,
628
- hostSessionId: options.hostSessionId ?? this.hostSessionId,
399
+ const { options, effectiveProcess, callStack } = runCtx;
400
+ const invoker = options.actionInvoker || this.actionInvoker;
401
+ if (!invoker) {
402
+ throw new ActionDockError(INVOCATION_UNSUPPORTED, "Nested action invocation requires a Host ActionInvoker");
403
+ }
404
+ const effectiveParentOwner = options.owner ||
405
+ createDefaultProcessOwner({
406
+ tenantId: options.tenantId,
407
+ principalId: options.principalId || options.ownerId,
408
+ packageInstanceId: this.packageInstanceId,
409
+ generationId: this.generationId,
629
410
  });
630
- if (!childResult.ok) {
631
- const err = new Error(childResult.error.message);
632
- err.code = childResult.error.code;
633
- err.details = childResult.error.details;
634
- throw err;
635
- }
636
- return childResult.data;
637
- }
638
- finally {
639
- this.activeSubRuns--;
640
- }
641
- }
642
- /**
643
- * 校验跨包子调用是否已在 uses 声明中授权(未声明时抛出 UNDECLARED_ACTION_DEPENDENCY)。
644
- */
645
- assertDeclaredUses(action, targetActionId, childPackageId, childActionId) {
646
- if (!childPackageId || childPackageId === this.packageId) {
647
- return;
648
- }
649
- const actionConfig = this.projectConfig?.actions?.[targetActionId];
650
- const rawUses = actionConfig?.uses ?? action?.uses;
651
- const declaredUses = Array.isArray(rawUses) ? rawUses : [];
652
- const targetRef = `${childPackageId}/${childActionId}`;
653
- const isAllowed = declaredUses.some((u) => u === targetRef || u === `${childPackageId}/*` || u === childPackageId);
654
- if (!isAllowed) {
655
- const err = new Error(`Undeclared cross-package dependency: Action '${this.packageId}/${targetActionId}' does not declare '${targetRef}' in 'uses'`);
656
- err.code = UNDECLARED_ACTION_DEPENDENCY;
657
- err.details = {
658
- caller: `${this.packageId}/${targetActionId}`,
659
- target: targetRef,
660
- declaredUses: rawUses,
661
- };
662
- throw err;
663
- }
664
- }
665
- /**
666
- * 解析子调用应使用的 Runner(本包或已解析的目标包 Runner)。
667
- */
668
- async resolveChildRunner(childPackageId, childActionId) {
669
- if (childPackageId && childPackageId !== this.packageId) {
670
- const targetRunner = await this.resolveTargetPackageRunner(childPackageId);
671
- if (targetRunner) {
672
- return targetRunner;
673
- }
674
- if (this.actions.has(`${childPackageId}/${childActionId}`)) {
675
- return this;
676
- }
677
- if (!this.actionResolver) {
678
- const err = new Error(`Package '${childPackageId}' could not be resolved`);
679
- err.code = PACKAGE_NOT_FOUND;
680
- throw err;
681
- }
682
- }
683
- return this;
411
+ const childRunId = crypto.randomUUID();
412
+ const invocationContext = {
413
+ runId: childRunId,
414
+ rootRunId,
415
+ parentRunId: parentRunId || runCtx.runId,
416
+ caller: {
417
+ packageId: this.packageId,
418
+ actionId: runCtx.targetActionId,
419
+ runId: runCtx.runId,
420
+ declaredUses: runCtx.action?.uses ||
421
+ this.projectConfig?.actions?.[runCtx.targetActionId]?.uses,
422
+ },
423
+ callStack: [...callStack],
424
+ package: this.identity,
425
+ signal: controller.signal,
426
+ timeoutMs: options.timeoutMs,
427
+ maxCallDepth: options.maxCallDepth,
428
+ config: options.configOverrides,
429
+ tenantId: effectiveParentOwner.tenantId,
430
+ principalId: effectiveParentOwner.principalId,
431
+ hostSessionId: options.hostSessionId || this.hostSessionId,
432
+ logger: options.logger,
433
+ progress: options.progress,
434
+ process: effectiveProcess,
435
+ owner: effectiveParentOwner,
436
+ };
437
+ return await invoker(childAction, childInput, invocationContext);
684
438
  }
685
439
  /**
686
440
  * 执行 Action 业务逻辑并与取消/超时信号进行竞态,返回执行结果信封 Promise。
@@ -710,16 +464,6 @@ export class ActionRunner {
710
464
  currentAction = resolution.action;
711
465
  runCtx.action = currentAction;
712
466
  }
713
- else if (resolution.status === "load_failed") {
714
- const cause = resolution.error;
715
- const error = describeActionLoadFailure(cause, {
716
- actionId: targetActionId,
717
- packageId: resolution.packageId,
718
- projectRoot: resolution.projectRoot,
719
- });
720
- finalizer.finalize("failed", undefined, error);
721
- return { ok: false, runId, error };
722
- }
723
467
  else {
724
468
  const error = {
725
469
  code: ACTION_NOT_FOUND,
@@ -729,12 +473,9 @@ export class ActionRunner {
729
473
  finalizer.finalize("failed", undefined, error);
730
474
  return { ok: false, runId, error };
731
475
  }
732
- const inputCheck = validateJsonValue(input);
476
+ const inputCheck = validateActionInputValue(input);
733
477
  if (!inputCheck.valid) {
734
- const error = {
735
- code: INPUT_NOT_JSON,
736
- message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
737
- };
478
+ const error = buildInputValidationError(targetActionId, inputCheck);
738
479
  finalizer.finalize("failed", undefined, error);
739
480
  return { ok: false, runId, error };
740
481
  }
@@ -795,9 +536,11 @@ export class ActionRunner {
795
536
  * 输出结果 Schema 校验(返回校验错误或 undefined)。
796
537
  */
797
538
  checkActionOutputSchema(action, targetActionId, rawOutput) {
798
- const targetOutputSchema = action?.outputSchema ?? this.projectConfig?.actions?.[targetActionId]?.outputSchema;
799
- if (targetOutputSchema) {
800
- const outVal = validateSchema(targetOutputSchema, rawOutput);
539
+ const targetOutputSchema = action?.outputSchema !== undefined
540
+ ? action.outputSchema
541
+ : this.projectConfig?.actions?.[targetActionId]?.outputSchema;
542
+ if (targetOutputSchema !== undefined) {
543
+ const outVal = validateSchemaOnly(targetOutputSchema, rawOutput);
801
544
  if (!outVal.valid) {
802
545
  return {
803
546
  code: OUTPUT_VALIDATION_FAILED,
@@ -849,15 +592,10 @@ export class ActionRunner {
849
592
  };
850
593
  }
851
594
  /**
852
- * 释放 Runner 持有的跨包资源:遍历关闭全部子包 Runner 及其独立创建的存储连接。
853
- *
854
- * 幂等且并发安全:重复调用直接复用首次 Promise;子包存储异常不阻断其余释放;
855
- * 仅关闭由本 Runner 构建子包 Runner 时独立创建的存储,外部注入的存储(如经
856
- * packageContextResolver 传入的目标包自身存储)生命周期归所有者管理,不在此误关。
595
+ * 释放 Runner 持有的资源。
596
+ * 铁律 4:Runner 仅执行自己所属 Package 的 Action,不管理外部包生命周期。
857
597
  */
858
- async dispose() {
859
- return this.packageFactory.dispose();
860
- }
598
+ async dispose() { }
861
599
  /**
862
600
  * 同步等待方式执行指定 Action,直接返回 ExecutionResult 信封结果。
863
601
  *