@nowcrew/daemon 0.6.99-beta.3 → 0.6.99-beta.4

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,414 +0,0 @@
1
- import { statfs, rm, readdir, opendir } from 'node:fs/promises';
2
- import { dirname } from 'node:path';
3
- import { z } from 'zod';
4
- import { atomicBytes, digest, inventory, readBytes, safePath, STATE_PATH, syncDirectory, validateMigrationRoot } from './migration-io.js';
5
- import { initializeExactWorkspaceSeed, planMemoryMigration, validateMigrationTarget } from './migration-plan.js';
6
- import { MigrationError, MigrationHistorySchema, MigrationProcessStoppedReceiptSchema, MigrationStateSchema } from './migration-schema.js';
7
- import { inspectManagedDetailFilesWithMappings } from './inventory-v2.js';
8
- import { assertMigrationStateIntegrity as assertIdentity, encodeMigrationData as encode, migrationOperationRoot as opRoot, migrationPlanDigest as planDigest, sameMigrationData as same, verifyMigrationIntent as verifyIntent, } from './migration-state-invariants.js';
9
- import { detectLocalMemoryStorageVersion, parseFullMemoryIndex } from './storage-format.js';
10
- import { isLocalMemoryInitializationIdentity, localMemoryInitializationIdentity } from './initialization-identity.js';
11
- async function immutable(root, path, data) {
12
- const previous = await readBytes(root, path, 64 * 1024 * 1024);
13
- if (previous) {
14
- if (digest(previous) !== digest(data))
15
- throw new MigrationError('migration_immutable_hash_mismatch', [{ path, reason: 'hash_mismatch' }]);
16
- return;
17
- }
18
- await atomicBytes(root, path, data);
19
- }
20
- async function save(root, state) {
21
- const parsed = MigrationStateSchema.parse(state);
22
- assertIdentity(parsed, parsed.identity);
23
- if (parsed.phase === 'committed' || parsed.phase === 'rolled_back') {
24
- const previous = await readBytes(root, `${opRoot(parsed.identity)}/terminal.json`, 32 * 1024 * 1024);
25
- if (previous && !same(JSON.parse(previous.toString('utf8')), parsed)) {
26
- throw new MigrationError('migration_terminal_conflict');
27
- }
28
- }
29
- await atomicBytes(root, STATE_PATH, encode(parsed));
30
- // State is the commit point. Retain the same terminal fact separately so accidental state loss cannot replay a completed plan.
31
- if (parsed.phase === 'committed' || parsed.phase === 'rolled_back')
32
- await immutable(root, `${opRoot(parsed.identity)}/terminal.json`, encode(parsed));
33
- return parsed;
34
- }
35
- async function readState(root) {
36
- const bytes = await readBytes(root, STATE_PATH, 32 * 1024 * 1024);
37
- if (!bytes)
38
- return null;
39
- let state;
40
- try {
41
- state = MigrationStateSchema.parse(JSON.parse(bytes.toString('utf8')));
42
- }
43
- catch {
44
- throw new MigrationError('migration_state_invalid');
45
- }
46
- return state;
47
- }
48
- async function load(input) {
49
- const state = await readState(input.agentDir);
50
- if (state)
51
- assertIdentity(state, input.identity);
52
- return state;
53
- }
54
- export async function statusMemoryMigration(input) {
55
- const identity = await validateMigrationRoot(input.agentDir, input.identity);
56
- return load({ ...input, identity });
57
- }
58
- export async function prepareMemoryMigration(input, hooks = {}) {
59
- const identity = await validateMigrationRoot(input.agentDir, input.identity);
60
- const history = MigrationHistorySchema.parse(input.serverHistory);
61
- const existing = await readState(input.agentDir);
62
- if (existing?.migrationId === identity.migrationId) {
63
- assertIdentity(existing, identity);
64
- return existing;
65
- }
66
- if (history.kind !== 'none' || history.writeIntentConfirmed === true)
67
- throw new MigrationError(history.kind === 'existing' ? 'migration_recovery_required' : 'migration_server_history_unavailable');
68
- const operations = await readdir(await safePath(input.agentDir, '.crew/memory/migrations')).catch((error) => {
69
- if (error.code === 'ENOENT')
70
- return [];
71
- throw error;
72
- });
73
- await verifyRolledBackPredecessors(input.agentDir, identity, history, operations, existing);
74
- const leftovers = await readBytes(input.agentDir, `${opRoot(identity)}/manifest.json`, 32 * 1024 * 1024);
75
- if (leftovers) {
76
- const prepared = MigrationStateSchema.parse(JSON.parse(leftovers.toString('utf8')));
77
- assertIdentity(prepared, identity);
78
- const intent = await readBytes(input.agentDir, `${opRoot(identity)}/intent.json`);
79
- const current = await classify(input.agentDir, prepared);
80
- if (intent || current.unknown.length || current.classes.some(value => value !== 'B' && value !== 'AB'))
81
- throw new MigrationError('migration_recovery_required');
82
- if (!await copies(input.agentDir, prepared, 'before') || !await copies(input.agentDir, prepared, 'after'))
83
- throw new MigrationError('migration_copy_invalid');
84
- return save(input.agentDir, prepared);
85
- }
86
- const before = await inventory(input.agentDir);
87
- const sourceMemory = before.files.get('MEMORY.md');
88
- if (history.isNewAgent === true && identity.sourceStorageVersion === 0 && before.files.size === 0 && before.directories.length === 0)
89
- throw new MigrationError('migration_workspace_seed_required');
90
- const previousSeed = existing?.origin === 'initialized' && existing.phase === 'rolled_back'
91
- ? existing.manifest.files.find(file => file.path === 'MEMORY.md')?.before : undefined;
92
- const retryingOriginalSeed = previousSeed?.exists === true && sourceMemory !== undefined && digest(sourceMemory) === previousSeed.sha256;
93
- const expectedInitializationIdentity = await localMemoryInitializationIdentity(input.agentDir, identity.agentId, {
94
- workspaceId: identity.workspaceId,
95
- machineId: identity.machineId,
96
- });
97
- const onlyCurrentOperationResidue = isLocalMemoryInitializationIdentity(identity, expectedInitializationIdentity)
98
- && existing === null && operations.length === 1
99
- && operations[0] === identity.migrationId;
100
- const pristineSeed = (history.isNewAgent === true && (operations.length === 0 || onlyCurrentOperationResidue) || retryingOriginalSeed) && identity.sourceStorageVersion === 0
101
- && before.files.size === 1 && sourceMemory !== undefined && before.directories.every(path => path === 'notes' || path === '.crew/memory/pending')
102
- ? initializeExactWorkspaceSeed(sourceMemory) : null;
103
- const initialized = history.isNewAgent === true && identity.sourceStorageVersion === 0
104
- && pristineSeed !== null;
105
- const after = pristineSeed !== null ? { files: new Map([['MEMORY.md', pristineSeed]]), recordMappings: [] } : planMemoryMigration(identity, before.files, input.recordMappings);
106
- if (pristineSeed !== null)
107
- validateMigrationTarget(after.files);
108
- const paths = [...new Set([...before.files.keys(), ...after.files.keys()])].sort();
109
- const manifest = { directories: before.directories, recordMappings: after.recordMappings, files: paths.map(path => {
110
- const b = before.files.get(path);
111
- const a = after.files.get(path);
112
- return { path, before: b ? { exists: true, sha256: digest(b), ref: `${opRoot(identity)}/backup/${digest(b)}`, bytes: b.length } : { exists: false },
113
- after: a ? { exists: true, sha256: digest(a), ref: `${opRoot(identity)}/staged/${digest(a)}`, bytes: a.length } : { exists: false } };
114
- }) };
115
- const sourceDigest = digest(encode({ directories: before.directories, files: manifest.files.map(file => ({ path: file.path, before: file.before.exists ? { exists: true, sha256: file.before.sha256 } : { exists: false } })) }));
116
- const state = { schemaVersion: 1, identity, storageVersion: identity.sourceStorageVersion, targetStorageVersion: 2, origin: initialized ? 'initialized' : 'migrated', migrationId: identity.migrationId, phase: 'prepared', sourceDigest,
117
- planDigest: planDigest(identity, sourceDigest, manifest), manifest, issues: [] };
118
- MigrationStateSchema.parse(state);
119
- const beforeBytes = [...before.files.values()].reduce((sum, value) => sum + value.length, 0);
120
- const afterBytes = [...after.files.values()].reduce((sum, value) => sum + value.length, 0);
121
- const max = Math.max(0, ...[...before.files.values(), ...after.files.values()].map(value => value.length));
122
- const available = hooks.availableBytes ? await hooks.availableBytes() : await statfs(input.agentDir).then(value => value.bavail * value.bsize);
123
- if (available < beforeBytes + afterBytes + max + encode(state).length * 3)
124
- throw new MigrationError('migration_insufficient_disk_space');
125
- for (const file of manifest.files) {
126
- if (file.before.exists)
127
- await immutable(input.agentDir, file.before.ref, before.files.get(file.path));
128
- if (file.after.exists)
129
- await immutable(input.agentDir, file.after.ref, after.files.get(file.path));
130
- }
131
- const current = await inventory(input.agentDir);
132
- if (!same([...current.files].map(([path, bytes]) => [path, digest(bytes)]), [...before.files].map(([path, bytes]) => [path, digest(bytes)])) || !same(current.directories, before.directories))
133
- throw new MigrationError('migration_source_changed');
134
- await immutable(input.agentDir, `${opRoot(identity)}/manifest.json`, encode(state));
135
- return save(input.agentDir, state);
136
- }
137
- /** 新操作只接纳服务端明确授权、摘要相符的回退终态;旧备份和历史保持不可变。 */
138
- async function verifyRolledBackPredecessors(root, identity, history, operations, existing) {
139
- const authorized = history.rolledBackOperations ?? [];
140
- const allowed = new Map(authorized.map(operation => [operation.migrationId, operation]));
141
- if (allowed.size !== authorized.length || allowed.has(identity.migrationId)
142
- || authorized.length > 0 && history.migrationId !== identity.migrationId
143
- || operations.some(name => name !== identity.migrationId && !allowed.has(name))
144
- || authorized.some(operation => !operations.includes(operation.migrationId))
145
- || existing && !allowed.has(existing.migrationId))
146
- throw new MigrationError('migration_operation_residue');
147
- for (const previous of authorized) {
148
- const bytes = await readBytes(root, `.crew/memory/migrations/${previous.migrationId}/terminal.json`, 32 * 1024 * 1024);
149
- if (!bytes)
150
- throw new MigrationError('migration_rollback_history_invalid');
151
- const terminal = MigrationStateSchema.parse(JSON.parse(bytes.toString('utf8')));
152
- assertIdentity(terminal, terminal.identity);
153
- if (terminal.phase !== 'rolled_back' || terminal.migrationId !== previous.migrationId
154
- || terminal.sourceDigest !== previous.sourceDigest || terminal.planDigest !== previous.planDigest
155
- || terminal.storageVersion !== terminal.identity.sourceStorageVersion
156
- || ['workspaceId', 'agentId', 'rootFingerprint', 'ownerIdentity', 'machineId'].some(key => terminal.identity[key] !== identity[key])
157
- || existing?.migrationId === terminal.migrationId && !same(existing, terminal))
158
- throw new MigrationError('migration_rollback_history_invalid');
159
- const manifest = await readBytes(root, `${opRoot(terminal.identity)}/manifest.json`, 32 * 1024 * 1024);
160
- if (!manifest)
161
- throw new MigrationError('migration_rollback_history_invalid');
162
- const prepared = MigrationStateSchema.parse(JSON.parse(manifest.toString('utf8')));
163
- assertIdentity(prepared, terminal.identity);
164
- if (prepared.planDigest !== terminal.planDigest || !await copies(root, terminal, 'before'))
165
- throw new MigrationError('migration_rollback_history_invalid');
166
- }
167
- }
168
- function matches(bytes, image) { return image.exists ? bytes !== undefined && digest(bytes) === image.sha256 : bytes === undefined; }
169
- function publicationPath(state, path, sha256) {
170
- return `${opRoot(state.identity)}/publish/${digest(path)}-${sha256}.tmp`;
171
- }
172
- async function validatePublicationCopies(root, state) {
173
- const directory = `${opRoot(state.identity)}/publish`;
174
- const allowed = new Map();
175
- for (const file of state.manifest.files)
176
- for (const image of [file.before, file.after]) {
177
- if (image.exists)
178
- allowed.set(publicationPath(state, file.path, image.sha256), image.sha256);
179
- }
180
- const stream = await opendir(await safePath(root, directory)).catch((error) => {
181
- if (error.code === 'ENOENT')
182
- return null;
183
- throw error;
184
- });
185
- if (!stream)
186
- return;
187
- for await (const entry of stream) {
188
- const path = `${directory}/${entry.name}`;
189
- const expected = allowed.get(path);
190
- if (!expected || !entry.isFile())
191
- throw new MigrationError('migration_publication_copy_unknown', [{ path, reason: 'unrecognized_publication_copy' }]);
192
- const content = await readBytes(root, path);
193
- if (!content || digest(content) !== expected)
194
- throw new MigrationError('migration_publication_copy_unknown', [{ path, reason: 'publication_hash_mismatch' }]);
195
- }
196
- }
197
- async function classify(root, state) {
198
- await validatePublicationCopies(root, state);
199
- const current = await inventory(root);
200
- const known = new Set(state.manifest.files.map(file => file.path));
201
- const allowedDirs = new Set(state.manifest.directories);
202
- for (const file of state.manifest.files)
203
- if (file.after.exists) {
204
- let dir = dirname(file.path);
205
- while (dir !== '.' && dir !== '.crew' && dir !== '.crew/memory') {
206
- allowedDirs.add(dir);
207
- dir = dirname(dir);
208
- }
209
- }
210
- const unknown = [...current.files.keys()].filter(path => !known.has(path));
211
- unknown.push(...current.directories.filter(path => !allowedDirs.has(path)), ...state.manifest.directories.filter(path => !current.directories.includes(path)));
212
- return { files: current.files, unknown, classes: state.manifest.files.map(file => { const b = matches(current.files.get(file.path), file.before), a = matches(current.files.get(file.path), file.after); return a && b ? 'AB' : a ? 'A' : b ? 'B' : 'X'; }) };
213
- }
214
- async function copies(root, state, side) {
215
- const files = new Map();
216
- try {
217
- for (const file of state.manifest.files) {
218
- const image = file[side];
219
- if (image.exists) {
220
- const bytes = await readBytes(root, image.ref);
221
- if (!bytes || bytes.length !== image.bytes || digest(bytes) !== image.sha256)
222
- return null;
223
- files.set(file.path, bytes);
224
- }
225
- }
226
- }
227
- catch {
228
- return null;
229
- }
230
- return files;
231
- }
232
- async function repair(root, state, paths, reason) { return save(root, { ...state, phase: 'repair_required', issues: paths.map(path => ({ path, reason })) }); }
233
- async function publish(root, state, side, hooks) {
234
- let index = 0;
235
- for (const file of state.manifest.files) {
236
- const current = await readBytes(root, file.path);
237
- const bytes = current ?? undefined;
238
- if (matches(bytes, file[side]))
239
- continue;
240
- const opposite = file[side === 'after' ? 'before' : 'after'];
241
- if (!matches(bytes, opposite))
242
- throw new MigrationError('migration_unknown_change', [{ path: file.path, reason: 'changed_before_replace' }]);
243
- const image = file[side];
244
- if (image.exists) {
245
- const data = await readBytes(root, image.ref);
246
- if (!data || digest(data) !== image.sha256)
247
- throw new MigrationError('migration_copy_invalid');
248
- const temporaryPath = publicationPath(state, file.path, image.sha256);
249
- await atomicBytes(root, file.path, data, { temporaryPath, beforeRename: () => hooks.beforeReplace?.(file.path, temporaryPath, index),
250
- afterWriteChunk: (writtenPath, written, total) => hooks.afterPublicationWrite?.(file.path, writtenPath, written, total) });
251
- }
252
- else {
253
- const target = await safePath(root, file.path);
254
- await rm(target);
255
- await syncDirectory(dirname(target));
256
- }
257
- await hooks.afterReplace?.(file.path, index++);
258
- }
259
- }
260
- function validTarget(files, mappings = [], sourceHashes = new Map()) {
261
- try {
262
- validateMigrationTarget(files);
263
- const memory = files.get('MEMORY.md');
264
- if (memory === undefined)
265
- return false;
266
- const entries = [...parseFullMemoryIndex(memory.toString('utf8'))];
267
- const sources = [...files]
268
- .filter(([path]) => path === 'MEMORY.md' || path.startsWith('notes/'))
269
- .map(([path, bytes]) => ({ path, bytes: bytes.length, sha256: digest(bytes), content: bytes.toString('utf8') }));
270
- const inspection = inspectManagedDetailFilesWithMappings(entries, sources, mappings, sourceHashes);
271
- return inspection.fileIssues.length === 0 && inspection.entryIssues.size === 0;
272
- }
273
- catch {
274
- return false;
275
- }
276
- }
277
- function manifestSourceHashes(manifest) {
278
- return new Map(manifest.files
279
- .flatMap(file => file.before.exists ? [[file.path, file.before.sha256]] : []));
280
- }
281
- async function finalState(input, state) {
282
- const terminalPath = `${opRoot(state.identity)}/terminal.json`;
283
- const bytes = await readBytes(input.agentDir, terminalPath, 32 * 1024 * 1024);
284
- let terminal = null;
285
- if (bytes) {
286
- terminal = MigrationStateSchema.parse(JSON.parse(bytes.toString('utf8')));
287
- assertIdentity(terminal, input.identity);
288
- if ((terminal.phase !== 'committed' && terminal.phase !== 'rolled_back') || terminal.planDigest !== state.planDigest)
289
- throw new MigrationError('migration_terminal_invalid');
290
- }
291
- else if (state.phase === 'committed' || state.phase === 'rolled_back') {
292
- // Persist the already-published commit point before any content-health update can replace the mutable state.
293
- await immutable(input.agentDir, terminalPath, encode(state));
294
- terminal = state;
295
- }
296
- if (terminal?.phase === 'committed') {
297
- let valid = false;
298
- try {
299
- valid = validTarget((await inventory(input.agentDir)).files, state.manifest.recordMappings, manifestSourceHashes(state.manifest));
300
- }
301
- catch { /* Protect damaged content without erasing commit history. */ }
302
- if (!valid)
303
- return repair(input.agentDir, terminal, ['MEMORY.md'], 'committed_target_invalid');
304
- return same(state, terminal) ? terminal : save(input.agentDir, terminal);
305
- }
306
- if (terminal?.phase === 'rolled_back')
307
- return same(state, terminal) ? terminal : save(input.agentDir, terminal);
308
- return null;
309
- }
310
- export async function applyMemoryMigration(input, hooks = {}) {
311
- await validateMigrationRoot(input.agentDir, input.identity);
312
- let state = await load(input);
313
- if (!state)
314
- throw new MigrationError('migration_state_missing');
315
- const done = await finalState(input, state);
316
- if (done)
317
- return done;
318
- if (state.phase !== 'prepared')
319
- throw new MigrationError('migration_recovery_required');
320
- const receipt = verifyIntent(input.intentReceipt, state);
321
- const classification = await classify(input.agentDir, state);
322
- if (classification.unknown.length || classification.classes.some(value => value !== 'B' && value !== 'AB'))
323
- return repair(input.agentDir, state, classification.unknown.length ? classification.unknown : ['MEMORY.md'], 'source_changed');
324
- const backup = await copies(input.agentDir, state, 'before'), staged = await copies(input.agentDir, state, 'after');
325
- if (!backup || !staged || !validTarget(staged, state.manifest.recordMappings, manifestSourceHashes(state.manifest)))
326
- throw new MigrationError('migration_copy_invalid');
327
- await immutable(input.agentDir, `${opRoot(state.identity)}/intent.json`, encode(receipt));
328
- state = await save(input.agentDir, { ...state, phase: 'applying', intentReceipt: receipt });
329
- await publish(input.agentDir, state, 'after', hooks);
330
- const current = await classify(input.agentDir, state);
331
- if (current.unknown.length || current.classes.some(value => value === 'X' || value === 'B') || !validTarget(current.files, state.manifest.recordMappings, manifestSourceHashes(state.manifest)))
332
- return repair(input.agentDir, state, ['MEMORY.md'], 'target_validation_failed');
333
- return save(input.agentDir, { ...state, phase: 'committed', storageVersion: 2 });
334
- }
335
- export async function recoverMemoryMigration(input, hooks = {}) {
336
- await validateMigrationRoot(input.agentDir, input.identity);
337
- let state = await readState(input.agentDir);
338
- if (state && state.migrationId !== input.identity.migrationId) {
339
- const history = MigrationHistorySchema.parse(input.serverHistory);
340
- if (history.kind !== 'existing' || history.migrationId !== input.identity.migrationId)
341
- throw new MigrationError('migration_history_missing_or_mismatch');
342
- const operations = await readdir(await safePath(input.agentDir, '.crew/memory/migrations'));
343
- await verifyRolledBackPredecessors(input.agentDir, input.identity, history, operations, state);
344
- state = null;
345
- }
346
- if (state)
347
- assertIdentity(state, input.identity);
348
- if (!state) {
349
- const history = MigrationHistorySchema.parse(input.serverHistory);
350
- if (history.kind !== 'existing' || history.migrationId !== input.identity.migrationId)
351
- throw new MigrationError('migration_history_missing_or_mismatch');
352
- const terminal = await readBytes(input.agentDir, `${opRoot(input.identity)}/terminal.json`, 32 * 1024 * 1024);
353
- if (terminal) {
354
- const terminalState = MigrationStateSchema.parse(JSON.parse(terminal.toString('utf8')));
355
- assertIdentity(terminalState, input.identity);
356
- if (terminalState.phase !== 'committed' && terminalState.phase !== 'rolled_back')
357
- throw new MigrationError('migration_terminal_invalid');
358
- const final = await finalState(input, terminalState);
359
- if (final)
360
- return save(input.agentDir, final);
361
- }
362
- const bytes = await readBytes(input.agentDir, `${opRoot(input.identity)}/manifest.json`, 32 * 1024 * 1024);
363
- if (!bytes)
364
- throw new MigrationError('migration_manifest_missing_repair_required');
365
- state = MigrationStateSchema.parse(JSON.parse(bytes.toString('utf8')));
366
- assertIdentity(state, input.identity);
367
- const receipt = await readBytes(input.agentDir, `${opRoot(input.identity)}/intent.json`);
368
- if (receipt)
369
- state = { ...state, phase: 'applying', intentReceipt: verifyIntent(JSON.parse(receipt.toString('utf8')), state) };
370
- else if (history.writeIntentConfirmed && !input.intentReceipt)
371
- throw new MigrationError('migration_intent_receipt_required');
372
- }
373
- const done = await finalState(input, state);
374
- if (done)
375
- return done;
376
- const stopped = MigrationProcessStoppedReceiptSchema.parse(input.stoppedReceipt);
377
- if (stopped.migrationId !== state.migrationId || stopped.machineId !== state.identity.machineId || stopped.executionId !== state.identity.executionId || stopped.ownerIdentity !== state.identity.ownerIdentity
378
- || (stopped.proof === 'not_started' && (state.phase !== 'prepared' || state.intentReceipt || input.intentReceipt)))
379
- throw new MigrationError('migration_stop_proof_invalid');
380
- const cancelled = z.boolean().parse(input.cancelled);
381
- let receipt = state.intentReceipt;
382
- if (input.intentReceipt)
383
- receipt = verifyIntent(input.intentReceipt, state);
384
- let current;
385
- try {
386
- current = await classify(input.agentDir, state);
387
- }
388
- catch {
389
- return repair(input.agentDir, state, ['MEMORY.md'], 'unsafe_or_unreadable_source');
390
- }
391
- if (current.unknown.length || current.classes.includes('X'))
392
- return repair(input.agentDir, state, [...current.unknown, ...state.manifest.files.filter((_, index) => current.classes[index] === 'X').map(file => file.path)], 'unknown_change');
393
- const allA = current.classes.every(value => value === 'A' || value === 'AB'), allB = current.classes.every(value => value === 'B' || value === 'AB');
394
- if (allA && !cancelled && validTarget(current.files, state.manifest.recordMappings, manifestSourceHashes(state.manifest)))
395
- return save(input.agentDir, { ...state, phase: 'committed', storageVersion: 2, ...(receipt ? { intentReceipt: receipt } : {}) });
396
- if (allB && (!receipt || cancelled))
397
- return save(input.agentDir, { ...state, phase: 'rolled_back', storageVersion: state.identity.sourceStorageVersion });
398
- const staged = await copies(input.agentDir, state, 'after');
399
- if (receipt && !cancelled && staged && validTarget(staged, state.manifest.recordMappings, manifestSourceHashes(state.manifest))) {
400
- state = await save(input.agentDir, { ...state, phase: 'applying', intentReceipt: receipt });
401
- await publish(input.agentDir, state, 'after', hooks);
402
- const target = await classify(input.agentDir, state);
403
- if (!target.unknown.length && target.classes.every(value => value === 'A' || value === 'AB') && validTarget(target.files, state.manifest.recordMappings, manifestSourceHashes(state.manifest)))
404
- return save(input.agentDir, { ...state, phase: 'committed', storageVersion: 2 });
405
- }
406
- const backup = await copies(input.agentDir, state, 'before');
407
- if (!backup || detectLocalMemoryStorageVersion(backup.get('MEMORY.md')?.toString('utf8') ?? '') !== state.identity.sourceStorageVersion)
408
- return repair(input.agentDir, state, ['MEMORY.md'], 'backup_unverifiable');
409
- await publish(input.agentDir, state, 'before', hooks);
410
- const restored = await classify(input.agentDir, state);
411
- if (restored.unknown.length || !restored.classes.every(value => value === 'B' || value === 'AB'))
412
- return repair(input.agentDir, state, ['MEMORY.md'], 'rollback_validation_failed');
413
- return save(input.agentDir, { ...state, phase: 'rolled_back', storageVersion: state.identity.sourceStorageVersion });
414
- }
@@ -1,21 +0,0 @@
1
- import { runMemoryMigrationCommand, MigrationCommandSchema } from "./migration-cli.js";
2
- import { withMemoryAccess } from "./migration-access.js";
3
- async function main() {
4
- const chunks = [];
5
- let bytes = 0;
6
- for await (const chunk of process.stdin) {
7
- const part = Buffer.from(chunk);
8
- bytes += part.length;
9
- if (bytes > 8 * 1024 * 1024)
10
- throw new Error("migration_input_limit");
11
- chunks.push(part);
12
- }
13
- const input = MigrationCommandSchema.parse(JSON.parse(Buffer.concat(chunks).toString("utf8")));
14
- const result = await withMemoryAccess(input.agentDir, () => runMemoryMigrationCommand(input), { maintenance: true });
15
- process.stdout.write(JSON.stringify(result) + "\n");
16
- process.exitCode = result.result === "success" || result.result === "missing" ? 0 : 1;
17
- }
18
- void main().catch(() => {
19
- process.stdout.write(JSON.stringify({ result: "failed", issues: [{ path: "", reason: "migration_tool_failed" }] }) + "\n");
20
- process.exitCode = 1;
21
- });
@@ -1,13 +0,0 @@
1
- /**
2
- * Files retained for audit/rollback but excluded from current memory coverage.
3
- * The patterns are deliberately conservative and only recognize obvious
4
- * backup/previous-version names.
5
- */
6
- export function isDeferredMemoryArtifactPath(path) {
7
- if (!path.startsWith("notes/"))
8
- return false;
9
- const name = path.slice("notes/".length);
10
- return name === "MEMORY.md"
11
- || /^MEMORY\.md(?:\.bak(?:[-_.].*)?|[-_.]20\d{2}[-_]\d{2}[-_]\d{2}.*)$/iu.test(name)
12
- || /^(?!bak(?:[-_.]|$)|backup(?:[-_.]|$)|prev(?:[-_.]|$)).*(?:[-_.](?:bak|backup|prev))(?:[-_.]|$).*$/iu.test(name);
13
- }