@nowcrew/daemon 0.6.89 → 0.6.91-beta.1

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 (116) hide show
  1. package/README.md +64 -3
  2. package/dist/agent-ability/materializer.js +21 -10
  3. package/dist/agent-environment-contract.js +55 -0
  4. package/dist/agent-environment-control.js +24 -0
  5. package/dist/agent-environment-execution.js +98 -0
  6. package/dist/agent-environments.js +275 -0
  7. package/dist/automatic-daemon-installer.js +49 -1
  8. package/dist/computer-main.js +18 -0
  9. package/dist/computer-profile.js +17 -4
  10. package/dist/computer-setup-client.js +318 -0
  11. package/dist/computer-setup-orchestration.js +203 -0
  12. package/dist/computer-setup.js +244 -0
  13. package/dist/config.js +30 -12
  14. package/dist/console-formatter.js +3 -3
  15. package/dist/console.js +15 -2
  16. package/dist/daemon-update-eligibility.js +1 -1
  17. package/dist/daemon-updater.js +2 -12
  18. package/dist/execution-journal.js +9 -6
  19. package/dist/execution-protocol.js +1 -0
  20. package/dist/execution-runner.js +13 -8
  21. package/dist/i18n.js +19 -0
  22. package/dist/list-models.js +12 -12
  23. package/dist/local-execution-lease.js +22 -0
  24. package/dist/local-executor.js +57 -84
  25. package/dist/local-memory/candidate.js +43 -0
  26. package/dist/local-memory/index-budget.js +8 -0
  27. package/dist/local-memory/inventory-cli.js +61 -0
  28. package/dist/local-memory/inventory-preview.js +159 -0
  29. package/dist/local-memory/inventory-source.js +159 -0
  30. package/dist/local-memory/pending-match.js +29 -0
  31. package/dist/local-memory/pending-query.js +100 -0
  32. package/dist/local-memory/pending-schema.js +29 -0
  33. package/dist/local-memory/pending-source.js +126 -0
  34. package/dist/local-memory/pending.js +34 -84
  35. package/dist/local-memory/prompt.js +19 -12
  36. package/dist/local-memory/read-cli.js +97 -12
  37. package/dist/local-memory/rollout.js +82 -0
  38. package/dist/local-memory/runtime.js +23 -17
  39. package/dist/local-memory/work-log-execution.js +31 -0
  40. package/dist/local-memory/work-log.js +58 -0
  41. package/dist/local-memory/write.js +27 -58
  42. package/dist/local-runtime-environment.js +50 -0
  43. package/dist/machine-environment-cache.js +147 -0
  44. package/dist/machine-environment-contract.js +10 -0
  45. package/dist/machine-environment-control.js +27 -0
  46. package/dist/machine-environment-policy.js +75 -0
  47. package/dist/machine-environment-process.js +110 -0
  48. package/dist/machine-info.js +70 -25
  49. package/dist/machine-runtime-environment.js +189 -0
  50. package/dist/machine-shell-environment-capture.js +135 -0
  51. package/dist/machine-windows-environment-capture.js +215 -0
  52. package/dist/machine-windows-environment-script.js +158 -0
  53. package/dist/main.js +4 -69
  54. package/dist/marketplace-command.js +21 -0
  55. package/dist/marketplace-controller.js +80 -0
  56. package/dist/marketplace-protocol.js +38 -0
  57. package/dist/marketplace-remote.js +48 -0
  58. package/dist/marketplace-runner.js +235 -0
  59. package/dist/marketplace-serve.js +41 -0
  60. package/dist/marketplace-source.js +103 -0
  61. package/dist/message-file-preview.js +246 -0
  62. package/dist/pre-admission-rejection.js +64 -0
  63. package/dist/project-skills/execution-adapter.js +3 -2
  64. package/dist/project-skills/runtime-launch.js +14 -0
  65. package/dist/provider-env.js +26 -15
  66. package/dist/runner.js +17 -4
  67. package/dist/runtime-environment-merge.js +16 -0
  68. package/dist/runtime-health-handler.js +10 -0
  69. package/dist/runtime-health.js +3 -1
  70. package/dist/runtime-path.js +22 -12
  71. package/dist/runtime-probe.js +35 -6
  72. package/dist/runtimes/acp-probe-transport.js +69 -0
  73. package/dist/runtimes/claude-models.js +20 -9
  74. package/dist/runtimes/deepseek-harness-launch.js +10 -0
  75. package/dist/runtimes/deepseek-harness-profile.js +92 -0
  76. package/dist/runtimes/deepseek-harness-runner.js +226 -0
  77. package/dist/runtimes/deepseek-harness.js +76 -15
  78. package/dist/runtimes/harness-probe-retry.js +42 -0
  79. package/dist/runtimes/hermes-models.js +2 -1
  80. package/dist/runtimes/kimi-acp-runner.js +78 -13
  81. package/dist/runtimes/opencode.js +2 -2
  82. package/dist/serve.js +88 -178
  83. package/dist/storage-identity.js +0 -3
  84. package/dist/storage-serve-adapter.js +6 -72
  85. package/dist/supervised-runtime.js +17 -2
  86. package/dist/token.js +0 -1
  87. package/dist/workspace-control-handler.js +27 -0
  88. package/dist/workspace-fs.js +66 -15
  89. package/dist/workspace.js +1 -1
  90. package/package.json +13 -12
  91. package/dist/codex-home-launch.js +0 -7
  92. package/dist/codex-home-mode-controller.js +0 -68
  93. package/dist/daemon-migration-controller.js +0 -211
  94. package/dist/daemon-migration-wiring.js +0 -22
  95. package/dist/runtimes/codex-home-migration-cli.js +0 -26
  96. package/dist/runtimes/codex-home-migration.js +0 -112
  97. package/dist/runtimes/codex-home.js +0 -236
  98. package/dist/storage-control.js +0 -212
  99. package/dist/storage-copy.js +0 -628
  100. package/dist/storage-credential-guard.js +0 -21
  101. package/dist/storage-doctor.js +0 -228
  102. package/dist/storage-manifest.js +0 -1027
  103. package/dist/storage-migration-controller.js +0 -440
  104. package/dist/storage-migration-manifest.js +0 -105
  105. package/dist/storage-migration-policy.js +0 -100
  106. package/dist/storage-runtime-controller.js +0 -381
  107. package/dist/storage-runtime-readiness.js +0 -241
  108. package/dist/storage-safe-fs.js +0 -302
  109. package/dist/storage-status-delivery.js +0 -224
  110. package/dist/storage-status-replay.js +0 -140
  111. package/dist/storage-target-confirmation.js +0 -129
  112. package/dist/storage-worker-command.js +0 -37
  113. package/dist/storage-worker-executor.js +0 -447
  114. package/dist/storage-worker-handoff.js +0 -151
  115. package/dist/storage-worker-operation.js +0 -294
  116. package/dist/storage-worker-status.js +0 -259
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { dirname, join, resolve } from "node:path";
3
- import { resolveReposRoot } from "./config.js";
3
+ import { isLocalMemoryV2Enabled, localMemoryV2Environment, resolveReposRoot } from "./config.js";
4
4
  import { DaemonToServerExecutionFrameSchema, ExecutionCompletedSchema, ExecutionRejectedSchema, ExecutionStartSchema, } from "./execution-protocol.js";
5
5
  import { JournalConflictError } from "./execution-journal.js";
6
6
  import { boundExecutionFrame } from "./execution-event-limit.js";
@@ -210,6 +210,7 @@ function launchProviderConfig(config) {
210
210
  ? {}
211
211
  : { providerSmallFastModel: config.providerSmallFastModel }),
212
212
  ...(config.envVars === undefined ? {} : { envVars: config.envVars }),
213
+ ...(config.environmentPlan === undefined ? {} : { environmentPlan: config.environmentPlan }),
213
214
  ...(config.description === undefined ? {} : { description: config.description }),
214
215
  };
215
216
  }
@@ -408,7 +409,8 @@ export async function runExecution(config, input, dependencies) {
408
409
  ? { permission: effectivePermission(spec, config) }
409
410
  : admission(spec, config, dependencies, initialAt);
410
411
  if ("rejected" in checked) {
411
- const frame = ExecutionRejectedSchema.parse(boundExecutionFrame(checked.rejected, config.executionLimits.maxEventBytes));
412
+ const proof = await dependencies.confirmPreAdmission?.(checked.rejected).catch(() => false);
413
+ const frame = ExecutionRejectedSchema.parse(boundExecutionFrame({ ...checked.rejected, ...(proof ? { preAdmissionProof: { specHash } } : {}) }, config.executionLimits.maxEventBytes));
412
414
  await dependencies.report(frame);
413
415
  return { kind: "rejected", frame };
414
416
  }
@@ -745,6 +747,7 @@ export async function runExecution(config, input, dependencies) {
745
747
  },
746
748
  });
747
749
  const localDependencies = {
750
+ ...(dependencies.machineEnvironment === undefined ? {} : { machineEnvironment: dependencies.machineEnvironment }),
748
751
  ...(dependencies.cancellation === undefined ? {} : { cancellation: dependencies.cancellation }),
749
752
  ...(dependencies.startupGate === undefined ? {} : { startupGate: dependencies.startupGate }),
750
753
  ...(dependencies.startupTimeoutMs === undefined
@@ -829,12 +832,15 @@ export async function runExecution(config, input, dependencies) {
829
832
  const baseSystemPromptBudget = spec.agent.abilityRelease === undefined
830
833
  ? systemPromptBudget
831
834
  : Math.max(0, Math.min(systemPromptBudget, Math.max(32 * 1024, systemPromptBudget - 168 * 1024)));
832
- const localMemoryV2Enabled = config.localMemoryV2Handles?.has(spec.agent.handle) === true;
833
- const systemPromptWithLocalFacts = withLocalExecutionFacts(`${spec.instructions.systemPrompt}${localMemoryV2Enabled ? renderLocalMemoryV2WriteGuard() : ""}`, baseSystemPromptBudget, {
834
- ...(selfTraining ? { isolateLocalMemory: true } : {}),
835
- collaborationTools: config.codexCollaborationTools && spec.runtime.name === "codex",
836
- });
835
+ const localMemoryV2Enabled = !selfTraining && isLocalMemoryV2Enabled(config, spec.agent.handle);
836
+ // 开发环境的系统/当前轮写保护由 prepareLocalMemoryPrompt 统一注入并预留预算。
837
+ const localMemoryGuardInBase = localMemoryV2Environment(config.serverUrl) === "custom";
837
838
  const boundedSystemPrompt = (context) => {
839
+ const enabled = context.workspace.localMemoryV2Enabled ?? localMemoryV2Enabled;
840
+ const systemPromptWithLocalFacts = withLocalExecutionFacts(`${spec.instructions.systemPrompt}${enabled && localMemoryGuardInBase ? renderLocalMemoryV2WriteGuard() : ""}`, baseSystemPromptBudget, {
841
+ ...(selfTraining ? { isolateLocalMemory: true } : {}),
842
+ collaborationTools: config.codexCollaborationTools && spec.runtime.name === "codex",
843
+ });
838
844
  const base = typeof systemPromptWithLocalFacts === "string"
839
845
  ? systemPromptWithLocalFacts
840
846
  : systemPromptWithLocalFacts(context);
@@ -877,7 +883,6 @@ export async function runExecution(config, input, dependencies) {
877
883
  ...(config.qaReportRoot === undefined ? {} : { qaReportRoot: config.qaReportRoot }),
878
884
  cliPath: config.cliPath,
879
885
  providerConfig,
880
- codexHomeMode: config.codexHomeMode,
881
886
  ...(providerConfig.description ? { description: providerConfig.description } : {}),
882
887
  ...(spec.reporting.allowBoundImDecision ? {
883
888
  systemEnv: {
package/dist/i18n.js CHANGED
@@ -18,6 +18,11 @@ export function detectDaemonLang(env = process.env) {
18
18
  const zh = {
19
19
  "Claude session started": "Claude 会话启动",
20
20
  "Codex session started": "Codex 会话启动",
21
+ "Kimi session started": "Kimi 会话启动",
22
+ "Hermes session started": "Hermes 会话启动",
23
+ "OpenCode session started": "OpenCode 会话启动",
24
+ "DeepSeek Harness session started": "DeepSeek Harness 会话启动",
25
+ "Session started": "会话启动",
21
26
  "Plan updated": "计划更新",
22
27
  "Thinking (~{{tokens}} tokens, content not streamed by Claude Code)": "思考(约 {{tokens}} tokens,Claude Code 未输出思考正文)",
23
28
  "Thinking (content not streamed by Claude Code)": "思考(Claude Code 未输出思考正文)",
@@ -26,6 +31,20 @@ const zh = {
26
31
  "Run finished": "本轮结束",
27
32
  "Missing CREW_MACHINE_TOKEN (sk_machine_*, printed by seed)": "缺少 CREW_MACHINE_TOKEN(sk_machine_*,由 seed 打印)",
28
33
  "Usage:": "用法:",
34
+ "Open {{url}}": "打开 {{url}}",
35
+ "Code {{code}}": "验证码 {{code}}",
36
+ "Browser open skipped: {{reason}}": "未能自动打开浏览器:{{reason}}",
37
+ "Starting computer setup pairing...": "正在发起电脑接入授权…",
38
+ "Workspace: {{value}}": "工作区:{{value}}",
39
+ "Machine: {{value}}": "机器: {{value}}",
40
+ "Environment: {{value}}": "环境:{{value}}",
41
+ "Daemon: profile {{profile}} / v{{version}} / {{status}}": "Daemon:配置 {{profile}} / v{{version}} / {{status}}",
42
+ "Local: {{value}}": "本地目录:{{value}}",
43
+ "Config: {{value}}": "配置目录:{{value}}",
44
+ "Agents: {{value}}": "Agent 目录:{{value}}",
45
+ "Storage ID: {{value}}": "存储标识:{{value}}",
46
+ "Status: connected and ready": "状态:已连接并就绪",
47
+ "Status: not ready": "状态:尚未就绪",
29
48
  "connect and stay resident": "连接并常驻",
30
49
  "run once manually": "手动运行一次",
31
50
  "resident, connecting to": "常驻,连接到",
@@ -8,8 +8,8 @@ const execFileRaw = promisify(execFile);
8
8
  const MODEL_PROBE_TIMEOUT_MS = 8_000;
9
9
  const MODEL_PROBE_MAX_BUFFER_BYTES = 1024 * 1024;
10
10
  // win32 上 npm CLI 是 .cmd shim,execFile 需 shell 才能执行;参数全是固定字面量,无注入面。
11
- const execFileP = (bin, args) => execFileRaw(bin, args, {
12
- env: { ...process.env, PATH: augmentedPath() },
11
+ const execFileP = (bin, args, env = { ...process.env, PATH: augmentedPath() }) => execFileRaw(bin, args, {
12
+ env,
13
13
  shell: isWin(),
14
14
  timeout: MODEL_PROBE_TIMEOUT_MS,
15
15
  killSignal: "SIGKILL",
@@ -18,17 +18,17 @@ const execFileP = (bin, args) => execFileRaw(bin, args, {
18
18
  export async function listRuntimeModels(runtime, options = {}) {
19
19
  switch (runtime) {
20
20
  case "claude":
21
- return listClaudeModels();
21
+ return listClaudeModels(undefined, options.env, options.environmentGeneration);
22
22
  case "codex":
23
- return parseCodexModels((await execFileP("codex", ["debug", "models"])).stdout);
23
+ return parseCodexModels((await execFileP("codex", ["debug", "models"], options.env)).stdout);
24
24
  case "cursor":
25
- return parseCursorModels((await execFileP("cursor-agent", ["--list-models"])).stdout);
25
+ return parseCursorModels((await execFileP("cursor-agent", ["--list-models"], options.env)).stdout);
26
26
  case "hermes":
27
27
  return listHermesModels(options);
28
28
  case "opencode":
29
- return listOpencodeModels();
29
+ return listOpencodeModels(options.env);
30
30
  case "pi":
31
- return parsePiModels((await execFileP("pi", ["--list-models"])).stdout);
31
+ return parsePiModels((await execFileP("pi", ["--list-models"], options.env)).stdout);
32
32
  default:
33
33
  return null;
34
34
  }
@@ -84,9 +84,9 @@ function parseCursorModels(stdout) {
84
84
  return { id: id.trim(), label: (label || id).trim(), ...(index === 0 ? { default: true } : {}) };
85
85
  });
86
86
  }
87
- async function outputEvenOnFailure(bin, args) {
87
+ async function outputEvenOnFailure(bin, args, env) {
88
88
  try {
89
- return (await execFileP(bin, args)).stdout;
89
+ return (await execFileP(bin, args, env)).stdout;
90
90
  }
91
91
  catch (error) {
92
92
  const stdout = error.stdout;
@@ -97,11 +97,11 @@ async function outputEvenOnFailure(bin, args) {
97
97
  return "";
98
98
  }
99
99
  }
100
- async function listOpencodeModels() {
101
- const verbose = parseOpencodeModels(await outputEvenOnFailure("opencode", ["models", "--verbose"]));
100
+ async function listOpencodeModels(env) {
101
+ const verbose = parseOpencodeModels(await outputEvenOnFailure("opencode", ["models", "--verbose"], env));
102
102
  if (verbose.length > 0)
103
103
  return verbose;
104
- return parseOpencodeModels(await outputEvenOnFailure("opencode", ["models"]));
104
+ return parseOpencodeModels(await outputEvenOnFailure("opencode", ["models"], env));
105
105
  }
106
106
  const VARIANT_ORDER = new Map([
107
107
  ["none", 0], ["minimal", 1], ["low", 2], ["medium", 3],
@@ -0,0 +1,22 @@
1
+ import { awaitWithCancellation } from "./runtime-cancellation.js";
2
+ const leaseTails = new Map();
3
+ /** 同一进程内的短操作或原生会话排队;等待取消也不会越过前一位持有者。 */
4
+ export async function withKeyedLease(key, operation, cancellation) {
5
+ const predecessor = leaseTails.get(key) ?? Promise.resolve();
6
+ // 解锁函数在等待 predecessor 前可用,保留取消等待时的队列顺序。
7
+ let unlock;
8
+ const current = new Promise(resolve => { unlock = resolve; });
9
+ const queued = predecessor.then(() => current);
10
+ leaseTails.set(key, queued);
11
+ try {
12
+ await awaitWithCancellation(predecessor, cancellation);
13
+ return await operation();
14
+ }
15
+ finally {
16
+ unlock();
17
+ void queued.then(() => {
18
+ if (leaseTails.get(key) === queued)
19
+ leaseTails.delete(key);
20
+ });
21
+ }
22
+ }
@@ -1,22 +1,26 @@
1
+ import { deepSeekHarnessLauncher } from "./runtimes/deepseek-harness.js";
2
+ import { buildLocalRuntimeEnvironment } from "./local-runtime-environment.js";
3
+ import { environmentForExecution } from "./agent-environments.js";
4
+ import { prepareEnvironmentExecutionSnapshot } from "./agent-environment-execution.js";
1
5
  import { createInterface } from "node:readline";
2
6
  import { readFile, rm, writeFile } from "node:fs/promises";
3
- import { delimiter, join } from "node:path";
7
+ import { join } from "node:path";
4
8
  import { cleanupSelfTrainingRunDir } from "./self-training-workspace.js";
5
9
  import { prepareWorkspace, rotateAgentSession, safeKey, } from "./workspace.js";
6
10
  import { CLAUDE_ADDITIONAL_DIRECTORY_INSTRUCTIONS_ENV, isClaudeAdditionalDirectoryInstructionsSupported, probeClaudeVersion, spawnClaude, } from "./runtimes/claude.js";
7
11
  import { spawnCodex } from "./runtimes/codex.js";
8
12
  import { DEEPSEEK_CODEX_MODEL, DEEPSEEK_CODEX_REASONING_LEVELS, materializeDeepSeekCodexHome, } from "./runtimes/codex-deepseek-config.js";
9
- import { materializeDefaultCodexHome } from "./runtimes/codex-home.js";
10
- import { materializePrivateCodexHome } from "./codex-home-launch.js";
11
- import { spawnKimi, KIMI_EFFORT_LEVELS } from "./runtimes/kimi.js";
13
+ import { spawnKimi } from "./runtimes/kimi.js";
12
14
  import { applyDeepSeekHarnessMachineEnv, applyProviderEnv, providerFingerprint, } from "./provider-env.js";
13
- import { augmentedPath } from "./runtime-path.js";
14
15
  import { deliverCloudReports, prepareCloudReportExecution } from "./cloud-report-uploader.js";
15
16
  import { reportDeliveryContext } from "./cloud-report-receipt.js";
16
17
  import { extractFinalText, extractRunMeta, normalizeEvent, parseLine, } from "./normalize.js";
17
18
  import { readSession, writeSession, pickResumeId, isNearBudget } from "./session.js";
18
19
  import { NativeThreadContext, isolateMaintenanceSession } from "./native-thread-context.js";
19
20
  import { prepareThreadDelivery } from "./thread-input-delivery.js";
21
+ import { resolveMemoryPruneWorkLog, workLogSha256 } from "./local-memory/work-log.js";
22
+ import { withKeyedLease } from "./local-execution-lease.js";
23
+ import { withWorkLogExecution, withWorkLogCommit, workLogExecutionKey } from "./local-memory/work-log-execution.js";
20
24
  import { providerErrorFromExit } from "./provider-error.js";
21
25
  import { createConsoleFormatter } from "./console-formatter.js";
22
26
  import { withLocalExecutionFacts, } from "./local-execution-prompt.js";
@@ -37,11 +41,11 @@ import { diffMemoryPruneNotes, evaluateMemoryPrunePostcondition, inspectMemoryPr
37
41
  import { createLocalMemoryTelemetry, logLocalMemoryContextPrepareFailure, logLocalMemoryDiagnosticsFailure, } from "./local-memory-telemetry.js";
38
42
  import { CodexStartupStageParser } from "./codex-startup-stage.js";
39
43
  import { formatRuntimeStartupError } from "./runtime-startup-error.js";
40
- import { machinePackageManagerEnv } from "./package-manager-env.js";
41
44
  import { logRuntimeEnvironmentContract } from "./runtime-environment-telemetry.js";
42
- import { sanitizeEnvVars } from "./runtime-env.js";
43
45
  import { commitLocalMemoryResultAfterExit, initializeLocalMemoryV2ForWorkspace, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, } from "./local-memory/runtime.js";
44
46
  import { prepareLocalMemoryPrompt } from "./local-memory/prompt.js";
47
+ import { reportLocalMemoryV2Deferred, resolveLocalMemoryV2Rollout } from "./local-memory/rollout.js";
48
+ import { localMemoryV2Environment } from "./config.js";
45
49
  export class RuntimeRunningCallbackTimeoutError extends Error {
46
50
  constructor(runtime, timeoutMs) {
47
51
  super(`${runtime} runtime-running callback timed out after ${timeoutMs}ms`);
@@ -159,28 +163,12 @@ async function launchLegacyRuntime(request) {
159
163
  function resolvePrompt(prompt, context) {
160
164
  return typeof prompt === "string" ? prompt : prompt(context);
161
165
  }
162
- const nativeSessionLeaseTails = new Map();
163
166
  const activeMemoryPrunes = new Map();
164
- async function withKeyedLease(key, operation, cancellation) {
165
- const predecessor = nativeSessionLeaseTails.get(key) ?? Promise.resolve();
166
- let release;
167
- const current = new Promise((resolve) => { release = resolve; });
168
- const tail = predecessor.then(() => current);
169
- nativeSessionLeaseTails.set(key, tail);
170
- try {
171
- await awaitWithCancellation(predecessor, cancellation);
172
- return await operation();
173
- }
174
- finally {
175
- release();
176
- void tail.then(() => {
177
- if (nativeSessionLeaseTails.get(key) === tail)
178
- nativeSessionLeaseTails.delete(key);
179
- });
180
- }
181
- }
182
167
  export async function executeLocal(input, callbacks = {}, dependencies = {}) {
183
168
  input = isolateMaintenanceSession(input);
169
+ return withWorkLogExecution(workLogExecutionKey(input.launch.agentsRoot, input.handle, input.taskKey), () => executeLocalWithSession(input, callbacks, dependencies), dependencies.cancellation);
170
+ }
171
+ async function executeLocalWithSession(input, callbacks, dependencies) {
184
172
  const versionedProjectSkills = input.projectSkills !== undefined
185
173
  && !Array.isArray(input.projectSkills);
186
174
  if (versionedProjectSkills && (input.runtime.name !== "codex" && input.runtime.name !== "claude")) {
@@ -248,7 +236,12 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
248
236
  }, "workspace_prepare", error);
249
237
  throw error;
250
238
  }
251
- const localMemoryV2Initialized = await initializeLocalMemoryV2ForWorkspace(workspace, input.localMemoryV2Enabled === true);
239
+ const memoryRollout = await resolveLocalMemoryV2Rollout({ workspace, handle: input.handle,
240
+ serverUrl: input.launch.serverUrl, requested: input.localMemoryV2Enabled === true,
241
+ selfTraining: isolatedMaintenance, ...(input.launch.description ? { description: input.launch.description } : {}) });
242
+ workspace = { ...workspace, localMemoryV2Enabled: memoryRollout.enabled };
243
+ input = { ...input, localMemoryV2Enabled: memoryRollout.enabled };
244
+ const localMemoryV2Initialized = await initializeLocalMemoryV2ForWorkspace(workspace, memoryRollout.enabled);
252
245
  let materialized = null;
253
246
  let knownAttachmentDirectory = null;
254
247
  const startupReservationState = { current: null };
@@ -259,10 +252,12 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
259
252
  let memoryPruneBeforeSnapshot = null;
260
253
  let memoryPruneSharedWriteKey = null;
261
254
  let localMemoryResultPath = null;
255
+ let environmentSnapshot;
262
256
  let executionWorkspace = workspace;
263
257
  let localMemoryTelemetry = null;
264
258
  let nativeThreadContext = null;
265
- const inheritedEnv = { ...process.env };
259
+ const machineSnapshot = dependencies.machineEnvironment ? await awaitWithCancellation(dependencies.machineEnvironment.acquire(), dependencies.cancellation) : undefined;
260
+ const inheritedEnv = { ...(machineSnapshot?.env ?? process.env) };
266
261
  const reportRoots = {
267
262
  root: workspace.dir,
268
263
  ...(input.launch.qaReportRoot === undefined ? {} : { qaReportRoot: input.launch.qaReportRoot }),
@@ -274,8 +269,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
274
269
  if (key.startsWith("CREW_AGENT_MEMORY_") || ["CREW_CLOUD_PROVIDER", "CREW_OSS_UPLOAD_SCRIPT", "CREW_OSS_REPORT_UPLOAD"].includes(key))
275
270
  delete inheritedEnv[key];
276
271
  }
277
- const sourceCodexHome = inheritedEnv.CODEX_HOME
278
- ?? (inheritedEnv.HOME === undefined ? undefined : join(inheritedEnv.HOME, ".codex"));
279
272
  let materializedDeepSeekHome;
280
273
  try {
281
274
  if (isDeepSeekCodex && !providerConfig.providerApiKey) {
@@ -356,8 +349,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
356
349
  const launchSessionId = resumeSessionId ?? (rotated
357
350
  ? await rotateAgentSession(workspace.sessionDir)
358
351
  : workspace.agentSessionId);
359
- await materializePrivateCodexHome({ runtime: runtime.name, homeDir: workspace.homeDir, ...(input.launch.codexHomeMode === undefined ? {} : { mode: input.launch.codexHomeMode }), ...(sourceCodexHome === undefined ? {} : { sourceHome: sourceCodexHome }), ...(launchSessionId === undefined ? {} : { sessionId: launchSessionId }), ...(dependencies.cancellation === undefined ? {} : { cancellation: dependencies.cancellation }), ...(dependencies.materializeDefaultCodexHome === undefined ? {} : { materialize: dependencies.materializeDefaultCodexHome }) });
360
- const promptContext = {
352
+ let promptContext = {
361
353
  workspace: executionWorkspace,
362
354
  resuming,
363
355
  rotatedForBudget,
@@ -396,8 +388,15 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
396
388
  }
397
389
  }
398
390
  const attachmentPlan = routeRuntimeAttachments(runtime.name, materialized?.attachments ?? []);
399
- const baseWakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
400
- memoryPruneTraceId = parseMemoryPruneTraceId(baseWakePrompt);
391
+ let baseWakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
392
+ memoryPruneTraceId = isolatedMaintenance ? null : parseMemoryPruneTraceId(baseWakePrompt);
393
+ if (memoryRollout.deferred)
394
+ reportLocalMemoryV2Deferred(input.executionId, input.handle, memoryPruneTraceId !== null || input.maintenancePurpose === "memory_prune");
395
+ if (memoryPruneTraceId !== null) {
396
+ executionWorkspace = await resolveMemoryPruneWorkLog(executionWorkspace, input);
397
+ promptContext = { ...promptContext, workspace: executionWorkspace };
398
+ baseWakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
399
+ }
401
400
  if (input.localMemoryV2Enabled === true && memoryPruneTraceId !== null
402
401
  && isLocalMemoryV2Eligible({
403
402
  enabledForHandle: true,
@@ -412,6 +411,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
412
411
  agentDir: executionWorkspace.dir, executionId: input.executionId, agentHandle: input.handle,
413
412
  enabled: input.localMemoryV2Enabled === true, query: baseWakePrompt,
414
413
  resultPath: localMemoryResultPath, maxBytes: input.maxSystemPromptBytes,
414
+ strictRollout: !isolatedMaintenance && localMemoryV2Environment(input.launch.serverUrl) === "dev",
415
+ deferred: memoryRollout.deferred,
415
416
  renderBase: (reservedPromptBytes) => resolvePrompt(input.systemPrompt, { ...promptContext, reservedPromptBytes }),
416
417
  });
417
418
  localMemoryResultPath = memoryPrompt.resultPath;
@@ -480,48 +481,9 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
480
481
  nativeThreadContext = new NativeThreadContext(join(executionWorkspace.runDir, `.crew-context-${input.executionId}.json`), contextThreadId, runtime.name, resumeSessionId, prior);
481
482
  await nativeThreadContext.initialize();
482
483
  }
483
- const baseEnv = {
484
- ...inheritedEnv,
485
- ...sanitizeEnvVars(providerConfig.envVars),
486
- ...(input.trainingProjectContext === undefined
487
- ? {}
488
- : machinePackageManagerEnv(inheritedEnv)),
489
- ...input.launch.systemEnv,
490
- PATH: `${workspace.crewDir}${delimiter}${augmentedPath()}`,
491
- CREW_SERVER_URL: input.launch.serverUrl,
492
- CREW_TOKEN: input.launch.token,
493
- CREW_CHANNEL: input.channelId,
494
- CREW_HOME: crewHome,
495
- // Empty values override any stale inherited hints from an enclosing runtime.
496
- CREW_THREAD_ROOT: contextThreadId ?? "",
497
- CREW_THREAD_CONTEXT_FILE: nativeThreadContext?.path ?? "",
498
- // Keep the daemon-selected storage root authoritative for runtimes.
499
- // In profile-backed NowWork sessions the daemon config may differ from
500
- // the daemon process environment, so inheriting CREW_AGENTS_ROOT is not
501
- // sufficient for QA/project diagnostics.
502
- CREW_AGENTS_ROOT: input.launch.agentsRoot,
503
- ...(input.launch.reposRoot === undefined ? {} : { NOWWORK_REPOS_ROOT: input.launch.reposRoot }),
504
- ...(input.launch.qaReportRoot === undefined ? {} : { QA_REPORT_HUB: input.launch.qaReportRoot }),
505
- // Expose only the training root; ability-specific workspace layout stays
506
- // owned by the training package rather than the NowWork daemon.
507
- QA_RUNTIME_ROOT: join(crewHome, "training"),
508
- QA_REPORT_DELIVERY_CONTEXT: JSON.stringify(reportContext),
509
- CREW_TASK_DIR: executionWorkspace.runDir,
510
- CREW_TASK_LOG: executionWorkspace.workLogPath,
511
- ...(input.wakeMessageId ? { CREW_WAKE_MESSAGE_ID: input.wakeMessageId } : {}),
512
- XDG_CONFIG_HOME: join(workspace.homeDir, ".config"),
513
- XDG_DATA_HOME: join(workspace.homeDir, ".local", "share"),
514
- XDG_CACHE_HOME: join(workspace.homeDir, ".cache"),
515
- GH_CONFIG_DIR: join(workspace.homeDir, ".config", "gh"),
516
- CLOUDSDK_CONFIG: join(workspace.homeDir, ".config", "gcloud"),
517
- ...(runtime.name === "kimi" && runtime.reasoning
518
- && KIMI_EFFORT_LEVELS.includes(runtime.reasoning)
519
- ? { KIMI_MODEL_THINKING_EFFORT: runtime.reasoning }
520
- : {}),
521
- };
522
- const providerEnv = applyProviderEnv(baseEnv, runtime.name, input.launch.codexHomeMode === undefined
523
- ? providerConfig
524
- : { ...providerConfig, codexHomeMode: input.launch.codexHomeMode }, workspace.homeDir, materializedDeepSeekHome);
484
+ const baseEnv = buildLocalRuntimeEnvironment({ input, workspace, executionWorkspace, inheritedEnv, providerConfig,
485
+ crewHome, contextThreadId, nativeThreadContextPath: nativeThreadContext?.path, reportContext, runtime });
486
+ const providerEnv = applyProviderEnv(baseEnv, runtime.name, providerConfig, workspace.homeDir, materializedDeepSeekHome);
525
487
  const childEnv = runtime.name === "deepseek-harness"
526
488
  ? applyDeepSeekHarnessMachineEnv(providerEnv, inheritedEnv)
527
489
  : providerEnv;
@@ -538,10 +500,15 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
538
500
  const abilityContext = input.abilityRelease !== undefined && dependencies.abilityRelease !== undefined
539
501
  ? await dependencies.abilityRelease.prepareAndLaunch(input.launch.agentsRoot, input.handle, input.abilityRelease, async (context) => context)
540
502
  : undefined;
503
+ if (providerConfig.environmentPlan !== undefined && !isolatedMaintenance) {
504
+ environmentSnapshot = await prepareEnvironmentExecutionSnapshot({ plan: providerConfig.environmentPlan, agentRoot: workspace.dir, runDir: executionWorkspace.runDir, executionId: input.executionId, ambient: inheritedEnv, runtime: runtime.name, providerConfig, runtimeEnvironment: childEnv, authoritativeKeys: Object.keys(input.launch.systemEnv ?? {}),
505
+ executionBaseEnvironment: childEnv,
506
+ ...(machineSnapshot === undefined ? {} : { machineEnvironmentVersion: { bootId: machineSnapshot.status.bootId, generation: machineSnapshot.status.generation } }),
507
+ selectedEnvironment: environmentForExecution(providerConfig.environmentPlan, workspace.dir, input.projectContext?.primary?.root ?? executionWorkspace.runDir, input.projectContext?.primary?.projectId),
508
+ projectRoots: [...(input.projectContext?.primary ? [input.projectContext.primary] : []), ...(input.projectContext?.secondary ?? [])] });
509
+ }
541
510
  const prepareRuntimeLaunch = async (runtimeRoot) => {
542
- const effectiveSystemPrompt = abilityContext === undefined
543
- ? systemPrompt
544
- : `${systemPrompt}\n\n${abilityContext.prompt}`;
511
+ const effectiveSystemPrompt = [systemPrompt, abilityContext?.prompt, environmentSnapshot?.prompt].filter(Boolean).join("\n\n");
545
512
  if (Buffer.byteLength(effectiveSystemPrompt, "utf8") + Buffer.byteLength(memoryPrompt.wakePromptSuffix, "utf8")
546
513
  > (input.maxSystemPromptBytes ?? Number.MAX_SAFE_INTEGER)) {
547
514
  throw new Error("ability_runtime_context_size_exceeded");
@@ -577,7 +544,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
577
544
  });
578
545
  }
579
546
  const trainingRoots = input.trainingProjectContext?.projects.map((project) => project.root) ?? [];
580
- const { codexSkillRoots, claudeAdditionalDirectories } = await projectSkillRuntimeDirectories({
547
+ const { codexSkillRoots, claudeAdditionalDirectories, harnessSkillRoots } = await projectSkillRuntimeDirectories({
581
548
  runtime: runtime.name,
582
549
  ...(runtimeRoot === undefined ? {} : { runtimeRoot }),
583
550
  agentRoot: workspace.dir,
@@ -615,7 +582,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
615
582
  const workspaceRoots = [...legacyRoots, ...trainingRoots];
616
583
  const launchRequest = {
617
584
  runtime: runtime.name,
618
- bin: runtime.name === "deepseek-harness" ? "dsh-acp-demo" : runtime.name,
585
+ bin: runtime.name === "deepseek-harness" ? deepSeekHarnessLauncher(runtimeEnv).bin : runtime.name,
586
+ ...(runtime.name === "deepseek-harness" ? { harnessStateRoot: executionWorkspace.runDir, harnessSkillRoots: harnessSkillRoots ?? [] } : {}),
619
587
  cwd: input.projectContext?.primary?.root ?? executionWorkspace.runDir,
620
588
  ...(runtimeRoot !== undefined
621
589
  || (input.projectSkills === undefined && input.abilityRelease === undefined)
@@ -731,7 +699,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
731
699
  // (incident: 2026-09-02, boss-alarm 排查结论未投递)。所有 runtime 的文本都喂给它。
732
700
  const externalOutput = new ExternalAnswerDecoder();
733
701
  // 每轮独立:tool_use/result 关联状态不能跨 execution 泄漏。
734
- const consoleFormatter = createConsoleFormatter();
702
+ const consoleFormatter = createConsoleFormatter(runtime.name);
735
703
  let runtimeReady = false;
736
704
  let runtimeRunning = false;
737
705
  let resolveRuntimeReady;
@@ -937,14 +905,17 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
937
905
  memoryPruneRuntimeExitCode = exitCode;
938
906
  if (localMemoryResultPath !== null) {
939
907
  memoryPruneFailurePhase = "memory_commit";
940
- const committed = await commitLocalMemoryResultAfterExit({
941
- resultPath: localMemoryResultPath,
908
+ const resultPath = localMemoryResultPath;
909
+ const commit = () => commitLocalMemoryResultAfterExit({
910
+ resultPath,
942
911
  agentDir: executionWorkspace.dir,
943
912
  executionId: input.executionId,
944
913
  pruneTraceId: memoryPruneTraceId,
945
914
  exitCode,
946
915
  workLogPath: executionWorkspace.workLogPath,
916
+ expectedWorkLogSha256: workLogSha256(executionWorkspace.workLog),
947
917
  });
918
+ const committed = exitCode !== 0 ? await commit() : await withWorkLogCommit(workLogExecutionKey(input.launch.agentsRoot, input.handle, input.taskKey), commit, dependencies.cancellation);
948
919
  if (committed.status === "failed" || committed.status === "missing") {
949
920
  const reason = committed.error ?? "local_memory_result_missing";
950
921
  dslog("local_memory.commit_failed", "结构化本地记忆提交或清理未完成,保留可用恢复材料", {
@@ -1122,6 +1093,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
1122
1093
  active_prune_count: activePruneCount,
1123
1094
  });
1124
1095
  }
1096
+ if (environmentSnapshot)
1097
+ await rm(environmentSnapshot.directory, { recursive: true, force: true }).catch(() => { });
1125
1098
  const attachmentDirectories = new Set([
1126
1099
  ...(knownAttachmentDirectory === null ? [] : [knownAttachmentDirectory]),
1127
1100
  ...(materialized === null ? [] : [materialized.directory]),
@@ -0,0 +1,43 @@
1
+ import { z } from "zod";
2
+ import { containsLikelySecret } from "../agent-memory/policy.js";
3
+ const CategorySchema = z.enum([
4
+ "preferences", "project-rules", "testing", "debugging", "git", "incidents", "lessons",
5
+ ]);
6
+ export const LocalMemorySingleLineText = z.string().trim().min(1).max(500).regex(/^[^\r\n]+$/u);
7
+ export const LocalMemoryChangeSchema = z.object({
8
+ operation: z.enum(["create", "update", "supersede"]),
9
+ memoryId: LocalMemorySingleLineText.regex(/^[^<>]+$/u).nullable(),
10
+ baseRevision: z.number().int().nonnegative().nullable(),
11
+ supersedesMemoryId: LocalMemorySingleLineText.regex(/^[^<>]+$/u).nullable(),
12
+ title: LocalMemorySingleLineText.max(120),
13
+ kind: z.enum(["procedural", "semantic", "episodic", "preference"]),
14
+ summary: LocalMemorySingleLineText,
15
+ triggers: z.array(LocalMemorySingleLineText.max(80)).min(1).max(16),
16
+ priority: z.enum(["high", "medium", "low"]),
17
+ detailCategory: CategorySchema,
18
+ detailBody: z.string().trim().min(1).max(12_000),
19
+ reviewAfter: LocalMemorySingleLineText.nullable(),
20
+ }).strict();
21
+ const CONTROL_CHARACTER_PATTERN = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u;
22
+ const RESERVED_MEMORY_MARKER = /NOWWORK:(?:MEMORY_ENTRY|MEMORY_INDEX):/iu;
23
+ export function validateLocalMemoryChange(change) {
24
+ const text = [change.title, change.summary, ...change.triggers, change.detailBody].join("\n");
25
+ if (containsLikelySecret(text))
26
+ throw new Error("local_memory_secret_candidate");
27
+ if (CONTROL_CHARACTER_PATTERN.test(text))
28
+ throw new Error("local_memory_control_character");
29
+ if (RESERVED_MEMORY_MARKER.test(text))
30
+ throw new Error("local_memory_reserved_marker");
31
+ if (change.operation === "create"
32
+ && (change.memoryId !== null || change.baseRevision !== null || change.supersedesMemoryId !== null)) {
33
+ throw new Error("local_memory_create_revision_invalid");
34
+ }
35
+ if (change.operation === "update"
36
+ && (change.memoryId === null || change.baseRevision === null || change.supersedesMemoryId !== null)) {
37
+ throw new Error("local_memory_update_revision_missing");
38
+ }
39
+ if (change.operation === "supersede"
40
+ && (change.memoryId !== null || change.baseRevision === null || change.supersedesMemoryId === null)) {
41
+ throw new Error("local_memory_supersede_revision_missing");
42
+ }
43
+ }
@@ -19,6 +19,14 @@ export function renderLocalMemoryIndexCard(entry) {
19
19
  + `- updated: ${entry.updated}\n`
20
20
  + (entry.reviewAfter === null ? "" : `- review_after: ${entry.reviewAfter}\n`);
21
21
  }
22
+ /** 写入和预演共用完整索引,预算包含起止标记与分隔换行。 */
23
+ export function renderGeneratedIndex(entries) {
24
+ const cards = entries
25
+ .filter(entry => PROJECTABLE_STATUSES.has(entry.status))
26
+ .map(renderLocalMemoryIndexCard)
27
+ .join("\n");
28
+ return `<!-- NOWWORK:MEMORY_INDEX:BEGIN schema=1 -->\n${cards}<!-- NOWWORK:MEMORY_INDEX:END -->`;
29
+ }
22
30
  function compareEntries(left, right) {
23
31
  const statusRank = (status) => status === "pinned" ? 0 : 1;
24
32
  const priorityRank = (priority) => (priority === "high" ? 0 : priority === "medium" ? 1 : 2);
@@ -0,0 +1,61 @@
1
+ const CommandSchema = z.object({
2
+ "agent-dir": z.string().min(1).refine(isAbsolute),
3
+ "dry-run": z.literal(true),
4
+ }).strict();
5
+ const USAGE = "只读记忆盘点与转换预演,不写文件,不支持 --apply。\n"
6
+ + "pnpm --filter @nowcrew/daemon exec tsx src/local-memory/inventory-cli.ts --agent-dir <单个Agent绝对路径> --dry-run\n"
7
+ + "报告包含原始记忆,需作为敏感本地资料保存;退出码0仅代表盘点及格式预演无阻塞,不代表批准迁移。\n";
8
+ export async function runMemoryInventoryCommand(args, io) {
9
+ if (args.length === 1 && args[0] === "--help") {
10
+ io.out(USAGE);
11
+ return 0;
12
+ }
13
+ let command;
14
+ try {
15
+ const parsed = parseArgs({ args, allowPositionals: false, options: {
16
+ "agent-dir": { type: "string" }, "dry-run": { type: "boolean" },
17
+ } });
18
+ command = CommandSchema.parse(parsed.values);
19
+ }
20
+ catch {
21
+ io.err(USAGE);
22
+ return 2;
23
+ }
24
+ try {
25
+ const scan = await scanMemorySources(command["agent-dir"]);
26
+ const scanComplete = scan.issues.length === 0;
27
+ const analysis = previewMemoryInventory(scan.sources, scanComplete);
28
+ const complete = scanComplete && !analysis.issues.some((issue) => issue.code === "missing_detail");
29
+ const issues = [...scan.issues, ...analysis.issues];
30
+ const referenced = new Set(analysis.entries.map((entry) => entry.sourceFields.file));
31
+ const files = scan.sources.map(({ path, bytes, sha256 }) => ({ path, bytes, sha256 }));
32
+ const report = {
33
+ schemaVersion: 1, mode: "dry-run", agentDir: scan.root, applySupported: false,
34
+ complete, scanComplete, requiresHumanReview: true,
35
+ coverage: ["MEMORY.md", "notes/**", ".crew/memory/pending/**", "根目录的 .memory-result.v1-*.json"],
36
+ limitations: ["不扫描历史执行目录;result清单仅覆盖Agent根目录。", "两遍摘要比对不等同跨文件原子快照,正式迁移仍需停止写入并重新核对。"],
37
+ limits: inventoryLimits, totalBytes: scan.totalBytes, files, ...analysis, issues,
38
+ pending: files.filter((file) => file.path.startsWith(".crew/memory/pending/")).map((file) => file.path),
39
+ orphanNotes: files.filter((file) => file.path.startsWith("notes/") && !referenced.has(file.path)).map((file) => file.path),
40
+ resultFiles: files.filter((file) => file.path.startsWith(".memory-result.v1-")).map((file) => file.path),
41
+ };
42
+ io.out(JSON.stringify(report, null, 2) + "\n");
43
+ return issues.length ? 1 : 0;
44
+ }
45
+ catch (error) {
46
+ const code = error instanceof Error ? error.code ?? error.message : "inventory_failed";
47
+ io.err(`记忆盘点失败:${code}\n`);
48
+ return 1;
49
+ }
50
+ }
51
+ if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
52
+ process.exitCode = await runMemoryInventoryCommand(process.argv.slice(2), {
53
+ out: (text) => process.stdout.write(text), err: (text) => process.stderr.write(text),
54
+ });
55
+ }
56
+ import { isAbsolute, resolve } from "node:path";
57
+ import { pathToFileURL } from "node:url";
58
+ import { parseArgs } from "node:util";
59
+ import { z } from "zod";
60
+ import { inventoryLimits, scanMemorySources } from "./inventory-source.js";
61
+ import { previewMemoryInventory } from "./inventory-preview.js";