@nowcrew/daemon 0.6.101-beta.6 → 0.6.101-beta.8

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 (57) hide show
  1. package/dist/cloud-report-upload.js +10 -1
  2. package/dist/execution-journal.js +1 -3
  3. package/dist/execution-protocol.js +1 -1
  4. package/dist/execution-runner.js +1 -4
  5. package/dist/local-execution-prompt.js +3 -8
  6. package/dist/local-executor.js +55 -78
  7. package/dist/local-memory/index-budget.js +3 -9
  8. package/dist/local-memory/inventory-cli.js +8 -52
  9. package/dist/local-memory/inventory-source.js +8 -15
  10. package/dist/local-memory/pending-query.js +2 -5
  11. package/dist/local-memory/pending-schema.js +1 -1
  12. package/dist/local-memory/pending.js +2 -16
  13. package/dist/local-memory/prompt.js +5 -21
  14. package/dist/local-memory/read-cli.js +76 -98
  15. package/dist/local-memory/rollout.js +11 -79
  16. package/dist/local-memory/runtime.js +17 -191
  17. package/dist/local-memory/write.js +21 -190
  18. package/dist/local-memory-diagnostics.js +15 -96
  19. package/dist/local-memory-telemetry.js +3 -31
  20. package/dist/machine-info.js +0 -3
  21. package/dist/marketplace-protocol.js +1 -1
  22. package/dist/marketplace-remote.js +1 -1
  23. package/dist/marketplace-runner.js +1 -1
  24. package/dist/memory-prune-diagnostics.js +4 -74
  25. package/dist/message-file-preview.js +1 -11
  26. package/dist/prompt.js +17 -9
  27. package/dist/runner.js +1 -1
  28. package/dist/runtimes/claude-thread-runner.js +4 -25
  29. package/dist/serve.js +3 -38
  30. package/dist/token.js +0 -1
  31. package/dist/workspace-control-handler.js +3 -16
  32. package/dist/workspace.js +4 -23
  33. package/package.json +1 -1
  34. package/dist/local-memory/commit-telemetry.js +0 -30
  35. package/dist/local-memory/execution-workspace.js +0 -117
  36. package/dist/local-memory/full-index-search.js +0 -47
  37. package/dist/local-memory/initialization-identity.js +0 -36
  38. package/dist/local-memory/inventory-v2.js +0 -362
  39. package/dist/local-memory/legacy-adapter.js +0 -233
  40. package/dist/local-memory/migration-access.js +0 -242
  41. package/dist/local-memory/migration-cli.js +0 -54
  42. package/dist/local-memory/migration-io.js +0 -199
  43. package/dist/local-memory/migration-pending.js +0 -9
  44. package/dist/local-memory/migration-plan.js +0 -432
  45. package/dist/local-memory/migration-schema.js +0 -58
  46. package/dist/local-memory/migration-state-invariants.js +0 -65
  47. package/dist/local-memory/migration-store.js +0 -414
  48. package/dist/local-memory/migration-tool-entry.js +0 -21
  49. package/dist/local-memory/release-artifact-guard.js +0 -55
  50. package/dist/local-memory/source-path.js +0 -13
  51. package/dist/local-memory/storage-format.js +0 -249
  52. package/dist/local-memory/writer-mode.js +0 -13
  53. package/dist/memory-migration-controller.js +0 -89
  54. package/dist/memory-migration-inspect.js +0 -116
  55. package/dist/memory-migration-journal.js +0 -14
  56. package/dist/memory-migration-protocol.js +0 -14
  57. package/dist/memory-migration-runtime.js +0 -367
@@ -6,8 +6,6 @@ import { dslog } from "../slog.js";
6
6
  import { LocalMemoryChangeSchema, LocalMemorySingleLineText, validateLocalMemoryChange, } from "./candidate.js";
7
7
  import { LOCAL_MEMORY_INDEX_CODE_POINT_CAP, LOCAL_MEMORY_INDEX_ENTRY_CAP, LOCAL_MEMORY_TOTAL_CODE_POINT_CAP, renderGeneratedIndex, selectLocalMemoryIndexEntries, } from "./index-budget.js";
8
8
  import { persistPendingMemoryCandidate, readPendingMemoryCandidates, removePendingMemoryCandidate, } from "./pending.js";
9
- import { assertLocalMemoryStorageSize, detectLocalMemoryStorageVersion, LOCAL_MEMORY_GENERATED_INDEX_PATTERN, LOCAL_MEMORY_NOTE_BYTE_CAP, LOCAL_MEMORY_READ_BYTE_CAP, parseFullMemoryIndex, replaceFullMemoryIndex, selectLocalMemoryPreview, } from "./storage-format.js";
10
- import { assertMemoryInventoryHealth, assertMemoryWriterAllowed, withMemoryAccess } from "./migration-access.js";
11
9
  const StatusChangeSchema = z.object({
12
10
  memoryId: LocalMemorySingleLineText,
13
11
  expectedRevision: z.number().int().positive(),
@@ -22,7 +20,6 @@ const ResultSchema = z.object({
22
20
  outcome: z.enum(["no_change", "proposed", "conflict", "failed"]),
23
21
  changes: z.array(LocalMemoryChangeSchema).max(50),
24
22
  statusChanges: z.array(StatusChangeSchema).max(50),
25
- workLogDisposition: z.enum(["clear", "preserve"]).optional(),
26
23
  }).strict();
27
24
  const GENERATED_INDEX_PATTERN = /<!--\s*NOWWORK:MEMORY_INDEX:BEGIN\s+schema=1\s*-->[\s\S]*?<!--\s*NOWWORK:MEMORY_INDEX:END\s*-->/u;
28
25
  const INDEX_HEADING = /^##\s+Knowledge Index\s*$/mu;
@@ -93,14 +90,12 @@ function parseCards(section) {
93
90
  });
94
91
  }
95
92
  export function parseLocalMemoryIndex(memory) {
96
- if (detectLocalMemoryStorageVersion(memory) === 2)
97
- return parseFullMemoryIndex(memory);
98
93
  const section = indexSection(memory);
99
94
  const generated = GENERATED_INDEX_PATTERN.exec(section)?.[0];
100
95
  return parseCards(generated ?? section);
101
96
  }
102
97
  function parseUnmarkedMemoryIndex(memory) {
103
- const section = indexSection(memory).replace(LOCAL_MEMORY_GENERATED_INDEX_PATTERN, "");
98
+ const section = indexSection(memory).replace(GENERATED_INDEX_PATTERN, "");
104
99
  return parseCards(section);
105
100
  }
106
101
  function replaceGeneratedIndex(memory, entries) {
@@ -127,36 +122,11 @@ function noteSection(id, revision, title, body) {
127
122
  }
128
123
  function notePattern(id) {
129
124
  const escaped = id.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
130
- return new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:BEGIN\\s+id=${escaped}(?=\\s|-->)[^>]*-->[\\s\\S]*?<!--\\s*NOWWORK:MEMORY_ENTRY:END\\s+id=${escaped}\\s*-->\\s*`, "u");
131
- }
132
- function uniqueNoteSection(content, id) {
133
- const escaped = id.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
134
- const begins = [...content.matchAll(new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:BEGIN\\s+id=${escaped}(?=\\s|-->)`, "gu"))];
135
- const ends = [...content.matchAll(new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:END\\s+id=${escaped}\\s*-->`, "gu"))];
136
- if (begins.length === 0 && ends.length === 0)
137
- return null;
138
- const section = notePattern(id).exec(content)?.[0];
139
- if (begins.length !== 1 || ends.length !== 1 || section === undefined
140
- || (section.match(/NOWWORK:MEMORY_ENTRY:(?:BEGIN|END)/gu) ?? []).length !== 2) {
141
- throw new Error("local_memory_note_anchor_invalid");
142
- }
143
- return section;
125
+ return new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:BEGIN\\s+id=${escaped}[^>]*-->[\\s\\S]*?<!--\\s*NOWWORK:MEMORY_ENTRY:END\\s+id=${escaped}\\s*-->\\s*`, "u");
144
126
  }
145
127
  function replaceNote(content, id, replacement) {
146
128
  const pattern = notePattern(id);
147
- return uniqueNoteSection(content, id) !== null ? content.replace(pattern, () => replacement) : null;
148
- }
149
- function updateNoteRevision(content, id, revision) {
150
- const section = uniqueNoteSection(content, id);
151
- const beginning = `<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${id} `;
152
- const ending = `<!-- NOWWORK:MEMORY_ENTRY:END id=${id} -->`;
153
- if (section === null || content.split(beginning).length !== 2 || content.split(ending).length !== 2) {
154
- throw new Error("local_memory_note_anchor_missing");
155
- }
156
- const updated = section.replace(/^(<!--\s*NOWWORK:MEMORY_ENTRY:BEGIN\s+id=[A-Za-z0-9_-]+\s+revision=)[1-9][0-9]*(\s*-->)/u, (_match, prefix, suffix) => `${prefix}${revision}${suffix}`);
157
- if (!updated.startsWith(`${beginning}revision=${revision} -->`))
158
- throw new Error("local_memory_note_anchor_missing");
159
- return content.replace(notePattern(id), () => updated);
129
+ return pattern.test(content) ? content.replace(pattern, replacement) : null;
160
130
  }
161
131
  function validateCandidate(result) {
162
132
  if (result.outcome === "no_change" && (result.changes.length > 0 || result.statusChanges.length > 0)) {
@@ -210,25 +180,6 @@ function stableId(executionId, index, change) {
210
180
  const input = [executionId, String(index), change.operation, change.title, change.summary, change.detailBody].join("\0");
211
181
  return `mem_${createHash("sha256").update(input, "utf8").digest("hex").slice(0, 12)}`;
212
182
  }
213
- function migrationPendingId(change) {
214
- // Keep the digest independent of object insertion order for callers that
215
- // reach applyLocalMemoryResult directly instead of the JSON/Zod boundary.
216
- const canonicalChange = {
217
- operation: change.operation,
218
- memoryId: change.memoryId,
219
- baseRevision: change.baseRevision,
220
- supersedesMemoryId: change.supersedesMemoryId,
221
- title: change.title,
222
- kind: change.kind,
223
- summary: change.summary,
224
- triggers: change.triggers,
225
- priority: change.priority,
226
- detailCategory: change.detailCategory,
227
- detailBody: change.detailBody,
228
- reviewAfter: change.reviewAfter,
229
- };
230
- return `pending-migration-${createHash("sha256").update(JSON.stringify(canonicalChange), "utf8").digest("hex").slice(0, 12)}`;
231
- }
232
183
  function normalize(value) {
233
184
  return value.normalize("NFKC").toLocaleLowerCase().replace(/\s+/gu, " ").trim();
234
185
  }
@@ -248,14 +199,14 @@ function detailCategoryFromFile(file) {
248
199
  return DETAIL_CATEGORIES.has(category) ? category : null;
249
200
  }
250
201
  function readNoteBody(content, id) {
251
- const section = uniqueNoteSection(content, id);
252
- if (section === null)
202
+ const begin = content.indexOf(`<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${id}`);
203
+ if (begin < 0)
253
204
  return null;
254
- const bodyStart = section.indexOf("\n\n");
255
- const bodyEnd = section.indexOf(`\n<!-- NOWWORK:MEMORY_ENTRY:END id=${id}`, bodyStart + 2);
205
+ const bodyStart = content.indexOf("\n\n", begin);
206
+ const bodyEnd = content.indexOf(`\n<!-- NOWWORK:MEMORY_ENTRY:END id=${id}`, bodyStart + 2);
256
207
  if (bodyStart < 0 || bodyEnd < 0)
257
208
  return null;
258
- return section.slice(bodyStart + 2, bodyEnd).trim();
209
+ return content.slice(bodyStart + 2, bodyEnd).trim();
259
210
  }
260
211
  function archivedChange(entry, detailBody) {
261
212
  const detailCategory = detailCategoryFromFile(entry.file);
@@ -279,34 +230,6 @@ function archivedChange(entry, detailBody) {
279
230
  function emptyResult() {
280
231
  return { createdIds: [], updatedIds: [], statusChangedIds: [], deferredIds: [], pendingIds: [] };
281
232
  }
282
- async function stageMigrationDeferredPending(input) {
283
- if (input.result.outcome !== "proposed")
284
- return emptyResult();
285
- if (input.result.statusChanges.length > 0 || input.result.changes.some((change) => change.operation !== "create")) {
286
- throw new Error("local_memory_migration_pending_create_only");
287
- }
288
- const executionId = input.executionId ?? "unknown";
289
- const pending = input.result.changes.map((change) => ({
290
- schemaVersion: 1,
291
- pendingId: migrationPendingId(change),
292
- createdAt: new Date().toISOString(),
293
- reason: "legacy_requires_migration",
294
- source: { executionId, pruneTraceId: input.pruneTraceId ?? null },
295
- candidate: change,
296
- }));
297
- await assertMemoryAccessDirectory(input.agentDir);
298
- await withMemoryAccess(input.agentDir, async () => {
299
- const memory = await readFile(join(input.agentDir, "MEMORY.md"), "utf8").catch(() => null);
300
- if (memory === null || detectLocalMemoryStorageVersion(memory) !== 1) {
301
- throw new Error("local_memory_migration_pending_storage_invalid");
302
- }
303
- for (const candidate of pending) {
304
- await persistPendingMemoryCandidate({ agentDir: input.agentDir, candidate });
305
- }
306
- }, { pendingOnly: true });
307
- const pendingIds = [...new Set(pending.map((candidate) => candidate.pendingId))];
308
- return { createdIds: [], updatedIds: [], statusChangedIds: [], deferredIds: pendingIds, pendingIds };
309
- }
310
233
  function isWithin(root, candidate) {
311
234
  const path = relative(root, candidate);
312
235
  return path === "" || (path !== ".." && !path.startsWith(`..${sep}`));
@@ -321,29 +244,12 @@ async function assertWritablePaths(agentDir, memoryPath) {
321
244
  if (!memoryStat.isFile() || memoryStat.isSymbolicLink())
322
245
  throw new Error("local_memory_memory_path_invalid");
323
246
  }
324
- async function assertMemoryAccessDirectory(agentDir) {
325
- try {
326
- const stat = await lstat(join(agentDir, ".crew/memory"));
327
- if (!stat.isDirectory() || stat.isSymbolicLink())
328
- throw new Error("local_memory_pending_path_invalid");
329
- }
330
- catch (error) {
331
- if (error.code === "ENOENT")
332
- return;
333
- throw error;
334
- }
335
- }
336
247
  async function readNote(path) {
337
248
  try {
338
249
  const stat = await lstat(path);
339
250
  if (!stat.isFile() || stat.isSymbolicLink())
340
251
  throw new Error("local_memory_note_path_invalid");
341
- if (stat.size > LOCAL_MEMORY_NOTE_BYTE_CAP)
342
- throw new Error("local_memory_note_byte_limit");
343
- const content = await readFile(path, "utf8");
344
- if (Buffer.byteLength(content, "utf8") > LOCAL_MEMORY_NOTE_BYTE_CAP)
345
- throw new Error("local_memory_note_byte_limit");
346
- return content;
252
+ return await readFile(path, "utf8");
347
253
  }
348
254
  catch (error) {
349
255
  if (error.code === "ENOENT")
@@ -363,11 +269,6 @@ async function atomicWrite(path, content) {
363
269
  }
364
270
  }
365
271
  function projectIndex(memory, entries, protectedIds) {
366
- if (detectLocalMemoryStorageVersion(memory) === 2) {
367
- const updated = replaceFullMemoryIndex(memory, entries);
368
- selectLocalMemoryPreview(updated);
369
- return { memory: updated, omittedIds: [], reason: null };
370
- }
371
272
  const empty = replaceGeneratedIndex(memory, []);
372
273
  if (codePointLength(empty) > LOCAL_MEMORY_TOTAL_CODE_POINT_CAP)
373
274
  throw new Error("local_memory_memory_over_budget");
@@ -405,29 +306,12 @@ async function withApplyLease(agentDir, operation) {
405
306
  }
406
307
  export async function applyLocalMemoryResult(input) {
407
308
  validateCandidate(input.result);
408
- if (input.migrationDeferredPending)
409
- return stageMigrationDeferredPending(input);
410
- if (input.result.outcome !== "proposed" && input.requiredStorageVersion !== 2)
309
+ if (input.result.outcome !== "proposed")
411
310
  return emptyResult();
412
311
  const memoryPath = join(input.agentDir, "MEMORY.md");
413
- if (input.result.outcome === "proposed")
414
- await assertWritablePaths(input.agentDir, memoryPath);
415
- await assertMemoryAccessDirectory(input.agentDir);
416
- return withApplyLease(input.agentDir, async () => withMemoryAccess(input.agentDir, async () => {
417
- await assertMemoryWriterAllowed(input.agentDir, input.writerCapabilities ?? []);
418
- const memoryStat = await lstat(memoryPath);
419
- if (memoryStat.size > 16 * 1024 * 1024)
420
- throw new Error("local_memory_storage_byte_limit");
312
+ await assertWritablePaths(input.agentDir, memoryPath);
313
+ return withApplyLease(input.agentDir, async () => {
421
314
  const memory = await readFile(memoryPath, "utf8");
422
- assertLocalMemoryStorageSize(memory);
423
- const fullStorage = detectLocalMemoryStorageVersion(memory) === 2;
424
- if (input.requiredStorageVersion === 2 && !fullStorage) {
425
- throw new Error("local_memory_v2_state_invalid_before_commit");
426
- }
427
- // A V2 no-op is still a commit decision. Return only after the access
428
- // lock and committed-state writer gate have validated the same snapshot.
429
- if (input.result.outcome !== "proposed")
430
- return emptyResult();
431
315
  if (parseUnmarkedMemoryIndex(memory).length > 0)
432
316
  throw new Error("local_memory_legacy_index_requires_migration");
433
317
  let entries = [...parseLocalMemoryIndex(memory)];
@@ -458,26 +342,6 @@ export async function applyLocalMemoryResult(input) {
458
342
  const pendingCandidates = [];
459
343
  const resolvedPendingIds = new Set();
460
344
  const noteUpdates = new Map();
461
- const noteCache = new Map();
462
- let totalReadBytes = Buffer.byteLength(memory, "utf8");
463
- const readStoredNote = async (path) => {
464
- const cached = noteUpdates.get(path) ?? noteCache.get(path);
465
- if (cached !== undefined)
466
- return cached;
467
- const stat = await lstat(path).catch((error) => {
468
- if (error.code === "ENOENT")
469
- return null;
470
- throw error;
471
- });
472
- if (stat !== null && totalReadBytes + stat.size > LOCAL_MEMORY_READ_BYTE_CAP)
473
- throw new Error("local_memory_read_byte_limit");
474
- const content = await readNote(path);
475
- totalReadBytes += Buffer.byteLength(content, "utf8");
476
- if (totalReadBytes > LOCAL_MEMORY_READ_BYTE_CAP)
477
- throw new Error("local_memory_read_byte_limit");
478
- noteCache.set(path, content);
479
- return content;
480
- };
481
345
  let allowActiveEviction = false;
482
346
  const randomId = input.randomId ?? randomUUID;
483
347
  const executionId = input.executionId ?? "unknown";
@@ -519,21 +383,10 @@ export async function applyLocalMemoryResult(input) {
519
383
  if (existingCreate !== undefined && !matchesCreateCandidate(existingCreate, change)) {
520
384
  throw new Error("local_memory_duplicate_candidate");
521
385
  }
522
- if (existingCreate === undefined && entries.some((entry) => (!fullStorage || entry.status === "active" || entry.status === "pinned")
523
- && (normalize(entry.title) === normalize(change.title) || normalize(entry.summary) === normalize(change.summary)))) {
386
+ if (existingCreate === undefined && entries.some((entry) => normalize(entry.title) === normalize(change.title)
387
+ || normalize(entry.summary) === normalize(change.summary))) {
524
388
  throw new Error("local_memory_duplicate_candidate");
525
389
  }
526
- if (existingCreate !== undefined) {
527
- if (existingCreate.revision !== 1 || existingCreate.status !== "active") {
528
- throw new Error("local_memory_create_replay_conflict");
529
- }
530
- const stored = await readStoredNote(resolve(input.agentDir, existingCreate.file));
531
- if (uniqueNoteSection(stored, id)?.trimEnd() !== noteSection(id, 1, change.title, change.detailBody).trimEnd()) {
532
- throw new Error("local_memory_create_replay_conflict");
533
- }
534
- // A proven duplicate is a no-op; it must never rewrite a newer body or status.
535
- continue;
536
- }
537
390
  }
538
391
  const revision = existingCreate?.revision ?? (current === undefined ? 1 : current.revision + 1);
539
392
  const entry = existingCreate ?? entryFromChange(change, id, revision, current?.status ?? "active");
@@ -547,7 +400,7 @@ export async function applyLocalMemoryResult(input) {
547
400
  : item),
548
401
  entry,
549
402
  ];
550
- const allowEvictionForChange = !fullStorage && change.operation === "create" && change.priority === "high";
403
+ const allowEvictionForChange = change.operation === "create" && change.priority === "high";
551
404
  const protectedIds = new Set(allowEvictionForChange ? pinnedIds(entries) : activeOrPinnedIds(entries));
552
405
  if (allowEvictionForChange)
553
406
  allowActiveEviction = true;
@@ -564,7 +417,7 @@ export async function applyLocalMemoryResult(input) {
564
417
  const notePath = resolve(input.agentDir, entry.file);
565
418
  if (!isWithin(resolve(input.agentDir, "notes"), notePath))
566
419
  throw new Error("local_memory_note_path_invalid");
567
- const previous = await readStoredNote(notePath);
420
+ const previous = noteUpdates.get(notePath) ?? await readNote(notePath);
568
421
  const replacement = noteSection(id, revision, change.title, change.detailBody);
569
422
  const existingSection = replaceNote(previous, id, replacement);
570
423
  const next = existingSection ?? (current === undefined
@@ -582,7 +435,7 @@ export async function applyLocalMemoryResult(input) {
582
435
  // Pending create candidates are durable overflow, not dead ends. Promote only
583
436
  // when the current catalog has room; supersede/update candidates stay pending
584
437
  // until a fresh revision-aware decision can safely replay them.
585
- const pendingCandidatesOnDisk = fullStorage ? [] : await readPendingMemoryCandidates({ agentDir: input.agentDir }).catch((error) => {
438
+ const pendingCandidatesOnDisk = await readPendingMemoryCandidates({ agentDir: input.agentDir }).catch((error) => {
586
439
  if (!(error instanceof Error) || error.message !== "memory_pending_directory_over_budget")
587
440
  throw error;
588
441
  // 自动提升是可选工作,目录查询预算不应阻断本轮已验证的更新或腾位操作。
@@ -593,10 +446,6 @@ export async function applyLocalMemoryResult(input) {
593
446
  return [];
594
447
  });
595
448
  for (const pending of pendingCandidatesOnDisk) {
596
- // Migration-deferred candidates require reviewed import; only capacity
597
- // overflow is eligible for the existing automatic promotion path.
598
- if (pending.reason !== "index_over_budget")
599
- continue;
600
449
  const change = pending.candidate;
601
450
  if (change.operation !== "create")
602
451
  continue;
@@ -611,7 +460,7 @@ export async function applyLocalMemoryResult(input) {
611
460
  || normalize(entry.summary) === normalize(change.summary)));
612
461
  if (duplicate !== undefined) {
613
462
  const duplicatePath = resolve(input.agentDir, duplicate.file);
614
- const duplicateBody = readNoteBody(await readStoredNote(duplicatePath), duplicate.id);
463
+ const duplicateBody = readNoteBody(noteUpdates.get(duplicatePath) ?? await readNote(duplicatePath), duplicate.id);
615
464
  if (duplicateBody === change.detailBody
616
465
  || duplicateBody === unwrappedNoteBody(change.title, change.detailBody).trim()) {
617
466
  resolvedPendingIds.add(pending.pendingId);
@@ -629,7 +478,7 @@ export async function applyLocalMemoryResult(input) {
629
478
  const notePath = resolve(input.agentDir, entry.file);
630
479
  if (!isWithin(resolve(input.agentDir, "notes"), notePath))
631
480
  throw new Error("local_memory_note_path_invalid");
632
- const previous = await readStoredNote(notePath);
481
+ const previous = noteUpdates.get(notePath) ?? await readNote(notePath);
633
482
  const replacement = noteSection(id, 1, change.title, change.detailBody);
634
483
  const existingSection = replaceNote(previous, id, replacement);
635
484
  const next = existingSection ?? `${previous.trimEnd()}${previous.trimEnd() ? "\n\n" : ""}${replacement}`;
@@ -638,17 +487,6 @@ export async function applyLocalMemoryResult(input) {
638
487
  createdIds.push(id);
639
488
  resolvedPendingIds.add(pending.pendingId);
640
489
  }
641
- if (fullStorage) {
642
- const changedMetadataIds = new Set([
643
- ...statusChangedIds,
644
- ...input.result.changes.flatMap(change => change.supersedesMemoryId === null ? [] : [change.supersedesMemoryId]),
645
- ]);
646
- for (const id of changedMetadataIds) {
647
- const entry = entries.find(item => item.id === id);
648
- const notePath = resolve(input.agentDir, entry.file);
649
- noteUpdates.set(notePath, updateNoteRevision(await readStoredNote(notePath), id, entry.revision));
650
- }
651
- }
652
490
  let finalProjection = projectIndex(memory, entries, allowActiveEviction ? pinnedIds(entries) : activeOrPinnedIds(entries));
653
491
  if (finalProjection.reason !== null
654
492
  && (!allowActiveEviction || finalProjection.reason === "index_over_budget_pinned")) {
@@ -660,7 +498,7 @@ export async function applyLocalMemoryResult(input) {
660
498
  .filter((entry) => entry?.status === "active");
661
499
  for (const entry of omittedActiveEntries) {
662
500
  const notePath = resolve(input.agentDir, entry.file);
663
- const content = await readStoredNote(notePath);
501
+ const content = noteUpdates.get(notePath) ?? await readNote(notePath);
664
502
  const detailBody = readNoteBody(content, entry.id);
665
503
  const change = detailBody === null ? null : archivedChange(entry, detailBody);
666
504
  if (change === null)
@@ -681,13 +519,6 @@ export async function applyLocalMemoryResult(input) {
681
519
  if (finalProjection.reason !== null)
682
520
  throw new Error(finalProjection.reason);
683
521
  }
684
- // Complete every resource check before the first persistent write.
685
- assertLocalMemoryStorageSize(finalProjection.memory);
686
- for (const content of noteUpdates.values()) {
687
- if (Buffer.byteLength(content, "utf8") > LOCAL_MEMORY_NOTE_BYTE_CAP)
688
- throw new Error("local_memory_note_byte_limit");
689
- }
690
- await assertMemoryInventoryHealth(input.agentDir);
691
522
  for (const candidate of pendingCandidates)
692
523
  await persistPendingMemoryCandidate({ agentDir: input.agentDir, candidate });
693
524
  for (const [path, content] of noteUpdates)
@@ -704,5 +535,5 @@ export async function applyLocalMemoryResult(input) {
704
535
  deferredIds,
705
536
  pendingIds: deferredIds,
706
537
  };
707
- }));
538
+ });
708
539
  }
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { lstat, readFile, readdir, stat } from "node:fs/promises";
2
+ import { readFile, stat } from "node:fs/promises";
3
3
  import { isAbsolute, join, normalize, relative, resolve } from "node:path";
4
4
  import { parseArgs } from "node:util";
5
5
  import { capMemoryForInject, MEMORY_INJECT_CAP } from "./prompt.js";
@@ -8,7 +8,6 @@ const INJECTION_MARKERS = [
8
8
  "## [注入] 你的 MEMORY.md(索引,只读参考)\n",
9
9
  ];
10
10
  const LOCAL_MEMORY_HASH_CAP_BYTES = 10 * 1024 * 1024;
11
- export const LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS = 2_000;
12
11
  function errorCode(error) {
13
12
  const code = error?.code;
14
13
  return typeof code === "string" ? code : error instanceof Error ? error.name : "unknown";
@@ -40,75 +39,30 @@ async function inspectFile(path, signal) {
40
39
  return { ...base, error_code: errorCode(error) };
41
40
  }
42
41
  }
43
- async function inspectPendingDirectory(path, signal) {
44
- let metadata;
45
- try {
46
- metadata = await lstat(path);
47
- }
48
- catch (error) {
49
- const code = errorCode(error);
50
- return code === "ENOENT" ? { exists: false, entry_count: 0 } : { exists: false, error_code: code };
51
- }
52
- const base = { exists: true, mtime_ms: metadata.mtimeMs };
53
- if (metadata.isSymbolicLink() || !metadata.isDirectory())
54
- return { ...base, hash_skipped_reason: "not_regular_file" };
55
- try {
56
- const names = (await readdir(path, { withFileTypes: true }))
57
- .filter(entry => entry.name.endsWith(".json"))
58
- .sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
59
- if (names.length > 128)
60
- return { ...base, entry_count: names.length, error_code: "entry_limit" };
61
- const hash = createHash("sha256");
62
- let size = 0;
63
- for (const entry of names) {
64
- if (signal.aborted)
65
- throw signal.reason;
66
- if (!entry.isFile() || entry.isSymbolicLink())
67
- return { ...base, entry_count: names.length, error_code: "invalid_entry" };
68
- const fact = await inspectFile(join(path, entry.name), signal);
69
- if (!fact.exists || fact.sha256 === undefined || fact.error_code !== undefined
70
- || fact.hash_skipped_reason !== undefined)
71
- return { ...base, entry_count: names.length, error_code: "incomplete_entry" };
72
- size += fact.size_bytes ?? 0;
73
- hash.update(entry.name).update("\0").update(fact.sha256).update("\0");
74
- }
75
- return { ...base, size_bytes: size, entry_count: names.length, sha256: hash.digest("hex") };
76
- }
77
- catch (error) {
78
- return { ...base, error_code: errorCode(error) };
79
- }
80
- }
81
- export async function withinLocalMemoryDiagnosticsDeadline(operation, timeoutMs) {
42
+ export async function inspectLocalMemoryFilesWithinDeadline(homeDir, timeoutMs) {
82
43
  const controller = new AbortController();
83
44
  let timer;
45
+ const inspection = Promise.all([
46
+ inspectFile(join(homeDir, "MEMORY.md"), controller.signal),
47
+ inspectFile(join(homeDir, "notes", "lessons.md"), controller.signal),
48
+ ]).then(([memory, lessons]) => ({ memory, lessons }));
84
49
  const timeout = new Promise((_resolve, reject) => {
85
50
  timer = setTimeout(() => {
86
- const error = Object.assign(new Error("local memory diagnostics timed out"), {
51
+ controller.abort();
52
+ reject(Object.assign(new Error("local memory diagnostics timed out"), {
87
53
  code: "diagnostics_timeout",
88
- });
89
- controller.abort(error);
90
- reject(error);
54
+ }));
91
55
  }, Math.max(1, timeoutMs));
92
56
  timer.unref?.();
93
57
  });
94
58
  try {
95
- return await Promise.race([operation(controller.signal), timeout]);
59
+ return await Promise.race([inspection, timeout]);
96
60
  }
97
61
  finally {
98
62
  if (timer !== undefined)
99
63
  clearTimeout(timer);
100
64
  }
101
65
  }
102
- export async function inspectLocalMemoryFilesWithinDeadline(homeDir, timeoutMs) {
103
- return withinLocalMemoryDiagnosticsDeadline(async (signal) => {
104
- const [memory, lessons, pending] = await Promise.all([
105
- inspectFile(join(homeDir, "MEMORY.md"), signal),
106
- inspectFile(join(homeDir, "notes", "lessons.md"), signal),
107
- inspectPendingDirectory(join(homeDir, ".crew", "memory", "pending"), signal),
108
- ]);
109
- return { memory, lessons, pending };
110
- }, timeoutMs);
111
- }
112
66
  export function localMemoryContentFacts(content, maxScanBytes = LOCAL_MEMORY_HASH_CAP_BYTES) {
113
67
  const sizeBytes = Buffer.byteLength(content, "utf8");
114
68
  if (sizeBytes > Math.max(0, maxScanBytes)) {
@@ -122,12 +76,12 @@ export function localMemoryContentFacts(content, maxScanBytes = LOCAL_MEMORY_HAS
122
76
  lessons_reference_count: content.match(/\bnotes[\\/]lessons\.md\b/gi)?.length ?? 0,
123
77
  };
124
78
  }
125
- export function analyzeLocalMemoryInjection(systemPrompt, sourceMemory, query = "") {
79
+ export function analyzeLocalMemoryInjection(systemPrompt, sourceMemory) {
126
80
  const sourceBytes = Buffer.byteLength(sourceMemory, "utf8");
127
81
  if (sourceMemory.length === 0) {
128
82
  return { state: "empty_source", source_bytes: 0, bounded_bytes: 0, injected_bytes: 0 };
129
83
  }
130
- const bounded = capMemoryForInject(sourceMemory, undefined, Number.POSITIVE_INFINITY, query);
84
+ const bounded = capMemoryForInject(sourceMemory);
131
85
  const marker = INJECTION_MARKERS.find((candidate) => systemPrompt.includes(candidate));
132
86
  if (marker === undefined) {
133
87
  return {
@@ -263,50 +217,15 @@ function crewMemoryReadAccess(command, paths) {
263
217
  return null;
264
218
  }
265
219
  }
266
- /**
267
- * Decode only the shell wrapper shape emitted by supported runtimes. The body must be one complete
268
- * quoted argument. Double-quoted bodies may escape only a quote or backslash; shell expansion and
269
- * concatenated quoting stay unknown so telemetry never guesses what the shell actually executed.
270
- */
271
- function unwrapLiteralShellCommand(command) {
272
- const trimmed = command.trim();
273
- const wrapper = /^\/bin\/(?:(?:zsh|bash)\s+-lc|sh\s+-c)\s+([\s\S]+)$/u.exec(trimmed);
274
- if (wrapper === null)
275
- return trimmed;
276
- const quoted = wrapper[1];
277
- const quote = quoted[0];
278
- if ((quote !== "'" && quote !== '"') || quoted.at(-1) !== quote || quoted.length < 2)
279
- return null;
280
- const body = quoted.slice(1, -1);
281
- if (quote === "'")
282
- return body.includes("'") ? null : body;
283
- let decoded = "";
284
- for (let index = 0; index < body.length; index += 1) {
285
- const character = body[index];
286
- if (character === '"' || character === "$" || character === "`")
287
- return null;
288
- if (character !== "\\") {
289
- decoded += character;
290
- continue;
291
- }
292
- const escaped = body[index + 1];
293
- if (escaped !== '"' && escaped !== "\\")
294
- return null;
295
- decoded += escaped;
296
- index += 1;
297
- }
298
- return decoded;
299
- }
300
220
  function commandAccesses(command, paths) {
301
- const unwrapped = unwrapLiteralShellCommand(command);
302
- if (unwrapped === null || /[;|&\n]/.test(unwrapped))
221
+ if (/[;|&\n]/.test(command))
303
222
  return [];
304
223
  const accesses = new Map();
305
- const memoryRead = crewMemoryReadAccess(unwrapped, paths);
224
+ const memoryRead = crewMemoryReadAccess(command, paths);
306
225
  if (memoryRead !== null) {
307
226
  accesses.set(`${memoryRead.target}:${memoryRead.relative_path ?? ""}:read`, memoryRead);
308
227
  }
309
- const expanded = unwrapped
228
+ const expanded = command
310
229
  .replaceAll("${CREW_HOME}", paths.homeDir)
311
230
  .replaceAll("$CREW_HOME", paths.homeDir)
312
231
  .replace(/["']/g, "");
@@ -25,17 +25,6 @@ export function logLocalMemoryContextPrepareFailure(identity, phase, error) {
25
25
  ...errorFields(error),
26
26
  });
27
27
  }
28
- export function logLocalMemoryContextDegraded(identity, error) {
29
- safeLog("local_memory.context_degraded", "本地记忆准备失败,已安全降级为无记忆执行", {
30
- level: "WARN",
31
- execution_id: identity.executionId,
32
- agent_handle: identity.agentHandle,
33
- task_key: identity.taskKey,
34
- memory_read_enabled: false,
35
- memory_write_enabled: false,
36
- ...errorFields(error),
37
- });
38
- }
39
28
  export function logLocalMemoryDiagnosticsFailure(identity, phase, error) {
40
29
  safeLog("local_memory.diagnostics_failed", "本地记忆诊断失败并已放行", {
41
30
  level: "WARN",
@@ -49,7 +38,6 @@ export function logLocalMemoryDiagnosticsFailure(identity, phase, error) {
49
38
  function snapshotFields(snapshot) {
50
39
  if (snapshot === null)
51
40
  return {};
52
- const pending = snapshot.pending ?? { exists: false, entry_count: 0 };
53
41
  return {
54
42
  memory_exists: snapshot.memory.exists,
55
43
  memory_size_bytes: snapshot.memory.size_bytes,
@@ -66,12 +54,6 @@ function snapshotFields(snapshot) {
66
54
  lessons_hash_skipped_reason: snapshot.lessons.hash_skipped_reason,
67
55
  lessons_error_code: snapshot.lessons.error_code,
68
56
  lessons_markdown_heading_count: snapshot.lessons.markdown_heading_count,
69
- pending_exists: pending.exists,
70
- pending_size_bytes: pending.size_bytes,
71
- pending_mtime_ms: pending.mtime_ms,
72
- pending_sha256: pending.sha256,
73
- pending_error_code: pending.error_code,
74
- pending_entry_count: pending.entry_count,
75
57
  };
76
58
  }
77
59
  function promptDelivery(runtime, stableProtocolRuntime) {
@@ -121,7 +103,7 @@ export function createLocalMemoryTelemetry(options) {
121
103
  contextPrepared(context) {
122
104
  try {
123
105
  const sourceFacts = localMemoryContentFacts(context.memory);
124
- const injectionFacts = analyzeLocalMemoryInjection(context.systemPrompt, context.memory, context.memoryQuery);
106
+ const injectionFacts = analyzeLocalMemoryInjection(context.systemPrompt, context.memory);
125
107
  safeLog("local_memory.context_prepared", "本地记忆上下文已准备", {
126
108
  execution_id: options.executionId,
127
109
  agent_handle: options.agentHandle,
@@ -201,13 +183,10 @@ export function createLocalMemoryTelemetry(options) {
201
183
  const lessonsChanged = beforeSnapshot !== null && afterSnapshot !== null
202
184
  ? localMemoryFileChanged(beforeSnapshot.lessons, afterSnapshot.lessons)
203
185
  : undefined;
204
- const pendingChanged = beforeSnapshot !== null && afterSnapshot !== null
205
- ? localMemoryFileChanged(beforeSnapshot.pending ?? { exists: false }, afterSnapshot.pending ?? { exists: false })
206
- : undefined;
207
186
  const accessSummary = accessObserver.summary();
208
- const sharedFilesChanged = memoryChanged === undefined || lessonsChanged === undefined || pendingChanged === undefined
187
+ const sharedFilesChanged = memoryChanged === undefined || lessonsChanged === undefined
209
188
  ? undefined
210
- : memoryChanged || lessonsChanged || pendingChanged;
189
+ : memoryChanged || lessonsChanged;
211
190
  safeLog("local_memory.execution_observed", "本地记忆执行观察已完成", {
212
191
  execution_id: options.executionId,
213
192
  agent_handle: options.agentHandle,
@@ -221,7 +200,6 @@ export function createLocalMemoryTelemetry(options) {
221
200
  executor_outcome: executorCompleted ? "succeeded" : "failed",
222
201
  memory_changed: memoryChanged,
223
202
  lessons_changed: lessonsChanged,
224
- pending_changed: pendingChanged,
225
203
  shared_files_changed_during_execution: sharedFilesChanged,
226
204
  unexpected_shared_write: options.isMemoryPrune
227
205
  ? undefined
@@ -246,12 +224,6 @@ export function createLocalMemoryTelemetry(options) {
246
224
  lessons_after_hash_skipped_reason: afterSnapshot.lessons.hash_skipped_reason,
247
225
  lessons_after_error_code: afterSnapshot.lessons.error_code,
248
226
  lessons_after_markdown_heading_count: afterSnapshot.lessons.markdown_heading_count,
249
- pending_after_exists: afterSnapshot.pending?.exists ?? false,
250
- pending_after_size_bytes: afterSnapshot.pending?.size_bytes,
251
- pending_after_mtime_ms: afterSnapshot.pending?.mtime_ms,
252
- pending_after_sha256: afterSnapshot.pending?.sha256,
253
- pending_after_error_code: afterSnapshot.pending?.error_code,
254
- pending_after_entry_count: afterSnapshot.pending?.entry_count ?? 0,
255
227
  }),
256
228
  });
257
229
  },
@@ -27,7 +27,6 @@ import { RUNTIME_HEALTH_PROBE_CAPABILITY } from "./runtime-health.js";
27
27
  import { probeDeepSeekHarnessAcp, deepSeekHarnessLauncher } from "./runtimes/deepseek-harness.js";
28
28
  import { daemonHome } from "./computer-profile.js";
29
29
  import { daemonReleaseProvenance } from "./release-provenance.js";
30
- import { MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY } from "./memory-migration-protocol.js";
31
30
  export { executableRuntimes } from "./runtime-capabilities.js";
32
31
  const execFileP = promisify(execFile);
33
32
  export const PROJECT_WORKSPACES_CAPABILITY = "project_workspaces_v1";
@@ -44,7 +43,6 @@ export const NATIVE_PROJECT_WORKSPACE_READINESS = Object.freeze({
44
43
  export const DAEMON_CAPABILITIES = Object.freeze([
45
44
  RUNTIME_TOOL_UPDATES_CAPABILITY,
46
45
  MARKETPLACE_CAPABILITY,
47
- MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY,
48
46
  MAINTENANCE_CAPABILITY,
49
47
  "scheduled_job_v1",
50
48
  "reply_origin_v1",
@@ -81,7 +79,6 @@ export const daemonCapabilities = (runtimePlatform = process.platform, readiness
81
79
  return Object.freeze(DAEMON_CAPABILITIES.filter((capability) => (((capability !== MARKETPLACE_CAPABILITY && capability !== MAINTENANCE_CAPABILITY && capability !== RUNTIME_TOOL_UPDATES_CAPABILITY) || nativeWorkspacePlatform)
82
80
  && (capability !== PROJECT_WORKSPACES_CAPABILITY || nativeWorkspaceReady)
83
81
  && (capability !== "message_file_preview_v1" || nativeWorkspacePlatform)
84
- && (nativeWorkspacePlatform || ![MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY].includes(capability))
85
82
  && (nativeWorkspacePlatform
86
83
  || (capability !== "project_skills_v1"
87
84
  && capability !== "project_skill_projection_v2"
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  // 与另一个进程中的 marketplace-protocol.ts 保持一致;跨进程契约测试校验摘要。
3
3
  export const MARKETPLACE_CAPABILITY = "marketplace_update_v1";
4
4
  export const MARKETPLACE = "nowwork-pm";
5
- export const PLUGIN = "aiinterview-plugin";
5
+ export const PLUGIN = "AIinterview-plugin";
6
6
  export const MarketplaceModeSchema = z.enum(["inspect", "refresh", "update"]);
7
7
  export const MarketplaceStatusSchema = z.enum(["queued", "running", "completed", "partial", "failed", "unknown"]);
8
8
  export const MarketplaceTargetSchema = z.object({