@nowcrew/daemon 0.6.88-beta.15 → 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.
Files changed (39) hide show
  1. package/README.md +15 -0
  2. package/dist/daemon-update-eligibility.js +1 -1
  3. package/dist/local-executor.js +4 -2
  4. package/dist/machine-info.js +48 -15
  5. package/dist/main.js +4 -69
  6. package/dist/project-skills/execution-adapter.js +3 -2
  7. package/dist/project-skills/runtime-launch.js +14 -0
  8. package/dist/provider-env.js +3 -4
  9. package/dist/runtime-probe.js +34 -5
  10. package/dist/runtimes/deepseek-harness-profile.js +92 -0
  11. package/dist/runtimes/deepseek-harness-runner.js +224 -0
  12. package/dist/runtimes/deepseek-harness.js +61 -10
  13. package/dist/runtimes/kimi-acp-runner.js +37 -6
  14. package/dist/serve.js +36 -68
  15. package/dist/storage-identity.js +0 -3
  16. package/dist/storage-serve-adapter.js +6 -72
  17. package/dist/supervised-runtime.js +17 -2
  18. package/package.json +1 -1
  19. package/dist/daemon-migration-controller.js +0 -211
  20. package/dist/daemon-migration-wiring.js +0 -22
  21. package/dist/storage-control.js +0 -212
  22. package/dist/storage-copy.js +0 -628
  23. package/dist/storage-credential-guard.js +0 -21
  24. package/dist/storage-doctor.js +0 -228
  25. package/dist/storage-manifest.js +0 -1027
  26. package/dist/storage-migration-controller.js +0 -440
  27. package/dist/storage-migration-manifest.js +0 -105
  28. package/dist/storage-migration-policy.js +0 -100
  29. package/dist/storage-runtime-controller.js +0 -381
  30. package/dist/storage-runtime-readiness.js +0 -240
  31. package/dist/storage-safe-fs.js +0 -302
  32. package/dist/storage-status-delivery.js +0 -224
  33. package/dist/storage-status-replay.js +0 -140
  34. package/dist/storage-target-confirmation.js +0 -129
  35. package/dist/storage-worker-command.js +0 -37
  36. package/dist/storage-worker-executor.js +0 -449
  37. package/dist/storage-worker-handoff.js +0 -151
  38. package/dist/storage-worker-operation.js +0 -294
  39. package/dist/storage-worker-status.js +0 -259
package/README.md CHANGED
@@ -3,6 +3,21 @@
3
3
  The daemon is NowWork's local execution adapter. It holds a machine credential, keeps one control-plane
4
4
  WebSocket open, and launches coding runtimes on that machine. Product policy remains on the server.
5
5
 
6
+ ## 已完成搬迁后的存储运行
7
+
8
+ NAS / `NOWWORK_HOME` 搬迁和旧 daemon 布局标准化入口已退役。daemon 不再提供内部
9
+ `storage-migration-worker` / `storage-migration-recover` 命令,不处理 `daemon:migrate`、
10
+ `daemon:storage-doctor`、`daemon:storage-migrate` 或迁移确认消息,也不再声明
11
+ `storage_migration_v1` / `storage_migration_status_v1` / `daemon_layout_migration_v1` 能力。
12
+
13
+ 现有 `installations.json` 即使仍有历史 `storageMigrationId`,也按当前 installation 解析
14
+ Agent、仓库和报告目录;启动、hello 和身份绑定不再读取迁移 receipt、outbox 或备份。
15
+ hello 继续报告布局、环境、Workspace、路径指纹与 ownership,但不再发送迁移 ID。
16
+ 每次连接仍须完成 Server 身份绑定后才能执行任务;Workspace 或 Machine 身份不匹配时拒绝执行。
17
+
18
+ 旧布局读取兼容、日常安装/升级、NAS 所有权校验、执行 journal 与 ACK/replay 继续保留。
19
+ 退役代码不会删除历史安装字段、迁移记录、receipt、备份或旧数据目录;这些数据的保留与清理另行处理。
20
+
6
21
  ## Run
7
22
 
8
23
  For a temporary foreground connection, run:
@@ -14,7 +14,7 @@ import { assertRegistryCoversDescriptors, listManagedServiceDescriptorPaths, man
14
14
  export async function managedDaemonCapabilities(eligibility) {
15
15
  try {
16
16
  return (await eligibility()).eligible
17
- ? ["daemon_update_v1", "daemon_restart_v1", "daemon_update_registry_v1", "daemon_layout_migration_v1"]
17
+ ? ["daemon_update_v1", "daemon_restart_v1", "daemon_update_registry_v1"]
18
18
  : [];
19
19
  }
20
20
  catch {
@@ -1,3 +1,4 @@
1
+ import { deepSeekHarnessLauncher } from "./runtimes/deepseek-harness.js";
1
2
  import { createInterface } from "node:readline";
2
3
  import { readFile, rm, writeFile } from "node:fs/promises";
3
4
  import { delimiter, join } from "node:path";
@@ -569,7 +570,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
569
570
  });
570
571
  }
571
572
  const trainingRoots = input.trainingProjectContext?.projects.map((project) => project.root) ?? [];
572
- const { codexSkillRoots, claudeAdditionalDirectories } = await projectSkillRuntimeDirectories({
573
+ const { codexSkillRoots, claudeAdditionalDirectories, harnessSkillRoots } = await projectSkillRuntimeDirectories({
573
574
  runtime: runtime.name,
574
575
  ...(runtimeRoot === undefined ? {} : { runtimeRoot }),
575
576
  agentRoot: workspace.dir,
@@ -607,7 +608,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
607
608
  const workspaceRoots = [...legacyRoots, ...trainingRoots];
608
609
  const launchRequest = {
609
610
  runtime: runtime.name,
610
- bin: runtime.name === "deepseek-harness" ? "dsh-acp-demo" : runtime.name,
611
+ bin: runtime.name === "deepseek-harness" ? deepSeekHarnessLauncher(runtimeEnv).bin : runtime.name,
612
+ ...(runtime.name === "deepseek-harness" ? { harnessStateRoot: executionWorkspace.runDir, harnessSkillRoots: harnessSkillRoots ?? [] } : {}),
611
613
  cwd: input.projectContext?.primary?.root ?? executionWorkspace.runDir,
612
614
  ...(runtimeRoot !== undefined
613
615
  || (input.projectSkills === undefined && input.abilityRelease === undefined)
@@ -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";
@@ -18,7 +20,7 @@ import { probeKimiAcp } from "./runtimes/kimi-acp-runner.js";
18
20
  import { probeHermesAcp } from "./runtimes/hermes.js";
19
21
  import { probeOpenCodeRun } from "./runtimes/opencode.js";
20
22
  import { RUNTIME_HEALTH_PROBE_CAPABILITY } from "./runtime-health.js";
21
- import { probeDeepSeekHarnessAcp } from "./runtimes/deepseek-harness.js";
23
+ import { probeDeepSeekHarnessAcp, deepSeekHarnessLauncher } from "./runtimes/deepseek-harness.js";
22
24
  import { daemonHome } from "./computer-profile.js";
23
25
  import { daemonReleaseProvenance } from "./release-provenance.js";
24
26
  export { executableRuntimes } from "./runtime-capabilities.js";
@@ -90,7 +92,7 @@ const RUNTIME_BINS = [
90
92
  ["gemini", "gemini"],
91
93
  ["hermes", "hermes"],
92
94
  ["opencode", "opencode"],
93
- ["deepseek-harness", "dsh-acp-demo"],
95
+ ["deepseek-harness", "dsh"],
94
96
  ["copilot", "copilot"],
95
97
  ["kimi", "kimi"],
96
98
  ["pi", "pi"],
@@ -107,29 +109,60 @@ async function isInstalled(bin) {
107
109
  }
108
110
  /** 并发探测所有候选 runtime,返回已安装的展示名列表。 */
109
111
  export async function detectRuntimes() {
110
- const checks = await Promise.all(RUNTIME_BINS.map(async ([name, bin]) => ((await isInstalled(bin)) ? name : null)));
112
+ const checks = await Promise.all(RUNTIME_BINS.map(async ([name, bin]) => {
113
+ try {
114
+ return await isInstalled(name === "deepseek-harness" ? deepSeekHarnessLauncher().bin : bin) ? name : null;
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ }));
111
120
  return checks.filter((x) => x !== null);
112
121
  }
113
- async function supportsKimiAcp(signal) {
114
- return probeKimiAcp({ bin: "kimi", ...(signal ? { signal } : {}) });
122
+ async function supportsKimiAcp(signal, probeId) {
123
+ return probeKimiAcp({ bin: "kimi", ...(probeId ? { probeId } : {}), ...(signal ? { signal } : {}) });
115
124
  }
116
- async function supportsHermesAcp(signal) {
117
- return probeHermesAcp({ bin: "hermes", ...(signal ? { signal } : {}) });
125
+ async function supportsHermesAcp(signal, probeId) {
126
+ return probeHermesAcp({ bin: "hermes", ...(probeId ? { probeId } : {}), ...(signal ? { signal } : {}) });
118
127
  }
119
128
  async function supportsOpenCodeRun(signal) {
120
129
  return probeOpenCodeRun(signal);
121
130
  }
122
- async function supportsDeepSeekHarnessAcp(signal) {
123
- return probeDeepSeekHarnessAcp({ bin: "dsh-acp-demo", ...(signal ? { signal } : {}) });
131
+ async function supportsDeepSeekHarnessAcp(signal, probeId) {
132
+ try {
133
+ return await probeDeepSeekHarnessAcp({ bin: deepSeekHarnessLauncher().bin, ...(probeId ? { probeId } : {}), ...(signal ? { signal } : {}) });
134
+ }
135
+ catch {
136
+ return false;
137
+ }
124
138
  }
125
139
  /** Runtime adapters that can satisfy the durable protocol-v1 process contract. */
126
- 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()) {
127
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
+ };
128
161
  const [kimiReady, hermesReady, openCodeReady, deepSeekHarnessReady] = await Promise.all([
129
- present.includes("kimi") ? kimiAcpProbe(signal) : false,
130
- present.includes("hermes") ? hermesAcpProbe(signal) : false,
131
- present.includes("opencode") ? openCodeProbe(signal) : false,
132
- present.includes("deepseek-harness") ? deepSeekHarnessProbe(signal) : false,
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,
133
166
  ]);
134
167
  return executableRuntimes(present).filter((runtime) => (runtime === "kimi" ? kimiReady
135
168
  : runtime === "hermes" ? hermesReady
@@ -137,7 +170,7 @@ export async function detectExecutionRuntimes(installed = detectRuntimes(), kimi
137
170
  : runtime === "deepseek-harness" ? deepSeekHarnessReady
138
171
  : true));
139
172
  }
140
- 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);
141
174
  export function daemonVersion() {
142
175
  try {
143
176
  const here = dirname(fileURLToPath(import.meta.url));
package/dist/main.js CHANGED
@@ -7,7 +7,6 @@
7
7
  */
8
8
  import { parseArgs } from "node:util";
9
9
  import { homedir } from "node:os";
10
- import { join } from "node:path";
11
10
  import { loadConfig, ConfigError } from "./config.js";
12
11
  import { detectDaemonLang, formatDaemonText, translateDaemon } from "./i18n.js";
13
12
  import { cliVersion, daemonVersion } from "./machine-info.js";
@@ -25,19 +24,9 @@ import { assertManagedServiceStartup } from "./managed-service-startup.js";
25
24
  import { daemonGlobalInstallation } from "./daemon-installation.js";
26
25
  import { acquireDaemonInstallationLease, runWithDaemonInstallationLease, } from "./daemon-installation-lease.js";
27
26
  import { createStorageRuntimeOwnershipBinding, loadStorageRuntime } from "./storage-runtime.js";
28
- import { readStorageMigrationReceipt } from "./storage-migration-controller.js";
29
- import { storageMigrationStatusReplay } from "./storage-status-replay.js";
30
- import { runStorageWorkerCommand } from "./storage-worker-command.js";
31
- import { probeStorageMigrationRuntime } from "./storage-runtime-readiness.js";
32
- import { createStorageTargetConfirmationHandler } from "./storage-target-confirmation.js";
33
27
  import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
34
28
  import { storageServiceEnvironment } from "./computer-service.js";
35
29
  async function main() {
36
- const workerResult = await runStorageWorkerCommand(process.argv.slice(2));
37
- if (workerResult !== null) {
38
- process.exitCode = workerResult;
39
- return;
40
- }
41
30
  const computerResult = await runComputerCommand(process.argv.slice(2));
42
31
  if (computerResult !== null) {
43
32
  process.exitCode = computerResult;
@@ -74,7 +63,6 @@ async function main() {
74
63
  // 不把 machine token 放进 argv / plist / systemd unit / scheduled task。
75
64
  if (values["daemon-home"])
76
65
  process.env.CREW_DAEMON_HOME = values["daemon-home"];
77
- let activeDaemonHome;
78
66
  const localHome = resolveDaemonLocalStateRoot({
79
67
  userHome: homedir(),
80
68
  platform: process.platform,
@@ -85,7 +73,6 @@ async function main() {
85
73
  });
86
74
  if (values.profile) {
87
75
  const home = daemonHome(process.env, values.profile);
88
- activeDaemonHome = home;
89
76
  const profile = await loadProfile(values.profile, home);
90
77
  if (cmd === "serve") {
91
78
  await assertProfileAgentsRootUnique(profile, home, homedir());
@@ -148,68 +135,16 @@ async function main() {
148
135
  const installationLease = installation === null
149
136
  ? null
150
137
  : await acquireDaemonInstallationLease(installation.npmPrefix, { localHome });
151
- const migrationRuntime = values.profile === undefined || activeDaemonHome === undefined || storageRuntime !== null
152
- ? { ready: false, reason: "not_legacy_source" }
153
- : await probeStorageMigrationRuntime({
154
- profile: values.profile,
155
- serverUrl: config.serverUrl,
156
- currentDaemonHome: activeDaemonHome,
157
- currentAgentsRoot: config.agentsRoot,
158
- userHome: homedir(),
159
- platform: process.platform,
160
- ...(process.getuid === undefined ? {} : { uid: process.getuid() }),
161
- environment: process.env,
162
- });
163
- const targetStorageBinding = storageRuntime === null
138
+ const storageBinding = storageRuntime === null
164
139
  ? null
165
140
  : createStorageRuntimeOwnershipBinding(storageRuntime);
166
- const targetConfirmation = storageRuntime?.identity.storageMigrationId === undefined
167
- ? null
168
- : createStorageTargetConfirmationHandler({
169
- backupsRoot: storageRuntime.local.backupsRoot,
170
- identity: storageRuntime.identity,
171
- profile: storageRuntime.installation.profile,
172
- completionTimeoutMs: 180_000,
173
- });
174
- const storageMigration = storageRuntime !== null
175
- ? {
176
- ready: false,
177
- statusOutboxPath: join(storageRuntime.local.stateRoot, "storage-status-outbox.json"),
178
- identity: targetStorageBinding.identity,
179
- bindServerIdentity: targetStorageBinding.bind,
180
- ...(targetConfirmation === null ? {} : { confirm: targetConfirmation.handle }),
181
- ...(storageRuntime.identity.storageMigrationId === undefined ? {} : {
182
- replayStatus: async (send) => {
183
- const migrationId = storageRuntime.identity.storageMigrationId;
184
- const receipt = await readStorageMigrationReceipt(storageRuntime.local.backupsRoot, migrationId);
185
- if (receipt === null)
186
- throw new Error(`storage migration receipt not found: ${migrationId}`);
187
- for (const frame of storageMigrationStatusReplay(receipt, {
188
- migrationId,
189
- environment: storageRuntime.identity.storageEnvironment,
190
- workspaceId: storageRuntime.identity.storageWorkspaceId,
191
- profile: storageRuntime.installation.profile,
192
- }))
193
- send(frame);
194
- },
195
- }),
196
- }
197
- : migrationRuntime.ready
198
- ? {
199
- ready: true,
200
- statusOutboxPath: migrationRuntime.runtime.statusOutboxPath,
201
- identity: migrationRuntime.runtime.identity,
202
- controller: migrationRuntime.runtime.controller,
203
- attach: migrationRuntime.runtime.attach,
204
- bindServerIdentity: migrationRuntime.runtime.bindServerIdentity,
205
- replayStatus: migrationRuntime.runtime.replayStatus,
206
- }
207
- : undefined;
208
141
  await runWithDaemonInstallationLease(installationLease, async () => {
209
142
  const service = serve(runtimeConfig, {
210
143
  ...(values.profile === undefined ? {} : { profileName: values.profile }),
211
144
  localHome,
212
- ...(storageMigration === undefined ? {} : { storageMigration }),
145
+ ...(storageBinding === null ? {} : {
146
+ storageRuntime: { identity: storageBinding.identity, bindServerIdentity: storageBinding.bind },
147
+ }),
213
148
  });
214
149
  await runServeLifecycle(service);
215
150
  });
@@ -9,10 +9,11 @@ export function projectSkillExecutionProjection(bindings, generation) {
9
9
  });
10
10
  }
11
11
  /**
12
- * 投影门控与 ownership 判定的唯一谓词:只有绑定了 Project Skills 且运行时是 codex/claude 的执行
12
+ * 投影门控与 ownership 判定的唯一谓词:只有绑定了 Project Skills 且运行时支持受控目录的执行
13
13
  * 才会有投影目录,也才需要在停止无法证明时保留它。两处判定分开写过一次,结果 kimi + projectSkills
14
14
  * 会在取消路径上错抛 ownership 未确认。
15
15
  */
16
16
  export const projectSkillProjectionApplies = (spec) => spec.agent.projectSkills !== undefined
17
- && (spec.runtime.name === "codex" || spec.runtime.name === "claude");
17
+ && (spec.runtime.name === "codex" || spec.runtime.name === "claude"
18
+ || spec.runtime.name === "deepseek-harness");
18
19
  export const projectSkillProjectionErrorCode = (error) => error instanceof ProjectProjectionError ? error.code : null;
@@ -11,12 +11,26 @@ export function projectSkillRuntimeLaunchFacts(runtime, root) {
11
11
  if (runtime === "claude") {
12
12
  return Object.freeze({ claudeAdditionalDirectory: root.claudeAdditionalDirectory });
13
13
  }
14
+ if (runtime === "deepseek-harness") {
15
+ return Object.freeze({ harnessSkillRoot: root.codexSkillRoot });
16
+ }
14
17
  throw new ProjectProjectionError("skill_projection_failed");
15
18
  }
16
19
  /** Builds Runtime-native arrays while keeping v1 shared roots separate from v2 exact roots. */
17
20
  export async function projectSkillRuntimeDirectories(input) {
18
21
  const trainingProjectRoots = input.trainingProjectRoots ?? [];
19
22
  const facts = projectSkillRuntimeLaunchFacts(input.runtime, input.runtimeRoot);
23
+ if (input.runtime === "deepseek-harness") {
24
+ // Harness 的 local provider 接受直接包含 Skill 子目录的受控 root,不扫描项目/用户目录。
25
+ return Object.freeze({
26
+ codexSkillRoots: Object.freeze([]),
27
+ claudeAdditionalDirectories: Object.freeze([]),
28
+ harnessSkillRoots: Object.freeze([
29
+ ...(facts.harnessSkillRoot === undefined ? [] : [facts.harnessSkillRoot]),
30
+ ...(input.abilitySkillRoot === undefined ? [] : [input.abilitySkillRoot]),
31
+ ]),
32
+ });
33
+ }
20
34
  const codexSkillRoots = input.runtime === "codex"
21
35
  && (input.runtimeRoot !== undefined || input.projectContext !== undefined)
22
36
  ? Object.freeze([
@@ -10,7 +10,6 @@
10
10
  */
11
11
  import { join } from "node:path";
12
12
  import { DEEPSEEK_CODEX_KEY_ENV, deepSeekCodexHome, } from "./runtimes/codex-deepseek-config.js";
13
- import { DEEPSEEK_HARNESS_CONFIG_ENV } from "./runtimes/deepseek-harness.js";
14
13
  const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
15
14
  const RESERVED_ENV = new Set([
16
15
  "PATH",
@@ -96,9 +95,9 @@ export function applyProviderEnv(base, runtime, cfg, homeDir, deepSeekHomeOverri
96
95
  }
97
96
  return env;
98
97
  }
99
- const deepSeekHarnessMachineOwnedKey = (key) => (key.startsWith("DEEPSEEK_")
100
- || key === DEEPSEEK_HARNESS_CONFIG_ENV
101
- || key.startsWith("DSH_"));
98
+ const deepSeekHarnessMachineOwnedKey = (key) => (key.toUpperCase().startsWith("DEEPSEEK_")
99
+ || key.toUpperCase().startsWith("NOWCREW_DEEPSEEK_HARNESS_")
100
+ || key.toUpperCase().startsWith("DSH_"));
102
101
  /** Agent env remains available to tools, but cannot replace Harness process policy or credentials. */
103
102
  export function applyDeepSeekHarnessMachineEnv(base, machineEnv) {
104
103
  const env = { ...base };
@@ -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
- if (existing)
14
- return existing;
15
- const pending = detector(installed, abort.signal).catch((error) => {
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() {
@@ -0,0 +1,92 @@
1
+ /** 短命执行配置:不读取全局 Harness home,不新增 daemon 状态。 */
2
+ import { mkdtemp, mkdir, writeFile, access } from "node:fs/promises";
3
+ import { join, isAbsolute } from "node:path";
4
+ import { stringify } from "yaml";
5
+ export const harnessPermissionMode = (permission) => permission === "sandboxed"
6
+ ? "read-only"
7
+ : permission === "workspace_write"
8
+ ? "workspace-write"
9
+ : "danger-full-access";
10
+ // A variable's returned value is interpolated once: literal {{...}} in NowWork
11
+ // instructions must not become unknown Harness template variables.
12
+ const instructionsPlugin = `export const name = 'nowwork-instructions';
13
+ export const inject = ['systemPrompt'];
14
+ export function apply(ctx, config) {
15
+ ctx.systemPrompt.variable('nowwork_instructions', () => config.text);
16
+ ctx.systemPrompt.section({name: 'nowwork-instructions', order: 100000, text: '{{nowwork_instructions}}'});
17
+ }
18
+ `;
19
+ export async function prepareHarnessProfile(input) {
20
+ if (![input.stateRoot, input.cwd, ...input.skillRoots].every(isAbsolute)) {
21
+ throw new Error("DeepSeek Harness execution paths must be absolute");
22
+ }
23
+ if (input.patchPath !== undefined) {
24
+ if (!isAbsolute(input.patchPath))
25
+ throw new Error("DeepSeek Harness patch must be absolute");
26
+ await access(input.patchPath);
27
+ }
28
+ await mkdir(input.stateRoot, { recursive: true, mode: 0o700 });
29
+ const home = await mkdtemp(join(input.stateRoot, ".deepseek-harness-"));
30
+ const pluginPath = join(home, "nowwork-instructions.mjs");
31
+ const policyPath = join(home, "nowwork-policy.yml");
32
+ const mode = harnessPermissionMode(input.effectivePermission);
33
+ await writeFile(pluginPath, instructionsPlugin, { mode: 0o600 });
34
+ await writeFile(policyPath, stringify([
35
+ {
36
+ id: "sandbox-policy",
37
+ disabled: false,
38
+ config: { mode, workspaceRoot: input.cwd },
39
+ },
40
+ { id: "approval", disabled: false, config: { policy: "never" } },
41
+ {
42
+ id: "permission",
43
+ disabled: false,
44
+ config: {
45
+ defaultPreset: "nowwork",
46
+ presets: { nowwork: { sandbox: mode, approval: "never" } },
47
+ },
48
+ },
49
+ {
50
+ id: "skill-filesystem",
51
+ disabled: false,
52
+ config: {
53
+ includeDefaultRoots: false,
54
+ customSkillDirs: [...input.skillRoots],
55
+ watch: false,
56
+ },
57
+ },
58
+ {
59
+ id: "session-persistence-jsonl",
60
+ disabled: false,
61
+ config: { root: join(home, "sessions") },
62
+ },
63
+ {
64
+ insert: [
65
+ {
66
+ id: "nowwork-instructions",
67
+ name: pluginPath,
68
+ config: { text: input.systemPrompt },
69
+ },
70
+ ],
71
+ },
72
+ ]), { mode: 0o600 });
73
+ const env = Object.fromEntries(Object.entries(input.env).filter(([key]) => !key.toUpperCase().startsWith("DSH_")));
74
+ return {
75
+ home,
76
+ policyPath,
77
+ args: [
78
+ "--profile",
79
+ "acp",
80
+ ...(input.patchPath ? ["--patch", input.patchPath] : []),
81
+ "--patch",
82
+ policyPath,
83
+ ],
84
+ env: {
85
+ ...env,
86
+ DSH_HOME: home,
87
+ DSH_AGENTS_HOME: join(home, "agents-home"),
88
+ DSH_PERMISSION_MODE: mode,
89
+ DSH_TELEMETRY_DISABLED: "1",
90
+ },
91
+ };
92
+ }