@nowcrew/daemon 0.6.99-beta.2 → 0.6.99-beta.3
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/cloud-report-upload.js +1 -10
- package/dist/computer-service.js +5 -30
- package/dist/execution-journal.js +3 -1
- package/dist/execution-protocol.js +1 -1
- package/dist/execution-runner.js +4 -1
- package/dist/local-execution-prompt.js +8 -3
- package/dist/local-executor.js +64 -39
- package/dist/local-memory/commit-telemetry.js +30 -0
- package/dist/local-memory/execution-workspace.js +62 -0
- package/dist/local-memory/full-index-search.js +47 -0
- package/dist/local-memory/index-budget.js +9 -3
- package/dist/local-memory/initialization-identity.js +36 -0
- package/dist/local-memory/inventory-cli.js +52 -8
- package/dist/local-memory/inventory-source.js +15 -8
- package/dist/local-memory/inventory-v2.js +362 -0
- package/dist/local-memory/legacy-adapter.js +233 -0
- package/dist/local-memory/migration-access.js +242 -0
- package/dist/local-memory/migration-cli.js +54 -0
- package/dist/local-memory/migration-io.js +199 -0
- package/dist/local-memory/migration-pending.js +9 -0
- package/dist/local-memory/migration-plan.js +432 -0
- package/dist/local-memory/migration-schema.js +55 -0
- package/dist/local-memory/migration-state-invariants.js +65 -0
- package/dist/local-memory/migration-store.js +414 -0
- package/dist/local-memory/migration-tool-entry.js +21 -0
- package/dist/local-memory/pending-query.js +5 -2
- package/dist/local-memory/pending-schema.js +1 -1
- package/dist/local-memory/pending.js +16 -2
- package/dist/local-memory/prompt.js +21 -5
- package/dist/local-memory/read-cli.js +98 -76
- package/dist/local-memory/rollout.js +79 -11
- package/dist/local-memory/runtime.js +181 -13
- package/dist/local-memory/source-path.js +13 -0
- package/dist/local-memory/storage-format.js +249 -0
- package/dist/local-memory/write.js +189 -21
- package/dist/local-memory/writer-mode.js +13 -0
- package/dist/local-memory-diagnostics.js +96 -15
- package/dist/local-memory-telemetry.js +20 -3
- package/dist/machine-info.js +3 -0
- package/dist/marketplace-protocol.js +1 -1
- package/dist/marketplace-remote.js +1 -1
- package/dist/marketplace-runner.js +1 -1
- package/dist/memory-migration-controller.js +89 -0
- package/dist/memory-migration-inspect.js +108 -0
- package/dist/memory-migration-journal.js +14 -0
- package/dist/memory-migration-protocol.js +14 -0
- package/dist/memory-migration-runtime.js +305 -0
- package/dist/memory-prune-diagnostics.js +60 -4
- package/dist/message-file-preview.js +11 -1
- package/dist/prompt.js +9 -17
- package/dist/runner.js +1 -1
- package/dist/runtimes/claude-thread-runner.js +25 -4
- package/dist/serve.js +38 -3
- package/dist/token.js +1 -0
- package/dist/workspace-control-handler.js +16 -3
- package/dist/workspace.js +13 -2
- package/package.json +1 -1
|
@@ -6,6 +6,8 @@ import { dslog } from "../slog.js";
|
|
|
6
6
|
import { LocalMemoryChangeSchema, LocalMemorySingleLineText, validateLocalMemoryChange, } from "./candidate.js";
|
|
7
7
|
import { LOCAL_MEMORY_INDEX_CODE_POINT_CAP, LOCAL_MEMORY_INDEX_ENTRY_CAP, LOCAL_MEMORY_TOTAL_CODE_POINT_CAP, renderGeneratedIndex, selectLocalMemoryIndexEntries, } from "./index-budget.js";
|
|
8
8
|
import { persistPendingMemoryCandidate, readPendingMemoryCandidates, removePendingMemoryCandidate, } from "./pending.js";
|
|
9
|
+
import { assertLocalMemoryStorageSize, detectLocalMemoryStorageVersion, LOCAL_MEMORY_GENERATED_INDEX_PATTERN, LOCAL_MEMORY_NOTE_BYTE_CAP, LOCAL_MEMORY_READ_BYTE_CAP, parseFullMemoryIndex, replaceFullMemoryIndex, selectLocalMemoryPreview, } from "./storage-format.js";
|
|
10
|
+
import { assertMemoryInventoryHealth, assertMemoryWriterAllowed, withMemoryAccess } from "./migration-access.js";
|
|
9
11
|
const StatusChangeSchema = z.object({
|
|
10
12
|
memoryId: LocalMemorySingleLineText,
|
|
11
13
|
expectedRevision: z.number().int().positive(),
|
|
@@ -90,12 +92,14 @@ function parseCards(section) {
|
|
|
90
92
|
});
|
|
91
93
|
}
|
|
92
94
|
export function parseLocalMemoryIndex(memory) {
|
|
95
|
+
if (detectLocalMemoryStorageVersion(memory) === 2)
|
|
96
|
+
return parseFullMemoryIndex(memory);
|
|
93
97
|
const section = indexSection(memory);
|
|
94
98
|
const generated = GENERATED_INDEX_PATTERN.exec(section)?.[0];
|
|
95
99
|
return parseCards(generated ?? section);
|
|
96
100
|
}
|
|
97
101
|
function parseUnmarkedMemoryIndex(memory) {
|
|
98
|
-
const section = indexSection(memory).replace(
|
|
102
|
+
const section = indexSection(memory).replace(LOCAL_MEMORY_GENERATED_INDEX_PATTERN, "");
|
|
99
103
|
return parseCards(section);
|
|
100
104
|
}
|
|
101
105
|
function replaceGeneratedIndex(memory, entries) {
|
|
@@ -122,11 +126,36 @@ function noteSection(id, revision, title, body) {
|
|
|
122
126
|
}
|
|
123
127
|
function notePattern(id) {
|
|
124
128
|
const escaped = id.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
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");
|
|
129
|
+
return new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:BEGIN\\s+id=${escaped}(?=\\s|-->)[^>]*-->[\\s\\S]*?<!--\\s*NOWWORK:MEMORY_ENTRY:END\\s+id=${escaped}\\s*-->\\s*`, "u");
|
|
130
|
+
}
|
|
131
|
+
function uniqueNoteSection(content, id) {
|
|
132
|
+
const escaped = id.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
133
|
+
const begins = [...content.matchAll(new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:BEGIN\\s+id=${escaped}(?=\\s|-->)`, "gu"))];
|
|
134
|
+
const ends = [...content.matchAll(new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:END\\s+id=${escaped}\\s*-->`, "gu"))];
|
|
135
|
+
if (begins.length === 0 && ends.length === 0)
|
|
136
|
+
return null;
|
|
137
|
+
const section = notePattern(id).exec(content)?.[0];
|
|
138
|
+
if (begins.length !== 1 || ends.length !== 1 || section === undefined
|
|
139
|
+
|| (section.match(/NOWWORK:MEMORY_ENTRY:(?:BEGIN|END)/gu) ?? []).length !== 2) {
|
|
140
|
+
throw new Error("local_memory_note_anchor_invalid");
|
|
141
|
+
}
|
|
142
|
+
return section;
|
|
126
143
|
}
|
|
127
144
|
function replaceNote(content, id, replacement) {
|
|
128
145
|
const pattern = notePattern(id);
|
|
129
|
-
return
|
|
146
|
+
return uniqueNoteSection(content, id) !== null ? content.replace(pattern, () => replacement) : null;
|
|
147
|
+
}
|
|
148
|
+
function updateNoteRevision(content, id, revision) {
|
|
149
|
+
const section = uniqueNoteSection(content, id);
|
|
150
|
+
const beginning = `<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${id} `;
|
|
151
|
+
const ending = `<!-- NOWWORK:MEMORY_ENTRY:END id=${id} -->`;
|
|
152
|
+
if (section === null || content.split(beginning).length !== 2 || content.split(ending).length !== 2) {
|
|
153
|
+
throw new Error("local_memory_note_anchor_missing");
|
|
154
|
+
}
|
|
155
|
+
const updated = section.replace(/^(<!--\s*NOWWORK:MEMORY_ENTRY:BEGIN\s+id=[A-Za-z0-9_-]+\s+revision=)[1-9][0-9]*(\s*-->)/u, (_match, prefix, suffix) => `${prefix}${revision}${suffix}`);
|
|
156
|
+
if (!updated.startsWith(`${beginning}revision=${revision} -->`))
|
|
157
|
+
throw new Error("local_memory_note_anchor_missing");
|
|
158
|
+
return content.replace(notePattern(id), () => updated);
|
|
130
159
|
}
|
|
131
160
|
function validateCandidate(result) {
|
|
132
161
|
if (result.outcome === "no_change" && (result.changes.length > 0 || result.statusChanges.length > 0)) {
|
|
@@ -180,6 +209,25 @@ function stableId(executionId, index, change) {
|
|
|
180
209
|
const input = [executionId, String(index), change.operation, change.title, change.summary, change.detailBody].join("\0");
|
|
181
210
|
return `mem_${createHash("sha256").update(input, "utf8").digest("hex").slice(0, 12)}`;
|
|
182
211
|
}
|
|
212
|
+
function migrationPendingId(change) {
|
|
213
|
+
// Keep the digest independent of object insertion order for callers that
|
|
214
|
+
// reach applyLocalMemoryResult directly instead of the JSON/Zod boundary.
|
|
215
|
+
const canonicalChange = {
|
|
216
|
+
operation: change.operation,
|
|
217
|
+
memoryId: change.memoryId,
|
|
218
|
+
baseRevision: change.baseRevision,
|
|
219
|
+
supersedesMemoryId: change.supersedesMemoryId,
|
|
220
|
+
title: change.title,
|
|
221
|
+
kind: change.kind,
|
|
222
|
+
summary: change.summary,
|
|
223
|
+
triggers: change.triggers,
|
|
224
|
+
priority: change.priority,
|
|
225
|
+
detailCategory: change.detailCategory,
|
|
226
|
+
detailBody: change.detailBody,
|
|
227
|
+
reviewAfter: change.reviewAfter,
|
|
228
|
+
};
|
|
229
|
+
return `pending-migration-${createHash("sha256").update(JSON.stringify(canonicalChange), "utf8").digest("hex").slice(0, 12)}`;
|
|
230
|
+
}
|
|
183
231
|
function normalize(value) {
|
|
184
232
|
return value.normalize("NFKC").toLocaleLowerCase().replace(/\s+/gu, " ").trim();
|
|
185
233
|
}
|
|
@@ -199,14 +247,14 @@ function detailCategoryFromFile(file) {
|
|
|
199
247
|
return DETAIL_CATEGORIES.has(category) ? category : null;
|
|
200
248
|
}
|
|
201
249
|
function readNoteBody(content, id) {
|
|
202
|
-
const
|
|
203
|
-
if (
|
|
250
|
+
const section = uniqueNoteSection(content, id);
|
|
251
|
+
if (section === null)
|
|
204
252
|
return null;
|
|
205
|
-
const bodyStart =
|
|
206
|
-
const bodyEnd =
|
|
253
|
+
const bodyStart = section.indexOf("\n\n");
|
|
254
|
+
const bodyEnd = section.indexOf(`\n<!-- NOWWORK:MEMORY_ENTRY:END id=${id}`, bodyStart + 2);
|
|
207
255
|
if (bodyStart < 0 || bodyEnd < 0)
|
|
208
256
|
return null;
|
|
209
|
-
return
|
|
257
|
+
return section.slice(bodyStart + 2, bodyEnd).trim();
|
|
210
258
|
}
|
|
211
259
|
function archivedChange(entry, detailBody) {
|
|
212
260
|
const detailCategory = detailCategoryFromFile(entry.file);
|
|
@@ -230,6 +278,34 @@ function archivedChange(entry, detailBody) {
|
|
|
230
278
|
function emptyResult() {
|
|
231
279
|
return { createdIds: [], updatedIds: [], statusChangedIds: [], deferredIds: [], pendingIds: [] };
|
|
232
280
|
}
|
|
281
|
+
async function stageMigrationDeferredPending(input) {
|
|
282
|
+
if (input.result.outcome !== "proposed")
|
|
283
|
+
return emptyResult();
|
|
284
|
+
if (input.result.statusChanges.length > 0 || input.result.changes.some((change) => change.operation !== "create")) {
|
|
285
|
+
throw new Error("local_memory_migration_pending_create_only");
|
|
286
|
+
}
|
|
287
|
+
const executionId = input.executionId ?? "unknown";
|
|
288
|
+
const pending = input.result.changes.map((change) => ({
|
|
289
|
+
schemaVersion: 1,
|
|
290
|
+
pendingId: migrationPendingId(change),
|
|
291
|
+
createdAt: new Date().toISOString(),
|
|
292
|
+
reason: "legacy_requires_migration",
|
|
293
|
+
source: { executionId, pruneTraceId: input.pruneTraceId ?? null },
|
|
294
|
+
candidate: change,
|
|
295
|
+
}));
|
|
296
|
+
await assertMemoryAccessDirectory(input.agentDir);
|
|
297
|
+
await withMemoryAccess(input.agentDir, async () => {
|
|
298
|
+
const memory = await readFile(join(input.agentDir, "MEMORY.md"), "utf8").catch(() => null);
|
|
299
|
+
if (memory === null || detectLocalMemoryStorageVersion(memory) !== 1) {
|
|
300
|
+
throw new Error("local_memory_migration_pending_storage_invalid");
|
|
301
|
+
}
|
|
302
|
+
for (const candidate of pending) {
|
|
303
|
+
await persistPendingMemoryCandidate({ agentDir: input.agentDir, candidate });
|
|
304
|
+
}
|
|
305
|
+
}, { pendingOnly: true });
|
|
306
|
+
const pendingIds = [...new Set(pending.map((candidate) => candidate.pendingId))];
|
|
307
|
+
return { createdIds: [], updatedIds: [], statusChangedIds: [], deferredIds: pendingIds, pendingIds };
|
|
308
|
+
}
|
|
233
309
|
function isWithin(root, candidate) {
|
|
234
310
|
const path = relative(root, candidate);
|
|
235
311
|
return path === "" || (path !== ".." && !path.startsWith(`..${sep}`));
|
|
@@ -244,12 +320,29 @@ async function assertWritablePaths(agentDir, memoryPath) {
|
|
|
244
320
|
if (!memoryStat.isFile() || memoryStat.isSymbolicLink())
|
|
245
321
|
throw new Error("local_memory_memory_path_invalid");
|
|
246
322
|
}
|
|
323
|
+
async function assertMemoryAccessDirectory(agentDir) {
|
|
324
|
+
try {
|
|
325
|
+
const stat = await lstat(join(agentDir, ".crew/memory"));
|
|
326
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
327
|
+
throw new Error("local_memory_pending_path_invalid");
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
if (error.code === "ENOENT")
|
|
331
|
+
return;
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
247
335
|
async function readNote(path) {
|
|
248
336
|
try {
|
|
249
337
|
const stat = await lstat(path);
|
|
250
338
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
251
339
|
throw new Error("local_memory_note_path_invalid");
|
|
252
|
-
|
|
340
|
+
if (stat.size > LOCAL_MEMORY_NOTE_BYTE_CAP)
|
|
341
|
+
throw new Error("local_memory_note_byte_limit");
|
|
342
|
+
const content = await readFile(path, "utf8");
|
|
343
|
+
if (Buffer.byteLength(content, "utf8") > LOCAL_MEMORY_NOTE_BYTE_CAP)
|
|
344
|
+
throw new Error("local_memory_note_byte_limit");
|
|
345
|
+
return content;
|
|
253
346
|
}
|
|
254
347
|
catch (error) {
|
|
255
348
|
if (error.code === "ENOENT")
|
|
@@ -269,6 +362,11 @@ async function atomicWrite(path, content) {
|
|
|
269
362
|
}
|
|
270
363
|
}
|
|
271
364
|
function projectIndex(memory, entries, protectedIds) {
|
|
365
|
+
if (detectLocalMemoryStorageVersion(memory) === 2) {
|
|
366
|
+
const updated = replaceFullMemoryIndex(memory, entries);
|
|
367
|
+
selectLocalMemoryPreview(updated);
|
|
368
|
+
return { memory: updated, omittedIds: [], reason: null };
|
|
369
|
+
}
|
|
272
370
|
const empty = replaceGeneratedIndex(memory, []);
|
|
273
371
|
if (codePointLength(empty) > LOCAL_MEMORY_TOTAL_CODE_POINT_CAP)
|
|
274
372
|
throw new Error("local_memory_memory_over_budget");
|
|
@@ -306,12 +404,29 @@ async function withApplyLease(agentDir, operation) {
|
|
|
306
404
|
}
|
|
307
405
|
export async function applyLocalMemoryResult(input) {
|
|
308
406
|
validateCandidate(input.result);
|
|
309
|
-
if (input.
|
|
407
|
+
if (input.migrationDeferredPending)
|
|
408
|
+
return stageMigrationDeferredPending(input);
|
|
409
|
+
if (input.result.outcome !== "proposed" && input.requiredStorageVersion !== 2)
|
|
310
410
|
return emptyResult();
|
|
311
411
|
const memoryPath = join(input.agentDir, "MEMORY.md");
|
|
312
|
-
|
|
313
|
-
|
|
412
|
+
if (input.result.outcome === "proposed")
|
|
413
|
+
await assertWritablePaths(input.agentDir, memoryPath);
|
|
414
|
+
await assertMemoryAccessDirectory(input.agentDir);
|
|
415
|
+
return withApplyLease(input.agentDir, async () => withMemoryAccess(input.agentDir, async () => {
|
|
416
|
+
await assertMemoryWriterAllowed(input.agentDir, input.writerCapabilities ?? []);
|
|
417
|
+
const memoryStat = await lstat(memoryPath);
|
|
418
|
+
if (memoryStat.size > 16 * 1024 * 1024)
|
|
419
|
+
throw new Error("local_memory_storage_byte_limit");
|
|
314
420
|
const memory = await readFile(memoryPath, "utf8");
|
|
421
|
+
assertLocalMemoryStorageSize(memory);
|
|
422
|
+
const fullStorage = detectLocalMemoryStorageVersion(memory) === 2;
|
|
423
|
+
if (input.requiredStorageVersion === 2 && !fullStorage) {
|
|
424
|
+
throw new Error("local_memory_v2_state_invalid_before_commit");
|
|
425
|
+
}
|
|
426
|
+
// A V2 no-op is still a commit decision. Return only after the access
|
|
427
|
+
// lock and committed-state writer gate have validated the same snapshot.
|
|
428
|
+
if (input.result.outcome !== "proposed")
|
|
429
|
+
return emptyResult();
|
|
315
430
|
if (parseUnmarkedMemoryIndex(memory).length > 0)
|
|
316
431
|
throw new Error("local_memory_legacy_index_requires_migration");
|
|
317
432
|
let entries = [...parseLocalMemoryIndex(memory)];
|
|
@@ -342,6 +457,26 @@ export async function applyLocalMemoryResult(input) {
|
|
|
342
457
|
const pendingCandidates = [];
|
|
343
458
|
const resolvedPendingIds = new Set();
|
|
344
459
|
const noteUpdates = new Map();
|
|
460
|
+
const noteCache = new Map();
|
|
461
|
+
let totalReadBytes = Buffer.byteLength(memory, "utf8");
|
|
462
|
+
const readStoredNote = async (path) => {
|
|
463
|
+
const cached = noteUpdates.get(path) ?? noteCache.get(path);
|
|
464
|
+
if (cached !== undefined)
|
|
465
|
+
return cached;
|
|
466
|
+
const stat = await lstat(path).catch((error) => {
|
|
467
|
+
if (error.code === "ENOENT")
|
|
468
|
+
return null;
|
|
469
|
+
throw error;
|
|
470
|
+
});
|
|
471
|
+
if (stat !== null && totalReadBytes + stat.size > LOCAL_MEMORY_READ_BYTE_CAP)
|
|
472
|
+
throw new Error("local_memory_read_byte_limit");
|
|
473
|
+
const content = await readNote(path);
|
|
474
|
+
totalReadBytes += Buffer.byteLength(content, "utf8");
|
|
475
|
+
if (totalReadBytes > LOCAL_MEMORY_READ_BYTE_CAP)
|
|
476
|
+
throw new Error("local_memory_read_byte_limit");
|
|
477
|
+
noteCache.set(path, content);
|
|
478
|
+
return content;
|
|
479
|
+
};
|
|
345
480
|
let allowActiveEviction = false;
|
|
346
481
|
const randomId = input.randomId ?? randomUUID;
|
|
347
482
|
const executionId = input.executionId ?? "unknown";
|
|
@@ -383,10 +518,21 @@ export async function applyLocalMemoryResult(input) {
|
|
|
383
518
|
if (existingCreate !== undefined && !matchesCreateCandidate(existingCreate, change)) {
|
|
384
519
|
throw new Error("local_memory_duplicate_candidate");
|
|
385
520
|
}
|
|
386
|
-
if (existingCreate === undefined && entries.some((entry) =>
|
|
387
|
-
|| normalize(entry.summary) === normalize(change.summary))) {
|
|
521
|
+
if (existingCreate === undefined && entries.some((entry) => (!fullStorage || entry.status === "active" || entry.status === "pinned")
|
|
522
|
+
&& (normalize(entry.title) === normalize(change.title) || normalize(entry.summary) === normalize(change.summary)))) {
|
|
388
523
|
throw new Error("local_memory_duplicate_candidate");
|
|
389
524
|
}
|
|
525
|
+
if (existingCreate !== undefined) {
|
|
526
|
+
if (existingCreate.revision !== 1 || existingCreate.status !== "active") {
|
|
527
|
+
throw new Error("local_memory_create_replay_conflict");
|
|
528
|
+
}
|
|
529
|
+
const stored = await readStoredNote(resolve(input.agentDir, existingCreate.file));
|
|
530
|
+
if (uniqueNoteSection(stored, id)?.trimEnd() !== noteSection(id, 1, change.title, change.detailBody).trimEnd()) {
|
|
531
|
+
throw new Error("local_memory_create_replay_conflict");
|
|
532
|
+
}
|
|
533
|
+
// A proven duplicate is a no-op; it must never rewrite a newer body or status.
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
390
536
|
}
|
|
391
537
|
const revision = existingCreate?.revision ?? (current === undefined ? 1 : current.revision + 1);
|
|
392
538
|
const entry = existingCreate ?? entryFromChange(change, id, revision, current?.status ?? "active");
|
|
@@ -400,7 +546,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
400
546
|
: item),
|
|
401
547
|
entry,
|
|
402
548
|
];
|
|
403
|
-
const allowEvictionForChange = change.operation === "create" && change.priority === "high";
|
|
549
|
+
const allowEvictionForChange = !fullStorage && change.operation === "create" && change.priority === "high";
|
|
404
550
|
const protectedIds = new Set(allowEvictionForChange ? pinnedIds(entries) : activeOrPinnedIds(entries));
|
|
405
551
|
if (allowEvictionForChange)
|
|
406
552
|
allowActiveEviction = true;
|
|
@@ -417,7 +563,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
417
563
|
const notePath = resolve(input.agentDir, entry.file);
|
|
418
564
|
if (!isWithin(resolve(input.agentDir, "notes"), notePath))
|
|
419
565
|
throw new Error("local_memory_note_path_invalid");
|
|
420
|
-
const previous =
|
|
566
|
+
const previous = await readStoredNote(notePath);
|
|
421
567
|
const replacement = noteSection(id, revision, change.title, change.detailBody);
|
|
422
568
|
const existingSection = replaceNote(previous, id, replacement);
|
|
423
569
|
const next = existingSection ?? (current === undefined
|
|
@@ -435,7 +581,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
435
581
|
// Pending create candidates are durable overflow, not dead ends. Promote only
|
|
436
582
|
// when the current catalog has room; supersede/update candidates stay pending
|
|
437
583
|
// until a fresh revision-aware decision can safely replay them.
|
|
438
|
-
const pendingCandidatesOnDisk = await readPendingMemoryCandidates({ agentDir: input.agentDir }).catch((error) => {
|
|
584
|
+
const pendingCandidatesOnDisk = fullStorage ? [] : await readPendingMemoryCandidates({ agentDir: input.agentDir }).catch((error) => {
|
|
439
585
|
if (!(error instanceof Error) || error.message !== "memory_pending_directory_over_budget")
|
|
440
586
|
throw error;
|
|
441
587
|
// 自动提升是可选工作,目录查询预算不应阻断本轮已验证的更新或腾位操作。
|
|
@@ -446,6 +592,10 @@ export async function applyLocalMemoryResult(input) {
|
|
|
446
592
|
return [];
|
|
447
593
|
});
|
|
448
594
|
for (const pending of pendingCandidatesOnDisk) {
|
|
595
|
+
// Migration-deferred candidates require reviewed import; only capacity
|
|
596
|
+
// overflow is eligible for the existing automatic promotion path.
|
|
597
|
+
if (pending.reason !== "index_over_budget")
|
|
598
|
+
continue;
|
|
449
599
|
const change = pending.candidate;
|
|
450
600
|
if (change.operation !== "create")
|
|
451
601
|
continue;
|
|
@@ -460,7 +610,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
460
610
|
|| normalize(entry.summary) === normalize(change.summary)));
|
|
461
611
|
if (duplicate !== undefined) {
|
|
462
612
|
const duplicatePath = resolve(input.agentDir, duplicate.file);
|
|
463
|
-
const duplicateBody = readNoteBody(
|
|
613
|
+
const duplicateBody = readNoteBody(await readStoredNote(duplicatePath), duplicate.id);
|
|
464
614
|
if (duplicateBody === change.detailBody
|
|
465
615
|
|| duplicateBody === unwrappedNoteBody(change.title, change.detailBody).trim()) {
|
|
466
616
|
resolvedPendingIds.add(pending.pendingId);
|
|
@@ -478,7 +628,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
478
628
|
const notePath = resolve(input.agentDir, entry.file);
|
|
479
629
|
if (!isWithin(resolve(input.agentDir, "notes"), notePath))
|
|
480
630
|
throw new Error("local_memory_note_path_invalid");
|
|
481
|
-
const previous =
|
|
631
|
+
const previous = await readStoredNote(notePath);
|
|
482
632
|
const replacement = noteSection(id, 1, change.title, change.detailBody);
|
|
483
633
|
const existingSection = replaceNote(previous, id, replacement);
|
|
484
634
|
const next = existingSection ?? `${previous.trimEnd()}${previous.trimEnd() ? "\n\n" : ""}${replacement}`;
|
|
@@ -487,6 +637,17 @@ export async function applyLocalMemoryResult(input) {
|
|
|
487
637
|
createdIds.push(id);
|
|
488
638
|
resolvedPendingIds.add(pending.pendingId);
|
|
489
639
|
}
|
|
640
|
+
if (fullStorage) {
|
|
641
|
+
const changedMetadataIds = new Set([
|
|
642
|
+
...statusChangedIds,
|
|
643
|
+
...input.result.changes.flatMap(change => change.supersedesMemoryId === null ? [] : [change.supersedesMemoryId]),
|
|
644
|
+
]);
|
|
645
|
+
for (const id of changedMetadataIds) {
|
|
646
|
+
const entry = entries.find(item => item.id === id);
|
|
647
|
+
const notePath = resolve(input.agentDir, entry.file);
|
|
648
|
+
noteUpdates.set(notePath, updateNoteRevision(await readStoredNote(notePath), id, entry.revision));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
490
651
|
let finalProjection = projectIndex(memory, entries, allowActiveEviction ? pinnedIds(entries) : activeOrPinnedIds(entries));
|
|
491
652
|
if (finalProjection.reason !== null
|
|
492
653
|
&& (!allowActiveEviction || finalProjection.reason === "index_over_budget_pinned")) {
|
|
@@ -498,7 +659,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
498
659
|
.filter((entry) => entry?.status === "active");
|
|
499
660
|
for (const entry of omittedActiveEntries) {
|
|
500
661
|
const notePath = resolve(input.agentDir, entry.file);
|
|
501
|
-
const content =
|
|
662
|
+
const content = await readStoredNote(notePath);
|
|
502
663
|
const detailBody = readNoteBody(content, entry.id);
|
|
503
664
|
const change = detailBody === null ? null : archivedChange(entry, detailBody);
|
|
504
665
|
if (change === null)
|
|
@@ -519,6 +680,13 @@ export async function applyLocalMemoryResult(input) {
|
|
|
519
680
|
if (finalProjection.reason !== null)
|
|
520
681
|
throw new Error(finalProjection.reason);
|
|
521
682
|
}
|
|
683
|
+
// Complete every resource check before the first persistent write.
|
|
684
|
+
assertLocalMemoryStorageSize(finalProjection.memory);
|
|
685
|
+
for (const content of noteUpdates.values()) {
|
|
686
|
+
if (Buffer.byteLength(content, "utf8") > LOCAL_MEMORY_NOTE_BYTE_CAP)
|
|
687
|
+
throw new Error("local_memory_note_byte_limit");
|
|
688
|
+
}
|
|
689
|
+
await assertMemoryInventoryHealth(input.agentDir);
|
|
522
690
|
for (const candidate of pendingCandidates)
|
|
523
691
|
await persistPendingMemoryCandidate({ agentDir: input.agentDir, candidate });
|
|
524
692
|
for (const [path, content] of noteUpdates)
|
|
@@ -535,5 +703,5 @@ export async function applyLocalMemoryResult(input) {
|
|
|
535
703
|
deferredIds,
|
|
536
704
|
pendingIds: deferredIds,
|
|
537
705
|
};
|
|
538
|
-
});
|
|
706
|
+
}));
|
|
539
707
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { detectLocalMemoryStorageVersion } from "./storage-format.js";
|
|
2
|
+
/** Resolve format and rollout into one writer decision before a prune result is requested. */
|
|
3
|
+
export function resolveLocalMemoryWriterMode(workspace, rollout) {
|
|
4
|
+
if (rollout.legacyFallback)
|
|
5
|
+
return "legacy";
|
|
6
|
+
if (!rollout.enabled)
|
|
7
|
+
return rollout.deferred ? "blocked" : "legacy";
|
|
8
|
+
// Production workspaces always report this fact. Historical partial unit
|
|
9
|
+
// callers retain their already-resolved rollout witness.
|
|
10
|
+
if (workspace.localMemoryV2Initialized === undefined)
|
|
11
|
+
return "v2";
|
|
12
|
+
return detectLocalMemoryStorageVersion(workspace.memory) === 2 ? "v2" : "blocked";
|
|
13
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { lstat, readFile, readdir, 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,6 +8,7 @@ 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;
|
|
11
12
|
function errorCode(error) {
|
|
12
13
|
const code = error?.code;
|
|
13
14
|
return typeof code === "string" ? code : error instanceof Error ? error.name : "unknown";
|
|
@@ -39,30 +40,75 @@ async function inspectFile(path, signal) {
|
|
|
39
40
|
return { ...base, error_code: errorCode(error) };
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
+
async function inspectPendingDirectory(path, signal) {
|
|
44
|
+
let metadata;
|
|
45
|
+
try {
|
|
46
|
+
metadata = await lstat(path);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const code = errorCode(error);
|
|
50
|
+
return code === "ENOENT" ? { exists: false, entry_count: 0 } : { exists: false, error_code: code };
|
|
51
|
+
}
|
|
52
|
+
const base = { exists: true, mtime_ms: metadata.mtimeMs };
|
|
53
|
+
if (metadata.isSymbolicLink() || !metadata.isDirectory())
|
|
54
|
+
return { ...base, hash_skipped_reason: "not_regular_file" };
|
|
55
|
+
try {
|
|
56
|
+
const names = (await readdir(path, { withFileTypes: true }))
|
|
57
|
+
.filter(entry => entry.name.endsWith(".json"))
|
|
58
|
+
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
59
|
+
if (names.length > 128)
|
|
60
|
+
return { ...base, entry_count: names.length, error_code: "entry_limit" };
|
|
61
|
+
const hash = createHash("sha256");
|
|
62
|
+
let size = 0;
|
|
63
|
+
for (const entry of names) {
|
|
64
|
+
if (signal.aborted)
|
|
65
|
+
throw signal.reason;
|
|
66
|
+
if (!entry.isFile() || entry.isSymbolicLink())
|
|
67
|
+
return { ...base, entry_count: names.length, error_code: "invalid_entry" };
|
|
68
|
+
const fact = await inspectFile(join(path, entry.name), signal);
|
|
69
|
+
if (!fact.exists || fact.sha256 === undefined || fact.error_code !== undefined
|
|
70
|
+
|| fact.hash_skipped_reason !== undefined)
|
|
71
|
+
return { ...base, entry_count: names.length, error_code: "incomplete_entry" };
|
|
72
|
+
size += fact.size_bytes ?? 0;
|
|
73
|
+
hash.update(entry.name).update("\0").update(fact.sha256).update("\0");
|
|
74
|
+
}
|
|
75
|
+
return { ...base, size_bytes: size, entry_count: names.length, sha256: hash.digest("hex") };
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return { ...base, error_code: errorCode(error) };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export async function withinLocalMemoryDiagnosticsDeadline(operation, timeoutMs) {
|
|
43
82
|
const controller = new AbortController();
|
|
44
83
|
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 }));
|
|
49
84
|
const timeout = new Promise((_resolve, reject) => {
|
|
50
85
|
timer = setTimeout(() => {
|
|
51
|
-
|
|
52
|
-
reject(Object.assign(new Error("local memory diagnostics timed out"), {
|
|
86
|
+
const error = Object.assign(new Error("local memory diagnostics timed out"), {
|
|
53
87
|
code: "diagnostics_timeout",
|
|
54
|
-
})
|
|
88
|
+
});
|
|
89
|
+
controller.abort(error);
|
|
90
|
+
reject(error);
|
|
55
91
|
}, Math.max(1, timeoutMs));
|
|
56
92
|
timer.unref?.();
|
|
57
93
|
});
|
|
58
94
|
try {
|
|
59
|
-
return await Promise.race([
|
|
95
|
+
return await Promise.race([operation(controller.signal), timeout]);
|
|
60
96
|
}
|
|
61
97
|
finally {
|
|
62
98
|
if (timer !== undefined)
|
|
63
99
|
clearTimeout(timer);
|
|
64
100
|
}
|
|
65
101
|
}
|
|
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
|
+
}
|
|
66
112
|
export function localMemoryContentFacts(content, maxScanBytes = LOCAL_MEMORY_HASH_CAP_BYTES) {
|
|
67
113
|
const sizeBytes = Buffer.byteLength(content, "utf8");
|
|
68
114
|
if (sizeBytes > Math.max(0, maxScanBytes)) {
|
|
@@ -76,12 +122,12 @@ export function localMemoryContentFacts(content, maxScanBytes = LOCAL_MEMORY_HAS
|
|
|
76
122
|
lessons_reference_count: content.match(/\bnotes[\\/]lessons\.md\b/gi)?.length ?? 0,
|
|
77
123
|
};
|
|
78
124
|
}
|
|
79
|
-
export function analyzeLocalMemoryInjection(systemPrompt, sourceMemory) {
|
|
125
|
+
export function analyzeLocalMemoryInjection(systemPrompt, sourceMemory, query = "") {
|
|
80
126
|
const sourceBytes = Buffer.byteLength(sourceMemory, "utf8");
|
|
81
127
|
if (sourceMemory.length === 0) {
|
|
82
128
|
return { state: "empty_source", source_bytes: 0, bounded_bytes: 0, injected_bytes: 0 };
|
|
83
129
|
}
|
|
84
|
-
const bounded = capMemoryForInject(sourceMemory);
|
|
130
|
+
const bounded = capMemoryForInject(sourceMemory, undefined, Number.POSITIVE_INFINITY, query);
|
|
85
131
|
const marker = INJECTION_MARKERS.find((candidate) => systemPrompt.includes(candidate));
|
|
86
132
|
if (marker === undefined) {
|
|
87
133
|
return {
|
|
@@ -217,15 +263,50 @@ function crewMemoryReadAccess(command, paths) {
|
|
|
217
263
|
return null;
|
|
218
264
|
}
|
|
219
265
|
}
|
|
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
|
+
}
|
|
220
300
|
function commandAccesses(command, paths) {
|
|
221
|
-
|
|
301
|
+
const unwrapped = unwrapLiteralShellCommand(command);
|
|
302
|
+
if (unwrapped === null || /[;|&\n]/.test(unwrapped))
|
|
222
303
|
return [];
|
|
223
304
|
const accesses = new Map();
|
|
224
|
-
const memoryRead = crewMemoryReadAccess(
|
|
305
|
+
const memoryRead = crewMemoryReadAccess(unwrapped, paths);
|
|
225
306
|
if (memoryRead !== null) {
|
|
226
307
|
accesses.set(`${memoryRead.target}:${memoryRead.relative_path ?? ""}:read`, memoryRead);
|
|
227
308
|
}
|
|
228
|
-
const expanded =
|
|
309
|
+
const expanded = unwrapped
|
|
229
310
|
.replaceAll("${CREW_HOME}", paths.homeDir)
|
|
230
311
|
.replaceAll("$CREW_HOME", paths.homeDir)
|
|
231
312
|
.replace(/["']/g, "");
|
|
@@ -38,6 +38,7 @@ 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 };
|
|
41
42
|
return {
|
|
42
43
|
memory_exists: snapshot.memory.exists,
|
|
43
44
|
memory_size_bytes: snapshot.memory.size_bytes,
|
|
@@ -54,6 +55,12 @@ function snapshotFields(snapshot) {
|
|
|
54
55
|
lessons_hash_skipped_reason: snapshot.lessons.hash_skipped_reason,
|
|
55
56
|
lessons_error_code: snapshot.lessons.error_code,
|
|
56
57
|
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,
|
|
57
64
|
};
|
|
58
65
|
}
|
|
59
66
|
function promptDelivery(runtime, stableProtocolRuntime) {
|
|
@@ -103,7 +110,7 @@ export function createLocalMemoryTelemetry(options) {
|
|
|
103
110
|
contextPrepared(context) {
|
|
104
111
|
try {
|
|
105
112
|
const sourceFacts = localMemoryContentFacts(context.memory);
|
|
106
|
-
const injectionFacts = analyzeLocalMemoryInjection(context.systemPrompt, context.memory);
|
|
113
|
+
const injectionFacts = analyzeLocalMemoryInjection(context.systemPrompt, context.memory, context.memoryQuery);
|
|
107
114
|
safeLog("local_memory.context_prepared", "本地记忆上下文已准备", {
|
|
108
115
|
execution_id: options.executionId,
|
|
109
116
|
agent_handle: options.agentHandle,
|
|
@@ -183,10 +190,13 @@ export function createLocalMemoryTelemetry(options) {
|
|
|
183
190
|
const lessonsChanged = beforeSnapshot !== null && afterSnapshot !== null
|
|
184
191
|
? localMemoryFileChanged(beforeSnapshot.lessons, afterSnapshot.lessons)
|
|
185
192
|
: undefined;
|
|
193
|
+
const pendingChanged = beforeSnapshot !== null && afterSnapshot !== null
|
|
194
|
+
? localMemoryFileChanged(beforeSnapshot.pending ?? { exists: false }, afterSnapshot.pending ?? { exists: false })
|
|
195
|
+
: undefined;
|
|
186
196
|
const accessSummary = accessObserver.summary();
|
|
187
|
-
const sharedFilesChanged = memoryChanged === undefined || lessonsChanged === undefined
|
|
197
|
+
const sharedFilesChanged = memoryChanged === undefined || lessonsChanged === undefined || pendingChanged === undefined
|
|
188
198
|
? undefined
|
|
189
|
-
: memoryChanged || lessonsChanged;
|
|
199
|
+
: memoryChanged || lessonsChanged || pendingChanged;
|
|
190
200
|
safeLog("local_memory.execution_observed", "本地记忆执行观察已完成", {
|
|
191
201
|
execution_id: options.executionId,
|
|
192
202
|
agent_handle: options.agentHandle,
|
|
@@ -200,6 +210,7 @@ export function createLocalMemoryTelemetry(options) {
|
|
|
200
210
|
executor_outcome: executorCompleted ? "succeeded" : "failed",
|
|
201
211
|
memory_changed: memoryChanged,
|
|
202
212
|
lessons_changed: lessonsChanged,
|
|
213
|
+
pending_changed: pendingChanged,
|
|
203
214
|
shared_files_changed_during_execution: sharedFilesChanged,
|
|
204
215
|
unexpected_shared_write: options.isMemoryPrune
|
|
205
216
|
? undefined
|
|
@@ -224,6 +235,12 @@ export function createLocalMemoryTelemetry(options) {
|
|
|
224
235
|
lessons_after_hash_skipped_reason: afterSnapshot.lessons.hash_skipped_reason,
|
|
225
236
|
lessons_after_error_code: afterSnapshot.lessons.error_code,
|
|
226
237
|
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,
|
|
227
244
|
}),
|
|
228
245
|
});
|
|
229
246
|
},
|
package/dist/machine-info.js
CHANGED
|
@@ -25,6 +25,7 @@ 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";
|
|
28
29
|
export { executableRuntimes } from "./runtime-capabilities.js";
|
|
29
30
|
const execFileP = promisify(execFile);
|
|
30
31
|
export const PROJECT_WORKSPACES_CAPABILITY = "project_workspaces_v1";
|
|
@@ -40,6 +41,7 @@ export const NATIVE_PROJECT_WORKSPACE_READINESS = Object.freeze({
|
|
|
40
41
|
});
|
|
41
42
|
export const DAEMON_CAPABILITIES = Object.freeze([
|
|
42
43
|
MARKETPLACE_CAPABILITY,
|
|
44
|
+
MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY,
|
|
43
45
|
"scheduled_job_v1",
|
|
44
46
|
"reply_origin_v1",
|
|
45
47
|
"origin_decision_v1",
|
|
@@ -75,6 +77,7 @@ export const daemonCapabilities = (runtimePlatform = process.platform, readiness
|
|
|
75
77
|
return Object.freeze(DAEMON_CAPABILITIES.filter((capability) => ((capability !== MARKETPLACE_CAPABILITY || nativeWorkspacePlatform)
|
|
76
78
|
&& (capability !== PROJECT_WORKSPACES_CAPABILITY || nativeWorkspaceReady)
|
|
77
79
|
&& (capability !== "message_file_preview_v1" || nativeWorkspacePlatform)
|
|
80
|
+
&& (nativeWorkspacePlatform || ![MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY].includes(capability))
|
|
78
81
|
&& (nativeWorkspacePlatform
|
|
79
82
|
|| (capability !== "project_skills_v1"
|
|
80
83
|
&& 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({
|