@nowcrew/daemon 0.6.80-beta.2 → 0.6.80-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.
- package/dist/automatic-daemon-installer.js +22 -6
- package/dist/computer-cli.js +3 -12
- package/dist/computer-service.js +24 -1
- package/dist/daemon-local-home.js +23 -0
- package/dist/daemon-migration-controller.js +4 -3
- package/dist/daemon-update-eligibility.js +7 -4
- package/dist/local-executor.js +7 -10
- package/dist/main.js +5 -12
- package/dist/managed-service-lifecycle.js +6 -2
- package/dist/package-manager-env.js +75 -0
- package/dist/runner.js +1 -1
- package/dist/runtime-env.js +21 -0
- package/dist/serve.js +1 -1
- package/dist/storage-config.js +26 -0
- package/dist/storage-migration-controller.js +0 -1
- package/dist/storage-runtime-readiness.js +50 -16
- package/dist/storage-worker-executor.js +29 -11
- package/dist/storage-worker-operation.js +6 -0
- package/package.json +9 -10
|
@@ -29,6 +29,14 @@ function profileStorage(platform, userHome, runner) {
|
|
|
29
29
|
function sameHostname(left, right) {
|
|
30
30
|
return new URL(left).hostname.toLowerCase() === new URL(right).hostname.toLowerCase();
|
|
31
31
|
}
|
|
32
|
+
function localHomeForTarget(target, platform) {
|
|
33
|
+
const paths = platform === "win32" ? win32 : posix;
|
|
34
|
+
return localHomeForNpmPrefix(target.npmPrefix, platform);
|
|
35
|
+
}
|
|
36
|
+
function localHomeForNpmPrefix(npmPrefix, platform) {
|
|
37
|
+
const paths = platform === "win32" ? win32 : posix;
|
|
38
|
+
return paths.resolve(npmPrefix, "..", "..");
|
|
39
|
+
}
|
|
32
40
|
function defaultDependencies() {
|
|
33
41
|
const platform = process.platform;
|
|
34
42
|
const userHome = homedir();
|
|
@@ -61,7 +69,7 @@ function defaultDependencies() {
|
|
|
61
69
|
},
|
|
62
70
|
captureService: async (spec, target) => {
|
|
63
71
|
if (platform !== "win32") {
|
|
64
|
-
managedRecords = await readManagedServiceRegistry({ userHome, localHome });
|
|
72
|
+
managedRecords = await readManagedServiceRegistry({ userHome, localHome: localHomeForTarget(target, platform) });
|
|
65
73
|
const entryPath = spec.daemonCommand[1];
|
|
66
74
|
const installation = daemonGlobalInstallation(entryPath, platform);
|
|
67
75
|
if (installation === null) {
|
|
@@ -95,7 +103,10 @@ function defaultDependencies() {
|
|
|
95
103
|
managedServices: managedRecords,
|
|
96
104
|
}),
|
|
97
105
|
stopService: (spec) => serviceAction(spec, "stop", runner),
|
|
98
|
-
acquireInstallationLease: (npmPrefix) => acquireDaemonInstallationLease(npmPrefix, {
|
|
106
|
+
acquireInstallationLease: (npmPrefix, options) => acquireDaemonInstallationLease(npmPrefix, {
|
|
107
|
+
userHome,
|
|
108
|
+
localHome: options?.localHome ?? localHome,
|
|
109
|
+
}),
|
|
99
110
|
installPackage: installExactDaemonPackage,
|
|
100
111
|
saveTargetProfile: async (profile, daemonHome) => {
|
|
101
112
|
await saveProfile(profile, daemonHome, storage);
|
|
@@ -149,13 +160,16 @@ function defaultDependencies() {
|
|
|
149
160
|
await restoreExactService(spec, previous, runner);
|
|
150
161
|
return;
|
|
151
162
|
}
|
|
152
|
-
const
|
|
163
|
+
const restoreLocalHome = previousManagedRecord === undefined
|
|
164
|
+
? localHome
|
|
165
|
+
: localHomeForNpmPrefix(previousManagedRecord.npmPrefix, platform);
|
|
166
|
+
const current = (await readManagedServiceRegistry({ userHome, localHome: restoreLocalHome })).find((record) => record.serviceId === spec.id);
|
|
153
167
|
if (current !== undefined && JSON.stringify(current) !== JSON.stringify(previousManagedRecord)) {
|
|
154
168
|
await uninstallManagedService({
|
|
155
169
|
spec,
|
|
156
170
|
record: current,
|
|
157
171
|
runner,
|
|
158
|
-
registry: { userHome, localHome },
|
|
172
|
+
registry: { userHome, localHome: restoreLocalHome },
|
|
159
173
|
});
|
|
160
174
|
}
|
|
161
175
|
if (previousManagedRecord !== undefined) {
|
|
@@ -163,7 +177,7 @@ function defaultDependencies() {
|
|
|
163
177
|
spec,
|
|
164
178
|
record: previousManagedRecord,
|
|
165
179
|
runner,
|
|
166
|
-
registry: { userHome, localHome },
|
|
180
|
+
registry: { userHome, localHome: restoreLocalHome },
|
|
167
181
|
});
|
|
168
182
|
if (previous.running)
|
|
169
183
|
await serviceAction(spec, "start", runner);
|
|
@@ -283,7 +297,9 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
|
|
|
283
297
|
stopped = true;
|
|
284
298
|
await dependencies.stopService(spec);
|
|
285
299
|
}
|
|
286
|
-
const lease = await dependencies.acquireInstallationLease(target.npmPrefix
|
|
300
|
+
const lease = await dependencies.acquireInstallationLease(target.npmPrefix, {
|
|
301
|
+
localHome: localHomeForTarget(target, dependencies.platform),
|
|
302
|
+
});
|
|
287
303
|
let installFailure;
|
|
288
304
|
try {
|
|
289
305
|
await dependencies.installPackage(target.npmPrefix, dependencies.version, dependencies.platform, dependencies.runner, target.registryUrl);
|
package/dist/computer-cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { posix, resolve, win32 } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { assertProfileAgentsRootUnique, daemonHome, inspectProfileAgentsRoot, listProfiles, loadProfile, PROFILE_AGENTS_ROOT_CONFLICT_MESSAGE, ProfileAgentsRootConflictError, profileIsPrivate, profilePath, publicProfile, removeProfile, resolveAgentsRoot, saveProfile, windowsDpapiProtector, } from "./computer-profile.js";
|
|
6
|
-
import { buildServiceSpec, doctorService, hardenWindowsProfile, installService, readServiceDescriptor, serviceAction, serviceStatus, systemCommandRunner, uninstallService, upgradeDaemon, windowsProfileIsPrivate, } from "./computer-service.js";
|
|
6
|
+
import { buildServiceSpec, doctorService, hardenWindowsProfile, installService, readServiceDescriptor, serviceAction, serviceStatus, systemCommandRunner, uninstallService, upgradeDaemon, windowsProfileIsPrivate, storageServiceEnvironment, } from "./computer-service.js";
|
|
7
7
|
import { releaseTagForServerUrl, assertReleaseTagForServerUrl } from "./automatic-install-target.js";
|
|
8
8
|
import { normalizeDaemonNpmRegistry } from "./npm-registry.js";
|
|
9
9
|
import { npmInvocation } from "./npm-command.js";
|
|
@@ -89,17 +89,6 @@ function translatedError(error, td) {
|
|
|
89
89
|
}
|
|
90
90
|
return td(error.message);
|
|
91
91
|
}
|
|
92
|
-
function storageServiceEnvironment(env) {
|
|
93
|
-
return {
|
|
94
|
-
...(env.NOWWORK_HOME === undefined ? {} : { NOWWORK_HOME: env.NOWWORK_HOME }),
|
|
95
|
-
...(env.NOWWORK_ENV_TAG === undefined ? {} : { NOWWORK_ENV_TAG: env.NOWWORK_ENV_TAG }),
|
|
96
|
-
...(env.NOWWORK_STORAGE_POLICY_PATH === undefined
|
|
97
|
-
? {} : { NOWWORK_STORAGE_POLICY_PATH: env.NOWWORK_STORAGE_POLICY_PATH }),
|
|
98
|
-
...(env.NOWWORK_LOCAL_HOME === undefined ? {} : { NOWWORK_LOCAL_HOME: env.NOWWORK_LOCAL_HOME }),
|
|
99
|
-
...(env.NOWWORK_LOCAL_HOME_MODE === undefined && env.NOWWORK_STORAGE_MODE === undefined
|
|
100
|
-
? {} : { NOWWORK_LOCAL_HOME_MODE: env.NOWWORK_LOCAL_HOME_MODE ?? env.NOWWORK_STORAGE_MODE }),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
92
|
export async function runComputerCommand(argv, overrides = {}) {
|
|
104
93
|
const command = argv[0];
|
|
105
94
|
if (!command || !COMPUTER_COMMANDS.has(command))
|
|
@@ -195,6 +184,8 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
195
184
|
platform: deps.platform,
|
|
196
185
|
explicit: deps.env.NOWWORK_LOCAL_HOME,
|
|
197
186
|
mode: configuredLocalHomeMode,
|
|
187
|
+
...(resolvedProfile === undefined ? {} : { profile: resolvedProfile }),
|
|
188
|
+
...(home === undefined ? {} : { daemonHome: home }),
|
|
198
189
|
});
|
|
199
190
|
const profileStorage = {
|
|
200
191
|
platform: deps.platform,
|
package/dist/computer-service.js
CHANGED
|
@@ -32,6 +32,18 @@ export const systemCommandRunner = async (command, args) => {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
|
+
/** Build the non-secret storage environment persisted in managed services. */
|
|
36
|
+
export function storageServiceEnvironment(env) {
|
|
37
|
+
return {
|
|
38
|
+
...(env.NOWWORK_HOME === undefined ? {} : { NOWWORK_HOME: env.NOWWORK_HOME }),
|
|
39
|
+
...(env.NOWWORK_ENV_TAG === undefined ? {} : { NOWWORK_ENV_TAG: env.NOWWORK_ENV_TAG }),
|
|
40
|
+
...(env.NOWWORK_STORAGE_POLICY_PATH === undefined
|
|
41
|
+
? {} : { NOWWORK_STORAGE_POLICY_PATH: env.NOWWORK_STORAGE_POLICY_PATH }),
|
|
42
|
+
...(env.NOWWORK_LOCAL_HOME === undefined ? {} : { NOWWORK_LOCAL_HOME: env.NOWWORK_LOCAL_HOME }),
|
|
43
|
+
...(env.NOWWORK_LOCAL_HOME_MODE === undefined && env.NOWWORK_STORAGE_MODE === undefined
|
|
44
|
+
? {} : { NOWWORK_LOCAL_HOME_MODE: env.NOWWORK_LOCAL_HOME_MODE ?? env.NOWWORK_STORAGE_MODE }),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
35
47
|
function serviceIdentitySuffix(profileHome, userHome) {
|
|
36
48
|
if (profileHome === undefined || resolve(profileHome) === resolve(userHome, ".crew", "daemon"))
|
|
37
49
|
return "";
|
|
@@ -50,6 +62,9 @@ function xml(value) {
|
|
|
50
62
|
function systemdArg(value) {
|
|
51
63
|
return `"${assertSingleLine(value, "service argument").replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%")}"`;
|
|
52
64
|
}
|
|
65
|
+
function systemdEnvironmentEntry(key, value) {
|
|
66
|
+
return `Environment=${systemdArg(`${key}=${value}`)}`;
|
|
67
|
+
}
|
|
53
68
|
export function windowsCommandArg(value) {
|
|
54
69
|
const input = assertSingleLine(value, "service argument");
|
|
55
70
|
return `"${input.replace(/(\\*)"/g, "$1$1\\\"").replace(/(\\*)$/g, "$1$1")}"`;
|
|
@@ -100,6 +115,14 @@ export function buildServiceSpec(input) {
|
|
|
100
115
|
}
|
|
101
116
|
if (platform === "linux") {
|
|
102
117
|
const id = `nowcrew-daemon-${profile}${identitySuffix}.service`;
|
|
118
|
+
const environmentEntries = [
|
|
119
|
+
["NOWWORK_HOME", environment.NOWWORK_HOME],
|
|
120
|
+
["NOWWORK_ENV_TAG", environment.NOWWORK_ENV_TAG],
|
|
121
|
+
["NOWWORK_STORAGE_POLICY_PATH", environment.NOWWORK_STORAGE_POLICY_PATH],
|
|
122
|
+
["NOWWORK_LOCAL_HOME", environment.NOWWORK_LOCAL_HOME],
|
|
123
|
+
["NOWWORK_LOCAL_HOME_MODE", environment.NOWWORK_LOCAL_HOME_MODE],
|
|
124
|
+
].flatMap(([key, value]) => value === undefined ? [] : [systemdEnvironmentEntry(key, value)]);
|
|
125
|
+
const environmentBlock = environmentEntries.length === 0 ? "" : `${environmentEntries.join("\n")}\n`;
|
|
103
126
|
return {
|
|
104
127
|
platform,
|
|
105
128
|
profile,
|
|
@@ -107,7 +130,7 @@ export function buildServiceSpec(input) {
|
|
|
107
130
|
descriptorPath: resolve(userHome, ".config", "systemd", "user", id),
|
|
108
131
|
daemonCommand,
|
|
109
132
|
managerDomain: null,
|
|
110
|
-
descriptor: `[Unit]\nDescription=NowCrew daemon (${profile})\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\
|
|
133
|
+
descriptor: `[Unit]\nDescription=NowCrew daemon (${profile})\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\n${environmentBlock}ExecStart=${daemonCommand.map(systemdArg).join(" ")}\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=default.target\n`,
|
|
111
134
|
};
|
|
112
135
|
}
|
|
113
136
|
const id = `NowCrew Daemon (${profile}${identitySuffix})`;
|
|
@@ -4,6 +4,13 @@ import { posix, win32 } from "node:path";
|
|
|
4
4
|
const PROFILE = /^[a-z0-9][a-z0-9_-]{0,47}$/u;
|
|
5
5
|
const CONTROL = /[\0\r\n]/u;
|
|
6
6
|
const pathsFor = (platform) => platform === "win32" ? win32 : posix;
|
|
7
|
+
function samePath(left, right, platform) {
|
|
8
|
+
const normalizedLeft = pathsFor(platform).resolve(left);
|
|
9
|
+
const normalizedRight = pathsFor(platform).resolve(right);
|
|
10
|
+
return platform === "win32"
|
|
11
|
+
? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
|
|
12
|
+
: normalizedLeft === normalizedRight;
|
|
13
|
+
}
|
|
7
14
|
export function parseDaemonLocalHomeMode(value) {
|
|
8
15
|
if (value === undefined || value.trim() === "")
|
|
9
16
|
return "auto";
|
|
@@ -86,6 +93,22 @@ export function resolveDaemonLocalStateRoot(input) {
|
|
|
86
93
|
return legacyRoot;
|
|
87
94
|
if (input.explicit !== undefined || mode === "nowwork")
|
|
88
95
|
return nowworkRoot;
|
|
96
|
+
// Managed service descriptors created before local-home was persisted carry
|
|
97
|
+
// only --daemon-home. Do not treat every --daemon-home as legacy: migrated
|
|
98
|
+
// services also use that argument, but point it at ~/.nowwork/config.
|
|
99
|
+
// Prefer the descriptor's exact home when it identifies either layout.
|
|
100
|
+
if (input.daemonHome !== undefined && input.profile !== undefined) {
|
|
101
|
+
const nowworkDaemonHome = resolution("nowwork", nowworkRoot, input.profile, input.platform, "auto_nowwork_existing").daemonHome;
|
|
102
|
+
const legacyDaemonHome = resolution("legacy", legacyRoot, input.profile, input.platform, "auto_legacy_existing").daemonHome;
|
|
103
|
+
if (samePath(input.daemonHome, nowworkDaemonHome, input.platform))
|
|
104
|
+
return nowworkRoot;
|
|
105
|
+
if (samePath(input.daemonHome, legacyDaemonHome, input.platform))
|
|
106
|
+
return legacyRoot;
|
|
107
|
+
// An arbitrary explicit daemon home has historically implied the legacy
|
|
108
|
+
// local state root. Preserve that behavior unless the descriptor declares
|
|
109
|
+
// a mode explicitly.
|
|
110
|
+
return legacyRoot;
|
|
111
|
+
}
|
|
89
112
|
const exists = input.exists ?? existsSync;
|
|
90
113
|
// Once the new root exists, shared state must follow it even while the
|
|
91
114
|
// legacy root is retained as a rollback source.
|
|
@@ -133,9 +133,9 @@ export function createDaemonMigrationController(input) {
|
|
|
133
133
|
requestedProfile: message.targetProfile,
|
|
134
134
|
userHome,
|
|
135
135
|
platform,
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
requestedLocalHomeMode: "
|
|
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
139
|
});
|
|
140
140
|
if (!isAbsolute(message.targetDaemonHome) || !isAbsolute(message.targetAgentsRoot)
|
|
141
141
|
|| resolve(message.targetDaemonHome) !== resolve(target.daemonHome)
|
|
@@ -178,6 +178,7 @@ export function createDaemonMigrationController(input) {
|
|
|
178
178
|
serverUrl: input.serverUrl,
|
|
179
179
|
machineToken: input.machineToken,
|
|
180
180
|
requestedProfile: message.targetProfile,
|
|
181
|
+
requestedLocalHomeMode: "nowwork",
|
|
181
182
|
...(currentRegistryUrl === undefined ? {} : { requestedRegistryUrl: currentRegistryUrl }),
|
|
182
183
|
});
|
|
183
184
|
if (result.profile !== message.targetProfile || result.serviceId.length === 0) {
|
|
@@ -4,7 +4,7 @@ import { realpathSync } from "node:fs";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { daemonHome, loadProfile, resolveAgentsRoot } from "./computer-profile.js";
|
|
6
6
|
import { builtDaemonEntry } from "./computer-cli.js";
|
|
7
|
-
import { buildServiceSpec, readServiceDescriptor, serviceStatus, } from "./computer-service.js";
|
|
7
|
+
import { buildServiceSpec, readServiceDescriptor, storageServiceEnvironment, serviceStatus, } from "./computer-service.js";
|
|
8
8
|
import { daemonGlobalInstallation } from "./daemon-installation.js";
|
|
9
9
|
import { daemonInstallationLeaseHeld } from "./daemon-installation-lease.js";
|
|
10
10
|
import { inspectLegacyServiceInstallation } from "./legacy-service-installation.js";
|
|
@@ -21,9 +21,9 @@ export async function managedDaemonCapabilities(eligibility) {
|
|
|
21
21
|
return [];
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
function defaults(profileName) {
|
|
24
|
+
function defaults(profileName, selectedLocalHome) {
|
|
25
25
|
const userHome = homedir();
|
|
26
|
-
const localHome = resolveDaemonLocalStateRoot({
|
|
26
|
+
const localHome = selectedLocalHome ?? resolveDaemonLocalStateRoot({
|
|
27
27
|
userHome,
|
|
28
28
|
platform: process.platform,
|
|
29
29
|
explicit: process.env.NOWWORK_LOCAL_HOME,
|
|
@@ -31,6 +31,7 @@ function defaults(profileName) {
|
|
|
31
31
|
});
|
|
32
32
|
return {
|
|
33
33
|
platform: process.platform,
|
|
34
|
+
localHome,
|
|
34
35
|
profileHome: daemonHome(process.env, profileName),
|
|
35
36
|
userHome,
|
|
36
37
|
uid: process.getuid?.(),
|
|
@@ -65,7 +66,7 @@ async function registryCoversUpdateScope(services, descriptorPaths, targetDescri
|
|
|
65
66
|
return true;
|
|
66
67
|
}
|
|
67
68
|
export async function detectDaemonUpdateEligibility(profileName, overrides = {}) {
|
|
68
|
-
const deps = { ...defaults(profileName), ...overrides };
|
|
69
|
+
const deps = { ...defaults(profileName, overrides.localHome), ...overrides };
|
|
69
70
|
if (deps.platform !== "darwin" && deps.platform !== "linux" && deps.platform !== "win32") {
|
|
70
71
|
return { eligible: false, reason: "unsupported_platform" };
|
|
71
72
|
}
|
|
@@ -92,6 +93,7 @@ export async function detectDaemonUpdateEligibility(profileName, overrides = {})
|
|
|
92
93
|
nodePath: deps.nodePath,
|
|
93
94
|
entryPath: deps.entryPath,
|
|
94
95
|
profileHome: deps.profileHome,
|
|
96
|
+
environment: storageServiceEnvironment(process.env),
|
|
95
97
|
});
|
|
96
98
|
if (deps.platform === "win32") {
|
|
97
99
|
const scope = await deps.inspectWindowsServiceUpdateScope(spec, installation.npmPrefix)
|
|
@@ -169,6 +171,7 @@ export async function detectDaemonUpdateEligibility(profileName, overrides = {})
|
|
|
169
171
|
entryPath: deps.entryPath,
|
|
170
172
|
profileHome: deps.profileHome,
|
|
171
173
|
legacyServiceId: true,
|
|
174
|
+
environment: storageServiceEnvironment(process.env),
|
|
172
175
|
});
|
|
173
176
|
const legacyDescriptor = await deps.readServiceDescriptor(legacySpec).catch(() => null);
|
|
174
177
|
if (legacyDescriptor !== legacySpec.descriptor) {
|
package/dist/local-executor.js
CHANGED
|
@@ -9,7 +9,7 @@ import { DEEPSEEK_CODEX_MODEL, DEEPSEEK_CODEX_REASONING_LEVELS, materializeDeepS
|
|
|
9
9
|
import { materializeDefaultCodexHome } from "./runtimes/codex-home.js";
|
|
10
10
|
import { materializePrivateCodexHome } from "./codex-home-launch.js";
|
|
11
11
|
import { spawnKimi, KIMI_EFFORT_LEVELS } from "./runtimes/kimi.js";
|
|
12
|
-
import { applyDeepSeekHarnessMachineEnv, applyProviderEnv, providerFingerprint,
|
|
12
|
+
import { applyDeepSeekHarnessMachineEnv, applyProviderEnv, providerFingerprint, } from "./provider-env.js";
|
|
13
13
|
import { augmentedPath } from "./runtime-path.js";
|
|
14
14
|
import { extractFinalText, extractRunMeta, normalizeEvent, parseLine, } from "./normalize.js";
|
|
15
15
|
import { readSession, writeSession, pickResumeId, isNearBudget } from "./session.js";
|
|
@@ -33,6 +33,8 @@ import { diffMemoryPruneNotes, evaluateMemoryPrunePostcondition, inspectMemoryPr
|
|
|
33
33
|
import { createLocalMemoryTelemetry, logLocalMemoryContextPrepareFailure, logLocalMemoryDiagnosticsFailure, } from "./local-memory-telemetry.js";
|
|
34
34
|
import { CodexStartupStageParser } from "./codex-startup-stage.js";
|
|
35
35
|
import { formatRuntimeStartupError } from "./runtime-startup-error.js";
|
|
36
|
+
import { machinePackageManagerEnv, packageManagerEnvFacts } from "./package-manager-env.js";
|
|
37
|
+
import { sanitizeEnvVars } from "./runtime-env.js";
|
|
36
38
|
import { commitLocalMemoryResultAfterExit, initializeLocalMemoryV2ForWorkspace, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, renderLocalMemoryResultInstructions, } from "./local-memory/runtime.js";
|
|
37
39
|
export class RuntimeRunningCallbackTimeoutError extends Error {
|
|
38
40
|
constructor(runtime, timeoutMs) {
|
|
@@ -50,15 +52,6 @@ const MEMORY_PRUNE_DIAGNOSTICS_TIMEOUT_MS = 2_000;
|
|
|
50
52
|
const LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS = 250;
|
|
51
53
|
const CLAUDE_INSTRUCTION_WARNING = "claude_additional_directory_instructions_unverified";
|
|
52
54
|
const CODEX_COLLABORATION_EXEC_WARNING = "codex_collaboration_disabled_on_exec";
|
|
53
|
-
const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
54
|
-
const RESERVED_ENV = new Set([
|
|
55
|
-
"PATH",
|
|
56
|
-
"XDG_CONFIG_HOME",
|
|
57
|
-
"XDG_DATA_HOME",
|
|
58
|
-
"XDG_CACHE_HOME",
|
|
59
|
-
"GH_CONFIG_DIR",
|
|
60
|
-
"CLOUDSDK_CONFIG",
|
|
61
|
-
]);
|
|
62
55
|
function withoutClaudeInstructionFlag(env) {
|
|
63
56
|
if (!(CLAUDE_ADDITIONAL_DIRECTORY_INSTRUCTIONS_ENV in env))
|
|
64
57
|
return env;
|
|
@@ -470,6 +463,9 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
470
463
|
const baseEnv = {
|
|
471
464
|
...inheritedEnv,
|
|
472
465
|
...sanitizeEnvVars(providerConfig.envVars),
|
|
466
|
+
...(input.trainingProjectContext === undefined
|
|
467
|
+
? {}
|
|
468
|
+
: machinePackageManagerEnv(inheritedEnv)),
|
|
473
469
|
...input.launch.systemEnv,
|
|
474
470
|
PATH: `${workspace.crewDir}${delimiter}${augmentedPath()}`,
|
|
475
471
|
CREW_SERVER_URL: input.launch.serverUrl,
|
|
@@ -512,6 +508,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
512
508
|
qa_runtime_root: childEnv.QA_RUNTIME_ROOT ?? null,
|
|
513
509
|
qa_runtime_root_present: childEnv.QA_RUNTIME_ROOT !== undefined,
|
|
514
510
|
inherited_qa_runtime_root_present: inheritedEnv.QA_RUNTIME_ROOT !== undefined,
|
|
511
|
+
...packageManagerEnvFacts(childEnv),
|
|
515
512
|
training_root: join(crewHome, "training"),
|
|
516
513
|
project_context_present: input.projectContext !== undefined,
|
|
517
514
|
project_ids: JSON.stringify(input.projectContext === undefined
|
package/dist/main.js
CHANGED
|
@@ -31,6 +31,7 @@ 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
33
|
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
34
|
+
import { storageServiceEnvironment } from "./computer-service.js";
|
|
34
35
|
async function main() {
|
|
35
36
|
const workerResult = await runStorageWorkerCommand(process.argv.slice(2));
|
|
36
37
|
if (workerResult !== null) {
|
|
@@ -78,9 +79,9 @@ async function main() {
|
|
|
78
79
|
userHome: homedir(),
|
|
79
80
|
platform: process.platform,
|
|
80
81
|
explicit: process.env.NOWWORK_LOCAL_HOME,
|
|
81
|
-
mode: process.env.NOWWORK_LOCAL_HOME_MODE
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
mode: process.env.NOWWORK_LOCAL_HOME_MODE ?? process.env.NOWWORK_STORAGE_MODE,
|
|
83
|
+
...(values.profile === undefined ? {} : { profile: values.profile }),
|
|
84
|
+
...(values["daemon-home"] === undefined ? {} : { daemonHome: values["daemon-home"] }),
|
|
84
85
|
});
|
|
85
86
|
if (values.profile) {
|
|
86
87
|
const home = daemonHome(process.env, values.profile);
|
|
@@ -98,15 +99,7 @@ async function main() {
|
|
|
98
99
|
agentsRoot: resolveAgentsRoot(profile.agentsRoot, homedir(), process.platform),
|
|
99
100
|
nodePath: process.execPath,
|
|
100
101
|
entryPath: builtDaemonEntry(),
|
|
101
|
-
environment:
|
|
102
|
-
...(process.env.NOWWORK_HOME === undefined ? {} : { NOWWORK_HOME: process.env.NOWWORK_HOME }),
|
|
103
|
-
...(process.env.NOWWORK_ENV_TAG === undefined ? {} : { NOWWORK_ENV_TAG: process.env.NOWWORK_ENV_TAG }),
|
|
104
|
-
...(process.env.NOWWORK_STORAGE_POLICY_PATH === undefined
|
|
105
|
-
? {} : { NOWWORK_STORAGE_POLICY_PATH: process.env.NOWWORK_STORAGE_POLICY_PATH }),
|
|
106
|
-
...(process.env.NOWWORK_LOCAL_HOME === undefined ? {} : { NOWWORK_LOCAL_HOME: process.env.NOWWORK_LOCAL_HOME }),
|
|
107
|
-
...(process.env.NOWWORK_LOCAL_HOME_MODE === undefined && process.env.NOWWORK_STORAGE_MODE === undefined
|
|
108
|
-
? {} : { NOWWORK_LOCAL_HOME_MODE: process.env.NOWWORK_LOCAL_HOME_MODE ?? process.env.NOWWORK_STORAGE_MODE }),
|
|
109
|
-
},
|
|
102
|
+
environment: storageServiceEnvironment(process.env),
|
|
110
103
|
});
|
|
111
104
|
}
|
|
112
105
|
applyProfileToEnv(profile, process.env);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { acquireDaemonInstallationLease } from "./daemon-installation-lease.js";
|
|
2
|
+
import { acquireDaemonInstallationLease, } from "./daemon-installation-lease.js";
|
|
3
3
|
import { installService, ensureServiceEnabled, readServiceDescriptor, serviceAction, replaceInstalledServiceDescriptor, uninstallService, } from "./computer-service.js";
|
|
4
4
|
import { ManagedServiceConflictError, ManagedServiceRegistryError, assertRegistryCoversDescriptors, listManagedServiceDescriptorPaths, managedServiceIdentityMatches, managedServiceConflict, normalizeManagedServiceRecord, serviceDescriptorSha256, withManagedServiceRegistryLock, } from "./managed-service-registry.js";
|
|
5
5
|
function requirePortableManagedSpec(spec) {
|
|
@@ -43,7 +43,7 @@ function sameManagedResource(left, right) {
|
|
|
43
43
|
function descriptorDiffIsStorageEnvironmentOnly(previous, next) {
|
|
44
44
|
if (!next.includes("\n <key>EnvironmentVariables</key>\n"))
|
|
45
45
|
return false;
|
|
46
|
-
const strip = (descriptor) => descriptor.replace(/\n <key>EnvironmentVariables<\/key>\n <dict>\n(?: <key>(?:NOWWORK_HOME|NOWWORK_ENV_TAG|NOWWORK_STORAGE_POLICY_PATH)<\/key><string>[^<]*<\/string>\n)+ <\/dict>\n/u, "\n");
|
|
46
|
+
const strip = (descriptor) => descriptor.replace(/\n <key>EnvironmentVariables<\/key>\n <dict>\n(?: <key>(?:NOWWORK_HOME|NOWWORK_ENV_TAG|NOWWORK_STORAGE_POLICY_PATH|NOWWORK_LOCAL_HOME|NOWWORK_LOCAL_HOME_MODE)<\/key><string>[^<]*<\/string>\n)+ <\/dict>\n/u, "\n");
|
|
47
47
|
return strip(previous) === strip(next);
|
|
48
48
|
}
|
|
49
49
|
function assertRecordDescribesSpec(input) {
|
|
@@ -90,6 +90,7 @@ export async function installManagedService(input) {
|
|
|
90
90
|
}
|
|
91
91
|
const lease = await acquire(input.record.npmPrefix, {
|
|
92
92
|
...(input.registry?.userHome === undefined ? {} : { userHome: input.registry.userHome }),
|
|
93
|
+
...(input.registry?.localHome === undefined ? {} : { localHome: input.registry.localHome }),
|
|
93
94
|
});
|
|
94
95
|
try {
|
|
95
96
|
await installService(input.spec, input.runner);
|
|
@@ -136,6 +137,7 @@ export async function installManagedService(input) {
|
|
|
136
137
|
}
|
|
137
138
|
const lease = await acquire(input.record.npmPrefix, {
|
|
138
139
|
...(input.registry?.userHome === undefined ? {} : { userHome: input.registry.userHome }),
|
|
140
|
+
...(input.registry?.localHome === undefined ? {} : { localHome: input.registry.localHome }),
|
|
139
141
|
});
|
|
140
142
|
try {
|
|
141
143
|
const descriptor = await readServiceDescriptor(input.spec);
|
|
@@ -170,6 +172,7 @@ export async function uninstallUnregisteredManagedService(input) {
|
|
|
170
172
|
const acquire = input.acquireInstallationLease ?? acquireDaemonInstallationLease;
|
|
171
173
|
const lease = await acquire(input.npmPrefix, {
|
|
172
174
|
...(input.registry?.userHome === undefined ? {} : { userHome: input.registry.userHome }),
|
|
175
|
+
...(input.registry?.localHome === undefined ? {} : { localHome: input.registry.localHome }),
|
|
173
176
|
});
|
|
174
177
|
try {
|
|
175
178
|
await uninstallService(input.spec, input.runner);
|
|
@@ -199,6 +202,7 @@ export async function uninstallManagedService(input) {
|
|
|
199
202
|
}
|
|
200
203
|
const lease = await acquire(input.record.npmPrefix, {
|
|
201
204
|
...(input.registry?.userHome === undefined ? {} : { userHome: input.registry.userHome }),
|
|
205
|
+
...(input.registry?.localHome === undefined ? {} : { localHome: input.registry.localHome }),
|
|
202
206
|
});
|
|
203
207
|
try {
|
|
204
208
|
if (descriptor !== null)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
function nonEmpty(value) {
|
|
3
|
+
const normalized = value?.trim();
|
|
4
|
+
return normalized === undefined || normalized === "" ? undefined : normalized;
|
|
5
|
+
}
|
|
6
|
+
function registryUrl(value) {
|
|
7
|
+
const normalized = nonEmpty(value);
|
|
8
|
+
if (normalized === undefined)
|
|
9
|
+
return undefined;
|
|
10
|
+
try {
|
|
11
|
+
const protocol = new URL(normalized).protocol;
|
|
12
|
+
return protocol === "http:" || protocol === "https:" ? normalized : undefined;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function configuredPath(env) {
|
|
19
|
+
const explicit = env.CREW_PACKAGE_MANAGER_USERCONFIG?.trim();
|
|
20
|
+
if (explicit !== undefined && explicit !== "")
|
|
21
|
+
return explicit;
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
function registryFromNpmrc(path) {
|
|
25
|
+
if (path === undefined)
|
|
26
|
+
return undefined;
|
|
27
|
+
try {
|
|
28
|
+
const line = readFileSync(path, "utf8")
|
|
29
|
+
.split(/\r?\n/u)
|
|
30
|
+
.find((entry) => /^\s*registry\s*=/u.test(entry));
|
|
31
|
+
return registryUrl(line?.replace(/^\s*registry\s*=\s*/u, ""));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function npmrcHasAuth(path) {
|
|
38
|
+
if (path === undefined)
|
|
39
|
+
return false;
|
|
40
|
+
try {
|
|
41
|
+
return /^\s*(?:(?:\/\/[^=\s]+:)?_(?:auth|authToken|password))\s*=/imu.test(readFileSync(path, "utf8"));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Keep machine-owned npm credentials available for explicitly authorized
|
|
49
|
+
* training dependency installs. The daemon must opt in with
|
|
50
|
+
* CREW_PACKAGE_MANAGER_USERCONFIG; Agent env cannot reach this path.
|
|
51
|
+
*/
|
|
52
|
+
export function machinePackageManagerEnv(env = process.env) {
|
|
53
|
+
const npmUserConfig = configuredPath(env);
|
|
54
|
+
const yarnRegistry = npmUserConfig === undefined
|
|
55
|
+
? undefined
|
|
56
|
+
: registryUrl(env.YARN_REGISTRY) ?? registryFromNpmrc(npmUserConfig);
|
|
57
|
+
const entries = npmUserConfig === undefined
|
|
58
|
+
? []
|
|
59
|
+
: [["NPM_CONFIG_USERCONFIG", npmUserConfig]];
|
|
60
|
+
const alwaysAuth = npmrcHasAuth(npmUserConfig) ? "true" : undefined;
|
|
61
|
+
return Object.fromEntries(yarnRegistry === undefined && alwaysAuth === undefined
|
|
62
|
+
? entries
|
|
63
|
+
: [
|
|
64
|
+
...entries,
|
|
65
|
+
...(yarnRegistry === undefined ? [] : [["YARN_REGISTRY", yarnRegistry]]),
|
|
66
|
+
...(alwaysAuth === undefined ? [] : [["NPM_CONFIG_ALWAYS_AUTH", alwaysAuth]]),
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
export function packageManagerEnvFacts(env) {
|
|
70
|
+
return {
|
|
71
|
+
npm_userconfig_present: Boolean(env.NPM_CONFIG_USERCONFIG?.trim()),
|
|
72
|
+
yarn_registry_present: Boolean(env.YARN_REGISTRY?.trim()),
|
|
73
|
+
npm_always_auth_present: Boolean(env.NPM_CONFIG_ALWAYS_AUTH?.trim()),
|
|
74
|
+
};
|
|
75
|
+
}
|
package/dist/runner.js
CHANGED
|
@@ -12,7 +12,7 @@ import { readBoundImDecisionFile, resetBoundImDecisionFile } from "./bound-im-de
|
|
|
12
12
|
import { launchSupervisedRuntime } from "./supervised-runtime.js";
|
|
13
13
|
import { awaitWithCancellation, } from "./runtime-cancellation.js";
|
|
14
14
|
export { awaitExit, exitActivity } from "./local-executor.js";
|
|
15
|
-
export { sanitizeEnvVars } from "./
|
|
15
|
+
export { sanitizeEnvVars } from "./runtime-env.js";
|
|
16
16
|
const ICON = {
|
|
17
17
|
init: "🟢", text: "💬", reading: "📖", sending: "📨", checking: "🔎",
|
|
18
18
|
claiming: "📌", crew: "⚙️", tool: "🛠️", tool_result: "↩️", done: "✅", error: "❌",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2
|
+
export const RESERVED_ENV = new Set([
|
|
3
|
+
"PATH",
|
|
4
|
+
"XDG_CONFIG_HOME",
|
|
5
|
+
"XDG_DATA_HOME",
|
|
6
|
+
"XDG_CACHE_HOME",
|
|
7
|
+
"GH_CONFIG_DIR",
|
|
8
|
+
"CLOUDSDK_CONFIG",
|
|
9
|
+
"NPM_CONFIG_USERCONFIG",
|
|
10
|
+
"YARN_REGISTRY",
|
|
11
|
+
"NPM_CONFIG_ALWAYS_AUTH",
|
|
12
|
+
]);
|
|
13
|
+
/** 用户自定义环境只能补充普通变量,不能覆盖 daemon 维护的运行时契约。 */
|
|
14
|
+
export function sanitizeEnvVars(raw) {
|
|
15
|
+
if (!raw)
|
|
16
|
+
return {};
|
|
17
|
+
return Object.fromEntries(Object.entries(raw).filter(([key, value]) => typeof value === "string"
|
|
18
|
+
&& ENV_KEY_RE.test(key)
|
|
19
|
+
&& !key.toUpperCase().startsWith("CREW_")
|
|
20
|
+
&& !RESERVED_ENV.has(key.toUpperCase())));
|
|
21
|
+
}
|
package/dist/serve.js
CHANGED
|
@@ -106,7 +106,7 @@ export function serve(config, opts = {}) {
|
|
|
106
106
|
const cancellations = new Map();
|
|
107
107
|
const legacyRuns = new Map();
|
|
108
108
|
const updateEligibility = opts.update?.eligibility
|
|
109
|
-
?? (() => detectDaemonUpdateEligibility(opts.profileName));
|
|
109
|
+
?? (() => detectDaemonUpdateEligibility(opts.profileName, { localHome }));
|
|
110
110
|
const updateController = createDaemonUpdateController({
|
|
111
111
|
eligibility: updateEligibility,
|
|
112
112
|
install: opts.update?.install ?? ((input) => installExactDaemonUpdate({
|
package/dist/storage-config.js
CHANGED
|
@@ -3,6 +3,7 @@ import { posix, win32 } from "node:path";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { atomicPrivateWrite } from "./atomic-private-write.js";
|
|
5
5
|
import { resolveEnvTag, resolveNowworkHome, storageLayout, } from "./storage-layout.js";
|
|
6
|
+
import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
|
|
6
7
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
7
8
|
const PROFILE = /^[a-z0-9][a-z0-9_-]{0,47}$/u;
|
|
8
9
|
const PORTABLE_ABSOLUTE_PATH = z.string().min(1).max(32768).refine((value) => posix.isAbsolute(value) || win32.isAbsolute(value), "must be an absolute path");
|
|
@@ -46,9 +47,34 @@ function absoluteBase(value, fallback, platform, name) {
|
|
|
46
47
|
}
|
|
47
48
|
return paths.normalize(selected);
|
|
48
49
|
}
|
|
50
|
+
function localHomeRoot(input) {
|
|
51
|
+
const environment = input.environment;
|
|
52
|
+
if (environment.NOWWORK_LOCAL_HOME === undefined
|
|
53
|
+
&& environment.NOWWORK_LOCAL_HOME_MODE === undefined
|
|
54
|
+
&& environment.NOWWORK_STORAGE_MODE === undefined) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return resolveDaemonLocalStateRootFromEnv(input);
|
|
58
|
+
}
|
|
59
|
+
function rootsUnderLocalHome(root, platform) {
|
|
60
|
+
const paths = pathsFor(platform);
|
|
61
|
+
const cacheRoot = paths.join(root, "cache");
|
|
62
|
+
const stateRoot = paths.join(root, "state");
|
|
63
|
+
return Object.freeze({
|
|
64
|
+
configRoot: paths.join(root, "config"),
|
|
65
|
+
runtimeBase: paths.join(root, "runtime"),
|
|
66
|
+
cacheRoot,
|
|
67
|
+
stateRoot,
|
|
68
|
+
backupsRoot: paths.join(stateRoot, "backups"),
|
|
69
|
+
runtimePackagesRoot: paths.join(cacheRoot, "runtime-packages"),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
49
72
|
export function resolveLocalStorageRoots(input) {
|
|
50
73
|
const paths = pathsFor(input.platform);
|
|
51
74
|
const userHome = absoluteBase(input.userHome, input.userHome, input.platform, "user home");
|
|
75
|
+
const configuredLocalHome = localHomeRoot({ ...input, userHome });
|
|
76
|
+
if (configuredLocalHome !== null)
|
|
77
|
+
return rootsUnderLocalHome(configuredLocalHome, input.platform);
|
|
52
78
|
if (input.platform === "win32") {
|
|
53
79
|
const local = absoluteBase(input.environment.LOCALAPPDATA, paths.join(userHome, "AppData", "Local"), input.platform, "LOCALAPPDATA");
|
|
54
80
|
const root = paths.join(local, "NowWork");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { realpathSync } from "node:fs";
|
|
2
|
+
import { lstat } from "node:fs/promises";
|
|
2
3
|
import { isAbsolute, resolve } from "node:path";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { buildServiceSpec } from "./computer-service.js";
|
|
@@ -10,7 +11,7 @@ import { readStorageMigrationPolicy } from "./storage-migration-policy.js";
|
|
|
10
11
|
import { createStorageMigrationRuntimeController, } from "./storage-runtime-controller.js";
|
|
11
12
|
import { resolveEnvTag, resolveNowworkHome } from "./storage-layout.js";
|
|
12
13
|
import { readStorageOwnershipClaim } from "./storage-ownership.js";
|
|
13
|
-
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
14
|
+
import { parseDaemonLocalHomeMode, resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
14
15
|
const notReady = (reason) => Object.freeze({ ready: false, reason });
|
|
15
16
|
function canonicalPath(value) {
|
|
16
17
|
const absolute = resolve(value);
|
|
@@ -75,15 +76,33 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
75
76
|
serverUrl: z.string().url().parse(rawInput.serverUrl),
|
|
76
77
|
platform: rawInput.platform,
|
|
77
78
|
};
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
explicit: rawInput.environment.NOWWORK_LOCAL_HOME,
|
|
82
|
-
mode: rawInput.environment.NOWWORK_LOCAL_HOME_MODE ?? rawInput.environment.NOWWORK_STORAGE_MODE,
|
|
83
|
-
});
|
|
79
|
+
let sourceLocalHome;
|
|
80
|
+
let targetLocalHome;
|
|
81
|
+
let targetEnvironment;
|
|
84
82
|
let nowworkHome;
|
|
85
83
|
let environment;
|
|
84
|
+
let local;
|
|
86
85
|
try {
|
|
86
|
+
sourceLocalHome = resolveDaemonLocalStateRoot({
|
|
87
|
+
userHome: rawInput.userHome,
|
|
88
|
+
platform: rawInput.platform,
|
|
89
|
+
profile: input.profile,
|
|
90
|
+
daemonHome: input.currentDaemonHome,
|
|
91
|
+
});
|
|
92
|
+
const requestedLocalHomeMode = parseDaemonLocalHomeMode(rawInput.environment.NOWWORK_LOCAL_HOME_MODE ?? rawInput.environment.NOWWORK_STORAGE_MODE);
|
|
93
|
+
if (requestedLocalHomeMode === "legacy")
|
|
94
|
+
throw new Error("storage migration target must use the canonical local .nowwork root");
|
|
95
|
+
targetLocalHome = resolveDaemonLocalStateRoot({
|
|
96
|
+
userHome: rawInput.userHome,
|
|
97
|
+
platform: rawInput.platform,
|
|
98
|
+
explicit: rawInput.environment.NOWWORK_LOCAL_HOME ?? resolve(rawInput.userHome, ".nowwork"),
|
|
99
|
+
mode: "nowwork",
|
|
100
|
+
});
|
|
101
|
+
targetEnvironment = {
|
|
102
|
+
...input.environment,
|
|
103
|
+
NOWWORK_LOCAL_HOME: targetLocalHome,
|
|
104
|
+
NOWWORK_LOCAL_HOME_MODE: "nowwork",
|
|
105
|
+
};
|
|
87
106
|
const home = resolveNowworkHome({
|
|
88
107
|
environment: configuredNowworkHome,
|
|
89
108
|
userHome: input.userHome,
|
|
@@ -97,6 +116,11 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
97
116
|
? {}
|
|
98
117
|
: { environment: input.environment.NOWWORK_ENV_TAG }),
|
|
99
118
|
}).environment;
|
|
119
|
+
local = resolveLocalStorageRoots({
|
|
120
|
+
platform: input.platform,
|
|
121
|
+
userHome: input.userHome,
|
|
122
|
+
environment: targetEnvironment,
|
|
123
|
+
});
|
|
100
124
|
}
|
|
101
125
|
catch {
|
|
102
126
|
return notReady("storage_environment_invalid");
|
|
@@ -119,7 +143,7 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
119
143
|
};
|
|
120
144
|
let sourceRecord;
|
|
121
145
|
try {
|
|
122
|
-
const records = await dependencies.readManagedServices({ userHome: input.userHome, localHome });
|
|
146
|
+
const records = await dependencies.readManagedServices({ userHome: input.userHome, localHome: sourceLocalHome });
|
|
123
147
|
const found = findSourceRecord(records, input);
|
|
124
148
|
if (found === null)
|
|
125
149
|
return notReady("source_service_record_missing");
|
|
@@ -141,7 +165,7 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
141
165
|
const assertSourceIdentity = () => dependencies.assertManagedServiceIdentity({
|
|
142
166
|
spec: sourceSpec,
|
|
143
167
|
record: sourceRecord,
|
|
144
|
-
registry: { userHome: input.userHome, localHome },
|
|
168
|
+
registry: { userHome: input.userHome, localHome: sourceLocalHome },
|
|
145
169
|
});
|
|
146
170
|
try {
|
|
147
171
|
await assertSourceIdentity();
|
|
@@ -173,11 +197,18 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
173
197
|
}
|
|
174
198
|
if (ownership.environment !== environment)
|
|
175
199
|
return notReady("storage_ownership_claim_invalid");
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
200
|
+
const targetInstallationsPath = installationsPath(local.configRoot);
|
|
201
|
+
let targetInstallationsArtifact = null;
|
|
202
|
+
try {
|
|
203
|
+
const targetInstallations = await lstat(targetInstallationsPath);
|
|
204
|
+
if (!targetInstallations.isFile())
|
|
205
|
+
return notReady("target_installations_invalid");
|
|
206
|
+
targetInstallationsArtifact = { name: "installations.json", path: targetInstallationsPath };
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
if (error.code !== "ENOENT")
|
|
210
|
+
return notReady("target_installations_invalid");
|
|
211
|
+
}
|
|
181
212
|
const runtime = dependencies.createController({
|
|
182
213
|
profile: input.profile,
|
|
183
214
|
serverUrl: input.serverUrl,
|
|
@@ -194,12 +225,15 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
|
|
|
194
225
|
NOWWORK_HOME: nowworkHome,
|
|
195
226
|
NOWWORK_ENV_TAG: environment,
|
|
196
227
|
NOWWORK_STORAGE_POLICY_PATH: policyPath,
|
|
228
|
+
...(targetEnvironment.NOWWORK_LOCAL_HOME === undefined ? {} : { NOWWORK_LOCAL_HOME: targetEnvironment.NOWWORK_LOCAL_HOME }),
|
|
229
|
+
...(targetEnvironment.NOWWORK_LOCAL_HOME_MODE === undefined
|
|
230
|
+
? {} : { NOWWORK_LOCAL_HOME_MODE: targetEnvironment.NOWWORK_LOCAL_HOME_MODE }),
|
|
197
231
|
},
|
|
198
232
|
backupArtifacts: [
|
|
199
233
|
{ name: "profile.json", path: profilePath(input.profile, input.currentDaemonHome) },
|
|
200
234
|
{ name: "service-descriptor", path: sourceRecord.descriptorPath },
|
|
201
|
-
{ name: "managed-service-registry.json", path: managedServiceRegistryPath(input.userHome,
|
|
202
|
-
|
|
235
|
+
{ name: "managed-service-registry.json", path: managedServiceRegistryPath(input.userHome, sourceLocalHome) },
|
|
236
|
+
...(targetInstallationsArtifact === null ? [] : [targetInstallationsArtifact]),
|
|
203
237
|
],
|
|
204
238
|
}, { assertSourceIdentity });
|
|
205
239
|
return Object.freeze({ ready: true, runtime });
|
|
@@ -12,6 +12,7 @@ import { readStorageWorkerHandoff } from "./storage-worker-handoff.js";
|
|
|
12
12
|
import { readStorageWorkerOperation } from "./storage-worker-operation.js";
|
|
13
13
|
import { storageIdFingerprint } from "./storage-identity.js";
|
|
14
14
|
import { waitForStorageMigrationAcknowledgement } from "./storage-target-confirmation.js";
|
|
15
|
+
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
15
16
|
const defaultServiceDependencies = () => ({
|
|
16
17
|
assertManagedServiceIdentity,
|
|
17
18
|
serviceAction,
|
|
@@ -27,7 +28,7 @@ const defaultServiceDependencies = () => ({
|
|
|
27
28
|
readInstallations,
|
|
28
29
|
writeInstallations,
|
|
29
30
|
});
|
|
30
|
-
function serviceSpec(operation, record, profileHome) {
|
|
31
|
+
function serviceSpec(operation, record, profileHome, layout = "target") {
|
|
31
32
|
return buildServiceSpec({
|
|
32
33
|
platform: operation.service.platform,
|
|
33
34
|
profile: operation.profile,
|
|
@@ -36,7 +37,7 @@ function serviceSpec(operation, record, profileHome) {
|
|
|
36
37
|
nodePath: record.nodePath,
|
|
37
38
|
entryPath: record.entryPath,
|
|
38
39
|
profileHome,
|
|
39
|
-
...(operation.service.serviceEnvironment === undefined
|
|
40
|
+
...(layout === "source" || operation.service.serviceEnvironment === undefined
|
|
40
41
|
? {}
|
|
41
42
|
: { environment: operation.service.serviceEnvironment }),
|
|
42
43
|
});
|
|
@@ -74,10 +75,27 @@ function canonicalServerUrl(value) {
|
|
|
74
75
|
}
|
|
75
76
|
export function createStorageWorkerServiceLifecycle(operation, injected = {}) {
|
|
76
77
|
const dependencies = { ...defaultServiceDependencies(), ...injected };
|
|
77
|
-
const
|
|
78
|
+
const sourceLocalHome = resolveDaemonLocalStateRoot({
|
|
79
|
+
userHome: operation.service.userHome,
|
|
80
|
+
platform: operation.service.platform,
|
|
81
|
+
profile: operation.service.sourceRecord.profile,
|
|
82
|
+
daemonHome: operation.service.sourceRecord.daemonHome,
|
|
83
|
+
});
|
|
84
|
+
const targetLocalHome = resolveDaemonLocalStateRoot({
|
|
85
|
+
userHome: operation.service.userHome,
|
|
86
|
+
platform: operation.service.platform,
|
|
87
|
+
profile: operation.service.targetRecord.profile,
|
|
88
|
+
daemonHome: operation.service.targetRecord.daemonHome,
|
|
89
|
+
...(operation.service.serviceEnvironment?.NOWWORK_LOCAL_HOME === undefined
|
|
90
|
+
? {} : { explicit: operation.service.serviceEnvironment.NOWWORK_LOCAL_HOME }),
|
|
91
|
+
...(operation.service.serviceEnvironment?.NOWWORK_LOCAL_HOME_MODE === undefined
|
|
92
|
+
? {} : { mode: operation.service.serviceEnvironment.NOWWORK_LOCAL_HOME_MODE }),
|
|
93
|
+
});
|
|
94
|
+
const sourceRegistry = { userHome: operation.service.userHome, localHome: sourceLocalHome };
|
|
95
|
+
const targetRegistry = { userHome: operation.service.userHome, localHome: targetLocalHome };
|
|
78
96
|
const sourceRecord = operation.service.sourceRecord;
|
|
79
97
|
const targetRecord = operation.service.targetRecord;
|
|
80
|
-
const sourceSpec = serviceSpec(operation, sourceRecord, operation.migration.sourceRoots["daemon-home"]);
|
|
98
|
+
const sourceSpec = serviceSpec(operation, sourceRecord, operation.migration.sourceRoots["daemon-home"], "source");
|
|
81
99
|
const targetSpec = serviceSpec(operation, targetRecord, operation.installation.configRoot);
|
|
82
100
|
assertRecordMatchesSpec(sourceRecord, sourceSpec);
|
|
83
101
|
assertRecordMatchesSpec(targetRecord, targetSpec);
|
|
@@ -89,7 +107,7 @@ export function createStorageWorkerServiceLifecycle(operation, injected = {}) {
|
|
|
89
107
|
const assertSourceIdentity = () => dependencies.assertManagedServiceIdentity({
|
|
90
108
|
spec: sourceSpec,
|
|
91
109
|
record: sourceRecord,
|
|
92
|
-
registry,
|
|
110
|
+
registry: sourceRegistry,
|
|
93
111
|
});
|
|
94
112
|
const readTargetInstallations = () => dependencies.readInstallations({
|
|
95
113
|
configRoot: operation.installation.configRoot,
|
|
@@ -140,7 +158,7 @@ export function createStorageWorkerServiceLifecycle(operation, injected = {}) {
|
|
|
140
158
|
spec: sourceSpec,
|
|
141
159
|
record: sourceRecord,
|
|
142
160
|
runner: systemCommandRunner,
|
|
143
|
-
registry,
|
|
161
|
+
registry: sourceRegistry,
|
|
144
162
|
});
|
|
145
163
|
const { version: _version, ...profile } = sourceProfile;
|
|
146
164
|
savedTargetProfile = await dependencies.saveProfile({
|
|
@@ -154,7 +172,7 @@ export function createStorageWorkerServiceLifecycle(operation, injected = {}) {
|
|
|
154
172
|
spec: targetSpec,
|
|
155
173
|
record: targetRecord,
|
|
156
174
|
runner: systemCommandRunner,
|
|
157
|
-
registry,
|
|
175
|
+
registry: targetRegistry,
|
|
158
176
|
});
|
|
159
177
|
const target = await dependencies.captureExactService(targetSpec, systemCommandRunner);
|
|
160
178
|
if (!target.installed || !target.exact || target.running) {
|
|
@@ -169,7 +187,7 @@ export function createStorageWorkerServiceLifecycle(operation, injected = {}) {
|
|
|
169
187
|
}
|
|
170
188
|
};
|
|
171
189
|
const removeTargetService = async () => {
|
|
172
|
-
const records = await dependencies.readManagedServices(
|
|
190
|
+
const records = await dependencies.readManagedServices(targetRegistry);
|
|
173
191
|
const registered = records.find((record) => record.serviceId === targetRecord.serviceId
|
|
174
192
|
|| (record.profile === targetRecord.profile && record.daemonHome === targetRecord.daemonHome));
|
|
175
193
|
if (registered !== undefined && !sameRecord(registered, targetRecord)) {
|
|
@@ -180,7 +198,7 @@ export function createStorageWorkerServiceLifecycle(operation, injected = {}) {
|
|
|
180
198
|
spec: targetSpec,
|
|
181
199
|
record: targetRecord,
|
|
182
200
|
runner: systemCommandRunner,
|
|
183
|
-
registry,
|
|
201
|
+
registry: targetRegistry,
|
|
184
202
|
});
|
|
185
203
|
}
|
|
186
204
|
else {
|
|
@@ -193,7 +211,7 @@ export function createStorageWorkerServiceLifecycle(operation, injected = {}) {
|
|
|
193
211
|
daemonHome: targetRecord.daemonHome,
|
|
194
212
|
npmPrefix: targetRecord.npmPrefix,
|
|
195
213
|
runner: systemCommandRunner,
|
|
196
|
-
registry,
|
|
214
|
+
registry: targetRegistry,
|
|
197
215
|
});
|
|
198
216
|
}
|
|
199
217
|
}
|
|
@@ -231,7 +249,7 @@ export function createStorageWorkerServiceLifecycle(operation, injected = {}) {
|
|
|
231
249
|
spec: sourceSpec,
|
|
232
250
|
record: sourceRecord,
|
|
233
251
|
runner: systemCommandRunner,
|
|
234
|
-
registry,
|
|
252
|
+
registry: sourceRegistry,
|
|
235
253
|
});
|
|
236
254
|
}
|
|
237
255
|
const afterInstall = current.installed
|
|
@@ -7,6 +7,7 @@ import { atomicPrivateWrite } from "./atomic-private-write.js";
|
|
|
7
7
|
import { assertStorageControlMaterialTokenFree } from "./storage-credential-guard.js";
|
|
8
8
|
import { storageLayout } from "./storage-layout.js";
|
|
9
9
|
import { StorageMigrationWarningCodeSchema } from "./storage-migration-policy.js";
|
|
10
|
+
import { parseDaemonLocalHomeMode } from "./daemon-local-home.js";
|
|
10
11
|
const CONTROL = /[\0\r\n]/u;
|
|
11
12
|
const PROFILE = /^[a-z0-9][a-z0-9_-]{0,47}$/u;
|
|
12
13
|
const ENVIRONMENT = /^[a-z0-9][a-z0-9_-]{0,31}$/u;
|
|
@@ -95,6 +96,8 @@ const StorageServiceEnvironmentSchema = z.object({
|
|
|
95
96
|
NOWWORK_HOME: AbsolutePathSchema.optional(),
|
|
96
97
|
NOWWORK_ENV_TAG: z.string().regex(ENVIRONMENT).optional(),
|
|
97
98
|
NOWWORK_STORAGE_POLICY_PATH: AbsolutePathSchema.optional(),
|
|
99
|
+
NOWWORK_LOCAL_HOME: AbsolutePathSchema.optional(),
|
|
100
|
+
NOWWORK_LOCAL_HOME_MODE: z.string().min(1).max(32).optional(),
|
|
98
101
|
}).strict();
|
|
99
102
|
const OperationSchema = z.object({
|
|
100
103
|
version: z.literal(1),
|
|
@@ -182,6 +185,9 @@ function isInside(root, candidate, platform) {
|
|
|
182
185
|
return child === "" || (!child.startsWith("..") && !paths.isAbsolute(child));
|
|
183
186
|
}
|
|
184
187
|
function assertSemanticIdentity(operation) {
|
|
188
|
+
const localHomeMode = operation.service.serviceEnvironment?.NOWWORK_LOCAL_HOME_MODE;
|
|
189
|
+
if (localHomeMode !== undefined)
|
|
190
|
+
parseDaemonLocalHomeMode(localHomeMode);
|
|
185
191
|
const identity = [operation.doctorInput, operation.installation.target];
|
|
186
192
|
if (identity.some((value) => value.workspaceId !== operation.workspaceId
|
|
187
193
|
|| value.environment !== operation.environment || value.profile !== operation.profile)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nowcrew/daemon",
|
|
3
|
-
"version": "0.6.80-beta.
|
|
3
|
+
"version": "0.6.80-beta.4",
|
|
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
|
+
}
|