@nowcrew/daemon 0.6.88-beta.14 → 0.6.88-beta.16

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 (38) hide show
  1. package/README.md +15 -0
  2. package/dist/daemon-update-eligibility.js +1 -1
  3. package/dist/local-executor.js +4 -2
  4. package/dist/machine-info.js +16 -4
  5. package/dist/main.js +4 -69
  6. package/dist/project-skills/execution-adapter.js +3 -2
  7. package/dist/project-skills/runtime-launch.js +14 -0
  8. package/dist/provider-env.js +3 -4
  9. package/dist/runtimes/deepseek-harness-profile.js +92 -0
  10. package/dist/runtimes/deepseek-harness-runner.js +224 -0
  11. package/dist/runtimes/deepseek-harness.js +61 -10
  12. package/dist/runtimes/kimi-acp-runner.js +5 -5
  13. package/dist/serve.js +14 -62
  14. package/dist/storage-identity.js +0 -3
  15. package/dist/storage-serve-adapter.js +6 -72
  16. package/dist/supervised-runtime.js +17 -2
  17. package/package.json +1 -1
  18. package/dist/daemon-migration-controller.js +0 -211
  19. package/dist/daemon-migration-wiring.js +0 -22
  20. package/dist/storage-control.js +0 -212
  21. package/dist/storage-copy.js +0 -628
  22. package/dist/storage-credential-guard.js +0 -21
  23. package/dist/storage-doctor.js +0 -228
  24. package/dist/storage-manifest.js +0 -1027
  25. package/dist/storage-migration-controller.js +0 -440
  26. package/dist/storage-migration-manifest.js +0 -105
  27. package/dist/storage-migration-policy.js +0 -100
  28. package/dist/storage-runtime-controller.js +0 -381
  29. package/dist/storage-runtime-readiness.js +0 -240
  30. package/dist/storage-safe-fs.js +0 -302
  31. package/dist/storage-status-delivery.js +0 -224
  32. package/dist/storage-status-replay.js +0 -140
  33. package/dist/storage-target-confirmation.js +0 -129
  34. package/dist/storage-worker-command.js +0 -37
  35. package/dist/storage-worker-executor.js +0 -449
  36. package/dist/storage-worker-handoff.js +0 -151
  37. package/dist/storage-worker-operation.js +0 -294
  38. package/dist/storage-worker-status.js +0 -259
@@ -1,211 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { cp, lstat, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
3
- import { homedir } from "node:os";
4
- import { isAbsolute, relative, resolve } from "node:path";
5
- import { z } from "zod";
6
- import { loadProfile } from "./computer-profile.js";
7
- import { builtDaemonEntry } from "./computer-cli.js";
8
- import { installDaemonAutomatically } from "./automatic-daemon-installer.js";
9
- import { buildServiceSpec, captureExactService, serviceStatus, systemCommandRunner } from "./computer-service.js";
10
- import { readManagedServiceRegistry } from "./managed-service-registry.js";
11
- import { uninstallManagedService } from "./managed-service-lifecycle.js";
12
- import { resolveAutomaticInstallTarget } from "./automatic-install-target.js";
13
- import { buildStorageMigrationMappings, createSafeServiceManifest, CredentialMaterialError, } from "./storage-migration-manifest.js";
14
- const MigrationMessageSchema = z.object({
15
- type: z.literal("daemon:migrate"),
16
- migrationId: z.string().uuid(),
17
- targetProfile: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,47}$/),
18
- targetDaemonHome: z.string().min(1),
19
- targetAgentsRoot: z.string().min(1),
20
- }).strict();
21
- function migrationBackupRoot(input, id) {
22
- return resolve(input.backupRoot ?? resolve(input.userHome ?? homedir(), ".crew", "migration-backups"), id);
23
- }
24
- function legacyLocalHome(input) {
25
- return resolve(input.userHome ?? homedir(), ".crew");
26
- }
27
- async function assertEmptyOrMissing(path) {
28
- try {
29
- const entries = await readdir(path);
30
- if (entries.length > 0)
31
- throw new Error(`target path is not empty: ${path}`);
32
- }
33
- catch (error) {
34
- if (error.code !== "ENOENT")
35
- throw error;
36
- }
37
- }
38
- async function hashedFiles(root, directory = root) {
39
- const entries = await readdir(directory, { withFileTypes: true });
40
- const files = [];
41
- for (const entry of entries) {
42
- const path = resolve(directory, entry.name);
43
- const stat = await lstat(path);
44
- if (stat.isSymbolicLink())
45
- throw new Error(`backup source contains a symbolic link: ${relative(root, path)}`);
46
- if (stat.isDirectory())
47
- files.push(...await hashedFiles(root, path));
48
- else if (stat.isFile())
49
- files.push({ path: relative(root, path), sha256: createHash("sha256").update(await readFile(path)).digest("hex") });
50
- }
51
- return files;
52
- }
53
- async function backupAndCopy(input, id, targetHome, targetAgents, service) {
54
- // Validate and project service identity before creating any backup bytes. A descriptor containing
55
- // a token must stop the migration before it can be copied into the local backup root.
56
- const safeService = createSafeServiceManifest({
57
- spec: service.spec,
58
- snapshot: service.snapshot,
59
- managedRecord: service.managedRecord,
60
- });
61
- const backup = migrationBackupRoot(input, id);
62
- await mkdir(backup, { recursive: true, mode: 0o700 });
63
- await cp(input.currentDaemonHome, resolve(backup, "daemon-home"), { recursive: true, force: false });
64
- await cp(input.currentAgentsRoot, resolve(backup, "agents-root"), { recursive: true, force: false });
65
- await cp(input.currentDaemonHome, targetHome, { recursive: true, force: false });
66
- await cp(input.currentAgentsRoot, targetAgents, { recursive: true, force: false });
67
- const daemonFiles = await hashedFiles(resolve(backup, "daemon-home"));
68
- const agentFiles = await hashedFiles(resolve(backup, "agents-root"));
69
- const mappings = buildStorageMigrationMappings({ daemonFiles, agentFiles });
70
- await writeFile(resolve(backup, "manifest.json"), `${JSON.stringify({
71
- version: 2,
72
- migrationId: id,
73
- profile: input.profileName,
74
- daemonHome: input.currentDaemonHome,
75
- agentsRoot: input.currentAgentsRoot,
76
- service: safeService,
77
- mappings,
78
- }, null, 2)}\n`, { mode: 0o600 });
79
- return backup;
80
- }
81
- async function cleanupCurrentServiceDefault(spec, input) {
82
- const userHome = input.userHome ?? homedir();
83
- const records = await readManagedServiceRegistry({ userHome, localHome: legacyLocalHome(input) });
84
- const record = records.find((candidate) => candidate.profile === spec.profile && candidate.daemonHome === input.currentDaemonHome);
85
- if (record !== undefined && (spec.platform === "darwin" || spec.platform === "linux")) {
86
- await uninstallManagedService({ spec, record, runner: systemCommandRunner, registry: { userHome, localHome: legacyLocalHome(input) } });
87
- return;
88
- }
89
- // Windows and legacy descriptors are handled by the normal CLI/service lifecycle.
90
- const { uninstallService } = await import("./computer-service.js");
91
- const status = await serviceStatus(spec, systemCommandRunner);
92
- if (status.installed)
93
- await uninstallService(spec, systemCommandRunner);
94
- }
95
- export function createDaemonMigrationController(input) {
96
- const handled = new Set();
97
- let running = null;
98
- const sendFailure = (id, errorCode, error) => {
99
- input.sendStatus({ type: "daemon:migration-status", migrationId: id, status: "failed", errorCode, errorMessage: error instanceof Error ? error.message : String(error) });
100
- };
101
- const execute = async (raw) => {
102
- const parsed = MigrationMessageSchema.safeParse(raw);
103
- if (!parsed.success)
104
- return false;
105
- const message = parsed.data;
106
- if (handled.has(message.migrationId))
107
- return true;
108
- if (running !== null) {
109
- sendFailure(message.migrationId, "target_conflict", new Error("another migration is already running"));
110
- return true;
111
- }
112
- running = (async () => {
113
- input.sendStatus({ type: "daemon:migration-status", migrationId: message.migrationId, status: "preflight" });
114
- if (input.isBusy()) {
115
- sendFailure(message.migrationId, "runtime_busy", new Error("active executions must finish before migration"));
116
- return;
117
- }
118
- const platform = input.platform ?? process.platform;
119
- const userHome = input.userHome ?? homedir();
120
- let targetPath;
121
- let targetAgents;
122
- let currentSpec;
123
- let currentService;
124
- let managedRecord = null;
125
- let currentRegistryUrl;
126
- try {
127
- const currentProfile = await (input.loadCurrentProfile ?? loadProfile)(input.profileName, input.currentDaemonHome);
128
- if (currentProfile.machineToken !== input.machineToken)
129
- throw new Error("active profile token does not match this daemon");
130
- currentRegistryUrl = currentProfile.registryUrl;
131
- const target = resolveAutomaticInstallTarget({
132
- serverUrl: input.serverUrl,
133
- requestedProfile: message.targetProfile,
134
- userHome,
135
- platform,
136
- // Layout migration creates the canonical service in the new local-home
137
- // root. Source registry and rollback material remain under legacy .crew.
138
- requestedLocalHomeMode: "nowwork",
139
- });
140
- if (!isAbsolute(message.targetDaemonHome) || !isAbsolute(message.targetAgentsRoot)
141
- || resolve(message.targetDaemonHome) !== resolve(target.daemonHome)
142
- || resolve(message.targetAgentsRoot) !== resolve(target.agentsRoot))
143
- throw new Error("requested target does not match the standard profile layout");
144
- targetPath = resolve(target.daemonHome);
145
- targetAgents = resolve(target.agentsRoot);
146
- await assertEmptyOrMissing(targetPath);
147
- await assertEmptyOrMissing(targetAgents);
148
- currentSpec = buildServiceSpec({ platform, profile: input.profileName, userHome, uid: process.getuid?.(), nodePath: process.execPath, entryPath: builtDaemonEntry(), profileHome: input.currentDaemonHome });
149
- const exact = await (input.captureCurrentService ?? ((spec) => captureExactService(spec, systemCommandRunner)))(currentSpec);
150
- if (!exact.installed || !exact.running || !exact.exact)
151
- throw new Error("current managed service identity is not exact and running");
152
- currentService = { installed: exact.installed, running: exact.running };
153
- if (platform === "darwin" || platform === "linux") {
154
- const records = await (input.readManagedRecords ?? (() => readManagedServiceRegistry({ userHome, localHome: legacyLocalHome(input) })))();
155
- managedRecord = records.find((record) => record.serviceId === currentSpec.id && record.profile === input.profileName && record.daemonHome === input.currentDaemonHome) ?? null;
156
- if (managedRecord === null)
157
- throw new Error("current managed service registry record is missing");
158
- }
159
- }
160
- catch (error) {
161
- sendFailure(message.migrationId, error instanceof CredentialMaterialError ? "credential_material" : "target_conflict", error);
162
- return;
163
- }
164
- input.sendStatus({ type: "daemon:migration-status", migrationId: message.migrationId, status: "backing_up" });
165
- let backupPath;
166
- try {
167
- backupPath = await backupAndCopy(input, message.migrationId, targetPath, targetAgents, { spec: currentSpec, snapshot: currentService, managedRecord });
168
- input.sendStatus({ type: "daemon:migration-status", migrationId: message.migrationId, status: "installing", backupPath });
169
- }
170
- catch (error) {
171
- await Promise.allSettled([rm(targetPath, { recursive: true, force: true }), rm(targetAgents, { recursive: true, force: true })]);
172
- sendFailure(message.migrationId, error instanceof CredentialMaterialError ? "credential_material" : "backup_failed", error);
173
- return;
174
- }
175
- const install = input.install ?? installDaemonAutomatically;
176
- try {
177
- const result = await install({
178
- serverUrl: input.serverUrl,
179
- machineToken: input.machineToken,
180
- requestedProfile: message.targetProfile,
181
- requestedLocalHomeMode: "nowwork",
182
- ...(currentRegistryUrl === undefined ? {} : { requestedRegistryUrl: currentRegistryUrl }),
183
- });
184
- if (result.profile !== message.targetProfile || result.serviceId.length === 0) {
185
- throw new Error("target service identity did not match the requested profile");
186
- }
187
- }
188
- catch (error) {
189
- await Promise.allSettled([rm(targetPath, { recursive: true, force: true }), rm(targetAgents, { recursive: true, force: true })]);
190
- sendFailure(message.migrationId, "install_failed", error);
191
- return;
192
- }
193
- input.sendStatus({ type: "daemon:migration-status", migrationId: message.migrationId, status: "restarting" });
194
- try {
195
- await (input.cleanupCurrentService ?? ((spec) => cleanupCurrentServiceDefault(spec, input)))(currentSpec);
196
- }
197
- catch (error) {
198
- const targetSpec = buildServiceSpec({ platform, profile: message.targetProfile, userHome, uid: process.getuid?.(), nodePath: process.execPath, entryPath: builtDaemonEntry(), profileHome: targetPath });
199
- await (input.cleanupTargetService ?? ((spec) => cleanupCurrentServiceDefault(spec, { ...input, profileName: message.targetProfile, currentDaemonHome: targetPath })))(targetSpec).catch(() => undefined);
200
- sendFailure(message.migrationId, "service_cleanup_failed", error);
201
- return;
202
- }
203
- })().finally(() => {
204
- handled.add(message.migrationId);
205
- running = null;
206
- });
207
- await running;
208
- return true;
209
- };
210
- return { handle: execute, drain: async () => { await running; } };
211
- }
@@ -1,22 +0,0 @@
1
- import { daemonHome } from "./computer-profile.js";
2
- import { createDaemonMigrationController } from "./daemon-migration-controller.js";
3
- export function createServeMigrationController(input) {
4
- if (input.profileName === undefined)
5
- return null;
6
- return createDaemonMigrationController({
7
- serverUrl: input.config.serverUrl,
8
- machineToken: input.config.machineToken,
9
- profileName: input.profileName,
10
- currentDaemonHome: daemonHome(),
11
- currentAgentsRoot: input.config.agentsRoot,
12
- isBusy: input.isBusy,
13
- sendStatus: input.sendStatus,
14
- });
15
- }
16
- export async function handleMigrationControlMessage(controller, decoded) {
17
- if (controller === null || typeof decoded !== "object" || decoded === null
18
- || !("type" in decoded) || decoded.type !== "daemon:migrate")
19
- return false;
20
- await controller.handle(decoded);
21
- return true;
22
- }
@@ -1,212 +0,0 @@
1
- import { z } from "zod";
2
- export const STORAGE_MIGRATION_CAPABILITY = "storage_migration_v1";
3
- export const STORAGE_MIGRATION_STATUS_CAPABILITY = "storage_migration_status_v1";
4
- const IdentitySchema = z.object({
5
- migrationId: z.string().uuid(),
6
- environment: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,31}$/u),
7
- workspaceId: z.string().uuid(),
8
- ownerEpoch: z.number().int().positive().safe().optional(),
9
- ownershipClaimFingerprint: z.string().regex(/^sha256:[0-9a-f]{64}$/u).optional(),
10
- });
11
- const DoctorRequestSchema = IdentitySchema.extend({ type: z.literal("daemon:storage-doctor") }).strict();
12
- const MigrateRequestSchema = IdentitySchema.extend({
13
- type: z.literal("daemon:storage-migrate"),
14
- manifestDigest: z.string().regex(/^[0-9a-f]{64}$/u),
15
- }).strict();
16
- const StatusSchema = z.enum([
17
- "preflight_running", "preflight_passed", "backing_up", "initial_sync", "quiescing", "final_sync", "verified",
18
- "service_switched", "hello_confirmed", "completed", "failed", "rollback_pending", "rolled_back",
19
- "rollback_failed",
20
- ]);
21
- const TOKEN = /(?:\bsk_(?:machine|user|agent)_[A-Za-z0-9_-]{4,}\b|\bbearer\s+[^\s]+|(?:password|secret|token)\s*[:=]\s*[^\s]+)/giu;
22
- const safeError = (error) => (error instanceof Error ? error.message : String(error))
23
- .replaceAll(TOKEN, "<redacted>").slice(0, 500) || "storage migration failed";
24
- const stableErrorCode = (candidate, fallback) => typeof candidate === "string" && /^[a-z0-9._-]{1,128}$/u.test(candidate) ? candidate : fallback;
25
- export function createStorageControlController(input) {
26
- const environment = z.string().regex(/^[a-z0-9][a-z0-9_-]{0,31}$/u).parse(input.environment);
27
- const workspaceId = z.string().uuid().parse(input.workspaceId);
28
- const profile = z.string().regex(/^[a-z0-9][a-z0-9_-]{0,47}$/u).parse(input.profile);
29
- const sourceStorageFingerprint = z.string().regex(/^sha256:[0-9a-f]{64}$/u).parse(input.sourceStorageFingerprint);
30
- const targetStorageFingerprint = z.string().regex(/^sha256:[0-9a-f]{64}$/u).parse(input.targetStorageFingerprint);
31
- const ownership = input.ownership === undefined ? undefined : Object.freeze({
32
- ownerEpoch: z.number().int().positive().safe().parse(input.ownership.ownerEpoch),
33
- ownershipClaimFingerprint: z.string().regex(/^sha256:[0-9a-f]{64}$/u)
34
- .parse(input.ownership.ownershipClaimFingerprint),
35
- });
36
- const preflights = new Map();
37
- const preflightRuns = new Map();
38
- const appliedMigrationIds = new Set();
39
- let runningMigrationId = null;
40
- const preflightTimeoutMs = z.number().int().positive().max(24 * 60 * 60 * 1000)
41
- .parse(input.preflightTimeoutMs ?? 30 * 60 * 1000);
42
- const frame = (identity, status, extra = {}) => ({
43
- type: "daemon:storage-migration-status",
44
- migrationId: identity.migrationId,
45
- status,
46
- environment,
47
- workspaceId,
48
- profile,
49
- ...(ownership === undefined ? {} : ownership),
50
- ...extra,
51
- });
52
- const failure = (identity, errorCode, error, reopenAdmission = false, extra = {}) => {
53
- reportStatus(frame(identity, "failed", {
54
- errorCode,
55
- ...extra,
56
- ...(error === undefined ? {} : { errorMessage: safeError(error) }),
57
- ...(reopenAdmission || error?.reopenAdmission === true
58
- ? { reopenAdmission: true }
59
- : {}),
60
- }));
61
- };
62
- const reportStatus = (statusFrame) => {
63
- try {
64
- input.sendStatus(statusFrame);
65
- }
66
- catch (error) {
67
- input.onStatusError?.(error, statusFrame);
68
- }
69
- };
70
- const matchesIdentity = (identity) => identity.environment === environment && identity.workspaceId === workspaceId
71
- && (ownership === undefined
72
- || (identity.ownerEpoch === ownership.ownerEpoch
73
- && identity.ownershipClaimFingerprint === ownership.ownershipClaimFingerprint));
74
- const preflightKey = (identity) => `${identity.migrationId}\0${identity.environment}\0${identity.workspaceId}`;
75
- const runPreflight = (identity, failureFallback = "doctor_failed") => {
76
- const key = preflightKey(identity);
77
- const existing = preflightRuns.get(key);
78
- if (existing !== undefined)
79
- return existing;
80
- const controller = new AbortController();
81
- let scannedEntries = 0;
82
- let timedOut = false;
83
- reportStatus(frame(identity, "preflight_running", {
84
- progress: { state: "running", scannedEntries: 0 },
85
- }));
86
- const doctorPromise = Promise.resolve().then(() => input.runDoctor(identity.migrationId, {
87
- signal: controller.signal,
88
- onProgress: (progress) => {
89
- if (timedOut)
90
- return;
91
- scannedEntries = progress.scannedEntries;
92
- reportStatus(frame(identity, "preflight_running", {
93
- progress: { state: "running", scannedEntries },
94
- }));
95
- },
96
- }));
97
- let timeoutHandle;
98
- const timeout = new Promise((_, reject) => {
99
- timeoutHandle = setTimeout(() => {
100
- timedOut = true;
101
- controller.abort();
102
- reject(Object.assign(new Error("storage doctor preflight timed out"), {
103
- code: "preflight_stale",
104
- scannedEntries,
105
- }));
106
- }, preflightTimeoutMs);
107
- timeoutHandle.unref?.();
108
- });
109
- void doctorPromise.catch(() => undefined);
110
- let outcome;
111
- outcome = Promise.race([doctorPromise, timeout]).then((doctor) => {
112
- if (doctor.manifest.migrationId !== identity.migrationId
113
- || doctor.environment !== environment || doctor.workspaceId !== workspaceId
114
- || doctor.profile !== profile) {
115
- failure(identity, "storage_identity_mismatch", undefined, true);
116
- return null;
117
- }
118
- if (!doctor.applyAllowed) {
119
- failure(identity, doctor.blockers[0]?.code ?? "doctor_blocked", undefined, true);
120
- return null;
121
- }
122
- preflights.set(identity.migrationId, doctor);
123
- reportStatus(frame(identity, "preflight_passed", {
124
- sourceStorageFingerprint,
125
- targetStorageFingerprint,
126
- manifestDigest: doctor.manifestDigest,
127
- }));
128
- return doctor;
129
- }).catch((error) => {
130
- const code = error.code;
131
- if (code === "preflight_stale") {
132
- failure(identity, "preflight_stale", undefined, true, {
133
- retryable: true,
134
- progress: { state: "stale", scannedEntries: Number(error.scannedEntries) || scannedEntries },
135
- });
136
- }
137
- else if (code === "preflight_aborted") {
138
- failure(identity, "preflight_aborted", error, true, {
139
- retryable: true,
140
- progress: { state: "stale", scannedEntries },
141
- });
142
- }
143
- else {
144
- failure(identity, stableErrorCode(code, failureFallback), error, true);
145
- }
146
- return null;
147
- }).finally(() => {
148
- if (timeoutHandle !== undefined)
149
- clearTimeout(timeoutHandle);
150
- if (preflightRuns.get(key) === outcome)
151
- preflightRuns.delete(key);
152
- });
153
- preflightRuns.set(key, outcome);
154
- return outcome;
155
- };
156
- const handle = async (raw) => {
157
- const type = typeof raw === "object" && raw !== null && "type" in raw ? raw.type : null;
158
- if (type !== "daemon:storage-doctor" && type !== "daemon:storage-migrate")
159
- return false;
160
- const schema = type === "daemon:storage-doctor" ? DoctorRequestSchema : MigrateRequestSchema;
161
- const parsed = schema.safeParse(raw);
162
- if (!parsed.success)
163
- return true;
164
- const identity = parsed.data;
165
- if (!matchesIdentity(identity)) {
166
- failure(identity, "storage_identity_mismatch", undefined, true);
167
- return true;
168
- }
169
- if (type === "daemon:storage-doctor") {
170
- await runPreflight(identity);
171
- return true;
172
- }
173
- if (runningMigrationId !== null) {
174
- if (runningMigrationId !== identity.migrationId)
175
- failure(identity, "migration_active");
176
- return true;
177
- }
178
- const migrate = MigrateRequestSchema.parse(identity);
179
- if (appliedMigrationIds.has(identity.migrationId))
180
- return true;
181
- let doctor = preflights.get(identity.migrationId);
182
- if (doctor === undefined) {
183
- const rebuilt = await runPreflight(identity, "preflight_rebuild_failed");
184
- if (rebuilt === null)
185
- return true;
186
- doctor = rebuilt;
187
- }
188
- if (!doctor.applyAllowed || doctor.manifestDigest !== migrate.manifestDigest
189
- || doctor.manifest.migrationId !== identity.migrationId
190
- || doctor.environment !== environment || doctor.workspaceId !== workspaceId || doctor.profile !== profile) {
191
- failure(identity, "preflight_digest_mismatch", undefined, true);
192
- return true;
193
- }
194
- preflights.delete(identity.migrationId);
195
- runningMigrationId = identity.migrationId;
196
- try {
197
- await input.runMigration(doctor, (status) => {
198
- if (StatusSchema.safeParse(status).success)
199
- reportStatus(frame(identity, status));
200
- });
201
- appliedMigrationIds.add(identity.migrationId);
202
- }
203
- catch (error) {
204
- failure(identity, stableErrorCode(error.code, "storage_migration_failed"), error);
205
- }
206
- finally {
207
- runningMigrationId = null;
208
- }
209
- return true;
210
- };
211
- return Object.freeze({ handle });
212
- }