@nowcrew/daemon 0.6.97-beta.2 → 0.6.97-beta.4

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 (55) 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 +39 -63
  7. package/dist/local-memory/index-budget.js +3 -9
  8. package/dist/local-memory/inventory-cli.js +8 -49
  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 +13 -181
  17. package/dist/local-memory/write.js +21 -189
  18. package/dist/local-memory-diagnostics.js +13 -94
  19. package/dist/local-memory-telemetry.js +2 -19
  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 -60
  25. package/dist/message-file-preview.js +1 -11
  26. package/dist/prompt.js +2 -6
  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 +2 -13
  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 -62
  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 -361
  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 -317
  45. package/dist/local-memory/migration-schema.js +0 -55
  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/storage-format.js +0 -152
  50. package/dist/local-memory/writer-mode.js +0 -13
  51. package/dist/memory-migration-controller.js +0 -86
  52. package/dist/memory-migration-inspect.js +0 -108
  53. package/dist/memory-migration-journal.js +0 -14
  54. package/dist/memory-migration-protocol.js +0 -14
  55. package/dist/memory-migration-runtime.js +0 -305
@@ -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(),
@@ -92,14 +90,12 @@ function parseCards(section) {
92
90
  });
93
91
  }
94
92
  export function parseLocalMemoryIndex(memory) {
95
- if (detectLocalMemoryStorageVersion(memory) === 2)
96
- return parseFullMemoryIndex(memory);
97
93
  const section = indexSection(memory);
98
94
  const generated = GENERATED_INDEX_PATTERN.exec(section)?.[0];
99
95
  return parseCards(generated ?? section);
100
96
  }
101
97
  function parseUnmarkedMemoryIndex(memory) {
102
- const section = indexSection(memory).replace(LOCAL_MEMORY_GENERATED_INDEX_PATTERN, "");
98
+ const section = indexSection(memory).replace(GENERATED_INDEX_PATTERN, "");
103
99
  return parseCards(section);
104
100
  }
105
101
  function replaceGeneratedIndex(memory, entries) {
@@ -126,36 +122,11 @@ function noteSection(id, revision, title, body) {
126
122
  }
127
123
  function notePattern(id) {
128
124
  const escaped = id.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
129
- 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");
130
- }
131
- function uniqueNoteSection(content, id) {
132
- const escaped = id.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
133
- const begins = [...content.matchAll(new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:BEGIN\\s+id=${escaped}(?=\\s|-->)`, "gu"))];
134
- const ends = [...content.matchAll(new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:END\\s+id=${escaped}\\s*-->`, "gu"))];
135
- if (begins.length === 0 && ends.length === 0)
136
- return null;
137
- const section = notePattern(id).exec(content)?.[0];
138
- if (begins.length !== 1 || ends.length !== 1 || section === undefined
139
- || (section.match(/NOWWORK:MEMORY_ENTRY:(?:BEGIN|END)/gu) ?? []).length !== 2) {
140
- throw new Error("local_memory_note_anchor_invalid");
141
- }
142
- 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");
143
126
  }
144
127
  function replaceNote(content, id, replacement) {
145
128
  const pattern = notePattern(id);
146
- return uniqueNoteSection(content, id) !== null ? content.replace(pattern, () => replacement) : null;
147
- }
148
- function updateNoteRevision(content, id, revision) {
149
- const section = uniqueNoteSection(content, id);
150
- const beginning = `<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${id} `;
151
- const ending = `<!-- NOWWORK:MEMORY_ENTRY:END id=${id} -->`;
152
- if (section === null || content.split(beginning).length !== 2 || content.split(ending).length !== 2) {
153
- throw new Error("local_memory_note_anchor_missing");
154
- }
155
- 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}`);
156
- if (!updated.startsWith(`${beginning}revision=${revision} -->`))
157
- throw new Error("local_memory_note_anchor_missing");
158
- return content.replace(notePattern(id), () => updated);
129
+ return pattern.test(content) ? content.replace(pattern, replacement) : null;
159
130
  }
160
131
  function validateCandidate(result) {
161
132
  if (result.outcome === "no_change" && (result.changes.length > 0 || result.statusChanges.length > 0)) {
@@ -209,25 +180,6 @@ function stableId(executionId, index, change) {
209
180
  const input = [executionId, String(index), change.operation, change.title, change.summary, change.detailBody].join("\0");
210
181
  return `mem_${createHash("sha256").update(input, "utf8").digest("hex").slice(0, 12)}`;
211
182
  }
212
- function migrationPendingId(change) {
213
- // Keep the digest independent of object insertion order for callers that
214
- // reach applyLocalMemoryResult directly instead of the JSON/Zod boundary.
215
- const canonicalChange = {
216
- operation: change.operation,
217
- memoryId: change.memoryId,
218
- baseRevision: change.baseRevision,
219
- supersedesMemoryId: change.supersedesMemoryId,
220
- title: change.title,
221
- kind: change.kind,
222
- summary: change.summary,
223
- triggers: change.triggers,
224
- priority: change.priority,
225
- detailCategory: change.detailCategory,
226
- detailBody: change.detailBody,
227
- reviewAfter: change.reviewAfter,
228
- };
229
- return `pending-migration-${createHash("sha256").update(JSON.stringify(canonicalChange), "utf8").digest("hex").slice(0, 12)}`;
230
- }
231
183
  function normalize(value) {
232
184
  return value.normalize("NFKC").toLocaleLowerCase().replace(/\s+/gu, " ").trim();
233
185
  }
@@ -247,14 +199,14 @@ function detailCategoryFromFile(file) {
247
199
  return DETAIL_CATEGORIES.has(category) ? category : null;
248
200
  }
249
201
  function readNoteBody(content, id) {
250
- const section = uniqueNoteSection(content, id);
251
- if (section === null)
202
+ const begin = content.indexOf(`<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${id}`);
203
+ if (begin < 0)
252
204
  return null;
253
- const bodyStart = section.indexOf("\n\n");
254
- 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);
255
207
  if (bodyStart < 0 || bodyEnd < 0)
256
208
  return null;
257
- return section.slice(bodyStart + 2, bodyEnd).trim();
209
+ return content.slice(bodyStart + 2, bodyEnd).trim();
258
210
  }
259
211
  function archivedChange(entry, detailBody) {
260
212
  const detailCategory = detailCategoryFromFile(entry.file);
@@ -278,34 +230,6 @@ function archivedChange(entry, detailBody) {
278
230
  function emptyResult() {
279
231
  return { createdIds: [], updatedIds: [], statusChangedIds: [], deferredIds: [], pendingIds: [] };
280
232
  }
281
- async function stageMigrationDeferredPending(input) {
282
- if (input.result.outcome !== "proposed")
283
- return emptyResult();
284
- if (input.result.statusChanges.length > 0 || input.result.changes.some((change) => change.operation !== "create")) {
285
- throw new Error("local_memory_migration_pending_create_only");
286
- }
287
- const executionId = input.executionId ?? "unknown";
288
- const pending = input.result.changes.map((change) => ({
289
- schemaVersion: 1,
290
- pendingId: migrationPendingId(change),
291
- createdAt: new Date().toISOString(),
292
- reason: "legacy_requires_migration",
293
- source: { executionId, pruneTraceId: input.pruneTraceId ?? null },
294
- candidate: change,
295
- }));
296
- await assertMemoryAccessDirectory(input.agentDir);
297
- await withMemoryAccess(input.agentDir, async () => {
298
- const memory = await readFile(join(input.agentDir, "MEMORY.md"), "utf8").catch(() => null);
299
- if (memory === null || detectLocalMemoryStorageVersion(memory) !== 1) {
300
- throw new Error("local_memory_migration_pending_storage_invalid");
301
- }
302
- for (const candidate of pending) {
303
- await persistPendingMemoryCandidate({ agentDir: input.agentDir, candidate });
304
- }
305
- }, { pendingOnly: true });
306
- const pendingIds = [...new Set(pending.map((candidate) => candidate.pendingId))];
307
- return { createdIds: [], updatedIds: [], statusChangedIds: [], deferredIds: pendingIds, pendingIds };
308
- }
309
233
  function isWithin(root, candidate) {
310
234
  const path = relative(root, candidate);
311
235
  return path === "" || (path !== ".." && !path.startsWith(`..${sep}`));
@@ -320,29 +244,12 @@ async function assertWritablePaths(agentDir, memoryPath) {
320
244
  if (!memoryStat.isFile() || memoryStat.isSymbolicLink())
321
245
  throw new Error("local_memory_memory_path_invalid");
322
246
  }
323
- async function assertMemoryAccessDirectory(agentDir) {
324
- try {
325
- const stat = await lstat(join(agentDir, ".crew/memory"));
326
- if (!stat.isDirectory() || stat.isSymbolicLink())
327
- throw new Error("local_memory_pending_path_invalid");
328
- }
329
- catch (error) {
330
- if (error.code === "ENOENT")
331
- return;
332
- throw error;
333
- }
334
- }
335
247
  async function readNote(path) {
336
248
  try {
337
249
  const stat = await lstat(path);
338
250
  if (!stat.isFile() || stat.isSymbolicLink())
339
251
  throw new Error("local_memory_note_path_invalid");
340
- if (stat.size > LOCAL_MEMORY_NOTE_BYTE_CAP)
341
- throw new Error("local_memory_note_byte_limit");
342
- const content = await readFile(path, "utf8");
343
- if (Buffer.byteLength(content, "utf8") > LOCAL_MEMORY_NOTE_BYTE_CAP)
344
- throw new Error("local_memory_note_byte_limit");
345
- return content;
252
+ return await readFile(path, "utf8");
346
253
  }
347
254
  catch (error) {
348
255
  if (error.code === "ENOENT")
@@ -362,11 +269,6 @@ async function atomicWrite(path, content) {
362
269
  }
363
270
  }
364
271
  function projectIndex(memory, entries, protectedIds) {
365
- if (detectLocalMemoryStorageVersion(memory) === 2) {
366
- const updated = replaceFullMemoryIndex(memory, entries);
367
- selectLocalMemoryPreview(updated);
368
- return { memory: updated, omittedIds: [], reason: null };
369
- }
370
272
  const empty = replaceGeneratedIndex(memory, []);
371
273
  if (codePointLength(empty) > LOCAL_MEMORY_TOTAL_CODE_POINT_CAP)
372
274
  throw new Error("local_memory_memory_over_budget");
@@ -404,29 +306,12 @@ async function withApplyLease(agentDir, operation) {
404
306
  }
405
307
  export async function applyLocalMemoryResult(input) {
406
308
  validateCandidate(input.result);
407
- if (input.migrationDeferredPending)
408
- return stageMigrationDeferredPending(input);
409
- if (input.result.outcome !== "proposed" && input.requiredStorageVersion !== 2)
309
+ if (input.result.outcome !== "proposed")
410
310
  return emptyResult();
411
311
  const memoryPath = join(input.agentDir, "MEMORY.md");
412
- if (input.result.outcome === "proposed")
413
- await assertWritablePaths(input.agentDir, memoryPath);
414
- await assertMemoryAccessDirectory(input.agentDir);
415
- return withApplyLease(input.agentDir, async () => withMemoryAccess(input.agentDir, async () => {
416
- await assertMemoryWriterAllowed(input.agentDir, input.writerCapabilities ?? []);
417
- const memoryStat = await lstat(memoryPath);
418
- if (memoryStat.size > 16 * 1024 * 1024)
419
- throw new Error("local_memory_storage_byte_limit");
312
+ await assertWritablePaths(input.agentDir, memoryPath);
313
+ return withApplyLease(input.agentDir, async () => {
420
314
  const memory = await readFile(memoryPath, "utf8");
421
- assertLocalMemoryStorageSize(memory);
422
- const fullStorage = detectLocalMemoryStorageVersion(memory) === 2;
423
- if (input.requiredStorageVersion === 2 && !fullStorage) {
424
- throw new Error("local_memory_v2_state_invalid_before_commit");
425
- }
426
- // A V2 no-op is still a commit decision. Return only after the access
427
- // lock and committed-state writer gate have validated the same snapshot.
428
- if (input.result.outcome !== "proposed")
429
- return emptyResult();
430
315
  if (parseUnmarkedMemoryIndex(memory).length > 0)
431
316
  throw new Error("local_memory_legacy_index_requires_migration");
432
317
  let entries = [...parseLocalMemoryIndex(memory)];
@@ -457,26 +342,6 @@ export async function applyLocalMemoryResult(input) {
457
342
  const pendingCandidates = [];
458
343
  const resolvedPendingIds = new Set();
459
344
  const noteUpdates = new Map();
460
- const noteCache = new Map();
461
- let totalReadBytes = Buffer.byteLength(memory, "utf8");
462
- const readStoredNote = async (path) => {
463
- const cached = noteUpdates.get(path) ?? noteCache.get(path);
464
- if (cached !== undefined)
465
- return cached;
466
- const stat = await lstat(path).catch((error) => {
467
- if (error.code === "ENOENT")
468
- return null;
469
- throw error;
470
- });
471
- if (stat !== null && totalReadBytes + stat.size > LOCAL_MEMORY_READ_BYTE_CAP)
472
- throw new Error("local_memory_read_byte_limit");
473
- const content = await readNote(path);
474
- totalReadBytes += Buffer.byteLength(content, "utf8");
475
- if (totalReadBytes > LOCAL_MEMORY_READ_BYTE_CAP)
476
- throw new Error("local_memory_read_byte_limit");
477
- noteCache.set(path, content);
478
- return content;
479
- };
480
345
  let allowActiveEviction = false;
481
346
  const randomId = input.randomId ?? randomUUID;
482
347
  const executionId = input.executionId ?? "unknown";
@@ -518,21 +383,10 @@ export async function applyLocalMemoryResult(input) {
518
383
  if (existingCreate !== undefined && !matchesCreateCandidate(existingCreate, change)) {
519
384
  throw new Error("local_memory_duplicate_candidate");
520
385
  }
521
- if (existingCreate === undefined && entries.some((entry) => (!fullStorage || entry.status === "active" || entry.status === "pinned")
522
- && (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))) {
523
388
  throw new Error("local_memory_duplicate_candidate");
524
389
  }
525
- if (existingCreate !== undefined) {
526
- if (existingCreate.revision !== 1 || existingCreate.status !== "active") {
527
- throw new Error("local_memory_create_replay_conflict");
528
- }
529
- const stored = await readStoredNote(resolve(input.agentDir, existingCreate.file));
530
- if (uniqueNoteSection(stored, id)?.trimEnd() !== noteSection(id, 1, change.title, change.detailBody).trimEnd()) {
531
- throw new Error("local_memory_create_replay_conflict");
532
- }
533
- // A proven duplicate is a no-op; it must never rewrite a newer body or status.
534
- continue;
535
- }
536
390
  }
537
391
  const revision = existingCreate?.revision ?? (current === undefined ? 1 : current.revision + 1);
538
392
  const entry = existingCreate ?? entryFromChange(change, id, revision, current?.status ?? "active");
@@ -546,7 +400,7 @@ export async function applyLocalMemoryResult(input) {
546
400
  : item),
547
401
  entry,
548
402
  ];
549
- const allowEvictionForChange = !fullStorage && change.operation === "create" && change.priority === "high";
403
+ const allowEvictionForChange = change.operation === "create" && change.priority === "high";
550
404
  const protectedIds = new Set(allowEvictionForChange ? pinnedIds(entries) : activeOrPinnedIds(entries));
551
405
  if (allowEvictionForChange)
552
406
  allowActiveEviction = true;
@@ -563,7 +417,7 @@ export async function applyLocalMemoryResult(input) {
563
417
  const notePath = resolve(input.agentDir, entry.file);
564
418
  if (!isWithin(resolve(input.agentDir, "notes"), notePath))
565
419
  throw new Error("local_memory_note_path_invalid");
566
- const previous = await readStoredNote(notePath);
420
+ const previous = noteUpdates.get(notePath) ?? await readNote(notePath);
567
421
  const replacement = noteSection(id, revision, change.title, change.detailBody);
568
422
  const existingSection = replaceNote(previous, id, replacement);
569
423
  const next = existingSection ?? (current === undefined
@@ -581,7 +435,7 @@ export async function applyLocalMemoryResult(input) {
581
435
  // Pending create candidates are durable overflow, not dead ends. Promote only
582
436
  // when the current catalog has room; supersede/update candidates stay pending
583
437
  // until a fresh revision-aware decision can safely replay them.
584
- const pendingCandidatesOnDisk = fullStorage ? [] : await readPendingMemoryCandidates({ agentDir: input.agentDir }).catch((error) => {
438
+ const pendingCandidatesOnDisk = await readPendingMemoryCandidates({ agentDir: input.agentDir }).catch((error) => {
585
439
  if (!(error instanceof Error) || error.message !== "memory_pending_directory_over_budget")
586
440
  throw error;
587
441
  // 自动提升是可选工作,目录查询预算不应阻断本轮已验证的更新或腾位操作。
@@ -592,10 +446,6 @@ export async function applyLocalMemoryResult(input) {
592
446
  return [];
593
447
  });
594
448
  for (const pending of pendingCandidatesOnDisk) {
595
- // Migration-deferred candidates require reviewed import; only capacity
596
- // overflow is eligible for the existing automatic promotion path.
597
- if (pending.reason !== "index_over_budget")
598
- continue;
599
449
  const change = pending.candidate;
600
450
  if (change.operation !== "create")
601
451
  continue;
@@ -610,7 +460,7 @@ export async function applyLocalMemoryResult(input) {
610
460
  || normalize(entry.summary) === normalize(change.summary)));
611
461
  if (duplicate !== undefined) {
612
462
  const duplicatePath = resolve(input.agentDir, duplicate.file);
613
- const duplicateBody = readNoteBody(await readStoredNote(duplicatePath), duplicate.id);
463
+ const duplicateBody = readNoteBody(noteUpdates.get(duplicatePath) ?? await readNote(duplicatePath), duplicate.id);
614
464
  if (duplicateBody === change.detailBody
615
465
  || duplicateBody === unwrappedNoteBody(change.title, change.detailBody).trim()) {
616
466
  resolvedPendingIds.add(pending.pendingId);
@@ -628,7 +478,7 @@ export async function applyLocalMemoryResult(input) {
628
478
  const notePath = resolve(input.agentDir, entry.file);
629
479
  if (!isWithin(resolve(input.agentDir, "notes"), notePath))
630
480
  throw new Error("local_memory_note_path_invalid");
631
- const previous = await readStoredNote(notePath);
481
+ const previous = noteUpdates.get(notePath) ?? await readNote(notePath);
632
482
  const replacement = noteSection(id, 1, change.title, change.detailBody);
633
483
  const existingSection = replaceNote(previous, id, replacement);
634
484
  const next = existingSection ?? `${previous.trimEnd()}${previous.trimEnd() ? "\n\n" : ""}${replacement}`;
@@ -637,17 +487,6 @@ export async function applyLocalMemoryResult(input) {
637
487
  createdIds.push(id);
638
488
  resolvedPendingIds.add(pending.pendingId);
639
489
  }
640
- if (fullStorage) {
641
- const changedMetadataIds = new Set([
642
- ...statusChangedIds,
643
- ...input.result.changes.flatMap(change => change.supersedesMemoryId === null ? [] : [change.supersedesMemoryId]),
644
- ]);
645
- for (const id of changedMetadataIds) {
646
- const entry = entries.find(item => item.id === id);
647
- const notePath = resolve(input.agentDir, entry.file);
648
- noteUpdates.set(notePath, updateNoteRevision(await readStoredNote(notePath), id, entry.revision));
649
- }
650
- }
651
490
  let finalProjection = projectIndex(memory, entries, allowActiveEviction ? pinnedIds(entries) : activeOrPinnedIds(entries));
652
491
  if (finalProjection.reason !== null
653
492
  && (!allowActiveEviction || finalProjection.reason === "index_over_budget_pinned")) {
@@ -659,7 +498,7 @@ export async function applyLocalMemoryResult(input) {
659
498
  .filter((entry) => entry?.status === "active");
660
499
  for (const entry of omittedActiveEntries) {
661
500
  const notePath = resolve(input.agentDir, entry.file);
662
- const content = await readStoredNote(notePath);
501
+ const content = noteUpdates.get(notePath) ?? await readNote(notePath);
663
502
  const detailBody = readNoteBody(content, entry.id);
664
503
  const change = detailBody === null ? null : archivedChange(entry, detailBody);
665
504
  if (change === null)
@@ -680,13 +519,6 @@ export async function applyLocalMemoryResult(input) {
680
519
  if (finalProjection.reason !== null)
681
520
  throw new Error(finalProjection.reason);
682
521
  }
683
- // Complete every resource check before the first persistent write.
684
- assertLocalMemoryStorageSize(finalProjection.memory);
685
- for (const content of noteUpdates.values()) {
686
- if (Buffer.byteLength(content, "utf8") > LOCAL_MEMORY_NOTE_BYTE_CAP)
687
- throw new Error("local_memory_note_byte_limit");
688
- }
689
- await assertMemoryInventoryHealth(input.agentDir);
690
522
  for (const candidate of pendingCandidates)
691
523
  await persistPendingMemoryCandidate({ agentDir: input.agentDir, candidate });
692
524
  for (const [path, content] of noteUpdates)
@@ -703,5 +535,5 @@ export async function applyLocalMemoryResult(input) {
703
535
  deferredIds,
704
536
  pendingIds: deferredIds,
705
537
  };
706
- }));
538
+ });
707
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)) {
@@ -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, "");
@@ -38,7 +38,6 @@ export function logLocalMemoryDiagnosticsFailure(identity, phase, error) {
38
38
  function snapshotFields(snapshot) {
39
39
  if (snapshot === null)
40
40
  return {};
41
- const pending = snapshot.pending ?? { exists: false, entry_count: 0 };
42
41
  return {
43
42
  memory_exists: snapshot.memory.exists,
44
43
  memory_size_bytes: snapshot.memory.size_bytes,
@@ -55,12 +54,6 @@ function snapshotFields(snapshot) {
55
54
  lessons_hash_skipped_reason: snapshot.lessons.hash_skipped_reason,
56
55
  lessons_error_code: snapshot.lessons.error_code,
57
56
  lessons_markdown_heading_count: snapshot.lessons.markdown_heading_count,
58
- pending_exists: pending.exists,
59
- pending_size_bytes: pending.size_bytes,
60
- pending_mtime_ms: pending.mtime_ms,
61
- pending_sha256: pending.sha256,
62
- pending_error_code: pending.error_code,
63
- pending_entry_count: pending.entry_count,
64
57
  };
65
58
  }
66
59
  function promptDelivery(runtime, stableProtocolRuntime) {
@@ -190,13 +183,10 @@ export function createLocalMemoryTelemetry(options) {
190
183
  const lessonsChanged = beforeSnapshot !== null && afterSnapshot !== null
191
184
  ? localMemoryFileChanged(beforeSnapshot.lessons, afterSnapshot.lessons)
192
185
  : undefined;
193
- const pendingChanged = beforeSnapshot !== null && afterSnapshot !== null
194
- ? localMemoryFileChanged(beforeSnapshot.pending ?? { exists: false }, afterSnapshot.pending ?? { exists: false })
195
- : undefined;
196
186
  const accessSummary = accessObserver.summary();
197
- const sharedFilesChanged = memoryChanged === undefined || lessonsChanged === undefined || pendingChanged === undefined
187
+ const sharedFilesChanged = memoryChanged === undefined || lessonsChanged === undefined
198
188
  ? undefined
199
- : memoryChanged || lessonsChanged || pendingChanged;
189
+ : memoryChanged || lessonsChanged;
200
190
  safeLog("local_memory.execution_observed", "本地记忆执行观察已完成", {
201
191
  execution_id: options.executionId,
202
192
  agent_handle: options.agentHandle,
@@ -210,7 +200,6 @@ export function createLocalMemoryTelemetry(options) {
210
200
  executor_outcome: executorCompleted ? "succeeded" : "failed",
211
201
  memory_changed: memoryChanged,
212
202
  lessons_changed: lessonsChanged,
213
- pending_changed: pendingChanged,
214
203
  shared_files_changed_during_execution: sharedFilesChanged,
215
204
  unexpected_shared_write: options.isMemoryPrune
216
205
  ? undefined
@@ -235,12 +224,6 @@ export function createLocalMemoryTelemetry(options) {
235
224
  lessons_after_hash_skipped_reason: afterSnapshot.lessons.hash_skipped_reason,
236
225
  lessons_after_error_code: afterSnapshot.lessons.error_code,
237
226
  lessons_after_markdown_heading_count: afterSnapshot.lessons.markdown_heading_count,
238
- pending_after_exists: afterSnapshot.pending?.exists ?? false,
239
- pending_after_size_bytes: afterSnapshot.pending?.size_bytes,
240
- pending_after_mtime_ms: afterSnapshot.pending?.mtime_ms,
241
- pending_after_sha256: afterSnapshot.pending?.sha256,
242
- pending_after_error_code: afterSnapshot.pending?.error_code,
243
- pending_after_entry_count: afterSnapshot.pending?.entry_count ?? 0,
244
227
  }),
245
228
  });
246
229
  },
@@ -25,7 +25,6 @@ import { RUNTIME_HEALTH_PROBE_CAPABILITY } from "./runtime-health.js";
25
25
  import { probeDeepSeekHarnessAcp, deepSeekHarnessLauncher } from "./runtimes/deepseek-harness.js";
26
26
  import { daemonHome } from "./computer-profile.js";
27
27
  import { daemonReleaseProvenance } from "./release-provenance.js";
28
- import { MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY } from "./memory-migration-protocol.js";
29
28
  export { executableRuntimes } from "./runtime-capabilities.js";
30
29
  const execFileP = promisify(execFile);
31
30
  export const PROJECT_WORKSPACES_CAPABILITY = "project_workspaces_v1";
@@ -41,7 +40,6 @@ export const NATIVE_PROJECT_WORKSPACE_READINESS = Object.freeze({
41
40
  });
42
41
  export const DAEMON_CAPABILITIES = Object.freeze([
43
42
  MARKETPLACE_CAPABILITY,
44
- MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY,
45
43
  "scheduled_job_v1",
46
44
  "reply_origin_v1",
47
45
  "origin_decision_v1",
@@ -77,7 +75,6 @@ export const daemonCapabilities = (runtimePlatform = process.platform, readiness
77
75
  return Object.freeze(DAEMON_CAPABILITIES.filter((capability) => ((capability !== MARKETPLACE_CAPABILITY || nativeWorkspacePlatform)
78
76
  && (capability !== PROJECT_WORKSPACES_CAPABILITY || nativeWorkspaceReady)
79
77
  && (capability !== "message_file_preview_v1" || nativeWorkspacePlatform)
80
- && (nativeWorkspacePlatform || ![MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY].includes(capability))
81
78
  && (nativeWorkspacePlatform
82
79
  || (capability !== "project_skills_v1"
83
80
  && 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({
@@ -10,7 +10,7 @@ const Target = z.object({
10
10
  version: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$/),
11
11
  source: z.object({ source: z.literal("git-subdir"),
12
12
  url: z.literal("git@codeup.aliyun.com:6128d2e757e7cd986dfae5ab/pm/AIinterview-agent-skill.git"),
13
- path: z.literal("plugins/aiinterview-plugin"), sha: z.string().regex(/^[a-f0-9]{40}$/) }),
13
+ path: z.literal("plugins/AIinterview-plugin"), sha: z.string().regex(/^[a-f0-9]{40}$/) }),
14
14
  });
15
15
  export async function readRemoteMarketplace(runtime, registered, env, command) {
16
16
  const source = validateMarketplaceSource(registered.url, registered.ref);
@@ -12,7 +12,7 @@ import { readMarketplaceSource } from "./marketplace-source.js";
12
12
  export { MarketplaceFailure, runMarketplaceCommand } from "./marketplace-command.js";
13
13
  const SELECTOR = `${PLUGIN}@${MARKETPLACE}`;
14
14
  const SOURCE = "git@codeup.aliyun.com:6128d2e757e7cd986dfae5ab/pm/AIinterview-agent-skill.git";
15
- const SOURCE_PATH = "plugins/aiinterview-plugin";
15
+ const SOURCE_PATH = "plugins/AIinterview-plugin";
16
16
  const obj = (x) => x !== null && typeof x === "object" && !Array.isArray(x) ? x : {};
17
17
  const parse = (text) => { try {
18
18
  return JSON.parse(text);