@nowcrew/daemon 0.6.88-beta.16 → 0.6.88-beta.17
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 +33 -12
- package/dist/runtime-probe.js +34 -5
- package/dist/runtimes/kimi-acp-runner.js +33 -2
- package/dist/serve.js +22 -6
- package/package.json +1 -1
package/dist/machine-info.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* 采集本机信息上报给控制面 (machine:hello):hostname / os / daemon 版本 / 已装 runtimes。
|
|
3
3
|
* runtimes 探测靠 `which <bin>`(win32 用 `where`),只报真实可执行的 CLI(用于展示 Detected Runtimes)。
|
|
4
4
|
*/
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { dslog } from "./slog.js";
|
|
5
7
|
import { hostname, arch, platform as osPlatform } from "node:os";
|
|
6
8
|
import { lookupCmd } from "./platform.js";
|
|
7
9
|
import { augmentedPath } from "./runtime-path.js";
|
|
@@ -117,31 +119,50 @@ export async function detectRuntimes() {
|
|
|
117
119
|
}));
|
|
118
120
|
return checks.filter((x) => x !== null);
|
|
119
121
|
}
|
|
120
|
-
async function supportsKimiAcp(signal) {
|
|
121
|
-
return probeKimiAcp({ bin: "kimi", ...(signal ? { signal } : {}) });
|
|
122
|
+
async function supportsKimiAcp(signal, probeId) {
|
|
123
|
+
return probeKimiAcp({ bin: "kimi", ...(probeId ? { probeId } : {}), ...(signal ? { signal } : {}) });
|
|
122
124
|
}
|
|
123
|
-
async function supportsHermesAcp(signal) {
|
|
124
|
-
return probeHermesAcp({ bin: "hermes", ...(signal ? { signal } : {}) });
|
|
125
|
+
async function supportsHermesAcp(signal, probeId) {
|
|
126
|
+
return probeHermesAcp({ bin: "hermes", ...(probeId ? { probeId } : {}), ...(signal ? { signal } : {}) });
|
|
125
127
|
}
|
|
126
128
|
async function supportsOpenCodeRun(signal) {
|
|
127
129
|
return probeOpenCodeRun(signal);
|
|
128
130
|
}
|
|
129
|
-
async function supportsDeepSeekHarnessAcp(signal) {
|
|
131
|
+
async function supportsDeepSeekHarnessAcp(signal, probeId) {
|
|
130
132
|
try {
|
|
131
|
-
return await probeDeepSeekHarnessAcp({ bin: deepSeekHarnessLauncher().bin, ...(signal ? { signal } : {}) });
|
|
133
|
+
return await probeDeepSeekHarnessAcp({ bin: deepSeekHarnessLauncher().bin, ...(probeId ? { probeId } : {}), ...(signal ? { signal } : {}) });
|
|
132
134
|
}
|
|
133
135
|
catch {
|
|
134
136
|
return false;
|
|
135
137
|
}
|
|
136
138
|
}
|
|
137
139
|
/** 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) {
|
|
140
|
+
export async function detectExecutionRuntimes(installed = detectRuntimes(), kimiAcpProbe = supportsKimiAcp, hermesAcpProbe = supportsHermesAcp, openCodeProbe = supportsOpenCodeRun, deepSeekHarnessProbe = supportsDeepSeekHarnessAcp, signal, probeId = randomUUID()) {
|
|
139
141
|
const present = await installed;
|
|
142
|
+
const probe = (runtime, detect) => {
|
|
143
|
+
const startedAt = Date.now();
|
|
144
|
+
dslog("runtime_probe.optional_start", "开始可选 runtime 探测", { probe_id: probeId, runtime, aborted: signal?.aborted ?? false });
|
|
145
|
+
const complete = (outcome, ready) => {
|
|
146
|
+
dslog("runtime_probe.optional_complete", "可选 runtime 探测结束", {
|
|
147
|
+
probe_id: probeId, runtime, outcome, duration_ms: Date.now() - startedAt,
|
|
148
|
+
aborted: signal?.aborted ?? false, ...(ready === undefined ? {} : { ready }),
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
let pending;
|
|
152
|
+
try {
|
|
153
|
+
pending = detect(signal, probeId);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
complete("threw");
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
return pending.then((ready) => { complete("resolved", ready); return ready; }, (error) => { complete("threw"); throw error; });
|
|
160
|
+
};
|
|
140
161
|
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") ? deepSeekHarnessProbe
|
|
162
|
+
present.includes("kimi") ? probe("kimi", kimiAcpProbe) : false,
|
|
163
|
+
present.includes("hermes") ? probe("hermes", hermesAcpProbe) : false,
|
|
164
|
+
present.includes("opencode") ? probe("opencode", openCodeProbe) : false,
|
|
165
|
+
present.includes("deepseek-harness") ? probe("deepseek-harness", deepSeekHarnessProbe) : false,
|
|
145
166
|
]);
|
|
146
167
|
return executableRuntimes(present).filter((runtime) => (runtime === "kimi" ? kimiReady
|
|
147
168
|
: runtime === "hermes" ? hermesReady
|
|
@@ -149,7 +170,7 @@ export async function detectExecutionRuntimes(installed = detectRuntimes(), kimi
|
|
|
149
170
|
: runtime === "deepseek-harness" ? deepSeekHarnessReady
|
|
150
171
|
: true));
|
|
151
172
|
}
|
|
152
|
-
export const detectExecutionRuntimesWithSignal = (installed, signal) => detectExecutionRuntimes(installed, supportsKimiAcp, supportsHermesAcp, supportsOpenCodeRun, supportsDeepSeekHarnessAcp, signal);
|
|
173
|
+
export const detectExecutionRuntimesWithSignal = (installed, signal, probeId) => detectExecutionRuntimes(installed, supportsKimiAcp, supportsHermesAcp, supportsOpenCodeRun, supportsDeepSeekHarnessAcp, signal, probeId);
|
|
153
174
|
export function daemonVersion() {
|
|
154
175
|
try {
|
|
155
176
|
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() {
|
|
@@ -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";
|
|
@@ -198,6 +200,13 @@ async function stopChild(child) {
|
|
|
198
200
|
export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
199
201
|
const provider = options.provider ?? "kimi";
|
|
200
202
|
const permission = options.effectivePermission ?? "sandboxed";
|
|
203
|
+
const probeId = options.probeId ?? randomUUID();
|
|
204
|
+
const started = Date.now();
|
|
205
|
+
let reason = "initializing", protocolVersion;
|
|
206
|
+
let stderrSeen = false;
|
|
207
|
+
const fields = () => ({ probe_id: probeId, runtime: provider, duration_ms: Date.now() - started,
|
|
208
|
+
reason, protocol_version: protocolVersion, stderr_seen: stderrSeen, aborted: options.signal?.aborted ?? false });
|
|
209
|
+
dslog("execution.acp_probe_started", "ACP 探测开始", { probe_id: probeId, runtime: provider, node_version: process.version });
|
|
201
210
|
const child = spawnProcess(options.bin, options.launchArgs ?? providerArgs(provider, options.configPath), {
|
|
202
211
|
cwd: options.launchCwd ?? process.cwd(),
|
|
203
212
|
// probe 在 daemon 自身 PATH 下运行,补上用户级 CLI 目录,与 which 探测保持一致。
|
|
@@ -206,6 +215,7 @@ export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
|
206
215
|
});
|
|
207
216
|
if (child.stdin === null || child.stdout === null || child.stderr === null)
|
|
208
217
|
return false;
|
|
218
|
+
child.stderr.on("data", () => { stderrSeen = true; });
|
|
209
219
|
child.stderr.resume();
|
|
210
220
|
const app = client({ name: `nowcrew-daemon-${provider}-probe` })
|
|
211
221
|
.onRequest(methods.client.session.requestPermission, () => ({
|
|
@@ -222,18 +232,27 @@ export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
|
222
232
|
clientCapabilities: {},
|
|
223
233
|
clientInfo: { name: "nowcrew-daemon", version: "1" },
|
|
224
234
|
});
|
|
225
|
-
|
|
235
|
+
protocolVersion = initialized.protocolVersion;
|
|
236
|
+
const ready = protocolVersion === PROTOCOL_VERSION;
|
|
237
|
+
if (reason === "initializing")
|
|
238
|
+
reason = ready ? "ready" : "protocol_mismatch";
|
|
239
|
+
dslog("execution.acp_probe_handshake", "ACP 握手结果", { ...fields(), ready });
|
|
240
|
+
return ready;
|
|
226
241
|
});
|
|
227
242
|
const result = await Promise.race([
|
|
228
243
|
connected,
|
|
229
244
|
new Promise((resolve) => {
|
|
230
245
|
timeout = setTimeout(() => {
|
|
246
|
+
if (reason === "initializing")
|
|
247
|
+
reason = "timeout";
|
|
231
248
|
void stopChild(child);
|
|
232
249
|
resolve(false);
|
|
233
250
|
}, PROBE_TIMEOUT_MS);
|
|
234
251
|
}),
|
|
235
252
|
new Promise((resolve) => {
|
|
236
253
|
onAbort = () => {
|
|
254
|
+
if (reason === "initializing")
|
|
255
|
+
reason = "aborted";
|
|
237
256
|
void stopChild(child);
|
|
238
257
|
resolve(false);
|
|
239
258
|
};
|
|
@@ -246,6 +265,8 @@ export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
|
246
265
|
return result;
|
|
247
266
|
}
|
|
248
267
|
catch {
|
|
268
|
+
if (reason === "initializing")
|
|
269
|
+
reason = options.signal?.aborted ? "aborted" : "transport_error";
|
|
249
270
|
return false;
|
|
250
271
|
}
|
|
251
272
|
finally {
|
|
@@ -253,7 +274,17 @@ export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
|
253
274
|
clearTimeout(timeout);
|
|
254
275
|
if (onAbort !== undefined)
|
|
255
276
|
options.signal?.removeEventListener("abort", onAbort);
|
|
256
|
-
|
|
277
|
+
dslog("execution.acp_probe_cleanup_started", "ACP 探测清理开始", fields());
|
|
278
|
+
let cleanupFinished = false;
|
|
279
|
+
try {
|
|
280
|
+
await stopChild(child);
|
|
281
|
+
cleanupFinished = true;
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
dslog("execution.acp_probe_finished", "ACP 探测结束", { ...fields(),
|
|
285
|
+
cleanup_finished: cleanupFinished, exit_code: child.exitCode, exit_signal: child.signalCode,
|
|
286
|
+
level: reason === "ready" && cleanupFinished ? "INFO" : "WARN" });
|
|
287
|
+
}
|
|
257
288
|
}
|
|
258
289
|
}
|
|
259
290
|
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
|
});
|