@nowcrew/daemon 0.6.93-beta.9 → 0.6.94

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 (51) 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/list-models.js +31 -6
  6. package/dist/local-execution-prompt.js +3 -8
  7. package/dist/local-executor.js +37 -70
  8. package/dist/local-memory/index-budget.js +3 -9
  9. package/dist/local-memory/inventory-cli.js +7 -45
  10. package/dist/local-memory/inventory-source.js +8 -15
  11. package/dist/local-memory/prompt.js +4 -20
  12. package/dist/local-memory/read-cli.js +77 -75
  13. package/dist/local-memory/rollout.js +11 -73
  14. package/dist/local-memory/runtime.js +13 -176
  15. package/dist/local-memory/write.js +21 -135
  16. package/dist/local-memory-diagnostics.js +13 -94
  17. package/dist/local-memory-telemetry.js +2 -19
  18. package/dist/machine-info.js +0 -3
  19. package/dist/marketplace-protocol.js +1 -1
  20. package/dist/marketplace-remote.js +1 -1
  21. package/dist/marketplace-runner.js +1 -1
  22. package/dist/memory-prune-diagnostics.js +4 -60
  23. package/dist/message-file-preview.js +1 -11
  24. package/dist/prompt.js +2 -6
  25. package/dist/runner.js +1 -1
  26. package/dist/runtimes/claude-thread-runner.js +4 -25
  27. package/dist/serve.js +3 -38
  28. package/dist/token.js +0 -1
  29. package/dist/workspace-control-handler.js +3 -16
  30. package/dist/workspace.js +2 -13
  31. package/package.json +1 -1
  32. package/dist/local-memory/execution-workspace.js +0 -62
  33. package/dist/local-memory/full-index-search.js +0 -47
  34. package/dist/local-memory/initialization-identity.js +0 -36
  35. package/dist/local-memory/inventory-v2.js +0 -282
  36. package/dist/local-memory/legacy-adapter.js +0 -233
  37. package/dist/local-memory/migration-access.js +0 -196
  38. package/dist/local-memory/migration-cli.js +0 -54
  39. package/dist/local-memory/migration-io.js +0 -199
  40. package/dist/local-memory/migration-plan.js +0 -317
  41. package/dist/local-memory/migration-schema.js +0 -55
  42. package/dist/local-memory/migration-state-invariants.js +0 -52
  43. package/dist/local-memory/migration-store.js +0 -401
  44. package/dist/local-memory/migration-tool-entry.js +0 -21
  45. package/dist/local-memory/storage-format.js +0 -152
  46. package/dist/local-memory/writer-mode.js +0 -13
  47. package/dist/memory-migration-controller.js +0 -86
  48. package/dist/memory-migration-inspect.js +0 -108
  49. package/dist/memory-migration-journal.js +0 -14
  50. package/dist/memory-migration-protocol.js +0 -14
  51. 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 { 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)) {
@@ -228,14 +199,14 @@ function detailCategoryFromFile(file) {
228
199
  return DETAIL_CATEGORIES.has(category) ? category : null;
229
200
  }
230
201
  function readNoteBody(content, id) {
231
- const section = uniqueNoteSection(content, id);
232
- if (section === null)
202
+ const begin = content.indexOf(`<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${id}`);
203
+ if (begin < 0)
233
204
  return null;
234
- const bodyStart = section.indexOf("\n\n");
235
- 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);
236
207
  if (bodyStart < 0 || bodyEnd < 0)
237
208
  return null;
238
- return section.slice(bodyStart + 2, bodyEnd).trim();
209
+ return content.slice(bodyStart + 2, bodyEnd).trim();
239
210
  }
240
211
  function archivedChange(entry, detailBody) {
241
212
  const detailCategory = detailCategoryFromFile(entry.file);
@@ -273,29 +244,12 @@ async function assertWritablePaths(agentDir, memoryPath) {
273
244
  if (!memoryStat.isFile() || memoryStat.isSymbolicLink())
274
245
  throw new Error("local_memory_memory_path_invalid");
275
246
  }
276
- async function assertMemoryAccessDirectory(agentDir) {
277
- try {
278
- const stat = await lstat(join(agentDir, ".crew/memory"));
279
- if (!stat.isDirectory() || stat.isSymbolicLink())
280
- throw new Error("local_memory_pending_path_invalid");
281
- }
282
- catch (error) {
283
- if (error.code === "ENOENT")
284
- return;
285
- throw error;
286
- }
287
- }
288
247
  async function readNote(path) {
289
248
  try {
290
249
  const stat = await lstat(path);
291
250
  if (!stat.isFile() || stat.isSymbolicLink())
292
251
  throw new Error("local_memory_note_path_invalid");
293
- if (stat.size > LOCAL_MEMORY_NOTE_BYTE_CAP)
294
- throw new Error("local_memory_note_byte_limit");
295
- const content = await readFile(path, "utf8");
296
- if (Buffer.byteLength(content, "utf8") > LOCAL_MEMORY_NOTE_BYTE_CAP)
297
- throw new Error("local_memory_note_byte_limit");
298
- return content;
252
+ return await readFile(path, "utf8");
299
253
  }
300
254
  catch (error) {
301
255
  if (error.code === "ENOENT")
@@ -315,11 +269,6 @@ async function atomicWrite(path, content) {
315
269
  }
316
270
  }
317
271
  function projectIndex(memory, entries, protectedIds) {
318
- if (detectLocalMemoryStorageVersion(memory) === 2) {
319
- const updated = replaceFullMemoryIndex(memory, entries);
320
- selectLocalMemoryPreview(updated);
321
- return { memory: updated, omittedIds: [], reason: null };
322
- }
323
272
  const empty = replaceGeneratedIndex(memory, []);
324
273
  if (codePointLength(empty) > LOCAL_MEMORY_TOTAL_CODE_POINT_CAP)
325
274
  throw new Error("local_memory_memory_over_budget");
@@ -357,27 +306,12 @@ async function withApplyLease(agentDir, operation) {
357
306
  }
358
307
  export async function applyLocalMemoryResult(input) {
359
308
  validateCandidate(input.result);
360
- if (input.result.outcome !== "proposed" && input.requiredStorageVersion !== 2)
309
+ if (input.result.outcome !== "proposed")
361
310
  return emptyResult();
362
311
  const memoryPath = join(input.agentDir, "MEMORY.md");
363
- if (input.result.outcome === "proposed")
364
- await assertWritablePaths(input.agentDir, memoryPath);
365
- await assertMemoryAccessDirectory(input.agentDir);
366
- return withApplyLease(input.agentDir, async () => withMemoryAccess(input.agentDir, async () => {
367
- await assertMemoryWriterAllowed(input.agentDir, input.writerCapabilities ?? []);
368
- const memoryStat = await lstat(memoryPath);
369
- if (memoryStat.size > 16 * 1024 * 1024)
370
- throw new Error("local_memory_storage_byte_limit");
312
+ await assertWritablePaths(input.agentDir, memoryPath);
313
+ return withApplyLease(input.agentDir, async () => {
371
314
  const memory = await readFile(memoryPath, "utf8");
372
- assertLocalMemoryStorageSize(memory);
373
- const fullStorage = detectLocalMemoryStorageVersion(memory) === 2;
374
- if (input.requiredStorageVersion === 2 && !fullStorage) {
375
- throw new Error("local_memory_v2_state_invalid_before_commit");
376
- }
377
- // A V2 no-op is still a commit decision. Return only after the access
378
- // lock and committed-state writer gate have validated the same snapshot.
379
- if (input.result.outcome !== "proposed")
380
- return emptyResult();
381
315
  if (parseUnmarkedMemoryIndex(memory).length > 0)
382
316
  throw new Error("local_memory_legacy_index_requires_migration");
383
317
  let entries = [...parseLocalMemoryIndex(memory)];
@@ -408,26 +342,6 @@ export async function applyLocalMemoryResult(input) {
408
342
  const pendingCandidates = [];
409
343
  const resolvedPendingIds = new Set();
410
344
  const noteUpdates = new Map();
411
- const noteCache = new Map();
412
- let totalReadBytes = Buffer.byteLength(memory, "utf8");
413
- const readStoredNote = async (path) => {
414
- const cached = noteUpdates.get(path) ?? noteCache.get(path);
415
- if (cached !== undefined)
416
- return cached;
417
- const stat = await lstat(path).catch((error) => {
418
- if (error.code === "ENOENT")
419
- return null;
420
- throw error;
421
- });
422
- if (stat !== null && totalReadBytes + stat.size > LOCAL_MEMORY_READ_BYTE_CAP)
423
- throw new Error("local_memory_read_byte_limit");
424
- const content = await readNote(path);
425
- totalReadBytes += Buffer.byteLength(content, "utf8");
426
- if (totalReadBytes > LOCAL_MEMORY_READ_BYTE_CAP)
427
- throw new Error("local_memory_read_byte_limit");
428
- noteCache.set(path, content);
429
- return content;
430
- };
431
345
  let allowActiveEviction = false;
432
346
  const randomId = input.randomId ?? randomUUID;
433
347
  const executionId = input.executionId ?? "unknown";
@@ -469,21 +383,10 @@ export async function applyLocalMemoryResult(input) {
469
383
  if (existingCreate !== undefined && !matchesCreateCandidate(existingCreate, change)) {
470
384
  throw new Error("local_memory_duplicate_candidate");
471
385
  }
472
- if (existingCreate === undefined && entries.some((entry) => (!fullStorage || entry.status === "active" || entry.status === "pinned")
473
- && (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))) {
474
388
  throw new Error("local_memory_duplicate_candidate");
475
389
  }
476
- if (existingCreate !== undefined) {
477
- if (existingCreate.revision !== 1 || existingCreate.status !== "active") {
478
- throw new Error("local_memory_create_replay_conflict");
479
- }
480
- const stored = await readStoredNote(resolve(input.agentDir, existingCreate.file));
481
- if (uniqueNoteSection(stored, id)?.trimEnd() !== noteSection(id, 1, change.title, change.detailBody).trimEnd()) {
482
- throw new Error("local_memory_create_replay_conflict");
483
- }
484
- // A proven duplicate is a no-op; it must never rewrite a newer body or status.
485
- continue;
486
- }
487
390
  }
488
391
  const revision = existingCreate?.revision ?? (current === undefined ? 1 : current.revision + 1);
489
392
  const entry = existingCreate ?? entryFromChange(change, id, revision, current?.status ?? "active");
@@ -497,7 +400,7 @@ export async function applyLocalMemoryResult(input) {
497
400
  : item),
498
401
  entry,
499
402
  ];
500
- const allowEvictionForChange = !fullStorage && change.operation === "create" && change.priority === "high";
403
+ const allowEvictionForChange = change.operation === "create" && change.priority === "high";
501
404
  const protectedIds = new Set(allowEvictionForChange ? pinnedIds(entries) : activeOrPinnedIds(entries));
502
405
  if (allowEvictionForChange)
503
406
  allowActiveEviction = true;
@@ -514,7 +417,7 @@ export async function applyLocalMemoryResult(input) {
514
417
  const notePath = resolve(input.agentDir, entry.file);
515
418
  if (!isWithin(resolve(input.agentDir, "notes"), notePath))
516
419
  throw new Error("local_memory_note_path_invalid");
517
- const previous = await readStoredNote(notePath);
420
+ const previous = noteUpdates.get(notePath) ?? await readNote(notePath);
518
421
  const replacement = noteSection(id, revision, change.title, change.detailBody);
519
422
  const existingSection = replaceNote(previous, id, replacement);
520
423
  const next = existingSection ?? (current === undefined
@@ -532,7 +435,7 @@ export async function applyLocalMemoryResult(input) {
532
435
  // Pending create candidates are durable overflow, not dead ends. Promote only
533
436
  // when the current catalog has room; supersede/update candidates stay pending
534
437
  // until a fresh revision-aware decision can safely replay them.
535
- const pendingCandidatesOnDisk = fullStorage ? [] : await readPendingMemoryCandidates({ agentDir: input.agentDir }).catch((error) => {
438
+ const pendingCandidatesOnDisk = await readPendingMemoryCandidates({ agentDir: input.agentDir }).catch((error) => {
536
439
  if (!(error instanceof Error) || error.message !== "memory_pending_directory_over_budget")
537
440
  throw error;
538
441
  // 自动提升是可选工作,目录查询预算不应阻断本轮已验证的更新或腾位操作。
@@ -557,7 +460,7 @@ export async function applyLocalMemoryResult(input) {
557
460
  || normalize(entry.summary) === normalize(change.summary)));
558
461
  if (duplicate !== undefined) {
559
462
  const duplicatePath = resolve(input.agentDir, duplicate.file);
560
- const duplicateBody = readNoteBody(await readStoredNote(duplicatePath), duplicate.id);
463
+ const duplicateBody = readNoteBody(noteUpdates.get(duplicatePath) ?? await readNote(duplicatePath), duplicate.id);
561
464
  if (duplicateBody === change.detailBody
562
465
  || duplicateBody === unwrappedNoteBody(change.title, change.detailBody).trim()) {
563
466
  resolvedPendingIds.add(pending.pendingId);
@@ -575,7 +478,7 @@ export async function applyLocalMemoryResult(input) {
575
478
  const notePath = resolve(input.agentDir, entry.file);
576
479
  if (!isWithin(resolve(input.agentDir, "notes"), notePath))
577
480
  throw new Error("local_memory_note_path_invalid");
578
- const previous = await readStoredNote(notePath);
481
+ const previous = noteUpdates.get(notePath) ?? await readNote(notePath);
579
482
  const replacement = noteSection(id, 1, change.title, change.detailBody);
580
483
  const existingSection = replaceNote(previous, id, replacement);
581
484
  const next = existingSection ?? `${previous.trimEnd()}${previous.trimEnd() ? "\n\n" : ""}${replacement}`;
@@ -584,17 +487,6 @@ export async function applyLocalMemoryResult(input) {
584
487
  createdIds.push(id);
585
488
  resolvedPendingIds.add(pending.pendingId);
586
489
  }
587
- if (fullStorage) {
588
- const changedMetadataIds = new Set([
589
- ...statusChangedIds,
590
- ...input.result.changes.flatMap(change => change.supersedesMemoryId === null ? [] : [change.supersedesMemoryId]),
591
- ]);
592
- for (const id of changedMetadataIds) {
593
- const entry = entries.find(item => item.id === id);
594
- const notePath = resolve(input.agentDir, entry.file);
595
- noteUpdates.set(notePath, updateNoteRevision(await readStoredNote(notePath), id, entry.revision));
596
- }
597
- }
598
490
  let finalProjection = projectIndex(memory, entries, allowActiveEviction ? pinnedIds(entries) : activeOrPinnedIds(entries));
599
491
  if (finalProjection.reason !== null
600
492
  && (!allowActiveEviction || finalProjection.reason === "index_over_budget_pinned")) {
@@ -606,7 +498,7 @@ export async function applyLocalMemoryResult(input) {
606
498
  .filter((entry) => entry?.status === "active");
607
499
  for (const entry of omittedActiveEntries) {
608
500
  const notePath = resolve(input.agentDir, entry.file);
609
- const content = await readStoredNote(notePath);
501
+ const content = noteUpdates.get(notePath) ?? await readNote(notePath);
610
502
  const detailBody = readNoteBody(content, entry.id);
611
503
  const change = detailBody === null ? null : archivedChange(entry, detailBody);
612
504
  if (change === null)
@@ -627,12 +519,6 @@ export async function applyLocalMemoryResult(input) {
627
519
  if (finalProjection.reason !== null)
628
520
  throw new Error(finalProjection.reason);
629
521
  }
630
- // Complete every resource check before the first persistent write.
631
- assertLocalMemoryStorageSize(finalProjection.memory);
632
- for (const content of noteUpdates.values()) {
633
- if (Buffer.byteLength(content, "utf8") > LOCAL_MEMORY_NOTE_BYTE_CAP)
634
- throw new Error("local_memory_note_byte_limit");
635
- }
636
522
  for (const candidate of pendingCandidates)
637
523
  await persistPendingMemoryCandidate({ agentDir: input.agentDir, candidate });
638
524
  for (const [path, content] of noteUpdates)
@@ -649,5 +535,5 @@ export async function applyLocalMemoryResult(input) {
649
535
  deferredIds,
650
536
  pendingIds: deferredIds,
651
537
  };
652
- }));
538
+ });
653
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";
@@ -40,7 +39,6 @@ export const NATIVE_PROJECT_WORKSPACE_READINESS = Object.freeze({
40
39
  });
41
40
  export const DAEMON_CAPABILITIES = Object.freeze([
42
41
  MARKETPLACE_CAPABILITY,
43
- MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY,
44
42
  "scheduled_job_v1",
45
43
  "reply_origin_v1",
46
44
  "origin_decision_v1",
@@ -75,7 +73,6 @@ export const daemonCapabilities = (runtimePlatform = process.platform, readiness
75
73
  return Object.freeze(DAEMON_CAPABILITIES.filter((capability) => ((capability !== MARKETPLACE_CAPABILITY || nativeWorkspacePlatform)
76
74
  && (capability !== PROJECT_WORKSPACES_CAPABILITY || nativeWorkspaceReady)
77
75
  && (capability !== "message_file_preview_v1" || nativeWorkspacePlatform)
78
- && (nativeWorkspacePlatform || ![MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY].includes(capability))
79
76
  && (nativeWorkspacePlatform
80
77
  || (capability !== "project_skills_v1"
81
78
  && 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);