@nowcrew/daemon 0.6.91-beta.10 → 0.6.91-beta.12

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 (35) hide show
  1. package/dist/execution-journal.js +1 -3
  2. package/dist/execution-protocol.js +1 -1
  3. package/dist/execution-runner.js +1 -3
  4. package/dist/local-execution-prompt.js +3 -8
  5. package/dist/local-executor.js +35 -14
  6. package/dist/local-memory/index-budget.js +3 -9
  7. package/dist/local-memory/prompt.js +4 -20
  8. package/dist/local-memory/read-cli.js +1 -36
  9. package/dist/local-memory/rollout.js +8 -53
  10. package/dist/local-memory/runtime.js +2 -5
  11. package/dist/local-memory/write.js +17 -108
  12. package/dist/local-runtime-environment.js +6 -1
  13. package/dist/machine-info.js +0 -3
  14. package/dist/message-file-preview.js +1 -11
  15. package/dist/prompt.js +1 -5
  16. package/dist/runner.js +1 -1
  17. package/dist/serve.js +3 -36
  18. package/dist/token.js +0 -1
  19. package/dist/workspace-control-handler.js +3 -16
  20. package/package.json +1 -1
  21. package/dist/local-memory/execution-workspace.js +0 -36
  22. package/dist/local-memory/full-index-search.js +0 -47
  23. package/dist/local-memory/migration-access.js +0 -118
  24. package/dist/local-memory/migration-cli.js +0 -54
  25. package/dist/local-memory/migration-io.js +0 -199
  26. package/dist/local-memory/migration-plan.js +0 -283
  27. package/dist/local-memory/migration-schema.js +0 -55
  28. package/dist/local-memory/migration-store.js +0 -424
  29. package/dist/local-memory/migration-tool-entry.js +0 -21
  30. package/dist/local-memory/storage-format.js +0 -152
  31. package/dist/memory-migration-controller.js +0 -84
  32. package/dist/memory-migration-inspect.js +0 -74
  33. package/dist/memory-migration-journal.js +0 -14
  34. package/dist/memory-migration-protocol.js +0 -14
  35. package/dist/memory-migration-runtime.js +0 -305
@@ -8,7 +8,7 @@ import { provePosixProcessGroupStopped } from "./execution-posix-stop-proof.js";
8
8
  export { JournalLockedError, JournalLockCorruptionError } from "./execution-journal-lock.js";
9
9
  const ExecutionIdSchema = z.string().uuid();
10
10
  const TimestampSchema = z.string().datetime({ offset: true });
11
- const RuntimeSchema = z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness", "memory_migration", "pi"]);
11
+ const RuntimeSchema = z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness", "pi"]);
12
12
  const RUNTIME_READY_SUFFIX = ".runtime-ready";
13
13
  const RawJournalEntrySchema = z.object({
14
14
  executionId: ExecutionIdSchema,
@@ -523,8 +523,6 @@ export function createExecutionJournal(agentsRoot, options = {}) {
523
523
  }
524
524
  };
525
525
  const pruneInternal = async () => {
526
- if (options.retainCompleted)
527
- return;
528
526
  const protectedExecutionIds = new Set(options.protectedExecutionIds?.() ?? []);
529
527
  const entries = await readAll();
530
528
  const acknowledged = entries
@@ -319,7 +319,7 @@ const RawExecutionCompletedSchema = z.object({
319
319
  providerErrorCategory: ProviderErrorCategorySchema.optional(),
320
320
  providerHttpStatus: ProviderHttpStatusSchema.optional(),
321
321
  providerErrorCode: ProviderErrorCodeSchema.optional(),
322
- runtime: z.union([RuntimeSchema, z.literal("memory_migration")]),
322
+ runtime: RuntimeSchema,
323
323
  model: z.string().optional(),
324
324
  resumed: z.boolean(),
325
325
  finalText: z.string().optional(),
@@ -1,7 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { dirname, join, resolve } from "node:path";
3
3
  import { isLocalMemoryV2Enabled, localMemoryV2Environment, resolveReposRoot } from "./config.js";
4
- import { detectLocalMemoryStorageVersion } from "./local-memory/storage-format.js";
5
4
  import { DaemonToServerExecutionFrameSchema, ExecutionCompletedSchema, ExecutionRejectedSchema, ExecutionStartSchema, } from "./execution-protocol.js";
6
5
  import { JournalConflictError } from "./execution-journal.js";
7
6
  import { boundExecutionFrame } from "./execution-event-limit.js";
@@ -839,9 +838,8 @@ export async function runExecution(config, input, dependencies) {
839
838
  const localMemoryGuardInBase = localMemoryV2Environment(config.serverUrl) === "custom";
840
839
  const boundedSystemPrompt = (context) => {
841
840
  const enabled = context.workspace.localMemoryV2Enabled ?? localMemoryV2Enabled;
842
- const systemPromptWithLocalFacts = withLocalExecutionFacts(`${spec.instructions.systemPrompt}${enabled && localMemoryGuardInBase && detectLocalMemoryStorageVersion(context.workspace.memory) !== 2 ? renderLocalMemoryV2WriteGuard() : ""}`, baseSystemPromptBudget, {
841
+ const systemPromptWithLocalFacts = withLocalExecutionFacts(`${spec.instructions.systemPrompt}${enabled && localMemoryGuardInBase ? renderLocalMemoryV2WriteGuard() : ""}`, baseSystemPromptBudget, {
843
842
  ...(selfTraining ? { isolateLocalMemory: true } : {}),
844
- memoryQuery: spec.instructions.wakePrompt,
845
843
  collaborationTools: config.codexCollaborationTools && spec.runtime.name === "codex",
846
844
  });
847
845
  const base = typeof systemPromptWithLocalFacts === "string"
@@ -2,9 +2,8 @@
2
2
  * 本机执行提示词的合成与预算裁剪。
3
3
  *
4
4
  * server 提示词是权威部分,绝不裁剪;项目工作区提示词是绑定执行的强制部分,放不下就直接失败;
5
- * 本机事实与旧格式预览可按预算省略;schema2身份和置顶规则必须保留,放不下则拒绝执行。
5
+ * 只有本机事实(CREW_HOME / work-log / MEMORY 注入)是可选增强,按整段丢弃或按字节截断。
6
6
  */
7
- import { detectLocalMemoryStorageVersion } from "./local-memory/storage-format.js";
8
7
  import { buildCollaborationToolsPrompt, buildRuntimeWorkspacePrompt, capMemoryForInject, capWorkLogForInject, } from "./prompt.js";
9
8
  const trainingProjectJson = (project) => JSON.stringify({
10
9
  repositoryKey: project.repositoryKey,
@@ -58,14 +57,10 @@ export function withLocalExecutionFacts(serverPrompt, maxBytes, options = {}) {
58
57
  }
59
58
  const remaining = maxBytes - requiredBytes - Math.max(0, reservedPromptBytes);
60
59
  const factsBytes = Buffer.byteLength(localFacts, "utf8");
61
- if (factsBytes > remaining) {
62
- if (!options.isolateLocalMemory && detectLocalMemoryStorageVersion(workspace.memory) === 2) {
63
- throw new Error("local_memory_identity_budget_exceeded");
64
- }
60
+ if (factsBytes > remaining)
65
61
  return `${serverPrompt}${collaborationPrompt}${mandatoryWorkspacePrompt}`;
66
- }
67
62
  const memoryHeading = "\n\n## Injected MEMORY.md (bounded local context)\n";
68
- const memory = options.isolateLocalMemory ? "" : capMemoryForInject(workspace.memory, undefined, remaining - factsBytes - Buffer.byteLength(memoryHeading, "utf8"), options.memoryQuery);
63
+ const memory = options.isolateLocalMemory ? "" : capMemoryForInject(workspace.memory, undefined, remaining - factsBytes - Buffer.byteLength(memoryHeading, "utf8"));
69
64
  const memorySection = memory === "" ? "" : memoryHeading + memory;
70
65
  const workLogSection = workLog === "" ? "" : `\n\n## Injected work-log (bounded local context)\n${workLog}`;
71
66
  const workLogBudget = remaining - factsBytes - Buffer.byteLength(memorySection, "utf8");
@@ -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";
@@ -42,12 +42,10 @@ import { createLocalMemoryTelemetry, logLocalMemoryContextPrepareFailure, logLoc
42
42
  import { CodexStartupStageParser } from "./codex-startup-stage.js";
43
43
  import { formatRuntimeStartupError } from "./runtime-startup-error.js";
44
44
  import { logRuntimeEnvironmentContract } from "./runtime-environment-telemetry.js";
45
- import { commitLocalMemoryResultAfterExit, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, } from "./local-memory/runtime.js";
45
+ import { commitLocalMemoryResultAfterExit, initializeLocalMemoryV2ForWorkspace, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, } from "./local-memory/runtime.js";
46
46
  import { prepareLocalMemoryPrompt } from "./local-memory/prompt.js";
47
- import { reportLocalMemoryV2Deferred } from "./local-memory/rollout.js";
48
- import { prepareExecutionMemoryWorkspace } from "./local-memory/execution-workspace.js";
47
+ import { reportLocalMemoryV2Deferred, resolveLocalMemoryV2Rollout } from "./local-memory/rollout.js";
49
48
  import { localMemoryV2Environment } from "./config.js";
50
- import { detectLocalMemoryStorageVersion } from "./local-memory/storage-format.js";
51
49
  export class RuntimeRunningCallbackTimeoutError extends Error {
52
50
  constructor(runtime, timeoutMs) {
53
51
  super(`${runtime} runtime-running callback timed out after ${timeoutMs}ms`);
@@ -213,11 +211,37 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
213
211
  : "high"
214
212
  : runtime.reasoning;
215
213
  const providerFp = providerFingerprint(runtime.name, providerConfig);
216
- const preparedMemory = await prepareExecutionMemoryWorkspace(input, isolatedMaintenance, dependencies.cancellation);
217
- let workspace = preparedMemory.workspace;
218
- 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 };
219
243
  input = { ...input, localMemoryV2Enabled: memoryRollout.enabled };
220
- const localMemoryV2Initialized = preparedMemory.initialized;
244
+ const localMemoryV2Initialized = await initializeLocalMemoryV2ForWorkspace(workspace, memoryRollout.enabled);
221
245
  let materialized = null;
222
246
  let knownAttachmentDirectory = null;
223
247
  const startupReservationState = { current: null };
@@ -367,7 +391,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
367
391
  let baseWakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
368
392
  memoryPruneTraceId = isolatedMaintenance ? null : parseMemoryPruneTraceId(baseWakePrompt);
369
393
  if (memoryRollout.deferred)
370
- reportLocalMemoryV2Deferred(input.executionId, input.handle, memoryPruneTraceId !== null || input.maintenancePurpose === "memory_prune", memoryRollout.legacyFallback);
394
+ reportLocalMemoryV2Deferred(input.executionId, input.handle, memoryPruneTraceId !== null || input.maintenancePurpose === "memory_prune");
371
395
  if (memoryPruneTraceId !== null) {
372
396
  executionWorkspace = await resolveMemoryPruneWorkLog(executionWorkspace, input);
373
397
  promptContext = { ...promptContext, workspace: executionWorkspace };
@@ -387,11 +411,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
387
411
  agentDir: executionWorkspace.dir, executionId: input.executionId, agentHandle: input.handle,
388
412
  enabled: input.localMemoryV2Enabled === true, query: baseWakePrompt,
389
413
  resultPath: localMemoryResultPath, maxBytes: input.maxSystemPromptBytes,
390
- storageVersion: detectLocalMemoryStorageVersion(executionWorkspace.memory),
391
- strictRollout: !isolatedMaintenance && !memoryRollout.legacyFallback && (localMemoryV2Environment(input.launch.serverUrl) === "dev"
392
- || detectLocalMemoryStorageVersion(executionWorkspace.memory) === 2),
414
+ strictRollout: !isolatedMaintenance && localMemoryV2Environment(input.launch.serverUrl) === "dev",
393
415
  deferred: memoryRollout.deferred,
394
- ...(memoryRollout.legacyFallback ? { legacyFallback: true } : {}),
395
416
  renderBase: (reservedPromptBytes) => resolvePrompt(input.systemPrompt, { ...promptContext, reservedPromptBytes }),
396
417
  });
397
418
  localMemoryResultPath = memoryPrompt.resultPath;
@@ -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,
@@ -1,7 +1,7 @@
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";
4
+ import { LOCAL_MEMORY_V2_DEFERRED_GUIDANCE } from "./rollout.js";
5
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 = "";
@@ -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);
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
  // 强制基础指令与契约本身仍无法共存时,保留既有非结构化兼容回退,不裁基础指令。
@@ -6,9 +6,6 @@ import { pathToFileURL } from "node:url";
6
6
  import { parseArgs, TextDecoder } from "node:util";
7
7
  import { z } from "zod";
8
8
  import { readPendingMemoryBody, searchPendingMemoryPage } from "./pending-query.js";
9
- import { detectLocalMemoryStorageVersion } from "./storage-format.js";
10
- import { searchFullMemoryIndex } from "./full-index-search.js";
11
- import { withMemoryAccess } from "./migration-access.js";
12
9
  const OUTPUT_BYTES = 12 * 1024;
13
10
  const FILE_BYTES = 32 * 1024 * 1024;
14
11
  const READ_LIMIT_MAX = 4_000;
@@ -30,12 +27,6 @@ const Common = {
30
27
  ifHash: z.string().regex(/^[a-f0-9]{64}$/u).optional(),
31
28
  };
32
29
  const CommandSchema = z.discriminatedUnion("command", [
33
- z.object({
34
- command: z.literal("search-history"), ...Common,
35
- query: z.string().trim().min(1).max(256),
36
- limit: z.coerce.number().int().min(1).max(Number.MAX_SAFE_INTEGER).default(10),
37
- afterLine: z.coerce.number().int().min(0).max(Number.MAX_SAFE_INTEGER).default(0),
38
- }).strict(),
39
30
  z.object({
40
31
  command: z.literal("search"), ...Common,
41
32
  query: z.string().trim().min(1).max(256),
@@ -63,7 +54,6 @@ const CommandSchema = z.discriminatedUnion("command", [
63
54
  ]);
64
55
  const USAGE = `只读记忆工具,默认输出 JSON,不修改任何记忆文件。
65
56
  crew-memory search --query <关键词> [--file MEMORY.md|notes/文件.md] [--limit 10] [--after-line 0]
66
- crew-memory search-history --query <关键词> [--limit 10] [--after-line 0] [--if-hash <sha256>]
67
57
  crew-memory read [--file MEMORY.md|notes/文件.md] [--offset 0] [--limit 2000]
68
58
  crew-memory search-pending --query <关键词> [--cursor <游标>] [--limit 10]
69
59
  crew-memory read-pending --id <候选ID> [--offset 0] [--limit 2000] [--if-hash <sha256>]
@@ -72,8 +62,7 @@ search 关键词按空白分隔,同一行须全部命中;返回完整文件
72
62
  读取的 offset/limit 按 Unicode 字符计数;用 nextOffset 继续,eof=true 表示结束。
73
63
  读取的 --limit 超出范围时,工具会自动降到 2000 并在结果中标记 warning;保留原文件或候选 ID 及 offset,按 nextOffset 分页,不要一次读完整文件。
74
64
  后续页可传 --if-hash <前一页的 sha256>,文件变化时停止,避免混读不同版本。
75
- schema=2 的 search 查询完整有效目录(active/pinned),按标题、摘要、触发词联合匹配并返回 ID、revision 和正文引用;search-history 显式查询历史/失效记录。预览未展示不代表记录不存在。
76
- 旧格式 search 及 read 读取原文;read 按指定文件和哈希分页,不扫描 pending。
65
+ search/read 读取 MEMORY.md 和 notes 原文,不过滤失效段落,也不扫描 pending。
77
66
  search-pending 只查因容量延期的 create 候选;有 nextCursor 就继续,即使本页 matches 为空。
78
67
  complete=true 才表示全部候选已检索完成且无遗漏;warnings 或非零退出码表示读取未完成,不能当作零命中。
79
68
  read-pending 只返回候选正文;offset 非零时必须传 --if-hash,update/supersede 候选不可读取。
@@ -243,18 +232,6 @@ function writeResult(result, io) {
243
232
  io.out(output);
244
233
  }
245
234
  export async function runMemoryReadCommand(argv, io) {
246
- if (!io.env.CREW_HOME || argv.includes("--help") || argv.includes("-h"))
247
- return runMemoryReadUnlocked(argv, io);
248
- try {
249
- return await withMemoryAccess(io.env.CREW_HOME, () => runMemoryReadUnlocked(argv, io));
250
- }
251
- catch (error) {
252
- const reason = error instanceof Error && /^(?:local_)?memory_[a-z_]+$/u.test(error.message) ? error.message : "memory_migration_access_failed";
253
- io.err(JSON.stringify({ error: reason, message: "记忆读取受到迁移或状态保护,不能当作没有记忆。" }));
254
- return 1;
255
- }
256
- }
257
- async function runMemoryReadUnlocked(argv, io) {
258
235
  let command;
259
236
  try {
260
237
  const { values, positionals } = parseArgs({
@@ -312,18 +289,6 @@ async function runMemoryReadUnlocked(argv, io) {
312
289
  const source = await readSource(io.env.CREW_HOME, command.file);
313
290
  if (command.ifHash !== undefined && command.ifHash !== source.sha256)
314
291
  throw new Error("memory_file_changed");
315
- const fullIndex = command.file === "MEMORY.md" && detectLocalMemoryStorageVersion(source.text) === 2;
316
- if ((command.command === "search" || command.command === "search-history") && fullIndex) {
317
- const normalized = normalizeLimit(command.limit, SEARCH_LIMIT_MAX, SEARCH_LIMIT_SAFE);
318
- const result = searchFullMemoryIndex(source.text, source.sha256, {
319
- ...command, limit: normalized.limit, history: command.command === "search-history",
320
- }, OUTPUT_BYTES - 256);
321
- writeResult({ ...result, ...(normalized.warning ? { warning: normalized.warning,
322
- requestedLimit: normalized.warning.requestedLimit, limit: normalized.warning.appliedLimit } : {}) }, io);
323
- return 0;
324
- }
325
- if (command.command === "search-history")
326
- throw new Error("memory_history_requires_full_index");
327
292
  const result = command.command === "search"
328
293
  ? (() => {
329
294
  const normalized = normalizeLimit(command.limit, SEARCH_LIMIT_MAX, SEARCH_LIMIT_SAFE);
@@ -7,57 +7,30 @@ import { inventoryLimits, readMemoryInventorySource } from "./inventory-source.j
7
7
  import { previewMemoryInventory } from "./inventory-preview.js";
8
8
  import { parseLocalMemoryIndex } from "./write.js";
9
9
  import { dslog } from "../slog.js";
10
- import { createHash } from "node:crypto";
11
- import { detectLocalMemoryStorageVersion, parseFullMemoryIndex } from "./storage-format.js";
12
- import { MigrationStateSchema } from "./migration-schema.js";
13
10
  export const LOCAL_MEMORY_V2_DEFERRED_GUIDANCE = `
14
11
 
15
12
  ## 本轮本地记忆兼容状态
16
13
  开发环境已开放 V2,但该 Agent 的旧记忆尚未通过格式兼容检查,本轮未启用结构化 V2。
17
14
  本节覆盖前文和旧会话中直接改写共享记忆的要求:本轮 MEMORY.md 与 notes 只读,不得改写、迁移或清空。
18
15
  用户要求新增或更新长期规则时,内容和授权均只记录到本任务的 $CREW_TASK_LOG;保留日志,不要清空,不要宣称长期记忆已生效。旧资料兼容处理完成前,记忆收尾会明确报告需要处理。`;
19
- export const LOCAL_MEMORY_LEGACY_GUIDANCE = `
20
-
21
- ## 本轮本地记忆兼容状态
22
- 本 Agent 尚未完成 V2 格式转换,本轮继续原记忆流程。用户明确要求新增或修改长期规则时,可按原规则最小范围更新共享记忆;记忆收尾继续依据任务日志更新原 MEMORY.md 和 notes,确认成功后再清理日志。
23
- 本节覆盖旧会话中因等待 V2 转换而冻结共享记忆写入的要求,但不是新的用户写入授权;本任务如明确要求不更新记忆,继续遵守。
24
- 保留原身份、目标及无关资料,不自行迁移或重置文件,不宣称已启用 V2。`;
25
- export function reportLocalMemoryV2Deferred(executionId, handle, memoryPrune, legacyFallback = false) {
26
- dslog("local_memory.v2_deferred", legacyFallback ? "旧格式继续原记忆流程,转换成功后再启用V2" : "记忆格式异常,保留原资料和待处理日志", {
27
- execution_id: executionId, agent_handle: handle, reason: legacyFallback ? "legacy_compatible" : "requires_migration"
16
+ export function reportLocalMemoryV2Deferred(executionId, handle, memoryPrune) {
17
+ dslog("local_memory.v2_deferred", "旧记忆尚未通过兼容检查,保留原资料和待处理日志", {
18
+ execution_id: executionId, agent_handle: handle, reason: "requires_migration"
28
19
  });
29
- if (memoryPrune && !legacyFallback)
20
+ if (memoryPrune)
30
21
  throw new Error("local_memory_v2_requires_migration");
31
22
  }
32
23
  /** 只核对小型索引和其引用;不扫描全部历史任务、不迁移或生成新存储。 */
33
24
  export async function resolveLocalMemoryV2Rollout(input) {
34
25
  const environment = localMemoryV2Environment(input.serverUrl);
35
- if (input.selfTraining)
36
- return { enabled: false, deferred: false };
37
- // 已提交的目标格式独立于新的迁移资格;关闭名单不能退回不兼容writer。
38
- if (input.workspace.memory.includes("NOWWORK:MEMORY_INDEX:BEGIN schema=2")) {
39
- try {
40
- if (detectLocalMemoryStorageVersion(input.workspace.memory) !== 2)
41
- throw new Error("invalid_storage");
42
- parseFullMemoryIndex(input.workspace.memory);
43
- const root = await realpath(input.workspace.dir);
44
- const record = await readMemoryInventorySource(root, ".crew/memory/migration.v1.json", inventoryLimits.fileBytes);
45
- const state = MigrationStateSchema.parse(JSON.parse(record.content));
46
- if (state.phase !== "committed" || state.storageVersion !== 2 || state.identity.agentId !== input.handle
47
- || state.identity.rootFingerprint !== createHash("sha256").update(root).digest("hex"))
48
- throw new Error("invalid_state");
49
- return { enabled: true, deferred: false };
50
- }
51
- catch {
52
- return { enabled: false, deferred: true };
53
- }
54
- }
55
- if (environment === "production")
26
+ if (input.selfTraining || environment === "production")
56
27
  return { enabled: false, deferred: false };
57
28
  if (environment === "custom")
58
29
  return { enabled: input.requested, deferred: false };
59
30
  const deferred = { enabled: false, deferred: true };
60
31
  const { workspace } = input;
32
+ if (Array.from(workspace.memory).length > LOCAL_MEMORY_TOTAL_CODE_POINT_CAP)
33
+ return deferred;
61
34
  try {
62
35
  const root = await realpath(workspace.dir);
63
36
  const notesStat = await lstat(join(root, "notes"));
@@ -69,26 +42,8 @@ export async function resolveLocalMemoryV2Rollout(input) {
69
42
  // 完整种子且无旧明细才可按空记忆接入;marker 不能替代这项核对。
70
43
  const seededRole = /^## Role\n([\s\S]*?)\n\n## Core Goals\n/mu.exec(memory.content)?.[1];
71
44
  if (seededRole !== undefined && memory.content === memorySeed(input.handle, seededRole)) {
72
- if (Array.from(memory.content).length <= LOCAL_MEMORY_TOTAL_CODE_POINT_CAP
73
- && (await readdir(join(root, "notes"))).length === 0)
74
- return { enabled: true, deferred: false };
45
+ return (await readdir(join(root, "notes"))).length === 0 ? { enabled: true, deferred: false } : deferred;
75
46
  }
76
- if (!/NOWWORK:MEMORY_INDEX:/u.test(memory.content)) {
77
- // 普通旧格式不是损坏V2。引用必须仍为根内可读文件,格式/容量例外保留原流程。
78
- const entries = parseLocalMemoryIndex(memory.content);
79
- // 已有受控ID或锚点的记录,不能因HTML标记丢失就退回旧writer。
80
- if (entries.some(entry => !entry.id.startsWith("legacy:") || entry.anchor !== null))
81
- return deferred;
82
- let remaining = inventoryLimits.totalBytes - memory.bytes;
83
- for (const file of new Set(entries.map(entry => entry.file))) {
84
- if (!file.startsWith("notes/"))
85
- return deferred;
86
- remaining -= (await readMemoryInventorySource(root, file, Math.min(remaining, inventoryLimits.fileBytes))).bytes;
87
- }
88
- return { enabled: false, deferred: true, legacyFallback: true };
89
- }
90
- if (Array.from(workspace.memory).length > LOCAL_MEMORY_TOTAL_CODE_POINT_CAP)
91
- return deferred;
92
47
  const generated = /<!-- NOWWORK:MEMORY_INDEX:BEGIN schema=1 -->[\s\S]*?<!-- NOWWORK:MEMORY_INDEX:END -->/u.exec(memory.content)?.[0];
93
48
  if (!generated || Array.from(generated).length > LOCAL_MEMORY_INDEX_CODE_POINT_CAP)
94
49
  return deferred;
@@ -48,10 +48,7 @@ export function localMemoryResultPath(runDir, executionId) {
48
48
  const suffix = createHash("sha256").update(executionId, "utf8").digest("hex").slice(0, 32);
49
49
  return join(runDir, `.memory-result.v1-${suffix}.json`);
50
50
  }
51
- export function renderLocalMemoryResultInstructions(resultPath, storageVersion = 1) {
52
- const storagePolicy = storageVersion === 2
53
- ? "完整目录保存在 MEMORY.md,模型只看到有界预览;未展示记录仍然有效。涉及旧规则时先用 crew-memory search 查询完整目录,按当前 ID/revision 更新,不因预览未命中就重复新建。20条预览不是有效记录上限;完整存储超出资源上限会拒绝提交,不得自行删除或降级旧记录。已有 pending 仍是未应用候选,须重新核对授权、重复与版本后通过 changes 申请保存,不因迁移自动生效。"
54
- : "daemon 会校验、去重、限制 MEMORY.md 大小,超出索引容量的候选会保存在 pending 归档中。容量暂存的 create 可按需检索参考但不能覆盖现行规则;有空间的后续 proposed 提交可提升。未应用的 update/supersede 不进入普通召回,必须重新核对目标版本后处理。";
51
+ export function renderLocalMemoryResultInstructions(resultPath) {
55
52
  return `
56
53
 
57
54
  ## Structured memory prune (daemon-managed)
@@ -73,7 +70,7 @@ outcome 只允许 no_change、proposed、conflict、failed。新增、更新、
73
70
  \`\`\`
74
71
 
75
72
  每个 changes 项须保留示例中的全部字段。operation=create|update|supersede;kind=procedural|semantic|episodic|preference;priority=high|medium|low;detailCategory=preferences|project-rules|testing|debugging|git|incidents|lessons。create 的 memoryId/baseRevision/supersedesMemoryId 必须为 null;update 按现有索引填写 memoryId/baseRevision,supersedesMemoryId 为 null;supersede 填 supersedesMemoryId/baseRevision,memoryId 为 null。
76
- 只提交可复用、与本次任务明确相关的少量候选;不要写入凭证、token、隐私或内部控制标记。${storagePolicy}
73
+ 只提交可复用、与本次任务明确相关的少量候选;不要写入凭证、token、隐私或内部控制标记。daemon 会校验、去重、限制 MEMORY.md 大小,超出索引容量的候选会保存在 pending 归档中。容量暂存的 create 可按需检索参考但不能覆盖现行规则;有空间的后续 proposed 提交可提升。未应用的 update/supersede 不进入普通召回,必须重新核对目标版本后处理。
77
74
  写入候选文件只表示提交候选,不能提前宣称已经记住或规则已经更新;daemon 在本轮结束后才处理,候选持久化与进入有效索引也不是同一结果。本结构化轮次覆盖前文提前清理工作日志的要求:保留 $CREW_TASK_LOG,不要清空或删除;daemon 将在处理成功后清理,失败时保留日志和候选用于排查或重试。
78
75
  `;
79
76
  }