@nowcrew/daemon 0.6.91-beta.9 → 0.6.92

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 (38) hide show
  1. package/dist/execution-journal.js +1 -3
  2. package/dist/execution-protocol.js +1 -1
  3. package/dist/execution-runner.js +1 -3
  4. package/dist/local-execution-prompt.js +3 -8
  5. package/dist/local-executor.js +35 -14
  6. package/dist/local-memory/index-budget.js +3 -9
  7. package/dist/local-memory/prompt.js +4 -20
  8. package/dist/local-memory/read-cli.js +1 -36
  9. package/dist/local-memory/rollout.js +8 -53
  10. package/dist/local-memory/runtime.js +2 -5
  11. package/dist/local-memory/write.js +17 -108
  12. package/dist/local-runtime-environment.js +6 -1
  13. package/dist/machine-environment-cache.js +18 -4
  14. package/dist/machine-environment-contract.js +3 -1
  15. package/dist/machine-environment-process.js +21 -1
  16. package/dist/machine-info.js +0 -3
  17. package/dist/message-file-preview.js +1 -11
  18. package/dist/prompt.js +1 -5
  19. package/dist/runner.js +1 -1
  20. package/dist/serve.js +3 -36
  21. package/dist/token.js +0 -1
  22. package/dist/workspace-control-handler.js +3 -16
  23. package/package.json +1 -1
  24. package/dist/local-memory/execution-workspace.js +0 -36
  25. package/dist/local-memory/full-index-search.js +0 -47
  26. package/dist/local-memory/migration-access.js +0 -118
  27. package/dist/local-memory/migration-cli.js +0 -54
  28. package/dist/local-memory/migration-io.js +0 -199
  29. package/dist/local-memory/migration-plan.js +0 -278
  30. package/dist/local-memory/migration-schema.js +0 -55
  31. package/dist/local-memory/migration-store.js +0 -424
  32. package/dist/local-memory/migration-tool-entry.js +0 -21
  33. package/dist/local-memory/storage-format.js +0 -152
  34. package/dist/memory-migration-controller.js +0 -84
  35. package/dist/memory-migration-inspect.js +0 -74
  36. package/dist/memory-migration-journal.js +0 -14
  37. package/dist/memory-migration-protocol.js +0 -14
  38. package/dist/memory-migration-runtime.js +0 -305
@@ -6,7 +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
9
  const StatusChangeSchema = z.object({
11
10
  memoryId: LocalMemorySingleLineText,
12
11
  expectedRevision: z.number().int().positive(),
@@ -91,14 +90,12 @@ function parseCards(section) {
91
90
  });
92
91
  }
93
92
  export function parseLocalMemoryIndex(memory) {
94
- if (detectLocalMemoryStorageVersion(memory) === 2)
95
- return parseFullMemoryIndex(memory);
96
93
  const section = indexSection(memory);
97
94
  const generated = GENERATED_INDEX_PATTERN.exec(section)?.[0];
98
95
  return parseCards(generated ?? section);
99
96
  }
100
97
  function parseUnmarkedMemoryIndex(memory) {
101
- const section = indexSection(memory).replace(LOCAL_MEMORY_GENERATED_INDEX_PATTERN, "");
98
+ const section = indexSection(memory).replace(GENERATED_INDEX_PATTERN, "");
102
99
  return parseCards(section);
103
100
  }
104
101
  function replaceGeneratedIndex(memory, entries) {
@@ -125,36 +122,11 @@ function noteSection(id, revision, title, body) {
125
122
  }
126
123
  function notePattern(id) {
127
124
  const escaped = id.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
128
- 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");
129
- }
130
- function uniqueNoteSection(content, id) {
131
- const escaped = id.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
132
- const begins = [...content.matchAll(new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:BEGIN\\s+id=${escaped}(?=\\s|-->)`, "gu"))];
133
- const ends = [...content.matchAll(new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:END\\s+id=${escaped}\\s*-->`, "gu"))];
134
- if (begins.length === 0 && ends.length === 0)
135
- return null;
136
- const section = notePattern(id).exec(content)?.[0];
137
- if (begins.length !== 1 || ends.length !== 1 || section === undefined
138
- || (section.match(/NOWWORK:MEMORY_ENTRY:(?:BEGIN|END)/gu) ?? []).length !== 2) {
139
- throw new Error("local_memory_note_anchor_invalid");
140
- }
141
- 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");
142
126
  }
143
127
  function replaceNote(content, id, replacement) {
144
128
  const pattern = notePattern(id);
145
- return uniqueNoteSection(content, id) !== null ? content.replace(pattern, () => replacement) : null;
146
- }
147
- function updateNoteRevision(content, id, revision) {
148
- const section = uniqueNoteSection(content, id);
149
- const beginning = `<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${id} `;
150
- const ending = `<!-- NOWWORK:MEMORY_ENTRY:END id=${id} -->`;
151
- if (section === null || content.split(beginning).length !== 2 || content.split(ending).length !== 2) {
152
- throw new Error("local_memory_note_anchor_missing");
153
- }
154
- 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}`);
155
- if (!updated.startsWith(`${beginning}revision=${revision} -->`))
156
- throw new Error("local_memory_note_anchor_missing");
157
- return content.replace(notePattern(id), () => updated);
129
+ return pattern.test(content) ? content.replace(pattern, replacement) : null;
158
130
  }
159
131
  function validateCandidate(result) {
160
132
  if (result.outcome === "no_change" && (result.changes.length > 0 || result.statusChanges.length > 0)) {
@@ -227,14 +199,14 @@ function detailCategoryFromFile(file) {
227
199
  return DETAIL_CATEGORIES.has(category) ? category : null;
228
200
  }
229
201
  function readNoteBody(content, id) {
230
- const section = uniqueNoteSection(content, id);
231
- if (section === null)
202
+ const begin = content.indexOf(`<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${id}`);
203
+ if (begin < 0)
232
204
  return null;
233
- const bodyStart = section.indexOf("\n\n");
234
- 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);
235
207
  if (bodyStart < 0 || bodyEnd < 0)
236
208
  return null;
237
- return section.slice(bodyStart + 2, bodyEnd).trim();
209
+ return content.slice(bodyStart + 2, bodyEnd).trim();
238
210
  }
239
211
  function archivedChange(entry, detailBody) {
240
212
  const detailCategory = detailCategoryFromFile(entry.file);
@@ -277,12 +249,7 @@ async function readNote(path) {
277
249
  const stat = await lstat(path);
278
250
  if (!stat.isFile() || stat.isSymbolicLink())
279
251
  throw new Error("local_memory_note_path_invalid");
280
- if (stat.size > LOCAL_MEMORY_NOTE_BYTE_CAP)
281
- throw new Error("local_memory_note_byte_limit");
282
- const content = await readFile(path, "utf8");
283
- if (Buffer.byteLength(content, "utf8") > LOCAL_MEMORY_NOTE_BYTE_CAP)
284
- throw new Error("local_memory_note_byte_limit");
285
- return content;
252
+ return await readFile(path, "utf8");
286
253
  }
287
254
  catch (error) {
288
255
  if (error.code === "ENOENT")
@@ -302,11 +269,6 @@ async function atomicWrite(path, content) {
302
269
  }
303
270
  }
304
271
  function projectIndex(memory, entries, protectedIds) {
305
- if (detectLocalMemoryStorageVersion(memory) === 2) {
306
- const updated = replaceFullMemoryIndex(memory, entries);
307
- selectLocalMemoryPreview(updated);
308
- return { memory: updated, omittedIds: [], reason: null };
309
- }
310
272
  const empty = replaceGeneratedIndex(memory, []);
311
273
  if (codePointLength(empty) > LOCAL_MEMORY_TOTAL_CODE_POINT_CAP)
312
274
  throw new Error("local_memory_memory_over_budget");
@@ -349,12 +311,7 @@ export async function applyLocalMemoryResult(input) {
349
311
  const memoryPath = join(input.agentDir, "MEMORY.md");
350
312
  await assertWritablePaths(input.agentDir, memoryPath);
351
313
  return withApplyLease(input.agentDir, async () => {
352
- const memoryStat = await lstat(memoryPath);
353
- if (memoryStat.size > 16 * 1024 * 1024)
354
- throw new Error("local_memory_storage_byte_limit");
355
314
  const memory = await readFile(memoryPath, "utf8");
356
- assertLocalMemoryStorageSize(memory);
357
- const fullStorage = detectLocalMemoryStorageVersion(memory) === 2;
358
315
  if (parseUnmarkedMemoryIndex(memory).length > 0)
359
316
  throw new Error("local_memory_legacy_index_requires_migration");
360
317
  let entries = [...parseLocalMemoryIndex(memory)];
@@ -385,26 +342,6 @@ export async function applyLocalMemoryResult(input) {
385
342
  const pendingCandidates = [];
386
343
  const resolvedPendingIds = new Set();
387
344
  const noteUpdates = new Map();
388
- const noteCache = new Map();
389
- let totalReadBytes = Buffer.byteLength(memory, "utf8");
390
- const readStoredNote = async (path) => {
391
- const cached = noteUpdates.get(path) ?? noteCache.get(path);
392
- if (cached !== undefined)
393
- return cached;
394
- const stat = await lstat(path).catch((error) => {
395
- if (error.code === "ENOENT")
396
- return null;
397
- throw error;
398
- });
399
- if (stat !== null && totalReadBytes + stat.size > LOCAL_MEMORY_READ_BYTE_CAP)
400
- throw new Error("local_memory_read_byte_limit");
401
- const content = await readNote(path);
402
- totalReadBytes += Buffer.byteLength(content, "utf8");
403
- if (totalReadBytes > LOCAL_MEMORY_READ_BYTE_CAP)
404
- throw new Error("local_memory_read_byte_limit");
405
- noteCache.set(path, content);
406
- return content;
407
- };
408
345
  let allowActiveEviction = false;
409
346
  const randomId = input.randomId ?? randomUUID;
410
347
  const executionId = input.executionId ?? "unknown";
@@ -446,21 +383,10 @@ export async function applyLocalMemoryResult(input) {
446
383
  if (existingCreate !== undefined && !matchesCreateCandidate(existingCreate, change)) {
447
384
  throw new Error("local_memory_duplicate_candidate");
448
385
  }
449
- if (existingCreate === undefined && entries.some((entry) => (!fullStorage || entry.status === "active" || entry.status === "pinned")
450
- && (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))) {
451
388
  throw new Error("local_memory_duplicate_candidate");
452
389
  }
453
- if (existingCreate !== undefined) {
454
- if (existingCreate.revision !== 1 || existingCreate.status !== "active") {
455
- throw new Error("local_memory_create_replay_conflict");
456
- }
457
- const stored = await readStoredNote(resolve(input.agentDir, existingCreate.file));
458
- if (uniqueNoteSection(stored, id)?.trimEnd() !== noteSection(id, 1, change.title, change.detailBody).trimEnd()) {
459
- throw new Error("local_memory_create_replay_conflict");
460
- }
461
- // A proven duplicate is a no-op; it must never rewrite a newer body or status.
462
- continue;
463
- }
464
390
  }
465
391
  const revision = existingCreate?.revision ?? (current === undefined ? 1 : current.revision + 1);
466
392
  const entry = existingCreate ?? entryFromChange(change, id, revision, current?.status ?? "active");
@@ -474,7 +400,7 @@ export async function applyLocalMemoryResult(input) {
474
400
  : item),
475
401
  entry,
476
402
  ];
477
- const allowEvictionForChange = !fullStorage && change.operation === "create" && change.priority === "high";
403
+ const allowEvictionForChange = change.operation === "create" && change.priority === "high";
478
404
  const protectedIds = new Set(allowEvictionForChange ? pinnedIds(entries) : activeOrPinnedIds(entries));
479
405
  if (allowEvictionForChange)
480
406
  allowActiveEviction = true;
@@ -491,7 +417,7 @@ export async function applyLocalMemoryResult(input) {
491
417
  const notePath = resolve(input.agentDir, entry.file);
492
418
  if (!isWithin(resolve(input.agentDir, "notes"), notePath))
493
419
  throw new Error("local_memory_note_path_invalid");
494
- const previous = await readStoredNote(notePath);
420
+ const previous = noteUpdates.get(notePath) ?? await readNote(notePath);
495
421
  const replacement = noteSection(id, revision, change.title, change.detailBody);
496
422
  const existingSection = replaceNote(previous, id, replacement);
497
423
  const next = existingSection ?? (current === undefined
@@ -509,7 +435,7 @@ export async function applyLocalMemoryResult(input) {
509
435
  // Pending create candidates are durable overflow, not dead ends. Promote only
510
436
  // when the current catalog has room; supersede/update candidates stay pending
511
437
  // until a fresh revision-aware decision can safely replay them.
512
- const pendingCandidatesOnDisk = fullStorage ? [] : await readPendingMemoryCandidates({ agentDir: input.agentDir }).catch((error) => {
438
+ const pendingCandidatesOnDisk = await readPendingMemoryCandidates({ agentDir: input.agentDir }).catch((error) => {
513
439
  if (!(error instanceof Error) || error.message !== "memory_pending_directory_over_budget")
514
440
  throw error;
515
441
  // 自动提升是可选工作,目录查询预算不应阻断本轮已验证的更新或腾位操作。
@@ -534,7 +460,7 @@ export async function applyLocalMemoryResult(input) {
534
460
  || normalize(entry.summary) === normalize(change.summary)));
535
461
  if (duplicate !== undefined) {
536
462
  const duplicatePath = resolve(input.agentDir, duplicate.file);
537
- const duplicateBody = readNoteBody(await readStoredNote(duplicatePath), duplicate.id);
463
+ const duplicateBody = readNoteBody(noteUpdates.get(duplicatePath) ?? await readNote(duplicatePath), duplicate.id);
538
464
  if (duplicateBody === change.detailBody
539
465
  || duplicateBody === unwrappedNoteBody(change.title, change.detailBody).trim()) {
540
466
  resolvedPendingIds.add(pending.pendingId);
@@ -552,7 +478,7 @@ export async function applyLocalMemoryResult(input) {
552
478
  const notePath = resolve(input.agentDir, entry.file);
553
479
  if (!isWithin(resolve(input.agentDir, "notes"), notePath))
554
480
  throw new Error("local_memory_note_path_invalid");
555
- const previous = await readStoredNote(notePath);
481
+ const previous = noteUpdates.get(notePath) ?? await readNote(notePath);
556
482
  const replacement = noteSection(id, 1, change.title, change.detailBody);
557
483
  const existingSection = replaceNote(previous, id, replacement);
558
484
  const next = existingSection ?? `${previous.trimEnd()}${previous.trimEnd() ? "\n\n" : ""}${replacement}`;
@@ -561,17 +487,6 @@ export async function applyLocalMemoryResult(input) {
561
487
  createdIds.push(id);
562
488
  resolvedPendingIds.add(pending.pendingId);
563
489
  }
564
- if (fullStorage) {
565
- const changedMetadataIds = new Set([
566
- ...statusChangedIds,
567
- ...input.result.changes.flatMap(change => change.supersedesMemoryId === null ? [] : [change.supersedesMemoryId]),
568
- ]);
569
- for (const id of changedMetadataIds) {
570
- const entry = entries.find(item => item.id === id);
571
- const notePath = resolve(input.agentDir, entry.file);
572
- noteUpdates.set(notePath, updateNoteRevision(await readStoredNote(notePath), id, entry.revision));
573
- }
574
- }
575
490
  let finalProjection = projectIndex(memory, entries, allowActiveEviction ? pinnedIds(entries) : activeOrPinnedIds(entries));
576
491
  if (finalProjection.reason !== null
577
492
  && (!allowActiveEviction || finalProjection.reason === "index_over_budget_pinned")) {
@@ -583,7 +498,7 @@ export async function applyLocalMemoryResult(input) {
583
498
  .filter((entry) => entry?.status === "active");
584
499
  for (const entry of omittedActiveEntries) {
585
500
  const notePath = resolve(input.agentDir, entry.file);
586
- const content = await readStoredNote(notePath);
501
+ const content = noteUpdates.get(notePath) ?? await readNote(notePath);
587
502
  const detailBody = readNoteBody(content, entry.id);
588
503
  const change = detailBody === null ? null : archivedChange(entry, detailBody);
589
504
  if (change === null)
@@ -604,12 +519,6 @@ export async function applyLocalMemoryResult(input) {
604
519
  if (finalProjection.reason !== null)
605
520
  throw new Error(finalProjection.reason);
606
521
  }
607
- // Complete every resource check before the first persistent write.
608
- assertLocalMemoryStorageSize(finalProjection.memory);
609
- for (const content of noteUpdates.values()) {
610
- if (Buffer.byteLength(content, "utf8") > LOCAL_MEMORY_NOTE_BYTE_CAP)
611
- throw new Error("local_memory_note_byte_limit");
612
- }
613
522
  for (const candidate of pendingCandidates)
614
523
  await persistPendingMemoryCandidate({ agentDir: input.agentDir, candidate });
615
524
  for (const [path, content] of noteUpdates)
@@ -1,4 +1,4 @@
1
- import { delimiter, join } from "node:path";
1
+ import { delimiter, join, resolve } from "node:path";
2
2
  import { environmentForExecution } from "./agent-environments.js";
3
3
  import { augmentedPath } from "./runtime-path.js";
4
4
  import { sanitizeEnvVars } from "./runtime-env.js";
@@ -10,6 +10,7 @@ export function buildLocalRuntimeEnvironment(context) {
10
10
  const { input, workspace, executionWorkspace, inheritedEnv, providerConfig, crewHome, contextThreadId, nativeThreadContextPath, reportContext, runtime } = context;
11
11
  const agentEnvironment = sanitizeEnvVars(providerConfig.environmentPlan === undefined ? providerConfig.envVars
12
12
  : environmentForExecution(providerConfig.environmentPlan, workspace.dir, input.projectContext?.primary?.root ?? executionWorkspace.runDir, input.projectContext?.primary?.projectId));
13
+ const boundImDecisionFile = input.launch.systemEnv?.CREW_BOUND_IM_DECISION_FILE;
13
14
  return mergeRuntimeEnvironments([
14
15
  inheritedEnv, agentEnvironment,
15
16
  input.trainingProjectContext === undefined ? {} : machinePackageManagerEnv(inheritedEnv),
@@ -35,6 +36,10 @@ export function buildLocalRuntimeEnvironment(context) {
35
36
  QA_REPORT_DELIVERY_CONTEXT: JSON.stringify(reportContext),
36
37
  CREW_TASK_DIR: executionWorkspace.runDir,
37
38
  CREW_TASK_LOG: executionWorkspace.workLogPath,
39
+ // CLI 可以从 Agent 主目录或项目目录调用,标记始终写回完成阶段读取的任务目录。
40
+ ...(boundImDecisionFile ? {
41
+ CREW_BOUND_IM_DECISION_FILE: resolve(executionWorkspace.runDir, boundImDecisionFile),
42
+ } : {}),
38
43
  ...(input.wakeMessageId ? { CREW_WAKE_MESSAGE_ID: input.wakeMessageId } : {}),
39
44
  XDG_CONFIG_HOME: join(workspace.homeDir, ".config"),
40
45
  XDG_DATA_HOME: join(workspace.homeDir, ".local", "share"),
@@ -31,6 +31,7 @@ export function createMachineEnvironmentProvider(options = {}) {
31
31
  let lastFailureTime = -Infinity;
32
32
  let stopped = false;
33
33
  let unsafeCleanup = false;
34
+ let isCleanupComplete;
34
35
  let flight;
35
36
  let controller;
36
37
  let captureEpoch = 0;
@@ -50,8 +51,19 @@ export function createMachineEnvironmentProvider(options = {}) {
50
51
  return module.captureWindowsMachineEnvironment(request);
51
52
  });
52
53
  const refresh = async (force, signal) => {
53
- if (!selected.source || stopped || unsafeCleanup)
54
+ if (!selected.source || stopped)
54
55
  return snapshot;
56
+ if (unsafeCleanup) {
57
+ try {
58
+ if (!isCleanupComplete?.())
59
+ return snapshot;
60
+ }
61
+ catch {
62
+ return snapshot;
63
+ }
64
+ unsafeCleanup = false;
65
+ isCleanupComplete = undefined;
66
+ }
55
67
  if (!force && now() - lastFailureTime < (options.backoffMs ?? 30_000))
56
68
  return snapshot;
57
69
  const attemptedAt = new Date(now()).toISOString();
@@ -64,7 +76,7 @@ export function createMachineEnvironmentProvider(options = {}) {
64
76
  return snapshot;
65
77
  publish(snapshot.env, { state: "loading", attemptedAt, errorCode: null });
66
78
  capturing = true;
67
- const deadline = Date.now() + (options.captureTimeoutMs ?? (selected.source === "windows-registry" ? 8_000 : 5_000));
79
+ const deadline = Date.now() + (options.captureTimeoutMs ?? (selected.source === "windows-registry" ? 8_000 : 10_000));
68
80
  const capture = selected.source === "windows-registry" ? captureWindows : options.captureShell ?? captureShellMachineEnvironment;
69
81
  for (let attempt = 0; attempt < 2; attempt++) {
70
82
  try {
@@ -111,8 +123,10 @@ export function createMachineEnvironmentProvider(options = {}) {
111
123
  }
112
124
  catch (error) {
113
125
  const errorCode = error instanceof MachineEnvironmentCaptureError ? error.code : selected.source === "windows-registry" ? "windows_environment_unavailable" : "shell_capture_failed";
114
- if (errorCode === "shell_cleanup_failed")
126
+ if (errorCode === "shell_cleanup_failed") {
115
127
  unsafeCleanup = true;
128
+ isCleanupComplete = error instanceof MachineEnvironmentCaptureError ? error.isCleanupComplete : undefined;
129
+ }
116
130
  lastFailureTime = now();
117
131
  return publish(lastGood?.env ?? baseline, { state: lastGood ? "stale" : "fallback", errorCode, attemptedAt });
118
132
  }
@@ -129,7 +143,7 @@ export function createMachineEnvironmentProvider(options = {}) {
129
143
  }
130
144
  return flight;
131
145
  }
132
- if (stopped || unsafeCleanup || selected.source === null)
146
+ if (stopped || selected.source === null)
133
147
  return Promise.resolve(snapshot);
134
148
  controller = new AbortController();
135
149
  flight = refresh(acquireOptions?.force === true, controller.signal).finally(() => { flight = undefined; controller = undefined; });
@@ -1,8 +1,10 @@
1
1
  export class MachineEnvironmentCaptureError extends Error {
2
2
  code;
3
- constructor(code) {
3
+ isCleanupComplete;
4
+ constructor(code, isCleanupComplete) {
4
5
  super(code);
5
6
  this.code = code;
7
+ this.isCleanupComplete = isCleanupComplete;
6
8
  this.name = "MachineEnvironmentCaptureError";
7
9
  }
8
10
  }
@@ -44,7 +44,11 @@ export async function runMachineEnvironmentProcess(options) {
44
44
  stdio[collectFd] = "pipe";
45
45
  if (options.metadataFd !== undefined)
46
46
  stdio[options.metadataFd] = "pipe";
47
- const child = spawn(options.file, [...options.args], { cwd: options.cwd, env: options.env, detached: true, stdio });
47
+ // launchd Background 的 I/O 策略会让 shell 初始化停滞;只调整短命采集子进程。
48
+ // taskpolicy 原地 exec,保持同一 PID/进程组、环境和私有管道。
49
+ const file = process.platform === "darwin" ? "/usr/sbin/taskpolicy" : options.file;
50
+ const args = process.platform === "darwin" ? ["-a", "--", options.file, ...options.args] : [...options.args];
51
+ const child = spawn(file, args, { cwd: options.cwd, env: options.env, detached: true, stdio });
48
52
  const chunks = [];
49
53
  let size = 0;
50
54
  const metadata = [];
@@ -102,6 +106,22 @@ export async function runMachineEnvironmentProcess(options) {
102
106
  try {
103
107
  await cleanupOnce();
104
108
  }
109
+ catch (error) {
110
+ const pid = child.pid;
111
+ if (error instanceof MachineEnvironmentCaptureError && error.code === "shell_cleanup_failed" && pid !== undefined) {
112
+ // 后续只验证旧进程组消失,不对可能复用的 PID 再发送终止信号。
113
+ throw new MachineEnvironmentCaptureError("shell_cleanup_failed", () => {
114
+ try {
115
+ process.kill(-pid, 0);
116
+ return false;
117
+ }
118
+ catch (cause) {
119
+ return cause.code === "ESRCH";
120
+ }
121
+ });
122
+ }
123
+ throw error;
124
+ }
105
125
  finally {
106
126
  for (const stream of child.stdio)
107
127
  stream?.destroy();
@@ -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"
@@ -9,7 +9,6 @@ import { z } from "zod";
9
9
  import { isInternalEnvironmentPath } from "./workspace-fs.js";
10
10
  import { opaqueKey, safeKey } from "./workspace.js";
11
11
  import { AgentHandleSchema } from "./execution-protocol.js";
12
- import { withMemoryAccess } from "./local-memory/migration-access.js";
13
12
  const MAX_BYTES = 256 * 1024;
14
13
  const MAX_BINARY_BYTES = 8 * 1024 * 1024;
15
14
  const MAX_CONCURRENT_READS = 4;
@@ -232,16 +231,7 @@ export async function readSharedMessageFile(agentsRoot, rawRequest, registry) {
232
231
  throw new SharedFileError("file_preview_busy");
233
232
  activeReads++;
234
233
  try {
235
- const candidate = await resolveCandidate(agentsRoot, parsed.data, registry);
236
- const agentRoot = await realpath(resolve(agentsRoot, parsed.data.handle)).catch((error) => {
237
- if (error.code === "ENOENT")
238
- return undefined;
239
- throw error;
240
- });
241
- // 记忆保护只用于该Agent根内资料,独立项目文件仍按原执行授权读取。
242
- return await (agentRoot && inside(agentRoot, candidate.path)
243
- ? withMemoryAccess(agentRoot, () => readCandidate(candidate))
244
- : readCandidate(candidate));
234
+ return await readCandidate(await resolveCandidate(agentsRoot, parsed.data, registry));
245
235
  }
246
236
  catch (error) {
247
237
  if (error instanceof SharedFileError)
package/dist/prompt.js CHANGED
@@ -5,7 +5,6 @@
5
5
  * claim-before-work、freshness/draft、"做完所有事再停"、inbox notice 语义、
6
6
  * 分层记忆与压缩安全、协作礼仪。措辞为本项目原创。
7
7
  */
8
- import { detectLocalMemoryStorageVersion, selectLocalMemoryPreview } from "./local-memory/storage-format.js";
9
8
  /** work-log 注入上限(字符):防冷启动注入随文件膨胀。超限保头(目标/结论)+尾(最近进展),
10
9
  * 截中段并提示读全文——全文仍在盘上,只限"默认推送量"不限"可获取量"。纯函数。 */
11
10
  export const WORKLOG_INJECT_CAP = 8000;
@@ -68,10 +67,7 @@ export function capWorkLogForInject(workLog, cap = WORKLOG_INJECT_CAP) {
68
67
  const tail = workLog.slice(-WORKLOG_TAIL);
69
68
  return `${head}\n…(work-log 过长已截去中段,全文用 Read 读 $CREW_TASK_LOG)\n${tail}`;
70
69
  }
71
- export function capMemoryForInject(memory, cap = MEMORY_INJECT_CAP, maxBytes = Number.POSITIVE_INFINITY, query = "") {
72
- if (detectLocalMemoryStorageVersion(memory) === 2) {
73
- return selectLocalMemoryPreview(memory, query, { maxCodePoints: cap, maxBytes }).memory;
74
- }
70
+ export function capMemoryForInject(memory, cap = MEMORY_INJECT_CAP, maxBytes = Number.POSITIVE_INFINITY) {
75
71
  if (memory.length <= cap && Buffer.byteLength(memory, "utf8") <= maxBytes)
76
72
  return memory;
77
73
  const notice = "[MEMORY.md preview truncated; use crew-memory search/read for more.]\n";
package/dist/runner.js CHANGED
@@ -80,7 +80,7 @@ async function runAgentWithMachineEnvironment(config, input, onActivity = defaul
80
80
  ? { scheduledExternalNotificationPolicy: input.scheduled.externalNotificationPolicy }
81
81
  : {}),
82
82
  ...(input.wakeOrigin ? { wakeOrigin: input.wakeOrigin } : {}),
83
- memory: capMemoryForInject(workspace.memory, undefined, undefined, baseWake),
83
+ memory: capMemoryForInject(workspace.memory),
84
84
  ...(resuming ? {} : { workLog: capWorkLogForInject(workspace.workLog) }),
85
85
  collaborationToolsEnabled: config.codexCollaborationTools && runtime === "codex",
86
86
  localMemoryV2Enabled: workspace.localMemoryV2Enabled ?? localMemoryV2Enabled,
package/dist/serve.js CHANGED
@@ -14,7 +14,6 @@ import { reconcileExecutionJournal } from "./execution-recovery.js";
14
14
  import { initReleaseSlog, reportDaemonConnectionClose, reportReleaseError } from "./release-channel-guard.js";
15
15
  import { handleWorkspaceControlRequest } from "./workspace-control-handler.js";
16
16
  import { inspectRaftWorkspace, importRaftWorkspace } from "./workspace-import.js";
17
- import { withMemoryAccess } from "./local-memory/migration-access.js";
18
17
  import { normalizeScheduledContext } from "./scheduled-report.js";
19
18
  import { formatDaemonLogLine } from "./log-format.js";
20
19
  import { reportAgentRunComplete } from "./scheduled-run-report.js";
@@ -51,8 +50,6 @@ import { probeRuntimeHealth } from "./runtime-health.js";
51
50
  import { createServeMarketplace } from "./marketplace-serve.js";
52
51
  import { buildControlPlaneUrl } from "./control-plane-url.js";
53
52
  import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
54
- import { createMemoryMigrationController } from "./memory-migration-controller.js";
55
- import { createMemoryMigrationJournal, replayExecutionJournals } from "./memory-migration-journal.js";
56
53
  import { bindStorageServerIdentity, createStorageIdentityAdmission, } from "./storage-serve-adapter.js";
57
54
  export { buildControlPlaneUrl } from "./control-plane-url.js";
58
55
  // normalize.ts 的活动种类 → activity 枚举
@@ -74,8 +71,6 @@ export function serve(config, opts = {}) {
74
71
  let reconnectTimer = null;
75
72
  let stopPromise = null;
76
73
  let executionJournal;
77
- let migrationExecutionJournal;
78
- let migrationJournalReady = false;
79
74
  const injectedExecutionJournal = opts.execution?.journal;
80
75
  const agentMemory = opts.execution?.dependencies?.agentMemory
81
76
  ?? (config.agentMemory === null ? undefined : createAgentMemoryBridge(config.agentMemory));
@@ -90,15 +85,6 @@ export function serve(config, opts = {}) {
90
85
  startupGapMs: config.executionLimits.startupGapMs,
91
86
  });
92
87
  const localSlots = createSharedSlotManager(config.executionLimits);
93
- const memoryMigrations = createMemoryMigrationController({ config, coordinator: hostCoordinator,
94
- isAdmissionClosed: () => stopped || storageIdentityAdmission.isAdmissionClosed(),
95
- journal: () => { if (!migrationJournalReady)
96
- throw new Error("memory_migration_journal_unavailable"); return migrationExecutionJournal; }, send: frame => {
97
- if (frame.type === "execution:completed")
98
- completionRetransmitter.track(frame);
99
- else if (ws?.readyState === WebSocket.OPEN)
100
- ws.send(JSON.stringify(frame));
101
- } });
102
88
  const sharedSlots = hostCoordinatedSlotManager(localSlots, hostCoordinator);
103
89
  const concurrencyTelemetry = createExecutionConcurrencyTelemetry(sharedSlots, {
104
90
  ...(opts.execution?.concurrencySnapshotIntervalMs === undefined
@@ -246,7 +232,7 @@ export function serve(config, opts = {}) {
246
232
  return null;
247
233
  };
248
234
  const sendSnapshot = async (reqId) => {
249
- const entries = (await replayExecutionJournals(executionJournal, migrationExecutionJournal)).map(snapshotEntry).filter((entry) => entry !== null);
235
+ const entries = (await executionJournal.replay()).map(snapshotEntry).filter((entry) => entry !== null);
250
236
  safeExecutionSend(ExecutionSnapshotSchema.parse({
251
237
  type: "execution:snapshot", protocolVersion: 1, reqId, entries,
252
238
  }));
@@ -327,12 +313,6 @@ export function serve(config, opts = {}) {
327
313
  machineRuntime.open(openedSocket, { capabilities: capabilities.machineHello, beforePublish: Promise.resolve(), connectionId });
328
314
  opts.onOpen?.(ws);
329
315
  void sendSnapshot(`reconnect-${randomUUID()}`);
330
- // 普通回执不等待维护日志IO;两类记录分别重放到同一发送器。
331
- void migrationExecutionJournal.replay().then(entries => {
332
- for (const entry of entries)
333
- if (entry.completion && !entry.completionAcknowledged)
334
- completionRetransmitter.track(entry.completion);
335
- }).catch(() => dslog("memory_migration.journal_unavailable", "维护回执重放失败,普通回执继续", { level: "ERROR" }));
336
316
  void executionJournal.replay()
337
317
  .then((entries) => {
338
318
  for (const entry of entries) {
@@ -407,8 +387,7 @@ export function serve(config, opts = {}) {
407
387
  return;
408
388
  if (frame.type === "execution:completion-ack") {
409
389
  try {
410
- const journal = await executionJournal.get(frame.executionId) ? executionJournal : migrationExecutionJournal;
411
- await journal.acknowledgeCompletion(frame.executionId);
390
+ await executionJournal.acknowledgeCompletion(frame.executionId);
412
391
  const attempts = completionRetransmitter.acknowledge(frame.executionId);
413
392
  dslog("execution.completion_acknowledged", "execution completion ACK 已持久化", {
414
393
  execution_id: frame.executionId,
@@ -434,8 +413,6 @@ export function serve(config, opts = {}) {
434
413
  return;
435
414
  }
436
415
  if (frame.type === "execution:cancel") {
437
- if (memoryMigrations.cancel(frame.executionId))
438
- return;
439
416
  if (!knownExecutionHashes.has(frame.executionId)
440
417
  && !executionRuns.has(frame.executionId)
441
418
  && await executionJournal.get(frame.executionId) === null)
@@ -758,8 +735,6 @@ export function serve(config, opts = {}) {
758
735
  // ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
759
736
  const r = msg;
760
737
  setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
761
- if (r.workspaceId && r.machineId)
762
- memoryMigrations.setIdentity({ workspaceId: r.workspaceId, machineId: r.machineId });
763
738
  if (opts.storageRuntime !== undefined && typeof r.workspaceId === "string" && typeof r.machineId === "string") {
764
739
  const bindingSocket = ws;
765
740
  bindStorageServerIdentity(opts.storageRuntime, { workspaceId: r.workspaceId, machineId: r.machineId }, {
@@ -826,8 +801,6 @@ export function serve(config, opts = {}) {
826
801
  }
827
802
  catch { /* server 会按请求超时处理 */ } }))
828
803
  return;
829
- if (await memoryMigrations.handle(msg))
830
- return;
831
804
  if (await agentAbilityRuntime.tryHandleControlMessage(msg, serverCapabilities, (frame) => ws?.send(JSON.stringify(frame))))
832
805
  return;
833
806
  // 导入 raft agent 工作区:inspect 反填 name/description;import 复制用户内容
@@ -842,7 +815,7 @@ export function serve(config, opts = {}) {
842
815
  try {
843
816
  const data2 = req.type === "raft:inspect"
844
817
  ? await inspectRaftWorkspace(req.path)
845
- : await withMemoryAccess(join(config.agentsRoot, req.handle), () => importRaftWorkspace(req.path, join(config.agentsRoot, req.handle)), { create: true });
818
+ : await importRaftWorkspace(req.path, join(config.agentsRoot, req.handle));
846
819
  if (req.type === "raft:import") {
847
820
  log(`📦 已导入 raft 工作区 → ${req.handle}: 复制 ${data2.copied.length} 项`);
848
821
  }
@@ -1286,9 +1259,6 @@ export function serve(config, opts = {}) {
1286
1259
  writeStderr: (line) => process.stderr.write(line),
1287
1260
  });
1288
1261
  await runProjectSkillStartupCleanup(config.agentsRoot, executionJournal, dslog);
1289
- migrationExecutionJournal = createMemoryMigrationJournal(config.agentsRoot);
1290
- await migrationExecutionJournal.reconcileAfterRestart().then(() => { migrationJournalReady = true; })
1291
- .catch(() => dslog("memory_migration.journal_unavailable", "迁移日志恢复失败,普通执行继续,迁移请求单独报错", { level: "ERROR" }));
1292
1262
  await connect();
1293
1263
  })();
1294
1264
  const stop = () => {
@@ -1306,7 +1276,6 @@ export function serve(config, opts = {}) {
1306
1276
  const webSocketClosed = closeWebSocketWithinDeadline(ws, deadline.signal);
1307
1277
  const pending = [
1308
1278
  machineRuntime.stop(),
1309
- memoryMigrations.stop(),
1310
1279
  webSocketClosed,
1311
1280
  updateController.drain(),
1312
1281
  marketplace.stop(),
@@ -1325,8 +1294,6 @@ export function serve(config, opts = {}) {
1325
1294
  if (executionJournal !== undefined) {
1326
1295
  await executionJournal.close({ signal: deadline.signal });
1327
1296
  }
1328
- if (migrationExecutionJournal !== undefined)
1329
- await migrationExecutionJournal.close({ signal: deadline.signal });
1330
1297
  }
1331
1298
  finally {
1332
1299
  deadline.dispose();
package/dist/token.js CHANGED
@@ -7,7 +7,6 @@ export async function mintAgentToken(serverUrl, machineToken, handle, displayNam
7
7
  },
8
8
  // 把本轮唤醒的线程根钉到 per-launch 令牌:服务端据此让 task/消息默认绑/回当前线程。
9
9
  body: JSON.stringify({
10
- memoryStateGuardVersion: 1,
11
10
  handle,
12
11
  ...(displayName ? { displayName } : {}),
13
12
  ...(options.wakeThreadRoot ? { wakeThreadRoot: options.wakeThreadRoot } : {}),
@@ -1,22 +1,9 @@
1
1
  /** 控制面文件/技能查看的按需处理,独立于执行生命周期。 */
2
- import { lstat } from "node:fs/promises";
3
- import { join, resolve } from "node:path";
2
+ import { join } from "node:path";
4
3
  import { readSharedMessageFile } from "./message-file-preview.js";
5
4
  import { listWorkspace, readWorkspaceFile } from "./workspace-fs.js";
6
5
  import { listSkills } from "./skills.js";
7
6
  import { listRuntimeModels } from "./list-models.js";
8
- import { withMemoryAccess } from "./local-memory/migration-access.js";
9
- async function listProtectedWorkspace(root, path) {
10
- try {
11
- await lstat(root);
12
- }
13
- catch (error) {
14
- if (error.code !== "ENOENT")
15
- throw error;
16
- return { root: resolve(root), path: path.replace(/^\/+/, ""), entries: [] };
17
- }
18
- return withMemoryAccess(root, () => listWorkspace(root, path));
19
- }
20
7
  export async function handleWorkspaceControlRequest(raw, agentsRoot, registry, models = listRuntimeModels) {
21
8
  if (!raw || typeof raw !== "object")
22
9
  return null;
@@ -28,8 +15,8 @@ export async function handleWorkspaceControlRequest(raw, agentsRoot, registry, m
28
15
  try {
29
16
  const root = join(agentsRoot, request.handle);
30
17
  const data = request.type === "fs:read-shared" ? await readSharedMessageFile(agentsRoot, raw, registry)
31
- : request.type === "fs:list" ? await listProtectedWorkspace(root, request.path)
32
- : request.type === "fs:read" ? await withMemoryAccess(root, () => readWorkspaceFile(root, request.path))
18
+ : request.type === "fs:list" ? await listWorkspace(root, request.path)
19
+ : request.type === "fs:read" ? await readWorkspaceFile(root, request.path)
33
20
  : request.type === "skills:list" ? await listSkills(request.runtime)
34
21
  : { models: await models(request.runtime ?? "") };
35
22
  return { type: "fs:result", reqId: request.reqId, ok: true, data };