@actiondock/core 2.2.1 → 2.3.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.
- package/README.md +8 -3
- package/dist/app/app.d.ts +2 -0
- package/dist/app/app.js +6 -0
- package/dist/app/types.d.ts +12 -0
- package/dist/errors.d.ts +38 -0
- package/dist/errors.js +44 -0
- package/dist/execution/service.d.ts +2 -0
- package/dist/execution/service.js +13 -2
- package/dist/execution/types.d.ts +17 -0
- package/dist/host/host.js +46 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/ipc/host.d.ts +5 -0
- package/dist/ipc/host.js +43 -1
- package/dist/ipc/target.d.ts +18 -0
- package/dist/ipc/target.js +74 -6
- package/dist/ipc/types.d.ts +10 -1
- package/dist/platform/default.d.ts +7 -1
- package/dist/platform/default.js +40 -2
- package/dist/process/context-process.d.ts +83 -0
- package/dist/process/context-process.js +168 -0
- package/dist/process/cursor.d.ts +52 -0
- package/dist/process/cursor.js +144 -0
- package/dist/process/driver.d.ts +214 -0
- package/dist/process/driver.js +197 -0
- package/dist/process/index.d.ts +6 -0
- package/dist/process/index.js +6 -0
- package/dist/process/metadata-store.d.ts +205 -0
- package/dist/process/metadata-store.js +462 -0
- package/dist/process/output-log.d.ts +136 -0
- package/dist/process/output-log.js +331 -0
- package/dist/process/process-manager.d.ts +279 -0
- package/dist/process/process-manager.js +1879 -0
- package/dist/profile/client.js +63 -21
- package/dist/project/init.js +2 -2
- package/dist/registry/registry.js +12 -3
- package/dist/runtime/context.d.ts +9 -6
- package/dist/runtime/context.js +31 -8
- package/dist/runtime/process.d.ts +19 -2
- package/dist/runtime/process.js +103 -131
- package/dist/runtime/runner.d.ts +45 -24
- package/dist/runtime/runner.js +106 -25
- package/dist/target/remote.js +7 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/profile/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { normalizeServerUrl } from "./manager.js";
|
|
3
|
-
import { ACTION_CANCELLED, NETWORK_ERROR } from "../errors.js";
|
|
3
|
+
import { ACTION_CANCELLED, ACTION_TIMEOUT, NETWORK_ERROR } from "../errors.js";
|
|
4
4
|
import { isLoopbackHost } from "../server/security.js";
|
|
5
5
|
/**
|
|
6
6
|
* 校验在携带认证 Token 时传输层协议是否安全。
|
|
@@ -154,6 +154,9 @@ export async function executeRemoteAction(serverUrl, actionId, input = {}, confi
|
|
|
154
154
|
if (requestId) {
|
|
155
155
|
executionPayload.requestId = requestId;
|
|
156
156
|
}
|
|
157
|
+
// 组合外部取消信号与本地超时守卫:服务端僵死时仍能在 timeoutMs 内本地中断,避免永久挂起
|
|
158
|
+
let timeoutTimer;
|
|
159
|
+
let localTimedOut = false;
|
|
157
160
|
try {
|
|
158
161
|
const headers = {
|
|
159
162
|
...buildHeaders(token),
|
|
@@ -168,34 +171,62 @@ export async function executeRemoteAction(serverUrl, actionId, input = {}, confi
|
|
|
168
171
|
config: configOverrides,
|
|
169
172
|
execution: Object.keys(executionPayload).length > 0 ? executionPayload : undefined,
|
|
170
173
|
});
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
174
|
+
const controller = new AbortController();
|
|
175
|
+
const onExternalAbort = () => controller.abort(signal?.reason);
|
|
176
|
+
if (signal) {
|
|
177
|
+
if (signal.aborted) {
|
|
178
|
+
controller.abort(signal.reason);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
signal.addEventListener("abort", onExternalAbort, { once: true });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (typeof timeoutMs === "number" && timeoutMs > 0) {
|
|
185
|
+
timeoutTimer = setTimeout(() => {
|
|
186
|
+
localTimedOut = true;
|
|
187
|
+
controller.abort();
|
|
188
|
+
}, timeoutMs);
|
|
189
|
+
}
|
|
190
|
+
let res;
|
|
191
|
+
try {
|
|
192
|
+
res = await fetch(v2Url, {
|
|
193
|
+
method: "POST",
|
|
194
|
+
headers,
|
|
195
|
+
body: reqBody,
|
|
196
|
+
signal: controller.signal,
|
|
197
|
+
});
|
|
198
|
+
if (res.status === 404) {
|
|
199
|
+
try {
|
|
200
|
+
const v1Res = await fetch(`${base}/api/v1/actions/${encodeURIComponent(actionId)}/run`, {
|
|
201
|
+
method: "POST",
|
|
202
|
+
headers,
|
|
203
|
+
body: reqBody,
|
|
204
|
+
signal: controller.signal,
|
|
205
|
+
});
|
|
206
|
+
if (v1Res.ok || v1Res.status !== 404) {
|
|
207
|
+
res = v1Res;
|
|
208
|
+
}
|
|
187
209
|
}
|
|
210
|
+
catch { }
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
if (timeoutTimer !== undefined) {
|
|
215
|
+
clearTimeout(timeoutTimer);
|
|
216
|
+
}
|
|
217
|
+
if (signal) {
|
|
218
|
+
signal.removeEventListener("abort", onExternalAbort);
|
|
188
219
|
}
|
|
189
|
-
catch { }
|
|
190
220
|
}
|
|
191
221
|
const data = (await res.json().catch(() => null));
|
|
192
222
|
if (data && typeof data === "object" && typeof data.ok === "boolean") {
|
|
193
223
|
return data;
|
|
194
224
|
}
|
|
195
225
|
if (!res.ok) {
|
|
226
|
+
// 错误信封严禁伪造 runId:与任何真实运行无关的标识会让调用方查询永远 not_found
|
|
196
227
|
return {
|
|
197
228
|
ok: false,
|
|
198
|
-
runId:
|
|
229
|
+
runId: "",
|
|
199
230
|
error: {
|
|
200
231
|
code: res.status === 401 ? "UNAUTHORIZED" : "REMOTE_EXECUTION_FAILED",
|
|
201
232
|
message: `Remote server HTTP ${res.status}: ${res.statusText}`,
|
|
@@ -210,10 +241,21 @@ export async function executeRemoteAction(serverUrl, actionId, input = {}, confi
|
|
|
210
241
|
};
|
|
211
242
|
}
|
|
212
243
|
catch (err) {
|
|
244
|
+
// 本地超时守卫触发时归类为超时;外部信号中止时归类为取消
|
|
245
|
+
if (localTimedOut) {
|
|
246
|
+
return {
|
|
247
|
+
ok: false,
|
|
248
|
+
runId: "",
|
|
249
|
+
error: {
|
|
250
|
+
code: ACTION_TIMEOUT,
|
|
251
|
+
message: `Remote execution exceeded local timeout of ${timeoutMs}ms`,
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
213
255
|
if (err.name === "AbortError" || signal?.aborted) {
|
|
214
256
|
return {
|
|
215
257
|
ok: false,
|
|
216
|
-
runId:
|
|
258
|
+
runId: "",
|
|
217
259
|
error: {
|
|
218
260
|
code: ACTION_CANCELLED,
|
|
219
261
|
message: "Action execution was cancelled",
|
|
@@ -222,7 +264,7 @@ export async function executeRemoteAction(serverUrl, actionId, input = {}, confi
|
|
|
222
264
|
}
|
|
223
265
|
return {
|
|
224
266
|
ok: false,
|
|
225
|
-
runId:
|
|
267
|
+
runId: "",
|
|
226
268
|
error: {
|
|
227
269
|
code: NETWORK_ERROR,
|
|
228
270
|
message: `Failed to connect to remote ActionDock server at ${serverUrl}: ${err.message}`,
|
package/dist/project/init.js
CHANGED
|
@@ -86,10 +86,10 @@ export function initProject(targetDir, options = {}) {
|
|
|
86
86
|
node: ">=24.12.0",
|
|
87
87
|
},
|
|
88
88
|
dependencies: {
|
|
89
|
-
"@actiondock/sdk": "^2.
|
|
89
|
+
"@actiondock/sdk": "^2.3.0",
|
|
90
90
|
},
|
|
91
91
|
devDependencies: {
|
|
92
|
-
"@actiondock/testing": "^2.
|
|
92
|
+
"@actiondock/testing": "^2.3.0",
|
|
93
93
|
"@types/node": "^22.13.0",
|
|
94
94
|
"tsx": "^4.19.0",
|
|
95
95
|
"typescript": "^5.7.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, renameSync, statSync } from "node:fs";
|
|
2
2
|
import { readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
4
4
|
import { findProjectRoot, loadProjectConfig } from "../project/loader.js";
|
|
5
5
|
import { getActionDockHome, getPackageSlug } from "../utils/index.js";
|
|
6
6
|
import { withRegistryLock } from "./lock.js";
|
|
@@ -12,6 +12,13 @@ import { probeActionAsync, probeActionSync, probePlaybook, resolveEntityFlow, ru
|
|
|
12
12
|
function emptyRegistry() {
|
|
13
13
|
return { version: "2.0.0", packages: {}, workspaces: {} };
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* 判定 entryPath 是否严格位于 baseDir 目录之内(携带路径分隔符边界)。
|
|
17
|
+
* 避免裸 startsWith 前缀匹配把兄弟目录(如 /home/ws 与 /home/ws2)误判为子路径。
|
|
18
|
+
*/
|
|
19
|
+
function isPathWithin(entryPath, baseDir) {
|
|
20
|
+
return entryPath === baseDir || entryPath.startsWith(baseDir + sep);
|
|
21
|
+
}
|
|
15
22
|
/**
|
|
16
23
|
* 注册表内容解析与 links 迁移(纯函数,同步与异步加载共享的唯一事实源)。
|
|
17
24
|
*
|
|
@@ -304,7 +311,8 @@ export async function unlinkPackage(identifier = process.cwd(), customHome) {
|
|
|
304
311
|
delete registry.workspaces[absPath];
|
|
305
312
|
let removedCount = 0;
|
|
306
313
|
for (const [id, entry] of Object.entries(registry.packages)) {
|
|
307
|
-
|
|
314
|
+
// 路径前缀匹配必须携带分隔符边界,避免 /home/ws 误删 /home/ws2 的记录
|
|
315
|
+
if (isPathWithin(entry.path, absPath) || entry.workspaceRoot === absPath) {
|
|
308
316
|
delete registry.packages[id];
|
|
309
317
|
removedCount++;
|
|
310
318
|
}
|
|
@@ -325,7 +333,8 @@ export async function unlinkPackage(identifier = process.cwd(), customHome) {
|
|
|
325
333
|
delete registry.workspaces[wsPath];
|
|
326
334
|
let removedCount = 0;
|
|
327
335
|
for (const [id, entry] of Object.entries(registry.packages)) {
|
|
328
|
-
|
|
336
|
+
// 路径前缀匹配必须携带分隔符边界,避免 /home/ws 误删 /home/ws2 的记录
|
|
337
|
+
if (isPathWithin(entry.path, wsPath) || entry.workspaceRoot === wsPath) {
|
|
329
338
|
delete registry.packages[id];
|
|
330
339
|
removedCount++;
|
|
331
340
|
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import type { ActionContext, ActionRef, Config, Logger, ProcessAPI, ProgressReporter, StateStore } from "@actiondock/sdk";
|
|
2
2
|
import type { ProjectConfig } from "../project/types.js";
|
|
3
3
|
import type { RuntimeStorage } from "../storage/types.js";
|
|
4
|
+
import { ProcessManager, type ProcessOwner } from "../process/index.js";
|
|
4
5
|
/**
|
|
5
6
|
* 生产级配置解析器实现。
|
|
6
7
|
* 严格践行 5 层配置解析优先级链:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* - CLI 临时参数覆写 (Overrides)
|
|
9
|
+
* - 包级持久化存储 (Package SQLite: ~/.actiondock/data/<package-id>/runtime.db)
|
|
10
|
+
* - 全局共享持久化存储 (Global SQLite: ~/.actiondock/global.db)
|
|
11
|
+
* - 操作系统环境变量 (process.env: 显式绑定 / 包名前缀 / SNAKE_CASE / 类型转换)
|
|
12
|
+
* - 项目默认配置 (actiondock.json 中的 default 字段)
|
|
13
|
+
* - 代码级默认回退值 (fallback)
|
|
13
14
|
*/
|
|
14
15
|
export declare class RuntimeConfig implements Config {
|
|
15
16
|
private overrides;
|
|
@@ -73,6 +74,8 @@ export interface ContextOptions {
|
|
|
73
74
|
callStack?: string[];
|
|
74
75
|
signal?: AbortSignal;
|
|
75
76
|
process?: ProcessAPI;
|
|
77
|
+
processManager?: ProcessManager;
|
|
78
|
+
owner?: ProcessOwner;
|
|
76
79
|
progress?: ProgressReporter;
|
|
77
80
|
logger?: Logger;
|
|
78
81
|
onActionInvoke?: (action: ActionRef | string, input: unknown, parentRunId?: string) => Promise<unknown>;
|
package/dist/runtime/context.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { MemoryProcessDriver, ProcessManager } from "../process/index.js";
|
|
2
3
|
import { resolveEnvValue } from "./env.js";
|
|
3
|
-
import { DefaultProcessExecutor } from "./process.js";
|
|
4
4
|
/**
|
|
5
5
|
* 生产级配置解析器实现。
|
|
6
6
|
* 严格践行 5 层配置解析优先级链:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* - CLI 临时参数覆写 (Overrides)
|
|
8
|
+
* - 包级持久化存储 (Package SQLite: ~/.actiondock/data/<package-id>/runtime.db)
|
|
9
|
+
* - 全局共享持久化存储 (Global SQLite: ~/.actiondock/global.db)
|
|
10
|
+
* - 操作系统环境变量 (process.env: 显式绑定 / 包名前缀 / SNAKE_CASE / 类型转换)
|
|
11
|
+
* - 项目默认配置 (actiondock.json 中的 default 字段)
|
|
12
|
+
* - 代码级默认回退值 (fallback)
|
|
13
13
|
*/
|
|
14
14
|
export class RuntimeConfig {
|
|
15
15
|
overrides;
|
|
@@ -221,7 +221,30 @@ export function createActionContext(options) {
|
|
|
221
221
|
};
|
|
222
222
|
const invokerFn = (ref, input) => invoke(ref, input);
|
|
223
223
|
const invoker = Object.assign(invokerFn, { invoke });
|
|
224
|
-
const
|
|
224
|
+
const defaultOwner = {
|
|
225
|
+
tenantId: "default",
|
|
226
|
+
principalId: "default",
|
|
227
|
+
packageInstanceId: "default",
|
|
228
|
+
generationId: "default",
|
|
229
|
+
};
|
|
230
|
+
const effectiveOwner = options.owner || defaultOwner;
|
|
231
|
+
// 外部注入的可能是平台级共享 ContextProcessAPI(未绑定 runId),直接复用会让
|
|
232
|
+
// 多个 run 共享同一隔离与回收状态;此处派生 run 级实例保证隔离与自动回收均以 run 为单位
|
|
233
|
+
const injectedProcess = options.process;
|
|
234
|
+
const isSharedContextProcessApi = injectedProcess &&
|
|
235
|
+
typeof injectedProcess === "object" &&
|
|
236
|
+
typeof injectedProcess.forOwner !== "function" &&
|
|
237
|
+
injectedProcess.manager &&
|
|
238
|
+
typeof injectedProcess.manager.forOwner === "function" &&
|
|
239
|
+
injectedProcess.runScoped !== true;
|
|
240
|
+
const processApi = injectedProcess && typeof injectedProcess.forOwner === "function"
|
|
241
|
+
? injectedProcess.forOwner(effectiveOwner, currentRunId, signal)
|
|
242
|
+
: isSharedContextProcessApi
|
|
243
|
+
? injectedProcess.manager.forOwner(effectiveOwner, currentRunId, signal)
|
|
244
|
+
: options.process ||
|
|
245
|
+
(options.processManager
|
|
246
|
+
? options.processManager.forOwner(effectiveOwner, currentRunId, signal)
|
|
247
|
+
: new ProcessManager({ driver: new MemoryProcessDriver() }).forOwner(effectiveOwner, currentRunId, signal));
|
|
225
248
|
const progressApi = options.progress || {
|
|
226
249
|
report() { },
|
|
227
250
|
};
|
|
@@ -1,9 +1,26 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CallOptions, type ControlGrant, type OperationReceipt, type ProcessAcquireInput, type ProcessAPI, type ProcessControlInput, type ProcessExecOptions, type ProcessInfo, type ProcessListInput, type ProcessListResult, type ProcessReadInput, type ProcessResult, type ProcessRunInput, type ProcessRunResult, type ProcessStartInput, type ProcessStartResult, type ProcessStopInput, type ProcessWriteInput, type ReadResult } from "@actiondock/sdk";
|
|
2
|
+
import { ProcessManager, type ProcessOwner } from "../process/index.js";
|
|
2
3
|
export type ProcessExecutor = ProcessAPI;
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
+
* 基于受管进程驱动的默认基础执行器实现。
|
|
6
|
+
* 保持内核彻底解耦与平台无关,不依赖操作系统原生进程句柄与 Node 模块。
|
|
5
7
|
*/
|
|
6
8
|
export declare class DefaultProcessExecutor implements ProcessExecutor {
|
|
9
|
+
private readonly manager;
|
|
10
|
+
private readonly owner;
|
|
11
|
+
constructor(manager?: ProcessManager, owner?: ProcessOwner);
|
|
7
12
|
exec(command: string, args?: string[], options?: ProcessExecOptions): Promise<ProcessResult>;
|
|
8
13
|
spawn(command: string, args?: string[], options?: ProcessExecOptions): Promise<ProcessResult>;
|
|
14
|
+
run(input: ProcessRunInput, call?: CallOptions): Promise<ProcessRunResult>;
|
|
15
|
+
start(input: ProcessStartInput, call?: CallOptions): Promise<ProcessStartResult>;
|
|
16
|
+
inspect(id: string, call?: CallOptions): Promise<ProcessInfo>;
|
|
17
|
+
list(input: ProcessListInput, call?: CallOptions): Promise<ProcessListResult>;
|
|
18
|
+
acquire(id: string, input: ProcessAcquireInput, call?: CallOptions): Promise<ControlGrant>;
|
|
19
|
+
renew(id: string, token: string, ttlMs: number, call?: CallOptions): Promise<ControlGrant>;
|
|
20
|
+
release(id: string, token: string, call?: CallOptions): Promise<void>;
|
|
21
|
+
write(id: string, input: ProcessWriteInput, call?: CallOptions): Promise<OperationReceipt>;
|
|
22
|
+
operation(id: string, requestId: string, call?: CallOptions): Promise<OperationReceipt>;
|
|
23
|
+
read(id: string, input: ProcessReadInput, call?: CallOptions): Promise<ReadResult>;
|
|
24
|
+
control(id: string, input: ProcessControlInput, call?: CallOptions): Promise<OperationReceipt>;
|
|
25
|
+
stop(id: string, input: ProcessStopInput, call?: CallOptions): Promise<ProcessInfo>;
|
|
9
26
|
}
|
package/dist/runtime/process.js
CHANGED
|
@@ -1,146 +1,118 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { PROCESS_OUTPUT_LIMIT, PROCESS_SPAWN_ERROR } from "../errors.js";
|
|
1
|
+
import { MemoryProcessDriver, ProcessManager } from "../process/index.js";
|
|
3
2
|
/**
|
|
4
|
-
*
|
|
3
|
+
* 基于受管进程驱动的默认基础执行器实现。
|
|
4
|
+
* 保持内核彻底解耦与平台无关,不依赖操作系统原生进程句柄与 Node 模块。
|
|
5
5
|
*/
|
|
6
6
|
export class DefaultProcessExecutor {
|
|
7
|
+
manager;
|
|
8
|
+
owner;
|
|
9
|
+
constructor(manager, owner) {
|
|
10
|
+
this.manager = manager ?? new ProcessManager({ driver: new MemoryProcessDriver() });
|
|
11
|
+
this.owner = owner ?? {
|
|
12
|
+
tenantId: "default",
|
|
13
|
+
principalId: "default",
|
|
14
|
+
packageInstanceId: "default",
|
|
15
|
+
generationId: "default",
|
|
16
|
+
};
|
|
17
|
+
}
|
|
7
18
|
async exec(command, args = [], options = {}) {
|
|
8
19
|
const startTime = Date.now();
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
try {
|
|
21
|
+
const runResult = await this.manager.run(this.owner, {
|
|
22
|
+
spec: {
|
|
23
|
+
executable: command,
|
|
24
|
+
args,
|
|
25
|
+
cwd: options.cwd,
|
|
26
|
+
env: options.env ? { inherit: "none", set: options.env } : undefined,
|
|
27
|
+
io: { mode: "pipe" },
|
|
28
|
+
},
|
|
29
|
+
timeoutMs: options.timeoutMs ?? 0,
|
|
30
|
+
maxOutputBytes: options.maxOutputBytes ?? 10 * 1024 * 1024,
|
|
31
|
+
}, options.signal ? { signal: options.signal } : undefined);
|
|
32
|
+
let stdout = "";
|
|
33
|
+
let stderr = "";
|
|
34
|
+
for (const chunk of runResult.chunks) {
|
|
35
|
+
if (chunk.stream === "stdout") {
|
|
36
|
+
stdout += chunk.data.data;
|
|
37
|
+
}
|
|
38
|
+
else if (chunk.stream === "stderr") {
|
|
39
|
+
stderr += chunk.data.data;
|
|
40
|
+
}
|
|
28
41
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
timedOut = true;
|
|
34
|
-
cp.kill("SIGTERM");
|
|
35
|
-
forceKillTimer = setTimeout(() => {
|
|
36
|
-
if (!cp.killed)
|
|
37
|
-
cp.kill("SIGKILL");
|
|
38
|
-
}, 1000);
|
|
39
|
-
}, options.timeoutMs);
|
|
42
|
+
const durationMs = Date.now() - startTime;
|
|
43
|
+
const ok = runResult.exit.code === 0;
|
|
44
|
+
if (!ok && options.throwOnError) {
|
|
45
|
+
throw new Error(stderr || `Process exited with code ${runResult.exit.code}`);
|
|
40
46
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
return {
|
|
48
|
+
ok,
|
|
49
|
+
exitCode: runResult.exit.code,
|
|
50
|
+
signal: runResult.exit.signal ?? undefined,
|
|
51
|
+
stdout,
|
|
52
|
+
stderr,
|
|
53
|
+
raw: new Uint8Array(),
|
|
54
|
+
timedOut: false,
|
|
55
|
+
cancelled: false,
|
|
56
|
+
durationMs,
|
|
48
57
|
};
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
else {
|
|
54
|
-
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
55
|
-
}
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
if (options.throwOnError) {
|
|
61
|
+
throw err;
|
|
56
62
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
exitCode: -1,
|
|
66
|
+
stdout: "",
|
|
67
|
+
stderr: err?.message || String(err),
|
|
68
|
+
raw: new Uint8Array(),
|
|
69
|
+
timedOut: false,
|
|
70
|
+
cancelled: false,
|
|
71
|
+
durationMs: Date.now() - startTime,
|
|
72
|
+
error: {
|
|
73
|
+
code: err?.code || "PROCESS_FAILED",
|
|
74
|
+
message: err?.message || String(err),
|
|
75
|
+
},
|
|
69
76
|
};
|
|
70
|
-
|
|
71
|
-
totalBytes += chunk.length;
|
|
72
|
-
if (totalBytes > maxOutputBytes) {
|
|
73
|
-
error = {
|
|
74
|
-
code: PROCESS_OUTPUT_LIMIT,
|
|
75
|
-
message: `Process output exceeded limit of ${maxOutputBytes} bytes`,
|
|
76
|
-
};
|
|
77
|
-
cp.kill("SIGKILL");
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
stdoutBuf += chunk.toString("utf-8");
|
|
81
|
-
});
|
|
82
|
-
cp.stderr?.on("data", (chunk) => {
|
|
83
|
-
totalBytes += chunk.length;
|
|
84
|
-
if (totalBytes > maxOutputBytes) {
|
|
85
|
-
error = {
|
|
86
|
-
code: PROCESS_OUTPUT_LIMIT,
|
|
87
|
-
message: `Process output exceeded limit of ${maxOutputBytes} bytes`,
|
|
88
|
-
};
|
|
89
|
-
cp.kill("SIGKILL");
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
stderrBuf += chunk.toString("utf-8");
|
|
93
|
-
});
|
|
94
|
-
cp.on("error", (err) => {
|
|
95
|
-
cleanup();
|
|
96
|
-
const durationMs = Date.now() - startTime;
|
|
97
|
-
const res = {
|
|
98
|
-
ok: false,
|
|
99
|
-
exitCode: null,
|
|
100
|
-
stdout: stdoutBuf.trim(),
|
|
101
|
-
stderr: stderrBuf.trim() || err.message,
|
|
102
|
-
raw: new TextEncoder().encode(stdoutBuf),
|
|
103
|
-
timedOut,
|
|
104
|
-
cancelled,
|
|
105
|
-
durationMs,
|
|
106
|
-
error: error || {
|
|
107
|
-
code: PROCESS_SPAWN_ERROR,
|
|
108
|
-
message: err.message,
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
if (options.throwOnError) {
|
|
112
|
-
reject(new Error(err.message));
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
resolve(res);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
cp.on("close", (exitCode, signal) => {
|
|
119
|
-
cleanup();
|
|
120
|
-
const durationMs = Date.now() - startTime;
|
|
121
|
-
const ok = exitCode === 0 && !timedOut && !cancelled && !error;
|
|
122
|
-
const res = {
|
|
123
|
-
ok,
|
|
124
|
-
exitCode,
|
|
125
|
-
signal: signal || undefined,
|
|
126
|
-
stdout: stdoutBuf.trim(),
|
|
127
|
-
stderr: stderrBuf.trim(),
|
|
128
|
-
raw: new TextEncoder().encode(stdoutBuf),
|
|
129
|
-
timedOut,
|
|
130
|
-
cancelled,
|
|
131
|
-
durationMs,
|
|
132
|
-
error,
|
|
133
|
-
};
|
|
134
|
-
if (!ok && options.throwOnError) {
|
|
135
|
-
reject(new Error(stderrBuf.trim() || `Process exited with code ${exitCode}`));
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
resolve(res);
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
});
|
|
77
|
+
}
|
|
142
78
|
}
|
|
143
79
|
async spawn(command, args = [], options = {}) {
|
|
144
80
|
return this.exec(command, args, options);
|
|
145
81
|
}
|
|
82
|
+
async run(input, call) {
|
|
83
|
+
return this.manager.run(this.owner, input, call);
|
|
84
|
+
}
|
|
85
|
+
async start(input, call) {
|
|
86
|
+
return this.manager.start(this.owner, input, call);
|
|
87
|
+
}
|
|
88
|
+
async inspect(id, call) {
|
|
89
|
+
return this.manager.inspect(this.owner, id, call);
|
|
90
|
+
}
|
|
91
|
+
async list(input, call) {
|
|
92
|
+
return this.manager.list(this.owner, input, call);
|
|
93
|
+
}
|
|
94
|
+
async acquire(id, input, call) {
|
|
95
|
+
return this.manager.acquire(this.owner, id, input, call);
|
|
96
|
+
}
|
|
97
|
+
async renew(id, token, ttlMs, call) {
|
|
98
|
+
return this.manager.renew(this.owner, id, token, ttlMs, call);
|
|
99
|
+
}
|
|
100
|
+
async release(id, token, call) {
|
|
101
|
+
return this.manager.release(this.owner, id, token, call);
|
|
102
|
+
}
|
|
103
|
+
async write(id, input, call) {
|
|
104
|
+
return this.manager.write(this.owner, id, input, call);
|
|
105
|
+
}
|
|
106
|
+
async operation(id, requestId, call) {
|
|
107
|
+
return this.manager.operation(this.owner, id, requestId, call);
|
|
108
|
+
}
|
|
109
|
+
async read(id, input, call) {
|
|
110
|
+
return this.manager.read(this.owner, id, input, call);
|
|
111
|
+
}
|
|
112
|
+
async control(id, input, call) {
|
|
113
|
+
return this.manager.control(this.owner, id, input, call);
|
|
114
|
+
}
|
|
115
|
+
async stop(id, input, call) {
|
|
116
|
+
return this.manager.stop(this.owner, id, input, call);
|
|
117
|
+
}
|
|
146
118
|
}
|