@actiondock/core 2.7.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 (236) 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 +102 -10
  18. package/dist/errors.js +103 -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 -24
  33. package/dist/index.js +19 -23
  34. package/dist/input/advice.d.ts +5 -1
  35. package/dist/input/advice.js +163 -214
  36. package/dist/input/describe.d.ts +1 -1
  37. package/dist/input/flat-errors.d.ts +5 -6
  38. package/dist/input/flat-errors.js +6 -6
  39. package/dist/input/flat-materializer.js +42 -44
  40. package/dist/invocation/index.d.ts +2 -0
  41. package/dist/invocation/index.js +2 -0
  42. package/dist/invocation/policy.d.ts +157 -0
  43. package/dist/invocation/policy.js +231 -0
  44. package/dist/invocation/types.d.ts +172 -0
  45. package/dist/invocation/types.js +97 -0
  46. package/dist/ipc/diagnostic.d.ts +0 -12
  47. package/dist/ipc/diagnostic.js +0 -21
  48. package/dist/ipc/host.d.ts +4 -4
  49. package/dist/ipc/host.js +44 -47
  50. package/dist/ipc/index.d.ts +1 -1
  51. package/dist/ipc/index.js +1 -1
  52. package/dist/ipc/service.d.ts +40 -0
  53. package/dist/ipc/service.js +349 -0
  54. package/dist/ipc/types.d.ts +21 -15
  55. package/dist/node/index.d.ts +1 -0
  56. package/dist/node/index.js +1 -0
  57. package/dist/node/module-loader.d.ts +63 -0
  58. package/dist/node/module-loader.js +112 -0
  59. package/dist/package/index.d.ts +23 -0
  60. package/dist/package/index.js +18 -0
  61. package/dist/{app/app.d.ts → package/runtime.d.ts} +41 -20
  62. package/dist/{app/app.js → package/runtime.js} +140 -95
  63. package/dist/{app → package}/static-index.js +58 -41
  64. package/dist/package/storage-view.d.ts +13 -0
  65. package/dist/package/storage-view.js +22 -0
  66. package/dist/{app → package}/types.d.ts +76 -57
  67. package/dist/package/types.js +22 -0
  68. package/dist/platform/index.d.ts +1 -1
  69. package/dist/platform/index.js +1 -1
  70. package/dist/platform/node.d.ts +48 -0
  71. package/dist/platform/node.js +96 -0
  72. package/dist/platform/types.d.ts +0 -9
  73. package/dist/process/control-arbiter.d.ts +2 -1
  74. package/dist/process/control-arbiter.js +1 -2
  75. package/dist/process/driver.d.ts +9 -8
  76. package/dist/process/driver.js +11 -22
  77. package/dist/process/index.d.ts +2 -0
  78. package/dist/process/index.js +2 -0
  79. package/dist/process/managed-record.d.ts +2 -2
  80. package/dist/process/process-driver.d.ts +109 -0
  81. package/dist/process/process-driver.js +650 -0
  82. package/dist/process/process-executor.d.ts +47 -0
  83. package/dist/process/process-executor.js +296 -0
  84. package/dist/process/process-manager.js +48 -26
  85. package/dist/process/run-executor.js +12 -12
  86. package/dist/profile/client-actions.js +2 -2
  87. package/dist/profile/client-health.js +2 -2
  88. package/dist/profile/client-query.js +5 -6
  89. package/dist/profile/client-transport.d.ts +19 -17
  90. package/dist/profile/client-transport.js +47 -80
  91. package/dist/profile/client.d.ts +2 -2
  92. package/dist/profile/client.js +1 -1
  93. package/dist/profile/index.d.ts +1 -0
  94. package/dist/profile/index.js +1 -0
  95. package/dist/profile/manager.d.ts +5 -0
  96. package/dist/profile/manager.js +41 -6
  97. package/dist/project/closure.js +2 -2
  98. package/dist/project/index.d.ts +8 -1
  99. package/dist/project/index.js +10 -1
  100. package/dist/project/init.js +2 -2
  101. package/dist/project/loader.d.ts +1 -1
  102. package/dist/project/loader.js +26 -29
  103. package/dist/project/transactions.js +5 -10
  104. package/dist/registry/index.d.ts +3 -1
  105. package/dist/registry/index.js +2 -1
  106. package/dist/registry/lock.d.ts +28 -7
  107. package/dist/registry/lock.js +136 -16
  108. package/dist/registry/registry.d.ts +1 -5
  109. package/dist/registry/registry.js +1 -53
  110. package/dist/registry/scan.d.ts +0 -15
  111. package/dist/registry/scan.js +0 -59
  112. package/dist/registry/store.d.ts +42 -0
  113. package/dist/registry/store.js +43 -0
  114. package/dist/registry/types.d.ts +0 -24
  115. package/dist/runtime/action-collection.d.ts +1 -1
  116. package/dist/runtime/action-registry.d.ts +0 -4
  117. package/dist/runtime/action-registry.js +0 -7
  118. package/dist/runtime/context.js +5 -11
  119. package/dist/runtime/events.js +3 -6
  120. package/dist/runtime/identity.d.ts +22 -0
  121. package/dist/runtime/identity.js +10 -0
  122. package/dist/runtime/index.d.ts +1 -2
  123. package/dist/runtime/index.js +1 -2
  124. package/dist/runtime/module-loader.d.ts +2 -34
  125. package/dist/runtime/module-loader.js +1 -16
  126. package/dist/runtime/process.d.ts +6 -24
  127. package/dist/runtime/process.js +1 -118
  128. package/dist/runtime/run-persistence.js +2 -5
  129. package/dist/runtime/runner.d.ts +33 -93
  130. package/dist/runtime/runner.js +132 -364
  131. package/dist/runtime/standalone.d.ts +23 -8
  132. package/dist/runtime/standalone.js +100 -147
  133. package/dist/server/body.d.ts +16 -8
  134. package/dist/server/body.js +33 -16
  135. package/dist/server/dispatcher.d.ts +6 -10
  136. package/dist/server/dispatcher.js +24 -32
  137. package/dist/server/http-server.d.ts +87 -0
  138. package/dist/server/http-server.js +304 -0
  139. package/dist/server/index.d.ts +11 -0
  140. package/dist/server/index.js +10 -0
  141. package/dist/server/mcp-endpoint.d.ts +61 -0
  142. package/dist/server/mcp-endpoint.js +118 -0
  143. package/dist/server/routes/actions.js +34 -28
  144. package/dist/server/routes/common.d.ts +10 -14
  145. package/dist/server/routes/common.js +32 -56
  146. package/dist/server/routes/config.js +54 -64
  147. package/dist/server/routes/doctor.d.ts +1 -1
  148. package/dist/server/routes/doctor.js +10 -18
  149. package/dist/server/routes/health.d.ts +1 -1
  150. package/dist/server/routes/health.js +2 -3
  151. package/dist/server/routes/info.d.ts +2 -2
  152. package/dist/server/routes/info.js +12 -13
  153. package/dist/server/routes/playbooks.js +16 -29
  154. package/dist/server/routes/runs.js +95 -117
  155. package/dist/server/routes/state.js +63 -62
  156. package/dist/server/server.d.ts +2 -2
  157. package/dist/server/server.js +43 -227
  158. package/dist/server/types.d.ts +8 -19
  159. package/dist/service/factory.d.ts +12 -0
  160. package/dist/service/factory.js +85 -0
  161. package/dist/{target → service}/index.d.ts +1 -1
  162. package/dist/{target → service}/index.js +1 -1
  163. package/dist/service/local.d.ts +27 -0
  164. package/dist/service/local.js +138 -0
  165. package/dist/{target → service}/remote-errors.d.ts +3 -6
  166. package/dist/service/remote-errors.js +38 -0
  167. package/dist/{target → service}/remote-polling.d.ts +2 -2
  168. package/dist/{target → service}/remote-polling.js +8 -8
  169. package/dist/service/remote.d.ts +34 -0
  170. package/dist/service/remote.js +634 -0
  171. package/dist/service/sse-stream.d.ts +24 -0
  172. package/dist/service/sse-stream.js +139 -0
  173. package/dist/service/types.d.ts +272 -0
  174. package/dist/service/types.js +32 -0
  175. package/dist/storage/data-dir-lock.js +10 -5
  176. package/dist/storage/driver.d.ts +2 -1
  177. package/dist/storage/driver.js +3 -52
  178. package/dist/storage/index.d.ts +1 -0
  179. package/dist/storage/index.js +1 -0
  180. package/dist/storage/lock-core.d.ts +2 -6
  181. package/dist/storage/lock-core.js +4 -17
  182. package/dist/storage/params.d.ts +2 -8
  183. package/dist/storage/params.js +2 -8
  184. package/dist/storage/sqlite-driver.d.ts +40 -0
  185. package/dist/storage/sqlite-driver.js +116 -0
  186. package/dist/storage/sqlite.js +21 -6
  187. package/dist/storage/types.d.ts +4 -4
  188. package/dist/storage/types.js +2 -2
  189. package/dist/utils/index.d.ts +53 -0
  190. package/dist/utils/index.js +99 -4
  191. package/dist/utils/process.d.ts +10 -0
  192. package/dist/utils/process.js +21 -0
  193. package/dist/version.d.ts +1 -1
  194. package/dist/version.js +1 -1
  195. package/package.json +41 -3
  196. package/dist/app/index.d.ts +0 -2
  197. package/dist/app/index.js +0 -2
  198. package/dist/app/types.js +0 -1
  199. package/dist/catalog/action-index.d.ts +0 -9
  200. package/dist/catalog/action-index.js +0 -95
  201. package/dist/catalog/action-resolver.d.ts +0 -20
  202. package/dist/catalog/action-resolver.js +0 -95
  203. package/dist/catalog/location-registry.d.ts +0 -15
  204. package/dist/catalog/location-registry.js +0 -138
  205. package/dist/catalog/package-catalog.d.ts +0 -7
  206. package/dist/catalog/package-catalog.js +0 -77
  207. package/dist/catalog/types.d.ts +0 -68
  208. package/dist/catalog/types.js +0 -1
  209. package/dist/export/index.d.ts +0 -1
  210. package/dist/export/index.js +0 -1
  211. package/dist/export/templates.d.ts +0 -71
  212. package/dist/export/templates.js +0 -635
  213. package/dist/ipc/target.d.ts +0 -74
  214. package/dist/ipc/target.js +0 -365
  215. package/dist/platform/default.d.ts +0 -38
  216. package/dist/platform/default.js +0 -78
  217. package/dist/project/resolver.d.ts +0 -116
  218. package/dist/project/resolver.js +0 -376
  219. package/dist/registry/resolve.d.ts +0 -62
  220. package/dist/registry/resolve.js +0 -195
  221. package/dist/runtime/execution-manager.d.ts +0 -48
  222. package/dist/runtime/execution-manager.js +0 -66
  223. package/dist/runtime/package-runner-factory.d.ts +0 -82
  224. package/dist/runtime/package-runner-factory.js +0 -186
  225. package/dist/target/local.d.ts +0 -53
  226. package/dist/target/local.js +0 -349
  227. package/dist/target/remote-errors.js +0 -51
  228. package/dist/target/remote.d.ts +0 -86
  229. package/dist/target/remote.js +0 -818
  230. package/dist/target/target.d.ts +0 -6
  231. package/dist/target/target.js +0 -37
  232. package/dist/target/types.d.ts +0 -261
  233. package/dist/target/types.js +0 -35
  234. /package/dist/{app → package}/static-index.d.ts +0 -0
  235. /package/dist/{target → service}/sse-parser.d.ts +0 -0
  236. /package/dist/{target → service}/sse-parser.js +0 -0
@@ -0,0 +1,296 @@
1
+ import { PROCESS_CANCELLED, PROCESS_OUTPUT_LIMIT, PROCESS_SPAWN_ERROR, PROCESS_TIMEOUT, UNSUPPORTED_CAPABILITY, ProcessError, } from "../errors.js";
2
+ import { NodeProcessDriver, killProcessGroup } from "./process-driver.js";
3
+ import { RunExecutor } from "./run-executor.js";
4
+ export { killProcessGroup };
5
+ /**
6
+ * 基于 NodeProcessDriver 实现的兼容命令执行器。
7
+ * 核心 OS 进程派生与生命周期管理统一由 NodeProcessDriver 承担,不再重复派生与调度。
8
+ */
9
+ export class NodeProcessExecutor {
10
+ driver;
11
+ gracePeriodMs;
12
+ constructor(options) {
13
+ if (typeof options === "number") {
14
+ this.gracePeriodMs = options;
15
+ this.driver = new NodeProcessDriver();
16
+ }
17
+ else {
18
+ this.gracePeriodMs = options?.gracePeriodMs ?? 500;
19
+ this.driver =
20
+ options?.driver ??
21
+ new NodeProcessDriver({
22
+ spawnFn: options?.spawnFn,
23
+ });
24
+ }
25
+ }
26
+ /**
27
+ * 执行外部系统命令,完整支持标准输入管道、超时控制、取消信号、输出容量截断及错误拦截。
28
+ *
29
+ * @param command 执行命令
30
+ * @param args 参数列表
31
+ * @param options 执行选项
32
+ * @param internal 内部专用参数:envAlreadyResolved 为 true 时 options.env 视为已完整解析
33
+ */
34
+ async exec(command, args = [], options = {}, internal) {
35
+ const startTime = Date.now();
36
+ const maxOutputBytes = options.maxOutputBytes ?? 10 * 1024 * 1024;
37
+ const encoding = options.encoding || "utf8";
38
+ const processId = `exec-${crypto.randomUUID()}`;
39
+ return new Promise(async (resolve, reject) => {
40
+ let settled = false;
41
+ let timedOut = false;
42
+ let cancelled = false;
43
+ let outputLimitExceeded = false;
44
+ let error;
45
+ let writeErr;
46
+ const stdoutChunks = [];
47
+ const stderrChunks = [];
48
+ let totalBytes = 0;
49
+ let timeoutTimer;
50
+ let graceTimer;
51
+ let abortHandler;
52
+ let handle;
53
+ /** 子进程是否已退出:grace 宽限期 SIGKILL 升级前的存活二闪确认依据 */
54
+ let childExited = false;
55
+ const finish = (result) => {
56
+ if (settled)
57
+ return;
58
+ settled = true;
59
+ if (timeoutTimer) {
60
+ clearTimeout(timeoutTimer);
61
+ timeoutTimer = undefined;
62
+ }
63
+ // 结算时同步清理 grace 宽限定时器:子进程已退出(或已故障),
64
+ // 后续 SIGKILL 升级不再必要,且可避免定时器泄漏与 pid 复用误杀
65
+ if (graceTimer) {
66
+ clearTimeout(graceTimer);
67
+ graceTimer = undefined;
68
+ }
69
+ if (options.signal && abortHandler) {
70
+ options.signal.removeEventListener("abort", abortHandler);
71
+ }
72
+ if (!result.ok && options.throwOnError) {
73
+ const errMsg = result.error?.message || result.stderr || `Process exited with code ${result.exitCode}`;
74
+ reject(new Error(errMsg));
75
+ }
76
+ else {
77
+ resolve(result);
78
+ }
79
+ };
80
+ const terminateChild = (sig = "SIGTERM") => {
81
+ const pid = handle?.pid;
82
+ if (pid) {
83
+ killProcessGroup(pid, sig, this.driver.spawnFn);
84
+ }
85
+ if (timeoutTimer) {
86
+ clearTimeout(timeoutTimer);
87
+ timeoutTimer = undefined;
88
+ }
89
+ if (sig === "SIGKILL") {
90
+ if (graceTimer) {
91
+ clearTimeout(graceTimer);
92
+ graceTimer = undefined;
93
+ }
94
+ }
95
+ else if (!graceTimer) {
96
+ graceTimer = setTimeout(() => {
97
+ graceTimer = undefined;
98
+ // 宽限期到时二次确认子进程句柄仍存活:已退出则跳过 SIGKILL,
99
+ // 杜绝对已复用 pid 的无关进程组误杀
100
+ if (childExited) {
101
+ return;
102
+ }
103
+ const targetPid = handle?.pid;
104
+ if (targetPid) {
105
+ killProcessGroup(targetPid, "SIGKILL", this.driver.spawnFn);
106
+ }
107
+ }, this.gracePeriodMs);
108
+ graceTimer.unref?.();
109
+ }
110
+ };
111
+ const callbacks = {
112
+ onOutput: (stream, data) => {
113
+ if (outputLimitExceeded)
114
+ return;
115
+ const chunk = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
116
+ totalBytes += chunk.length;
117
+ if (totalBytes > maxOutputBytes) {
118
+ outputLimitExceeded = true;
119
+ if (!error) {
120
+ error = {
121
+ code: PROCESS_OUTPUT_LIMIT,
122
+ message: `Process output exceeded limit of ${maxOutputBytes} bytes`,
123
+ };
124
+ }
125
+ terminateChild("SIGTERM");
126
+ return;
127
+ }
128
+ if (stream === "stdout")
129
+ stdoutChunks.push(chunk);
130
+ else if (stream === "stderr")
131
+ stderrChunks.push(chunk);
132
+ },
133
+ onExit: (exit) => {
134
+ childExited = true;
135
+ const durationMs = Date.now() - startTime;
136
+ const stdoutBuf = Buffer.concat(stdoutChunks);
137
+ const stderrBuf = Buffer.concat(writeErr
138
+ ? [...stderrChunks, Buffer.from(`\n[stdin write failed: ${writeErr.message}]`)]
139
+ : stderrChunks);
140
+ const stdoutStr = stdoutBuf.toString(encoding);
141
+ const stderrStr = stderrBuf.toString(encoding);
142
+ const ok = exit.code === 0 && !timedOut && !cancelled && !error;
143
+ finish({
144
+ ok,
145
+ exitCode: exit.code,
146
+ signal: exit.signal || undefined,
147
+ stdout: stdoutStr.trim(),
148
+ stderr: stderrStr.trim(),
149
+ raw: new Uint8Array(stdoutBuf),
150
+ timedOut,
151
+ cancelled,
152
+ durationMs,
153
+ error,
154
+ });
155
+ },
156
+ onError: (err) => {
157
+ childExited = true;
158
+ const durationMs = Date.now() - startTime;
159
+ const spawnError = error || {
160
+ code: PROCESS_SPAWN_ERROR,
161
+ message: err.message || "Failed to spawn process",
162
+ };
163
+ const stdoutBuf = Buffer.concat(stdoutChunks);
164
+ const stderrBuf = Buffer.concat(writeErr
165
+ ? [...stderrChunks, Buffer.from(`\n[stdin write failed: ${writeErr.message}]`)]
166
+ : stderrChunks);
167
+ finish({
168
+ ok: false,
169
+ exitCode: null,
170
+ signal: undefined,
171
+ stdout: stdoutBuf.toString(encoding).trim(),
172
+ stderr: stderrBuf.toString(encoding).trim() || spawnError.message,
173
+ raw: new Uint8Array(stdoutBuf),
174
+ timedOut,
175
+ cancelled,
176
+ durationMs,
177
+ error: spawnError,
178
+ });
179
+ },
180
+ };
181
+ const effectiveEnv = internal?.envAlreadyResolved
182
+ ? { inherit: "none", set: options.env }
183
+ : options.env
184
+ ? { inherit: "allowlisted", set: options.env }
185
+ : undefined;
186
+ const spec = {
187
+ executable: command,
188
+ args,
189
+ cwd: options.cwd,
190
+ env: effectiveEnv,
191
+ io: { mode: "pipe" },
192
+ };
193
+ if (options.signal) {
194
+ abortHandler = () => {
195
+ cancelled = true;
196
+ if (!error) {
197
+ error = {
198
+ code: PROCESS_CANCELLED,
199
+ message: "Process was cancelled by AbortSignal",
200
+ };
201
+ }
202
+ terminateChild("SIGTERM");
203
+ };
204
+ if (options.signal.aborted) {
205
+ abortHandler();
206
+ }
207
+ else {
208
+ options.signal.addEventListener("abort", abortHandler, { once: true });
209
+ }
210
+ }
211
+ if (options.timeoutMs && options.timeoutMs > 0) {
212
+ timeoutTimer = setTimeout(() => {
213
+ timedOut = true;
214
+ if (!error) {
215
+ error = {
216
+ code: PROCESS_TIMEOUT,
217
+ message: `Process timed out after ${options.timeoutMs}ms`,
218
+ };
219
+ }
220
+ terminateChild("SIGTERM");
221
+ }, options.timeoutMs);
222
+ }
223
+ try {
224
+ const observer = {
225
+ output: (stream, data) => callbacks.onOutput(stream, data),
226
+ exited: (exit) => callbacks.onExit(exit),
227
+ outputClosed: (reason) => callbacks.onOutputClosed?.(reason),
228
+ fault: (err) => callbacks.onError(err),
229
+ };
230
+ handle = (await this.driver.spawn(spec, observer));
231
+ if (cancelled || timedOut || outputLimitExceeded) {
232
+ terminateChild("SIGTERM");
233
+ }
234
+ }
235
+ catch (spawnErr) {
236
+ callbacks.onError(spawnErr);
237
+ return;
238
+ }
239
+ if (options.input !== undefined && options.input !== null && handle) {
240
+ const inputBytes = typeof options.input === "string"
241
+ ? new TextEncoder().encode(options.input)
242
+ : options.input;
243
+ handle
244
+ .write(inputBytes)
245
+ .then(() => handle?.sendInputEOF?.())
246
+ .catch((err) => {
247
+ writeErr = err;
248
+ });
249
+ }
250
+ });
251
+ }
252
+ async spawn(command, args = [], options = {}) {
253
+ return this.exec(command, args, options);
254
+ }
255
+ async run(input, call) {
256
+ const runExecutor = new RunExecutor(this.driver);
257
+ return runExecutor.execute(input, call);
258
+ }
259
+ async start(_input, _call) {
260
+ throw new ProcessError(UNSUPPORTED_CAPABILITY, "NodeProcessExecutor does not support managed process start; use ProcessManager with NodeProcessDriver");
261
+ }
262
+ async inspect(_id, _call) {
263
+ throw new ProcessError(UNSUPPORTED_CAPABILITY, "NodeProcessExecutor does not support managed process inspect");
264
+ }
265
+ async list(_input, _call) {
266
+ return { processes: [] };
267
+ }
268
+ async acquire(_id, _input, _call) {
269
+ throw new ProcessError(UNSUPPORTED_CAPABILITY, "NodeProcessExecutor does not support managed process acquire");
270
+ }
271
+ async renew(_id, _token, _ttlMs, _call) {
272
+ throw new ProcessError(UNSUPPORTED_CAPABILITY, "NodeProcessExecutor does not support managed process renew");
273
+ }
274
+ async release(_id, _token, _call) {
275
+ throw new ProcessError(UNSUPPORTED_CAPABILITY, "NodeProcessExecutor does not support managed process release");
276
+ }
277
+ async write(_id, _input, _call) {
278
+ throw new ProcessError(UNSUPPORTED_CAPABILITY, "NodeProcessExecutor does not support managed process write");
279
+ }
280
+ async operation(_id, _requestId, _call) {
281
+ throw new ProcessError(UNSUPPORTED_CAPABILITY, "NodeProcessExecutor does not support managed process operation");
282
+ }
283
+ async read(_id, _input, _call) {
284
+ throw new ProcessError(UNSUPPORTED_CAPABILITY, "NodeProcessExecutor does not support managed process read");
285
+ }
286
+ async control(_id, _input, _call) {
287
+ throw new ProcessError(UNSUPPORTED_CAPABILITY, "NodeProcessExecutor does not support managed process control");
288
+ }
289
+ async stop(_id, _input, _call) {
290
+ throw new ProcessError(UNSUPPORTED_CAPABILITY, "NodeProcessExecutor does not support managed process stop");
291
+ }
292
+ }
293
+ /**
294
+ * 兼容原有类名导出。
295
+ */
296
+ export { NodeProcessExecutor as ExecaProcessExecutor };
@@ -1,4 +1,4 @@
1
- import { createHash, randomUUID } from "node:crypto";
1
+ import { createHash } from "node:crypto";
2
2
  import { decodeBytes, encodeBytes, } from "@actiondock/sdk";
3
3
  import { ACCESS_DENIED, CONTROL_BUSY, CONTROL_REVOKED, INPUT_VALIDATION_FAILED, NOT_FOUND, PROCESS_CANCELLED, PROCESS_LOST, PROCESS_QUARANTINED, PROCESS_SPAWN_ERROR, QUOTA_EXCEEDED, REQUEST_CONFLICT, SERVER_ERROR, UNSUPPORTED_CAPABILITY, INVALID_CURSOR, OUTPUT_UNAVAILABLE, ProcessError, } from "../errors.js";
4
4
  import { parseCursor, compareCursorPos } from "./cursor.js";
@@ -145,7 +145,7 @@ export class ProcessManager {
145
145
  initPromise;
146
146
  isShutdown = false;
147
147
  constructor(options) {
148
- this.hostEpoch = options.hostEpoch ?? `epoch-${Date.now()}-${randomUUID().slice(0, 8)}`;
148
+ this.hostEpoch = options.hostEpoch ?? `epoch-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
149
149
  this.driver = options.driver;
150
150
  this.metadataStore = options.metadataStore ?? new MemoryProcessMetadataStore();
151
151
  this.quotas = {
@@ -321,7 +321,7 @@ export class ProcessManager {
321
321
  * 为指定所有者与运行上下文创建 ProcessAPI 代理适配器。
322
322
  */
323
323
  forOwner(owner, runId, signal) {
324
- return new ContextProcessAPI(this, owner, runId ?? randomUUID(), signal, Boolean(runId));
324
+ return new ContextProcessAPI(this, owner, runId ?? crypto.randomUUID(), signal, Boolean(runId));
325
325
  }
326
326
  /**
327
327
  * 启动新的受管进程资源。
@@ -362,7 +362,7 @@ export class ProcessManager {
362
362
  if (input.spec.io.mode === "pty" && !caps.pty) {
363
363
  throw new ProcessError(UNSUPPORTED_CAPABILITY, "Driver does not support PTY mode");
364
364
  }
365
- const processId = `proc-${randomUUID()}`;
365
+ const processId = `proc-${crypto.randomUUID()}`;
366
366
  const effectiveLimits = {
367
367
  idleMs: input.limits?.idleMs ?? this.defaultLimits.idleMs,
368
368
  lifetimeMs: input.limits?.lifetimeMs ?? this.defaultLimits.lifetimeMs,
@@ -402,17 +402,18 @@ export class ProcessManager {
402
402
  // 调用驱动派生进程
403
403
  let abortListener;
404
404
  try {
405
- const callbacks = {
406
- onOutput: (stream, data) => {
405
+ const observer = {
406
+ processId,
407
+ output: (stream, data) => {
407
408
  procRecord.outputLog.append(stream, data);
408
409
  },
409
- onExit: (exit) => {
410
+ exited: (exit) => {
410
411
  this.handleProcessExit(procRecord, exit);
411
412
  },
412
- onOutputClosed: (reason) => {
413
+ outputClosed: (reason) => {
413
414
  this.handleOutputClosed(procRecord, reason);
414
415
  },
415
- onError: (err) => {
416
+ fault: (err) => {
416
417
  this.handleProcessError(procRecord, err);
417
418
  },
418
419
  };
@@ -421,7 +422,7 @@ export class ProcessManager {
421
422
  ? call.signal.reason
422
423
  : new ProcessError(PROCESS_CANCELLED, "Process start was cancelled");
423
424
  }
424
- const spawnPromise = this.driver.spawn(processId, input.spec, callbacks);
425
+ const spawnPromise = this.driver.spawn(input.spec, observer);
425
426
  if (call?.signal) {
426
427
  const abortPromise = new Promise((_, reject) => {
427
428
  abortListener = () => {
@@ -432,13 +433,13 @@ export class ProcessManager {
432
433
  call.signal?.addEventListener("abort", abortListener, { once: true });
433
434
  });
434
435
  try {
435
- procRecord.handle = await Promise.race([spawnPromise, abortPromise]);
436
+ procRecord.handle = (await Promise.race([spawnPromise, abortPromise]));
436
437
  }
437
438
  catch (raceErr) {
438
439
  void spawnPromise
439
440
  .then((h) => {
440
- if (h && typeof h.terminate === "function") {
441
- void h.terminate(0);
441
+ if (h) {
442
+ void this.driver.terminate(h, 0);
442
443
  }
443
444
  })
444
445
  .catch(() => { });
@@ -451,11 +452,11 @@ export class ProcessManager {
451
452
  }
452
453
  }
453
454
  else {
454
- procRecord.handle = await spawnPromise;
455
+ procRecord.handle = (await spawnPromise);
455
456
  }
456
457
  if (call?.signal?.aborted) {
457
- if (procRecord.handle && typeof procRecord.handle.terminate === "function") {
458
- await procRecord.handle.terminate(0);
458
+ if (procRecord.handle) {
459
+ await this.driver.terminate(procRecord.handle, 0);
459
460
  }
460
461
  throw call.signal.reason instanceof ProcessError
461
462
  ? call.signal.reason
@@ -510,8 +511,14 @@ export class ProcessManager {
510
511
  this.commitReservation(key, "start", startResult);
511
512
  return startResult;
512
513
  }
514
+ catch (err) {
515
+ // 失败路径:以原始错误结算预占,等待同 requestId 的并发调用方收到真实失败原因而非固定冲突错误
516
+ this.rejectReservation(key, "start", err);
517
+ throw err;
518
+ }
513
519
  finally {
514
- // 成功路径已在 commit 中移除预占;此处仅对异常路径释放并唤醒等待方
520
+ // 成功路径已在 commit 中移除预占;失败路径已在 catch 中以原始错误结算;
521
+ // 此处仅为既未 commit 也未 reject 的异常逃逸路径释放预占并唤醒等待方
515
522
  this.rejectReservation(key, "start", new ProcessError(REQUEST_CONFLICT, "Concurrent start request did not produce a result", { requestId: input.requestId }));
516
523
  }
517
524
  }
@@ -600,8 +607,14 @@ export class ProcessManager {
600
607
  this.commitReservation(key, "acquire", grant);
601
608
  return grant;
602
609
  }
610
+ catch (err) {
611
+ // 失败路径:以原始错误结算预占,等待同 requestId 的并发调用方收到真实失败原因而非固定冲突错误
612
+ this.rejectReservation(key, "acquire", err);
613
+ throw err;
614
+ }
603
615
  finally {
604
- // 成功路径已在 commit 中移除预占;此处仅对异常路径释放并唤醒等待方
616
+ // 成功路径已在 commit 中移除预占;失败路径已在 catch 中以原始错误结算;
617
+ // 此处仅为既未 commit 也未 reject 的异常逃逸路径释放预占并唤醒等待方
605
618
  this.rejectReservation(key, "acquire", new ProcessError(REQUEST_CONFLICT, "Concurrent acquire request did not produce a result", { requestId: input.requestId }));
606
619
  }
607
620
  }
@@ -682,8 +695,14 @@ export class ProcessManager {
682
695
  this.commitReservation(key, "write", queued);
683
696
  return queued;
684
697
  }
698
+ catch (err) {
699
+ // 失败路径:以原始错误结算预占,等待同 requestId 的并发调用方收到真实失败原因而非固定冲突错误
700
+ this.rejectReservation(key, "write", err);
701
+ throw err;
702
+ }
685
703
  finally {
686
- // 成功路径已在 commit 中移除预占;此处仅对异常路径释放并唤醒等待方
704
+ // 成功路径已在 commit 中移除预占;失败路径已在 catch 中以原始错误结算;
705
+ // 此处仅为既未 commit 也未 reject 的异常逃逸路径释放预占并唤醒等待方
687
706
  this.rejectReservation(key, "write", new ProcessError(REQUEST_CONFLICT, "Concurrent write request did not produce a result", { requestId: input.requestId }));
688
707
  }
689
708
  }
@@ -741,8 +760,14 @@ export class ProcessManager {
741
760
  this.commitReservation(key, "control", queued);
742
761
  return queued;
743
762
  }
763
+ catch (err) {
764
+ // 失败路径:以原始错误结算预占,等待同 requestId 的并发调用方收到真实失败原因而非固定冲突错误
765
+ this.rejectReservation(key, "control", err);
766
+ throw err;
767
+ }
744
768
  finally {
745
- // 成功路径已在 commit 中移除预占;此处仅对异常路径释放并唤醒等待方
769
+ // 成功路径已在 commit 中移除预占;失败路径已在 catch 中以原始错误结算;
770
+ // 此处仅为既未 commit 也未 reject 的异常逃逸路径释放预占并唤醒等待方
746
771
  this.rejectReservation(key, "control", new ProcessError(REQUEST_CONFLICT, "Concurrent control request did not produce a result", { requestId: input.requestId }));
747
772
  }
748
773
  }
@@ -874,10 +899,7 @@ export class ProcessManager {
874
899
  // 调用底层驱动终止;真实退出状态与输出流关闭由底层观察者事件收敛
875
900
  try {
876
901
  if (proc.handle) {
877
- await proc.handle.terminate(input.graceMs);
878
- }
879
- else if (this.driver.terminate) {
880
- await this.driver.terminate(id, input.graceMs);
902
+ await this.driver.terminate(proc.handle, input.graceMs);
881
903
  }
882
904
  }
883
905
  catch (err) {
@@ -981,7 +1003,7 @@ export class ProcessManager {
981
1003
  }
982
1004
  proc.info.endReason = "idle";
983
1005
  this.stop(proc.owner, proc.info.id, {
984
- requestId: `idle-${randomUUID()}`,
1006
+ requestId: `idle-${crypto.randomUUID()}`,
985
1007
  graceMs: 1000,
986
1008
  }).catch((err) => {
987
1009
  this.recordDiagnostic(`Idle timeout stop failed for process '${proc.info.id}'`, err);
@@ -998,7 +1020,7 @@ export class ProcessManager {
998
1020
  }
999
1021
  proc.info.endReason = "lifetime";
1000
1022
  this.stop(proc.owner, proc.info.id, {
1001
- requestId: `lifetime-${randomUUID()}`,
1023
+ requestId: `lifetime-${crypto.randomUUID()}`,
1002
1024
  graceMs: 1000,
1003
1025
  }).catch((err) => {
1004
1026
  this.recordDiagnostic(`Lifetime timeout stop failed for process '${proc.info.id}'`, err);
@@ -1,4 +1,3 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import { encodeBytes, } from "@actiondock/sdk";
3
2
  import { PROCESS_CANCELLED, PROCESS_TIMEOUT, UNSUPPORTED_CAPABILITY, ProcessError, } from "../errors.js";
4
3
  /**
@@ -27,7 +26,7 @@ export class RunExecutor {
27
26
  mode: "pipe",
28
27
  },
29
28
  };
30
- const runProcessId = `run-${randomUUID()}`;
29
+ const runProcessId = `run-${crypto.randomUUID()}`;
31
30
  const chunks = [];
32
31
  let totalBytes = 0;
33
32
  let truncated = false;
@@ -40,8 +39,8 @@ export class RunExecutor {
40
39
  rejectError = reject;
41
40
  });
42
41
  let driverHandle;
43
- const callbacks = {
44
- onOutput: (stream, data) => {
42
+ const observer = {
43
+ output: (stream, data) => {
45
44
  if (truncated)
46
45
  return;
47
46
  if (stream !== "stdout" && stream !== "stderr")
@@ -50,7 +49,7 @@ export class RunExecutor {
50
49
  if (remaining <= 0) {
51
50
  truncated = true;
52
51
  if (driverHandle) {
53
- void driverHandle.terminate(1000);
52
+ void this.driver.terminate(driverHandle, 1000);
54
53
  }
55
54
  return;
56
55
  }
@@ -63,7 +62,7 @@ export class RunExecutor {
63
62
  totalBytes += slice.byteLength;
64
63
  truncated = true;
65
64
  if (driverHandle) {
66
- void driverHandle.terminate(1000);
65
+ void this.driver.terminate(driverHandle, 1000);
67
66
  }
68
67
  }
69
68
  else {
@@ -74,10 +73,11 @@ export class RunExecutor {
74
73
  totalBytes += data.byteLength;
75
74
  }
76
75
  },
77
- onExit: (exit) => {
76
+ exited: (exit) => {
78
77
  resolveExit(exit);
79
78
  },
80
- onError: (err) => {
79
+ outputClosed: () => { },
80
+ fault: (err) => {
81
81
  rejectError(err);
82
82
  },
83
83
  };
@@ -87,13 +87,13 @@ export class RunExecutor {
87
87
  ? call.signal.reason
88
88
  : new ProcessError(PROCESS_CANCELLED, "Process run was cancelled");
89
89
  }
90
- driverHandle = await this.driver.spawn(runProcessId, effectiveSpec, callbacks);
90
+ driverHandle = await this.driver.spawn(effectiveSpec, observer);
91
91
  let timeoutTimer;
92
92
  if (input.timeoutMs > 0) {
93
93
  timeoutTimer = setTimeout(() => {
94
94
  timedOut = true;
95
95
  if (driverHandle) {
96
- void driverHandle.terminate(1000);
96
+ void this.driver.terminate(driverHandle, 1000);
97
97
  }
98
98
  }, input.timeoutMs);
99
99
  if (typeof timeoutTimer?.unref === "function") {
@@ -106,7 +106,7 @@ export class RunExecutor {
106
106
  if (timeoutTimer)
107
107
  clearTimeout(timeoutTimer);
108
108
  if (driverHandle)
109
- void driverHandle.terminate(1000);
109
+ void this.driver.terminate(driverHandle, 1000);
110
110
  throw call.signal.reason ?? new ProcessError(PROCESS_CANCELLED, "Process run was cancelled");
111
111
  }
112
112
  onAbort = () => {
@@ -114,7 +114,7 @@ export class RunExecutor {
114
114
  if (timeoutTimer)
115
115
  clearTimeout(timeoutTimer);
116
116
  if (driverHandle)
117
- void driverHandle.terminate(1000);
117
+ void this.driver.terminate(driverHandle, 1000);
118
118
  };
119
119
  call.signal.addEventListener("abort", onAbort, { once: true });
120
120
  }
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { ACTION_CANCELLED, ACTION_TIMEOUT, NETWORK_ERROR } from "../errors.js";
3
3
  import { getInsecureDispatcher } from "../server/dispatcher.js";
4
4
  import { normalizeServerUrl } from "./manager.js";
5
- import { assertSecureTransport, buildHeaders, fetchWithProtocolFallback, } from "./client-transport.js";
5
+ import { assertSecureTransport, buildHeaders, fetchRemoteRoute, } from "./client-transport.js";
6
6
  import { buildQueryString, fetchRemoteJson } from "./client-query.js";
7
7
  /**
8
8
  * 执行远端 Action:支持幂等标识、异步触发、外部取消信号与本地超时守卫组合。
@@ -101,7 +101,7 @@ export async function executeRemoteAction(serverUrl, actionId, input = {}, confi
101
101
  }
102
102
  let res;
103
103
  try {
104
- res = await fetchWithProtocolFallback(normalizeServerUrl(serverUrl), `/api/v2/actions/${encodeURIComponent(actionId)}/run`, fetchInit);
104
+ res = await fetchRemoteRoute(normalizeServerUrl(serverUrl), `/api/v2/actions/${encodeURIComponent(actionId)}/run`, fetchInit);
105
105
  }
106
106
  finally {
107
107
  if (timeoutTimer !== undefined) {
@@ -1,5 +1,5 @@
1
1
  import { normalizeServerUrl } from "./manager.js";
2
- import { assertSecureTransport, buildHeaders, fetchWithProtocolFallback, } from "./client-transport.js";
2
+ import { assertSecureTransport, buildHeaders, fetchRemoteRoute, } from "./client-transport.js";
3
3
  import { getInsecureDispatcher } from "../server/dispatcher.js";
4
4
  /**
5
5
  * 远端健康检查域端点。
@@ -34,7 +34,7 @@ export async function checkRemoteHealth(serverUrl, token, timeoutMs = 5000, opti
34
34
  fetchInit.dispatcher = getInsecureDispatcher();
35
35
  fetchInit.tls = { rejectUnauthorized: false };
36
36
  }
37
- const res = await fetchWithProtocolFallback(normalizeServerUrl(serverUrl), "/api/v2/health", fetchInit);
37
+ const res = await fetchRemoteRoute(normalizeServerUrl(serverUrl), "/api/v2/health", fetchInit);
38
38
  const latencyMs = Date.now() - startTime;
39
39
  if (!res.ok) {
40
40
  const text = await res.text().catch(() => "");
@@ -1,4 +1,5 @@
1
1
  import { createRemoteFetch } from "./client-transport.js";
2
+ import { ActionDockError, NOT_FOUND, REMOTE_REQUEST_FAILED, UNAUTHORIZED } from "../errors.js";
2
3
  /**
3
4
  * 远端端点函数共享的请求与查询构造层。
4
5
  *
@@ -23,13 +24,11 @@ export async function fetchRemoteJson(serverUrl, path, token, options = {}) {
23
24
  if (!res.ok || (options.method === "POST" && data && data.ok === false)) {
24
25
  const errorPrefix = options.errorPrefix || "Remote request failed";
25
26
  const msg = data?.error?.message || `${errorPrefix} (${res.status}): ${res.statusText}`;
26
- const err = new Error(msg);
27
- if (data?.error?.code) {
28
- err.code = data.error.code;
29
- }
30
- err.status = res.status;
27
+ const code = data?.error?.code ||
28
+ (res.status === 404 ? NOT_FOUND : res.status === 401 ? UNAUTHORIZED : REMOTE_REQUEST_FAILED);
29
+ const details = data?.error?.details ?? data?.error;
30
+ const err = new ActionDockError(code, msg, details, res.status);
31
31
  err.errorData = data?.error;
32
- err.details = data?.error?.details ?? data?.error;
33
32
  throw err;
34
33
  }
35
34
  return data;