@nowcrew/daemon 0.6.93-beta.1 → 0.6.93-beta.11
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.
- package/dist/agent-environment-execution.js +7 -2
- package/dist/execution-journal.js +1 -3
- package/dist/execution-protocol.js +1 -1
- package/dist/execution-runner.js +1 -3
- package/dist/local-execution-prompt.js +3 -8
- package/dist/local-executor.js +35 -64
- package/dist/local-memory/index-budget.js +3 -9
- package/dist/local-memory/inventory-cli.js +1 -23
- package/dist/local-memory/inventory-source.js +8 -15
- package/dist/local-memory/prompt.js +4 -20
- package/dist/local-memory/read-cli.js +77 -75
- package/dist/local-memory/rollout.js +8 -54
- package/dist/local-memory/runtime.js +13 -148
- package/dist/local-memory/write.js +19 -125
- package/dist/local-memory-diagnostics.js +2 -41
- package/dist/local-memory-telemetry.js +2 -19
- package/dist/machine-info.js +0 -3
- package/dist/memory-prune-diagnostics.js +4 -60
- package/dist/message-file-preview.js +1 -11
- package/dist/prompt.js +1 -5
- package/dist/runner.js +1 -1
- package/dist/serve.js +3 -36
- package/dist/token.js +0 -1
- package/dist/workspace-control-handler.js +3 -16
- package/dist/workspace.js +2 -10
- package/package.json +1 -1
- package/dist/local-memory/execution-workspace.js +0 -55
- package/dist/local-memory/full-index-search.js +0 -47
- package/dist/local-memory/initialization-identity.js +0 -23
- package/dist/local-memory/legacy-adapter.js +0 -233
- package/dist/local-memory/migration-access.js +0 -184
- package/dist/local-memory/migration-cli.js +0 -54
- package/dist/local-memory/migration-io.js +0 -199
- package/dist/local-memory/migration-plan.js +0 -283
- package/dist/local-memory/migration-schema.js +0 -55
- package/dist/local-memory/migration-store.js +0 -429
- package/dist/local-memory/migration-tool-entry.js +0 -21
- package/dist/local-memory/storage-format.js +0 -152
- package/dist/memory-migration-controller.js +0 -84
- package/dist/memory-migration-inspect.js +0 -75
- package/dist/memory-migration-journal.js +0 -14
- package/dist/memory-migration-protocol.js +0 -14
- 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(
|
|
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}
|
|
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
|
|
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
|
|
232
|
-
if (
|
|
202
|
+
const begin = content.indexOf(`<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${id}`);
|
|
203
|
+
if (begin < 0)
|
|
233
204
|
return null;
|
|
234
|
-
const bodyStart =
|
|
235
|
-
const bodyEnd =
|
|
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
|
|
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
|
-
|
|
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");
|
|
@@ -361,15 +310,8 @@ export async function applyLocalMemoryResult(input) {
|
|
|
361
310
|
return emptyResult();
|
|
362
311
|
const memoryPath = join(input.agentDir, "MEMORY.md");
|
|
363
312
|
await assertWritablePaths(input.agentDir, memoryPath);
|
|
364
|
-
|
|
365
|
-
return withApplyLease(input.agentDir, async () => withMemoryAccess(input.agentDir, async () => {
|
|
366
|
-
await assertMemoryWriterAllowed(input.agentDir, input.writerCapabilities ?? []);
|
|
367
|
-
const memoryStat = await lstat(memoryPath);
|
|
368
|
-
if (memoryStat.size > 16 * 1024 * 1024)
|
|
369
|
-
throw new Error("local_memory_storage_byte_limit");
|
|
313
|
+
return withApplyLease(input.agentDir, async () => {
|
|
370
314
|
const memory = await readFile(memoryPath, "utf8");
|
|
371
|
-
assertLocalMemoryStorageSize(memory);
|
|
372
|
-
const fullStorage = detectLocalMemoryStorageVersion(memory) === 2;
|
|
373
315
|
if (parseUnmarkedMemoryIndex(memory).length > 0)
|
|
374
316
|
throw new Error("local_memory_legacy_index_requires_migration");
|
|
375
317
|
let entries = [...parseLocalMemoryIndex(memory)];
|
|
@@ -400,26 +342,6 @@ export async function applyLocalMemoryResult(input) {
|
|
|
400
342
|
const pendingCandidates = [];
|
|
401
343
|
const resolvedPendingIds = new Set();
|
|
402
344
|
const noteUpdates = new Map();
|
|
403
|
-
const noteCache = new Map();
|
|
404
|
-
let totalReadBytes = Buffer.byteLength(memory, "utf8");
|
|
405
|
-
const readStoredNote = async (path) => {
|
|
406
|
-
const cached = noteUpdates.get(path) ?? noteCache.get(path);
|
|
407
|
-
if (cached !== undefined)
|
|
408
|
-
return cached;
|
|
409
|
-
const stat = await lstat(path).catch((error) => {
|
|
410
|
-
if (error.code === "ENOENT")
|
|
411
|
-
return null;
|
|
412
|
-
throw error;
|
|
413
|
-
});
|
|
414
|
-
if (stat !== null && totalReadBytes + stat.size > LOCAL_MEMORY_READ_BYTE_CAP)
|
|
415
|
-
throw new Error("local_memory_read_byte_limit");
|
|
416
|
-
const content = await readNote(path);
|
|
417
|
-
totalReadBytes += Buffer.byteLength(content, "utf8");
|
|
418
|
-
if (totalReadBytes > LOCAL_MEMORY_READ_BYTE_CAP)
|
|
419
|
-
throw new Error("local_memory_read_byte_limit");
|
|
420
|
-
noteCache.set(path, content);
|
|
421
|
-
return content;
|
|
422
|
-
};
|
|
423
345
|
let allowActiveEviction = false;
|
|
424
346
|
const randomId = input.randomId ?? randomUUID;
|
|
425
347
|
const executionId = input.executionId ?? "unknown";
|
|
@@ -461,21 +383,10 @@ export async function applyLocalMemoryResult(input) {
|
|
|
461
383
|
if (existingCreate !== undefined && !matchesCreateCandidate(existingCreate, change)) {
|
|
462
384
|
throw new Error("local_memory_duplicate_candidate");
|
|
463
385
|
}
|
|
464
|
-
if (existingCreate === undefined && entries.some((entry) => (
|
|
465
|
-
|
|
386
|
+
if (existingCreate === undefined && entries.some((entry) => normalize(entry.title) === normalize(change.title)
|
|
387
|
+
|| normalize(entry.summary) === normalize(change.summary))) {
|
|
466
388
|
throw new Error("local_memory_duplicate_candidate");
|
|
467
389
|
}
|
|
468
|
-
if (existingCreate !== undefined) {
|
|
469
|
-
if (existingCreate.revision !== 1 || existingCreate.status !== "active") {
|
|
470
|
-
throw new Error("local_memory_create_replay_conflict");
|
|
471
|
-
}
|
|
472
|
-
const stored = await readStoredNote(resolve(input.agentDir, existingCreate.file));
|
|
473
|
-
if (uniqueNoteSection(stored, id)?.trimEnd() !== noteSection(id, 1, change.title, change.detailBody).trimEnd()) {
|
|
474
|
-
throw new Error("local_memory_create_replay_conflict");
|
|
475
|
-
}
|
|
476
|
-
// A proven duplicate is a no-op; it must never rewrite a newer body or status.
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
390
|
}
|
|
480
391
|
const revision = existingCreate?.revision ?? (current === undefined ? 1 : current.revision + 1);
|
|
481
392
|
const entry = existingCreate ?? entryFromChange(change, id, revision, current?.status ?? "active");
|
|
@@ -489,7 +400,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
489
400
|
: item),
|
|
490
401
|
entry,
|
|
491
402
|
];
|
|
492
|
-
const allowEvictionForChange =
|
|
403
|
+
const allowEvictionForChange = change.operation === "create" && change.priority === "high";
|
|
493
404
|
const protectedIds = new Set(allowEvictionForChange ? pinnedIds(entries) : activeOrPinnedIds(entries));
|
|
494
405
|
if (allowEvictionForChange)
|
|
495
406
|
allowActiveEviction = true;
|
|
@@ -506,7 +417,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
506
417
|
const notePath = resolve(input.agentDir, entry.file);
|
|
507
418
|
if (!isWithin(resolve(input.agentDir, "notes"), notePath))
|
|
508
419
|
throw new Error("local_memory_note_path_invalid");
|
|
509
|
-
const previous = await
|
|
420
|
+
const previous = noteUpdates.get(notePath) ?? await readNote(notePath);
|
|
510
421
|
const replacement = noteSection(id, revision, change.title, change.detailBody);
|
|
511
422
|
const existingSection = replaceNote(previous, id, replacement);
|
|
512
423
|
const next = existingSection ?? (current === undefined
|
|
@@ -524,7 +435,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
524
435
|
// Pending create candidates are durable overflow, not dead ends. Promote only
|
|
525
436
|
// when the current catalog has room; supersede/update candidates stay pending
|
|
526
437
|
// until a fresh revision-aware decision can safely replay them.
|
|
527
|
-
const pendingCandidatesOnDisk =
|
|
438
|
+
const pendingCandidatesOnDisk = await readPendingMemoryCandidates({ agentDir: input.agentDir }).catch((error) => {
|
|
528
439
|
if (!(error instanceof Error) || error.message !== "memory_pending_directory_over_budget")
|
|
529
440
|
throw error;
|
|
530
441
|
// 自动提升是可选工作,目录查询预算不应阻断本轮已验证的更新或腾位操作。
|
|
@@ -549,7 +460,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
549
460
|
|| normalize(entry.summary) === normalize(change.summary)));
|
|
550
461
|
if (duplicate !== undefined) {
|
|
551
462
|
const duplicatePath = resolve(input.agentDir, duplicate.file);
|
|
552
|
-
const duplicateBody = readNoteBody(await
|
|
463
|
+
const duplicateBody = readNoteBody(noteUpdates.get(duplicatePath) ?? await readNote(duplicatePath), duplicate.id);
|
|
553
464
|
if (duplicateBody === change.detailBody
|
|
554
465
|
|| duplicateBody === unwrappedNoteBody(change.title, change.detailBody).trim()) {
|
|
555
466
|
resolvedPendingIds.add(pending.pendingId);
|
|
@@ -567,7 +478,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
567
478
|
const notePath = resolve(input.agentDir, entry.file);
|
|
568
479
|
if (!isWithin(resolve(input.agentDir, "notes"), notePath))
|
|
569
480
|
throw new Error("local_memory_note_path_invalid");
|
|
570
|
-
const previous = await
|
|
481
|
+
const previous = noteUpdates.get(notePath) ?? await readNote(notePath);
|
|
571
482
|
const replacement = noteSection(id, 1, change.title, change.detailBody);
|
|
572
483
|
const existingSection = replaceNote(previous, id, replacement);
|
|
573
484
|
const next = existingSection ?? `${previous.trimEnd()}${previous.trimEnd() ? "\n\n" : ""}${replacement}`;
|
|
@@ -576,17 +487,6 @@ export async function applyLocalMemoryResult(input) {
|
|
|
576
487
|
createdIds.push(id);
|
|
577
488
|
resolvedPendingIds.add(pending.pendingId);
|
|
578
489
|
}
|
|
579
|
-
if (fullStorage) {
|
|
580
|
-
const changedMetadataIds = new Set([
|
|
581
|
-
...statusChangedIds,
|
|
582
|
-
...input.result.changes.flatMap(change => change.supersedesMemoryId === null ? [] : [change.supersedesMemoryId]),
|
|
583
|
-
]);
|
|
584
|
-
for (const id of changedMetadataIds) {
|
|
585
|
-
const entry = entries.find(item => item.id === id);
|
|
586
|
-
const notePath = resolve(input.agentDir, entry.file);
|
|
587
|
-
noteUpdates.set(notePath, updateNoteRevision(await readStoredNote(notePath), id, entry.revision));
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
490
|
let finalProjection = projectIndex(memory, entries, allowActiveEviction ? pinnedIds(entries) : activeOrPinnedIds(entries));
|
|
591
491
|
if (finalProjection.reason !== null
|
|
592
492
|
&& (!allowActiveEviction || finalProjection.reason === "index_over_budget_pinned")) {
|
|
@@ -598,7 +498,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
598
498
|
.filter((entry) => entry?.status === "active");
|
|
599
499
|
for (const entry of omittedActiveEntries) {
|
|
600
500
|
const notePath = resolve(input.agentDir, entry.file);
|
|
601
|
-
const content = await
|
|
501
|
+
const content = noteUpdates.get(notePath) ?? await readNote(notePath);
|
|
602
502
|
const detailBody = readNoteBody(content, entry.id);
|
|
603
503
|
const change = detailBody === null ? null : archivedChange(entry, detailBody);
|
|
604
504
|
if (change === null)
|
|
@@ -619,12 +519,6 @@ export async function applyLocalMemoryResult(input) {
|
|
|
619
519
|
if (finalProjection.reason !== null)
|
|
620
520
|
throw new Error(finalProjection.reason);
|
|
621
521
|
}
|
|
622
|
-
// Complete every resource check before the first persistent write.
|
|
623
|
-
assertLocalMemoryStorageSize(finalProjection.memory);
|
|
624
|
-
for (const content of noteUpdates.values()) {
|
|
625
|
-
if (Buffer.byteLength(content, "utf8") > LOCAL_MEMORY_NOTE_BYTE_CAP)
|
|
626
|
-
throw new Error("local_memory_note_byte_limit");
|
|
627
|
-
}
|
|
628
522
|
for (const candidate of pendingCandidates)
|
|
629
523
|
await persistPendingMemoryCandidate({ agentDir: input.agentDir, candidate });
|
|
630
524
|
for (const [path, content] of noteUpdates)
|
|
@@ -641,5 +535,5 @@ export async function applyLocalMemoryResult(input) {
|
|
|
641
535
|
deferredIds,
|
|
642
536
|
pendingIds: deferredIds,
|
|
643
537
|
};
|
|
644
|
-
})
|
|
538
|
+
});
|
|
645
539
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
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";
|
|
@@ -39,52 +39,13 @@ async function inspectFile(path, signal) {
|
|
|
39
39
|
return { ...base, error_code: errorCode(error) };
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
async function inspectPendingDirectory(path, signal) {
|
|
43
|
-
let metadata;
|
|
44
|
-
try {
|
|
45
|
-
metadata = await lstat(path);
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
const code = errorCode(error);
|
|
49
|
-
return code === "ENOENT" ? { exists: false, entry_count: 0 } : { exists: false, error_code: code };
|
|
50
|
-
}
|
|
51
|
-
const base = { exists: true, mtime_ms: metadata.mtimeMs };
|
|
52
|
-
if (metadata.isSymbolicLink() || !metadata.isDirectory())
|
|
53
|
-
return { ...base, hash_skipped_reason: "not_regular_file" };
|
|
54
|
-
try {
|
|
55
|
-
const names = (await readdir(path, { withFileTypes: true }))
|
|
56
|
-
.filter(entry => entry.name.endsWith(".json"))
|
|
57
|
-
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
58
|
-
if (names.length > 128)
|
|
59
|
-
return { ...base, entry_count: names.length, error_code: "entry_limit" };
|
|
60
|
-
const hash = createHash("sha256");
|
|
61
|
-
let size = 0;
|
|
62
|
-
for (const entry of names) {
|
|
63
|
-
if (signal.aborted)
|
|
64
|
-
throw signal.reason;
|
|
65
|
-
if (!entry.isFile() || entry.isSymbolicLink())
|
|
66
|
-
return { ...base, entry_count: names.length, error_code: "invalid_entry" };
|
|
67
|
-
const fact = await inspectFile(join(path, entry.name), signal);
|
|
68
|
-
if (!fact.exists || fact.sha256 === undefined || fact.error_code !== undefined
|
|
69
|
-
|| fact.hash_skipped_reason !== undefined)
|
|
70
|
-
return { ...base, entry_count: names.length, error_code: "incomplete_entry" };
|
|
71
|
-
size += fact.size_bytes ?? 0;
|
|
72
|
-
hash.update(entry.name).update("\0").update(fact.sha256).update("\0");
|
|
73
|
-
}
|
|
74
|
-
return { ...base, size_bytes: size, entry_count: names.length, sha256: hash.digest("hex") };
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
return { ...base, error_code: errorCode(error) };
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
42
|
export async function inspectLocalMemoryFilesWithinDeadline(homeDir, timeoutMs) {
|
|
81
43
|
const controller = new AbortController();
|
|
82
44
|
let timer;
|
|
83
45
|
const inspection = Promise.all([
|
|
84
46
|
inspectFile(join(homeDir, "MEMORY.md"), controller.signal),
|
|
85
47
|
inspectFile(join(homeDir, "notes", "lessons.md"), controller.signal),
|
|
86
|
-
|
|
87
|
-
]).then(([memory, lessons, pending]) => ({ memory, lessons, pending }));
|
|
48
|
+
]).then(([memory, lessons]) => ({ memory, lessons }));
|
|
88
49
|
const timeout = new Promise((_resolve, reject) => {
|
|
89
50
|
timer = setTimeout(() => {
|
|
90
51
|
controller.abort();
|
|
@@ -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
|
|
187
|
+
const sharedFilesChanged = memoryChanged === undefined || lessonsChanged === undefined
|
|
198
188
|
? undefined
|
|
199
|
-
: memoryChanged || lessonsChanged
|
|
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
|
},
|
package/dist/machine-info.js
CHANGED
|
@@ -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"
|
|
@@ -7,7 +7,6 @@ import { dslog } from "./slog.js";
|
|
|
7
7
|
const TRACE_PATTERN = /\[memory-prune trace_id=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\]/i;
|
|
8
8
|
export const MEMORY_PRUNE_HASH_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
9
9
|
export const MEMORY_PRUNE_NOTES_MANIFEST_LIMIT = 128;
|
|
10
|
-
export const MEMORY_PRUNE_PENDING_MANIFEST_LIMIT = 128;
|
|
11
10
|
export const MEMORY_PRUNE_NOTE_CHANGE_LIMIT = 128;
|
|
12
11
|
function compareCodeUnits(left, right) {
|
|
13
12
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
@@ -193,50 +192,6 @@ export async function inspectMemoryPruneNotes(homeDir, signal) {
|
|
|
193
192
|
...(signal?.aborted ? { error: "ABORT_ERR" } : {}),
|
|
194
193
|
};
|
|
195
194
|
}
|
|
196
|
-
export async function inspectMemoryPrunePending(homeDir, signal) {
|
|
197
|
-
const pendingDir = join(homeDir, ".crew", "memory", "pending");
|
|
198
|
-
try {
|
|
199
|
-
const metadata = await lstat(pendingDir);
|
|
200
|
-
if (metadata.isSymbolicLink())
|
|
201
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: "symbolic_link" };
|
|
202
|
-
if (!metadata.isDirectory())
|
|
203
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: "ENOTDIR" };
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
if (error.code === "ENOENT")
|
|
207
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false };
|
|
208
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: diagnosticError(error) };
|
|
209
|
-
}
|
|
210
|
-
let names;
|
|
211
|
-
try {
|
|
212
|
-
names = (await readdir(pendingDir, { withFileTypes: true }))
|
|
213
|
-
.filter(entry => (entry.isFile() || entry.isSymbolicLink()) && entry.name.endsWith(".json"))
|
|
214
|
-
.map(entry => entry.name).sort(compareCodeUnits);
|
|
215
|
-
}
|
|
216
|
-
catch (error) {
|
|
217
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: diagnosticError(error) };
|
|
218
|
-
}
|
|
219
|
-
const selected = names.slice(0, MEMORY_PRUNE_PENDING_MANIFEST_LIMIT);
|
|
220
|
-
const files = [];
|
|
221
|
-
for (const name of selected) {
|
|
222
|
-
if (signal?.aborted)
|
|
223
|
-
break;
|
|
224
|
-
const path = join(pendingDir, name);
|
|
225
|
-
try {
|
|
226
|
-
if ((await lstat(path)).isSymbolicLink()) {
|
|
227
|
-
files.push({ relative_path: `.crew/memory/pending/${name}`, exists: false, error: "symbolic_link" });
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
catch (error) {
|
|
232
|
-
files.push({ relative_path: `.crew/memory/pending/${name}`, exists: false, error: diagnosticError(error) });
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
files.push({ relative_path: `.crew/memory/pending/${name}`, ...await inspectFile(path, false, undefined, signal) });
|
|
236
|
-
}
|
|
237
|
-
return { files, total_count: names.length, omitted_count: Math.max(0, names.length - selected.length),
|
|
238
|
-
truncated: names.length > selected.length, ...(signal?.aborted ? { error: "ABORT_ERR" } : {}) };
|
|
239
|
-
}
|
|
240
195
|
async function inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal) {
|
|
241
196
|
const cwdIsHome = runDir === homeDir;
|
|
242
197
|
const [memory, lessons, cwdMemory, cwdLessons, workLog] = await Promise.all([
|
|
@@ -255,17 +210,15 @@ async function inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal
|
|
|
255
210
|
};
|
|
256
211
|
}
|
|
257
212
|
export async function inspectMemoryPruneFiles(homeDir, runDir, workLogPath, signal) {
|
|
258
|
-
const [fixedFiles, notesManifest
|
|
213
|
+
const [fixedFiles, notesManifest] = await Promise.all([
|
|
259
214
|
inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal),
|
|
260
215
|
inspectMemoryPruneNotes(homeDir, signal),
|
|
261
|
-
inspectMemoryPrunePending(homeDir, signal),
|
|
262
216
|
]);
|
|
263
|
-
return { ...fixedFiles, notes_manifest: notesManifest
|
|
217
|
+
return { ...fixedFiles, notes_manifest: notesManifest };
|
|
264
218
|
}
|
|
265
219
|
export async function inspectMemoryPruneFilesWithinDeadline(homeDir, runDir, workLogPath, timeoutMs) {
|
|
266
220
|
const fixedFiles = await withinMemoryPruneDiagnosticsDeadline((signal) => inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal), timeoutMs);
|
|
267
221
|
let notesManifest;
|
|
268
|
-
let pendingManifest;
|
|
269
222
|
try {
|
|
270
223
|
notesManifest = await withinMemoryPruneDiagnosticsDeadline((signal) => inspectMemoryPruneNotes(homeDir, signal), timeoutMs);
|
|
271
224
|
}
|
|
@@ -278,13 +231,7 @@ export async function inspectMemoryPruneFilesWithinDeadline(homeDir, runDir, wor
|
|
|
278
231
|
error: diagnosticError(error),
|
|
279
232
|
};
|
|
280
233
|
}
|
|
281
|
-
|
|
282
|
-
pendingManifest = await withinMemoryPruneDiagnosticsDeadline(signal => inspectMemoryPrunePending(homeDir, signal), timeoutMs);
|
|
283
|
-
}
|
|
284
|
-
catch (error) {
|
|
285
|
-
pendingManifest = { files: [], total_count: 0, omitted_count: 0, truncated: false, error: diagnosticError(error) };
|
|
286
|
-
}
|
|
287
|
-
return { ...fixedFiles, notes_manifest: notesManifest, pending_manifest: pendingManifest };
|
|
234
|
+
return { ...fixedFiles, notes_manifest: notesManifest };
|
|
288
235
|
}
|
|
289
236
|
export async function withinMemoryPruneDiagnosticsDeadline(operation, timeoutMs) {
|
|
290
237
|
const controller = new AbortController();
|
|
@@ -414,7 +361,6 @@ export function evaluateMemoryPrunePostcondition(before, after, execution) {
|
|
|
414
361
|
&& (after.cwd_memory.lessons_reference_count ?? 0) > 0,
|
|
415
362
|
cwd_lessons_changed: !after.cwd_lessons.same_as_shared_path
|
|
416
363
|
&& fileChanged(before.cwd_lessons, after.cwd_lessons),
|
|
417
|
-
pending_changed: diffMemoryPruneNotes(before.pending_manifest ?? { files: [], total_count: 0, omitted_count: 0, truncated: false }, after.pending_manifest ?? { files: [], total_count: 0, omitted_count: 0, truncated: false }).change_count > 0,
|
|
418
364
|
};
|
|
419
365
|
let outcome;
|
|
420
366
|
if (!execution.executorCompleted)
|
|
@@ -442,8 +388,6 @@ export function evaluateMemoryPrunePostcondition(before, after, execution) {
|
|
|
442
388
|
outcome = "promoted_without_index_change";
|
|
443
389
|
else if (facts.shared_memory_changed)
|
|
444
390
|
outcome = "shared_memory_updated";
|
|
445
|
-
else if (facts.pending_changed)
|
|
446
|
-
outcome = "pending_updated";
|
|
447
391
|
else
|
|
448
392
|
outcome = "no_files_changed";
|
|
449
393
|
return { outcome, ...facts };
|
|
@@ -464,7 +408,7 @@ function memoryPruneFileFactFields(fileFacts) {
|
|
|
464
408
|
}
|
|
465
409
|
return fields;
|
|
466
410
|
}
|
|
467
|
-
export function memoryPruneSnapshotFields({ notes_manifest: _notesManifest,
|
|
411
|
+
export function memoryPruneSnapshotFields({ notes_manifest: _notesManifest, ...fileFacts }) {
|
|
468
412
|
return memoryPruneFileFactFields(fileFacts);
|
|
469
413
|
}
|
|
470
414
|
export function logMemoryPruneDiagnosticsFailure(input, traceId, phase, error) {
|
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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,
|