@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
package/dist/host/host.js CHANGED
@@ -1,15 +1,22 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { existsSync } from "node:fs";
3
- import { DefaultActionDockApp } from "../app/app.js";
3
+ import { DefaultPackageRuntime } from "../package/runtime.js";
4
+ import { applyActionSummaryFilters } from "../package/types.js";
5
+ import { createRootInvocationContext, } from "../invocation/types.js";
4
6
  import { findProjectRoot, loadProjectConfig } from "../project/loader.js";
5
- import { ActionPackageResolver } from "../project/resolver.js";
6
7
  import { hasPendingTransactions, isProjectLockHeld, recoverPendingTransactions } from "../project/transactions.js";
7
8
  import { listLinkedPackages } from "../registry/registry.js";
8
9
  import { InMemoryEventSink } from "../runtime/events.js";
9
- import { ACTION_CALL_CYCLE, ACTION_MAX_DEPTH_EXCEEDED, ACTION_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_SUBRUN_LIMIT, INVALID_ACTION_REF, MAX_SUBRUNS_REACHED, PACKAGE_NOT_FOUND, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, UNDECLARED_ACTION_DEPENDENCY, } from "../errors.js";
10
+ import { ActionDockError, ACTION_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_SUBRUN_LIMIT, INVALID_ACTION_REF, PACKAGE_NOT_FOUND, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, UNDECLARED_ACTION_DEPENDENCY, } from "../errors.js";
11
+ import { InvocationPolicy } from "../invocation/policy.js";
10
12
  import { DataDirLock } from "../storage/data-dir-lock.js";
11
- import { ambiguousActionMessage, buildRuntimeError, collectPackageInfos, describeActionAcrossApps, describeVisiblePlaybook, isRootCallVisible, listVisiblePlaybooks, packageNotFoundMessage, parseRefLoose, resolveProjectRoot, resolveShortRef, } from "./routing.js";
12
- function isActionDockApp(item) {
13
+ import { DefaultActionCatalog, DefaultPackageGraph, PackageGraphBuilder, resolveAction, } from "../catalog/index.js";
14
+ import { buildRuntimeError, collectPackageInfos, describeActionAcrossRuntimes, describeVisiblePlaybook, isRootCallVisible, listVisiblePlaybooks, packageNotFoundMessage, parseRefLoose, resolveProjectRoot, } from "./routing.js";
15
+ import { ServiceError } from "../service/types.js";
16
+ import { CAPABILITY_UNAVAILABLE } from "../errors.js";
17
+ import { createGlobalStorage, isSecretConfigKey } from "../storage/index.js";
18
+ import { filterByIntent } from "../filter/index.js";
19
+ function isPackageRuntime(item) {
13
20
  return (typeof item === "object" &&
14
21
  item !== null &&
15
22
  "info" in item &&
@@ -17,18 +24,12 @@ function isActionDockApp(item) {
17
24
  "runAction" in item &&
18
25
  typeof item.runAction === "function");
19
26
  }
20
- /**
21
- * ActionDock 统一多包宿主容器默认实现。
22
- * 负责聚合与调度多个 ActionDockApp 实例,提供跨包引用路由、依赖声明校验与资源配额控制。
23
- */
24
27
  export class DefaultActionDockHost {
25
28
  hostSessionId;
26
29
  options;
27
- apps = new Map();
28
- internallyCreatedApps = new Set();
29
- activeSubRunsPerRoot = new Map();
30
+ runtimes = new Map();
31
+ policy;
30
32
  hostPublicPackageIds = new Set();
31
- resolver;
32
33
  maxCallDepth;
33
34
  maxSubRuns;
34
35
  eventSink;
@@ -36,89 +37,167 @@ export class DefaultActionDockHost {
36
37
  dataDirLock;
37
38
  failedLinkedPackages = new Map();
38
39
  failedAutoLoad;
39
- /** 透传给内部创建 App 的存储收割开关(Host 默认持有者身份,显式可关) */
40
+ /** 透传给内部创建 Runtime 的存储收割开关(Host 默认持有者身份,显式可关) */
40
41
  recoverOrphans;
41
- constructor(options = {}) {
42
+ graph;
43
+ catalog;
44
+ globalStorage;
45
+ constructor(options = {}, internalOptions) {
42
46
  this.hostSessionId = randomUUID();
43
47
  this.options = options;
44
48
  this.maxCallDepth = options.maxCallDepth ?? 16;
45
49
  this.maxSubRuns = options.maxSubRuns ?? 64;
50
+ this.policy = new InvocationPolicy({
51
+ maxCallDepth: this.maxCallDepth,
52
+ maxSubRuns: this.maxSubRuns,
53
+ });
46
54
  this.eventSink = options.eventSink ?? options.platform?.eventSink ?? new InMemoryEventSink();
47
55
  // Host 默认声明数据目录持有者身份;查询旁观方(CLI 查询命令)显式置 false
48
56
  this.recoverOrphans = options.recoverOrphans !== false;
57
+ this.graph = new DefaultPackageGraph(new Map());
58
+ this.catalog = new DefaultActionCatalog(this.graph);
49
59
  // 当指定非内存 dataDir 时获取排他目录锁,防止并发冲突
50
60
  if (options.dataDir && !options.inMemory) {
51
61
  this.dataDirLock = DataDirLock.acquire(options.dataDir, {
52
62
  hostSessionId: this.hostSessionId,
53
63
  });
54
64
  }
65
+ if (internalOptions?.deferInit) {
66
+ return;
67
+ }
55
68
  try {
56
- // 阶段一:注册显式传入的 packages 列表
57
- this.registerExplicitPackages(options);
58
- // 阶段二:自动加载当前工程(若发现工程根目录且未显式禁用)
59
- if (options.autoLoadCurrentProject !== false) {
60
- this.loadCurrentProject(options);
61
- }
62
- // 阶段三:扫描已软链接的外部包并注册至 Host
63
- if (options.scanLinkedPackages) {
64
- this.registerLinkedPackages(options);
65
- }
69
+ this.initializeSync();
66
70
  }
67
71
  catch (err) {
68
- try {
69
- this.dataDirLock?.release();
70
- }
71
- catch {
72
- // 忽略排他锁释放异常
72
+ this.rollbackSync();
73
+ throw err;
74
+ }
75
+ }
76
+ initializeSync() {
77
+ // 阶段一:注册显式传入的 packages 列表
78
+ this.registerExplicitPackages(this.options);
79
+ // 阶段二:自动加载当前工程(若发现工程根目录且未显式禁用)
80
+ if (this.options.autoLoadCurrentProject !== false) {
81
+ this.loadCurrentProject(this.options);
82
+ }
83
+ // 阶段三:扫描已软链接的外部包并注册至 Host
84
+ if (this.options.scanLinkedPackages) {
85
+ this.registerLinkedPackages(this.options);
86
+ }
87
+ this.rebuildGraphAndCatalog();
88
+ }
89
+ async initializeAsync() {
90
+ // 阶段一:注册显式传入的 packages 列表
91
+ this.registerExplicitPackages(this.options);
92
+ // 阶段二:自动加载当前工程(若发现工程根目录且未显式禁用)
93
+ if (this.options.autoLoadCurrentProject !== false) {
94
+ const root = resolveProjectRoot(this.options, () => findProjectRoot());
95
+ if (root) {
96
+ if (isProjectLockHeld(root)) {
97
+ throw new ActionDockError(PROJECT_BUSY, "PROJECT_BUSY: Project directory is locked by another active process holding project.lock");
98
+ }
99
+ // 依据事务日志恢复未完成提交的悬空事务(锁持有者存活时严禁判定为崩溃事务并禁止自动恢复)
100
+ if (hasPendingTransactions(root)) {
101
+ await recoverPendingTransactions(root, { frozenInstall: true });
102
+ }
73
103
  }
74
- this.dataDirLock = undefined;
75
- // 仅安全关闭宿主内部创建的子 app,外部传入的 app 保持调用方生命周期与所有权
76
- for (const app of this.internallyCreatedApps) {
104
+ this.loadCurrentProject(this.options);
105
+ }
106
+ // 阶段三:扫描已软链接的外部包并注册至 Host
107
+ if (this.options.scanLinkedPackages) {
108
+ this.registerLinkedPackages(this.options);
109
+ }
110
+ this.rebuildGraphAndCatalog();
111
+ }
112
+ /**
113
+ * 安全回滚初始化失败时所占用的资源。
114
+ *
115
+ * 同步语境(构造函数路径)下对 runtime.close() 采用 fire-and-forget:
116
+ * 仅附加吞没异常的 catch 处理器,不 await,避免构造链路引入异步;
117
+ * 异步语境下逐个 await 全部 runtime.close() 后再依次释放全局存储与目录锁。
118
+ */
119
+ async rollbackResources(sync) {
120
+ if (sync) {
121
+ // 安全关闭宿主管理的所有 Runtime 实例(同步语境 fire-and-forget)
122
+ for (const runtime of this.runtimes.values()) {
77
123
  try {
78
- const closePromise = app.close();
124
+ const closePromise = runtime.close();
79
125
  if (closePromise && typeof closePromise.catch === "function") {
80
126
  closePromise.catch(() => { });
81
127
  }
82
128
  }
83
129
  catch {
84
- // 忽略 app 关闭异常
130
+ // 忽略 runtime 关闭异常
85
131
  }
86
132
  }
87
- this.internallyCreatedApps.clear();
88
- this.apps.clear();
89
- throw err;
90
133
  }
134
+ else {
135
+ const allRuntimes = Array.from(this.runtimes.values());
136
+ await Promise.all(allRuntimes.map(async (runtime) => {
137
+ try {
138
+ await runtime.close();
139
+ }
140
+ catch {
141
+ // 忽略 Runtime 关闭异常
142
+ }
143
+ }));
144
+ }
145
+ this.runtimes.clear();
146
+ try {
147
+ this.globalStorage?.close();
148
+ }
149
+ catch {
150
+ // 忽略全局存储关闭异常
151
+ }
152
+ this.globalStorage = undefined;
153
+ try {
154
+ this.dataDirLock?.release();
155
+ }
156
+ catch {
157
+ // 忽略排他锁释放异常
158
+ }
159
+ this.dataDirLock = undefined;
160
+ this.isClosed = true;
91
161
  }
92
162
  /**
93
- * 阶段函数:注册显式传入的 packages 列表(现成 App 实例或 AppOptions 配置)。
163
+ * 异步安全回滚初始化失败时所占用的资源。
164
+ */
165
+ async rollbackInitialization() {
166
+ await this.rollbackResources(false);
167
+ }
168
+ rollbackSync() {
169
+ void this.rollbackResources(true);
170
+ }
171
+ /**
172
+ * 阶段函数:注册显式传入的 packages 列表(PackageRuntimeOptions 配置或包目录物理路径)。
94
173
  */
95
174
  registerExplicitPackages(options) {
96
175
  if (!options.packages || !Array.isArray(options.packages)) {
97
176
  return;
98
177
  }
99
178
  for (const item of options.packages) {
100
- if (isActionDockApp(item)) {
101
- this.registerAppInternal(item, true);
179
+ if (isPackageRuntime(item)) {
180
+ this.registerRuntimeInternal(item, true);
102
181
  }
103
182
  else {
104
- const app = new DefaultActionDockApp({
105
- ...item,
183
+ const packageOptions = typeof item === "string" ? { packageRoot: item } : item;
184
+ const runtime = new DefaultPackageRuntime({
185
+ ...packageOptions,
106
186
  hostSessionId: this.hostSessionId,
107
- platform: item.platform ?? options.platform,
108
- inMemory: item.inMemory ?? options.inMemory,
109
- customHome: item.customHome ?? options.customHome,
110
- dataDir: item.dataDir ?? options.dataDir,
111
- clock: item.clock ?? options.clock,
112
- process: item.process ?? options.process,
113
- logger: item.logger ?? options.logger,
114
- eventSink: item.eventSink ?? this.eventSink,
115
- maxCallDepth: item.maxCallDepth ?? this.maxCallDepth,
116
- maxSubRuns: item.maxSubRuns ?? this.maxSubRuns,
187
+ globalStorage: packageOptions.globalStorage ?? this.getGlobalStorage(),
188
+ platform: packageOptions.platform ?? options.platform,
189
+ inMemory: packageOptions.inMemory ?? options.inMemory,
190
+ customHome: packageOptions.customHome ?? options.customHome,
191
+ dataDir: packageOptions.dataDir ?? options.dataDir,
192
+ clock: packageOptions.clock ?? options.clock,
193
+ process: packageOptions.process ?? options.process,
194
+ logger: packageOptions.logger ?? options.logger,
195
+ eventSink: packageOptions.eventSink ?? this.eventSink,
196
+ maxCallDepth: packageOptions.maxCallDepth ?? this.maxCallDepth,
197
+ maxSubRuns: packageOptions.maxSubRuns ?? this.maxSubRuns,
117
198
  recoverOrphans: this.recoverOrphans,
118
- packageContextResolver: this.resolvePackageContext.bind(this),
119
199
  });
120
- this.internallyCreatedApps.add(app);
121
- this.registerAppInternal(app, true);
200
+ this.registerRuntimeInternal(runtime, true);
122
201
  }
123
202
  }
124
203
  }
@@ -149,25 +228,28 @@ export class DefaultActionDockHost {
149
228
  }
150
229
  try {
151
230
  const config = loadProjectConfig(root);
152
- this.hostPublicPackageIds.add(config.id);
153
- this.resolver = new ActionPackageResolver({
231
+ const builder = new PackageGraphBuilder({
154
232
  projectRoot: root,
155
233
  manifest: config,
156
234
  allowDevLinks: options.scanLinkedPackages,
157
235
  customHome: options.customHome,
158
236
  });
159
- const graph = this.resolver.resolveSync();
160
- this.hostPublicPackageIds.add(graph.rootPackageId);
237
+ const graph = builder.buildSync();
238
+ if (graph.rootPackageId) {
239
+ this.hostPublicPackageIds.add(graph.rootPackageId);
240
+ }
161
241
  for (const depId of graph.directDependencyIds) {
162
242
  this.hostPublicPackageIds.add(depId);
163
243
  }
164
244
  for (const pkg of graph.packages.values()) {
165
- if (!this.apps.has(pkg.packageId)) {
245
+ if (!this.runtimes.has(pkg.packageId)) {
166
246
  const isDirectOrRoot = this.hostPublicPackageIds.has(pkg.packageId);
167
- const app = new DefaultActionDockApp({
168
- packageRoot: pkg.packageRoot,
247
+ const runtime = new DefaultPackageRuntime({
248
+ packageRoot: pkg.root,
169
249
  projectConfig: pkg.manifest,
250
+ identity: pkg.identity,
170
251
  hostSessionId: this.hostSessionId,
252
+ globalStorage: this.getGlobalStorage(),
171
253
  platform: options.platform,
172
254
  inMemory: options.inMemory,
173
255
  customHome: options.customHome,
@@ -179,10 +261,8 @@ export class DefaultActionDockHost {
179
261
  maxCallDepth: this.maxCallDepth,
180
262
  maxSubRuns: this.maxSubRuns,
181
263
  recoverOrphans: this.recoverOrphans,
182
- packageContextResolver: this.resolvePackageContext.bind(this),
183
264
  });
184
- this.internallyCreatedApps.add(app);
185
- this.registerAppInternal(app, isDirectOrRoot);
265
+ this.registerRuntimeInternal(runtime, isDirectOrRoot);
186
266
  }
187
267
  }
188
268
  }
@@ -208,7 +288,7 @@ export class DefaultActionDockHost {
208
288
  */
209
289
  registerLinkedPackages(options) {
210
290
  for (const linked of listLinkedPackages(options.customHome)) {
211
- if (this.apps.has(linked.id)) {
291
+ if (this.runtimes.has(linked.id)) {
212
292
  continue;
213
293
  }
214
294
  if (!existsSync(linked.path)) {
@@ -220,10 +300,11 @@ export class DefaultActionDockHost {
220
300
  try {
221
301
  const config = loadProjectConfig(linked.path);
222
302
  this.hostPublicPackageIds.add(linked.id);
223
- const app = new DefaultActionDockApp({
303
+ const runtime = new DefaultPackageRuntime({
224
304
  packageRoot: linked.path,
225
305
  projectConfig: config,
226
306
  hostSessionId: this.hostSessionId,
307
+ globalStorage: this.getGlobalStorage(),
227
308
  platform: options.platform,
228
309
  inMemory: options.inMemory,
229
310
  customHome: options.customHome,
@@ -235,10 +316,8 @@ export class DefaultActionDockHost {
235
316
  maxCallDepth: this.maxCallDepth,
236
317
  maxSubRuns: this.maxSubRuns,
237
318
  recoverOrphans: this.recoverOrphans,
238
- packageContextResolver: this.resolvePackageContext.bind(this),
239
319
  });
240
- this.internallyCreatedApps.add(app);
241
- this.registerAppInternal(app, true);
320
+ this.registerRuntimeInternal(runtime, true);
242
321
  }
243
322
  catch (err) {
244
323
  const errDetail = err?.message || String(err);
@@ -247,119 +326,225 @@ export class DefaultActionDockHost {
247
326
  }
248
327
  }
249
328
  }
250
- resolvePackageContext(packageId) {
251
- const targetApp = this.getApp(packageId);
252
- if (!targetApp)
253
- return undefined;
254
- return {
255
- projectRoot: targetApp.packageRoot,
256
- projectConfig: targetApp.projectConfig,
257
- storage: targetApp.storage,
258
- actions: targetApp.actionsMap,
259
- packageInstanceId: targetApp.packageInstanceId,
260
- generationId: targetApp.generationId,
261
- };
262
- }
263
- bindApp(app) {
264
- const runner = app.executionService?.runner;
265
- if (runner && typeof runner.setPackageContextResolver === "function") {
266
- runner.setPackageContextResolver(this.resolvePackageContext.bind(this));
329
+ bindRuntime(runtime) {
330
+ const invoker = this.createActionInvoker(runtime);
331
+ if ("setActionInvoker" in runtime && typeof runtime.setActionInvoker === "function") {
332
+ runtime.setActionInvoker(invoker);
267
333
  }
268
334
  }
269
- /** 路由可见性上下文(公开包集合与依赖解析器) */
335
+ /**
336
+ * 构造应用专属动作调用委托器。
337
+ * 铁律 2:跨包调用改走 ActionInvoker 回到 Host 执行主链,经由唯一的 InvocationPolicy 授权后调用目标 Package 的 ExecutionService。
338
+ */
339
+ createActionInvoker(callerRuntime) {
340
+ return async (childAction, childInput, context) => {
341
+ const resolved = resolveAction(childAction, {
342
+ caller: callerRuntime.packageId,
343
+ graph: this.graph,
344
+ catalog: this.catalog,
345
+ });
346
+ const targetPackageId = resolved.package.id;
347
+ const targetActionId = resolved.ref.actionId;
348
+ const isSamePackage = targetPackageId === callerRuntime.packageId;
349
+ const targetRuntime = isSamePackage ? callerRuntime : this.getRuntime(targetPackageId);
350
+ if (!targetRuntime) {
351
+ throw new ActionDockError(PACKAGE_NOT_FOUND, packageNotFoundMessage(targetPackageId, this.failedLinkedPackages));
352
+ }
353
+ // 跨包 uses 依赖声明校验(统一委托 InvocationPolicy 单一事实源)
354
+ if (!isSamePackage) {
355
+ let declaredUses = context.caller?.declaredUses;
356
+ if (!declaredUses) {
357
+ try {
358
+ const callerSpec = await callerRuntime.describeAction(context.caller?.actionId || "");
359
+ declaredUses = callerSpec.uses;
360
+ }
361
+ catch {
362
+ // 忽略规范提取异常
363
+ }
364
+ }
365
+ const authErr = this.policy.checkUsesAuthorization({
366
+ packageId: callerRuntime.packageId,
367
+ actionId: context.caller?.actionId || "",
368
+ declaredUses,
369
+ }, { packageId: targetPackageId, actionId: targetActionId }, this.graph);
370
+ if (authErr) {
371
+ throw new ActionDockError(authErr.code, authErr.message, authErr.details);
372
+ }
373
+ }
374
+ // 调用嵌套深度限制校验
375
+ const depthErr = this.policy.checkCallDepth(context.callStack, targetActionId, context.maxCallDepth);
376
+ if (depthErr) {
377
+ throw new ActionDockError(depthErr.code, depthErr.message, depthErr.details);
378
+ }
379
+ // 调用链环路死锁检测
380
+ const cycle = this.policy.checkCycle(context.callStack, targetActionId, targetPackageId, callerRuntime.packageId);
381
+ if (cycle.error) {
382
+ throw new ActionDockError(cycle.error.code, cycle.error.message, cycle.error.details);
383
+ }
384
+ // 针对根运行的并发子任务配额校验与申请
385
+ const rootRunId = context.rootRunId;
386
+ const quotaErr = this.policy.checkSubRunQuota(rootRunId);
387
+ if (quotaErr) {
388
+ throw new ActionDockError(quotaErr.code, quotaErr.message, quotaErr.details);
389
+ }
390
+ if (!this.policy.acquireSubRun(rootRunId)) {
391
+ throw new ActionDockError(ACTION_SUBRUN_LIMIT, `Maximum concurrent sub-runs (${this.policy.maxSubRuns}) reached`, { limit: this.policy.maxSubRuns });
392
+ }
393
+ try {
394
+ const nextCallStack = [...context.callStack, cycle.callKey];
395
+ const targetOwner = context.owner
396
+ ? {
397
+ tenantId: context.owner.tenantId,
398
+ principalId: context.owner.principalId,
399
+ packageInstanceId: isSamePackage
400
+ ? (context.owner.packageInstanceId || callerRuntime.identity.instanceId)
401
+ : targetRuntime.identity.instanceId,
402
+ generationId: isSamePackage
403
+ ? (context.owner.generationId || callerRuntime.identity.generation)
404
+ : targetRuntime.identity.generation,
405
+ }
406
+ : undefined;
407
+ const subInvocationContext = {
408
+ runId: context.runId,
409
+ rootRunId: context.rootRunId,
410
+ parentRunId: context.parentRunId,
411
+ caller: context.caller,
412
+ callStack: nextCallStack,
413
+ package: targetRuntime.identity,
414
+ signal: context.signal,
415
+ timeoutMs: context.timeoutMs,
416
+ config: context.config,
417
+ requestId: context.requestId,
418
+ tenantId: context.tenantId,
419
+ principalId: context.principalId,
420
+ hostSessionId: context.hostSessionId || this.hostSessionId,
421
+ maxCallDepth: context.maxCallDepth ?? this.maxCallDepth,
422
+ logger: context.logger,
423
+ progress: context.progress,
424
+ process: context.process,
425
+ owner: targetOwner,
426
+ };
427
+ const ticket = await targetRuntime.startInvocation(targetActionId, childInput, subInvocationContext);
428
+ if (!ticket.result) {
429
+ throw new Error(`Execution ticket for run '${ticket.runId}' has no result Promise`);
430
+ }
431
+ const result = await ticket.result;
432
+ if (!result.ok) {
433
+ throw new ActionDockError(result.error.code, result.error.message, result.error.details);
434
+ }
435
+ return result.data;
436
+ }
437
+ finally {
438
+ this.policy.releaseSubRun(rootRunId);
439
+ }
440
+ };
441
+ }
442
+ /** 路由可见性上下文(公开包集合与包依赖图) */
270
443
  visibility() {
271
444
  return {
272
445
  hostPublicPackageIds: this.hostPublicPackageIds,
273
- resolver: this.resolver,
446
+ graph: this.graph,
274
447
  };
275
448
  }
276
- getApp(packageId) {
277
- return this.apps.get(packageId);
449
+ rebuildGraphAndCatalog() {
450
+ const discovered = Array.from(this.runtimes.values()).map((runtime) => ({
451
+ id: runtime.packageId,
452
+ root: runtime.packageRoot || "",
453
+ manifest: runtime.projectConfig,
454
+ identity: runtime.identity,
455
+ isCurrentProject: this.hostPublicPackageIds.has(runtime.packageId),
456
+ }));
457
+ const rootRuntime = Array.from(this.runtimes.values()).find((runtime) => this.hostPublicPackageIds.has(runtime.packageId));
458
+ const builder = new PackageGraphBuilder({
459
+ packages: discovered,
460
+ root: rootRuntime?.packageRoot || rootRuntime?.packageId,
461
+ });
462
+ this.graph = builder.buildSync();
463
+ this.catalog = new DefaultActionCatalog(this.graph, (pkgId) => {
464
+ const runtime = this.getRuntime(pkgId);
465
+ if (!runtime)
466
+ return undefined;
467
+ return new Map(runtime.actionsMap);
468
+ });
469
+ this.policy.setVisibilityContext(this.visibility());
278
470
  }
279
- listApps() {
280
- return Array.from(this.apps.values());
471
+ getRuntime(packageId) {
472
+ return this.runtimes.get(packageId);
281
473
  }
282
- /**
283
- * 获取自动加载工程的失败诊断信息。
284
- * 仅自动探测场景会记录此诊断;显式传入 projectRoot 的加载失败会直接抛出,不产生此诊断。
285
- */
286
- getAutoLoadFailure() {
287
- return this.failedAutoLoad;
474
+ listRuntimes() {
475
+ return Array.from(this.runtimes.values());
288
476
  }
289
- registerAppInternal(app, isPublic) {
290
- if (this.apps.has(app.packageId)) {
291
- const existing = this.apps.get(app.packageId);
292
- if (existing === app) {
477
+ registerRuntimeInternal(runtime, isPublic) {
478
+ if (this.runtimes.has(runtime.packageId)) {
479
+ const existing = this.runtimes.get(runtime.packageId);
480
+ if (existing === runtime) {
293
481
  return;
294
482
  }
295
- throw new Error(`Package ID conflict: package '${app.packageId}' is already registered in host`);
483
+ throw new Error(`Package ID conflict: package '${runtime.packageId}' is already registered in host`);
296
484
  }
297
- this.apps.set(app.packageId, app);
485
+ this.runtimes.set(runtime.packageId, runtime);
298
486
  if (isPublic) {
299
- this.hostPublicPackageIds.add(app.packageId);
487
+ this.hostPublicPackageIds.add(runtime.packageId);
300
488
  }
301
- this.bindApp(app);
489
+ this.bindRuntime(runtime);
490
+ this.rebuildGraphAndCatalog();
302
491
  // 接管与恢复:仅持有者身份的 Host 自动将死亡会话或遗留非终态运行收敛为 interrupted;
303
492
  // 旁观查询 Host(CLI state/runs/config 命令)跳过本步骤,不动其他进程的在途记录
304
493
  if (this.recoverOrphans) {
305
- const st = app.storage;
306
- if (st && typeof st.recoverDeadSessionRuns === "function") {
494
+ if (typeof runtime.recoverDeadSessionRuns === "function") {
307
495
  try {
308
- st.recoverDeadSessionRuns(this.hostSessionId);
496
+ runtime.recoverDeadSessionRuns(this.hostSessionId);
309
497
  }
310
498
  catch (err) {
311
499
  // 单包恢复失败不阻断整体接管流程,但必须可观测
312
- console.warn(`[actiondock] recoverDeadSessionRuns failed for package '${app.packageId}': ${err instanceof Error ? err.message : String(err)}`);
500
+ console.warn(`[actiondock] recoverDeadSessionRuns failed for package '${runtime.packageId}': ${err instanceof Error ? err.message : String(err)}`);
313
501
  }
314
502
  }
315
503
  }
316
504
  }
317
- registerApp(app) {
318
- this.registerAppInternal(app, true);
505
+ registerRuntime(runtime) {
506
+ this.registerRuntimeInternal(runtime, true);
319
507
  }
320
508
  async info() {
321
- return collectPackageInfos(this.listApps());
509
+ return collectPackageInfos(this.listRuntimes());
322
510
  }
323
511
  async listActions(options) {
324
- let results = [];
325
- const apps = this.listApps();
326
- for (const app of apps) {
327
- const appSummaries = await app.listActions();
328
- for (const item of appSummaries) {
329
- if (!isRootCallVisible(app.packageId, item.id, this.hostPublicPackageIds, this.resolver)) {
512
+ // 外层完成可见性筛选与跨包限定名改写后,统一委托共享过滤函数完成检索
513
+ const results = [];
514
+ const runtimes = this.listRuntimes();
515
+ for (const runtime of runtimes) {
516
+ const runtimeSummaries = await runtime.listActions();
517
+ for (const item of runtimeSummaries) {
518
+ if (!isRootCallVisible(runtime.packageId, item.id, this.hostPublicPackageIds, this.graph)) {
330
519
  continue;
331
520
  }
332
- const qualifiedId = apps.length > 1 && !item.id.includes("/")
333
- ? `${app.packageId}/${item.id}`
521
+ const qualifiedId = runtimes.length > 1 && !item.id.includes("/")
522
+ ? `${runtime.packageId}/${item.id}`
334
523
  : item.id;
335
524
  results.push({
336
525
  ...item,
337
526
  id: qualifiedId,
338
- packageId: app.packageId,
527
+ packageId: runtime.packageId,
339
528
  });
340
529
  }
341
530
  }
342
- if (options?.tags && options.tags.length > 0) {
343
- results = results.filter((s) => options.tags.every((t) => s.tags?.includes(t)));
344
- }
345
- if (options?.query) {
346
- const q = options.query.toLowerCase();
347
- results = results.filter((s) => s.id.toLowerCase().includes(q) ||
348
- (s.description && s.description.toLowerCase().includes(q)));
349
- }
350
- if (options?.prefix) {
351
- results = results.filter((s) => s.id.startsWith(options.prefix));
352
- }
353
- return results;
531
+ return applyActionSummaryFilters(results, options);
354
532
  }
355
533
  async describeAction(ref) {
356
- return describeActionAcrossApps(ref, this.listApps(), this.visibility(), this.failedLinkedPackages);
534
+ return describeActionAcrossRuntimes(ref, this.listRuntimes(), this.visibility(), this.failedLinkedPackages, this.catalog, this.graph);
357
535
  }
358
- async listPlaybooks() {
359
- return listVisiblePlaybooks(this.listApps(), this.visibility());
536
+ async listPlaybooks(options) {
537
+ let pbs = await listVisiblePlaybooks(this.listRuntimes(), this.visibility());
538
+ if (options?.package) {
539
+ pbs = pbs.filter((p) => p.packageId === options.package);
540
+ }
541
+ if (options?.intent) {
542
+ pbs = filterByIntent(pbs, options.intent, [(p) => p.id, (p) => p.description || "", (p) => p.packageId || "", (p) => (p.actions || []).join(" ")], false);
543
+ }
544
+ return pbs;
360
545
  }
361
546
  async describePlaybook(id) {
362
- return describeVisiblePlaybook(this.listApps(), id, this.visibility());
547
+ return describeVisiblePlaybook(this.listRuntimes(), id, this.visibility());
363
548
  }
364
549
  async runAction(ref, input, options = {}) {
365
550
  const ticket = await this.startAction(ref, input, options);
@@ -372,185 +557,136 @@ export class DefaultActionDockHost {
372
557
  if (this.isClosed) {
373
558
  throw new Error("ActionDockHost is closed: new tasks rejected");
374
559
  }
375
- // - 解析目标包与 Action 动作标识
376
- const parsed = parseRefLoose(ref);
377
- let targetApp;
378
- let targetActionId = parsed.actionId;
379
- let targetPackageId = parsed.packageId;
380
- if (targetPackageId) {
381
- targetApp = this.getApp(targetPackageId);
382
- if (!targetApp) {
383
- const runId = randomUUID();
384
- const error = buildRuntimeError(PACKAGE_NOT_FOUND, packageNotFoundMessage(targetPackageId, this.failedLinkedPackages));
385
- return {
386
- runId,
387
- status: "failed",
388
- result: Promise.resolve({ ok: false, runId, error }),
389
- };
390
- }
560
+ // - 统一基于纯领域 resolveAction 解析目标包与 Action 动作标识
561
+ let resolved;
562
+ try {
563
+ resolved = resolveAction(ref, {
564
+ graph: this.graph,
565
+ catalog: this.catalog,
566
+ });
391
567
  }
392
- else {
393
- const resolution = await resolveShortRef(this.listApps(), targetActionId, this.visibility());
394
- if (resolution.kind === "unique") {
395
- targetApp = resolution.app;
396
- targetPackageId = targetApp.packageId;
397
- }
398
- else if (resolution.kind === "ambiguous") {
399
- const runId = randomUUID();
400
- const error = buildRuntimeError(INVALID_ACTION_REF, ambiguousActionMessage(targetActionId, resolution.candidates), { alias: "AMBIGUOUS_ACTION_REF", candidates: resolution.candidates });
401
- return {
402
- runId,
403
- status: "failed",
404
- result: Promise.resolve({ ok: false, runId, error }),
405
- };
568
+ catch (err) {
569
+ const runId = randomUUID();
570
+ let code = err.code || ACTION_NOT_FOUND;
571
+ let message = err.message || String(err);
572
+ if (code === PACKAGE_NOT_FOUND) {
573
+ code = PACKAGE_NOT_FOUND;
574
+ const parsed = parseRefLoose(ref);
575
+ if (parsed.packageId) {
576
+ message = packageNotFoundMessage(parsed.packageId, this.failedLinkedPackages);
577
+ }
406
578
  }
407
- else {
408
- const runId = randomUUID();
409
- const error = buildRuntimeError(ACTION_NOT_FOUND, `Action '${targetActionId}' not found in any registered package`);
579
+ else if (code === INVALID_ACTION_REF && err.details?.alias === "AMBIGUOUS_ACTION_REF") {
410
580
  return {
411
581
  runId,
412
582
  status: "failed",
413
- result: Promise.resolve({ ok: false, runId, error }),
583
+ result: Promise.resolve({
584
+ ok: false,
585
+ runId,
586
+ error: buildRuntimeError(INVALID_ACTION_REF, message, err.details),
587
+ }),
414
588
  };
415
589
  }
416
- }
417
- // 根调用可见性鉴权
418
- const parentRunId = options.parentRunId;
419
- if (!parentRunId && targetPackageId) {
420
- if (!isRootCallVisible(targetPackageId, targetActionId, this.hostPublicPackageIds, this.resolver)) {
421
- const runId = randomUUID();
422
- const error = buildRuntimeError(UNDECLARED_ACTION_DEPENDENCY, `Root call to action '${targetPackageId}/${targetActionId}' is not allowed: package '${targetPackageId}' is not declared as a direct dependency in actiondock.json and is not delegated by a visible playbook`, { target: `${targetPackageId}/${targetActionId}` });
423
- return {
590
+ return {
591
+ runId,
592
+ status: "failed",
593
+ result: Promise.resolve({
594
+ ok: false,
424
595
  runId,
425
- status: "failed",
426
- result: Promise.resolve({ ok: false, runId, error }),
427
- };
428
- }
429
- }
430
- // - 父子任务血缘关系、调用配额与跨包 uses 声明依赖校验
431
- let effectiveRootRunId = options.rootRunId;
432
- if (parentRunId) {
433
- const parentRun = await this.getRun(parentRunId);
434
- if (parentRun) {
435
- effectiveRootRunId = effectiveRootRunId || parentRun.rootRunId || parentRunId;
436
- // 跨包 uses 依赖声明校验
437
- const callerPackageId = parentRun.packageId;
438
- const callerActionId = parentRun.actionId;
439
- if (callerPackageId && callerPackageId !== targetPackageId) {
440
- const callerApp = this.getApp(callerPackageId);
441
- if (callerApp) {
442
- try {
443
- const callerSpec = await callerApp.describeAction(callerActionId);
444
- const usesList = callerSpec.uses || [];
445
- const targetRef = `${targetPackageId}/${targetActionId}`;
446
- const isAllowed = this.resolver
447
- ? this.resolver.canCascadeCall(callerPackageId, callerActionId, targetPackageId, targetActionId)
448
- : usesList.some((u) => u === targetRef || u === `${targetPackageId}/*` || u === targetPackageId);
449
- if (!isAllowed) {
450
- const runId = randomUUID();
451
- const error = buildRuntimeError(UNDECLARED_ACTION_DEPENDENCY, `Action '${callerPackageId}/${callerActionId}' does not declare dependency on '${targetRef}' in 'uses'`, {
452
- caller: `${callerPackageId}/${callerActionId}`,
453
- target: targetRef,
454
- declaredUses: usesList,
455
- });
456
- return {
457
- runId,
458
- status: "failed",
459
- result: Promise.resolve({ ok: false, runId, error }),
460
- };
461
- }
462
- }
463
- catch {
464
- // 忽略规范提取异常,交由执行服务执行
465
- }
466
- }
467
- }
468
- // 调用嵌套深度限制校验
469
- let depth = 1;
470
- let cur = parentRun;
471
- while (cur && cur.parentRunId) {
472
- depth++;
473
- if (depth > this.maxCallDepth)
474
- break;
475
- cur = await this.getRun(cur.parentRunId);
476
- }
477
- if (depth >= this.maxCallDepth) {
478
- const runId = randomUUID();
479
- const error = buildRuntimeError(ACTION_CALL_CYCLE, `Maximum call depth of ${this.maxCallDepth} exceeded`, { alias: ACTION_MAX_DEPTH_EXCEEDED, reason: "depth_exceeded", maxDepth: this.maxCallDepth });
480
- return {
481
- runId,
482
- status: "failed",
483
- result: Promise.resolve({ ok: false, runId, error }),
484
- };
485
- }
486
- // 针对根运行的并发子任务数限制校验
487
- if (effectiveRootRunId) {
488
- const currentSubRuns = this.activeSubRunsPerRoot.get(effectiveRootRunId) || 0;
489
- if (currentSubRuns >= this.maxSubRuns) {
490
- const runId = randomUUID();
491
- const error = buildRuntimeError(ACTION_SUBRUN_LIMIT, `Maximum concurrent sub-runs (${this.maxSubRuns}) reached for root run '${effectiveRootRunId}'`, { alias: MAX_SUBRUNS_REACHED, limit: this.maxSubRuns });
492
- return {
493
- runId,
494
- status: "failed",
495
- result: Promise.resolve({ ok: false, runId, error }),
496
- };
497
- }
498
- }
499
- }
500
- }
501
- // - 构造子运行参数并调度至目标 App 执行
502
- const execOptions = {
503
- ...options,
504
- rootRunId: effectiveRootRunId,
505
- parentRunId,
506
- hostSessionId: this.hostSessionId,
507
- maxCallDepth: options.maxCallDepth ?? this.maxCallDepth,
508
- };
509
- if (parentRunId && effectiveRootRunId) {
510
- this.activeSubRunsPerRoot.set(effectiveRootRunId, (this.activeSubRunsPerRoot.get(effectiveRootRunId) || 0) + 1);
511
- }
512
- let ticket;
596
+ error: buildRuntimeError(code, message),
597
+ }),
598
+ };
599
+ }
600
+ const targetPackageId = resolved.package.id;
601
+ const targetActionId = resolved.ref.actionId;
602
+ const targetRuntime = this.getRuntime(targetPackageId);
603
+ if (!targetRuntime) {
604
+ const runId = randomUUID();
605
+ const error = buildRuntimeError(PACKAGE_NOT_FOUND, packageNotFoundMessage(targetPackageId, this.failedLinkedPackages));
606
+ return {
607
+ runId,
608
+ status: "failed",
609
+ result: Promise.resolve({ ok: false, runId, error }),
610
+ };
611
+ }
612
+ // 根调用可见性鉴权(统一委托 InvocationPolicy 单一事实源断言)
513
613
  try {
514
- ticket = await targetApp.startAction(targetActionId, input, execOptions);
614
+ this.policy.assertRootVisibility(resolved, this.visibility());
515
615
  }
516
616
  catch (err) {
517
- // 启动失败(并发上限、仓储不可用、幂等冲突等)时必须回滚配额计数,避免泄漏后误拒后续合法子任务
518
- if (parentRunId && effectiveRootRunId) {
519
- const cnt = this.activeSubRunsPerRoot.get(effectiveRootRunId) || 1;
520
- if (cnt <= 1) {
521
- this.activeSubRunsPerRoot.delete(effectiveRootRunId);
522
- }
523
- else {
524
- this.activeSubRunsPerRoot.set(effectiveRootRunId, cnt - 1);
525
- }
526
- }
527
- throw err;
528
- }
529
- if (parentRunId && effectiveRootRunId && ticket.result) {
530
- const rootId = effectiveRootRunId;
531
- ticket.result = ticket.result.finally(() => {
532
- const cnt = this.activeSubRunsPerRoot.get(rootId) || 1;
533
- if (cnt <= 1) {
534
- this.activeSubRunsPerRoot.delete(rootId);
535
- }
536
- else {
537
- this.activeSubRunsPerRoot.set(rootId, cnt - 1);
538
- }
539
- });
540
- }
541
- return ticket;
617
+ const runId = randomUUID();
618
+ return {
619
+ runId,
620
+ status: "failed",
621
+ result: Promise.resolve({
622
+ ok: false,
623
+ runId,
624
+ error: buildRuntimeError(err.code || UNDECLARED_ACTION_DEPENDENCY, err.message, err.details),
625
+ }),
626
+ };
627
+ }
628
+ // 在边界安全组装受信任的根调用上下文(纯 Root Call,无父级血缘)
629
+ const rootKey = `${targetPackageId}/${targetActionId}`;
630
+ const rootContext = createRootInvocationContext({
631
+ targetPackage: targetRuntime.identity,
632
+ callStack: [rootKey],
633
+ signal: options.signal,
634
+ timeoutMs: options.timeoutMs,
635
+ config: options.config,
636
+ requestId: options.requestId,
637
+ hostSessionId: this.hostSessionId,
638
+ maxCallDepth: this.maxCallDepth,
639
+ process: this.options.process,
640
+ });
641
+ // 调度目标包执行服务
642
+ return targetRuntime.startInvocation(targetActionId, input, rootContext);
542
643
  }
543
644
  async getRun(runId) {
544
- for (const app of this.listApps()) {
545
- const record = await app.getRun(runId);
645
+ for (const runtime of this.listRuntimes()) {
646
+ const record = await runtime.getRun(runId);
546
647
  if (record)
547
648
  return record;
548
649
  }
549
650
  return undefined;
550
651
  }
652
+ async listRuns(query) {
653
+ const runtimes = query?.packageId
654
+ ? [this.getRuntime(query.packageId)].filter(Boolean)
655
+ : this.listRuntimes();
656
+ const records = [];
657
+ for (const runtime of runtimes) {
658
+ const recs = await runtime.listRuns(query);
659
+ for (const r of recs) {
660
+ records.push({
661
+ ...r,
662
+ packageId: r.packageId || runtime.packageId,
663
+ });
664
+ }
665
+ }
666
+ records.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
667
+ let result = records;
668
+ if (query?.intent) {
669
+ result = filterByIntent(result, query.intent, [(r) => r.id, (r) => r.actionId, (r) => r.status, (r) => r.packageId], false);
670
+ }
671
+ if (query?.limit && result.length > query.limit) {
672
+ result.length = query.limit;
673
+ }
674
+ return result;
675
+ }
676
+ async clearRuns(options) {
677
+ const runtimes = options?.packageId
678
+ ? [this.getRuntime(options.packageId)].filter(Boolean)
679
+ : this.listRuntimes();
680
+ let total = 0;
681
+ for (const runtime of runtimes) {
682
+ const res = await runtime.clearRuns(options);
683
+ total += res;
684
+ }
685
+ return total;
686
+ }
551
687
  async cancelRun(runId, reason) {
552
- for (const app of this.listApps()) {
553
- const res = await app.cancelRun(runId, reason);
688
+ for (const runtime of this.listRuntimes()) {
689
+ const res = await runtime.cancelRun(runId, reason);
554
690
  if (res.outcome !== "not_found") {
555
691
  return res;
556
692
  }
@@ -558,43 +694,169 @@ export class DefaultActionDockHost {
558
694
  return { outcome: "not_found", runId };
559
695
  }
560
696
  events(runId, options) {
561
- for (const app of this.listApps()) {
562
- if (app.executionService?.getActiveHandle?.(runId)) {
563
- return app.events(runId, options);
697
+ for (const runtime of this.listRuntimes()) {
698
+ const storageRecord = runtime.storage?.getRun?.(runId);
699
+ if (storageRecord) {
700
+ return runtime.events(runId, options);
564
701
  }
565
702
  }
566
- for (const app of this.listApps()) {
567
- const storageRecord = app.storage?.getRun?.(runId);
568
- if (storageRecord) {
569
- return app.events(runId, options);
703
+ const firstRuntime = this.listRuntimes()[0];
704
+ if (firstRuntime) {
705
+ return firstRuntime.events(runId, options);
706
+ }
707
+ return this.eventSink.subscribe(runId, options);
708
+ }
709
+ getGlobalStorage() {
710
+ if (!this.globalStorage) {
711
+ const hasInMemoryPackage = this.options.packages?.some((item) => typeof item !== "string" && !isPackageRuntime(item) && item.inMemory);
712
+ const inMemory = Boolean(this.options.inMemory || hasInMemoryPackage);
713
+ this.globalStorage =
714
+ this.options.platform?.storage?.createGlobalStorage?.({
715
+ dataDir: this.options.dataDir,
716
+ customHome: this.options.customHome,
717
+ }) ??
718
+ createGlobalStorage({
719
+ dataDir: this.options.dataDir,
720
+ customHome: this.options.customHome,
721
+ inMemory,
722
+ });
723
+ }
724
+ return this.globalStorage;
725
+ }
726
+ findDeclaredConfigItem(key) {
727
+ let foundItem;
728
+ for (const runtime of this.listRuntimes()) {
729
+ const item = runtime.projectConfig?.config?.[key];
730
+ if (item) {
731
+ if (item.secret)
732
+ return item;
733
+ foundItem = item;
570
734
  }
571
735
  }
572
- const firstApp = this.listApps()[0];
573
- if (firstApp) {
574
- return firstApp.events(runId, options);
736
+ return foundItem;
737
+ }
738
+ resolveRuntime(packageId) {
739
+ if (packageId) {
740
+ return this.getRuntime(packageId);
575
741
  }
576
- return (async function* () { })();
742
+ const runtimes = this.listRuntimes();
743
+ return runtimes.length === 1 ? runtimes[0] : undefined;
744
+ }
745
+ /**
746
+ * 解析目标包运行时:未命中时抛出统一的包未找到错误。
747
+ */
748
+ requireRuntime(packageId) {
749
+ const runtime = this.resolveRuntime(packageId);
750
+ if (!runtime) {
751
+ throw new Error(`Package '${packageId}' not found in host`);
752
+ }
753
+ return runtime;
754
+ }
755
+ async getConfig(packageId, key) {
756
+ if (packageId === "global") {
757
+ const globalStorage = this.getGlobalStorage();
758
+ const val = globalStorage.getConfig(key);
759
+ const configured = val !== undefined;
760
+ const declaredItem = this.findDeclaredConfigItem(key);
761
+ const isSecret = isSecretConfigKey(key, declaredItem);
762
+ return {
763
+ key,
764
+ configured,
765
+ secret: isSecret,
766
+ source: configured ? "global" : "default",
767
+ value: !isSecret && configured ? val : undefined,
768
+ };
769
+ }
770
+ return this.requireRuntime(packageId).getConfig(key);
771
+ }
772
+ async setConfig(packageId, key, value) {
773
+ if (packageId === "global") {
774
+ await this.getGlobalStorage().setConfig(key, value);
775
+ return;
776
+ }
777
+ await this.requireRuntime(packageId).setConfig(key, value);
778
+ }
779
+ async deleteConfig(packageId, key) {
780
+ if (packageId === "global") {
781
+ return await this.getGlobalStorage().deleteConfig(key);
782
+ }
783
+ return await this.requireRuntime(packageId).deleteConfig(key);
784
+ }
785
+ async listConfig(packageId) {
786
+ if (packageId === "global") {
787
+ const globalStorage = this.getGlobalStorage();
788
+ const all = globalStorage.listConfig();
789
+ return Object.entries(all).map(([key, val]) => {
790
+ const declaredItem = this.findDeclaredConfigItem(key);
791
+ const isSecret = isSecretConfigKey(key, declaredItem);
792
+ return {
793
+ key,
794
+ configured: true,
795
+ secret: isSecret,
796
+ source: "global",
797
+ value: isSecret ? undefined : val,
798
+ };
799
+ });
800
+ }
801
+ return this.requireRuntime(packageId).listConfig();
802
+ }
803
+ async getState(packageId, actionId, key, options) {
804
+ return this.requireRuntime(packageId).getState(actionId, key, options);
805
+ }
806
+ async setState(packageId, actionId, key, value, options) {
807
+ const runtime = this.requireRuntime(packageId);
808
+ if (actionId) {
809
+ await runtime.setActionState(actionId, key, value, options);
810
+ }
811
+ else {
812
+ await runtime.setState(key, value, options);
813
+ }
814
+ }
815
+ async deleteState(packageId, actionId, key, options) {
816
+ return this.requireRuntime(packageId).deleteState(actionId, key, options);
817
+ }
818
+ async listStateKeys(packageId, actionId, options) {
819
+ return this.requireRuntime(packageId).listStateKeys(actionId, options);
820
+ }
821
+ async clearState(packageId, actionId, options) {
822
+ return this.requireRuntime(packageId).clearState(actionId, options);
823
+ }
824
+ async listStateEntries(packageId, options) {
825
+ const runtime = this.resolveRuntime(packageId);
826
+ if (!runtime) {
827
+ throw new ServiceError(CAPABILITY_UNAVAILABLE, `CAPABILITY_UNAVAILABLE: Package '${packageId}' not found in host`);
828
+ }
829
+ if (!runtime.listStateEntries) {
830
+ return [];
831
+ }
832
+ return runtime.listStateEntries(options);
577
833
  }
578
834
  /**
579
835
  * 优雅关闭宿主容器。
580
- * 仅对内部创建的 App 实例执行 close 并安全释放底层资源;
581
- * 外部传入借用的 App 实例生命周期完全由调用方负责管理,Host 关闭时仅解绑引用并清理自身内部实例。
836
+ * 仅对内部创建的 Runtime 实例执行 close 并安全释放底层资源;
837
+ * 外部传入借用的 Runtime 实例生命周期完全由调用方负责管理,Host 关闭时仅解绑引用并清理自身内部实例。
582
838
  */
583
839
  async close(options) {
584
840
  if (this.isClosed)
585
841
  return;
586
842
  this.isClosed = true;
587
- const internalApps = Array.from(this.internallyCreatedApps);
588
- await Promise.all(internalApps.map(async (app) => {
843
+ const allRuntimes = Array.from(this.runtimes.values());
844
+ await Promise.all(allRuntimes.map(async (runtime) => {
589
845
  try {
590
- await app.close(options);
846
+ await runtime.close(options);
591
847
  }
592
848
  catch {
593
- // 忽略内部 App 关闭异常,确保全部安全释放
849
+ // 忽略 Runtime 关闭异常,确保全部安全释放
594
850
  }
595
851
  }));
596
- this.internallyCreatedApps.clear();
597
- this.apps.clear();
852
+ this.runtimes.clear();
853
+ try {
854
+ this.globalStorage?.close();
855
+ }
856
+ catch {
857
+ // 忽略全局存储关闭异常
858
+ }
859
+ this.globalStorage = undefined;
598
860
  try {
599
861
  this.dataDirLock?.release();
600
862
  }
@@ -610,30 +872,17 @@ export class DefaultActionDockHost {
610
872
  export async function createActionDockHost(options = {}) {
611
873
  let createdHost;
612
874
  try {
613
- if (options.autoLoadCurrentProject !== false) {
614
- const root = resolveProjectRoot(options, () => findProjectRoot());
615
- if (root) {
616
- if (isProjectLockHeld(root)) {
617
- const err = new Error("PROJECT_BUSY: Project directory is locked by another active process holding project.lock");
618
- err.code = PROJECT_BUSY;
619
- throw err;
620
- }
621
- // 依据事务日志恢复未完成提交的悬空事务(锁持有者存活时严禁判定为崩溃事务并禁止自动恢复)
622
- if (hasPendingTransactions(root)) {
623
- await recoverPendingTransactions(root, { frozenInstall: true });
624
- }
625
- }
626
- }
627
- createdHost = new DefaultActionDockHost(options);
875
+ createdHost = new DefaultActionDockHost(options, { deferInit: true });
876
+ await createdHost.initializeAsync();
628
877
  return createdHost;
629
878
  }
630
879
  catch (err) {
631
880
  if (createdHost) {
632
881
  try {
633
- await createdHost.close();
882
+ await createdHost.rollbackInitialization();
634
883
  }
635
884
  catch {
636
- // 忽略宿主关闭异常
885
+ // 忽略宿主回滚异常,确保抛出原始异常
637
886
  }
638
887
  }
639
888
  throw err;