@nowcrew/daemon 0.6.88-beta.16 → 0.6.88-beta.18
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/dist/machine-info.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { retryHarnessProbe } from "./runtimes/harness-probe-retry.js";
|
|
1
2
|
/**
|
|
2
3
|
* 采集本机信息上报给控制面 (machine:hello):hostname / os / daemon 版本 / 已装 runtimes。
|
|
3
4
|
* runtimes 探测靠 `which <bin>`(win32 用 `where`),只报真实可执行的 CLI(用于展示 Detected Runtimes)。
|
|
4
5
|
*/
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { dslog } from "./slog.js";
|
|
5
8
|
import { hostname, arch, platform as osPlatform } from "node:os";
|
|
6
9
|
import { lookupCmd } from "./platform.js";
|
|
7
10
|
import { augmentedPath } from "./runtime-path.js";
|
|
@@ -14,7 +17,7 @@ import { createRequire } from "node:module";
|
|
|
14
17
|
import { dirname, resolve } from "node:path";
|
|
15
18
|
import { executableRuntimes } from "./runtime-capabilities.js";
|
|
16
19
|
import { executionBackendCapability } from "./execution-backend.js";
|
|
17
|
-
import { probeKimiAcp } from "./runtimes/kimi-acp-runner.js";
|
|
20
|
+
import { probeKimiAcp, AcpProbeCleanupError } from "./runtimes/kimi-acp-runner.js";
|
|
18
21
|
import { probeHermesAcp } from "./runtimes/hermes.js";
|
|
19
22
|
import { probeOpenCodeRun } from "./runtimes/opencode.js";
|
|
20
23
|
import { RUNTIME_HEALTH_PROBE_CAPABILITY } from "./runtime-health.js";
|
|
@@ -117,31 +120,54 @@ export async function detectRuntimes() {
|
|
|
117
120
|
}));
|
|
118
121
|
return checks.filter((x) => x !== null);
|
|
119
122
|
}
|
|
120
|
-
async function supportsKimiAcp(signal) {
|
|
121
|
-
return probeKimiAcp({ bin: "kimi", ...(signal ? { signal } : {}) });
|
|
123
|
+
async function supportsKimiAcp(signal, probeId) {
|
|
124
|
+
return probeKimiAcp({ bin: "kimi", ...(probeId ? { probeId } : {}), ...(signal ? { signal } : {}) });
|
|
122
125
|
}
|
|
123
|
-
async function supportsHermesAcp(signal) {
|
|
124
|
-
return probeHermesAcp({ bin: "hermes", ...(signal ? { signal } : {}) });
|
|
126
|
+
async function supportsHermesAcp(signal, probeId) {
|
|
127
|
+
return probeHermesAcp({ bin: "hermes", ...(probeId ? { probeId } : {}), ...(signal ? { signal } : {}) });
|
|
125
128
|
}
|
|
126
129
|
async function supportsOpenCodeRun(signal) {
|
|
127
130
|
return probeOpenCodeRun(signal);
|
|
128
131
|
}
|
|
129
|
-
async function supportsDeepSeekHarnessAcp(signal) {
|
|
132
|
+
async function supportsDeepSeekHarnessAcp(signal, probeId) {
|
|
130
133
|
try {
|
|
131
|
-
return await probeDeepSeekHarnessAcp({ bin: deepSeekHarnessLauncher().bin, ...(signal ? { signal } : {}) });
|
|
134
|
+
return await probeDeepSeekHarnessAcp({ bin: deepSeekHarnessLauncher().bin, ...(probeId ? { probeId } : {}), ...(signal ? { signal } : {}) });
|
|
132
135
|
}
|
|
133
|
-
catch {
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error instanceof AcpProbeCleanupError)
|
|
138
|
+
throw error;
|
|
134
139
|
return false;
|
|
135
140
|
}
|
|
136
141
|
}
|
|
137
142
|
/** Runtime adapters that can satisfy the durable protocol-v1 process contract. */
|
|
138
|
-
export async function detectExecutionRuntimes(installed = detectRuntimes(), kimiAcpProbe = supportsKimiAcp, hermesAcpProbe = supportsHermesAcp, openCodeProbe = supportsOpenCodeRun, deepSeekHarnessProbe = supportsDeepSeekHarnessAcp, signal) {
|
|
143
|
+
export async function detectExecutionRuntimes(installed = detectRuntimes(), kimiAcpProbe = supportsKimiAcp, hermesAcpProbe = supportsHermesAcp, openCodeProbe = supportsOpenCodeRun, deepSeekHarnessProbe = supportsDeepSeekHarnessAcp, signal, probeId = randomUUID()) {
|
|
139
144
|
const present = await installed;
|
|
145
|
+
const probe = (runtime, detect) => {
|
|
146
|
+
const startedAt = Date.now();
|
|
147
|
+
dslog("runtime_probe.optional_start", "开始可选 runtime 探测", { probe_id: probeId, runtime, aborted: signal?.aborted ?? false });
|
|
148
|
+
const complete = (outcome, ready) => {
|
|
149
|
+
dslog("runtime_probe.optional_complete", "可选 runtime 探测结束", {
|
|
150
|
+
probe_id: probeId, runtime, outcome, duration_ms: Date.now() - startedAt,
|
|
151
|
+
aborted: signal?.aborted ?? false, ...(ready === undefined ? {} : { ready }),
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
let pending;
|
|
155
|
+
try {
|
|
156
|
+
pending = detect(signal, probeId);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
complete("threw");
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
return pending.then((ready) => { complete("resolved", ready); return ready; }, (error) => { complete("threw"); throw error; });
|
|
163
|
+
};
|
|
140
164
|
const [kimiReady, hermesReady, openCodeReady, deepSeekHarnessReady] = await Promise.all([
|
|
141
|
-
present.includes("kimi") ? kimiAcpProbe
|
|
142
|
-
present.includes("hermes") ? hermesAcpProbe
|
|
143
|
-
present.includes("opencode") ? openCodeProbe
|
|
144
|
-
present.includes("deepseek-harness")
|
|
165
|
+
present.includes("kimi") ? probe("kimi", kimiAcpProbe) : false,
|
|
166
|
+
present.includes("hermes") ? probe("hermes", hermesAcpProbe) : false,
|
|
167
|
+
present.includes("opencode") ? probe("opencode", openCodeProbe) : false,
|
|
168
|
+
present.includes("deepseek-harness")
|
|
169
|
+
? retryHarnessProbe(() => probe("deepseek-harness", deepSeekHarnessProbe), signal, probeId)
|
|
170
|
+
: false,
|
|
145
171
|
]);
|
|
146
172
|
return executableRuntimes(present).filter((runtime) => (runtime === "kimi" ? kimiReady
|
|
147
173
|
: runtime === "hermes" ? hermesReady
|
|
@@ -149,7 +175,7 @@ export async function detectExecutionRuntimes(installed = detectRuntimes(), kimi
|
|
|
149
175
|
: runtime === "deepseek-harness" ? deepSeekHarnessReady
|
|
150
176
|
: true));
|
|
151
177
|
}
|
|
152
|
-
export const detectExecutionRuntimesWithSignal = (installed, signal) => detectExecutionRuntimes(installed, supportsKimiAcp, supportsHermesAcp, supportsOpenCodeRun, supportsDeepSeekHarnessAcp, signal);
|
|
178
|
+
export const detectExecutionRuntimesWithSignal = (installed, signal, probeId) => detectExecutionRuntimes(installed, supportsKimiAcp, supportsHermesAcp, supportsOpenCodeRun, supportsDeepSeekHarnessAcp, signal, probeId);
|
|
153
179
|
export function daemonVersion() {
|
|
154
180
|
try {
|
|
155
181
|
const here = dirname(fileURLToPath(import.meta.url));
|
package/dist/runtime-probe.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { dslog } from "./slog.js";
|
|
1
3
|
import { executableRuntimes } from "./runtime-capabilities.js";
|
|
2
4
|
export function conservativeExecutionRuntimes(installed) {
|
|
3
5
|
return executableRuntimes(installed)
|
|
@@ -7,16 +9,43 @@ export function createRuntimeProbeCoordinator() {
|
|
|
7
9
|
const abort = new AbortController();
|
|
8
10
|
const flights = new Map();
|
|
9
11
|
return {
|
|
10
|
-
detect(installed, detector) {
|
|
12
|
+
detect(installed, detector, connectionId) {
|
|
11
13
|
const key = [...installed].sort().join("\0");
|
|
12
14
|
const existing = flights.get(key);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
const probeId = existing?.probeId ?? randomUUID();
|
|
16
|
+
const fields = { probe_id: probeId, connection_id: connectionId, installed_runtimes: JSON.stringify(installed) };
|
|
17
|
+
dslog("runtime_probe.request", "请求 runtime 执行能力探测", fields);
|
|
18
|
+
if (existing) {
|
|
19
|
+
dslog("runtime_probe.cache_hit", "复用 runtime 探测结果", { ...fields, cache_state: existing.state });
|
|
20
|
+
return existing.promise;
|
|
21
|
+
}
|
|
22
|
+
const startedAt = Date.now();
|
|
23
|
+
const complete = (outcome, executionRuntimes) => {
|
|
24
|
+
dslog("runtime_probe.complete", "runtime 探测聚合结束", {
|
|
25
|
+
...fields, outcome, duration_ms: Date.now() - startedAt, aborted: abort.signal.aborted,
|
|
26
|
+
...(executionRuntimes === undefined ? {} : { execution_runtimes: JSON.stringify(executionRuntimes) }),
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
let detection;
|
|
30
|
+
try {
|
|
31
|
+
detection = detector(installed, abort.signal, probeId);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
complete("threw");
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
const pending = detection.then((result) => {
|
|
38
|
+
const flight = flights.get(key);
|
|
39
|
+
if (flight)
|
|
40
|
+
flights.set(key, { ...flight, state: "resolved" });
|
|
41
|
+
complete("resolved", result);
|
|
42
|
+
return result;
|
|
43
|
+
}, (error) => {
|
|
16
44
|
flights.delete(key);
|
|
45
|
+
complete("threw");
|
|
17
46
|
throw error;
|
|
18
47
|
});
|
|
19
|
-
flights.set(key, pending);
|
|
48
|
+
flights.set(key, { promise: pending, probeId, state: "pending" });
|
|
20
49
|
return pending;
|
|
21
50
|
},
|
|
22
51
|
stop() {
|
|
@@ -4,7 +4,7 @@ import { mkdtemp, rm } from "node:fs/promises";
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { prepareHarnessProfile } from "./deepseek-harness-profile.js";
|
|
7
|
-
import { probeKimiAcp } from "./kimi-acp-runner.js";
|
|
7
|
+
import { probeKimiAcp, AcpProbeCleanupError } from "./kimi-acp-runner.js";
|
|
8
8
|
export const DEEPSEEK_HARNESS_CONFIG_ENV = "NOWCREW_DEEPSEEK_HARNESS_CONFIG";
|
|
9
9
|
export const DEEPSEEK_HARNESS_PATCH_ENV = "NOWCREW_DEEPSEEK_HARNESS_PATCH";
|
|
10
10
|
export function deepSeekHarnessLauncher(env = process.env) {
|
|
@@ -61,7 +61,9 @@ export async function probeDeepSeekHarnessAcp(options, spawnProcess = spawn) {
|
|
|
61
61
|
launchCwd: root,
|
|
62
62
|
}, spawnProcess);
|
|
63
63
|
}
|
|
64
|
-
catch {
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error instanceof AcpProbeCleanupError)
|
|
66
|
+
throw error;
|
|
65
67
|
return false;
|
|
66
68
|
}
|
|
67
69
|
finally {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { dslog } from "../slog.js";
|
|
2
|
+
const RETRY_DELAYS_MS = [1_000, 2_000];
|
|
3
|
+
/** 只延迟当前启动探测,不设后台定时任务;stop 立即清除等待。 */
|
|
4
|
+
function waitForRetry(ms, signal) {
|
|
5
|
+
if (signal?.aborted)
|
|
6
|
+
return Promise.resolve(false);
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
const finish = (ready) => {
|
|
9
|
+
clearTimeout(timer);
|
|
10
|
+
signal?.removeEventListener("abort", onAbort);
|
|
11
|
+
resolve(ready);
|
|
12
|
+
};
|
|
13
|
+
const onAbort = () => finish(false);
|
|
14
|
+
const timer = setTimeout(() => finish(true), ms);
|
|
15
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** 成功才公布能力;每次 probe 返回前已完成进程清理,异常保持原有传播。 */
|
|
19
|
+
export async function retryHarnessProbe(probe, signal, probeId) {
|
|
20
|
+
for (let attempt = 1; attempt <= RETRY_DELAYS_MS.length + 1; attempt++) {
|
|
21
|
+
if (signal?.aborted)
|
|
22
|
+
return false;
|
|
23
|
+
const ready = await probe();
|
|
24
|
+
dslog("runtime_probe.harness_attempt", "Harness 启动探测尝试结束", {
|
|
25
|
+
probe_id: probeId, runtime: "deepseek-harness", attempt, ready,
|
|
26
|
+
aborted: signal?.aborted ?? false,
|
|
27
|
+
});
|
|
28
|
+
if (signal?.aborted)
|
|
29
|
+
return false;
|
|
30
|
+
if (ready)
|
|
31
|
+
return true;
|
|
32
|
+
const delayMs = RETRY_DELAYS_MS[attempt - 1];
|
|
33
|
+
if (delayMs === undefined)
|
|
34
|
+
return false;
|
|
35
|
+
dslog("runtime_probe.harness_retry", "延迟重试 Harness 启动探测", {
|
|
36
|
+
probe_id: probeId, runtime: "deepseek-harness", next_attempt: attempt + 1, delay_ms: delayMs,
|
|
37
|
+
});
|
|
38
|
+
if (!await waitForRetry(delayMs, signal))
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { dslog } from "../slog.js";
|
|
1
3
|
import { once } from "node:events";
|
|
2
4
|
import { isAbsolute } from "node:path";
|
|
3
5
|
import { parseArgs } from "node:util";
|
|
@@ -194,10 +196,20 @@ async function stopChild(child) {
|
|
|
194
196
|
await closed;
|
|
195
197
|
}
|
|
196
198
|
}
|
|
199
|
+
export class AcpProbeCleanupError extends Error {
|
|
200
|
+
constructor() { super("ACP probe process cleanup unconfirmed"); this.name = "AcpProbeCleanupError"; }
|
|
201
|
+
}
|
|
197
202
|
/** Probe the ACP transport without starting a session or forcing an optional interactive login flow. */
|
|
198
203
|
export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
199
204
|
const provider = options.provider ?? "kimi";
|
|
200
205
|
const permission = options.effectivePermission ?? "sandboxed";
|
|
206
|
+
const probeId = options.probeId ?? randomUUID();
|
|
207
|
+
const started = Date.now();
|
|
208
|
+
let reason = "initializing", protocolVersion;
|
|
209
|
+
let stderrSeen = false;
|
|
210
|
+
const fields = () => ({ probe_id: probeId, runtime: provider, duration_ms: Date.now() - started,
|
|
211
|
+
reason, protocol_version: protocolVersion, stderr_seen: stderrSeen, aborted: options.signal?.aborted ?? false });
|
|
212
|
+
dslog("execution.acp_probe_started", "ACP 探测开始", { probe_id: probeId, runtime: provider, node_version: process.version });
|
|
201
213
|
const child = spawnProcess(options.bin, options.launchArgs ?? providerArgs(provider, options.configPath), {
|
|
202
214
|
cwd: options.launchCwd ?? process.cwd(),
|
|
203
215
|
// probe 在 daemon 自身 PATH 下运行,补上用户级 CLI 目录,与 which 探测保持一致。
|
|
@@ -206,6 +218,7 @@ export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
|
206
218
|
});
|
|
207
219
|
if (child.stdin === null || child.stdout === null || child.stderr === null)
|
|
208
220
|
return false;
|
|
221
|
+
child.stderr.on("data", () => { stderrSeen = true; });
|
|
209
222
|
child.stderr.resume();
|
|
210
223
|
const app = client({ name: `nowcrew-daemon-${provider}-probe` })
|
|
211
224
|
.onRequest(methods.client.session.requestPermission, () => ({
|
|
@@ -222,18 +235,27 @@ export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
|
222
235
|
clientCapabilities: {},
|
|
223
236
|
clientInfo: { name: "nowcrew-daemon", version: "1" },
|
|
224
237
|
});
|
|
225
|
-
|
|
238
|
+
protocolVersion = initialized.protocolVersion;
|
|
239
|
+
const ready = protocolVersion === PROTOCOL_VERSION;
|
|
240
|
+
if (reason === "initializing")
|
|
241
|
+
reason = ready ? "ready" : "protocol_mismatch";
|
|
242
|
+
dslog("execution.acp_probe_handshake", "ACP 握手结果", { ...fields(), ready });
|
|
243
|
+
return ready;
|
|
226
244
|
});
|
|
227
245
|
const result = await Promise.race([
|
|
228
246
|
connected,
|
|
229
247
|
new Promise((resolve) => {
|
|
230
248
|
timeout = setTimeout(() => {
|
|
249
|
+
if (reason === "initializing")
|
|
250
|
+
reason = "timeout";
|
|
231
251
|
void stopChild(child);
|
|
232
252
|
resolve(false);
|
|
233
253
|
}, PROBE_TIMEOUT_MS);
|
|
234
254
|
}),
|
|
235
255
|
new Promise((resolve) => {
|
|
236
256
|
onAbort = () => {
|
|
257
|
+
if (reason === "initializing")
|
|
258
|
+
reason = "aborted";
|
|
237
259
|
void stopChild(child);
|
|
238
260
|
resolve(false);
|
|
239
261
|
};
|
|
@@ -246,6 +268,8 @@ export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
|
246
268
|
return result;
|
|
247
269
|
}
|
|
248
270
|
catch {
|
|
271
|
+
if (reason === "initializing")
|
|
272
|
+
reason = options.signal?.aborted ? "aborted" : "transport_error";
|
|
249
273
|
return false;
|
|
250
274
|
}
|
|
251
275
|
finally {
|
|
@@ -253,7 +277,20 @@ export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
|
253
277
|
clearTimeout(timeout);
|
|
254
278
|
if (onAbort !== undefined)
|
|
255
279
|
options.signal?.removeEventListener("abort", onAbort);
|
|
256
|
-
|
|
280
|
+
dslog("execution.acp_probe_cleanup_started", "ACP 探测清理开始", fields());
|
|
281
|
+
let cleanupFinished = false;
|
|
282
|
+
try {
|
|
283
|
+
await stopChild(child);
|
|
284
|
+
cleanupFinished = true;
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
throw new AcpProbeCleanupError();
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
dslog("execution.acp_probe_finished", "ACP 探测结束", { ...fields(),
|
|
291
|
+
cleanup_finished: cleanupFinished, exit_code: child.exitCode, exit_signal: child.signalCode,
|
|
292
|
+
level: reason === "ready" && cleanupFinished ? "INFO" : "WARN" });
|
|
293
|
+
}
|
|
257
294
|
}
|
|
258
295
|
}
|
|
259
296
|
export async function runKimiAcp(requestedOptions) {
|
package/dist/serve.js
CHANGED
|
@@ -291,10 +291,11 @@ export function serve(config, opts = {}) {
|
|
|
291
291
|
connectionEpochToken = projectSkillsController.beginConnectionEpoch();
|
|
292
292
|
storageIdentityAdmission.beginIdentityBinding();
|
|
293
293
|
const openedSocket = ws;
|
|
294
|
+
const connectionId = randomUUID();
|
|
294
295
|
backoff = 1000;
|
|
295
296
|
connectedAt = Date.now();
|
|
296
297
|
log(`🔌 已连接控制面 ${config.serverUrl}`);
|
|
297
|
-
dslog("daemon.ws_open", "已连接控制面", { server_url: config.serverUrl });
|
|
298
|
+
dslog("daemon.ws_open", "已连接控制面", { server_url: config.serverUrl, connection_id: connectionId });
|
|
298
299
|
// 连上了才有机会把离线期间(断连原因/退出前)落盘的日志补传上去
|
|
299
300
|
void drainSpool();
|
|
300
301
|
// 上报本机信息 (hostname/os/daemon 版本/已装 runtimes)
|
|
@@ -312,21 +313,36 @@ export function serve(config, opts = {}) {
|
|
|
312
313
|
.then(async (hello) => {
|
|
313
314
|
if (hello.executionProtocol === undefined) {
|
|
314
315
|
executionRuntimesKnown = true;
|
|
316
|
+
dslog("runtime_probe.hello_refresh", "runtime hello 刷新决策", {
|
|
317
|
+
connection_id: connectionId, reason: "protocol_unavailable",
|
|
318
|
+
current_socket: ws === openedSocket, socket_open: openedSocket.readyState === WebSocket.OPEN,
|
|
319
|
+
socket_ready_state: openedSocket.readyState, installed_runtimes: JSON.stringify(hello.runtimes),
|
|
320
|
+
execution_runtimes: JSON.stringify(hello.executionRuntimes),
|
|
321
|
+
});
|
|
315
322
|
return [];
|
|
316
323
|
}
|
|
317
|
-
const detected = await runtimeProbe.detect(hello.runtimes, detectExecutable);
|
|
324
|
+
const detected = await runtimeProbe.detect(hello.runtimes, detectExecutable, connectionId);
|
|
318
325
|
executionRuntimesKnown = true;
|
|
319
|
-
|
|
320
|
-
|
|
326
|
+
const currentSocket = ws === openedSocket;
|
|
327
|
+
const socketOpen = openedSocket.readyState === WebSocket.OPEN;
|
|
328
|
+
const hasNewRuntime = detected.some((runtime) => !hello.executionRuntimes.includes(runtime));
|
|
329
|
+
dslog("runtime_probe.hello_refresh", "runtime hello 刷新决策", {
|
|
330
|
+
connection_id: connectionId, current_socket: currentSocket, socket_open: socketOpen,
|
|
331
|
+
socket_ready_state: openedSocket.readyState, installed_runtimes: JSON.stringify(hello.runtimes),
|
|
332
|
+
initial_execution_runtimes: JSON.stringify(hello.executionRuntimes), execution_runtimes: JSON.stringify(detected),
|
|
333
|
+
reason: !currentSocket ? "stale_connection" : !socketOpen ? "socket_not_open" : !hasNewRuntime ? "unchanged" : "publish",
|
|
334
|
+
});
|
|
335
|
+
if (currentSocket && socketOpen && hasNewRuntime) {
|
|
321
336
|
detectedExecutionRuntimes = detected;
|
|
322
337
|
machineHelloPublisher.publish({ ...hello, executionRuntimes: [...detected] }, openedSocket);
|
|
323
338
|
}
|
|
324
339
|
return detected;
|
|
325
340
|
})
|
|
326
|
-
.catch((
|
|
341
|
+
.catch(() => {
|
|
327
342
|
executionRuntimesKnown = true;
|
|
328
343
|
dslog("execution.runtime_detection_failed", "runtime 探测失败", {
|
|
329
|
-
level: "ERROR",
|
|
344
|
+
level: "ERROR", reason: "detection_threw", connection_id: connectionId,
|
|
345
|
+
current_socket: ws === openedSocket, socket_open: openedSocket.readyState === WebSocket.OPEN,
|
|
330
346
|
});
|
|
331
347
|
return [];
|
|
332
348
|
});
|