@nowcrew/daemon 0.6.88-beta.2 → 0.6.88-beta.21

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 (81) hide show
  1. package/README.md +43 -13
  2. package/dist/agent-memory/policy.js +3 -1
  3. package/dist/computer-profile.js +9 -4
  4. package/dist/config.js +0 -9
  5. package/dist/daemon-update-eligibility.js +1 -1
  6. package/dist/execution-journal.js +9 -6
  7. package/dist/execution-protocol.js +2 -1
  8. package/dist/execution-runner.js +5 -4
  9. package/dist/list-models.js +2 -0
  10. package/dist/local-execution-lease.js +22 -0
  11. package/dist/local-executor.js +58 -45
  12. package/dist/local-memory/candidate.js +43 -0
  13. package/dist/local-memory/index-budget.js +8 -0
  14. package/dist/local-memory/inventory-cli.js +61 -0
  15. package/dist/local-memory/inventory-preview.js +154 -0
  16. package/dist/local-memory/inventory-source.js +159 -0
  17. package/dist/local-memory/pending-match.js +29 -0
  18. package/dist/local-memory/pending-query.js +100 -0
  19. package/dist/local-memory/pending-schema.js +29 -0
  20. package/dist/local-memory/pending-source.js +126 -0
  21. package/dist/local-memory/pending.js +34 -84
  22. package/dist/local-memory/prompt.js +14 -11
  23. package/dist/local-memory/read-cli.js +97 -12
  24. package/dist/local-memory/runtime.js +24 -17
  25. package/dist/local-memory/work-log-execution.js +31 -0
  26. package/dist/local-memory/work-log.js +58 -0
  27. package/dist/local-memory/write.js +27 -58
  28. package/dist/machine-info.js +54 -17
  29. package/dist/main.js +4 -69
  30. package/dist/native-thread-context.js +59 -0
  31. package/dist/pre-admission-rejection.js +64 -0
  32. package/dist/project-skills/execution-adapter.js +3 -2
  33. package/dist/project-skills/runtime-launch.js +14 -0
  34. package/dist/prompt.js +15 -15
  35. package/dist/provider-env.js +3 -6
  36. package/dist/runner.js +14 -16
  37. package/dist/runtime-probe.js +34 -5
  38. package/dist/runtimes/acp-probe-transport.js +69 -0
  39. package/dist/runtimes/claude-session-start-hook.js +24 -0
  40. package/dist/runtimes/claude-thread-runner.js +266 -0
  41. package/dist/runtimes/claude.js +23 -4
  42. package/dist/runtimes/codex-app-server-runner.js +43 -4
  43. package/dist/runtimes/deepseek-harness-profile.js +92 -0
  44. package/dist/runtimes/deepseek-harness-runner.js +224 -0
  45. package/dist/runtimes/deepseek-harness.js +64 -11
  46. package/dist/runtimes/harness-probe-retry.js +42 -0
  47. package/dist/runtimes/kimi-acp-runner.js +63 -10
  48. package/dist/runtimes/opencode-runner.js +7 -1
  49. package/dist/serve.js +59 -101
  50. package/dist/session.js +4 -0
  51. package/dist/storage-identity.js +0 -3
  52. package/dist/storage-serve-adapter.js +6 -72
  53. package/dist/supervised-runtime.js +28 -18
  54. package/dist/thread-input-delivery.js +217 -0
  55. package/package.json +2 -3
  56. package/dist/codex-home-launch.js +0 -7
  57. package/dist/codex-home-mode-controller.js +0 -68
  58. package/dist/daemon-migration-controller.js +0 -211
  59. package/dist/daemon-migration-wiring.js +0 -22
  60. package/dist/runtimes/codex-home-migration-cli.js +0 -26
  61. package/dist/runtimes/codex-home-migration.js +0 -112
  62. package/dist/runtimes/codex-home.js +0 -236
  63. package/dist/storage-control.js +0 -212
  64. package/dist/storage-copy.js +0 -628
  65. package/dist/storage-credential-guard.js +0 -21
  66. package/dist/storage-doctor.js +0 -228
  67. package/dist/storage-manifest.js +0 -1027
  68. package/dist/storage-migration-controller.js +0 -440
  69. package/dist/storage-migration-manifest.js +0 -105
  70. package/dist/storage-migration-policy.js +0 -100
  71. package/dist/storage-runtime-controller.js +0 -381
  72. package/dist/storage-runtime-readiness.js +0 -240
  73. package/dist/storage-safe-fs.js +0 -302
  74. package/dist/storage-status-delivery.js +0 -224
  75. package/dist/storage-status-replay.js +0 -140
  76. package/dist/storage-target-confirmation.js +0 -129
  77. package/dist/storage-worker-command.js +0 -37
  78. package/dist/storage-worker-executor.js +0 -449
  79. package/dist/storage-worker-handoff.js +0 -151
  80. package/dist/storage-worker-operation.js +0 -294
  81. package/dist/storage-worker-status.js +0 -259
@@ -0,0 +1,31 @@
1
+ import { resolve } from "node:path";
2
+ import { withKeyedLease } from "../local-execution-lease.js";
3
+ // 只记录尚未结束的本机执行,最后一个执行结束即移除,不落盘。
4
+ const activeExecutions = new Map();
5
+ export function workLogExecutionKey(agentsRoot, handle, taskKey) {
6
+ return `work-log:${JSON.stringify([resolve(agentsRoot, handle), taskKey ?? "default"])}`;
7
+ }
8
+ export async function withWorkLogExecution(key, operation, cancellation) {
9
+ // 只在登记期间持锁;普通任务与收尾的模型调用仍可并行。
10
+ await withKeyedLease(key, async () => {
11
+ activeExecutions.set(key, (activeExecutions.get(key) ?? 0) + 1);
12
+ }, cancellation);
13
+ try {
14
+ return await operation();
15
+ }
16
+ finally {
17
+ const remaining = (activeExecutions.get(key) ?? 1) - 1;
18
+ if (remaining === 0)
19
+ activeExecutions.delete(key);
20
+ else
21
+ activeExecutions.set(key, remaining);
22
+ }
23
+ }
24
+ export async function withWorkLogCommit(key, operation, cancellation) {
25
+ return withKeyedLease(key, async () => {
26
+ // 已有同任务执行可能继续写日志,不能提交旧提案;新执行等短提交结束后再登记。
27
+ if (activeExecutions.get(key) !== 1)
28
+ throw new Error("local_memory_work_log_execution_conflict");
29
+ return operation();
30
+ }, cancellation);
31
+ }
@@ -0,0 +1,58 @@
1
+ import { lstat, readFile, realpath } from "node:fs/promises";
2
+ import { createHash } from "node:crypto";
3
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
4
+ import { safeKey } from "../workspace.js";
5
+ /** 收尾会话可以换目录,但仍应消费同一任务已写好的日志;不迁移文件或复用会话。 */
6
+ export async function resolveMemoryPruneWorkLog(workspace, input) {
7
+ // 旧映射有损时无法证明是同一任务,不能用它查找或清理来源。
8
+ if (input.keyMode !== "opaque" || !input.taskKey || safeKey(input.taskKey) !== input.taskKey) {
9
+ return workspace;
10
+ }
11
+ const legacyPath = join(workspace.dir, "tasks", input.taskKey, "work-log.md");
12
+ if (legacyPath === workspace.workLogPath)
13
+ return workspace;
14
+ let legacyStat;
15
+ try {
16
+ legacyStat = await lstat(legacyPath);
17
+ }
18
+ catch (error) {
19
+ if (error.code === "ENOENT")
20
+ return workspace;
21
+ throw error;
22
+ }
23
+ if (!legacyStat.isFile() || legacyStat.isSymbolicLink()
24
+ || await realpath(legacyPath) !== join(await realpath(workspace.dir), "tasks", input.taskKey, "work-log.md")) {
25
+ throw new Error("local_memory_work_log_path_invalid");
26
+ }
27
+ const workLog = await readFile(legacyPath, "utf8");
28
+ if (workLog.length === 0)
29
+ return workspace;
30
+ if ((await readFile(workspace.workLogPath, "utf8")).length > 0) {
31
+ throw new Error("local_memory_work_log_source_conflict");
32
+ }
33
+ return { ...workspace, workLogPath: legacyPath, workLog };
34
+ }
35
+ export function workLogSha256(content) {
36
+ return createHash("sha256").update(content).digest("hex");
37
+ }
38
+ /** 调用方须在同任务的提交互斥边界内校验;指纹对应实际注入模型的那一版内容。 */
39
+ export async function verifyWorkLogSource(agentDir, path, expectedSha256) {
40
+ if (expectedSha256 === undefined)
41
+ throw new Error("local_memory_work_log_version_missing");
42
+ const relativePath = relative(resolve(agentDir), resolve(path));
43
+ if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) {
44
+ throw new Error("local_memory_work_log_path_invalid");
45
+ }
46
+ const before = await lstat(path);
47
+ if (!before.isFile() || before.isSymbolicLink()
48
+ || await realpath(path) !== join(await realpath(agentDir), relativePath)) {
49
+ throw new Error("local_memory_work_log_path_invalid");
50
+ }
51
+ const content = await readFile(path);
52
+ const after = await lstat(path);
53
+ if (workLogSha256(content) !== expectedSha256 || before.dev !== after.dev || before.ino !== after.ino
54
+ || before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs) {
55
+ throw new Error("local_memory_work_log_source_changed");
56
+ }
57
+ return after;
58
+ }
@@ -2,29 +2,12 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import { lstat, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
3
3
  import { dirname, join, relative, resolve, sep } from "node:path";
4
4
  import { z } from "zod";
5
- import { containsLikelySecret } from "../agent-memory/policy.js";
6
- import { LOCAL_MEMORY_INDEX_CODE_POINT_CAP, LOCAL_MEMORY_INDEX_ENTRY_CAP, LOCAL_MEMORY_TOTAL_CODE_POINT_CAP, renderLocalMemoryIndexCard, selectLocalMemoryIndexEntries, } from "./index-budget.js";
5
+ import { dslog } from "../slog.js";
6
+ import { LocalMemoryChangeSchema, LocalMemorySingleLineText, validateLocalMemoryChange, } from "./candidate.js";
7
+ import { LOCAL_MEMORY_INDEX_CODE_POINT_CAP, LOCAL_MEMORY_INDEX_ENTRY_CAP, LOCAL_MEMORY_TOTAL_CODE_POINT_CAP, renderGeneratedIndex, selectLocalMemoryIndexEntries, } from "./index-budget.js";
7
8
  import { persistPendingMemoryCandidate, readPendingMemoryCandidates, removePendingMemoryCandidate, } from "./pending.js";
8
- const CategorySchema = z.enum([
9
- "preferences", "project-rules", "testing", "debugging", "git", "incidents", "lessons",
10
- ]);
11
- const SingleLineText = z.string().trim().min(1).max(500).regex(/^[^\r\n]+$/u);
12
- const ChangeSchema = z.object({
13
- operation: z.enum(["create", "update", "supersede"]),
14
- memoryId: SingleLineText.regex(/^[^<>]+$/u).nullable(),
15
- baseRevision: z.number().int().nonnegative().nullable(),
16
- supersedesMemoryId: SingleLineText.regex(/^[^<>]+$/u).nullable(),
17
- title: SingleLineText.max(120),
18
- kind: z.enum(["procedural", "semantic", "episodic", "preference"]),
19
- summary: SingleLineText,
20
- triggers: z.array(SingleLineText.max(80)).min(1).max(16),
21
- priority: z.enum(["high", "medium", "low"]),
22
- detailCategory: CategorySchema,
23
- detailBody: z.string().trim().min(1).max(12_000),
24
- reviewAfter: SingleLineText.nullable(),
25
- }).strict();
26
9
  const StatusChangeSchema = z.object({
27
- memoryId: SingleLineText,
10
+ memoryId: LocalMemorySingleLineText,
28
11
  expectedRevision: z.number().int().positive(),
29
12
  expectedStatus: z.enum(["candidate", "active", "pinned", "stale", "superseded", "archived"]),
30
13
  targetStatus: z.enum(["candidate", "active", "pinned", "stale", "archived"]),
@@ -35,7 +18,7 @@ const ResultSchema = z.object({
35
18
  purpose: z.literal("memory_prune"),
36
19
  baseCatalogRevision: z.number().int().nonnegative(),
37
20
  outcome: z.enum(["no_change", "proposed", "conflict", "failed"]),
38
- changes: z.array(ChangeSchema).max(50),
21
+ changes: z.array(LocalMemoryChangeSchema).max(50),
39
22
  statusChanges: z.array(StatusChangeSchema).max(50),
40
23
  }).strict();
41
24
  const GENERATED_INDEX_PATTERN = /<!--\s*NOWWORK:MEMORY_INDEX:BEGIN\s+schema=1\s*-->[\s\S]*?<!--\s*NOWWORK:MEMORY_INDEX:END\s*-->/u;
@@ -43,8 +26,6 @@ const INDEX_HEADING = /^##\s+Knowledge Index\s*$/mu;
43
26
  const NEXT_SECTION = /^##\s+(?!#)/mu;
44
27
  const CARD_HEADING = /^###\s+(.+?)\s*$/gmu;
45
28
  const FIELD_LINE = /^-\s+([a-z][a-z_]*)\s*:\s*(.*?)\s*$/iu;
46
- const CONTROL_CHARACTER_PATTERN = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u;
47
- const RESERVED_MEMORY_MARKER = /NOWWORK:(?:MEMORY_ENTRY|MEMORY_INDEX):/iu;
48
29
  function codePointLength(value) {
49
30
  return Array.from(value).length;
50
31
  }
@@ -117,13 +98,6 @@ function parseUnmarkedMemoryIndex(memory) {
117
98
  const section = indexSection(memory).replace(GENERATED_INDEX_PATTERN, "");
118
99
  return parseCards(section);
119
100
  }
120
- function renderGeneratedIndex(entries) {
121
- const cards = entries
122
- .filter((entry) => entry.status === "pinned" || entry.status === "active")
123
- .map(renderLocalMemoryIndexCard)
124
- .join("\n");
125
- return `<!-- NOWWORK:MEMORY_INDEX:BEGIN schema=1 -->\n${cards}<!-- NOWWORK:MEMORY_INDEX:END -->`;
126
- }
127
101
  function replaceGeneratedIndex(memory, entries) {
128
102
  const generated = renderGeneratedIndex(entries);
129
103
  if (GENERATED_INDEX_PATTERN.test(memory))
@@ -135,9 +109,15 @@ function replaceGeneratedIndex(memory, entries) {
135
109
  }
136
110
  return `${memory.trimEnd()}\n\n## Knowledge Index\n${generated}\n`;
137
111
  }
112
+ function unwrappedNoteBody(title, body) {
113
+ const detail = body.trim();
114
+ const heading = /^([^\r\n]+)\r?\n\r?\n/u.exec(detail);
115
+ // Agent 可能回传包含包装标题的全文;只解开一层,保留内部及历史标题。
116
+ return heading?.[1] === `## ${title}` ? detail.slice(heading[0].length) : detail;
117
+ }
138
118
  function noteSection(id, revision, title, body) {
139
119
  return `<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${id} revision=${revision} -->\n`
140
- + `## ${title}\n\n${body.trim()}\n`
120
+ + `## ${title}\n\n${unwrappedNoteBody(title, body)}\n`
141
121
  + `<!-- NOWWORK:MEMORY_ENTRY:END id=${id} -->\n`;
142
122
  }
143
123
  function notePattern(id) {
@@ -148,34 +128,12 @@ function replaceNote(content, id, replacement) {
148
128
  const pattern = notePattern(id);
149
129
  return pattern.test(content) ? content.replace(pattern, replacement) : null;
150
130
  }
151
- function containsReservedText(change) {
152
- return RESERVED_MEMORY_MARKER.test([change.title, change.summary, ...change.triggers, change.detailBody].join("\n"));
153
- }
154
131
  function validateCandidate(result) {
155
132
  if (result.outcome === "no_change" && (result.changes.length > 0 || result.statusChanges.length > 0)) {
156
133
  throw new Error("local_memory_no_change_has_operations");
157
134
  }
158
- for (const change of result.changes) {
159
- const text = [change.title, change.summary, ...change.triggers, change.detailBody].join("\n");
160
- if (containsLikelySecret(text))
161
- throw new Error("local_memory_secret_candidate");
162
- if (CONTROL_CHARACTER_PATTERN.test(text))
163
- throw new Error("local_memory_control_character");
164
- if (containsReservedText(change))
165
- throw new Error("local_memory_reserved_marker");
166
- if (change.operation === "create"
167
- && (change.memoryId !== null || change.baseRevision !== null || change.supersedesMemoryId !== null)) {
168
- throw new Error("local_memory_create_revision_invalid");
169
- }
170
- if (change.operation === "update"
171
- && (change.memoryId === null || change.baseRevision === null || change.supersedesMemoryId !== null)) {
172
- throw new Error("local_memory_update_revision_missing");
173
- }
174
- if (change.operation === "supersede"
175
- && (change.memoryId !== null || change.baseRevision === null || change.supersedesMemoryId === null)) {
176
- throw new Error("local_memory_supersede_revision_missing");
177
- }
178
- }
135
+ for (const change of result.changes)
136
+ validateLocalMemoryChange(change);
179
137
  }
180
138
  export function parseLocalMemoryResult(raw) {
181
139
  let value;
@@ -477,7 +435,16 @@ export async function applyLocalMemoryResult(input) {
477
435
  // Pending create candidates are durable overflow, not dead ends. Promote only
478
436
  // when the current catalog has room; supersede/update candidates stay pending
479
437
  // until a fresh revision-aware decision can safely replay them.
480
- const pendingCandidatesOnDisk = await readPendingMemoryCandidates({ agentDir: input.agentDir });
438
+ const pendingCandidatesOnDisk = await readPendingMemoryCandidates({ agentDir: input.agentDir }).catch((error) => {
439
+ if (!(error instanceof Error) || error.message !== "memory_pending_directory_over_budget")
440
+ throw error;
441
+ // 自动提升是可选工作,目录查询预算不应阻断本轮已验证的更新或腾位操作。
442
+ dslog("local_memory.pending_promotion_skipped", "pending目录超出扫描预算,本轮跳过自动提升", {
443
+ level: "WARN", execution_id: input.executionId, prune_trace_id: input.pruneTraceId,
444
+ reason: "memory_pending_directory_over_budget",
445
+ });
446
+ return [];
447
+ });
481
448
  for (const pending of pendingCandidatesOnDisk) {
482
449
  const change = pending.candidate;
483
450
  if (change.operation !== "create")
@@ -494,8 +461,10 @@ export async function applyLocalMemoryResult(input) {
494
461
  if (duplicate !== undefined) {
495
462
  const duplicatePath = resolve(input.agentDir, duplicate.file);
496
463
  const duplicateBody = readNoteBody(noteUpdates.get(duplicatePath) ?? await readNote(duplicatePath), duplicate.id);
497
- if (duplicateBody === change.detailBody)
464
+ if (duplicateBody === change.detailBody
465
+ || duplicateBody === unwrappedNoteBody(change.title, change.detailBody).trim()) {
498
466
  resolvedPendingIds.add(pending.pendingId);
467
+ }
499
468
  continue;
500
469
  }
501
470
  const entry = entryFromChange(change, id, 1, "active");
@@ -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,11 +17,11 @@ 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";
21
- import { probeDeepSeekHarnessAcp } from "./runtimes/deepseek-harness.js";
24
+ import { probeDeepSeekHarnessAcp, deepSeekHarnessLauncher } from "./runtimes/deepseek-harness.js";
22
25
  import { daemonHome } from "./computer-profile.js";
23
26
  import { daemonReleaseProvenance } from "./release-provenance.js";
24
27
  export { executableRuntimes } from "./runtime-capabilities.js";
@@ -90,7 +93,7 @@ const RUNTIME_BINS = [
90
93
  ["gemini", "gemini"],
91
94
  ["hermes", "hermes"],
92
95
  ["opencode", "opencode"],
93
- ["deepseek-harness", "dsh-acp-demo"],
96
+ ["deepseek-harness", "dsh"],
94
97
  ["copilot", "copilot"],
95
98
  ["kimi", "kimi"],
96
99
  ["pi", "pi"],
@@ -107,29 +110,64 @@ async function isInstalled(bin) {
107
110
  }
108
111
  /** 并发探测所有候选 runtime,返回已安装的展示名列表。 */
109
112
  export async function detectRuntimes() {
110
- const checks = await Promise.all(RUNTIME_BINS.map(async ([name, bin]) => ((await isInstalled(bin)) ? name : null)));
113
+ const checks = await Promise.all(RUNTIME_BINS.map(async ([name, bin]) => {
114
+ try {
115
+ return await isInstalled(name === "deepseek-harness" ? deepSeekHarnessLauncher().bin : bin) ? name : null;
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ }));
111
121
  return checks.filter((x) => x !== null);
112
122
  }
113
- async function supportsKimiAcp(signal) {
114
- return probeKimiAcp({ bin: "kimi", ...(signal ? { signal } : {}) });
123
+ async function supportsKimiAcp(signal, probeId) {
124
+ return probeKimiAcp({ bin: "kimi", ...(probeId ? { probeId } : {}), ...(signal ? { signal } : {}) });
115
125
  }
116
- async function supportsHermesAcp(signal) {
117
- return probeHermesAcp({ bin: "hermes", ...(signal ? { signal } : {}) });
126
+ async function supportsHermesAcp(signal, probeId) {
127
+ return probeHermesAcp({ bin: "hermes", ...(probeId ? { probeId } : {}), ...(signal ? { signal } : {}) });
118
128
  }
119
129
  async function supportsOpenCodeRun(signal) {
120
130
  return probeOpenCodeRun(signal);
121
131
  }
122
- async function supportsDeepSeekHarnessAcp(signal) {
123
- return probeDeepSeekHarnessAcp({ bin: "dsh-acp-demo", ...(signal ? { signal } : {}) });
132
+ async function supportsDeepSeekHarnessAcp(signal, probeId) {
133
+ try {
134
+ return await probeDeepSeekHarnessAcp({ bin: deepSeekHarnessLauncher().bin, ...(probeId ? { probeId } : {}), ...(signal ? { signal } : {}) });
135
+ }
136
+ catch (error) {
137
+ if (error instanceof AcpProbeCleanupError)
138
+ throw error;
139
+ return false;
140
+ }
124
141
  }
125
142
  /** 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) {
143
+ export async function detectExecutionRuntimes(installed = detectRuntimes(), kimiAcpProbe = supportsKimiAcp, hermesAcpProbe = supportsHermesAcp, openCodeProbe = supportsOpenCodeRun, deepSeekHarnessProbe = supportsDeepSeekHarnessAcp, signal, probeId = randomUUID()) {
127
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
+ };
128
164
  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,
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,
133
171
  ]);
134
172
  return executableRuntimes(present).filter((runtime) => (runtime === "kimi" ? kimiReady
135
173
  : runtime === "hermes" ? hermesReady
@@ -137,7 +175,7 @@ export async function detectExecutionRuntimes(installed = detectRuntimes(), kimi
137
175
  : runtime === "deepseek-harness" ? deepSeekHarnessReady
138
176
  : true));
139
177
  }
140
- 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);
141
179
  export function daemonVersion() {
142
180
  try {
143
181
  const here = dirname(fileURLToPath(import.meta.url));
@@ -203,7 +241,6 @@ export async function collectMachineHello(agentsRoot, executionLimits, runtimePl
203
241
  runtimes,
204
242
  executionRuntimes,
205
243
  capabilities,
206
- ...(dependencies.codexHomeMode === undefined ? {} : { codexHomeMode: dependencies.codexHomeMode }),
207
244
  ...(dependencies.profileName === undefined ? {} : {
208
245
  layoutVersion: 1,
209
246
  profileName: dependencies.profileName,
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
  });
@@ -0,0 +1,59 @@
1
+ import { readFile, writeFile, rm } from "node:fs/promises";
2
+ export function isolateMaintenanceSession(input) {
3
+ if (input.maintenancePurpose === undefined)
4
+ return input;
5
+ const { resumeKey: _ignored, ...rest } = input;
6
+ return { ...rest, session: { ...input.session, enabled: false } };
7
+ }
8
+ /** A per-execution runtime handoff, not a transcript or daemon business store. */
9
+ export class NativeThreadContext {
10
+ path;
11
+ threadRoot;
12
+ runtime;
13
+ requestedSessionId;
14
+ prior;
15
+ confirmed = false;
16
+ constructor(path, threadRoot, runtime, requestedSessionId, prior) {
17
+ this.path = path;
18
+ this.threadRoot = threadRoot;
19
+ this.runtime = runtime;
20
+ this.requestedSessionId = requestedSessionId;
21
+ this.prior = prior;
22
+ }
23
+ snapshot() {
24
+ const reusable = this.requestedSessionId !== null && this.prior?.sessionId === this.requestedSessionId
25
+ && this.prior?.threadRoot === this.threadRoot && this.prior?.runtime === this.runtime;
26
+ return { schemaVersion: 1, threadRoot: this.threadRoot, resumeConfirmed: this.confirmed,
27
+ historyComplete: false, readSeq: 0,
28
+ resumeCandidate: reusable ? { sessionId: this.requestedSessionId,
29
+ historyComplete: this.prior?.threadHistoryComplete === true, readSeq: this.prior?.threadContextSeq ?? 0 } : null };
30
+ }
31
+ async initialize() { await writeFile(this.path, JSON.stringify(this.snapshot()), { mode: 0o600 }); }
32
+ observe(raw) {
33
+ if (this.confirmed || !this.requestedSessionId || !raw || typeof raw !== "object")
34
+ return;
35
+ const event = raw;
36
+ const confirmed = (this.runtime === "claude" || this.runtime === "codex")
37
+ && event.type === "nowwork.native_resume" && event.session_id === this.requestedSessionId
38
+ && (event.runtime === undefined || event.runtime === this.runtime);
39
+ if (!confirmed)
40
+ return;
41
+ this.confirmed = true;
42
+ // Telemetry only. The native adapter verifies its handshake directly and
43
+ // commits accepted input itself. Writing here could race that commit and
44
+ // reset the delivered cursor when parent stdout consumption is delayed.
45
+ }
46
+ async checkpoint() {
47
+ try {
48
+ const state = JSON.parse(await readFile(this.path, "utf8"));
49
+ if (state.schemaVersion !== 1 || state.threadRoot !== this.threadRoot
50
+ || !Number.isSafeInteger(state.readSeq) || state.readSeq < 0)
51
+ return null;
52
+ return { threadContextSeq: state.readSeq, threadHistoryComplete: state.historyComplete === true };
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ async cleanup() { await rm(this.path, { force: true }); }
59
+ }
@@ -0,0 +1,64 @@
1
+ import { ExecutionCompletedSchema, ExecutionRejectedSchema } from "./execution-protocol.js";
2
+ import { hashExecutionSpec } from "./execution-runner.js";
3
+ import { dslog } from "./slog.js";
4
+ /** Queue rejection is admission work too: own the ID before any asynchronous journal write. */
5
+ export async function rejectQueuedAdmission(context, spec, executionClass, facts) {
6
+ const id = spec.executionId;
7
+ const hash = hashExecutionSpec(spec);
8
+ if (context.knownExecutionHashes.has(id) || context.executionRuns.has(id)) {
9
+ // A same-ID request won while the caller awaited journal.get. Its journal may
10
+ // still be pending; silence lets its real result arrive instead of rejecting it.
11
+ const existing = await context.journal.get(id);
12
+ if (existing?.specHash === hash)
13
+ context.replay(existing);
14
+ return;
15
+ }
16
+ context.knownExecutionHashes.set(id, hash);
17
+ const rejected = ExecutionRejectedSchema.parse({
18
+ type: "execution:rejected", protocolVersion: 1, executionId: id,
19
+ reason: "resource_limit", message: "Local machine execution queue is full", at: new Date().toISOString(),
20
+ });
21
+ dslog("execution.machine_queue_rejected", "机器执行队列已满", {
22
+ level: "WARN", execution_id: id, agent_handle: spec.agent.handle, execution_class: executionClass, ...facts,
23
+ });
24
+ const rejecting = persistPreAdmissionRejection(context.journal, spec, rejected)
25
+ .then(async (entry) => {
26
+ if (entry)
27
+ await context.report({ ...rejected, preAdmissionProof: { specHash: hash } });
28
+ else {
29
+ const existing = await context.journal.get(id);
30
+ if (existing?.specHash === hash)
31
+ context.replay(existing);
32
+ }
33
+ })
34
+ .catch(error => {
35
+ // A failed durable write has no safe rejection proof; a restart/sync will
36
+ // reconcile an accepted tombstone if this admission created one.
37
+ dslog("execution.admission_rejection_failed", "准入拒绝未取得持久停止证明", {
38
+ level: "ERROR", execution_id: id, error_message: String(error),
39
+ });
40
+ })
41
+ .finally(() => {
42
+ if (context.executionRuns.get(id) === rejecting) {
43
+ context.executionRuns.delete(id);
44
+ context.knownExecutionHashes.delete(id);
45
+ }
46
+ });
47
+ context.executionRuns.set(id, rejecting);
48
+ await rejecting;
49
+ }
50
+ /** The caller synchronously owns this admission ID. Never complete somebody else's record. */
51
+ export async function persistPreAdmissionRejection(journal, spec, rejection) {
52
+ const accepted = await journal.accept(spec.executionId, hashExecutionSpec(spec), {
53
+ runtime: spec.runtime.name, ...(spec.runtime.model ? { model: spec.runtime.model } : {}), resumed: false,
54
+ });
55
+ if (accepted.kind !== "created")
56
+ return null;
57
+ return journal.complete(spec.executionId, ExecutionCompletedSchema.parse({
58
+ type: "execution:completed", protocolVersion: 1, executionId: spec.executionId,
59
+ outcome: "failed", errorCode: rejection.reason, errorMessage: rejection.message ?? "Admission rejected before runtime start",
60
+ runtime: spec.runtime.name, ...(spec.runtime.model ? { model: spec.runtime.model } : {}), resumed: false,
61
+ startedAt: accepted.acceptedAt,
62
+ finishedAt: new Date(Math.max(Date.now(), Date.parse(accepted.acceptedAt))).toISOString(),
63
+ }));
64
+ }
@@ -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([