@actiondock/core 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -68
- package/dist/catalog/action-catalog.d.ts +56 -0
- package/dist/catalog/action-catalog.js +202 -0
- package/dist/catalog/discovery.d.ts +60 -0
- package/dist/catalog/discovery.js +119 -0
- package/dist/catalog/graph.d.ts +190 -0
- package/dist/catalog/graph.js +589 -0
- package/dist/catalog/index.d.ts +5 -5
- package/dist/catalog/index.js +5 -5
- package/dist/catalog/resolve-action.d.ts +43 -0
- package/dist/catalog/resolve-action.js +119 -0
- package/dist/catalog/resolve-playbook.d.ts +26 -0
- package/dist/catalog/resolve-playbook.js +114 -0
- package/dist/doctor/checks/project.js +27 -8
- package/dist/doctor/checks/registry.js +2 -2
- package/dist/doctor/checks/runtime.js +34 -1
- package/dist/errors.d.ts +122 -10
- package/dist/errors.js +123 -10
- package/dist/execution/service.d.ts +18 -16
- package/dist/execution/service.js +183 -252
- package/dist/execution/types.d.ts +25 -69
- package/dist/execution/types.js +2 -1
- package/dist/filter/intent.d.ts +8 -9
- package/dist/filter/intent.js +49 -10
- package/dist/graph/index.d.ts +4 -0
- package/dist/graph/index.js +4 -0
- package/dist/host/host.d.ts +74 -32
- package/dist/host/host.js +585 -336
- package/dist/host/routing.d.ts +15 -48
- package/dist/host/routing.js +90 -153
- package/dist/host/types.d.ts +50 -17
- package/dist/index.d.ts +21 -22
- package/dist/index.js +19 -21
- package/dist/input/advice.d.ts +104 -0
- package/dist/input/advice.js +716 -0
- package/dist/input/describe.d.ts +106 -0
- package/dist/input/describe.js +176 -0
- package/dist/input/file-input.d.ts +54 -0
- package/dist/input/file-input.js +124 -0
- package/dist/input/flat-decode.d.ts +11 -0
- package/dist/input/flat-decode.js +13 -0
- package/dist/input/flat-errors.d.ts +71 -0
- package/dist/input/flat-errors.js +87 -0
- package/dist/input/flat-materializer.d.ts +29 -0
- package/dist/input/flat-materializer.js +177 -0
- package/dist/input/flat-parser.d.ts +50 -0
- package/dist/input/flat-parser.js +169 -0
- package/dist/input/flat-predicates.d.ts +34 -0
- package/dist/input/flat-predicates.js +50 -0
- package/dist/input/index.d.ts +14 -0
- package/dist/input/index.js +14 -0
- package/dist/input/input-resolver.d.ts +63 -0
- package/dist/input/input-resolver.js +254 -0
- package/dist/input/json-depth-scanner.d.ts +17 -0
- package/dist/input/json-depth-scanner.js +49 -0
- package/dist/input/metadata.d.ts +99 -0
- package/dist/input/metadata.js +99 -0
- package/dist/input/stdin-input.d.ts +26 -0
- package/dist/input/stdin-input.js +125 -0
- package/dist/input/utf8.d.ts +19 -0
- package/dist/input/utf8.js +36 -0
- package/dist/input/validation-mapper.d.ts +27 -0
- package/dist/input/validation-mapper.js +144 -0
- package/dist/invocation/index.d.ts +2 -0
- package/dist/invocation/index.js +2 -0
- package/dist/invocation/policy.d.ts +157 -0
- package/dist/invocation/policy.js +231 -0
- package/dist/invocation/types.d.ts +172 -0
- package/dist/invocation/types.js +97 -0
- package/dist/ipc/diagnostic.d.ts +0 -12
- package/dist/ipc/diagnostic.js +0 -21
- package/dist/ipc/host.d.ts +4 -4
- package/dist/ipc/host.js +44 -47
- package/dist/ipc/index.d.ts +1 -1
- package/dist/ipc/index.js +1 -1
- package/dist/ipc/service.d.ts +40 -0
- package/dist/ipc/service.js +349 -0
- package/dist/ipc/types.d.ts +21 -15
- package/dist/json/index.d.ts +1 -0
- package/dist/json/index.js +1 -0
- package/dist/json/value-validator.d.ts +88 -0
- package/dist/json/value-validator.js +330 -0
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +1 -0
- package/dist/node/module-loader.d.ts +63 -0
- package/dist/node/module-loader.js +112 -0
- package/dist/package/index.d.ts +23 -0
- package/dist/package/index.js +18 -0
- package/dist/{app/app.d.ts → package/runtime.d.ts} +41 -20
- package/dist/{app/app.js → package/runtime.js} +140 -95
- package/dist/{app → package}/static-index.js +58 -41
- package/dist/package/storage-view.d.ts +13 -0
- package/dist/package/storage-view.js +22 -0
- package/dist/{app → package}/types.d.ts +76 -57
- package/dist/package/types.js +22 -0
- package/dist/platform/index.d.ts +1 -1
- package/dist/platform/index.js +1 -1
- package/dist/platform/node.d.ts +48 -0
- package/dist/platform/node.js +96 -0
- package/dist/platform/types.d.ts +0 -9
- package/dist/process/control-arbiter.d.ts +2 -1
- package/dist/process/control-arbiter.js +1 -2
- package/dist/process/driver.d.ts +9 -8
- package/dist/process/driver.js +11 -22
- package/dist/process/index.d.ts +2 -0
- package/dist/process/index.js +2 -0
- package/dist/process/managed-record.d.ts +2 -2
- package/dist/process/process-driver.d.ts +109 -0
- package/dist/process/process-driver.js +650 -0
- package/dist/process/process-executor.d.ts +47 -0
- package/dist/process/process-executor.js +296 -0
- package/dist/process/process-manager.js +48 -26
- package/dist/process/run-executor.js +12 -12
- package/dist/profile/client-actions.js +2 -2
- package/dist/profile/client-health.js +3 -2
- package/dist/profile/client-query.js +5 -6
- package/dist/profile/client-transport.d.ts +19 -17
- package/dist/profile/client-transport.js +48 -80
- package/dist/profile/client.d.ts +2 -2
- package/dist/profile/client.js +1 -1
- package/dist/profile/index.d.ts +1 -0
- package/dist/profile/index.js +1 -0
- package/dist/profile/manager.d.ts +5 -0
- package/dist/profile/manager.js +41 -6
- package/dist/project/closure.js +2 -2
- package/dist/project/index.d.ts +8 -1
- package/dist/project/index.js +10 -1
- package/dist/project/init.js +2 -2
- package/dist/project/loader.d.ts +1 -1
- package/dist/project/loader.js +26 -29
- package/dist/project/transactions.js +5 -10
- package/dist/registry/index.d.ts +3 -1
- package/dist/registry/index.js +2 -1
- package/dist/registry/lock.d.ts +28 -7
- package/dist/registry/lock.js +136 -16
- package/dist/registry/registry.d.ts +1 -5
- package/dist/registry/registry.js +1 -53
- package/dist/registry/scan.d.ts +0 -15
- package/dist/registry/scan.js +0 -59
- package/dist/registry/store.d.ts +42 -0
- package/dist/registry/store.js +43 -0
- package/dist/registry/types.d.ts +0 -24
- package/dist/runtime/action-collection.d.ts +1 -1
- package/dist/runtime/action-registry.d.ts +0 -4
- package/dist/runtime/action-registry.js +0 -7
- package/dist/runtime/context.js +5 -11
- package/dist/runtime/events.js +3 -6
- package/dist/runtime/identity.d.ts +22 -0
- package/dist/runtime/identity.js +10 -0
- package/dist/runtime/index.d.ts +1 -2
- package/dist/runtime/index.js +1 -2
- package/dist/runtime/module-loader.d.ts +2 -34
- package/dist/runtime/module-loader.js +1 -16
- package/dist/runtime/process.d.ts +6 -24
- package/dist/runtime/process.js +1 -118
- package/dist/runtime/run-persistence.js +2 -5
- package/dist/runtime/runner.d.ts +33 -103
- package/dist/runtime/runner.js +142 -404
- package/dist/runtime/standalone.d.ts +37 -9
- package/dist/runtime/standalone.js +195 -136
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/validator.d.ts +10 -0
- package/dist/schema/validator.js +48 -54
- package/dist/server/body.d.ts +16 -8
- package/dist/server/body.js +33 -16
- package/dist/server/dispatcher.d.ts +6 -10
- package/dist/server/dispatcher.js +24 -32
- package/dist/server/http-server.d.ts +87 -0
- package/dist/server/http-server.js +304 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.js +10 -0
- package/dist/server/mcp-endpoint.d.ts +61 -0
- package/dist/server/mcp-endpoint.js +118 -0
- package/dist/server/routes/actions.js +34 -28
- package/dist/server/routes/common.d.ts +10 -14
- package/dist/server/routes/common.js +32 -56
- package/dist/server/routes/config.js +54 -64
- package/dist/server/routes/doctor.d.ts +1 -1
- package/dist/server/routes/doctor.js +10 -18
- package/dist/server/routes/health.d.ts +1 -1
- package/dist/server/routes/health.js +2 -3
- package/dist/server/routes/info.d.ts +2 -2
- package/dist/server/routes/info.js +12 -13
- package/dist/server/routes/playbooks.js +16 -29
- package/dist/server/routes/runs.js +95 -117
- package/dist/server/routes/state.js +63 -62
- package/dist/server/server.d.ts +2 -2
- package/dist/server/server.js +43 -227
- package/dist/server/types.d.ts +8 -19
- package/dist/service/factory.d.ts +12 -0
- package/dist/service/factory.js +85 -0
- package/dist/{target → service}/index.d.ts +1 -1
- package/dist/{target → service}/index.js +1 -1
- package/dist/service/local.d.ts +27 -0
- package/dist/service/local.js +138 -0
- package/dist/{target → service}/remote-errors.d.ts +3 -6
- package/dist/service/remote-errors.js +38 -0
- package/dist/{target → service}/remote-polling.d.ts +2 -2
- package/dist/{target → service}/remote-polling.js +8 -8
- package/dist/service/remote.d.ts +34 -0
- package/dist/service/remote.js +634 -0
- package/dist/service/sse-stream.d.ts +24 -0
- package/dist/service/sse-stream.js +139 -0
- package/dist/service/types.d.ts +272 -0
- package/dist/service/types.js +32 -0
- package/dist/storage/data-dir-lock.js +10 -5
- package/dist/storage/driver.d.ts +2 -1
- package/dist/storage/driver.js +3 -52
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/lock-core.d.ts +2 -6
- package/dist/storage/lock-core.js +4 -17
- package/dist/storage/params.d.ts +2 -8
- package/dist/storage/params.js +2 -8
- package/dist/storage/sqlite-driver.d.ts +40 -0
- package/dist/storage/sqlite-driver.js +116 -0
- package/dist/storage/sqlite.js +21 -6
- package/dist/storage/types.d.ts +4 -4
- package/dist/storage/types.js +2 -2
- package/dist/utils/index.d.ts +53 -0
- package/dist/utils/index.js +99 -4
- package/dist/utils/process.d.ts +10 -0
- package/dist/utils/process.js +21 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +41 -3
- package/dist/app/index.d.ts +0 -2
- package/dist/app/index.js +0 -2
- package/dist/app/types.js +0 -1
- package/dist/catalog/action-index.d.ts +0 -9
- package/dist/catalog/action-index.js +0 -95
- package/dist/catalog/action-resolver.d.ts +0 -20
- package/dist/catalog/action-resolver.js +0 -95
- package/dist/catalog/location-registry.d.ts +0 -15
- package/dist/catalog/location-registry.js +0 -138
- package/dist/catalog/package-catalog.d.ts +0 -7
- package/dist/catalog/package-catalog.js +0 -77
- package/dist/catalog/types.d.ts +0 -68
- package/dist/catalog/types.js +0 -1
- package/dist/export/index.d.ts +0 -1
- package/dist/export/index.js +0 -1
- package/dist/export/templates.d.ts +0 -71
- package/dist/export/templates.js +0 -623
- package/dist/ipc/target.d.ts +0 -74
- package/dist/ipc/target.js +0 -365
- package/dist/platform/default.d.ts +0 -38
- package/dist/platform/default.js +0 -78
- package/dist/project/resolver.d.ts +0 -116
- package/dist/project/resolver.js +0 -376
- package/dist/registry/resolve.d.ts +0 -62
- package/dist/registry/resolve.js +0 -195
- package/dist/runtime/execution-manager.d.ts +0 -48
- package/dist/runtime/execution-manager.js +0 -66
- package/dist/runtime/package-runner-factory.d.ts +0 -82
- package/dist/runtime/package-runner-factory.js +0 -186
- package/dist/target/local.d.ts +0 -53
- package/dist/target/local.js +0 -349
- package/dist/target/remote-errors.js +0 -51
- package/dist/target/remote.d.ts +0 -86
- package/dist/target/remote.js +0 -818
- package/dist/target/target.d.ts +0 -6
- package/dist/target/target.js +0 -37
- package/dist/target/types.d.ts +0 -261
- package/dist/target/types.js +0 -35
- /package/dist/{app → package}/static-index.d.ts +0 -0
- /package/dist/{target → service}/sse-parser.d.ts +0 -0
- /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
|
|
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
|
|
406
|
-
|
|
405
|
+
const observer = {
|
|
406
|
+
processId,
|
|
407
|
+
output: (stream, data) => {
|
|
407
408
|
procRecord.outputLog.append(stream, data);
|
|
408
409
|
},
|
|
409
|
-
|
|
410
|
+
exited: (exit) => {
|
|
410
411
|
this.handleProcessExit(procRecord, exit);
|
|
411
412
|
},
|
|
412
|
-
|
|
413
|
+
outputClosed: (reason) => {
|
|
413
414
|
this.handleOutputClosed(procRecord, reason);
|
|
414
415
|
},
|
|
415
|
-
|
|
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(
|
|
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
|
|
441
|
-
void
|
|
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
|
|
458
|
-
await
|
|
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
|
|
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
|
|
44
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
76
|
+
exited: (exit) => {
|
|
78
77
|
resolveExit(exit);
|
|
79
78
|
},
|
|
80
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
2
|
+
import { assertSecureTransport, buildHeaders, fetchRemoteRoute, } from "./client-transport.js";
|
|
3
3
|
import { getInsecureDispatcher } from "../server/dispatcher.js";
|
|
4
4
|
/**
|
|
5
5
|
* 远端健康检查域端点。
|
|
@@ -32,8 +32,9 @@ export async function checkRemoteHealth(serverUrl, token, timeoutMs = 5000, opti
|
|
|
32
32
|
}
|
|
33
33
|
else if (options?.insecure) {
|
|
34
34
|
fetchInit.dispatcher = getInsecureDispatcher();
|
|
35
|
+
fetchInit.tls = { rejectUnauthorized: false };
|
|
35
36
|
}
|
|
36
|
-
const res = await
|
|
37
|
+
const res = await fetchRemoteRoute(normalizeServerUrl(serverUrl), "/api/v2/health", fetchInit);
|
|
37
38
|
const latencyMs = Date.now() - startTime;
|
|
38
39
|
if (!res.ok) {
|
|
39
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
|
|
27
|
-
|
|
28
|
-
|
|
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;
|