@nowcrew/daemon 0.6.93-beta.2 → 0.6.93-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 (46) hide show
  1. package/dist/cloud-report-upload.js +10 -1
  2. package/dist/execution-journal.js +1 -3
  3. package/dist/execution-protocol.js +1 -1
  4. package/dist/execution-runner.js +1 -3
  5. package/dist/local-execution-prompt.js +3 -8
  6. package/dist/local-executor.js +35 -64
  7. package/dist/local-memory/index-budget.js +3 -9
  8. package/dist/local-memory/inventory-cli.js +1 -23
  9. package/dist/local-memory/inventory-source.js +8 -15
  10. package/dist/local-memory/prompt.js +4 -20
  11. package/dist/local-memory/read-cli.js +77 -75
  12. package/dist/local-memory/rollout.js +8 -54
  13. package/dist/local-memory/runtime.js +13 -148
  14. package/dist/local-memory/write.js +19 -125
  15. package/dist/local-memory-diagnostics.js +2 -41
  16. package/dist/local-memory-telemetry.js +2 -19
  17. package/dist/machine-info.js +0 -3
  18. package/dist/marketplace-protocol.js +1 -1
  19. package/dist/marketplace-remote.js +1 -1
  20. package/dist/marketplace-runner.js +1 -1
  21. package/dist/memory-prune-diagnostics.js +4 -60
  22. package/dist/message-file-preview.js +1 -11
  23. package/dist/prompt.js +1 -5
  24. package/dist/runner.js +1 -1
  25. package/dist/serve.js +3 -36
  26. package/dist/token.js +0 -1
  27. package/dist/workspace-control-handler.js +3 -16
  28. package/dist/workspace.js +2 -10
  29. package/package.json +1 -1
  30. package/dist/local-memory/execution-workspace.js +0 -55
  31. package/dist/local-memory/full-index-search.js +0 -47
  32. package/dist/local-memory/initialization-identity.js +0 -23
  33. package/dist/local-memory/legacy-adapter.js +0 -233
  34. package/dist/local-memory/migration-access.js +0 -184
  35. package/dist/local-memory/migration-cli.js +0 -54
  36. package/dist/local-memory/migration-io.js +0 -199
  37. package/dist/local-memory/migration-plan.js +0 -283
  38. package/dist/local-memory/migration-schema.js +0 -55
  39. package/dist/local-memory/migration-store.js +0 -429
  40. package/dist/local-memory/migration-tool-entry.js +0 -21
  41. package/dist/local-memory/storage-format.js +0 -152
  42. package/dist/memory-migration-controller.js +0 -84
  43. package/dist/memory-migration-inspect.js +0 -75
  44. package/dist/memory-migration-journal.js +0 -14
  45. package/dist/memory-migration-protocol.js +0 -14
  46. package/dist/memory-migration-runtime.js +0 -305
@@ -1,184 +0,0 @@
1
- import { lstat, mkdir, open, readFile, realpath } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { z } from "zod";
4
- import { createJournalLease, createJournalLeaseRegistry, JournalLockedError } from "../execution-journal-lock.js";
5
- import { defaultProcessController } from "../execution-journal.js";
6
- import { createHash } from "node:crypto";
7
- import { MigrationStateSchema } from "./migration-schema.js";
8
- import { detectLocalMemoryStorageVersion, parseFullMemoryIndex } from "./storage-format.js";
9
- import { readMemoryInventorySource, inventoryLimits } from "./inventory-source.js";
10
- import { MEMORY_STATE_GUARD_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY } from "../memory-migration-protocol.js";
11
- import { isLocalMemoryInitializationIdentity, localMemoryInitializationIdentity } from "./initialization-identity.js";
12
- export const LOCAL_MEMORY_V2_WRITER_CAPABILITIES = [MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY];
13
- /**
14
- * Keep an execution that started before migration from writing a committed V2
15
- * catalog with the legacy result writer. Server-side daemon capability checks
16
- * stop new work, but an already-running old process can still reach its local
17
- * result commit after the migration has finished.
18
- */
19
- export async function assertMemoryWriterAllowed(agentDir, capabilities = []) {
20
- const root = await realpath(agentDir);
21
- let state;
22
- try {
23
- const source = await readMemoryInventorySource(root, ".crew/memory/migration.v1.json", inventoryLimits.fileBytes);
24
- state = StateGate.parse(JSON.parse(source.content));
25
- }
26
- catch (error) {
27
- if (error.code === "ENOENT") {
28
- // A missing state file is only compatible with an old, unmarked store.
29
- // Once MEMORY.md carries a schema2 index, absence of the state must fail closed.
30
- try {
31
- const memory = await readMemoryInventorySource(root, "MEMORY.md", inventoryLimits.fileBytes);
32
- if (detectLocalMemoryStorageVersion(memory.content) === 2) {
33
- throw new Error("memory_migration_state_missing");
34
- }
35
- }
36
- catch (memoryError) {
37
- if (memoryError.code === "ENOENT")
38
- return;
39
- throw memoryError;
40
- }
41
- return;
42
- }
43
- if (error instanceof z.ZodError || error instanceof SyntaxError)
44
- throw new Error("memory_migration_state_invalid");
45
- throw error;
46
- }
47
- if (state.phase === "applying" || state.phase === "repair_required" || state.phase === "prepared") {
48
- throw new Error("memory_migration_in_progress");
49
- }
50
- if (state.phase !== "committed")
51
- return;
52
- // Reuse the committed-state integrity checks before admitting any writer;
53
- // a truncated committed state must fail closed instead of looking legacy.
54
- await assertMemoryAccessAllowed(root);
55
- if (state.storageVersion !== 2)
56
- return;
57
- if (LOCAL_MEMORY_V2_WRITER_CAPABILITIES.some((capability) => !capabilities.includes(capability))) {
58
- throw new Error("memory_v2_writer_capability_missing");
59
- }
60
- }
61
- const StateGate = z.object({
62
- schemaVersion: z.literal(1), migrationId: z.string().min(1),
63
- phase: z.enum(["prepared", "applying", "committed", "rolled_back", "repair_required"]),
64
- storageVersion: z.union([z.literal(0), z.literal(1), z.literal(2)]).optional(),
65
- }).passthrough();
66
- async function checkedDirectory(parent, name) {
67
- const directory = join(parent, name);
68
- await mkdir(directory, { recursive: true, mode: 0o700 });
69
- const stat = await lstat(directory);
70
- if (!stat.isDirectory() || stat.isSymbolicLink() || await realpath(directory) !== directory) {
71
- throw new Error("memory_migration_path_invalid");
72
- }
73
- return directory;
74
- }
75
- export async function assertMemoryAccessAllowed(agentDir, options = {}) {
76
- let state = null;
77
- try {
78
- const source = await readMemoryInventorySource(agentDir, ".crew/memory/migration.v1.json", inventoryLimits.fileBytes);
79
- state = StateGate.parse(JSON.parse(source.content));
80
- if (state.phase === "applying" || state.phase === "repair_required") {
81
- const agentId = options.initializedRecoveryAgentId;
82
- if (agentId === undefined)
83
- throw new Error("memory_migration_in_progress");
84
- const fullState = MigrationStateSchema.parse(state);
85
- const identity = await localMemoryInitializationIdentity(agentDir, agentId);
86
- if (fullState.origin !== "initialized"
87
- || !isLocalMemoryInitializationIdentity(fullState.identity, identity)) {
88
- throw new Error("memory_migration_in_progress");
89
- }
90
- return;
91
- }
92
- }
93
- catch (error) {
94
- if (error instanceof Error && error.message === "memory_migration_in_progress")
95
- throw error;
96
- if (error.code !== "ENOENT")
97
- throw new Error("memory_migration_state_invalid");
98
- }
99
- let source;
100
- try {
101
- source = await readMemoryInventorySource(agentDir, "MEMORY.md", inventoryLimits.fileBytes);
102
- }
103
- catch (error) {
104
- if (error.code === "ENOENT" && state === null)
105
- return;
106
- if (error instanceof Error && error.message === "read_limit_exceeded")
107
- throw new Error("memory_file_too_large");
108
- throw error;
109
- }
110
- const version = detectLocalMemoryStorageVersion(source.content);
111
- if (version === 2 || state?.phase === "committed") {
112
- if (!state)
113
- throw new Error("memory_migration_state_missing");
114
- const verified = MigrationStateSchema.parse(state);
115
- if (verified.phase !== "committed" || verified.storageVersion !== 2 || version !== 2
116
- || verified.identity.rootFingerprint !== createHash("sha256").update(await realpath(agentDir)).digest("hex")) {
117
- throw new Error("memory_migration_state_invalid");
118
- }
119
- parseFullMemoryIndex(source.content);
120
- }
121
- }
122
- /** 复用现有进程身份锁;仅保护一次本地IO,不持锁等待模型,避免子CLI自锁。 */
123
- export async function withMemoryAccess(agentDir, operation, options = {}) {
124
- let created = false;
125
- if (options.create) {
126
- created = await lstat(agentDir).then(() => false, (e) => { if (e.code === "ENOENT")
127
- return true; throw e; });
128
- await mkdir(agentDir, { recursive: true, mode: 0o700 });
129
- }
130
- const root = await realpath(agentDir);
131
- if ((await lstat(agentDir)).isSymbolicLink())
132
- throw new Error("memory_migration_path_invalid");
133
- const crew = await checkedDirectory(root, ".crew");
134
- const memory = await checkedDirectory(crew, "memory");
135
- const directory = await checkedDirectory(memory, "access.lock");
136
- const lease = createJournalLease({ directory, currentPid: process.pid,
137
- captureCurrentIdentity: async () => {
138
- const identity = await defaultProcessController.inspectIdentity(process.pid);
139
- if (!identity)
140
- throw new Error("memory_migration_process_unknown");
141
- return identity;
142
- },
143
- inspectIdentity: pid => defaultProcessController.inspectIdentity(pid),
144
- registry: createJournalLeaseRegistry(),
145
- syncDirectory: async (path) => {
146
- if (process.platform === "win32")
147
- return;
148
- const handle = await open(path, "r");
149
- try {
150
- await handle.sync();
151
- }
152
- finally {
153
- await handle.close();
154
- }
155
- },
156
- });
157
- const deadline = Date.now() + (options.waitMs ?? 2000);
158
- for (;;) {
159
- try {
160
- await lease.acquire();
161
- break;
162
- }
163
- catch (error) {
164
- if (!(error instanceof JournalLockedError))
165
- throw error;
166
- if (Date.now() >= deadline)
167
- throw new Error("memory_migration_access_busy");
168
- await new Promise(resolve => setTimeout(resolve, 25));
169
- }
170
- }
171
- try {
172
- if (!options.maintenance)
173
- await assertMemoryAccessAllowed(root, {
174
- ...(options.initializedRecoveryAgentId === undefined ? {} : {
175
- initializedRecoveryAgentId: options.initializedRecoveryAgentId,
176
- }),
177
- });
178
- lease.assertUsable();
179
- return await operation({ created });
180
- }
181
- finally {
182
- await lease.close();
183
- }
184
- }
@@ -1,54 +0,0 @@
1
- import { isAbsolute, resolve } from 'node:path';
2
- import { fileURLToPath } from 'node:url';
3
- import { z } from 'zod';
4
- import { applyMemoryMigration, prepareMemoryMigration, recoverMemoryMigration, statusMemoryMigration } from './migration-store.js';
5
- import { MigrationError, MigrationHistorySchema, MigrationIdentitySchema, MigrationIntentReceiptSchema, MigrationProcessStoppedReceiptSchema, MigrationRecordMappingSchema } from './migration-schema.js';
6
- const base = z.object({ agentDir: z.string().refine(isAbsolute), identity: MigrationIdentitySchema });
7
- export const MigrationCommandSchema = z.discriminatedUnion('command', [
8
- base.extend({ command: z.literal('prepare'), serverHistory: MigrationHistorySchema, recordMappings: z.array(MigrationRecordMappingSchema).max(10000).optional() }).strict(),
9
- base.extend({ command: z.literal('status') }).strict(),
10
- base.extend({ command: z.literal('apply'), intentReceipt: MigrationIntentReceiptSchema }).strict(),
11
- base.extend({ command: z.literal('recover'), serverHistory: MigrationHistorySchema.optional(), intentReceipt: MigrationIntentReceiptSchema.optional(), stoppedReceipt: MigrationProcessStoppedReceiptSchema.optional(), cancelled: z.boolean() }).strict(),
12
- ]);
13
- export function summarizeMemoryMigration(state) {
14
- return { migrationId: state?.migrationId ?? null, phase: state?.phase ?? null, storageVersion: state?.storageVersion ?? null, sourceDigest: state?.sourceDigest ?? null, planDigest: state?.planDigest ?? null,
15
- issues: state?.issues ?? [], result: state === null ? 'missing' : state.phase === 'repair_required' ? 'repair_required' : 'success' };
16
- }
17
- /** The caller owns the execution/maintenance lease. This boundary never accepts or reads credentials. */
18
- export async function runMemoryMigrationCommand(raw) {
19
- let migrationId = null;
20
- try {
21
- const input = MigrationCommandSchema.parse(raw);
22
- migrationId = input.identity.migrationId;
23
- const state = input.command === 'prepare' ? await prepareMemoryMigration(input) : input.command === 'apply' ? await applyMemoryMigration(input)
24
- : input.command === 'recover' ? await recoverMemoryMigration(input) : await statusMemoryMigration(input);
25
- return summarizeMemoryMigration(state);
26
- }
27
- catch (error) {
28
- const reason = error instanceof MigrationError ? error.code : error instanceof z.ZodError ? 'migration_input_invalid' : 'migration_operation_failed';
29
- return { ...summarizeMemoryMigration(null), migrationId, result: reason === 'migration_needs_review' ? 'needs_review' : reason.includes('repair_required') ? 'repair_required' : 'failed',
30
- issues: error instanceof MigrationError && error.issues.length ? error.issues : [{ path: '', reason }] };
31
- }
32
- }
33
- async function main() {
34
- try {
35
- const chunks = [];
36
- let bytes = 0;
37
- for await (const chunk of process.stdin) {
38
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
39
- bytes += buffer.length;
40
- if (bytes > 8 * 1024 * 1024)
41
- throw new Error('input_limit');
42
- chunks.push(buffer);
43
- }
44
- const result = await runMemoryMigrationCommand(JSON.parse(Buffer.concat(chunks).toString('utf8')));
45
- process.stdout.write(`${JSON.stringify(result)}\n`);
46
- process.exitCode = result.result === 'success' || result.result === 'missing' ? 0 : 1;
47
- }
48
- catch {
49
- process.stdout.write(`${JSON.stringify({ ...summarizeMemoryMigration(null), result: 'failed', issues: [{ path: '', reason: 'migration_input_invalid' }] })}\n`);
50
- process.exitCode = 1;
51
- }
52
- }
53
- if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url))
54
- void main();
@@ -1,199 +0,0 @@
1
- import { createHash, randomUUID } from 'node:crypto';
2
- import { constants } from 'node:fs';
3
- import { lstat, mkdir, open, opendir, realpath, rename, rm } from 'node:fs/promises';
4
- import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
5
- import { MigrationError, MigrationIdentitySchema } from './migration-schema.js';
6
- import { LOCAL_MEMORY_NOTE_BYTE_CAP, LOCAL_MEMORY_READ_BYTE_CAP, LOCAL_MEMORY_STORAGE_BYTE_CAP } from './storage-format.js';
7
- export const digest = (data) => createHash('sha256').update(data).digest('hex');
8
- export const STATE_PATH = '.crew/memory/migration.v1.json';
9
- export async function validateMigrationRoot(agentDir, raw) {
10
- const identity = MigrationIdentitySchema.parse(raw);
11
- if (!isAbsolute(agentDir) || resolve(agentDir) !== agentDir)
12
- throw new MigrationError('migration_root_invalid');
13
- // macOS /var -> /private/var is a trusted system alias; the agent root itself may not be a symlink.
14
- const stat = await lstat(agentDir);
15
- if (!stat.isDirectory() || stat.isSymbolicLink() || String(stat.uid) !== identity.ownerIdentity)
16
- throw new MigrationError('migration_owner_invalid');
17
- if (digest(await realpath(agentDir)) !== identity.rootFingerprint)
18
- throw new MigrationError('migration_root_fingerprint_invalid');
19
- return identity;
20
- }
21
- export async function safePath(root, path, createParents = false) {
22
- const target = resolve(root, path);
23
- const rel = relative(root, target);
24
- if (!rel || rel.startsWith(`..${sep}`) || rel === '..' || isAbsolute(rel))
25
- throw new MigrationError('migration_path_invalid', [{ path, reason: 'outside_root' }]);
26
- const parts = rel.split(sep);
27
- let current = root;
28
- for (let i = 0; i < parts.length; i++) {
29
- current = join(current, parts[i]);
30
- let stat = await lstat(current).catch((error) => {
31
- if (error.code === 'ENOENT')
32
- return null;
33
- throw error;
34
- });
35
- if (!stat && createParents && i < parts.length - 1) {
36
- await mkdir(current, { mode: 0o700 });
37
- stat = await lstat(current);
38
- }
39
- if (stat?.isSymbolicLink() || (stat && i < parts.length - 1 && !stat.isDirectory()))
40
- throw new MigrationError('migration_path_unsafe', [{ path, reason: 'symlink_or_non_directory' }]);
41
- }
42
- return target;
43
- }
44
- export async function readBytes(root, path, cap = LOCAL_MEMORY_NOTE_BYTE_CAP) {
45
- const target = await safePath(root, path);
46
- const file = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW).catch((error) => {
47
- if (error.code === 'ENOENT')
48
- return null;
49
- throw error;
50
- });
51
- if (!file)
52
- return null;
53
- try {
54
- const stat = await file.stat();
55
- if (!stat.isFile() || stat.size > cap)
56
- throw new MigrationError('migration_read_limit', [{ path, reason: 'invalid_file_or_size' }]);
57
- const data = Buffer.alloc(stat.size + 1);
58
- const { bytesRead } = await file.read(data, 0, data.length, 0);
59
- if (bytesRead !== stat.size || (await file.stat()).size !== stat.size)
60
- throw new MigrationError('migration_source_changed', [{ path, reason: 'changed_during_read' }]);
61
- return data.subarray(0, bytesRead);
62
- }
63
- finally {
64
- await file.close();
65
- }
66
- }
67
- export async function atomicBytes(root, path, data, options = {}) {
68
- const target = await safePath(root, path, true);
69
- const temporaryPath = options.temporaryPath ?? `${path}.tmp-${randomUUID()}`;
70
- const temp = await safePath(root, temporaryPath, true);
71
- if ((await lstat(dirname(target))).dev !== (await lstat(dirname(temp))).dev) {
72
- throw new MigrationError('migration_publication_filesystem_mismatch');
73
- }
74
- const existing = options.temporaryPath ? await readBytes(root, temporaryPath) : null;
75
- if (existing && digest(existing) !== digest(data)) {
76
- throw new MigrationError('migration_publication_copy_unknown', [{ path: temporaryPath, reason: 'publication_hash_mismatch' }]);
77
- }
78
- if (!existing) {
79
- // The plan-recognized name is only published after every byte is complete and synced.
80
- // An interrupted unpublished file is never a recovery source and is preserved as evidence.
81
- const writePath = options.temporaryPath
82
- ? `${dirname(dirname(temporaryPath))}/unpublished/${randomUUID()}.tmp`
83
- : temporaryPath;
84
- const unpublished = await safePath(root, writePath, true);
85
- if ((await lstat(dirname(unpublished))).dev !== (await lstat(dirname(temp))).dev) {
86
- throw new MigrationError('migration_publication_filesystem_mismatch');
87
- }
88
- const file = await open(unpublished, 'wx', 0o600);
89
- try {
90
- if (options.temporaryPath) {
91
- for (let offset = 0; offset < data.length; offset += 64 * 1024) {
92
- const end = Math.min(offset + 64 * 1024, data.length);
93
- await file.writeFile(data.subarray(offset, end));
94
- await options.afterWriteChunk?.(writePath, end, data.length);
95
- }
96
- }
97
- else
98
- await file.writeFile(data);
99
- await file.sync();
100
- }
101
- finally {
102
- await file.close();
103
- }
104
- if (options.temporaryPath) {
105
- const complete = await readBytes(root, writePath);
106
- if (!complete || digest(complete) !== digest(data))
107
- throw new MigrationError('migration_unpublished_copy_invalid');
108
- const appeared = await readBytes(root, temporaryPath);
109
- if (appeared && digest(appeared) !== digest(data))
110
- throw new MigrationError('migration_publication_copy_unknown');
111
- if (!appeared) {
112
- await safePath(root, temporaryPath);
113
- await rename(unpublished, temp);
114
- await syncDirectory(dirname(temp));
115
- await syncDirectory(dirname(unpublished));
116
- }
117
- }
118
- }
119
- try {
120
- await options.beforeRename?.();
121
- await safePath(root, path);
122
- if (options.temporaryPath) {
123
- const verified = await readBytes(root, temporaryPath);
124
- if (!verified || digest(verified) !== digest(data))
125
- throw new MigrationError('migration_publication_copy_unknown');
126
- }
127
- await rename(temp, target);
128
- await syncDirectory(dirname(target));
129
- }
130
- finally {
131
- // Controlled copies survive interruption; recovery verifies their plan-bound name and content before reuse.
132
- if (!options.temporaryPath)
133
- await rm(temp, { force: true });
134
- }
135
- }
136
- export async function syncDirectory(path) {
137
- const fd = await open(path, 'r');
138
- try {
139
- await fd.sync();
140
- }
141
- finally {
142
- await fd.close();
143
- }
144
- }
145
- export async function inventory(root) {
146
- const files = new Map();
147
- const directories = [];
148
- let bytes = 0;
149
- let fileCount = 0;
150
- let directoryCount = 0;
151
- function reserveFile() {
152
- if (++fileCount > 20000)
153
- throw new MigrationError('migration_file_limit');
154
- }
155
- function reserveDirectory() {
156
- if (++directoryCount > 10000)
157
- throw new MigrationError('migration_directory_limit');
158
- }
159
- async function scan(path, reserved = false) {
160
- const target = await safePath(root, path);
161
- const stat = await lstat(target).catch((error) => {
162
- if (error.code === 'ENOENT')
163
- return null;
164
- throw error;
165
- });
166
- if (!stat)
167
- return;
168
- if (stat.isDirectory()) {
169
- if (!reserved)
170
- reserveDirectory();
171
- directories.push(path);
172
- const children = [];
173
- for await (const entry of await opendir(target)) {
174
- if (entry.isDirectory())
175
- reserveDirectory();
176
- else
177
- reserveFile();
178
- children.push(entry.name);
179
- }
180
- for (const name of children.sort())
181
- await scan(`${path}/${name}`, true);
182
- }
183
- else {
184
- if (!reserved)
185
- reserveFile();
186
- const content = await readBytes(root, path, path === 'MEMORY.md' ? LOCAL_MEMORY_STORAGE_BYTE_CAP : LOCAL_MEMORY_NOTE_BYTE_CAP);
187
- if (content) {
188
- bytes += content.length;
189
- if (bytes > LOCAL_MEMORY_READ_BYTE_CAP)
190
- throw new MigrationError('migration_read_limit');
191
- files.set(path, content);
192
- }
193
- }
194
- }
195
- await scan('MEMORY.md');
196
- await scan('notes');
197
- await scan('.crew/memory/pending');
198
- return { files, directories: directories.sort() };
199
- }