@nowcrew/daemon 0.6.77-beta.8 → 0.6.77-beta.9
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/README.md +7 -0
- package/dist/automatic-daemon-installer.js +8 -6
- package/dist/computer-cli.js +15 -9
- package/dist/daemon-installation-lease.js +7 -4
- package/dist/daemon-local-home.js +35 -0
- package/dist/daemon-migration-controller.js +6 -3
- package/dist/daemon-update-eligibility.js +15 -7
- package/dist/host-execution-coordinator.js +4 -3
- package/dist/main.js +12 -1
- package/dist/managed-service-diagnostics.js +1 -0
- package/dist/managed-service-registry.js +12 -7
- package/dist/managed-service-startup.js +4 -1
- package/dist/serve.js +3 -0
- package/dist/slog.js +14 -1
- package/dist/storage-runtime-readiness.js +10 -3
- package/package.json +9 -10
package/README.md
CHANGED
|
@@ -486,6 +486,11 @@ Workspace persistence root is still selected independently by `NOWWORK_HOME`; it
|
|
|
486
486
|
directory or an ECS-specific NAS directory. Runtime packages, locks, sockets, PIDs and temporary
|
|
487
487
|
files must remain on the machine running the daemon.
|
|
488
488
|
|
|
489
|
+
The same local root also owns shared daemon control state:
|
|
490
|
+
`daemon/managed-services/registry.json`, `daemon/managed-services/installation-leases/`,
|
|
491
|
+
`daemon/compute-execution/`, and `logs/sls-spool/`. This keeps service ownership, upgrade leases,
|
|
492
|
+
host-wide execution admission, and offline log delivery on the daemon host rather than on NAS.
|
|
493
|
+
|
|
489
494
|
The selector is controlled by `NOWWORK_LOCAL_HOME_MODE`:
|
|
490
495
|
|
|
491
496
|
- `auto` (default): use an existing `.nowwork` installation, otherwise preserve an existing `.crew`
|
|
@@ -498,6 +503,8 @@ local root and should point to a local filesystem, never to the NAS Workspace ro
|
|
|
498
503
|
check remains a doctor/migration gate. If the same profile
|
|
499
504
|
is found in both roots, startup/install is refused until the duplicate is resolved explicitly. The
|
|
500
505
|
old `.crew` tree is not deleted by this compatibility layer.
|
|
506
|
+
For shared machine control state, `auto` prefers an existing `~/.nowwork` root; during a mixed
|
|
507
|
+
migration window, set `NOWWORK_LOCAL_HOME_MODE=legacy` on services that still use the old registry.
|
|
501
508
|
|
|
502
509
|
- `src/serve.ts`: connection, negotiation, routing, sync, and legacy boundary.
|
|
503
510
|
- `src/daemon-update-eligibility.ts`, `src/daemon-updater.ts`, and `src/daemon-update-controller.ts`:
|
|
@@ -13,6 +13,7 @@ import { daemonReleaseChannel, isDaemonReleaseVersion } from "./daemon-release-v
|
|
|
13
13
|
import { daemonRegistryCandidates } from "./daemon-registry.js";
|
|
14
14
|
import { npmInvocation } from "./npm-command.js";
|
|
15
15
|
import { discoverExactLegacyService, } from "./automatic-service-migration.js";
|
|
16
|
+
import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
|
|
16
17
|
function profileStorage(platform, userHome, runner) {
|
|
17
18
|
return {
|
|
18
19
|
platform,
|
|
@@ -31,6 +32,7 @@ function sameHostname(left, right) {
|
|
|
31
32
|
function defaultDependencies() {
|
|
32
33
|
const platform = process.platform;
|
|
33
34
|
const userHome = homedir();
|
|
35
|
+
const localHome = resolveDaemonLocalStateRootFromEnv({ userHome, platform, environment: process.env });
|
|
34
36
|
const runner = systemCommandRunner;
|
|
35
37
|
const storage = profileStorage(platform, userHome, runner);
|
|
36
38
|
let managedRecords = [];
|
|
@@ -59,7 +61,7 @@ function defaultDependencies() {
|
|
|
59
61
|
},
|
|
60
62
|
captureService: async (spec, target) => {
|
|
61
63
|
if (platform !== "win32") {
|
|
62
|
-
managedRecords = await readManagedServiceRegistry({ userHome });
|
|
64
|
+
managedRecords = await readManagedServiceRegistry({ userHome, localHome });
|
|
63
65
|
const entryPath = spec.daemonCommand[1];
|
|
64
66
|
const installation = daemonGlobalInstallation(entryPath, platform);
|
|
65
67
|
if (installation === null) {
|
|
@@ -93,7 +95,7 @@ function defaultDependencies() {
|
|
|
93
95
|
managedServices: managedRecords,
|
|
94
96
|
}),
|
|
95
97
|
stopService: (spec) => serviceAction(spec, "stop", runner),
|
|
96
|
-
acquireInstallationLease: (npmPrefix) => acquireDaemonInstallationLease(npmPrefix, { userHome }),
|
|
98
|
+
acquireInstallationLease: (npmPrefix) => acquireDaemonInstallationLease(npmPrefix, { userHome, localHome }),
|
|
97
99
|
installPackage: installExactDaemonPackage,
|
|
98
100
|
saveTargetProfile: async (profile, daemonHome) => {
|
|
99
101
|
await saveProfile(profile, daemonHome, storage);
|
|
@@ -120,7 +122,7 @@ function defaultDependencies() {
|
|
|
120
122
|
spec,
|
|
121
123
|
record,
|
|
122
124
|
runner,
|
|
123
|
-
registry: { userHome },
|
|
125
|
+
registry: { userHome, localHome },
|
|
124
126
|
});
|
|
125
127
|
},
|
|
126
128
|
removeLegacyService: async (legacy) => {
|
|
@@ -147,13 +149,13 @@ function defaultDependencies() {
|
|
|
147
149
|
await restoreExactService(spec, previous, runner);
|
|
148
150
|
return;
|
|
149
151
|
}
|
|
150
|
-
const current = (await readManagedServiceRegistry({ userHome })).find((record) => record.serviceId === spec.id);
|
|
152
|
+
const current = (await readManagedServiceRegistry({ userHome, localHome })).find((record) => record.serviceId === spec.id);
|
|
151
153
|
if (current !== undefined && JSON.stringify(current) !== JSON.stringify(previousManagedRecord)) {
|
|
152
154
|
await uninstallManagedService({
|
|
153
155
|
spec,
|
|
154
156
|
record: current,
|
|
155
157
|
runner,
|
|
156
|
-
registry: { userHome },
|
|
158
|
+
registry: { userHome, localHome },
|
|
157
159
|
});
|
|
158
160
|
}
|
|
159
161
|
if (previousManagedRecord !== undefined) {
|
|
@@ -161,7 +163,7 @@ function defaultDependencies() {
|
|
|
161
163
|
spec,
|
|
162
164
|
record: previousManagedRecord,
|
|
163
165
|
runner,
|
|
164
|
-
registry: { userHome },
|
|
166
|
+
registry: { userHome, localHome },
|
|
165
167
|
});
|
|
166
168
|
if (previous.running)
|
|
167
169
|
await serviceAction(spec, "start", runner);
|
package/dist/computer-cli.js
CHANGED
|
@@ -18,7 +18,7 @@ import { assertManagedServiceIdentity, buildManagedServiceRecord, installManaged
|
|
|
18
18
|
import { inspectManagedServiceDiagnostics } from "./managed-service-diagnostics.js";
|
|
19
19
|
import { readManagedServiceRegistry } from "./managed-service-registry.js";
|
|
20
20
|
import { installDaemonAutomatically, } from "./automatic-daemon-installer.js";
|
|
21
|
-
import { resolveDaemonLocalHome } from "./daemon-local-home.js";
|
|
21
|
+
import { resolveDaemonLocalHome, resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
22
22
|
const COMPUTER_COMMANDS = new Set(["profile", "doctor", "install", "uninstall", "start", "stop", "restart", "status", "upgrade"]);
|
|
23
23
|
export function builtDaemonEntry(moduleUrl = import.meta.url) {
|
|
24
24
|
return moduleUrl.endsWith(".js")
|
|
@@ -187,6 +187,12 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
187
187
|
...(configuredLocalHomeMode === undefined ? {} : { mode: configuredLocalHomeMode }),
|
|
188
188
|
}).daemonHome
|
|
189
189
|
: daemonHome(deps.env);
|
|
190
|
+
const localHome = resolveDaemonLocalStateRoot({
|
|
191
|
+
userHome: deps.userHome,
|
|
192
|
+
platform: deps.platform,
|
|
193
|
+
explicit: deps.env.NOWWORK_LOCAL_HOME,
|
|
194
|
+
mode: configuredLocalHomeMode,
|
|
195
|
+
});
|
|
190
196
|
const profileStorage = {
|
|
191
197
|
platform: deps.platform,
|
|
192
198
|
userHome: deps.userHome,
|
|
@@ -298,7 +304,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
298
304
|
try {
|
|
299
305
|
installationLease = installation === null
|
|
300
306
|
? null
|
|
301
|
-
: await deps.acquireInstallationLease(installation.npmPrefix, { userHome: deps.userHome });
|
|
307
|
+
: await deps.acquireInstallationLease(installation.npmPrefix, { userHome: deps.userHome, localHome });
|
|
302
308
|
await upgradeDaemon(deps.platform, deps.runner, installation?.npmPrefix, releaseTag, configuredRegistryUrl === undefined
|
|
303
309
|
? restartProfile?.registryUrl
|
|
304
310
|
?? configuredRegistryUrl
|
|
@@ -380,7 +386,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
380
386
|
spec,
|
|
381
387
|
record: managedRecord,
|
|
382
388
|
runner: deps.runner,
|
|
383
|
-
registry: { userHome: deps.userHome },
|
|
389
|
+
registry: { userHome: deps.userHome, localHome },
|
|
384
390
|
});
|
|
385
391
|
deps.stdout(`${td("Installed '{{id}}'. Use status to confirm runtime state.", { id: spec.id })}\n`);
|
|
386
392
|
return 0;
|
|
@@ -389,7 +395,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
389
395
|
if (managedRecord === null)
|
|
390
396
|
await uninstallService(spec, deps.runner);
|
|
391
397
|
else {
|
|
392
|
-
const current = (await readManagedServiceRegistry({ userHome: deps.userHome })).find((record) => record.serviceId === spec.id
|
|
398
|
+
const current = (await readManagedServiceRegistry({ userHome: deps.userHome, localHome })).find((record) => record.serviceId === spec.id
|
|
393
399
|
&& record.daemonHome === managedRecord.daemonHome
|
|
394
400
|
&& record.profile === name);
|
|
395
401
|
if (current === undefined) {
|
|
@@ -400,7 +406,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
400
406
|
daemonHome: home,
|
|
401
407
|
npmPrefix: installation.npmPrefix,
|
|
402
408
|
runner: deps.runner,
|
|
403
|
-
registry: { userHome: deps.userHome },
|
|
409
|
+
registry: { userHome: deps.userHome, localHome },
|
|
404
410
|
acquireInstallationLease: deps.acquireInstallationLease,
|
|
405
411
|
});
|
|
406
412
|
}
|
|
@@ -420,7 +426,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
420
426
|
daemonHome: home,
|
|
421
427
|
npmPrefix: installation.npmPrefix,
|
|
422
428
|
runner: deps.runner,
|
|
423
|
-
registry: { userHome: deps.userHome },
|
|
429
|
+
registry: { userHome: deps.userHome, localHome },
|
|
424
430
|
acquireInstallationLease: deps.acquireInstallationLease,
|
|
425
431
|
});
|
|
426
432
|
}
|
|
@@ -430,7 +436,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
430
436
|
spec,
|
|
431
437
|
record: current,
|
|
432
438
|
runner: deps.runner,
|
|
433
|
-
registry: { userHome: deps.userHome },
|
|
439
|
+
registry: { userHome: deps.userHome, localHome },
|
|
434
440
|
});
|
|
435
441
|
}
|
|
436
442
|
}
|
|
@@ -442,7 +448,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
442
448
|
await assertManagedServiceIdentity({
|
|
443
449
|
spec,
|
|
444
450
|
record: managedRecord,
|
|
445
|
-
registry: { userHome: deps.userHome },
|
|
451
|
+
registry: { userHome: deps.userHome, localHome },
|
|
446
452
|
});
|
|
447
453
|
}
|
|
448
454
|
await serviceAction(spec, command, deps.runner);
|
|
@@ -473,7 +479,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
473
479
|
: await inspectManagedServiceDiagnostics({
|
|
474
480
|
spec,
|
|
475
481
|
expectedRecord: managedRecord,
|
|
476
|
-
registry: { userHome: deps.userHome },
|
|
482
|
+
registry: { userHome: deps.userHome, localHome },
|
|
477
483
|
});
|
|
478
484
|
const checks = [
|
|
479
485
|
...serviceChecks,
|
|
@@ -3,6 +3,7 @@ import { realpathSync } from "node:fs";
|
|
|
3
3
|
import { mkdir, open } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
|
+
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
6
7
|
import { defaultProcessController } from "./execution-journal.js";
|
|
7
8
|
import { createJournalLease, createJournalLeaseRegistry, inspectJournalLock, } from "./execution-journal-lock.js";
|
|
8
9
|
const registry = createJournalLeaseRegistry();
|
|
@@ -18,9 +19,10 @@ function canonicalPrefix(npmPrefix) {
|
|
|
18
19
|
return absolute;
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
|
-
function leaseDirectory(npmPrefix, userHome) {
|
|
22
|
+
function leaseDirectory(npmPrefix, userHome, localHome) {
|
|
22
23
|
const fingerprint = createHash("sha256").update(resolve(npmPrefix)).digest("hex");
|
|
23
|
-
|
|
24
|
+
const root = localHome ?? resolveDaemonLocalStateRoot({ userHome, platform: process.platform });
|
|
25
|
+
return resolve(root, "daemon", "managed-services", "installation-leases", fingerprint);
|
|
24
26
|
}
|
|
25
27
|
async function syncDirectory(path) {
|
|
26
28
|
if (process.platform === "win32")
|
|
@@ -35,7 +37,8 @@ async function syncDirectory(path) {
|
|
|
35
37
|
}
|
|
36
38
|
export async function acquireDaemonInstallationLease(npmPrefix, options = {}) {
|
|
37
39
|
const normalizedPrefix = canonicalPrefix(npmPrefix);
|
|
38
|
-
const
|
|
40
|
+
const userHome = options.userHome ?? homedir();
|
|
41
|
+
const directory = leaseDirectory(normalizedPrefix, userHome, options.localHome);
|
|
39
42
|
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
40
43
|
const lease = createJournalLease({
|
|
41
44
|
directory,
|
|
@@ -75,7 +78,7 @@ export function daemonInstallationLeaseHeld(npmPrefix) {
|
|
|
75
78
|
export async function inspectDaemonInstallationLease(npmPrefix, options = {}) {
|
|
76
79
|
const normalizedPrefix = canonicalPrefix(npmPrefix);
|
|
77
80
|
return inspectJournalLock({
|
|
78
|
-
directory: leaseDirectory(normalizedPrefix, options.userHome ?? homedir()),
|
|
81
|
+
directory: leaseDirectory(normalizedPrefix, options.userHome ?? homedir(), options.localHome),
|
|
79
82
|
inspectIdentity: options.inspectIdentity ?? defaultProcessController.inspectIdentity,
|
|
80
83
|
});
|
|
81
84
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { posix, win32 } from "node:path";
|
|
3
4
|
const PROFILE = /^[a-z0-9][a-z0-9_-]{0,47}$/u;
|
|
4
5
|
const CONTROL = /[\0\r\n]/u;
|
|
@@ -71,3 +72,37 @@ export function resolveDaemonLocalHome(input) {
|
|
|
71
72
|
return legacy;
|
|
72
73
|
return resolution("nowwork", nowworkRoot, input.profile, input.platform, "auto_new");
|
|
73
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolve the local machine state root shared by service metadata, leases,
|
|
77
|
+
* host coordination and offline logs. It intentionally stays separate from
|
|
78
|
+
* NOWWORK_HOME, which may point at a NAS persistent data root.
|
|
79
|
+
*/
|
|
80
|
+
export function resolveDaemonLocalStateRoot(input) {
|
|
81
|
+
const paths = pathsFor(input.platform);
|
|
82
|
+
const mode = parseDaemonLocalHomeMode(input.mode);
|
|
83
|
+
const nowworkRoot = absoluteRoot(input.explicit ?? paths.join(input.userHome, ".nowwork"), input.userHome, input.platform);
|
|
84
|
+
const legacyRoot = absoluteRoot(paths.join(input.userHome, ".crew"), input.userHome, input.platform);
|
|
85
|
+
if (mode === "legacy")
|
|
86
|
+
return legacyRoot;
|
|
87
|
+
if (input.explicit !== undefined || mode === "nowwork")
|
|
88
|
+
return nowworkRoot;
|
|
89
|
+
const exists = input.exists ?? existsSync;
|
|
90
|
+
// Once the new root exists, shared state must follow it even while the
|
|
91
|
+
// legacy root is retained as a rollback source.
|
|
92
|
+
if (exists(nowworkRoot))
|
|
93
|
+
return nowworkRoot;
|
|
94
|
+
if (exists(legacyRoot))
|
|
95
|
+
return legacyRoot;
|
|
96
|
+
return nowworkRoot;
|
|
97
|
+
}
|
|
98
|
+
export function resolveDaemonLocalStateRootFromEnv(input = {}) {
|
|
99
|
+
const environment = input.environment ?? process.env;
|
|
100
|
+
return resolveDaemonLocalStateRoot({
|
|
101
|
+
userHome: input.userHome ?? homedir(),
|
|
102
|
+
platform: input.platform ?? process.platform,
|
|
103
|
+
...(environment.NOWWORK_LOCAL_HOME === undefined ? {} : { explicit: environment.NOWWORK_LOCAL_HOME }),
|
|
104
|
+
...(environment.NOWWORK_LOCAL_HOME_MODE === undefined && environment.NOWWORK_STORAGE_MODE === undefined
|
|
105
|
+
? {}
|
|
106
|
+
: { mode: environment.NOWWORK_LOCAL_HOME_MODE ?? environment.NOWWORK_STORAGE_MODE }),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -21,6 +21,9 @@ const MigrationMessageSchema = z.object({
|
|
|
21
21
|
function migrationBackupRoot(input, id) {
|
|
22
22
|
return resolve(input.backupRoot ?? resolve(input.userHome ?? homedir(), ".crew", "migration-backups"), id);
|
|
23
23
|
}
|
|
24
|
+
function legacyLocalHome(input) {
|
|
25
|
+
return resolve(input.userHome ?? homedir(), ".crew");
|
|
26
|
+
}
|
|
24
27
|
async function assertEmptyOrMissing(path) {
|
|
25
28
|
try {
|
|
26
29
|
const entries = await readdir(path);
|
|
@@ -77,10 +80,10 @@ async function backupAndCopy(input, id, targetHome, targetAgents, service) {
|
|
|
77
80
|
}
|
|
78
81
|
async function cleanupCurrentServiceDefault(spec, input) {
|
|
79
82
|
const userHome = input.userHome ?? homedir();
|
|
80
|
-
const records = await readManagedServiceRegistry({ userHome });
|
|
83
|
+
const records = await readManagedServiceRegistry({ userHome, localHome: legacyLocalHome(input) });
|
|
81
84
|
const record = records.find((candidate) => candidate.profile === spec.profile && candidate.daemonHome === input.currentDaemonHome);
|
|
82
85
|
if (record !== undefined && (spec.platform === "darwin" || spec.platform === "linux")) {
|
|
83
|
-
await uninstallManagedService({ spec, record, runner: systemCommandRunner, registry: { userHome } });
|
|
86
|
+
await uninstallManagedService({ spec, record, runner: systemCommandRunner, registry: { userHome, localHome: legacyLocalHome(input) } });
|
|
84
87
|
return;
|
|
85
88
|
}
|
|
86
89
|
// Windows and legacy descriptors are handled by the normal CLI/service lifecycle.
|
|
@@ -148,7 +151,7 @@ export function createDaemonMigrationController(input) {
|
|
|
148
151
|
throw new Error("current managed service identity is not exact and running");
|
|
149
152
|
currentService = { installed: exact.installed, running: exact.running };
|
|
150
153
|
if (platform === "darwin" || platform === "linux") {
|
|
151
|
-
const records = await (input.readManagedRecords ?? (() => readManagedServiceRegistry({ userHome })))();
|
|
154
|
+
const records = await (input.readManagedRecords ?? (() => readManagedServiceRegistry({ userHome, localHome: legacyLocalHome(input) })))();
|
|
152
155
|
managedRecord = records.find((record) => record.serviceId === currentSpec.id && record.profile === input.profileName && record.daemonHome === input.currentDaemonHome) ?? null;
|
|
153
156
|
if (managedRecord === null)
|
|
154
157
|
throw new Error("current managed service registry record is missing");
|
|
@@ -9,6 +9,7 @@ import { daemonGlobalInstallation } from "./daemon-installation.js";
|
|
|
9
9
|
import { daemonInstallationLeaseHeld } from "./daemon-installation-lease.js";
|
|
10
10
|
import { inspectLegacyServiceInstallation } from "./legacy-service-installation.js";
|
|
11
11
|
import { inspectWindowsServiceUpdateScope } from "./windows-scheduled-task.js";
|
|
12
|
+
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
12
13
|
import { assertRegistryCoversDescriptors, listManagedServiceDescriptorPaths, managedServiceIdentityMatches, readManagedServiceRegistry, serviceDescriptorSha256, } from "./managed-service-registry.js";
|
|
13
14
|
export async function managedDaemonCapabilities(eligibility) {
|
|
14
15
|
try {
|
|
@@ -20,24 +21,31 @@ export async function managedDaemonCapabilities(eligibility) {
|
|
|
20
21
|
return [];
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
|
-
function defaults() {
|
|
24
|
+
function defaults(profileName) {
|
|
25
|
+
const userHome = homedir();
|
|
26
|
+
const localHome = resolveDaemonLocalStateRoot({
|
|
27
|
+
userHome,
|
|
28
|
+
platform: process.platform,
|
|
29
|
+
explicit: process.env.NOWWORK_LOCAL_HOME,
|
|
30
|
+
mode: process.env.NOWWORK_LOCAL_HOME_MODE ?? process.env.NOWWORK_STORAGE_MODE,
|
|
31
|
+
});
|
|
24
32
|
return {
|
|
25
33
|
platform: process.platform,
|
|
26
|
-
profileHome: daemonHome(),
|
|
27
|
-
userHome
|
|
34
|
+
profileHome: daemonHome(process.env, profileName),
|
|
35
|
+
userHome,
|
|
28
36
|
uid: process.getuid?.(),
|
|
29
37
|
nodePath: process.execPath,
|
|
30
38
|
entryPath: builtDaemonEntry(),
|
|
31
39
|
loadProfile,
|
|
32
|
-
readManagedServices: () => readManagedServiceRegistry(),
|
|
33
|
-
listManagedDescriptorPaths: () => listManagedServiceDescriptorPaths(process.platform,
|
|
40
|
+
readManagedServices: () => readManagedServiceRegistry({ userHome, localHome }),
|
|
41
|
+
listManagedDescriptorPaths: () => listManagedServiceDescriptorPaths(process.platform, userHome),
|
|
34
42
|
readServiceDescriptor,
|
|
35
43
|
serviceStatus,
|
|
36
44
|
assertWritable: (path) => access(path, constants.W_OK),
|
|
37
45
|
installationLeaseHeld: daemonInstallationLeaseHeld,
|
|
38
46
|
installationRealpath: realpathSync.native,
|
|
39
47
|
inspectWindowsServiceUpdateScope,
|
|
40
|
-
inspectLegacyDescriptorInstallation: (descriptorPath) => inspectLegacyServiceInstallation(descriptorPath, process.platform,
|
|
48
|
+
inspectLegacyDescriptorInstallation: (descriptorPath) => inspectLegacyServiceInstallation(descriptorPath, process.platform, userHome, process.getuid?.()),
|
|
41
49
|
};
|
|
42
50
|
}
|
|
43
51
|
async function registryCoversUpdateScope(services, descriptorPaths, targetDescriptorPath, npmPrefix, inspectLegacyDescriptorInstallation) {
|
|
@@ -57,7 +65,7 @@ async function registryCoversUpdateScope(services, descriptorPaths, targetDescri
|
|
|
57
65
|
return true;
|
|
58
66
|
}
|
|
59
67
|
export async function detectDaemonUpdateEligibility(profileName, overrides = {}) {
|
|
60
|
-
const deps = { ...defaults(), ...overrides };
|
|
68
|
+
const deps = { ...defaults(profileName), ...overrides };
|
|
61
69
|
if (deps.platform !== "darwin" && deps.platform !== "linux" && deps.platform !== "win32") {
|
|
62
70
|
return { eligible: false, reason: "unsupported_platform" };
|
|
63
71
|
}
|
|
@@ -5,6 +5,7 @@ import { randomUUID } from "node:crypto";
|
|
|
5
5
|
import { defaultProcessController } from "./execution-journal.js";
|
|
6
6
|
import { createJournalLease, createJournalLeaseRegistry, JournalLockedError, } from "./execution-journal-lock.js";
|
|
7
7
|
import { dslog } from "./slog.js";
|
|
8
|
+
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
8
9
|
const HOST_EXECUTION_SLOT_COUNT = 10;
|
|
9
10
|
const HOST_STARTUP_SLOT_COUNT = 10;
|
|
10
11
|
const HOST_STARTUP_GAP_MS = 500;
|
|
@@ -16,10 +17,10 @@ export class HostReservationCancelledError extends Error {
|
|
|
16
17
|
super(message);
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
|
-
export function defaultHostExecutionCoordinatorRoot(userHome = homedir()) {
|
|
20
|
+
export function defaultHostExecutionCoordinatorRoot(userHome = homedir(), localHome) {
|
|
20
21
|
// Deliberately independent of CREW_DAEMON_HOME and agentsRoot: every profile
|
|
21
22
|
// owned by this OS user must share the same physical-compute safety boundary.
|
|
22
|
-
return resolve(userHome,
|
|
23
|
+
return resolve(localHome ?? resolveDaemonLocalStateRoot({ userHome, platform: process.platform }), "daemon", "compute-execution");
|
|
23
24
|
}
|
|
24
25
|
async function syncDirectory(path) {
|
|
25
26
|
if (process.platform === "win32")
|
|
@@ -132,7 +133,7 @@ async function writeLaunchTimestamp(path, value) {
|
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
export function createHostExecutionCoordinator(options = {}) {
|
|
135
|
-
const root = resolve(options.root ?? defaultHostExecutionCoordinatorRoot());
|
|
136
|
+
const root = resolve(options.root ?? defaultHostExecutionCoordinatorRoot(options.userHome, options.localHome));
|
|
136
137
|
const executionSlots = options.executionSlots ?? HOST_EXECUTION_SLOT_COUNT;
|
|
137
138
|
const startupSlots = options.startupSlots ?? HOST_STARTUP_SLOT_COUNT;
|
|
138
139
|
const startupGapMs = options.startupGapMs ?? HOST_STARTUP_GAP_MS;
|
package/dist/main.js
CHANGED
|
@@ -30,6 +30,7 @@ import { storageMigrationStatusReplay } from "./storage-status-replay.js";
|
|
|
30
30
|
import { runStorageWorkerCommand } from "./storage-worker-command.js";
|
|
31
31
|
import { probeStorageMigrationRuntime } from "./storage-runtime-readiness.js";
|
|
32
32
|
import { createStorageTargetConfirmationHandler } from "./storage-target-confirmation.js";
|
|
33
|
+
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
33
34
|
async function main() {
|
|
34
35
|
const workerResult = await runStorageWorkerCommand(process.argv.slice(2));
|
|
35
36
|
if (workerResult !== null) {
|
|
@@ -73,6 +74,14 @@ async function main() {
|
|
|
73
74
|
if (values["daemon-home"])
|
|
74
75
|
process.env.CREW_DAEMON_HOME = values["daemon-home"];
|
|
75
76
|
let activeDaemonHome;
|
|
77
|
+
const localHome = resolveDaemonLocalStateRoot({
|
|
78
|
+
userHome: homedir(),
|
|
79
|
+
platform: process.platform,
|
|
80
|
+
explicit: process.env.NOWWORK_LOCAL_HOME,
|
|
81
|
+
mode: process.env.NOWWORK_LOCAL_HOME_MODE
|
|
82
|
+
?? process.env.NOWWORK_STORAGE_MODE
|
|
83
|
+
?? (values["daemon-home"] === undefined ? undefined : "legacy"),
|
|
84
|
+
});
|
|
76
85
|
if (values.profile) {
|
|
77
86
|
const home = daemonHome(process.env, values.profile);
|
|
78
87
|
activeDaemonHome = home;
|
|
@@ -83,6 +92,7 @@ async function main() {
|
|
|
83
92
|
platform: process.platform,
|
|
84
93
|
profile: profile.name,
|
|
85
94
|
userHome: homedir(),
|
|
95
|
+
localHome,
|
|
86
96
|
uid: process.getuid?.(),
|
|
87
97
|
daemonHome: home,
|
|
88
98
|
agentsRoot: resolveAgentsRoot(profile.agentsRoot, homedir(), process.platform),
|
|
@@ -141,7 +151,7 @@ async function main() {
|
|
|
141
151
|
: daemonGlobalInstallation(builtDaemonEntry(), process.platform);
|
|
142
152
|
const installationLease = installation === null
|
|
143
153
|
? null
|
|
144
|
-
: await acquireDaemonInstallationLease(installation.npmPrefix);
|
|
154
|
+
: await acquireDaemonInstallationLease(installation.npmPrefix, { localHome });
|
|
145
155
|
const migrationRuntime = values.profile === undefined || activeDaemonHome === undefined || storageRuntime !== null
|
|
146
156
|
? { ready: false, reason: "not_legacy_source" }
|
|
147
157
|
: await probeStorageMigrationRuntime({
|
|
@@ -198,6 +208,7 @@ async function main() {
|
|
|
198
208
|
await runWithDaemonInstallationLease(installationLease, async () => {
|
|
199
209
|
const service = serve(runtimeConfig, {
|
|
200
210
|
...(values.profile === undefined ? {} : { profileName: values.profile }),
|
|
211
|
+
localHome,
|
|
201
212
|
...(storageMigration === undefined ? {} : { storageMigration }),
|
|
202
213
|
});
|
|
203
214
|
await runServeLifecycle(service);
|
|
@@ -16,6 +16,7 @@ export async function inspectManagedServiceDiagnostics(input) {
|
|
|
16
16
|
const inspectLease = input.inspectInstallationLease
|
|
17
17
|
?? (() => inspectDaemonInstallationLease(input.expectedRecord.npmPrefix, {
|
|
18
18
|
...(userHome === undefined ? {} : { userHome }),
|
|
19
|
+
...(registry.localHome === undefined ? {} : { localHome: registry.localHome }),
|
|
19
20
|
}));
|
|
20
21
|
let services = null;
|
|
21
22
|
let registryCheck;
|
|
@@ -5,6 +5,7 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { dirname, isAbsolute, resolve } from "node:path";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { acquireProfileSaveLock } from "./computer-profile-lock.js";
|
|
8
|
+
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
8
9
|
const AbsolutePathSchema = z.string().min(1).refine(isAbsolute, "must be an absolute path");
|
|
9
10
|
const ManagedServiceRecordSchema = z.object({
|
|
10
11
|
version: z.literal(1),
|
|
@@ -42,11 +43,15 @@ export class ManagedServiceConflictError extends ManagedServiceRegistryError {
|
|
|
42
43
|
this.owner = owner;
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
|
-
function registryDirectory(userHome) {
|
|
46
|
-
|
|
46
|
+
function registryDirectory(userHome, localHome) {
|
|
47
|
+
const root = localHome ?? resolveDaemonLocalStateRoot({
|
|
48
|
+
userHome,
|
|
49
|
+
platform: process.platform,
|
|
50
|
+
});
|
|
51
|
+
return resolve(root, "daemon", "managed-services");
|
|
47
52
|
}
|
|
48
|
-
export function managedServiceRegistryPath(userHome = homedir()) {
|
|
49
|
-
return resolve(registryDirectory(userHome), "registry.json");
|
|
53
|
+
export function managedServiceRegistryPath(userHome = homedir(), localHome) {
|
|
54
|
+
return resolve(registryDirectory(userHome, localHome), "registry.json");
|
|
50
55
|
}
|
|
51
56
|
export async function listManagedServiceDescriptorPaths(platform, userHome = homedir()) {
|
|
52
57
|
const directory = platform === "darwin"
|
|
@@ -219,14 +224,14 @@ export function managedServiceConflict(services, candidate) {
|
|
|
219
224
|
}
|
|
220
225
|
export async function withManagedServiceRegistryLock(options, operation) {
|
|
221
226
|
const userHome = options.userHome ?? homedir();
|
|
222
|
-
const directory = registryDirectory(userHome);
|
|
227
|
+
const directory = registryDirectory(userHome, options.localHome);
|
|
223
228
|
const release = await acquireProfileSaveLock(directory, {
|
|
224
229
|
...(options.lockTimeoutMs === undefined ? {} : { timeoutMs: options.lockTimeoutMs }),
|
|
225
230
|
...(options.lockRetryMs === undefined ? {} : { retryMs: options.lockRetryMs }),
|
|
226
231
|
});
|
|
227
232
|
let operationError;
|
|
228
233
|
try {
|
|
229
|
-
const path = managedServiceRegistryPath(userHome);
|
|
234
|
+
const path = managedServiceRegistryPath(userHome, options.localHome);
|
|
230
235
|
const services = await readRegistryFile(path);
|
|
231
236
|
return await operation({
|
|
232
237
|
services,
|
|
@@ -258,7 +263,7 @@ export async function withManagedServiceRegistryLock(options, operation) {
|
|
|
258
263
|
}
|
|
259
264
|
}
|
|
260
265
|
export async function readManagedServiceRegistry(options = {}) {
|
|
261
|
-
return readRegistryFile(managedServiceRegistryPath(options.userHome ?? homedir()));
|
|
266
|
+
return readRegistryFile(managedServiceRegistryPath(options.userHome ?? homedir(), options.localHome));
|
|
262
267
|
}
|
|
263
268
|
export async function registerManagedService(input, options = {}) {
|
|
264
269
|
const candidate = normalizeManagedServiceRecord(input);
|
|
@@ -17,7 +17,10 @@ export async function assertManagedServiceStartup(input) {
|
|
|
17
17
|
...(input.environment === undefined ? {} : { environment: input.environment }),
|
|
18
18
|
});
|
|
19
19
|
const readRegistry = input.readManagedServices
|
|
20
|
-
?? (() => readManagedServiceRegistry({
|
|
20
|
+
?? (() => readManagedServiceRegistry({
|
|
21
|
+
userHome: input.userHome,
|
|
22
|
+
...(input.localHome === undefined ? {} : { localHome: input.localHome }),
|
|
23
|
+
}));
|
|
21
24
|
const listDescriptors = input.listManagedDescriptorPaths
|
|
22
25
|
?? (() => listManagedServiceDescriptorPaths(input.platform, input.userHome));
|
|
23
26
|
const readDescriptor = input.readServiceDescriptor ?? readServiceDescriptor;
|
package/dist/serve.js
CHANGED
|
@@ -53,6 +53,7 @@ import { handleRuntimeHealthProbeFrame } from "./runtime-health-handler.js";
|
|
|
53
53
|
import { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
54
54
|
import { createServeMigrationController, handleMigrationControlMessage } from "./daemon-migration-wiring.js";
|
|
55
55
|
import { createCodexHomeModeController } from "./codex-home-mode-controller.js";
|
|
56
|
+
import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
|
|
56
57
|
import { attachStorageMigrationRuntime, createServeStorageStatusDelivery, handleStorageMigrationControl, replayStorageMigrationStatus, storageMigrationCapabilities, } from "./storage-serve-adapter.js";
|
|
57
58
|
export { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
58
59
|
// normalize.ts 的活动种类 → activity 枚举
|
|
@@ -83,8 +84,10 @@ export function serve(config, opts = {}) {
|
|
|
83
84
|
let detectedExecutionRuntimes = [], executionRuntimesKnown = false;
|
|
84
85
|
let runtimeFacts = null;
|
|
85
86
|
const runtimeProbe = createRuntimeProbeCoordinator();
|
|
87
|
+
const localHome = opts.localHome ?? resolveDaemonLocalStateRootFromEnv();
|
|
86
88
|
const hostCoordinator = opts.execution?.hostCoordinator
|
|
87
89
|
?? createHostExecutionCoordinator({
|
|
90
|
+
localHome,
|
|
88
91
|
executionSlots: config.executionLimits.maxParallelTotal,
|
|
89
92
|
startupSlots: config.executionLimits.maxStartingTotal,
|
|
90
93
|
startupGapMs: config.executionLimits.startupGapMs,
|
package/dist/slog.js
CHANGED
|
@@ -15,6 +15,7 @@ import { homedir } from "node:os";
|
|
|
15
15
|
import { join, resolve } from "node:path";
|
|
16
16
|
import { createHash } from "node:crypto";
|
|
17
17
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
|
|
18
|
+
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
18
19
|
const FLUSH_INTERVAL_MS = 1500;
|
|
19
20
|
const BATCH_SIZE = 50;
|
|
20
21
|
const SPOOL_MAX_FILES = 50; // spool 目录文件数上限,超出丢最旧(日志非关键数据,防无限膨胀)
|
|
@@ -27,6 +28,7 @@ let flushPromise = null;
|
|
|
27
28
|
let inFlightBatch = [];
|
|
28
29
|
let lastErrorAt = 0;
|
|
29
30
|
let exitHookInstalled = false;
|
|
31
|
+
let localStateRoot;
|
|
30
32
|
function deploymentEnvironment(serverHost) {
|
|
31
33
|
if (serverHost === "nowwork-in.nowcoder.com")
|
|
32
34
|
return "prod";
|
|
@@ -40,12 +42,23 @@ function deploymentEnvironment(serverHost) {
|
|
|
40
42
|
return "unknown";
|
|
41
43
|
}
|
|
42
44
|
function spoolDir() {
|
|
43
|
-
return process.env.CREW_SLS_SPOOL_DIR ?? join(
|
|
45
|
+
return process.env.CREW_SLS_SPOOL_DIR ?? join(localStateRoot ?? resolveDaemonLocalStateRoot({
|
|
46
|
+
userHome: homedir(),
|
|
47
|
+
platform: process.platform,
|
|
48
|
+
explicit: process.env.NOWWORK_LOCAL_HOME,
|
|
49
|
+
mode: process.env.NOWWORK_LOCAL_HOME_MODE ?? process.env.NOWWORK_STORAGE_MODE,
|
|
50
|
+
}), "logs", "sls-spool");
|
|
44
51
|
}
|
|
45
52
|
/** serve/run 启动时调用一次;disabled(CREW_SLS_LOG=off)则保持 no-op。 */
|
|
46
53
|
export function initSlog(serverUrl, machineToken, identity = {}) {
|
|
47
54
|
if (process.env.CREW_SLS_LOG === "off" || process.env.CREW_SLS_LOG === "0")
|
|
48
55
|
return;
|
|
56
|
+
localStateRoot = resolveDaemonLocalStateRoot({
|
|
57
|
+
userHome: homedir(),
|
|
58
|
+
platform: process.platform,
|
|
59
|
+
explicit: process.env.NOWWORK_LOCAL_HOME,
|
|
60
|
+
mode: process.env.NOWWORK_LOCAL_HOME_MODE ?? process.env.NOWWORK_STORAGE_MODE,
|
|
61
|
+
});
|
|
49
62
|
cfg = { serverUrl: serverUrl.replace(/\/+$/, ""), token: machineToken };
|
|
50
63
|
let serverHost;
|
|
51
64
|
try {
|
|
@@ -10,6 +10,7 @@ import { readStorageMigrationPolicy } from "./storage-migration-policy.js";
|
|
|
10
10
|
import { createStorageMigrationRuntimeController, } from "./storage-runtime-controller.js";
|
|
11
11
|
import { resolveEnvTag, resolveNowworkHome } from "./storage-layout.js";
|
|
12
12
|
import { readStorageOwnershipClaim } from "./storage-ownership.js";
|
|
13
|
+
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
13
14
|
const notReady = (reason) => Object.freeze({ ready: false, reason });
|
|
14
15
|
function canonicalPath(value) {
|
|
15
16
|
const absolute = resolve(value);
|
|
@@ -74,6 +75,12 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
74
75
|
serverUrl: z.string().url().parse(rawInput.serverUrl),
|
|
75
76
|
platform: rawInput.platform,
|
|
76
77
|
};
|
|
78
|
+
const localHome = resolveDaemonLocalStateRoot({
|
|
79
|
+
userHome: rawInput.userHome,
|
|
80
|
+
platform: rawInput.platform,
|
|
81
|
+
explicit: rawInput.environment.NOWWORK_LOCAL_HOME,
|
|
82
|
+
mode: rawInput.environment.NOWWORK_LOCAL_HOME_MODE ?? rawInput.environment.NOWWORK_STORAGE_MODE,
|
|
83
|
+
});
|
|
77
84
|
let nowworkHome;
|
|
78
85
|
let environment;
|
|
79
86
|
try {
|
|
@@ -112,7 +119,7 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
112
119
|
};
|
|
113
120
|
let sourceRecord;
|
|
114
121
|
try {
|
|
115
|
-
const records = await dependencies.readManagedServices({ userHome: input.userHome });
|
|
122
|
+
const records = await dependencies.readManagedServices({ userHome: input.userHome, localHome });
|
|
116
123
|
const found = findSourceRecord(records, input);
|
|
117
124
|
if (found === null)
|
|
118
125
|
return notReady("source_service_record_missing");
|
|
@@ -134,7 +141,7 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
134
141
|
const assertSourceIdentity = () => dependencies.assertManagedServiceIdentity({
|
|
135
142
|
spec: sourceSpec,
|
|
136
143
|
record: sourceRecord,
|
|
137
|
-
registry: { userHome: input.userHome },
|
|
144
|
+
registry: { userHome: input.userHome, localHome },
|
|
138
145
|
});
|
|
139
146
|
try {
|
|
140
147
|
await assertSourceIdentity();
|
|
@@ -191,7 +198,7 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
191
198
|
backupArtifacts: [
|
|
192
199
|
{ name: "profile.json", path: profilePath(input.profile, input.currentDaemonHome) },
|
|
193
200
|
{ name: "service-descriptor", path: sourceRecord.descriptorPath },
|
|
194
|
-
{ name: "managed-service-registry.json", path: managedServiceRegistryPath(input.userHome) },
|
|
201
|
+
{ name: "managed-service-registry.json", path: managedServiceRegistryPath(input.userHome, localHome) },
|
|
195
202
|
{ name: "installations.json", path: installationsPath(local.configRoot) },
|
|
196
203
|
],
|
|
197
204
|
}, { assertSourceIdentity });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nowcrew/daemon",
|
|
3
|
-
"version": "0.6.77-beta.
|
|
3
|
+
"version": "0.6.77-beta.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -16,14 +16,6 @@
|
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
21
|
-
"build": "tsc -p tsconfig.json",
|
|
22
|
-
"codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
|
|
23
|
-
"prepublishOnly": "pnpm build && node ../scripts/daemon-release-artifact.mjs --strict-registry",
|
|
24
|
-
"test": "vitest run",
|
|
25
|
-
"typecheck": "tsc --noEmit"
|
|
26
|
-
},
|
|
27
19
|
"dependencies": {
|
|
28
20
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
29
21
|
"@nowcrew/cli": "0.4.13",
|
|
@@ -44,5 +36,12 @@
|
|
|
44
36
|
"tsx": "^4.19.0",
|
|
45
37
|
"typescript": "^5.6.0",
|
|
46
38
|
"vitest": "^2.1.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
42
|
+
"build": "tsc -p tsconfig.json",
|
|
43
|
+
"codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"typecheck": "tsc --noEmit"
|
|
47
46
|
}
|
|
48
|
-
}
|
|
47
|
+
}
|