@nowcrew/daemon 0.6.93-beta.6 → 0.6.93-beta.7
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/cloud-report-upload.js +10 -1
- package/dist/execution-journal.js +1 -3
- package/dist/execution-protocol.js +1 -1
- package/dist/execution-runner.js +1 -4
- package/dist/local-execution-prompt.js +3 -8
- package/dist/local-executor.js +37 -70
- package/dist/local-memory/index-budget.js +3 -9
- package/dist/local-memory/inventory-cli.js +7 -45
- 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 +11 -73
- package/dist/local-memory/runtime.js +13 -176
- package/dist/local-memory/write.js +21 -135
- package/dist/local-memory-diagnostics.js +13 -94
- package/dist/local-memory-telemetry.js +2 -19
- package/dist/machine-info.js +0 -3
- package/dist/marketplace-protocol.js +1 -1
- package/dist/marketplace-remote.js +1 -1
- package/dist/marketplace-runner.js +1 -1
- package/dist/memory-prune-diagnostics.js +4 -60
- package/dist/message-file-preview.js +1 -11
- package/dist/prompt.js +2 -6
- package/dist/runner.js +1 -1
- package/dist/runtimes/claude-thread-runner.js +4 -25
- package/dist/serve.js +3 -38
- package/dist/token.js +0 -1
- package/dist/workspace-control-handler.js +3 -16
- package/dist/workspace.js +2 -13
- package/package.json +1 -1
- package/dist/local-memory/execution-workspace.js +0 -62
- package/dist/local-memory/full-index-search.js +0 -47
- package/dist/local-memory/initialization-identity.js +0 -36
- package/dist/local-memory/inventory-v2.js +0 -282
- package/dist/local-memory/legacy-adapter.js +0 -233
- package/dist/local-memory/migration-access.js +0 -196
- 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 -286
- package/dist/local-memory/migration-schema.js +0 -55
- package/dist/local-memory/migration-state-invariants.js +0 -52
- package/dist/local-memory/migration-store.js +0 -401
- package/dist/local-memory/migration-tool-entry.js +0 -21
- package/dist/local-memory/storage-format.js +0 -152
- package/dist/local-memory/writer-mode.js +0 -13
- package/dist/memory-migration-controller.js +0 -86
- package/dist/memory-migration-inspect.js +0 -108
- 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");
|
|
@@ -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"
|
|
309
|
+
if (input.result.outcome !== "proposed")
|
|
361
310
|
return emptyResult();
|
|
362
311
|
const memoryPath = join(input.agentDir, "MEMORY.md");
|
|
363
|
-
|
|
364
|
-
|
|
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) => (
|
|
473
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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([
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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
|
|
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"
|
|
@@ -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 = "
|
|
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/
|
|
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/
|
|
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);
|