@nowcrew/daemon 0.6.88-beta.3 → 0.6.88-beta.30

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 (129) hide show
  1. package/README.md +89 -14
  2. package/dist/agent-ability/controller.js +11 -1
  3. package/dist/agent-ability/git-errors.js +22 -0
  4. package/dist/agent-ability/materializer.js +21 -10
  5. package/dist/agent-ability/resolver.js +12 -4
  6. package/dist/agent-environment-contract.js +55 -0
  7. package/dist/agent-environment-control.js +24 -0
  8. package/dist/agent-environment-execution.js +98 -0
  9. package/dist/agent-environments.js +275 -0
  10. package/dist/agent-memory/policy.js +3 -1
  11. package/dist/automatic-daemon-installer.js +51 -2
  12. package/dist/automatic-install-target.js +8 -0
  13. package/dist/computer-main.js +18 -0
  14. package/dist/computer-profile.js +17 -4
  15. package/dist/computer-setup-client.js +318 -0
  16. package/dist/computer-setup-orchestration.js +203 -0
  17. package/dist/computer-setup.js +244 -0
  18. package/dist/config.js +30 -12
  19. package/dist/console-formatter.js +3 -3
  20. package/dist/console.js +15 -2
  21. package/dist/daemon-update-eligibility.js +1 -1
  22. package/dist/execution-journal.js +9 -6
  23. package/dist/execution-protocol.js +2 -1
  24. package/dist/execution-runner.js +16 -10
  25. package/dist/i18n.js +19 -0
  26. package/dist/list-models.js +13 -11
  27. package/dist/local-execution-lease.js +22 -0
  28. package/dist/local-executor.js +88 -92
  29. package/dist/local-memory/candidate.js +43 -0
  30. package/dist/local-memory/index-budget.js +8 -0
  31. package/dist/local-memory/inventory-cli.js +61 -0
  32. package/dist/local-memory/inventory-preview.js +159 -0
  33. package/dist/local-memory/inventory-source.js +159 -0
  34. package/dist/local-memory/pending-match.js +29 -0
  35. package/dist/local-memory/pending-query.js +100 -0
  36. package/dist/local-memory/pending-schema.js +29 -0
  37. package/dist/local-memory/pending-source.js +126 -0
  38. package/dist/local-memory/pending.js +34 -84
  39. package/dist/local-memory/prompt.js +20 -12
  40. package/dist/local-memory/read-cli.js +97 -12
  41. package/dist/local-memory/rollout.js +82 -0
  42. package/dist/local-memory/runtime.js +24 -17
  43. package/dist/local-memory/work-log-execution.js +31 -0
  44. package/dist/local-memory/work-log.js +58 -0
  45. package/dist/local-memory/write.js +27 -58
  46. package/dist/local-runtime-environment.js +50 -0
  47. package/dist/machine-environment-cache.js +147 -0
  48. package/dist/machine-environment-contract.js +10 -0
  49. package/dist/machine-environment-control.js +27 -0
  50. package/dist/machine-environment-policy.js +75 -0
  51. package/dist/machine-environment-process.js +110 -0
  52. package/dist/machine-info.js +70 -25
  53. package/dist/machine-runtime-environment.js +189 -0
  54. package/dist/machine-shell-environment-capture.js +135 -0
  55. package/dist/machine-windows-environment-capture.js +215 -0
  56. package/dist/machine-windows-environment-script.js +158 -0
  57. package/dist/main.js +4 -69
  58. package/dist/marketplace-controller.js +75 -0
  59. package/dist/marketplace-protocol.js +38 -0
  60. package/dist/marketplace-runner.js +234 -0
  61. package/dist/marketplace-serve.js +17 -0
  62. package/dist/message-file-preview.js +246 -0
  63. package/dist/native-thread-context.js +59 -0
  64. package/dist/pre-admission-rejection.js +64 -0
  65. package/dist/project-skills/execution-adapter.js +3 -2
  66. package/dist/project-skills/runtime-launch.js +14 -0
  67. package/dist/prompt.js +15 -15
  68. package/dist/provider-env.js +26 -15
  69. package/dist/runner.js +31 -19
  70. package/dist/runtime-environment-merge.js +16 -0
  71. package/dist/runtime-health-handler.js +10 -0
  72. package/dist/runtime-health.js +3 -1
  73. package/dist/runtime-path.js +22 -12
  74. package/dist/runtime-probe.js +35 -6
  75. package/dist/runtimes/acp-probe-transport.js +69 -0
  76. package/dist/runtimes/claude-models.js +20 -9
  77. package/dist/runtimes/claude-session-start-hook.js +24 -0
  78. package/dist/runtimes/claude-thread-runner.js +266 -0
  79. package/dist/runtimes/claude.js +23 -4
  80. package/dist/runtimes/codex-app-server-runner.js +43 -4
  81. package/dist/runtimes/deepseek-harness-launch.js +10 -0
  82. package/dist/runtimes/deepseek-harness-profile.js +92 -0
  83. package/dist/runtimes/deepseek-harness-runner.js +226 -0
  84. package/dist/runtimes/deepseek-harness.js +76 -15
  85. package/dist/runtimes/harness-probe-retry.js +42 -0
  86. package/dist/runtimes/hermes-models.js +2 -1
  87. package/dist/runtimes/kimi-acp-runner.js +84 -15
  88. package/dist/runtimes/opencode-runner.js +7 -1
  89. package/dist/runtimes/opencode.js +2 -2
  90. package/dist/serve.js +95 -184
  91. package/dist/server-url.js +25 -0
  92. package/dist/session.js +4 -0
  93. package/dist/slog.js +2 -1
  94. package/dist/storage-identity.js +0 -3
  95. package/dist/storage-runtime.js +2 -2
  96. package/dist/storage-serve-adapter.js +6 -72
  97. package/dist/supervised-runtime.js +28 -18
  98. package/dist/thread-input-delivery.js +217 -0
  99. package/dist/token.js +0 -1
  100. package/dist/workspace-control-handler.js +27 -0
  101. package/dist/workspace-fs.js +66 -15
  102. package/dist/workspace.js +1 -1
  103. package/package.json +5 -4
  104. package/dist/codex-home-launch.js +0 -7
  105. package/dist/codex-home-mode-controller.js +0 -68
  106. package/dist/daemon-migration-controller.js +0 -211
  107. package/dist/daemon-migration-wiring.js +0 -22
  108. package/dist/runtimes/codex-home-migration-cli.js +0 -26
  109. package/dist/runtimes/codex-home-migration.js +0 -112
  110. package/dist/runtimes/codex-home.js +0 -236
  111. package/dist/storage-control.js +0 -212
  112. package/dist/storage-copy.js +0 -628
  113. package/dist/storage-credential-guard.js +0 -21
  114. package/dist/storage-doctor.js +0 -228
  115. package/dist/storage-manifest.js +0 -1027
  116. package/dist/storage-migration-controller.js +0 -440
  117. package/dist/storage-migration-manifest.js +0 -105
  118. package/dist/storage-migration-policy.js +0 -100
  119. package/dist/storage-runtime-controller.js +0 -381
  120. package/dist/storage-runtime-readiness.js +0 -240
  121. package/dist/storage-safe-fs.js +0 -302
  122. package/dist/storage-status-delivery.js +0 -224
  123. package/dist/storage-status-replay.js +0 -140
  124. package/dist/storage-target-confirmation.js +0 -129
  125. package/dist/storage-worker-command.js +0 -37
  126. package/dist/storage-worker-executor.js +0 -449
  127. package/dist/storage-worker-handoff.js +0 -151
  128. package/dist/storage-worker-operation.js +0 -294
  129. package/dist/storage-worker-status.js +0 -259
@@ -1,20 +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";
19
+ import { NativeThreadContext, isolateMaintenanceSession } from "./native-thread-context.js";
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";
18
24
  import { providerErrorFromExit } from "./provider-error.js";
19
25
  import { createConsoleFormatter } from "./console-formatter.js";
20
26
  import { withLocalExecutionFacts, } from "./local-execution-prompt.js";
@@ -35,11 +41,11 @@ import { diffMemoryPruneNotes, evaluateMemoryPrunePostcondition, inspectMemoryPr
35
41
  import { createLocalMemoryTelemetry, logLocalMemoryContextPrepareFailure, logLocalMemoryDiagnosticsFailure, } from "./local-memory-telemetry.js";
36
42
  import { CodexStartupStageParser } from "./codex-startup-stage.js";
37
43
  import { formatRuntimeStartupError } from "./runtime-startup-error.js";
38
- import { machinePackageManagerEnv } from "./package-manager-env.js";
39
44
  import { logRuntimeEnvironmentContract } from "./runtime-environment-telemetry.js";
40
- import { sanitizeEnvVars } from "./runtime-env.js";
41
45
  import { commitLocalMemoryResultAfterExit, initializeLocalMemoryV2ForWorkspace, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, } from "./local-memory/runtime.js";
42
46
  import { prepareLocalMemoryPrompt } from "./local-memory/prompt.js";
47
+ import { reportLocalMemoryV2Deferred, resolveLocalMemoryV2Rollout } from "./local-memory/rollout.js";
48
+ import { localMemoryV2Environment } from "./config.js";
43
49
  export class RuntimeRunningCallbackTimeoutError extends Error {
44
50
  constructor(runtime, timeoutMs) {
45
51
  super(`${runtime} runtime-running callback timed out after ${timeoutMs}ms`);
@@ -108,6 +114,9 @@ async function launchLegacyRuntime(request) {
108
114
  if (request.runtime === "deepseek-harness") {
109
115
  throw new Error("DeepSeek Harness requires execution protocol v1");
110
116
  }
117
+ if (request.runtime !== "claude")
118
+ request = { ...request, wakePrompt: (await prepareThreadDelivery({ env: request.env,
119
+ resumed: false, sessionId: "unverified-cold-legacy", wakePrompt: request.wakePrompt })).prompt };
111
120
  const common = {
112
121
  bin: request.bin,
113
122
  cwd: request.cwd,
@@ -154,27 +163,12 @@ async function launchLegacyRuntime(request) {
154
163
  function resolvePrompt(prompt, context) {
155
164
  return typeof prompt === "string" ? prompt : prompt(context);
156
165
  }
157
- const nativeSessionLeaseTails = new Map();
158
166
  const activeMemoryPrunes = new Map();
159
- async function withKeyedLease(key, operation, cancellation) {
160
- const predecessor = nativeSessionLeaseTails.get(key) ?? Promise.resolve();
161
- let release;
162
- const current = new Promise((resolve) => { release = resolve; });
163
- const tail = predecessor.then(() => current);
164
- nativeSessionLeaseTails.set(key, tail);
165
- try {
166
- await awaitWithCancellation(predecessor, cancellation);
167
- return await operation();
168
- }
169
- finally {
170
- release();
171
- void tail.then(() => {
172
- if (nativeSessionLeaseTails.get(key) === tail)
173
- nativeSessionLeaseTails.delete(key);
174
- });
175
- }
176
- }
177
167
  export async function executeLocal(input, callbacks = {}, dependencies = {}) {
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) {
178
172
  const versionedProjectSkills = input.projectSkills !== undefined
179
173
  && !Array.isArray(input.projectSkills);
180
174
  if (versionedProjectSkills && (input.runtime.name !== "codex" && input.runtime.name !== "claude")) {
@@ -242,7 +236,12 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
242
236
  }, "workspace_prepare", error);
243
237
  throw error;
244
238
  }
245
- 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);
246
245
  let materialized = null;
247
246
  let knownAttachmentDirectory = null;
248
247
  const startupReservationState = { current: null };
@@ -253,9 +252,12 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
253
252
  let memoryPruneBeforeSnapshot = null;
254
253
  let memoryPruneSharedWriteKey = null;
255
254
  let localMemoryResultPath = null;
255
+ let environmentSnapshot;
256
256
  let executionWorkspace = workspace;
257
257
  let localMemoryTelemetry = null;
258
- const inheritedEnv = { ...process.env };
258
+ let nativeThreadContext = null;
259
+ const machineSnapshot = dependencies.machineEnvironment ? await awaitWithCancellation(dependencies.machineEnvironment.acquire(), dependencies.cancellation) : undefined;
260
+ const inheritedEnv = { ...(machineSnapshot?.env ?? process.env) };
259
261
  const reportRoots = {
260
262
  root: workspace.dir,
261
263
  ...(input.launch.qaReportRoot === undefined ? {} : { qaReportRoot: input.launch.qaReportRoot }),
@@ -267,8 +269,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
267
269
  if (key.startsWith("CREW_AGENT_MEMORY_") || ["CREW_CLOUD_PROVIDER", "CREW_OSS_UPLOAD_SCRIPT", "CREW_OSS_REPORT_UPLOAD"].includes(key))
268
270
  delete inheritedEnv[key];
269
271
  }
270
- const sourceCodexHome = inheritedEnv.CODEX_HOME
271
- ?? (inheritedEnv.HOME === undefined ? undefined : join(inheritedEnv.HOME, ".codex"));
272
272
  let materializedDeepSeekHome;
273
273
  try {
274
274
  if (isDeepSeekCodex && !providerConfig.providerApiKey) {
@@ -294,12 +294,11 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
294
294
  : null;
295
295
  const currentPrior = storedCurrentPrior !== null
296
296
  && storedCurrentPrior.sessionContextFingerprint === input.sessionContextFingerprint
297
+ && storedCurrentPrior.runtime === runtime.name
298
+ && storedCurrentPrior.threadRoot === input.resumeKey
297
299
  ? storedCurrentPrior
298
300
  : null;
299
- // Interactive V1 uses collision-resistant session directories, while the
300
- // older legacy path stored metadata under tasks/<safe thread key>. Import
301
- // that metadata once so an existing thread can cross the protocol boundary
302
- // without losing its native runtime conversation.
301
+ // Import compatible legacy metadata across the protocol boundary.
303
302
  const legacyRunDir = input.session.enabled
304
303
  && supportsNativeResume
305
304
  && input.keyMode === "opaque"
@@ -315,7 +314,9 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
315
314
  const legacyPrior = legacyRunDir === null || (currentPrior !== null && !legacyCwdPinned)
316
315
  ? null
317
316
  : await readSession(legacyRunDir);
318
- const prior = currentPrior ?? legacyPrior;
317
+ const compatibleLegacyPrior = legacyPrior?.runtime === runtime.name && legacyPrior?.threadRoot === input.resumeKey
318
+ ? legacyPrior : null;
319
+ const prior = currentPrior ?? compatibleLegacyPrior;
319
320
  const sessionExists = workspace.sessionResume || legacyPrior !== null;
320
321
  const usesLegacyCwd = legacyRunDir !== null
321
322
  && (legacyCwdPinned || (currentPrior === null && legacyPrior !== null));
@@ -336,11 +337,11 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
336
337
  ? prior?.model ?? currentModel
337
338
  : currentModel;
338
339
  const resumeSessionId = supportsNativeResume && sessionExists
339
- ? pickResumeId(prior, Date.now(), input.session.warmMs, resumeComparisonModel, input.session.budgetTokens, input.session.maxTurns, providerFp)
340
+ ? pickResumeId(input.resumeKey && (!Number.isSafeInteger(prior?.threadContextSeq) || prior?.threadHistoryComplete !== true) ? null : prior, Date.now(), input.resumeKey ? 0 : input.session.warmMs, resumeComparisonModel, input.resumeKey ? 0 : input.session.budgetTokens, input.resumeKey ? 0 : input.session.maxTurns, providerFp)
340
341
  : null;
341
342
  const resuming = resumeSessionId !== null;
342
343
  const rotatedForBudget = !resuming && supportsNativeResume && sessionExists
343
- && pickResumeId(prior, Date.now(), input.session.warmMs, resumeComparisonModel, 0, 0, providerFp) !== null;
344
+ && pickResumeId(prior, Date.now(), input.resumeKey ? 0 : input.session.warmMs, resumeComparisonModel, 0, 0, providerFp) !== null;
344
345
  const nearBudget = resuming && isNearBudget(prior, input.session.softTokens);
345
346
  const rotated = Boolean(workspace.agentSessionId && sessionExists && !resuming);
346
347
  // Codex and Kimi choose their own id on a fresh start, so the native id persisted in
@@ -348,8 +349,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
348
349
  const launchSessionId = resumeSessionId ?? (rotated
349
350
  ? await rotateAgentSession(workspace.sessionDir)
350
351
  : workspace.agentSessionId);
351
- 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 }) });
352
- const promptContext = {
352
+ let promptContext = {
353
353
  workspace: executionWorkspace,
354
354
  resuming,
355
355
  rotatedForBudget,
@@ -388,8 +388,15 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
388
388
  }
389
389
  }
390
390
  const attachmentPlan = routeRuntimeAttachments(runtime.name, materialized?.attachments ?? []);
391
- const wakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
392
- memoryPruneTraceId = parseMemoryPruneTraceId(wakePrompt);
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
+ }
393
400
  if (input.localMemoryV2Enabled === true && memoryPruneTraceId !== null
394
401
  && isLocalMemoryV2Eligible({
395
402
  enabledForHandle: true,
@@ -402,12 +409,15 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
402
409
  }
403
410
  const memoryPrompt = await prepareLocalMemoryPrompt({
404
411
  agentDir: executionWorkspace.dir, executionId: input.executionId, agentHandle: input.handle,
405
- enabled: input.localMemoryV2Enabled === true, query: wakePrompt,
412
+ enabled: input.localMemoryV2Enabled === true, query: baseWakePrompt,
406
413
  resultPath: localMemoryResultPath, maxBytes: input.maxSystemPromptBytes,
414
+ strictRollout: !isolatedMaintenance && localMemoryV2Environment(input.launch.serverUrl) === "dev",
415
+ deferred: memoryRollout.deferred,
407
416
  renderBase: (reservedPromptBytes) => resolvePrompt(input.systemPrompt, { ...promptContext, reservedPromptBytes }),
408
417
  });
409
418
  localMemoryResultPath = memoryPrompt.resultPath;
410
419
  const systemPrompt = memoryPrompt.systemPrompt;
420
+ const wakePrompt = baseWakePrompt + memoryPrompt.wakePromptSuffix;
411
421
  if (!isolatedMaintenance)
412
422
  try {
413
423
  localMemoryTelemetry = createLocalMemoryTelemetry({
@@ -466,45 +476,14 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
466
476
  }
467
477
  memoryPruneFailurePhase = "runtime_prepare";
468
478
  const crewHome = isolatedMaintenance ? executionWorkspace.runDir : workspace.dir;
469
- const baseEnv = {
470
- ...inheritedEnv,
471
- ...sanitizeEnvVars(providerConfig.envVars),
472
- ...(input.trainingProjectContext === undefined
473
- ? {}
474
- : machinePackageManagerEnv(inheritedEnv)),
475
- ...input.launch.systemEnv,
476
- PATH: `${workspace.crewDir}${delimiter}${augmentedPath()}`,
477
- CREW_SERVER_URL: input.launch.serverUrl,
478
- CREW_TOKEN: input.launch.token,
479
- CREW_CHANNEL: input.channelId,
480
- CREW_HOME: crewHome,
481
- // Keep the daemon-selected storage root authoritative for runtimes.
482
- // In profile-backed NowWork sessions the daemon config may differ from
483
- // the daemon process environment, so inheriting CREW_AGENTS_ROOT is not
484
- // sufficient for QA/project diagnostics.
485
- CREW_AGENTS_ROOT: input.launch.agentsRoot,
486
- ...(input.launch.reposRoot === undefined ? {} : { NOWWORK_REPOS_ROOT: input.launch.reposRoot }),
487
- ...(input.launch.qaReportRoot === undefined ? {} : { QA_REPORT_HUB: input.launch.qaReportRoot }),
488
- // Expose only the training root; ability-specific workspace layout stays
489
- // owned by the training package rather than the NowWork daemon.
490
- QA_RUNTIME_ROOT: join(crewHome, "training"),
491
- QA_REPORT_DELIVERY_CONTEXT: JSON.stringify(reportContext),
492
- CREW_TASK_DIR: executionWorkspace.runDir,
493
- CREW_TASK_LOG: executionWorkspace.workLogPath,
494
- ...(input.wakeMessageId ? { CREW_WAKE_MESSAGE_ID: input.wakeMessageId } : {}),
495
- XDG_CONFIG_HOME: join(workspace.homeDir, ".config"),
496
- XDG_DATA_HOME: join(workspace.homeDir, ".local", "share"),
497
- XDG_CACHE_HOME: join(workspace.homeDir, ".cache"),
498
- GH_CONFIG_DIR: join(workspace.homeDir, ".config", "gh"),
499
- CLOUDSDK_CONFIG: join(workspace.homeDir, ".config", "gcloud"),
500
- ...(runtime.name === "kimi" && runtime.reasoning
501
- && KIMI_EFFORT_LEVELS.includes(runtime.reasoning)
502
- ? { KIMI_MODEL_THINKING_EFFORT: runtime.reasoning }
503
- : {}),
504
- };
505
- const providerEnv = applyProviderEnv(baseEnv, runtime.name, input.launch.codexHomeMode === undefined
506
- ? providerConfig
507
- : { ...providerConfig, codexHomeMode: input.launch.codexHomeMode }, workspace.homeDir, materializedDeepSeekHome);
479
+ const contextThreadId = input.threadId ?? input.resumeKey;
480
+ if (contextThreadId && input.maintenancePurpose === undefined) {
481
+ nativeThreadContext = new NativeThreadContext(join(executionWorkspace.runDir, `.crew-context-${input.executionId}.json`), contextThreadId, runtime.name, resumeSessionId, prior);
482
+ await nativeThreadContext.initialize();
483
+ }
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);
508
487
  const childEnv = runtime.name === "deepseek-harness"
509
488
  ? applyDeepSeekHarnessMachineEnv(providerEnv, inheritedEnv)
510
489
  : providerEnv;
@@ -521,11 +500,17 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
521
500
  const abilityContext = input.abilityRelease !== undefined && dependencies.abilityRelease !== undefined
522
501
  ? await dependencies.abilityRelease.prepareAndLaunch(input.launch.agentsRoot, input.handle, input.abilityRelease, async (context) => context)
523
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
+ }
524
510
  const prepareRuntimeLaunch = async (runtimeRoot) => {
525
- const effectiveSystemPrompt = abilityContext === undefined
526
- ? systemPrompt
527
- : `${systemPrompt}\n\n${abilityContext.prompt}`;
528
- if (Buffer.byteLength(effectiveSystemPrompt, "utf8") > (input.maxSystemPromptBytes ?? Number.MAX_SAFE_INTEGER)) {
511
+ const effectiveSystemPrompt = [systemPrompt, abilityContext?.prompt, environmentSnapshot?.prompt].filter(Boolean).join("\n\n");
512
+ if (Buffer.byteLength(effectiveSystemPrompt, "utf8") + Buffer.byteLength(memoryPrompt.wakePromptSuffix, "utf8")
513
+ > (input.maxSystemPromptBytes ?? Number.MAX_SAFE_INTEGER)) {
529
514
  throw new Error("ability_runtime_context_size_exceeded");
530
515
  }
531
516
  memoryPruneFailurePhase = "prompt_write";
@@ -559,7 +544,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
559
544
  });
560
545
  }
561
546
  const trainingRoots = input.trainingProjectContext?.projects.map((project) => project.root) ?? [];
562
- const { codexSkillRoots, claudeAdditionalDirectories } = await projectSkillRuntimeDirectories({
547
+ const { codexSkillRoots, claudeAdditionalDirectories, harnessSkillRoots } = await projectSkillRuntimeDirectories({
563
548
  runtime: runtime.name,
564
549
  ...(runtimeRoot === undefined ? {} : { runtimeRoot }),
565
550
  agentRoot: workspace.dir,
@@ -597,7 +582,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
597
582
  const workspaceRoots = [...legacyRoots, ...trainingRoots];
598
583
  const launchRequest = {
599
584
  runtime: runtime.name,
600
- 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 ?? [] } : {}),
601
587
  cwd: input.projectContext?.primary?.root ?? executionWorkspace.runDir,
602
588
  ...(runtimeRoot !== undefined
603
589
  || (input.projectSkills === undefined && input.abilityRelease === undefined)
@@ -713,7 +699,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
713
699
  // (incident: 2026-09-02, boss-alarm 排查结论未投递)。所有 runtime 的文本都喂给它。
714
700
  const externalOutput = new ExternalAnswerDecoder();
715
701
  // 每轮独立:tool_use/result 关联状态不能跨 execution 泄漏。
716
- const consoleFormatter = createConsoleFormatter();
702
+ const consoleFormatter = createConsoleFormatter(runtime.name);
717
703
  let runtimeReady = false;
718
704
  let runtimeRunning = false;
719
705
  let resolveRuntimeReady;
@@ -801,6 +787,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
801
787
  const event = parseLine(line);
802
788
  if (!event)
803
789
  return;
790
+ nativeThreadContext?.observe(event);
804
791
  localMemoryTelemetry?.observe(event);
805
792
  if (isRuntimeReadyEvent(runtime.name, event))
806
793
  markRuntimeReady();
@@ -918,14 +905,17 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
918
905
  memoryPruneRuntimeExitCode = exitCode;
919
906
  if (localMemoryResultPath !== null) {
920
907
  memoryPruneFailurePhase = "memory_commit";
921
- const committed = await commitLocalMemoryResultAfterExit({
922
- resultPath: localMemoryResultPath,
908
+ const resultPath = localMemoryResultPath;
909
+ const commit = () => commitLocalMemoryResultAfterExit({
910
+ resultPath,
923
911
  agentDir: executionWorkspace.dir,
924
912
  executionId: input.executionId,
925
913
  pruneTraceId: memoryPruneTraceId,
926
914
  exitCode,
927
915
  workLogPath: executionWorkspace.workLogPath,
916
+ expectedWorkLogSha256: workLogSha256(executionWorkspace.workLog),
928
917
  });
918
+ const committed = exitCode !== 0 ? await commit() : await withWorkLogCommit(workLogExecutionKey(input.launch.agentsRoot, input.handle, input.taskKey), commit, dependencies.cancellation);
929
919
  if (committed.status === "failed" || committed.status === "missing") {
930
920
  const reason = committed.error ?? "local_memory_result_missing";
931
921
  dslog("local_memory.commit_failed", "结构化本地记忆提交或清理未完成,保留可用恢复材料", {
@@ -959,9 +949,12 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
959
949
  ? usage.inputTokens + usage.cacheReadTokens + usage.cacheCreationTokens
960
950
  : (resuming ? prior?.contextTokens : undefined);
961
951
  await writeSession(workspace.sessionDir, {
952
+ runtime: runtime.name,
953
+ ...(input.resumeKey === undefined ? {} : { threadRoot: input.resumeKey }),
954
+ ...(exitCode === 0 ? await nativeThreadContext?.checkpoint() ?? {} : {}),
962
955
  sessionId,
963
956
  lastRunAt: Date.now(),
964
- turns: resuming ? (prior?.turns ?? 0) + 1 : 1,
957
+ turns: nativeThreadContext?.confirmed ? (prior?.turns ?? 0) + 1 : 1,
965
958
  model: currentModel,
966
959
  providerFingerprint: providerFp,
967
960
  ...(input.sessionContextFingerprint === undefined
@@ -995,7 +988,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
995
988
  ...(usage === undefined ? {} : { usage }),
996
989
  model: observedModel,
997
990
  runtime: runtime.name,
998
- resumed: resuming,
991
+ resumed: nativeThreadContext?.confirmed ?? false,
999
992
  sessionId,
1000
993
  errorMessage: errorTail || null,
1001
994
  ...(providerError === undefined ? {} : { providerError }),
@@ -1007,6 +1000,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
1007
1000
  };
1008
1001
  }
1009
1002
  finally {
1003
+ await nativeThreadContext?.cleanup().catch(() => undefined);
1010
1004
  startupReservationState.current?.release();
1011
1005
  try {
1012
1006
  await localMemoryTelemetry?.finish();
@@ -1099,6 +1093,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
1099
1093
  active_prune_count: activePruneCount,
1100
1094
  });
1101
1095
  }
1096
+ if (environmentSnapshot)
1097
+ await rm(environmentSnapshot.directory, { recursive: true, force: true }).catch(() => { });
1102
1098
  const attachmentDirectories = new Set([
1103
1099
  ...(knownAttachmentDirectory === null ? [] : [knownAttachmentDirectory]),
1104
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";