@nowcrew/daemon 0.6.88-beta.17 → 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.
@@ -1,3 +1,4 @@
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)。
@@ -16,7 +17,7 @@ import { createRequire } from "node:module";
16
17
  import { dirname, resolve } from "node:path";
17
18
  import { executableRuntimes } from "./runtime-capabilities.js";
18
19
  import { executionBackendCapability } from "./execution-backend.js";
19
- import { probeKimiAcp } from "./runtimes/kimi-acp-runner.js";
20
+ import { probeKimiAcp, AcpProbeCleanupError } from "./runtimes/kimi-acp-runner.js";
20
21
  import { probeHermesAcp } from "./runtimes/hermes.js";
21
22
  import { probeOpenCodeRun } from "./runtimes/opencode.js";
22
23
  import { RUNTIME_HEALTH_PROBE_CAPABILITY } from "./runtime-health.js";
@@ -132,7 +133,9 @@ async function supportsDeepSeekHarnessAcp(signal, probeId) {
132
133
  try {
133
134
  return await probeDeepSeekHarnessAcp({ bin: deepSeekHarnessLauncher().bin, ...(probeId ? { probeId } : {}), ...(signal ? { signal } : {}) });
134
135
  }
135
- catch {
136
+ catch (error) {
137
+ if (error instanceof AcpProbeCleanupError)
138
+ throw error;
136
139
  return false;
137
140
  }
138
141
  }
@@ -162,7 +165,9 @@ export async function detectExecutionRuntimes(installed = detectRuntimes(), kimi
162
165
  present.includes("kimi") ? probe("kimi", kimiAcpProbe) : false,
163
166
  present.includes("hermes") ? probe("hermes", hermesAcpProbe) : false,
164
167
  present.includes("opencode") ? probe("opencode", openCodeProbe) : false,
165
- present.includes("deepseek-harness") ? probe("deepseek-harness", deepSeekHarnessProbe) : false,
168
+ present.includes("deepseek-harness")
169
+ ? retryHarnessProbe(() => probe("deepseek-harness", deepSeekHarnessProbe), signal, probeId)
170
+ : false,
166
171
  ]);
167
172
  return executableRuntimes(present).filter((runtime) => (runtime === "kimi" ? kimiReady
168
173
  : runtime === "hermes" ? hermesReady
@@ -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
+ }
@@ -196,6 +196,9 @@ async function stopChild(child) {
196
196
  await closed;
197
197
  }
198
198
  }
199
+ export class AcpProbeCleanupError extends Error {
200
+ constructor() { super("ACP probe process cleanup unconfirmed"); this.name = "AcpProbeCleanupError"; }
201
+ }
199
202
  /** Probe the ACP transport without starting a session or forcing an optional interactive login flow. */
200
203
  export async function probeKimiAcp(options, spawnProcess = spawn) {
201
204
  const provider = options.provider ?? "kimi";
@@ -280,6 +283,9 @@ export async function probeKimiAcp(options, spawnProcess = spawn) {
280
283
  await stopChild(child);
281
284
  cleanupFinished = true;
282
285
  }
286
+ catch {
287
+ throw new AcpProbeCleanupError();
288
+ }
283
289
  finally {
284
290
  dslog("execution.acp_probe_finished", "ACP 探测结束", { ...fields(),
285
291
  cleanup_finished: cleanupFinished, exit_code: child.exitCode, exit_signal: child.signalCode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.88-beta.17",
3
+ "version": "0.6.88-beta.18",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",