@nowcrew/daemon 0.6.99-beta.3 → 0.6.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +9 -4
  2. package/dist/cloud-report-upload.js +10 -1
  3. package/dist/computer-main.js +0 -0
  4. package/dist/computer-service.js +30 -5
  5. package/dist/execution-journal.js +1 -3
  6. package/dist/execution-protocol.js +1 -1
  7. package/dist/execution-runner.js +1 -4
  8. package/dist/local-execution-prompt.js +3 -8
  9. package/dist/local-executor.js +39 -64
  10. package/dist/local-memory/index-budget.js +3 -9
  11. package/dist/local-memory/inventory-cli.js +8 -52
  12. package/dist/local-memory/inventory-source.js +8 -15
  13. package/dist/local-memory/pending-query.js +2 -5
  14. package/dist/local-memory/pending-schema.js +1 -1
  15. package/dist/local-memory/pending.js +2 -16
  16. package/dist/local-memory/prompt.js +5 -21
  17. package/dist/local-memory/read-cli.js +76 -98
  18. package/dist/local-memory/rollout.js +11 -79
  19. package/dist/local-memory/runtime.js +13 -181
  20. package/dist/local-memory/write.js +21 -189
  21. package/dist/local-memory-diagnostics.js +15 -96
  22. package/dist/local-memory-telemetry.js +3 -20
  23. package/dist/machine-info.js +0 -3
  24. package/dist/main.js +0 -0
  25. package/dist/marketplace-protocol.js +1 -1
  26. package/dist/marketplace-remote.js +1 -1
  27. package/dist/marketplace-runner.js +1 -1
  28. package/dist/memory-prune-diagnostics.js +4 -60
  29. package/dist/message-file-preview.js +1 -11
  30. package/dist/prompt.js +17 -9
  31. package/dist/runner.js +1 -1
  32. package/dist/runtimes/claude-thread-runner.js +4 -25
  33. package/dist/serve.js +3 -38
  34. package/dist/token.js +0 -1
  35. package/dist/workspace-control-handler.js +3 -16
  36. package/dist/workspace.js +2 -13
  37. package/package.json +10 -8
  38. package/dist/local-memory/commit-telemetry.js +0 -30
  39. package/dist/local-memory/execution-workspace.js +0 -62
  40. package/dist/local-memory/full-index-search.js +0 -47
  41. package/dist/local-memory/initialization-identity.js +0 -36
  42. package/dist/local-memory/inventory-v2.js +0 -362
  43. package/dist/local-memory/legacy-adapter.js +0 -233
  44. package/dist/local-memory/migration-access.js +0 -242
  45. package/dist/local-memory/migration-cli.js +0 -54
  46. package/dist/local-memory/migration-io.js +0 -199
  47. package/dist/local-memory/migration-pending.js +0 -9
  48. package/dist/local-memory/migration-plan.js +0 -432
  49. package/dist/local-memory/migration-schema.js +0 -55
  50. package/dist/local-memory/migration-state-invariants.js +0 -65
  51. package/dist/local-memory/migration-store.js +0 -414
  52. package/dist/local-memory/migration-tool-entry.js +0 -21
  53. package/dist/local-memory/source-path.js +0 -13
  54. package/dist/local-memory/storage-format.js +0 -249
  55. package/dist/local-memory/writer-mode.js +0 -13
  56. package/dist/memory-migration-controller.js +0 -89
  57. package/dist/memory-migration-inspect.js +0 -108
  58. package/dist/memory-migration-journal.js +0 -14
  59. package/dist/memory-migration-protocol.js +0 -14
  60. package/dist/memory-migration-runtime.js +0 -305
@@ -1,249 +0,0 @@
1
- import { LOCAL_MEMORY_INDEX_CODE_POINT_CAP, LOCAL_MEMORY_INDEX_ENTRY_CAP, LOCAL_MEMORY_TOTAL_CODE_POINT_CAP, renderGeneratedIndex, renderLocalMemoryIndexCard, selectLocalMemoryIndexEntries, } from "./index-budget.js";
2
- export const LOCAL_MEMORY_STORAGE_BYTE_CAP = 16 * 1024 * 1024;
3
- export const LOCAL_MEMORY_STORAGE_ENTRY_CAP = 10_000;
4
- export const LOCAL_MEMORY_NOTE_BYTE_CAP = 32 * 1024 * 1024;
5
- export const LOCAL_MEMORY_READ_BYTE_CAP = 64 * 1024 * 1024;
6
- export const LOCAL_MEMORY_GENERATED_INDEX_PATTERN = /<!--\s*NOWWORK:MEMORY_INDEX:BEGIN\s+schema=[12]\s*-->[\s\S]*?<!--\s*NOWWORK:MEMORY_INDEX:END\s*-->/u;
7
- const LEGACY_INDEX_HEADING = /^##[\t ]+Knowledge Index[\t ]*\r?\n/mu;
8
- const LEGACY_NEXT_SECTION = /^##[\t ]+(?!#)/mu;
9
- const LEGACY_CARD_HEADING = /^###[\t ]+(.+?)[\t ]*\r?$/gmu;
10
- const MEMORY_PREVIEW_NOTICE = "[MEMORY.md preview truncated; use crew-memory search/read for more.]\n";
11
- const length = (text) => {
12
- let count = 0;
13
- for (const _character of text)
14
- count += 1;
15
- return count;
16
- };
17
- function queryTerms(query) {
18
- const normalized = query.normalize("NFKC").toLowerCase();
19
- // 空白切词对中文混排请求只会产生一个无法命中的长 token;同时提取英文/数字
20
- // 子 token,让 legacy 和 V2 预览共用同一套召回规则。
21
- const whitespaceTokens = normalized.split(/\s+/u).filter(Boolean);
22
- const alnumTokens = Array.from(normalized.matchAll(/[a-z0-9]{2,}/gu)).map((match) => match[0]);
23
- return Array.from(new Set([...whitespaceTokens, ...alnumTokens]));
24
- }
25
- function fitsLegacyPreview(text, maxUnits, maxBytes) {
26
- return text.length <= maxUnits && Buffer.byteLength(text, "utf8") <= maxBytes;
27
- }
28
- function truncateLegacyText(text, maxUnits, maxBytes) {
29
- if (maxUnits <= 0 || maxBytes <= 0)
30
- return "";
31
- let result = "";
32
- let units = 0;
33
- let bytes = 0;
34
- for (const character of text) {
35
- const characterBytes = Buffer.byteLength(character, "utf8");
36
- if (units + character.length > maxUnits || bytes + characterBytes > maxBytes)
37
- break;
38
- result += character;
39
- units += character.length;
40
- bytes += characterBytes;
41
- }
42
- return result;
43
- }
44
- /**
45
- * Legacy MEMORY.md has no schema-2 generated index, so the old path used to
46
- * inject only the first 6k UTF-16 units and could hide a matching late card.
47
- * Keep identity/preamble intact, then select complete legacy cards by query
48
- * while retaining the same UTF-16 and UTF-8 byte bounds.
49
- */
50
- export function selectLegacyMemoryPreview(memory, query = "", options = {}) {
51
- // Keep legacy behavior: MEMORY_INJECT_CAP historically counted UTF-16 units.
52
- const maxUnits = Math.min(LOCAL_MEMORY_TOTAL_CODE_POINT_CAP, options.maxCodePoints ?? Infinity);
53
- const maxBytes = options.maxBytes ?? Infinity;
54
- if (fitsLegacyPreview(memory, maxUnits, maxBytes))
55
- return memory;
56
- if (!fitsLegacyPreview(MEMORY_PREVIEW_NOTICE, maxUnits, maxBytes))
57
- return "";
58
- const heading = LEGACY_INDEX_HEADING.exec(memory);
59
- if (heading?.index === undefined) {
60
- const noticeBytes = Buffer.byteLength(MEMORY_PREVIEW_NOTICE, "utf8");
61
- return `${MEMORY_PREVIEW_NOTICE}${truncateLegacyText(memory, maxUnits - MEMORY_PREVIEW_NOTICE.length, maxBytes - noticeBytes)}`;
62
- }
63
- const indexStart = heading.index + heading[0].length;
64
- const rest = memory.slice(indexStart);
65
- const nextSection = LEGACY_NEXT_SECTION.exec(rest);
66
- const indexEnd = nextSection?.index === undefined ? memory.length : indexStart + nextSection.index;
67
- const indexContent = memory.slice(indexStart, indexEnd);
68
- const masked = indexContent.replace(/<!--[\s\S]*?-->/gu, (comment) => comment.replace(/[^\r\n]/gu, " "));
69
- const headings = [...masked.matchAll(LEGACY_CARD_HEADING)];
70
- if (headings.length === 0) {
71
- const noticeBytes = Buffer.byteLength(MEMORY_PREVIEW_NOTICE, "utf8");
72
- return `${MEMORY_PREVIEW_NOTICE}${truncateLegacyText(memory, maxUnits - MEMORY_PREVIEW_NOTICE.length, maxBytes - noticeBytes)}`;
73
- }
74
- const terms = queryTerms(query);
75
- const cards = headings.map((card, index) => {
76
- const start = card.index ?? 0;
77
- const end = headings[index + 1]?.index ?? indexContent.length;
78
- const text = indexContent.slice(start, end);
79
- const searchable = text.normalize("NFKC").toLowerCase();
80
- return {
81
- text,
82
- index,
83
- relevance: terms.filter((term) => searchable.includes(term)).length,
84
- };
85
- });
86
- const ranked = [...cards].sort((left, right) => right.relevance - left.relevance || left.index - right.index);
87
- const firstCard = headings[0]?.index ?? 0;
88
- const preamble = indexContent.slice(0, firstCard);
89
- const before = memory.slice(0, indexStart);
90
- const suffix = memory.slice(indexEnd);
91
- const fixed = `${MEMORY_PREVIEW_NOTICE}${before}${preamble}`;
92
- if (!fitsLegacyPreview(fixed, maxUnits, maxBytes)) {
93
- const noticeBytes = Buffer.byteLength(MEMORY_PREVIEW_NOTICE, "utf8");
94
- return `${MEMORY_PREVIEW_NOTICE}${truncateLegacyText(memory, maxUnits - MEMORY_PREVIEW_NOTICE.length, maxBytes - noticeBytes)}`;
95
- }
96
- let selected = fixed;
97
- let selectedCount = 0;
98
- for (const card of ranked) {
99
- const candidate = `${selected}${card.text}`;
100
- if (!fitsLegacyPreview(candidate, maxUnits, maxBytes))
101
- continue;
102
- selected = candidate;
103
- selectedCount += 1;
104
- }
105
- if (selectedCount === 0)
106
- return fixed;
107
- const withSuffix = `${selected}${suffix}`;
108
- return fitsLegacyPreview(withSuffix, maxUnits, maxBytes) ? withSuffix : selected;
109
- }
110
- const TYPES = new Set(["procedural", "semantic", "episodic", "preference"]);
111
- const STATUSES = new Set(["candidate", "active", "pinned", "stale", "superseded", "archived"]);
112
- const PRIORITIES = new Set(["high", "medium", "low"]);
113
- const FIELDS = new Set(["id", "file", "type", "status", "summary", "triggers", "priority", "revision", "anchor", "updated", "review_after"]);
114
- /** A malformed/unknown marker is never treated as an unmarked legacy file. */
115
- export function detectLocalMemoryStorageVersion(memory) {
116
- const markerCount = (memory.match(/NOWWORK:MEMORY_INDEX:/gu) ?? []).length;
117
- if (markerCount === 0)
118
- return 0;
119
- const block = LOCAL_MEMORY_GENERATED_INDEX_PATTERN.exec(memory);
120
- if (markerCount !== 2 || block?.index === undefined)
121
- throw new Error("local_memory_storage_version_invalid");
122
- const headings = [...memory.matchAll(/^##[\t ]+Knowledge Index[\t ]*\r?$/gmu)];
123
- const heading = headings[0];
124
- if (headings.length !== 1 || heading?.index === undefined)
125
- throw new Error("local_memory_storage_index_invalid");
126
- const sectionStart = heading.index + heading[0].length;
127
- const nextSection = /^##[\t ]+(?!#)/mu.exec(memory.slice(sectionStart));
128
- const sectionEnd = nextSection?.index === undefined ? memory.length : sectionStart + nextSection.index;
129
- if (block.index < sectionStart || block.index + block[0].length > sectionEnd) {
130
- throw new Error("local_memory_storage_index_invalid");
131
- }
132
- return /schema=2\s*-->/u.test(block[0].slice(0, block[0].indexOf("\n") + 1) || block[0]) ? 2 : 1;
133
- }
134
- function validateEntry(entry) {
135
- const singleLine = (value) => !/[\r\n\u0000-\u001f\u007f]/u.test(value)
136
- && !/<!--/u.test(value);
137
- if (!entry.title.trim() || !entry.summary.trim()
138
- || ![entry.title, entry.summary, entry.updated, ...entry.triggers].every(singleLine)
139
- || !/^[A-Za-z0-9_-]+$/u.test(entry.id) || entry.anchor !== entry.id
140
- || !/^notes\/(?:preferences|project-rules|testing|debugging|git|incidents|lessons)\.md$/u.test(entry.file)
141
- || !TYPES.has(entry.type) || !STATUSES.has(entry.status) || !PRIORITIES.has(entry.priority)
142
- || !Number.isSafeInteger(entry.revision) || entry.revision < 1
143
- || !entry.updated.trim()
144
- || (entry.reviewAfter !== null && !singleLine(entry.reviewAfter))) {
145
- throw new Error("local_memory_storage_entry_invalid");
146
- }
147
- }
148
- function validateEntries(entries) {
149
- if (entries.length > LOCAL_MEMORY_STORAGE_ENTRY_CAP)
150
- throw new Error("local_memory_storage_entry_limit");
151
- const ids = new Set();
152
- for (const entry of entries) {
153
- validateEntry(entry);
154
- if (ids.has(entry.id))
155
- throw new Error("local_memory_storage_duplicate_id");
156
- ids.add(entry.id);
157
- }
158
- }
159
- export function assertLocalMemoryStorageSize(memory) {
160
- if (Buffer.byteLength(memory, "utf8") > LOCAL_MEMORY_STORAGE_BYTE_CAP)
161
- throw new Error("local_memory_storage_byte_limit");
162
- }
163
- /** schema=2 stores every lifecycle state, unlike the schema=1 prompt projection. */
164
- export function parseFullMemoryIndex(memory) {
165
- assertLocalMemoryStorageSize(memory);
166
- if (detectLocalMemoryStorageVersion(memory) !== 2)
167
- throw new Error("local_memory_storage_version_invalid");
168
- const block = LOCAL_MEMORY_GENERATED_INDEX_PATTERN.exec(memory)[0];
169
- const body = block.replace(/^<!--[^>]*-->\s*/u, "").replace(/<!--[^>]*-->$/u, "");
170
- const headings = [...body.matchAll(/^###[\t ]+(.+?)[\t ]*\r?$/gmu)];
171
- if (headings.length > LOCAL_MEMORY_STORAGE_ENTRY_CAP)
172
- throw new Error("local_memory_storage_entry_limit");
173
- if (body.slice(0, headings[0]?.index ?? body.length).trim())
174
- throw new Error("local_memory_storage_entry_invalid");
175
- const entries = headings.map((heading, index) => {
176
- const fields = {};
177
- const tail = body.slice(heading.index + heading[0].length, headings[index + 1]?.index ?? body.length);
178
- for (const line of tail.split(/\r?\n/u)) {
179
- if (!line.trim())
180
- continue;
181
- const field = /^-\s+([a-z][a-z_]*)\s*:\s*(.*?)\s*$/u.exec(line);
182
- if (!field || !FIELDS.has(field[1]) || fields[field[1]] !== undefined)
183
- throw new Error("local_memory_storage_entry_invalid");
184
- fields[field[1]] = field[2];
185
- }
186
- if ([...FIELDS].filter(field => field !== "review_after").some(field => fields[field] === undefined)) {
187
- throw new Error("local_memory_storage_entry_invalid");
188
- }
189
- if (!/^[1-9][0-9]*$/u.test(fields.revision))
190
- throw new Error("local_memory_storage_entry_invalid");
191
- return {
192
- id: fields.id, title: heading[1], file: fields.file, type: fields.type,
193
- status: fields.status, summary: fields.summary,
194
- triggers: fields.triggers.split(/[,,、]/u).map(value => value.trim()).filter(Boolean),
195
- priority: fields.priority, revision: Number(fields.revision),
196
- anchor: fields.anchor, updated: fields.updated, reviewAfter: fields.review_after ?? null,
197
- };
198
- });
199
- validateEntries(entries);
200
- const section = memory.slice(memory.indexOf("## Knowledge Index") + "## Knowledge Index".length);
201
- const remaining = section.replace(LOCAL_MEMORY_GENERATED_INDEX_PATTERN, "").split(/^##[\t ]+(?!#)/mu)[0] ?? "";
202
- if (/^###[\t ]+/mu.test(remaining.replace(/<!--[\s\S]*?-->/gu, "")))
203
- throw new Error("local_memory_legacy_index_requires_migration");
204
- return entries;
205
- }
206
- export function renderFullMemoryIndex(entries) {
207
- validateEntries(entries);
208
- const block = `<!-- NOWWORK:MEMORY_INDEX:BEGIN schema=2 -->\n${entries.map(renderLocalMemoryIndexCard).join("\n")}<!-- NOWWORK:MEMORY_INDEX:END -->`;
209
- assertLocalMemoryStorageSize(block);
210
- return block;
211
- }
212
- export function replaceFullMemoryIndex(memory, entries) {
213
- if (detectLocalMemoryStorageVersion(memory) !== 2)
214
- throw new Error("local_memory_storage_version_invalid");
215
- const updated = memory.replace(LOCAL_MEMORY_GENERATED_INDEX_PATTERN, () => renderFullMemoryIndex(entries));
216
- assertLocalMemoryStorageSize(updated);
217
- return updated;
218
- }
219
- export function selectLocalMemoryPreview(memory, query = "", options = {}) {
220
- const entries = parseFullMemoryIndex(memory);
221
- const totalActiveEntries = entries.filter(entry => entry.status === "active" || entry.status === "pinned").length;
222
- const emptyIndex = renderGeneratedIndex([]);
223
- // Reserve the largest count label before selecting; all discovery text counts toward the same cap.
224
- const hint = (shown) => `\nMemory preview: ${shown}/${totalActiveEntries} active entries. Use crew-memory search --query <text> and crew-memory read --file <file> for more.\n`;
225
- const base = memory.replace(LOCAL_MEMORY_GENERATED_INDEX_PATTERN, () => `${emptyIndex}${hint(totalActiveEntries)}`);
226
- const remaining = Math.min(LOCAL_MEMORY_TOTAL_CODE_POINT_CAP, options.maxCodePoints ?? Infinity) - length(base);
227
- const remainingBytes = (options.maxBytes ?? Infinity) - Buffer.byteLength(base, "utf8");
228
- if (remaining < 0 || remainingBytes < 0 || Number.isNaN(remaining) || Number.isNaN(remainingBytes)) {
229
- throw new Error("local_memory_identity_budget_exceeded");
230
- }
231
- const terms = queryTerms(query);
232
- const selection = selectLocalMemoryIndexEntries(entries, {
233
- entryCap: LOCAL_MEMORY_INDEX_ENTRY_CAP,
234
- indexCodePointCap: Math.min(LOCAL_MEMORY_INDEX_CODE_POINT_CAP - length(emptyIndex), remaining),
235
- indexByteCap: remainingBytes,
236
- }, {
237
- protectedIds: new Set(entries.filter(entry => entry.status === "pinned").map(entry => entry.id)),
238
- relevance: entry => {
239
- const searchable = `${entry.title} ${entry.summary} ${entry.triggers.join(" ")}`.normalize("NFKC").toLowerCase();
240
- return terms.filter(term => searchable.includes(term)).length;
241
- },
242
- });
243
- if (selection.reason === "index_over_budget_pinned")
244
- throw new Error("local_memory_pinned_budget_exceeded");
245
- return {
246
- ...selection, totalActiveEntries,
247
- memory: memory.replace(LOCAL_MEMORY_GENERATED_INDEX_PATTERN, () => `${renderGeneratedIndex(selection.entries)}${hint(selection.entries.length)}`),
248
- };
249
- }
@@ -1,13 +0,0 @@
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,89 +0,0 @@
1
- import { z } from "zod";
2
- import { createRuntimeCancellation } from "./runtime-cancellation.js";
3
- import { MemoryMigrationStartSchema } from "./memory-migration-protocol.js";
4
- import { runMemoryMigration } from "./memory-migration-runtime.js";
5
- import { inspectMemoryForMigration } from "./memory-migration-inspect.js";
6
- const Inspect = MemoryMigrationStartSchema.pick({ reqId: true, protocolVersion: true, workspaceId: true,
7
- agentId: true, handle: true, machineId: true }).extend({ type: z.literal("memory:migration:inspect"), executionId: z.string().uuid().optional() }).strict();
8
- export function createMemoryMigrationController(input) {
9
- let identity = null;
10
- const runs = new Map();
11
- return {
12
- setIdentity(value) { identity = value; },
13
- clearIdentity() { identity = null; },
14
- getIdentity() { return identity === null ? null : { ...identity }; },
15
- cancel(executionId) { const run = runs.get(executionId); if (!run)
16
- return false; run.cancel(); return true; },
17
- async handle(raw) {
18
- const type = raw?.type;
19
- if (type !== "memory:migration:start" && type !== "memory:migration:inspect")
20
- return false;
21
- const parsed = type === "memory:migration:start" ? MemoryMigrationStartSchema.safeParse(raw) : Inspect.safeParse(raw);
22
- if (!parsed.success) {
23
- const reqId = raw.reqId;
24
- if (typeof reqId === "string" && reqId.length <= 200)
25
- input.send({ type: "fs:result", reqId, ok: false, error: "memory_migration_invalid_request" });
26
- return true;
27
- }
28
- const request = parsed.data;
29
- if (!identity || request.workspaceId !== identity.workspaceId || request.machineId !== identity.machineId) {
30
- input.send({ type: "fs:result", reqId: request.reqId, ok: false, error: "memory_migration_identity_mismatch" });
31
- return true;
32
- }
33
- let journal;
34
- try {
35
- journal = input.journal();
36
- }
37
- catch {
38
- input.send({ type: "fs:result", reqId: request.reqId, ok: false, error: "memory_migration_journal_unavailable" });
39
- return true;
40
- }
41
- if (request.type === "memory:migration:inspect") {
42
- try {
43
- const data = await inspectMemoryForMigration(input.config.agentsRoot, {
44
- ...request,
45
- serverUrl: input.config.serverUrl,
46
- }, journal);
47
- input.send({ type: "fs:result", reqId: request.reqId, ok: true, data });
48
- }
49
- catch {
50
- input.send({ type: "fs:result", reqId: request.reqId, ok: false, error: "memory_migration_inspection_failed" });
51
- }
52
- return true;
53
- }
54
- const existing = runs.get(request.executionId);
55
- if (existing) {
56
- input.send({ type: "fs:result", reqId: request.reqId, ok: true, data: { kind: "running" } });
57
- return true;
58
- }
59
- if (input.isAdmissionClosed?.()) {
60
- input.send({ type: "fs:result", reqId: request.reqId, ok: false, error: "memory_migration_storage_busy" });
61
- return true;
62
- }
63
- const cancellation = createRuntimeCancellation();
64
- const promise = runMemoryMigration(request, {
65
- agentsRoot: input.config.agentsRoot, coordinator: input.coordinator, journal,
66
- cancellation: cancellation.cancellation, send: input.send,
67
- control: async (step, body) => {
68
- const base = `/daemon/memory-migrations/${encodeURIComponent(request.agentId)}`;
69
- const path = step === "history" ? `${base}/history` : `${base}/${request.migrationId}/${step === "needs-review" ? "review" : step}`;
70
- const response = await fetch(new URL(path, input.config.serverUrl), {
71
- method: step === "history" ? "GET" : "POST", signal: AbortSignal.timeout(10_000),
72
- headers: { authorization: `Bearer ${input.config.machineToken}`, "content-type": "application/json" },
73
- ...(step === "history" ? {} : { body: JSON.stringify(body) }),
74
- });
75
- const result = await response.json();
76
- if (!response.ok || result.success !== true)
77
- throw new Error("migration_control_unconfirmed");
78
- return result.data;
79
- },
80
- }).then(data => input.send({ type: "fs:result", reqId: request.reqId, ok: true, data }))
81
- .catch(() => input.send({ type: "fs:result", reqId: request.reqId, ok: false, error: "memory_migration_failed" }))
82
- .finally(() => runs.delete(request.executionId));
83
- runs.set(request.executionId, { promise, cancel: cancellation.request });
84
- return true;
85
- },
86
- async stop() { for (const run of runs.values())
87
- run.cancel(); await Promise.allSettled([...runs.values()].map(run => run.promise)); },
88
- };
89
- }
@@ -1,108 +0,0 @@
1
- import { lstat, readFile, realpath, readdir } from "node:fs/promises";
2
- import { join, resolve } from "node:path";
3
- import { createHash } from "node:crypto";
4
- import { withMemoryAccess } from "./local-memory/migration-access.js";
5
- import { MigrationStateSchema } from "./local-memory/migration-schema.js";
6
- import { assertMigrationStateIntegrity } from "./local-memory/migration-state-invariants.js";
7
- import { detectLocalMemoryStorageVersion, parseFullMemoryIndex } from "./local-memory/storage-format.js";
8
- import { resolveLocalMemoryV2Rollout } from "./local-memory/rollout.js";
9
- import { readMemoryInventorySource, inventoryLimits } from "./local-memory/inventory-source.js";
10
- import { isLocalMemoryInitializationIdentity, localMemoryInitializationIdentity } from "./local-memory/initialization-identity.js";
11
- export async function inspectMemoryForMigration(agentsRoot, input, journal) {
12
- const parent = await realpath(agentsRoot);
13
- const path = join(parent, input.handle);
14
- const missingBase = { workspaceId: input.workspaceId, agentId: input.agentId, machineId: input.machineId,
15
- storageRootFingerprint: createHash("sha256").update(resolve(path)).digest("hex"),
16
- ownerIdentity: String((await lstat(parent)).uid) };
17
- const prior = input.executionId ? await journal?.get(input.executionId) : null;
18
- const stoppedExecutionId = prior?.requestedRuntime === "memory_migration" && (prior.state === "completed" || prior.state === "interrupted") ? prior.executionId : null;
19
- const missing = { ...missingBase, storageVersion: null, classification: "empty", emptyWithoutResidue: true, localState: null, issueCodes: [], stoppedExecutionId };
20
- try {
21
- await lstat(path);
22
- }
23
- catch (e) {
24
- if (e.code === "ENOENT")
25
- return missing;
26
- throw e;
27
- }
28
- const root = await realpath(path);
29
- const base = { ...missingBase,
30
- storageRootFingerprint: createHash("sha256").update(root).digest("hex"),
31
- ownerIdentity: String((await lstat(root)).uid) };
32
- const empty = { ...missing, ...base };
33
- try {
34
- return await withMemoryAccess(path, async () => {
35
- let localState = null;
36
- try {
37
- const record = await readMemoryInventorySource(path, ".crew/memory/migration.v1.json", inventoryLimits.fileBytes);
38
- const state = MigrationStateSchema.parse(JSON.parse(record.content));
39
- assertMigrationStateIntegrity(state, state.identity);
40
- const expectedInitialization = state.origin === "initialized"
41
- ? await localMemoryInitializationIdentity(path, input.agentId, {
42
- workspaceId: input.workspaceId,
43
- machineId: input.machineId,
44
- })
45
- : null;
46
- const expectedLegacyInitialization = state.origin === "initialized"
47
- ? await localMemoryInitializationIdentity(path, input.agentId)
48
- : null;
49
- const localInitialization = expectedInitialization !== null
50
- && (isLocalMemoryInitializationIdentity(state.identity, expectedInitialization)
51
- || expectedLegacyInitialization !== null
52
- && isLocalMemoryInitializationIdentity(state.identity, expectedLegacyInitialization));
53
- const serverBoundIdentity = state.identity.workspaceId === input.workspaceId
54
- && state.identity.machineId === input.machineId;
55
- const operationIdentityValid = state.origin === "initialized" ? localInitialization : serverBoundIdentity;
56
- if (state.identity.rootFingerprint !== base.storageRootFingerprint
57
- || state.identity.ownerIdentity !== base.ownerIdentity
58
- || state.identity.agentId !== input.agentId || !operationIdentityValid) {
59
- throw new Error("migration_identity_mismatch");
60
- }
61
- localState = { migrationId: state.migrationId, phase: state.phase, storageVersion: state.storageVersion,
62
- sourceDigest: state.sourceDigest, planDigest: state.planDigest, origin: state.origin,
63
- issues: state.issues.map(issue => issue.reason),
64
- initializationIdentity: localInitialization ? {
65
- agentId: state.identity.agentId, migrationId: state.identity.migrationId,
66
- workspaceId: state.identity.workspaceId, machineId: state.identity.machineId,
67
- executionId: state.identity.executionId, sourceStorageVersion: state.identity.sourceStorageVersion,
68
- targetStorageVersion: state.identity.targetStorageVersion,
69
- rootFingerprint: state.identity.rootFingerprint, ownerIdentity: state.identity.ownerIdentity,
70
- } : null };
71
- if (state.phase === "applying" || state.phase === "repair_required")
72
- return {
73
- ...empty, localState, classification: "repair_required", emptyWithoutResidue: false, issueCodes: ["migration_recovery_required"],
74
- };
75
- }
76
- catch (e) {
77
- if (e.code !== "ENOENT")
78
- throw e;
79
- }
80
- let source;
81
- try {
82
- source = await readMemoryInventorySource(path, "MEMORY.md", inventoryLimits.fileBytes);
83
- }
84
- catch (e) {
85
- if (e.code !== "ENOENT")
86
- throw e;
87
- const names = await readdir(path);
88
- const nonMetadata = names.filter(name => name !== ".crew");
89
- return { ...empty, localState, emptyWithoutResidue: nonMetadata.length === 0 && localState === null,
90
- classification: nonMetadata.length === 0 && localState === null ? "empty" : "repair_required", issueCodes: ["memory_source_missing"] };
91
- }
92
- const storageVersion = detectLocalMemoryStorageVersion(source.content);
93
- if (storageVersion === 2) {
94
- parseFullMemoryIndex(source.content);
95
- return { ...base, storageVersion, classification: "valid_v2", emptyWithoutResidue: false, localState, issueCodes: [], stoppedExecutionId };
96
- }
97
- const eligibility = await resolveLocalMemoryV2Rollout({ workspace: { dir: path, memory: source.content },
98
- serverUrl: input.serverUrl, handle: input.handle, requested: true });
99
- return { ...base, storageVersion, classification: storageVersion === 1 && eligibility.enabled ? "valid_v1"
100
- : eligibility.legacyFallback || eligibility.enabled ? "legacy" : "repair_required",
101
- emptyWithoutResidue: false, localState, stoppedExecutionId, issueCodes: eligibility.enabled || eligibility.legacyFallback ? [] : ["memory_source_incompatible"] };
102
- }, { maintenance: true, waitMs: 0 });
103
- }
104
- catch (error) {
105
- const code = error instanceof Error && /^[a-z_]+$/u.test(error.message) ? error.message : "memory_inspection_failed";
106
- return { ...empty, emptyWithoutResidue: false, classification: code === "memory_migration_access_busy" ? "busy" : "repair_required", issueCodes: [code] };
107
- }
108
- }
@@ -1,14 +0,0 @@
1
- import { join } from "node:path";
2
- import { createExecutionJournal } from "./execution-journal.js";
3
- import { dslog } from "./slog.js";
4
- /** 复用journal的监管/恢复实现;旧daemon只读普通目录,不会解析维护类型。 */
5
- export function createMemoryMigrationJournal(agentsRoot) {
6
- return createExecutionJournal(join(agentsRoot, ".crew", "memory-migration-runtime"), { retainCompleted: true });
7
- }
8
- export async function replayExecutionJournals(normal, maintenance) {
9
- const [ordinary, migration] = await Promise.all([normal.replay(), maintenance.replay().catch(() => {
10
- dslog("memory_migration.journal_unavailable", "迁移日志暂不可读,保留普通执行能力", { level: "ERROR" });
11
- return [];
12
- })]);
13
- return [...ordinary, ...migration];
14
- }
@@ -1,14 +0,0 @@
1
- import { z } from "zod";
2
- import { MigrationRecordMappingSchema } from "./local-memory/migration-schema.js";
3
- export const MEMORY_MIGRATION_CAPABILITY = "memory_migration_v1";
4
- export const MEMORY_STORAGE_V2_CAPABILITY = "memory_storage_v2";
5
- export const MEMORY_STATE_GUARD_CAPABILITY = "memory_state_guard_v1";
6
- export const MemoryMigrationStartSchema = z.object({
7
- type: z.literal("memory:migration:start"), reqId: z.string().min(1).max(200), protocolVersion: z.literal(1),
8
- workspaceId: z.string().uuid(), agentId: z.string().uuid(), handle: z.string().min(1).max(128).regex(/^[A-Za-z0-9_-]+$/u),
9
- machineId: z.string().uuid(), executionId: z.string().uuid(), migrationId: z.string().uuid(),
10
- expectedRevision: z.number().int().nonnegative(), attemptKey: z.string().min(1).max(200),
11
- sourceStorageVersion: z.union([z.literal(0), z.literal(1)]), targetStorageVersion: z.literal(2),
12
- action: z.enum(["migrate", "recover"]),
13
- recordMappings: z.array(MigrationRecordMappingSchema).max(10_000).optional(),
14
- }).strict();