@nowcrew/daemon 0.6.98-beta.4 → 0.6.98-beta.5
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/automatic-daemon-installer.js +10 -0
- package/dist/cloud-report-upload.js +10 -1
- package/dist/computer-setup-client.js +17 -2
- package/dist/computer-setup-existing.js +154 -0
- package/dist/computer-setup-orchestration.js +21 -6
- package/dist/computer-setup.js +1 -0
- package/dist/computer-token-setup.js +2 -2
- package/dist/execution-journal.js +1 -3
- package/dist/execution-protocol.js +1 -1
- package/dist/execution-runner.js +29 -20
- package/dist/external-output.js +12 -1
- package/dist/i18n.js +6 -0
- package/dist/local-execution-prompt.js +3 -8
- package/dist/local-executor.js +52 -69
- package/dist/local-memory/index-budget.js +3 -9
- package/dist/local-memory/inventory-cli.js +8 -52
- package/dist/local-memory/inventory-source.js +8 -15
- package/dist/local-memory/pending-query.js +2 -5
- package/dist/local-memory/pending-schema.js +1 -1
- package/dist/local-memory/pending.js +2 -16
- package/dist/local-memory/prompt.js +5 -21
- package/dist/local-memory/read-cli.js +76 -98
- package/dist/local-memory/rollout.js +11 -79
- package/dist/local-memory/runtime.js +13 -181
- package/dist/local-memory/write.js +21 -189
- 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/runtimes/pi.js +85 -17
- 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/commit-telemetry.js +0 -30
- 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 -362
- package/dist/local-memory/legacy-adapter.js +0 -233
- package/dist/local-memory/migration-access.js +0 -242
- package/dist/local-memory/migration-cli.js +0 -54
- package/dist/local-memory/migration-io.js +0 -199
- package/dist/local-memory/migration-pending.js +0 -9
- package/dist/local-memory/migration-plan.js +0 -432
- package/dist/local-memory/migration-schema.js +0 -55
- package/dist/local-memory/migration-state-invariants.js +0 -65
- package/dist/local-memory/migration-store.js +0 -414
- package/dist/local-memory/migration-tool-entry.js +0 -21
- package/dist/local-memory/source-path.js +0 -13
- 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 -89
- 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
|
@@ -1,152 +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 length = (text) => {
|
|
8
|
-
let count = 0;
|
|
9
|
-
for (const _character of text)
|
|
10
|
-
count += 1;
|
|
11
|
-
return count;
|
|
12
|
-
};
|
|
13
|
-
const TYPES = new Set(["procedural", "semantic", "episodic", "preference"]);
|
|
14
|
-
const STATUSES = new Set(["candidate", "active", "pinned", "stale", "superseded", "archived"]);
|
|
15
|
-
const PRIORITIES = new Set(["high", "medium", "low"]);
|
|
16
|
-
const FIELDS = new Set(["id", "file", "type", "status", "summary", "triggers", "priority", "revision", "anchor", "updated", "review_after"]);
|
|
17
|
-
/** A malformed/unknown marker is never treated as an unmarked legacy file. */
|
|
18
|
-
export function detectLocalMemoryStorageVersion(memory) {
|
|
19
|
-
const markerCount = (memory.match(/NOWWORK:MEMORY_INDEX:/gu) ?? []).length;
|
|
20
|
-
if (markerCount === 0)
|
|
21
|
-
return 0;
|
|
22
|
-
const block = LOCAL_MEMORY_GENERATED_INDEX_PATTERN.exec(memory);
|
|
23
|
-
if (markerCount !== 2 || block?.index === undefined)
|
|
24
|
-
throw new Error("local_memory_storage_version_invalid");
|
|
25
|
-
const headings = [...memory.matchAll(/^##[\t ]+Knowledge Index[\t ]*\r?$/gmu)];
|
|
26
|
-
const heading = headings[0];
|
|
27
|
-
if (headings.length !== 1 || heading?.index === undefined)
|
|
28
|
-
throw new Error("local_memory_storage_index_invalid");
|
|
29
|
-
const sectionStart = heading.index + heading[0].length;
|
|
30
|
-
const nextSection = /^##[\t ]+(?!#)/mu.exec(memory.slice(sectionStart));
|
|
31
|
-
const sectionEnd = nextSection?.index === undefined ? memory.length : sectionStart + nextSection.index;
|
|
32
|
-
if (block.index < sectionStart || block.index + block[0].length > sectionEnd) {
|
|
33
|
-
throw new Error("local_memory_storage_index_invalid");
|
|
34
|
-
}
|
|
35
|
-
return /schema=2\s*-->/u.test(block[0].slice(0, block[0].indexOf("\n") + 1) || block[0]) ? 2 : 1;
|
|
36
|
-
}
|
|
37
|
-
function validateEntry(entry) {
|
|
38
|
-
const singleLine = (value) => !/[\r\n\u0000-\u001f\u007f]/u.test(value)
|
|
39
|
-
&& !/<!--/u.test(value);
|
|
40
|
-
if (!entry.title.trim() || !entry.summary.trim()
|
|
41
|
-
|| ![entry.title, entry.summary, entry.updated, ...entry.triggers].every(singleLine)
|
|
42
|
-
|| !/^[A-Za-z0-9_-]+$/u.test(entry.id) || entry.anchor !== entry.id
|
|
43
|
-
|| !/^notes\/(?:preferences|project-rules|testing|debugging|git|incidents|lessons)\.md$/u.test(entry.file)
|
|
44
|
-
|| !TYPES.has(entry.type) || !STATUSES.has(entry.status) || !PRIORITIES.has(entry.priority)
|
|
45
|
-
|| !Number.isSafeInteger(entry.revision) || entry.revision < 1
|
|
46
|
-
|| !entry.updated.trim()
|
|
47
|
-
|| (entry.reviewAfter !== null && !singleLine(entry.reviewAfter))) {
|
|
48
|
-
throw new Error("local_memory_storage_entry_invalid");
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function validateEntries(entries) {
|
|
52
|
-
if (entries.length > LOCAL_MEMORY_STORAGE_ENTRY_CAP)
|
|
53
|
-
throw new Error("local_memory_storage_entry_limit");
|
|
54
|
-
const ids = new Set();
|
|
55
|
-
for (const entry of entries) {
|
|
56
|
-
validateEntry(entry);
|
|
57
|
-
if (ids.has(entry.id))
|
|
58
|
-
throw new Error("local_memory_storage_duplicate_id");
|
|
59
|
-
ids.add(entry.id);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
export function assertLocalMemoryStorageSize(memory) {
|
|
63
|
-
if (Buffer.byteLength(memory, "utf8") > LOCAL_MEMORY_STORAGE_BYTE_CAP)
|
|
64
|
-
throw new Error("local_memory_storage_byte_limit");
|
|
65
|
-
}
|
|
66
|
-
/** schema=2 stores every lifecycle state, unlike the schema=1 prompt projection. */
|
|
67
|
-
export function parseFullMemoryIndex(memory) {
|
|
68
|
-
assertLocalMemoryStorageSize(memory);
|
|
69
|
-
if (detectLocalMemoryStorageVersion(memory) !== 2)
|
|
70
|
-
throw new Error("local_memory_storage_version_invalid");
|
|
71
|
-
const block = LOCAL_MEMORY_GENERATED_INDEX_PATTERN.exec(memory)[0];
|
|
72
|
-
const body = block.replace(/^<!--[^>]*-->\s*/u, "").replace(/<!--[^>]*-->$/u, "");
|
|
73
|
-
const headings = [...body.matchAll(/^###[\t ]+(.+?)[\t ]*\r?$/gmu)];
|
|
74
|
-
if (headings.length > LOCAL_MEMORY_STORAGE_ENTRY_CAP)
|
|
75
|
-
throw new Error("local_memory_storage_entry_limit");
|
|
76
|
-
if (body.slice(0, headings[0]?.index ?? body.length).trim())
|
|
77
|
-
throw new Error("local_memory_storage_entry_invalid");
|
|
78
|
-
const entries = headings.map((heading, index) => {
|
|
79
|
-
const fields = {};
|
|
80
|
-
const tail = body.slice(heading.index + heading[0].length, headings[index + 1]?.index ?? body.length);
|
|
81
|
-
for (const line of tail.split(/\r?\n/u)) {
|
|
82
|
-
if (!line.trim())
|
|
83
|
-
continue;
|
|
84
|
-
const field = /^-\s+([a-z][a-z_]*)\s*:\s*(.*?)\s*$/u.exec(line);
|
|
85
|
-
if (!field || !FIELDS.has(field[1]) || fields[field[1]] !== undefined)
|
|
86
|
-
throw new Error("local_memory_storage_entry_invalid");
|
|
87
|
-
fields[field[1]] = field[2];
|
|
88
|
-
}
|
|
89
|
-
if ([...FIELDS].filter(field => field !== "review_after").some(field => fields[field] === undefined)) {
|
|
90
|
-
throw new Error("local_memory_storage_entry_invalid");
|
|
91
|
-
}
|
|
92
|
-
if (!/^[1-9][0-9]*$/u.test(fields.revision))
|
|
93
|
-
throw new Error("local_memory_storage_entry_invalid");
|
|
94
|
-
return {
|
|
95
|
-
id: fields.id, title: heading[1], file: fields.file, type: fields.type,
|
|
96
|
-
status: fields.status, summary: fields.summary,
|
|
97
|
-
triggers: fields.triggers.split(/[,,、]/u).map(value => value.trim()).filter(Boolean),
|
|
98
|
-
priority: fields.priority, revision: Number(fields.revision),
|
|
99
|
-
anchor: fields.anchor, updated: fields.updated, reviewAfter: fields.review_after ?? null,
|
|
100
|
-
};
|
|
101
|
-
});
|
|
102
|
-
validateEntries(entries);
|
|
103
|
-
const section = memory.slice(memory.indexOf("## Knowledge Index") + "## Knowledge Index".length);
|
|
104
|
-
const remaining = section.replace(LOCAL_MEMORY_GENERATED_INDEX_PATTERN, "").split(/^##[\t ]+(?!#)/mu)[0] ?? "";
|
|
105
|
-
if (/^###[\t ]+/mu.test(remaining.replace(/<!--[\s\S]*?-->/gu, "")))
|
|
106
|
-
throw new Error("local_memory_legacy_index_requires_migration");
|
|
107
|
-
return entries;
|
|
108
|
-
}
|
|
109
|
-
export function renderFullMemoryIndex(entries) {
|
|
110
|
-
validateEntries(entries);
|
|
111
|
-
const block = `<!-- NOWWORK:MEMORY_INDEX:BEGIN schema=2 -->\n${entries.map(renderLocalMemoryIndexCard).join("\n")}<!-- NOWWORK:MEMORY_INDEX:END -->`;
|
|
112
|
-
assertLocalMemoryStorageSize(block);
|
|
113
|
-
return block;
|
|
114
|
-
}
|
|
115
|
-
export function replaceFullMemoryIndex(memory, entries) {
|
|
116
|
-
if (detectLocalMemoryStorageVersion(memory) !== 2)
|
|
117
|
-
throw new Error("local_memory_storage_version_invalid");
|
|
118
|
-
const updated = memory.replace(LOCAL_MEMORY_GENERATED_INDEX_PATTERN, () => renderFullMemoryIndex(entries));
|
|
119
|
-
assertLocalMemoryStorageSize(updated);
|
|
120
|
-
return updated;
|
|
121
|
-
}
|
|
122
|
-
export function selectLocalMemoryPreview(memory, query = "", options = {}) {
|
|
123
|
-
const entries = parseFullMemoryIndex(memory);
|
|
124
|
-
const totalActiveEntries = entries.filter(entry => entry.status === "active" || entry.status === "pinned").length;
|
|
125
|
-
const emptyIndex = renderGeneratedIndex([]);
|
|
126
|
-
// Reserve the largest count label before selecting; all discovery text counts toward the same cap.
|
|
127
|
-
const hint = (shown) => `\nMemory preview: ${shown}/${totalActiveEntries} active entries. Use crew-memory search --query <text> and crew-memory read --file <file> for more.\n`;
|
|
128
|
-
const base = memory.replace(LOCAL_MEMORY_GENERATED_INDEX_PATTERN, () => `${emptyIndex}${hint(totalActiveEntries)}`);
|
|
129
|
-
const remaining = Math.min(LOCAL_MEMORY_TOTAL_CODE_POINT_CAP, options.maxCodePoints ?? Infinity) - length(base);
|
|
130
|
-
const remainingBytes = (options.maxBytes ?? Infinity) - Buffer.byteLength(base, "utf8");
|
|
131
|
-
if (remaining < 0 || remainingBytes < 0 || Number.isNaN(remaining) || Number.isNaN(remainingBytes)) {
|
|
132
|
-
throw new Error("local_memory_identity_budget_exceeded");
|
|
133
|
-
}
|
|
134
|
-
const terms = query.normalize("NFKC").toLowerCase().split(/\s+/u).filter(Boolean);
|
|
135
|
-
const selection = selectLocalMemoryIndexEntries(entries, {
|
|
136
|
-
entryCap: LOCAL_MEMORY_INDEX_ENTRY_CAP,
|
|
137
|
-
indexCodePointCap: Math.min(LOCAL_MEMORY_INDEX_CODE_POINT_CAP - length(emptyIndex), remaining),
|
|
138
|
-
indexByteCap: remainingBytes,
|
|
139
|
-
}, {
|
|
140
|
-
protectedIds: new Set(entries.filter(entry => entry.status === "pinned").map(entry => entry.id)),
|
|
141
|
-
relevance: entry => {
|
|
142
|
-
const searchable = `${entry.title} ${entry.summary} ${entry.triggers.join(" ")}`.normalize("NFKC").toLowerCase();
|
|
143
|
-
return terms.filter(term => searchable.includes(term)).length;
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
if (selection.reason === "index_over_budget_pinned")
|
|
147
|
-
throw new Error("local_memory_pinned_budget_exceeded");
|
|
148
|
-
return {
|
|
149
|
-
...selection, totalActiveEntries,
|
|
150
|
-
memory: memory.replace(LOCAL_MEMORY_GENERATED_INDEX_PATTERN, () => `${renderGeneratedIndex(selection.entries)}${hint(selection.entries.length)}`),
|
|
151
|
-
};
|
|
152
|
-
}
|
|
@@ -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();
|