@actiondock/core 2.5.1 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/dist/app/app.d.ts +14 -1
  2. package/dist/app/app.js +88 -222
  3. package/dist/app/static-index.d.ts +39 -0
  4. package/dist/app/static-index.js +164 -0
  5. package/dist/app/types.d.ts +5 -0
  6. package/dist/catalog/action-index.js +6 -4
  7. package/dist/catalog/package-catalog.js +2 -1
  8. package/dist/doctor/checks/index.d.ts +5 -0
  9. package/dist/doctor/checks/index.js +5 -0
  10. package/dist/doctor/checks/project-locate.d.ts +6 -0
  11. package/dist/doctor/checks/project-locate.js +36 -0
  12. package/dist/doctor/checks/project.d.ts +6 -0
  13. package/dist/doctor/checks/project.js +383 -0
  14. package/dist/doctor/checks/registry.d.ts +13 -0
  15. package/dist/doctor/checks/registry.js +207 -0
  16. package/dist/doctor/checks/runtime.d.ts +13 -0
  17. package/dist/doctor/checks/runtime.js +90 -0
  18. package/dist/doctor/checks/storage.d.ts +5 -0
  19. package/dist/doctor/checks/storage.js +34 -0
  20. package/dist/doctor/context.d.ts +35 -0
  21. package/dist/doctor/context.js +11 -0
  22. package/dist/doctor/doctor.d.ts +5 -0
  23. package/dist/doctor/doctor.js +33 -602
  24. package/dist/errors.d.ts +22 -0
  25. package/dist/errors.js +22 -0
  26. package/dist/execution/service.js +14 -9
  27. package/dist/export/templates.js +42 -30
  28. package/dist/host/host.d.ts +33 -0
  29. package/dist/host/host.js +222 -362
  30. package/dist/host/routing.d.ts +92 -0
  31. package/dist/host/routing.js +238 -0
  32. package/dist/host/types.d.ts +8 -0
  33. package/dist/index.d.ts +3 -1
  34. package/dist/index.js +3 -1
  35. package/dist/input/advice.d.ts +100 -0
  36. package/dist/input/advice.js +767 -0
  37. package/dist/input/describe.d.ts +106 -0
  38. package/dist/input/describe.js +176 -0
  39. package/dist/input/file-input.d.ts +54 -0
  40. package/dist/input/file-input.js +124 -0
  41. package/dist/input/flat-decode.d.ts +11 -0
  42. package/dist/input/flat-decode.js +13 -0
  43. package/dist/input/flat-errors.d.ts +72 -0
  44. package/dist/input/flat-errors.js +87 -0
  45. package/dist/input/flat-materializer.d.ts +29 -0
  46. package/dist/input/flat-materializer.js +179 -0
  47. package/dist/input/flat-parser.d.ts +50 -0
  48. package/dist/input/flat-parser.js +169 -0
  49. package/dist/input/flat-predicates.d.ts +34 -0
  50. package/dist/input/flat-predicates.js +50 -0
  51. package/dist/input/index.d.ts +14 -0
  52. package/dist/input/index.js +14 -0
  53. package/dist/input/input-resolver.d.ts +63 -0
  54. package/dist/input/input-resolver.js +254 -0
  55. package/dist/input/json-depth-scanner.d.ts +17 -0
  56. package/dist/input/json-depth-scanner.js +49 -0
  57. package/dist/input/metadata.d.ts +99 -0
  58. package/dist/input/metadata.js +99 -0
  59. package/dist/input/stdin-input.d.ts +26 -0
  60. package/dist/input/stdin-input.js +125 -0
  61. package/dist/input/utf8.d.ts +19 -0
  62. package/dist/input/utf8.js +36 -0
  63. package/dist/input/validation-mapper.d.ts +27 -0
  64. package/dist/input/validation-mapper.js +144 -0
  65. package/dist/ipc/host.js +38 -0
  66. package/dist/json/index.d.ts +1 -0
  67. package/dist/json/index.js +1 -0
  68. package/dist/json/value-validator.d.ts +88 -0
  69. package/dist/json/value-validator.js +330 -0
  70. package/dist/platform/types.d.ts +7 -0
  71. package/dist/process/control-arbiter.d.ts +107 -0
  72. package/dist/process/control-arbiter.js +251 -0
  73. package/dist/process/diagnostics-sink.d.ts +26 -0
  74. package/dist/process/diagnostics-sink.js +35 -0
  75. package/dist/process/index.d.ts +7 -0
  76. package/dist/process/index.js +7 -0
  77. package/dist/process/input-dispatcher.d.ts +88 -0
  78. package/dist/process/input-dispatcher.js +240 -0
  79. package/dist/process/managed-record.d.ts +78 -0
  80. package/dist/process/managed-record.js +1 -0
  81. package/dist/process/process-manager.d.ts +15 -56
  82. package/dist/process/process-manager.js +119 -707
  83. package/dist/process/reservation-table.d.ts +39 -0
  84. package/dist/process/reservation-table.js +80 -0
  85. package/dist/process/run-executor.d.ts +16 -0
  86. package/dist/process/run-executor.js +155 -0
  87. package/dist/process/terminal-output-cache.d.ts +66 -0
  88. package/dist/process/terminal-output-cache.js +132 -0
  89. package/dist/profile/client-actions.d.ts +56 -0
  90. package/dist/profile/client-actions.js +215 -0
  91. package/dist/profile/client-config.d.ts +20 -0
  92. package/dist/profile/client-config.js +51 -0
  93. package/dist/profile/client-health.d.ts +14 -0
  94. package/dist/profile/client-health.js +69 -0
  95. package/dist/profile/client-playbooks.d.ts +19 -0
  96. package/dist/profile/client-playbooks.js +36 -0
  97. package/dist/profile/client-query.d.ts +30 -0
  98. package/dist/profile/client-query.js +70 -0
  99. package/dist/profile/client-runs.d.ts +42 -0
  100. package/dist/profile/client-runs.js +58 -0
  101. package/dist/profile/client-state.d.ts +56 -0
  102. package/dist/profile/client-state.js +83 -0
  103. package/dist/profile/client-transport.d.ts +61 -0
  104. package/dist/profile/client-transport.js +156 -0
  105. package/dist/profile/client.d.ts +23 -142
  106. package/dist/profile/client.js +28 -588
  107. package/dist/profile/manager.js +1 -1
  108. package/dist/project/closure.d.ts +4 -4
  109. package/dist/project/closure.js +11 -5
  110. package/dist/project/init.js +2 -2
  111. package/dist/project/loader.d.ts +0 -25
  112. package/dist/project/loader.js +1 -154
  113. package/dist/project/transactions.d.ts +12 -14
  114. package/dist/project/transactions.js +60 -836
  115. package/dist/registry/registry.js +6 -4
  116. package/dist/registry/resolve.js +1 -1
  117. package/dist/runtime/action-registry.d.ts +45 -0
  118. package/dist/runtime/action-registry.js +108 -0
  119. package/dist/runtime/clock.d.ts +5 -17
  120. package/dist/runtime/clock.js +5 -15
  121. package/dist/runtime/context.d.ts +3 -0
  122. package/dist/runtime/context.js +17 -56
  123. package/dist/runtime/index.d.ts +3 -0
  124. package/dist/runtime/index.js +3 -0
  125. package/dist/runtime/package-runner-factory.d.ts +82 -0
  126. package/dist/runtime/package-runner-factory.js +186 -0
  127. package/dist/runtime/run-persistence.d.ts +85 -0
  128. package/dist/runtime/run-persistence.js +111 -0
  129. package/dist/runtime/runner.d.ts +25 -45
  130. package/dist/runtime/runner.js +130 -354
  131. package/dist/runtime/standalone.d.ts +25 -2
  132. package/dist/runtime/standalone.js +305 -87
  133. package/dist/schema/index.d.ts +1 -0
  134. package/dist/schema/index.js +1 -0
  135. package/dist/schema/validator.d.ts +10 -0
  136. package/dist/schema/validator.js +48 -54
  137. package/dist/server/dispatcher.d.ts +8 -3
  138. package/dist/server/dispatcher.js +16 -40
  139. package/dist/server/routes/runs.js +6 -4
  140. package/dist/server/security.d.ts +1 -7
  141. package/dist/server/security.js +2 -13
  142. package/dist/storage/clock.d.ts +25 -0
  143. package/dist/storage/clock.js +23 -0
  144. package/dist/storage/data-dir-lock.d.ts +3 -36
  145. package/dist/storage/data-dir-lock.js +48 -881
  146. package/dist/storage/index.d.ts +8 -1
  147. package/dist/storage/index.js +8 -2
  148. package/dist/storage/lazy.js +1 -0
  149. package/dist/storage/lock-core.d.ts +162 -0
  150. package/dist/storage/lock-core.js +885 -0
  151. package/dist/storage/sqlite.d.ts +19 -6
  152. package/dist/storage/sqlite.js +81 -89
  153. package/dist/storage/types.d.ts +27 -5
  154. package/dist/storage/types.js +21 -0
  155. package/dist/storage/utils.d.ts +22 -0
  156. package/dist/storage/utils.js +34 -0
  157. package/dist/target/local.d.ts +1 -0
  158. package/dist/target/local.js +23 -17
  159. package/dist/target/remote-errors.d.ts +21 -0
  160. package/dist/target/remote-errors.js +51 -0
  161. package/dist/target/remote-polling.d.ts +35 -0
  162. package/dist/target/remote-polling.js +104 -0
  163. package/dist/target/remote.d.ts +4 -0
  164. package/dist/target/remote.js +108 -301
  165. package/dist/target/sse-parser.d.ts +35 -0
  166. package/dist/target/sse-parser.js +163 -0
  167. package/dist/target/target.js +2 -0
  168. package/dist/target/types.d.ts +6 -0
  169. package/dist/utils/index.d.ts +1 -0
  170. package/dist/utils/index.js +1 -0
  171. package/dist/utils/net.d.ts +10 -0
  172. package/dist/utils/net.js +16 -0
  173. package/dist/version.d.ts +1 -1
  174. package/dist/version.js +1 -1
  175. package/package.json +2 -2
package/dist/host/host.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { existsSync } from "node:fs";
3
3
  import { DefaultActionDockApp } from "../app/app.js";
4
- import { ActionResolver } from "../catalog/action-resolver.js";
5
4
  import { findProjectRoot, loadProjectConfig } from "../project/loader.js";
6
5
  import { ActionPackageResolver } from "../project/resolver.js";
7
6
  import { hasPendingTransactions, isProjectLockHeld, recoverPendingTransactions } from "../project/transactions.js";
@@ -9,6 +8,7 @@ import { listLinkedPackages } from "../registry/registry.js";
9
8
  import { InMemoryEventSink } from "../runtime/events.js";
10
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";
11
10
  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
12
  function isActionDockApp(item) {
13
13
  return (typeof item === "object" &&
14
14
  item !== null &&
@@ -17,29 +17,13 @@ function isActionDockApp(item) {
17
17
  "runAction" in item &&
18
18
  typeof item.runAction === "function");
19
19
  }
20
- /**
21
- * 判定异常是否为 not-found 语义(包内确实不存在该 Action)。
22
- *
23
- * 遍历匹配短标识符时仅跳过此类错误;存储损坏、模块加载失败等内部错误必须向调用方透传,
24
- * 严禁被吞没后伪装成 ACTION_NOT_FOUND。
25
- */
26
- function isActionNotFoundLikeError(err) {
27
- if (!err) {
28
- return false;
29
- }
30
- if (err.code === ACTION_NOT_FOUND || err.code === "NOT_FOUND") {
31
- return true;
32
- }
33
- const message = typeof err.message === "string" ? err.message : String(err);
34
- return (message.startsWith("ACTION_NOT_FOUND:") ||
35
- /Action '[^']*' not found in package/.test(message));
36
- }
37
20
  /**
38
21
  * ActionDock 统一多包宿主容器默认实现。
39
22
  * 负责聚合与调度多个 ActionDockApp 实例,提供跨包引用路由、依赖声明校验与资源配额控制。
40
23
  */
41
24
  export class DefaultActionDockHost {
42
25
  hostSessionId;
26
+ options;
43
27
  apps = new Map();
44
28
  internallyCreatedApps = new Set();
45
29
  activeSubRunsPerRoot = new Map();
@@ -51,11 +35,17 @@ export class DefaultActionDockHost {
51
35
  isClosed = false;
52
36
  dataDirLock;
53
37
  failedLinkedPackages = new Map();
38
+ failedAutoLoad;
39
+ /** 透传给内部创建 App 的存储收割开关(Host 默认持有者身份,显式可关) */
40
+ recoverOrphans;
54
41
  constructor(options = {}) {
55
42
  this.hostSessionId = randomUUID();
43
+ this.options = options;
56
44
  this.maxCallDepth = options.maxCallDepth ?? 16;
57
45
  this.maxSubRuns = options.maxSubRuns ?? 64;
58
46
  this.eventSink = options.eventSink ?? options.platform?.eventSink ?? new InMemoryEventSink();
47
+ // Host 默认声明数据目录持有者身份;查询旁观方(CLI 查询命令)显式置 false
48
+ this.recoverOrphans = options.recoverOrphans !== false;
59
49
  // 当指定非内存 dataDir 时获取排他目录锁,防止并发冲突
60
50
  if (options.dataDir && !options.inMemory) {
61
51
  this.dataDirLock = DataDirLock.acquire(options.dataDir, {
@@ -63,140 +53,15 @@ export class DefaultActionDockHost {
63
53
  });
64
54
  }
65
55
  try {
66
- // 注册显式传入的 packages 列表
67
- if (options.packages && Array.isArray(options.packages)) {
68
- for (const item of options.packages) {
69
- if (isActionDockApp(item)) {
70
- this.registerAppInternal(item, true);
71
- }
72
- else {
73
- const app = new DefaultActionDockApp({
74
- ...item,
75
- hostSessionId: this.hostSessionId,
76
- platform: item.platform ?? options.platform,
77
- inMemory: item.inMemory ?? options.inMemory,
78
- customHome: item.customHome ?? options.customHome,
79
- dataDir: item.dataDir ?? options.dataDir,
80
- clock: item.clock ?? options.clock,
81
- process: item.process ?? options.process,
82
- logger: item.logger ?? options.logger,
83
- eventSink: item.eventSink ?? this.eventSink,
84
- maxCallDepth: item.maxCallDepth ?? this.maxCallDepth,
85
- maxSubRuns: item.maxSubRuns ?? this.maxSubRuns,
86
- packageContextResolver: this.resolvePackageContext.bind(this),
87
- });
88
- this.internallyCreatedApps.add(app);
89
- this.registerAppInternal(app, true);
90
- }
91
- }
92
- }
93
- // 自动加载当前工程(若发现工程根目录且未显式禁用)
56
+ // 阶段一:注册显式传入的 packages 列表
57
+ this.registerExplicitPackages(options);
58
+ // 阶段二:自动加载当前工程(若发现工程根目录且未显式禁用)
94
59
  if (options.autoLoadCurrentProject !== false) {
95
- let root = options.projectRoot;
96
- if (!root) {
97
- const detected = findProjectRoot();
98
- if (detected) {
99
- root = detected;
100
- }
101
- }
102
- if (root && existsSync(root)) {
103
- if (isProjectLockHeld(root)) {
104
- const err = new Error("PROJECT_BUSY: Project directory is locked by another active process holding project.lock");
105
- err.code = PROJECT_BUSY;
106
- throw err;
107
- }
108
- if (hasPendingTransactions(root)) {
109
- const err = new Error(`PROJECT_RECOVERY_REQUIRED: Project directory '${root}' has pending transactions requiring recovery; use async createActionDockHost() to recover automatically`);
110
- err.code = PROJECT_RECOVERY_REQUIRED;
111
- throw err;
112
- }
113
- try {
114
- const config = loadProjectConfig(root);
115
- this.hostPublicPackageIds.add(config.id);
116
- this.resolver = new ActionPackageResolver({
117
- projectRoot: root,
118
- manifest: config,
119
- allowDevLinks: options.scanLinkedPackages,
120
- customHome: options.customHome,
121
- });
122
- const graph = this.resolver.resolveSync();
123
- this.hostPublicPackageIds.add(graph.rootPackageId);
124
- for (const depId of graph.directDependencyIds) {
125
- this.hostPublicPackageIds.add(depId);
126
- }
127
- for (const pkg of graph.packages.values()) {
128
- if (!this.apps.has(pkg.packageId)) {
129
- const isDirectOrRoot = this.hostPublicPackageIds.has(pkg.packageId);
130
- const app = new DefaultActionDockApp({
131
- packageRoot: pkg.packageRoot,
132
- projectConfig: pkg.manifest,
133
- hostSessionId: this.hostSessionId,
134
- platform: options.platform,
135
- inMemory: options.inMemory,
136
- customHome: options.customHome,
137
- dataDir: options.dataDir,
138
- clock: options.clock,
139
- process: options.process,
140
- logger: options.logger,
141
- eventSink: this.eventSink,
142
- maxCallDepth: this.maxCallDepth,
143
- maxSubRuns: this.maxSubRuns,
144
- packageContextResolver: this.resolvePackageContext.bind(this),
145
- });
146
- this.internallyCreatedApps.add(app);
147
- this.registerAppInternal(app, isDirectOrRoot);
148
- }
149
- }
150
- }
151
- catch (err) {
152
- if (err?.code === ACTION_PACKAGE_VERSION_CONFLICT ||
153
- err?.code === PROJECT_RECOVERY_REQUIRED ||
154
- err?.code === PROJECT_BUSY) {
155
- throw err;
156
- }
157
- // 忽略非 ActionDock 工程目录解析异常
158
- }
159
- }
60
+ this.loadCurrentProject(options);
160
61
  }
161
- // 扫描已软链接的外部包并注册至 Host
62
+ // 阶段三:扫描已软链接的外部包并注册至 Host
162
63
  if (options.scanLinkedPackages) {
163
- for (const linked of listLinkedPackages(options.customHome)) {
164
- if (!this.apps.has(linked.id)) {
165
- if (!existsSync(linked.path)) {
166
- const err = `Linked package '${linked.id}' path does not exist on disk: '${linked.path}'`;
167
- this.failedLinkedPackages.set(linked.id, { path: linked.path, error: err });
168
- options.logger?.warn?.(`[Host] ${err}`);
169
- continue;
170
- }
171
- try {
172
- const config = loadProjectConfig(linked.path);
173
- this.hostPublicPackageIds.add(linked.id);
174
- const app = new DefaultActionDockApp({
175
- packageRoot: linked.path,
176
- projectConfig: config,
177
- hostSessionId: this.hostSessionId,
178
- platform: options.platform,
179
- inMemory: options.inMemory,
180
- customHome: options.customHome,
181
- dataDir: options.dataDir,
182
- clock: options.clock,
183
- process: options.process,
184
- logger: options.logger,
185
- eventSink: this.eventSink,
186
- maxCallDepth: this.maxCallDepth,
187
- maxSubRuns: this.maxSubRuns,
188
- packageContextResolver: this.resolvePackageContext.bind(this),
189
- });
190
- this.internallyCreatedApps.add(app);
191
- this.registerAppInternal(app, true);
192
- }
193
- catch (err) {
194
- const errDetail = err?.message || String(err);
195
- this.failedLinkedPackages.set(linked.id, { path: linked.path, error: errDetail });
196
- options.logger?.warn?.(`[Host] Failed to load linked package '${linked.id}' from '${linked.path}': ${errDetail}`);
197
- }
198
- }
199
- }
64
+ this.registerLinkedPackages(options);
200
65
  }
201
66
  }
202
67
  catch (err) {
@@ -224,6 +89,164 @@ export class DefaultActionDockHost {
224
89
  throw err;
225
90
  }
226
91
  }
92
+ /**
93
+ * 阶段函数:注册显式传入的 packages 列表(现成 App 实例或 AppOptions 配置)。
94
+ */
95
+ registerExplicitPackages(options) {
96
+ if (!options.packages || !Array.isArray(options.packages)) {
97
+ return;
98
+ }
99
+ for (const item of options.packages) {
100
+ if (isActionDockApp(item)) {
101
+ this.registerAppInternal(item, true);
102
+ }
103
+ else {
104
+ const app = new DefaultActionDockApp({
105
+ ...item,
106
+ 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,
117
+ recoverOrphans: this.recoverOrphans,
118
+ packageContextResolver: this.resolvePackageContext.bind(this),
119
+ });
120
+ this.internallyCreatedApps.add(app);
121
+ this.registerAppInternal(app, true);
122
+ }
123
+ }
124
+ }
125
+ /**
126
+ * 阶段函数:自动加载当前工程并注册依赖闭包内的全部包。
127
+ *
128
+ * 失败语义区分场景:
129
+ * - 显式传入 projectRoot 时,任何解析失败(损坏 actiondock.json、文件系统错误等)
130
+ * 必须向上抛出,由调用方感知,严禁吞没;
131
+ * - 自动探测场景(未传 projectRoot,由 findProjectRoot 发现)失败时记录实例诊断
132
+ * failedAutoLoad 并在创建时输出单行告警,保持宿主其余能力可用但绝不无声。
133
+ */
134
+ loadCurrentProject(options) {
135
+ const explicitRoot = options.projectRoot;
136
+ const root = resolveProjectRoot(options, () => findProjectRoot());
137
+ if (!root) {
138
+ return;
139
+ }
140
+ if (isProjectLockHeld(root)) {
141
+ const err = new Error("PROJECT_BUSY: Project directory is locked by another active process holding project.lock");
142
+ err.code = PROJECT_BUSY;
143
+ throw err;
144
+ }
145
+ if (hasPendingTransactions(root)) {
146
+ const err = new Error(`PROJECT_RECOVERY_REQUIRED: Project directory '${root}' has pending transactions requiring recovery; use async createActionDockHost() to recover automatically`);
147
+ err.code = PROJECT_RECOVERY_REQUIRED;
148
+ throw err;
149
+ }
150
+ try {
151
+ const config = loadProjectConfig(root);
152
+ this.hostPublicPackageIds.add(config.id);
153
+ this.resolver = new ActionPackageResolver({
154
+ projectRoot: root,
155
+ manifest: config,
156
+ allowDevLinks: options.scanLinkedPackages,
157
+ customHome: options.customHome,
158
+ });
159
+ const graph = this.resolver.resolveSync();
160
+ this.hostPublicPackageIds.add(graph.rootPackageId);
161
+ for (const depId of graph.directDependencyIds) {
162
+ this.hostPublicPackageIds.add(depId);
163
+ }
164
+ for (const pkg of graph.packages.values()) {
165
+ if (!this.apps.has(pkg.packageId)) {
166
+ const isDirectOrRoot = this.hostPublicPackageIds.has(pkg.packageId);
167
+ const app = new DefaultActionDockApp({
168
+ packageRoot: pkg.packageRoot,
169
+ projectConfig: pkg.manifest,
170
+ hostSessionId: this.hostSessionId,
171
+ platform: options.platform,
172
+ inMemory: options.inMemory,
173
+ customHome: options.customHome,
174
+ dataDir: options.dataDir,
175
+ clock: options.clock,
176
+ process: options.process,
177
+ logger: options.logger,
178
+ eventSink: this.eventSink,
179
+ maxCallDepth: this.maxCallDepth,
180
+ maxSubRuns: this.maxSubRuns,
181
+ recoverOrphans: this.recoverOrphans,
182
+ packageContextResolver: this.resolvePackageContext.bind(this),
183
+ });
184
+ this.internallyCreatedApps.add(app);
185
+ this.registerAppInternal(app, isDirectOrRoot);
186
+ }
187
+ }
188
+ }
189
+ catch (err) {
190
+ if (err?.code === ACTION_PACKAGE_VERSION_CONFLICT ||
191
+ err?.code === PROJECT_RECOVERY_REQUIRED ||
192
+ err?.code === PROJECT_BUSY) {
193
+ throw err;
194
+ }
195
+ if (explicitRoot) {
196
+ // 显式指定的工程根目录解析失败必须抛出:调用方明确指定了位置,损坏配置不允许被吞没
197
+ throw err;
198
+ }
199
+ // 自动探测场景:记录实例诊断并输出单行告警,避免损坏工程被无声跳过
200
+ const detail = err?.message || String(err);
201
+ this.failedAutoLoad = { projectRoot: root, error: detail };
202
+ console.warn(`[Host] Failed to auto-load project at '${root}': ${detail}`);
203
+ }
204
+ }
205
+ /**
206
+ * 阶段函数:扫描全局注册表中已软链接的外部包并注册至 Host。
207
+ * 加载失败的链接包记录至 failedLinkedPackages 诊断并在调阅时透传精准失败原因。
208
+ */
209
+ registerLinkedPackages(options) {
210
+ for (const linked of listLinkedPackages(options.customHome)) {
211
+ if (this.apps.has(linked.id)) {
212
+ continue;
213
+ }
214
+ if (!existsSync(linked.path)) {
215
+ const err = `Linked package '${linked.id}' path does not exist on disk: '${linked.path}'`;
216
+ this.failedLinkedPackages.set(linked.id, { path: linked.path, error: err });
217
+ options.logger?.warn?.(`[Host] ${err}`);
218
+ continue;
219
+ }
220
+ try {
221
+ const config = loadProjectConfig(linked.path);
222
+ this.hostPublicPackageIds.add(linked.id);
223
+ const app = new DefaultActionDockApp({
224
+ packageRoot: linked.path,
225
+ projectConfig: config,
226
+ hostSessionId: this.hostSessionId,
227
+ platform: options.platform,
228
+ inMemory: options.inMemory,
229
+ customHome: options.customHome,
230
+ dataDir: options.dataDir,
231
+ clock: options.clock,
232
+ process: options.process,
233
+ logger: options.logger,
234
+ eventSink: this.eventSink,
235
+ maxCallDepth: this.maxCallDepth,
236
+ maxSubRuns: this.maxSubRuns,
237
+ recoverOrphans: this.recoverOrphans,
238
+ packageContextResolver: this.resolvePackageContext.bind(this),
239
+ });
240
+ this.internallyCreatedApps.add(app);
241
+ this.registerAppInternal(app, true);
242
+ }
243
+ catch (err) {
244
+ const errDetail = err?.message || String(err);
245
+ this.failedLinkedPackages.set(linked.id, { path: linked.path, error: errDetail });
246
+ options.logger?.warn?.(`[Host] Failed to load linked package '${linked.id}' from '${linked.path}': ${errDetail}`);
247
+ }
248
+ }
249
+ }
227
250
  resolvePackageContext(packageId) {
228
251
  const targetApp = this.getApp(packageId);
229
252
  if (!targetApp)
@@ -243,12 +266,26 @@ export class DefaultActionDockHost {
243
266
  runner.setPackageContextResolver(this.resolvePackageContext.bind(this));
244
267
  }
245
268
  }
269
+ /** 路由可见性上下文(公开包集合与依赖解析器) */
270
+ visibility() {
271
+ return {
272
+ hostPublicPackageIds: this.hostPublicPackageIds,
273
+ resolver: this.resolver,
274
+ };
275
+ }
246
276
  getApp(packageId) {
247
277
  return this.apps.get(packageId);
248
278
  }
249
279
  listApps() {
250
280
  return Array.from(this.apps.values());
251
281
  }
282
+ /**
283
+ * 获取自动加载工程的失败诊断信息。
284
+ * 仅自动探测场景会记录此诊断;显式传入 projectRoot 的加载失败会直接抛出,不产生此诊断。
285
+ */
286
+ getAutoLoadFailure() {
287
+ return this.failedAutoLoad;
288
+ }
252
289
  registerAppInternal(app, isPublic) {
253
290
  if (this.apps.has(app.packageId)) {
254
291
  const existing = this.apps.get(app.packageId);
@@ -262,22 +299,18 @@ export class DefaultActionDockHost {
262
299
  this.hostPublicPackageIds.add(app.packageId);
263
300
  }
264
301
  this.bindApp(app);
265
- // 接管与恢复:自动将死亡会话或遗留非终态运行收敛为 interrupted
266
- const st = app.storage;
267
- if (st && typeof st.recoverDeadSessionRuns === "function") {
268
- try {
269
- st.recoverDeadSessionRuns(this.hostSessionId);
270
- }
271
- catch {
272
- // 忽略单包恢复异常
273
- }
274
- }
275
- else if (st && typeof st.recoverRunningRuns === "function") {
276
- try {
277
- st.recoverRunningRuns(this.hostSessionId);
278
- }
279
- catch {
280
- // 忽略单包恢复异常
302
+ // 接管与恢复:仅持有者身份的 Host 自动将死亡会话或遗留非终态运行收敛为 interrupted
303
+ // 旁观查询 Host(CLI state/runs/config 命令)跳过本步骤,不动其他进程的在途记录
304
+ if (this.recoverOrphans) {
305
+ const st = app.storage;
306
+ if (st && typeof st.recoverDeadSessionRuns === "function") {
307
+ try {
308
+ st.recoverDeadSessionRuns(this.hostSessionId);
309
+ }
310
+ catch (err) {
311
+ // 单包恢复失败不阻断整体接管流程,但必须可观测
312
+ console.warn(`[actiondock] recoverDeadSessionRuns failed for package '${app.packageId}': ${err instanceof Error ? err.message : String(err)}`);
313
+ }
281
314
  }
282
315
  }
283
316
  }
@@ -285,16 +318,15 @@ export class DefaultActionDockHost {
285
318
  this.registerAppInternal(app, true);
286
319
  }
287
320
  async info() {
288
- return Promise.all(this.listApps().map((app) => app.info()));
321
+ return collectPackageInfos(this.listApps());
289
322
  }
290
323
  async listActions(options) {
291
324
  let results = [];
292
325
  const apps = this.listApps();
293
326
  for (const app of apps) {
294
- const isPublic = this.hostPublicPackageIds.has(app.packageId);
295
327
  const appSummaries = await app.listActions();
296
328
  for (const item of appSummaries) {
297
- if (!isPublic && this.resolver && !this.resolver.canRootCall(app.packageId, item.id)) {
329
+ if (!isRootCallVisible(app.packageId, item.id, this.hostPublicPackageIds, this.resolver)) {
298
330
  continue;
299
331
  }
300
332
  const qualifiedId = apps.length > 1 && !item.id.includes("/")
@@ -321,123 +353,13 @@ export class DefaultActionDockHost {
321
353
  return results;
322
354
  }
323
355
  async describeAction(ref) {
324
- let parsed;
325
- try {
326
- parsed = ActionResolver.parseRef(ref);
327
- }
328
- catch {
329
- parsed = typeof ref === "object" ? ref : { actionId: ref };
330
- }
331
- if (parsed.packageId) {
332
- const isPublic = this.hostPublicPackageIds.has(parsed.packageId);
333
- if (!isPublic && this.resolver && !this.resolver.canRootCall(parsed.packageId, parsed.actionId)) {
334
- throw new Error(`UNDECLARED_ACTION_DEPENDENCY: Action '${parsed.packageId}/${parsed.actionId}' is not declared as a direct dependency in actiondock.json and is not delegated by a visible playbook`);
335
- }
336
- const app = this.getApp(parsed.packageId);
337
- if (!app) {
338
- const failed = this.failedLinkedPackages.get(parsed.packageId);
339
- if (failed) {
340
- throw new Error(`Package '${parsed.packageId}' not found in host (failed to load from '${failed.path}': ${failed.error})`);
341
- }
342
- throw new Error(`Package '${parsed.packageId}' not found in host`);
343
- }
344
- const spec = await app.describeAction(parsed.actionId);
345
- return {
346
- ...spec,
347
- packageId: app.packageId,
348
- };
349
- }
350
- const matches = [];
351
- for (const app of this.listApps()) {
352
- try {
353
- const isPublic = this.hostPublicPackageIds.has(app.packageId);
354
- if (!isPublic && this.resolver && !this.resolver.canRootCall(app.packageId, parsed.actionId)) {
355
- continue;
356
- }
357
- const spec = await app.describeAction(parsed.actionId);
358
- matches.push({ app, spec: { ...spec, packageId: app.packageId } });
359
- }
360
- catch (err) {
361
- // 仅将 not-found 语义视为「包内无此 Action」;其余异常必须透传,避免内部错误伪装成 ACTION_NOT_FOUND
362
- if (!isActionNotFoundLikeError(err)) {
363
- throw err;
364
- }
365
- }
366
- }
367
- if (matches.length === 1) {
368
- return {
369
- ...matches[0].spec,
370
- packageId: matches[0].app.packageId,
371
- };
372
- }
373
- if (matches.length > 1) {
374
- const candidates = matches.map((m) => `${m.app.packageId}/${parsed.actionId}`).join(", ");
375
- const err = new Error(`INVALID_ACTION_REF: Action '${parsed.actionId}' is ambiguous and provided by multiple packages: ${candidates}. Please specify '<package-id>/${parsed.actionId}'. (AMBIGUOUS_ACTION_REF)`);
376
- err.code = "INVALID_ACTION_REF";
377
- err.details = { alias: "AMBIGUOUS_ACTION_REF", candidates: matches.map((m) => m.app.packageId) };
378
- throw err;
379
- }
380
- throw new Error(`ACTION_NOT_FOUND: Action '${parsed.actionId}' not found in any registered package`);
356
+ return describeActionAcrossApps(ref, this.listApps(), this.visibility(), this.failedLinkedPackages);
381
357
  }
382
358
  async listPlaybooks() {
383
- const results = [];
384
- const apps = this.listApps();
385
- for (const app of apps) {
386
- const isPublic = this.hostPublicPackageIds.has(app.packageId);
387
- if (!isPublic && this.resolver) {
388
- const graph = this.resolver.resolveSync();
389
- if (!graph.directDependencyIds.has(app.packageId)) {
390
- continue;
391
- }
392
- }
393
- const appPlaybooks = await app.listPlaybooks();
394
- for (const item of appPlaybooks) {
395
- const qualifiedId = apps.length > 1 && !item.id.includes("/")
396
- ? `${app.packageId}/${item.id}`
397
- : item.id;
398
- results.push({
399
- ...item,
400
- id: qualifiedId,
401
- packageId: app.packageId,
402
- });
403
- }
404
- }
405
- return results;
359
+ return listVisiblePlaybooks(this.listApps(), this.visibility());
406
360
  }
407
361
  async describePlaybook(id) {
408
- if (id.includes("/")) {
409
- const lastSlashIndex = id.lastIndexOf("/");
410
- const packageId = id.slice(0, lastSlashIndex);
411
- const playbookId = id.slice(lastSlashIndex + 1);
412
- const isPublic = this.hostPublicPackageIds.has(packageId);
413
- if (!isPublic && this.resolver) {
414
- const graph = this.resolver.resolveSync();
415
- if (!graph.directDependencyIds.has(packageId)) {
416
- throw new Error(`UNDECLARED_ACTION_DEPENDENCY: Playbook '${id}' belongs to undeclared transitive package '${packageId}'`);
417
- }
418
- }
419
- const app = this.getApp(packageId);
420
- if (!app) {
421
- throw new Error(`Package '${packageId}' not found in host`);
422
- }
423
- return app.describePlaybook(playbookId);
424
- }
425
- for (const app of this.listApps()) {
426
- try {
427
- const isPublic = this.hostPublicPackageIds.has(app.packageId);
428
- if (!isPublic && this.resolver) {
429
- const graph = this.resolver.resolveSync();
430
- if (!graph.directDependencyIds.has(app.packageId)) {
431
- continue;
432
- }
433
- }
434
- return await app.describePlaybook(id);
435
- }
436
- catch {
437
- // 忽略未匹配的包
438
- }
439
- }
440
- throw new Error(`Playbook '${id}' not found in any registered package`);
362
+ return describeVisiblePlaybook(this.listApps(), id, this.visibility());
441
363
  }
442
364
  async runAction(ref, input, options = {}) {
443
365
  const ticket = await this.startAction(ref, input, options);
@@ -451,13 +373,7 @@ export class DefaultActionDockHost {
451
373
  throw new Error("ActionDockHost is closed: new tasks rejected");
452
374
  }
453
375
  // - 解析目标包与 Action 动作标识
454
- let parsed;
455
- try {
456
- parsed = ActionResolver.parseRef(ref);
457
- }
458
- catch {
459
- parsed = typeof ref === "object" ? ref : { actionId: ref };
460
- }
376
+ const parsed = parseRefLoose(ref);
461
377
  let targetApp;
462
378
  let targetActionId = parsed.actionId;
463
379
  let targetPackageId = parsed.packageId;
@@ -465,14 +381,7 @@ export class DefaultActionDockHost {
465
381
  targetApp = this.getApp(targetPackageId);
466
382
  if (!targetApp) {
467
383
  const runId = randomUUID();
468
- const failed = this.failedLinkedPackages.get(targetPackageId);
469
- const errorMsg = failed
470
- ? `Package '${targetPackageId}' not found in host (failed to load from '${failed.path}': ${failed.error})`
471
- : `Package '${targetPackageId}' not found in host`;
472
- const error = {
473
- code: PACKAGE_NOT_FOUND,
474
- message: errorMsg,
475
- };
384
+ const error = buildRuntimeError(PACKAGE_NOT_FOUND, packageNotFoundMessage(targetPackageId, this.failedLinkedPackages));
476
385
  return {
477
386
  runId,
478
387
  status: "failed",
@@ -481,35 +390,14 @@ export class DefaultActionDockHost {
481
390
  }
482
391
  }
483
392
  else {
484
- const matches = [];
485
- for (const app of this.listApps()) {
486
- try {
487
- const isPublic = this.hostPublicPackageIds.has(app.packageId);
488
- if (!isPublic && this.resolver && !this.resolver.canRootCall(app.packageId, targetActionId)) {
489
- continue;
490
- }
491
- await app.describeAction(targetActionId);
492
- matches.push(app);
493
- }
494
- catch (err) {
495
- // 仅将 not-found 语义视为「包内无此 Action」;其余异常必须透传,避免内部错误伪装成 ACTION_NOT_FOUND
496
- if (!isActionNotFoundLikeError(err)) {
497
- throw err;
498
- }
499
- }
500
- }
501
- if (matches.length === 1) {
502
- targetApp = matches[0];
393
+ const resolution = await resolveShortRef(this.listApps(), targetActionId, this.visibility());
394
+ if (resolution.kind === "unique") {
395
+ targetApp = resolution.app;
503
396
  targetPackageId = targetApp.packageId;
504
397
  }
505
- else if (matches.length > 1) {
506
- const candidates = matches.map((m) => `${m.packageId}/${targetActionId}`).join(", ");
398
+ else if (resolution.kind === "ambiguous") {
507
399
  const runId = randomUUID();
508
- const error = {
509
- code: INVALID_ACTION_REF,
510
- message: `Action '${targetActionId}' is ambiguous and provided by multiple packages: ${candidates}. Please specify '<package-id>/${targetActionId}'.`,
511
- details: { alias: "AMBIGUOUS_ACTION_REF", candidates: matches.map((m) => m.packageId) },
512
- };
400
+ const error = buildRuntimeError(INVALID_ACTION_REF, ambiguousActionMessage(targetActionId, resolution.candidates), { alias: "AMBIGUOUS_ACTION_REF", candidates: resolution.candidates });
513
401
  return {
514
402
  runId,
515
403
  status: "failed",
@@ -518,10 +406,7 @@ export class DefaultActionDockHost {
518
406
  }
519
407
  else {
520
408
  const runId = randomUUID();
521
- const error = {
522
- code: ACTION_NOT_FOUND,
523
- message: `Action '${targetActionId}' not found in any registered package`,
524
- };
409
+ const error = buildRuntimeError(ACTION_NOT_FOUND, `Action '${targetActionId}' not found in any registered package`);
525
410
  return {
526
411
  runId,
527
412
  status: "failed",
@@ -532,16 +417,9 @@ export class DefaultActionDockHost {
532
417
  // 根调用可见性鉴权
533
418
  const parentRunId = options.parentRunId;
534
419
  if (!parentRunId && targetPackageId) {
535
- const isPublic = this.hostPublicPackageIds.has(targetPackageId);
536
- if (!isPublic && this.resolver && !this.resolver.canRootCall(targetPackageId, targetActionId)) {
420
+ if (!isRootCallVisible(targetPackageId, targetActionId, this.hostPublicPackageIds, this.resolver)) {
537
421
  const runId = randomUUID();
538
- const error = {
539
- code: UNDECLARED_ACTION_DEPENDENCY,
540
- message: `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`,
541
- details: {
542
- target: `${targetPackageId}/${targetActionId}`,
543
- },
544
- };
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}` });
545
423
  return {
546
424
  runId,
547
425
  status: "failed",
@@ -570,15 +448,11 @@ export class DefaultActionDockHost {
570
448
  : usesList.some((u) => u === targetRef || u === `${targetPackageId}/*` || u === targetPackageId);
571
449
  if (!isAllowed) {
572
450
  const runId = randomUUID();
573
- const error = {
574
- code: UNDECLARED_ACTION_DEPENDENCY,
575
- message: `Action '${callerPackageId}/${callerActionId}' does not declare dependency on '${targetRef}' in 'uses'`,
576
- details: {
577
- caller: `${callerPackageId}/${callerActionId}`,
578
- target: targetRef,
579
- declaredUses: usesList,
580
- },
581
- };
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
+ });
582
456
  return {
583
457
  runId,
584
458
  status: "failed",
@@ -602,11 +476,7 @@ export class DefaultActionDockHost {
602
476
  }
603
477
  if (depth >= this.maxCallDepth) {
604
478
  const runId = randomUUID();
605
- const error = {
606
- code: ACTION_CALL_CYCLE,
607
- message: `Maximum call depth of ${this.maxCallDepth} exceeded`,
608
- details: { alias: ACTION_MAX_DEPTH_EXCEEDED, reason: "depth_exceeded", maxDepth: this.maxCallDepth },
609
- };
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 });
610
480
  return {
611
481
  runId,
612
482
  status: "failed",
@@ -618,11 +488,7 @@ export class DefaultActionDockHost {
618
488
  const currentSubRuns = this.activeSubRunsPerRoot.get(effectiveRootRunId) || 0;
619
489
  if (currentSubRuns >= this.maxSubRuns) {
620
490
  const runId = randomUUID();
621
- const error = {
622
- code: ACTION_SUBRUN_LIMIT,
623
- message: `Maximum concurrent sub-runs (${this.maxSubRuns}) reached for root run '${effectiveRootRunId}'`,
624
- details: { alias: MAX_SUBRUNS_REACHED, limit: this.maxSubRuns },
625
- };
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 });
626
492
  return {
627
493
  runId,
628
494
  status: "failed",
@@ -745,14 +611,8 @@ export async function createActionDockHost(options = {}) {
745
611
  let createdHost;
746
612
  try {
747
613
  if (options.autoLoadCurrentProject !== false) {
748
- let root = options.projectRoot;
749
- if (!root) {
750
- const detected = findProjectRoot();
751
- if (detected) {
752
- root = detected;
753
- }
754
- }
755
- if (root && existsSync(root)) {
614
+ const root = resolveProjectRoot(options, () => findProjectRoot());
615
+ if (root) {
756
616
  if (isProjectLockHeld(root)) {
757
617
  const err = new Error("PROJECT_BUSY: Project directory is locked by another active process holding project.lock");
758
618
  err.code = PROJECT_BUSY;