@nowcrew/daemon 0.6.98-beta.2 → 0.6.98-beta.3

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 (68) hide show
  1. package/README.md +41 -1
  2. package/dist/cloud-report-upload.js +10 -1
  3. package/dist/computer-main.js +0 -0
  4. package/dist/computer-setup-client.js +17 -6
  5. package/dist/computer-setup-orchestration.js +13 -2
  6. package/dist/computer-setup.js +36 -6
  7. package/dist/computer-token-setup.js +46 -0
  8. package/dist/execution-journal.js +1 -3
  9. package/dist/execution-protocol.js +1 -1
  10. package/dist/execution-runner.js +1 -4
  11. package/dist/i18n.js +10 -0
  12. package/dist/local-execution-prompt.js +3 -8
  13. package/dist/local-executor.js +39 -63
  14. package/dist/local-memory/index-budget.js +3 -9
  15. package/dist/local-memory/inventory-cli.js +8 -52
  16. package/dist/local-memory/inventory-source.js +8 -15
  17. package/dist/local-memory/pending-query.js +2 -5
  18. package/dist/local-memory/pending-schema.js +1 -1
  19. package/dist/local-memory/pending.js +2 -16
  20. package/dist/local-memory/prompt.js +5 -21
  21. package/dist/local-memory/read-cli.js +76 -98
  22. package/dist/local-memory/rollout.js +11 -79
  23. package/dist/local-memory/runtime.js +13 -181
  24. package/dist/local-memory/write.js +21 -189
  25. package/dist/local-memory-diagnostics.js +13 -94
  26. package/dist/local-memory-telemetry.js +2 -19
  27. package/dist/machine-info.js +0 -3
  28. package/dist/main.js +0 -0
  29. package/dist/marketplace-protocol.js +1 -1
  30. package/dist/marketplace-remote.js +1 -1
  31. package/dist/marketplace-runner.js +1 -1
  32. package/dist/memory-prune-diagnostics.js +4 -60
  33. package/dist/message-file-preview.js +1 -11
  34. package/dist/prompt.js +2 -6
  35. package/dist/provider-env.js +8 -3
  36. package/dist/runner.js +1 -1
  37. package/dist/runtimes/claude-thread-runner.js +4 -25
  38. package/dist/runtimes/codex-app-server-runner.js +2 -0
  39. package/dist/runtimes/codex-execution-policy.js +12 -0
  40. package/dist/runtimes/codex.js +2 -0
  41. package/dist/serve.js +3 -38
  42. package/dist/token.js +0 -1
  43. package/dist/workspace-control-handler.js +3 -16
  44. package/dist/workspace.js +2 -13
  45. package/package.json +9 -8
  46. package/dist/local-memory/commit-telemetry.js +0 -30
  47. package/dist/local-memory/execution-workspace.js +0 -62
  48. package/dist/local-memory/full-index-search.js +0 -47
  49. package/dist/local-memory/initialization-identity.js +0 -36
  50. package/dist/local-memory/inventory-v2.js +0 -362
  51. package/dist/local-memory/legacy-adapter.js +0 -233
  52. package/dist/local-memory/migration-access.js +0 -242
  53. package/dist/local-memory/migration-cli.js +0 -54
  54. package/dist/local-memory/migration-io.js +0 -199
  55. package/dist/local-memory/migration-pending.js +0 -9
  56. package/dist/local-memory/migration-plan.js +0 -432
  57. package/dist/local-memory/migration-schema.js +0 -55
  58. package/dist/local-memory/migration-state-invariants.js +0 -65
  59. package/dist/local-memory/migration-store.js +0 -414
  60. package/dist/local-memory/migration-tool-entry.js +0 -21
  61. package/dist/local-memory/source-path.js +0 -13
  62. package/dist/local-memory/storage-format.js +0 -152
  63. package/dist/local-memory/writer-mode.js +0 -13
  64. package/dist/memory-migration-controller.js +0 -89
  65. package/dist/memory-migration-inspect.js +0 -108
  66. package/dist/memory-migration-journal.js +0 -14
  67. package/dist/memory-migration-protocol.js +0 -14
  68. package/dist/memory-migration-runtime.js +0 -305
@@ -6,7 +6,7 @@ import { createInterface } from "node:readline";
6
6
  import { readFile, rm, writeFile } from "node:fs/promises";
7
7
  import { join } from "node:path";
8
8
  import { cleanupSelfTrainingRunDir } from "./self-training-workspace.js";
9
- import { rotateAgentSession, safeKey, } from "./workspace.js";
9
+ import { prepareWorkspace, rotateAgentSession, safeKey, } from "./workspace.js";
10
10
  import { CLAUDE_ADDITIONAL_DIRECTORY_INSTRUCTIONS_ENV, isClaudeAdditionalDirectoryInstructionsSupported, probeClaudeVersion, spawnClaude, } from "./runtimes/claude.js";
11
11
  import { spawnCodex } from "./runtimes/codex.js";
12
12
  import { DEEPSEEK_CODEX_MODEL, DEEPSEEK_CODEX_REASONING_LEVELS, materializeDeepSeekCodexHome, } from "./runtimes/codex-deepseek-config.js";
@@ -39,20 +39,13 @@ import { projectSkillRuntimeDirectories, } from "./project-skills/runtime-launch
39
39
  import { logIdentityRosterRuntimePrepared } from "./identity-roster-telemetry.js";
40
40
  import { diffMemoryPruneNotes, evaluateMemoryPrunePostcondition, inspectMemoryPruneFilesWithinDeadline, logMemoryPruneDiagnosticsFailure, memoryPruneSnapshotFields, parseMemoryPruneTraceId, } from "./memory-prune-diagnostics.js";
41
41
  import { createLocalMemoryTelemetry, logLocalMemoryContextPrepareFailure, logLocalMemoryDiagnosticsFailure, } from "./local-memory-telemetry.js";
42
- import { LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS } from "./local-memory-diagnostics.js";
43
42
  import { CodexStartupStageParser } from "./codex-startup-stage.js";
44
43
  import { formatRuntimeStartupError } from "./runtime-startup-error.js";
45
44
  import { logRuntimeEnvironmentContract } from "./runtime-environment-telemetry.js";
46
- import { commitLocalMemoryResultAfterExit, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, } from "./local-memory/runtime.js";
45
+ import { commitLocalMemoryResultAfterExit, initializeLocalMemoryV2ForWorkspace, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, } from "./local-memory/runtime.js";
47
46
  import { prepareLocalMemoryPrompt } from "./local-memory/prompt.js";
48
- import { reportLocalMemoryV2Deferred } from "./local-memory/rollout.js";
49
- import { prepareExecutionMemoryWorkspace } from "./local-memory/execution-workspace.js";
47
+ import { reportLocalMemoryV2Deferred, resolveLocalMemoryV2Rollout } from "./local-memory/rollout.js";
50
48
  import { localMemoryV2Environment } from "./config.js";
51
- import { detectLocalMemoryStorageVersion } from "./local-memory/storage-format.js";
52
- import { MEMORY_STATE_GUARD_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY } from "./memory-migration-protocol.js";
53
- import { resolveLocalMemoryWriterMode } from "./local-memory/writer-mode.js";
54
- import { logLocalMemoryCommitApplied } from "./local-memory/commit-telemetry.js";
55
- import { shouldUseMigrationDeferredPending } from "./local-memory/migration-pending.js";
56
49
  export class RuntimeRunningCallbackTimeoutError extends Error {
57
50
  constructor(runtime, timeoutMs) {
58
51
  super(`${runtime} runtime-running callback timed out after ${timeoutMs}ms`);
@@ -66,6 +59,7 @@ const STDERR_TAIL_CAP = 2_000;
66
59
  // the old failure mode where a callback could hold execution slots forever.
67
60
  const RUNTIME_RUNNING_CALLBACK_TIMEOUT_MS = 30_000;
68
61
  const MEMORY_PRUNE_DIAGNOSTICS_TIMEOUT_MS = 2_000;
62
+ const LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS = 250;
69
63
  const CLAUDE_INSTRUCTION_WARNING = "claude_additional_directory_instructions_unverified";
70
64
  const CODEX_COLLABORATION_EXEC_WARNING = "codex_collaboration_disabled_on_exec";
71
65
  function withoutClaudeInstructionFlag(env) {
@@ -217,12 +211,37 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
217
211
  : "high"
218
212
  : runtime.reasoning;
219
213
  const providerFp = providerFingerprint(runtime.name, providerConfig);
220
- const preparedMemory = await prepareExecutionMemoryWorkspace(input, isolatedMaintenance, dependencies.cancellation);
221
- let workspace = preparedMemory.workspace;
222
- const memoryRollout = preparedMemory.mode;
214
+ let workspace;
215
+ try {
216
+ workspace = await awaitWithCancellation(prepareWorkspace({
217
+ agentsRoot: input.launch.agentsRoot,
218
+ handle: input.handle,
219
+ cliPath: input.launch.cliPath,
220
+ executionId: input.executionId,
221
+ ...(input.keyMode === undefined ? {} : { keyMode: input.keyMode }),
222
+ ...(input.taskKey === undefined ? {} : { taskKey: input.taskKey }),
223
+ ...(input.resumeKey === undefined ? {} : { resumeKey: input.resumeKey }),
224
+ ...(input.sessionContextFingerprint === undefined
225
+ ? {}
226
+ : { sessionContextFingerprint: input.sessionContextFingerprint }),
227
+ ...(input.launch.description ? { description: input.launch.description } : {}),
228
+ ...(isolatedMaintenance ? { skipLocalMemory: true } : {}),
229
+ }), dependencies.cancellation);
230
+ }
231
+ catch (error) {
232
+ logLocalMemoryContextPrepareFailure({
233
+ executionId: input.executionId,
234
+ agentHandle: input.handle,
235
+ ...(input.taskKey === undefined ? {} : { taskKey: input.taskKey }),
236
+ }, "workspace_prepare", error);
237
+ throw error;
238
+ }
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 };
223
243
  input = { ...input, localMemoryV2Enabled: memoryRollout.enabled };
224
- const localMemoryV2Initialized = preparedMemory.initialized;
225
- let localMemoryWriterMode = resolveLocalMemoryWriterMode(workspace, memoryRollout);
244
+ const localMemoryV2Initialized = await initializeLocalMemoryV2ForWorkspace(workspace, memoryRollout.enabled);
226
245
  let materialized = null;
227
246
  let knownAttachmentDirectory = null;
228
247
  const startupReservationState = { current: null };
@@ -233,7 +252,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
233
252
  let memoryPruneBeforeSnapshot = null;
234
253
  let memoryPruneSharedWriteKey = null;
235
254
  let localMemoryResultPath = null;
236
- let migrationDeferredPending = false;
237
255
  let environmentSnapshot;
238
256
  let executionWorkspace = workspace;
239
257
  let localMemoryTelemetry = null;
@@ -372,41 +390,29 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
372
390
  const attachmentPlan = routeRuntimeAttachments(runtime.name, materialized?.attachments ?? []);
373
391
  let baseWakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
374
392
  memoryPruneTraceId = isolatedMaintenance ? null : parseMemoryPruneTraceId(baseWakePrompt);
375
- localMemoryWriterMode = resolveLocalMemoryWriterMode(executionWorkspace, memoryRollout);
376
- migrationDeferredPending = shouldUseMigrationDeferredPending({ pruneTraceId: memoryPruneTraceId,
377
- writerMode: localMemoryWriterMode, rolloutEnabled: memoryRollout.enabled,
378
- legacyFallback: memoryRollout.legacyFallback,
379
- failure: memoryRollout.failure, memory: executionWorkspace.memory });
380
- if (memoryRollout.deferred || migrationDeferredPending)
381
- reportLocalMemoryV2Deferred(input.executionId, input.handle, memoryPruneTraceId !== null || input.maintenancePurpose === "memory_prune", memoryRollout.legacyFallback, memoryRollout.failure, migrationDeferredPending);
382
- if (memoryPruneTraceId !== null && localMemoryWriterMode === "blocked" && !migrationDeferredPending) {
383
- throw new Error(memoryRollout.failure === "inspection_failed" ? "local_memory_inspection_failed" : "local_memory_v2_requires_migration");
384
- }
393
+ if (memoryRollout.deferred)
394
+ reportLocalMemoryV2Deferred(input.executionId, input.handle, memoryPruneTraceId !== null || input.maintenancePurpose === "memory_prune");
385
395
  if (memoryPruneTraceId !== null) {
386
396
  executionWorkspace = await resolveMemoryPruneWorkLog(executionWorkspace, input);
387
397
  promptContext = { ...promptContext, workspace: executionWorkspace };
388
398
  baseWakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
389
399
  }
390
- if ((localMemoryWriterMode === "v2" && memoryPruneTraceId !== null
400
+ if (input.localMemoryV2Enabled === true && memoryPruneTraceId !== null
391
401
  && isLocalMemoryV2Eligible({
392
402
  enabledForHandle: true,
393
403
  agentHomePreexisted: workspace.agentHomePreexisted === true,
394
404
  memorySeedCreated: executionWorkspace.memorySeedCreated === true && (localMemoryV2Initialized || workspace.localMemoryV2Initialized !== false),
395
405
  localMemoryV2Initialized,
396
406
  memoryPrune: true,
397
- })) || migrationDeferredPending) {
407
+ })) {
398
408
  localMemoryResultPath = makeLocalMemoryResultPath(executionWorkspace.runDir, input.executionId);
399
409
  }
400
410
  const memoryPrompt = await prepareLocalMemoryPrompt({
401
411
  agentDir: executionWorkspace.dir, executionId: input.executionId, agentHandle: input.handle,
402
412
  enabled: input.localMemoryV2Enabled === true, query: baseWakePrompt,
403
413
  resultPath: localMemoryResultPath, maxBytes: input.maxSystemPromptBytes,
404
- storageVersion: detectLocalMemoryStorageVersion(executionWorkspace.memory),
405
- strictRollout: !isolatedMaintenance && !memoryRollout.legacyFallback && (localMemoryV2Environment(input.launch.serverUrl) === "dev"
406
- || detectLocalMemoryStorageVersion(executionWorkspace.memory) === 2),
414
+ strictRollout: !isolatedMaintenance && localMemoryV2Environment(input.launch.serverUrl) === "dev",
407
415
  deferred: memoryRollout.deferred,
408
- ...(memoryRollout.legacyFallback ? { legacyFallback: true } : {}),
409
- ...(migrationDeferredPending ? { migrationDeferred: true } : {}),
410
416
  renderBase: (reservedPromptBytes) => resolvePrompt(input.systemPrompt, { ...promptContext, reservedPromptBytes }),
411
417
  });
412
418
  localMemoryResultPath = memoryPrompt.resultPath;
@@ -903,11 +909,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
903
909
  exitCode,
904
910
  workLogPath: executionWorkspace.workLogPath,
905
911
  expectedWorkLogSha256: workLogSha256(executionWorkspace.workLog),
906
- ...(localMemoryWriterMode === "v2" ? {
907
- writerCapabilities: [MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY],
908
- requiredStorageVersion: 2,
909
- } : {}),
910
- ...(migrationDeferredPending ? { migrationDeferredPending: true } : {}),
911
912
  });
912
913
  const committed = exitCode !== 0 ? await commit() : await withWorkLogCommit(workLogExecutionKey(input.launch.agentsRoot, input.handle, input.taskKey), commit, dependencies.cancellation);
913
914
  if (committed.status === "failed" || committed.status === "missing") {
@@ -921,14 +922,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
921
922
  });
922
923
  throw new Error(`local_memory_commit_failed: ${reason}`);
923
924
  }
924
- if (committed.status === "applied") {
925
- if (localMemoryWriterMode === "v2" && (committed.storageVersionBefore !== 2 || committed.storageVersionAfter !== 2
926
- || committed.committedState !== true)) {
927
- throw new Error("local_memory_commit_failed: local_memory_v2_commit_state_invalid");
928
- }
929
- logLocalMemoryCommitApplied({ executionId: input.executionId, agentHandle: input.handle,
930
- pruneTraceId: memoryPruneTraceId, committed });
931
- }
932
925
  memoryPruneFailurePhase = "runtime_finalize";
933
926
  }
934
927
  const errorTail = [
@@ -1052,23 +1045,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
1052
1045
  after_incomplete_file_count: noteDiff.after_manifest.incomplete_file_count,
1053
1046
  changes: serializedChanges.json,
1054
1047
  });
1055
- const pendingDiff = diffMemoryPruneNotes(memoryPruneBeforeSnapshot.pending_manifest
1056
- ?? { files: [], total_count: 0, omitted_count: 0, truncated: false }, snapshot.pending_manifest
1057
- ?? { files: [], total_count: 0, omitted_count: 0, truncated: false });
1058
- const serializedPendingChanges = boundedDiagnosticJsonArray(pendingDiff.changes);
1059
- const pendingChangesTruncated = pendingDiff.truncated || serializedPendingChanges.truncated;
1060
- dslog("memory_prune.pending_changed", "长期记忆 pending 候选变化已评估", {
1061
- execution_id: input.executionId,
1062
- agent_handle: input.handle,
1063
- task_key: input.taskKey,
1064
- prune_trace_id: memoryPruneTraceId,
1065
- change_count: pendingDiff.change_count,
1066
- changes_truncated: pendingChangesTruncated,
1067
- evidence_complete: pendingDiff.evidence_complete && !pendingChangesTruncated,
1068
- before_total_count: pendingDiff.before_manifest.total_count,
1069
- after_total_count: pendingDiff.after_manifest.total_count,
1070
- changes: serializedPendingChanges.json,
1071
- });
1072
1048
  const postcondition = evaluateMemoryPrunePostcondition(memoryPruneBeforeSnapshot, snapshot, {
1073
1049
  executorCompleted: memoryPruneExecutorCompleted,
1074
1050
  ...(memoryPruneRuntimeExitCode === undefined
@@ -50,17 +50,14 @@ export function selectLocalMemoryIndexEntries(entries, budget, options = {}) {
50
50
  .map((entry) => entry.id);
51
51
  const protectedIds = options.protectedIds ?? new Set();
52
52
  const protectedEntries = projectable.filter((entry) => protectedIds.has(entry.id)).sort(compareEntries);
53
- const candidates = projectable.filter((entry) => !protectedIds.has(entry.id)).sort((left, right) => (options.relevance?.(right) ?? 0) - (options.relevance?.(left) ?? 0) || compareEntries(left, right));
53
+ const candidates = projectable.filter((entry) => !protectedIds.has(entry.id)).sort(compareEntries);
54
54
  const selected = [];
55
55
  let renderedLength = 0;
56
- let renderedBytes = 0;
57
56
  for (const entry of protectedEntries) {
58
57
  selected.push(entry);
59
58
  renderedLength += cardLength(entry, selected.length > 1);
60
- renderedBytes += Buffer.byteLength(renderLocalMemoryIndexCard(entry), "utf8") + (selected.length > 1 ? 1 : 0);
61
59
  }
62
- if (selected.length > budget.entryCap || renderedLength > budget.indexCodePointCap
63
- || renderedBytes > (budget.indexByteCap ?? Infinity)) {
60
+ if (selected.length > budget.entryCap || renderedLength > budget.indexCodePointCap) {
64
61
  return {
65
62
  entries: selected,
66
63
  omittedIds: [...omittedIds, ...candidates.map((entry) => entry.id)],
@@ -68,15 +65,12 @@ export function selectLocalMemoryIndexEntries(entries, budget, options = {}) {
68
65
  };
69
66
  }
70
67
  for (const entry of candidates) {
71
- const bytes = Buffer.byteLength(renderLocalMemoryIndexCard(entry), "utf8") + (selected.length > 0 ? 1 : 0);
72
- if (selected.length >= budget.entryCap || renderedLength + cardLength(entry, selected.length > 0) > budget.indexCodePointCap
73
- || renderedBytes + bytes > (budget.indexByteCap ?? Infinity)) {
68
+ if (selected.length >= budget.entryCap || renderedLength + cardLength(entry, selected.length > 0) > budget.indexCodePointCap) {
74
69
  omittedIds.push(entry.id);
75
70
  continue;
76
71
  }
77
72
  selected.push(entry);
78
73
  renderedLength += cardLength(entry, selected.length > 1);
79
- renderedBytes += bytes;
80
74
  }
81
75
  return {
82
76
  entries: selected,
@@ -2,9 +2,9 @@ const CommandSchema = z.object({
2
2
  "agent-dir": z.string().min(1).refine(isAbsolute),
3
3
  "dry-run": z.literal(true),
4
4
  }).strict();
5
- const USAGE = "只读记忆盘点、schema0/1 转换预演与 schema2 完整性校验,不写文件,不支持 --apply。\n"
5
+ const USAGE = "只读记忆盘点与转换预演,不写文件,不支持 --apply。\n"
6
6
  + "pnpm --filter @nowcrew/daemon exec tsx src/local-memory/inventory-cli.ts --agent-dir <单个Agent绝对路径> --dry-run\n"
7
- + "报告包含原始记忆,需作为敏感本地资料保存;退出码0仅代表盘点、格式预演或完整性校验无阻塞,不代表批准迁移。\n";
7
+ + "报告包含原始记忆,需作为敏感本地资料保存;退出码0仅代表盘点及格式预演无阻塞,不代表批准迁移。\n";
8
8
  export async function runMemoryInventoryCommand(args, io) {
9
9
  if (args.length === 1 && args[0] === "--help") {
10
10
  io.out(USAGE);
@@ -24,60 +24,19 @@ export async function runMemoryInventoryCommand(args, io) {
24
24
  try {
25
25
  const scan = await scanMemorySources(command["agent-dir"]);
26
26
  const scanComplete = scan.issues.length === 0;
27
- const memory = scan.sources.find((source) => source.path === "MEMORY.md")?.content ?? "";
28
- let storageVersion = null;
29
- let detectionIssue = null;
30
- try {
31
- storageVersion = detectLocalMemoryStorageVersion(memory);
32
- }
33
- catch (error) {
34
- detectionIssue = {
35
- code: error instanceof Error && /^[a-z0-9_]+$/u.test(error.message)
36
- ? error.message
37
- : "local_memory_storage_version_invalid",
38
- path: "MEMORY.md",
39
- };
40
- }
41
- const v2 = storageVersion === 2
42
- ? await inspectV2MemoryInventory(scan.root, scan.sources, scanComplete)
43
- : null;
44
- const analysis = detectionIssue
45
- ? { entries: [], issues: [detectionIssue], preview: null, rawMemory: memory }
46
- : v2?.analysis ?? previewMemoryInventory(scan.sources, scanComplete);
27
+ const analysis = previewMemoryInventory(scan.sources, scanComplete);
47
28
  const complete = scanComplete && !analysis.issues.some((issue) => issue.code === "missing_detail");
48
29
  const issues = [...scan.issues, ...analysis.issues];
49
- const referenced = new Set([
50
- ...analysis.entries.map((entry) => entry.sourceFields.file),
51
- ...(v2?.preservedSourcePaths ?? []),
52
- ]);
53
- const reportSources = v2?.stateSource ? [...scan.sources, v2.stateSource] : scan.sources;
54
- const files = reportSources.map(({ path, bytes, sha256 }) => ({ path, bytes, sha256 }));
55
- const deferredFiles = files.filter((file) => isDeferredMemoryArtifactPath(file.path)).map((file) => file.path);
56
- const orphanNotes = files.filter((file) => file.path.startsWith("notes/") && !isDeferredMemoryArtifactPath(file.path) && !referenced.has(file.path)).map((file) => file.path);
57
- const indexedEntries = analysis.entries.filter((entry) => entry.format === "managed").length;
58
- const legacyEntries = analysis.entries.length - indexedEntries;
30
+ const referenced = new Set(analysis.entries.map((entry) => entry.sourceFields.file));
31
+ const files = scan.sources.map(({ path, bytes, sha256 }) => ({ path, bytes, sha256 }));
59
32
  const report = {
60
33
  schemaVersion: 1, mode: "dry-run", agentDir: scan.root, applySupported: false,
61
34
  complete, scanComplete, requiresHumanReview: true,
62
- // 只读派生观测,不改变资格判断,也不新增持久化状态。
63
- // A dry-run does not create a shadow index or server state. Existing
64
- // schema1 cards therefore remain legacy; indexedEntries is only coverage
65
- // information, not proof that the Agent entered shadow mode.
66
- storageMode: storageVersion === 2 ? (v2?.writeReady ? "v2_write" : "repair_required")
67
- : detectionIssue ? "repair_required" : "legacy",
68
- contextMode: storageVersion === 2 ? (v2?.writeReady ? "bounded_preview" : "blocked")
69
- : detectionIssue ? "blocked" : "legacy_context",
70
- readerMode: storageVersion === 2 ? "v2" : detectionIssue ? "blocked" : "legacy",
71
- detectedIndexVersion: storageVersion,
72
- migrationCoverage: { detectedIndexVersion: storageVersion, storageVersion, indexedEntries, legacyEntries, unindexedFiles: orphanNotes.length,
73
- totalEntries: analysis.entries.length, complete },
74
- coverage: ["MEMORY.md", "notes/**", ...(storageVersion === 2 ? [".crew/memory/migration.v1.json"] : []),
75
- ".crew/memory/pending/**", "根目录的 .memory-result.v1-*.json"],
35
+ coverage: ["MEMORY.md", "notes/**", ".crew/memory/pending/**", "根目录的 .memory-result.v1-*.json"],
76
36
  limitations: ["不扫描历史执行目录;result清单仅覆盖Agent根目录。", "两遍摘要比对不等同跨文件原子快照,正式迁移仍需停止写入并重新核对。"],
77
- limits: inventoryLimits, totalBytes: reportSources.reduce((total, source) => total + source.bytes, 0), files, ...analysis, issues,
37
+ limits: inventoryLimits, totalBytes: scan.totalBytes, files, ...analysis, issues,
78
38
  pending: files.filter((file) => file.path.startsWith(".crew/memory/pending/")).map((file) => file.path),
79
- deferredFiles,
80
- orphanNotes,
39
+ orphanNotes: files.filter((file) => file.path.startsWith("notes/") && !referenced.has(file.path)).map((file) => file.path),
81
40
  resultFiles: files.filter((file) => file.path.startsWith(".memory-result.v1-")).map((file) => file.path),
82
41
  };
83
42
  io.out(JSON.stringify(report, null, 2) + "\n");
@@ -100,6 +59,3 @@ import { parseArgs } from "node:util";
100
59
  import { z } from "zod";
101
60
  import { inventoryLimits, scanMemorySources } from "./inventory-source.js";
102
61
  import { previewMemoryInventory } from "./inventory-preview.js";
103
- import { inspectV2MemoryInventory } from "./inventory-v2.js";
104
- import { detectLocalMemoryStorageVersion } from "./storage-format.js";
105
- import { isDeferredMemoryArtifactPath } from "./source-path.js";
@@ -73,8 +73,7 @@ export async function readMemoryInventorySource(root, file, budget) {
73
73
  await handle.close();
74
74
  }
75
75
  }
76
- export async function scanMemorySources(agentDir, options = {}) {
77
- const includeOperationalFiles = options.includeOperationalFiles ?? true;
76
+ export async function scanMemorySources(agentDir) {
78
77
  const root = await realpath(agentDir);
79
78
  if ((await lstat(agentDir)).isSymbolicLink())
80
79
  throw new Error("agent_root_symlink");
@@ -125,14 +124,10 @@ export async function scanMemorySources(agentDir, options = {}) {
125
124
  }
126
125
  await visit("MEMORY.md");
127
126
  await visit("notes", true);
128
- const resultNames = includeOperationalFiles
129
- ? (await readdir(root)).filter((name) => /^\.memory-result\.v1-.*\.json$/u.test(name)).sort()
130
- : [];
131
- if (includeOperationalFiles) {
132
- await visit(".crew/memory/pending", true);
133
- for (const name of resultNames)
134
- await visit(name);
135
- }
127
+ await visit(".crew/memory/pending", true);
128
+ const resultNames = (await readdir(root)).filter((name) => /^\.memory-result\.v1-.*\.json$/u.test(name)).sort();
129
+ for (const name of resultNames)
130
+ await visit(name);
136
131
  // 第二遍比对摘要和目录清单,发现扫描期间的变化就拒绝完整性结论;这不是跨文件事务锁。
137
132
  for (const source of sources) {
138
133
  try {
@@ -155,11 +150,9 @@ export async function scanMemorySources(agentDir, options = {}) {
155
150
  addIssue("source_changed", file);
156
151
  }
157
152
  }
158
- if (includeOperationalFiles) {
159
- const currentResults = (await readdir(root)).filter((name) => /^\.memory-result\.v1-.*\.json$/u.test(name)).sort();
160
- if (JSON.stringify(currentResults) !== JSON.stringify(resultNames))
161
- addIssue("source_changed", ".memory-result.v1-*.json");
162
- }
153
+ const currentResults = (await readdir(root)).filter((name) => /^\.memory-result\.v1-.*\.json$/u.test(name)).sort();
154
+ if (JSON.stringify(currentResults) !== JSON.stringify(resultNames))
155
+ addIssue("source_changed", ".memory-result.v1-*.json");
163
156
  if (await realpath(agentDir) !== root)
164
157
  addIssue("source_changed", "agent_root");
165
158
  return { root, sources: sources.sort((a, b) => a.path.localeCompare(b.path)), issues, totalBytes };
@@ -15,7 +15,6 @@ const SearchSchema = z.object({
15
15
  cursor: z.string().max(2048).optional(), limit: z.number().int().min(1).max(40).default(10),
16
16
  }).strict();
17
17
  const excerpt = (text, cap) => Array.from(text).slice(0, cap).join("");
18
- const pendingState = (reason) => reason === "legacy_requires_migration" ? "migration_deferred" : "capacity_deferred";
19
18
  export async function searchPendingMemoryPage(input) {
20
19
  const parsed = SearchSchema.safeParse(input);
21
20
  if (!parsed.success)
@@ -62,8 +61,7 @@ export async function searchPendingMemoryPage(input) {
62
61
  const match = source && isRecallablePending(source.candidate) && pendingRecallScore(source.candidate, args.query) > 0
63
62
  ? { pendingId: source.candidate.pendingId, sha256: source.sha256, title: source.candidate.candidate.title,
64
63
  summary: excerpt(source.candidate.candidate.summary, 180), triggers: source.candidate.candidate.triggers.slice(0, 4),
65
- operation: "create", reason: source.candidate.reason,
66
- state: pendingState(source.candidate.reason) }
64
+ operation: "create", state: "capacity_deferred" }
67
65
  : null;
68
66
  const addition = match ?? warning;
69
67
  const size = addition ? Buffer.byteLength(JSON.stringify(addition), "utf8") + 2 : 0;
@@ -98,6 +96,5 @@ export async function readPendingMemoryBody(input) {
98
96
  throw new Error("memory_pending_not_recallable");
99
97
  const candidate = source.candidate.candidate;
100
98
  return { pendingId: source.candidate.pendingId, sha256: source.sha256, title: candidate.title,
101
- operation: "create", reason: source.candidate.reason,
102
- state: pendingState(source.candidate.reason), content: candidate.detailBody };
99
+ operation: "create", state: "capacity_deferred", content: candidate.detailBody };
103
100
  }
@@ -8,7 +8,7 @@ const ArchivedMemoryChangeSchema = LocalMemoryChangeSchema.extend({
8
8
  export const PendingIdSchema = z.string().max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u);
9
9
  export const PendingMemorySchema = z.object({
10
10
  schemaVersion: z.literal(1), pendingId: PendingIdSchema,
11
- createdAt: z.string().min(1).max(100), reason: z.enum(["index_over_budget", "legacy_requires_migration"]),
11
+ createdAt: z.string().min(1).max(100), reason: z.literal("index_over_budget"),
12
12
  source: z.object({ executionId: z.string().min(1).max(500), pruneTraceId: z.string().min(1).max(500).nullable() }).strict(),
13
13
  candidate: ArchivedMemoryChangeSchema,
14
14
  }).strict();
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { lstat, mkdir, realpath, rename, rm, readFile, writeFile } from "node:fs/promises";
2
+ import { lstat, mkdir, realpath, rename, rm, writeFile } from "node:fs/promises";
3
3
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
4
4
  import { isRecallablePending, parsePendingMemoryCandidate } from "./pending-schema.js";
5
5
  import { pendingFileNames, pendingReadDirectory, PENDING_FILE_BYTES, readPendingSource } from "./pending-source.js";
@@ -103,17 +103,6 @@ export async function persistPendingMemoryCandidate(input) {
103
103
  const directory = await pendingDirectory(input.agentDir);
104
104
  const target = join(directory, `${parsed.pendingId}.json`);
105
105
  await assertRegularFile(target);
106
- try {
107
- const existing = parsePendingMemoryCandidate(JSON.parse(await readFile(target, "utf8")));
108
- const dedupKey = (value) => value.reason === "legacy_requires_migration"
109
- ? JSON.stringify({ schemaVersion: value.schemaVersion, reason: value.reason, candidate: value.candidate })
110
- : JSON.stringify({ ...value, createdAt: "" });
111
- if (dedupKey(existing) === dedupKey(parsed))
112
- return target;
113
- }
114
- catch {
115
- // A malformed existing candidate is replaced by the validated input below.
116
- }
117
106
  await atomicWrite(target, `${JSON.stringify(parsed, null, 2)}\n`);
118
107
  return target;
119
108
  }
@@ -175,10 +164,7 @@ function truncateCodePoints(value, maxCodePoints) {
175
164
  return Array.from(value).slice(0, Math.max(0, maxCodePoints)).join("");
176
165
  }
177
166
  export function renderPendingMemoryContext(candidates, maxCodePoints = 2_000) {
178
- // Migration-deferred candidates remain explicitly searchable, but must not
179
- // be injected into the ordinary prompt before a reviewed migration promotes
180
- // them to the active catalog.
181
- const recallable = candidates.filter((candidate) => isRecallablePending(candidate) && candidate.reason === "index_over_budget");
167
+ const recallable = candidates.filter(isRecallablePending);
182
168
  if (recallable.length === 0 || maxCodePoints <= 0)
183
169
  return "";
184
170
  const header = `\n\n## Archived memory candidates (daemon-managed)\n容量暂存的新增资料,仅为部分预览,不代表完整检索;可参考但不能覆盖现行规则,未包含内容请按需查询。\n`;
@@ -1,8 +1,8 @@
1
1
  import { dslog } from "../slog.js";
2
2
  import { renderPendingMemoryContext, searchPendingMemoryCandidates } from "./pending.js";
3
3
  import { renderLocalMemoryResultInstructions, renderLocalMemoryV2WriteGuard } from "./runtime.js";
4
- import { LOCAL_MEMORY_V2_DEFERRED_GUIDANCE, LOCAL_MEMORY_LEGACY_GUIDANCE } from "./rollout.js";
5
- const PENDING_READ_GUIDANCE = "\n\nV2新增资料预览未命中不代表没有资料;先查有效索引,必要时用 crew-memory search-pending --query '关键词',按 nextCursor 续查;用 read-pending --id ID 分页读正文。容量暂存或迁移延期资料不能覆盖现行规则,未应用修改不作为记忆;迁移延期候选必须等 reviewed migration 后再按当前 ID/revision/hash 处理。用 crew-memory --help 查看参数。\n";
4
+ import { LOCAL_MEMORY_V2_DEFERRED_GUIDANCE } from "./rollout.js";
5
+ const PENDING_READ_GUIDANCE = "\n\nV2新增资料预览未命中不代表没有资料;先查有效索引,必要时用 crew-memory search-pending --query '关键词',按 nextCursor 续查;用 read-pending --id ID 分页读正文。容量暂存资料不能覆盖现行规则,未应用修改不作为记忆。用 crew-memory --help 查看参数。\n";
6
6
  export async function prepareLocalMemoryPrompt(input) {
7
7
  let pending = "";
8
8
  if (input.enabled) {
@@ -22,11 +22,10 @@ export async function prepareLocalMemoryPrompt(input) {
22
22
  }
23
23
  }
24
24
  const original = input.renderBase(0);
25
- const guard = input.legacyFallback ? LOCAL_MEMORY_LEGACY_GUIDANCE : input.deferred ? LOCAL_MEMORY_V2_DEFERRED_GUIDANCE
25
+ const guard = input.deferred ? LOCAL_MEMORY_V2_DEFERRED_GUIDANCE
26
26
  : input.strictRollout && input.enabled && input.resultPath === null ? renderLocalMemoryV2WriteGuard() : "";
27
- const structured = input.resultPath === null ? "" : renderLocalMemoryResultInstructions(input.resultPath, input.storageVersion, input.migrationDeferred === true);
27
+ const structured = input.resultPath === null ? "" : renderLocalMemoryResultInstructions(input.resultPath);
28
28
  const guidance = input.enabled ? PENDING_READ_GUIDANCE : "";
29
- let requiredMemoryBudgetError;
30
29
  // 短命令指引放在当前轮,独立于可舍弃预览;收尾契约仍保持系统与当前轮两份。
31
30
  for (const choice of [
32
31
  { suffix: pending + guard + structured, wake: guard + structured + guidance },
@@ -34,26 +33,11 @@ export async function prepareLocalMemoryPrompt(input) {
34
33
  { suffix: guard + structured, wake: guard + structured },
35
34
  ]) {
36
35
  const reserved = Buffer.byteLength(choice.suffix + choice.wake, "utf8");
37
- let base;
38
- try {
39
- base = reserved === 0 ? original : input.renderBase(reserved);
40
- }
41
- catch (error) {
42
- // 可选候选先让出预算;最后仍不能容纳身份/置顶时必须拒绝,不能省略必需记忆。
43
- if (input.storageVersion === 2 && error instanceof Error
44
- && ["local_memory_identity_budget_exceeded", "local_memory_pinned_budget_exceeded"].includes(error.message)) {
45
- requiredMemoryBudgetError = error;
46
- continue;
47
- }
48
- throw error;
49
- }
50
- const systemPrompt = base + choice.suffix;
36
+ const systemPrompt = (reserved === 0 ? original : input.renderBase(reserved)) + choice.suffix;
51
37
  if (input.maxBytes === undefined || Buffer.byteLength(systemPrompt + choice.wake, "utf8") <= input.maxBytes) {
52
38
  return { systemPrompt, resultPath: input.resultPath, wakePromptSuffix: choice.wake };
53
39
  }
54
40
  }
55
- if (requiredMemoryBudgetError)
56
- throw requiredMemoryBudgetError;
57
41
  if (input.strictRollout)
58
42
  throw new Error("local_memory_v2_prompt_over_budget");
59
43
  // 强制基础指令与契约本身仍无法共存时,保留既有非结构化兼容回退,不裁基础指令。