@nowcrew/daemon 0.6.80-beta.3 → 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-service.js +12 -1
- package/dist/daemon-migration-controller.js +4 -3
- 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 +1 -1
- package/dist/project-skills/capability.js +0 -109
- package/dist/project-skills/controller-convergence.js +0 -57
- package/dist/project-skills/projection-set-switch.js +0 -419
- package/dist/project-skills/projection-state-domain.js +0 -170
- package/dist/project-skills/projection-state-store.js +0 -842
- package/dist/project-skills/projection-state-transaction.js +0 -322
- package/dist/project-skills/projection-state.js +0 -3
- package/dist/project-skills/runtime-root-bootstrap.js +0 -47
- package/dist/project-skills/runtime-root-domain.js +0 -268
- package/dist/project-skills/runtime-root-gc.js +0 -293
- package/dist/project-skills/runtime-root-lease-artifact.js +0 -46
- package/dist/project-skills/runtime-root-leases.js +0 -487
- package/dist/project-skills/runtime-root-source-identity.js +0 -60
- package/dist/project-skills/runtime-root-startup.js +0 -49
- package/dist/project-skills/runtime-root-state-artifact-domain.js +0 -143
- package/dist/project-skills/runtime-root-state-index.js +0 -356
- package/dist/project-skills/runtime-root-store.js +0 -722
- package/dist/project-skills/serve-capability.js +0 -28
- package/dist/project-skills/serve-startup.js +0 -37
|
@@ -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-service.js
CHANGED
|
@@ -62,6 +62,9 @@ function xml(value) {
|
|
|
62
62
|
function systemdArg(value) {
|
|
63
63
|
return `"${assertSingleLine(value, "service argument").replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%")}"`;
|
|
64
64
|
}
|
|
65
|
+
function systemdEnvironmentEntry(key, value) {
|
|
66
|
+
return `Environment=${systemdArg(`${key}=${value}`)}`;
|
|
67
|
+
}
|
|
65
68
|
export function windowsCommandArg(value) {
|
|
66
69
|
const input = assertSingleLine(value, "service argument");
|
|
67
70
|
return `"${input.replace(/(\\*)"/g, "$1$1\\\"").replace(/(\\*)$/g, "$1$1")}"`;
|
|
@@ -112,6 +115,14 @@ export function buildServiceSpec(input) {
|
|
|
112
115
|
}
|
|
113
116
|
if (platform === "linux") {
|
|
114
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`;
|
|
115
126
|
return {
|
|
116
127
|
platform,
|
|
117
128
|
profile,
|
|
@@ -119,7 +130,7 @@ export function buildServiceSpec(input) {
|
|
|
119
130
|
descriptorPath: resolve(userHome, ".config", "systemd", "user", id),
|
|
120
131
|
daemonCommand,
|
|
121
132
|
managerDomain: null,
|
|
122
|
-
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`,
|
|
123
134
|
};
|
|
124
135
|
}
|
|
125
136
|
const id = `NowCrew Daemon (${profile}${identitySuffix})`;
|
|
@@ -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) {
|
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,109 +0,0 @@
|
|
|
1
|
-
import { PROJECT_SKILLS_CAPABILITY, PROJECT_SKILL_PROJECTION_V2_CAPABILITY, } from "./types.js";
|
|
2
|
-
export function projectSkillV2RolloutReady(platform, readiness) {
|
|
3
|
-
const platformReady = platform === "darwin"
|
|
4
|
-
|| platform === "linux"
|
|
5
|
-
|| (platform === "win32" && readiness.nativeWindowsSmokePassed);
|
|
6
|
-
return platformReady
|
|
7
|
-
&& readiness.runtimeRootStorePrimitiveReady
|
|
8
|
-
&& readiness.runtimeRootStoreRecoveryReady
|
|
9
|
-
&& readiness.leaseRecoveryReady
|
|
10
|
-
&& readiness.controllerReady
|
|
11
|
-
&& readiness.executionHandlerReady
|
|
12
|
-
&& readiness.codexAdapterReady
|
|
13
|
-
&& readiness.claudeAdapterReady;
|
|
14
|
-
}
|
|
15
|
-
export function effectiveProjectSkillCapabilities(capabilities, projectSkillsReady, projectSkillV2Ready) {
|
|
16
|
-
const projectSkillCapabilitiesReady = projectSkillsReady && projectSkillV2Ready;
|
|
17
|
-
if (!projectSkillCapabilitiesReady
|
|
18
|
-
&& !capabilities.includes(PROJECT_SKILLS_CAPABILITY)
|
|
19
|
-
&& !capabilities.includes(PROJECT_SKILL_PROJECTION_V2_CAPABILITY)
|
|
20
|
-
&& Object.isFrozen(capabilities))
|
|
21
|
-
return capabilities;
|
|
22
|
-
const filtered = capabilities.filter((capability) => capability !== PROJECT_SKILL_PROJECTION_V2_CAPABILITY
|
|
23
|
-
&& (projectSkillCapabilitiesReady || capability !== PROJECT_SKILLS_CAPABILITY));
|
|
24
|
-
if (!projectSkillCapabilitiesReady || !filtered.includes(PROJECT_SKILLS_CAPABILITY)) {
|
|
25
|
-
return Object.freeze(filtered);
|
|
26
|
-
}
|
|
27
|
-
const v1Index = filtered.indexOf(PROJECT_SKILLS_CAPABILITY);
|
|
28
|
-
return Object.freeze([
|
|
29
|
-
...filtered.slice(0, v1Index + 1),
|
|
30
|
-
PROJECT_SKILL_PROJECTION_V2_CAPABILITY,
|
|
31
|
-
...filtered.slice(v1Index + 1),
|
|
32
|
-
]);
|
|
33
|
-
}
|
|
34
|
-
export function withProjectSkillCapabilityStatus(value, status) {
|
|
35
|
-
return {
|
|
36
|
-
...value,
|
|
37
|
-
projectSkillsStatus: status,
|
|
38
|
-
capabilities: status === "ready"
|
|
39
|
-
? value.capabilities
|
|
40
|
-
: effectiveProjectSkillCapabilities(value.capabilities, false, false),
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
const settleReadinessGate = async (operation) => {
|
|
44
|
-
try {
|
|
45
|
-
return await operation() === true;
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
export async function createProjectSkillConnectionCapabilitySnapshot(input) {
|
|
52
|
-
const runtimeRootStorePrimitiveReady = await settleReadinessGate(input.probeRuntimeRootStorePrimitive);
|
|
53
|
-
const controllerReady = await settleReadinessGate(input.initializeController);
|
|
54
|
-
const recoveryReady = await settleReadinessGate(input.recoverRuntimeRootsAndLeases);
|
|
55
|
-
const readiness = Object.freeze({
|
|
56
|
-
runtimeRootStorePrimitiveReady,
|
|
57
|
-
runtimeRootStoreRecoveryReady: recoveryReady,
|
|
58
|
-
leaseRecoveryReady: recoveryReady,
|
|
59
|
-
controllerReady,
|
|
60
|
-
executionHandlerReady: true,
|
|
61
|
-
codexAdapterReady: true,
|
|
62
|
-
claudeAdapterReady: true,
|
|
63
|
-
nativeWindowsSmokePassed: false,
|
|
64
|
-
});
|
|
65
|
-
const projectSkillsReady = projectSkillV2RolloutReady(input.platform, readiness);
|
|
66
|
-
return Object.freeze({
|
|
67
|
-
capabilities: effectiveProjectSkillCapabilities(input.baseCapabilities, projectSkillsReady, projectSkillsReady),
|
|
68
|
-
projectSkillsReady,
|
|
69
|
-
readiness,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
export async function prepareProjectSkillConnectionCapabilities(input) {
|
|
73
|
-
const snapshot = await createProjectSkillConnectionCapabilitySnapshot({
|
|
74
|
-
...input,
|
|
75
|
-
baseCapabilities: [...input.baseCapabilities, ...await input.additionalCapabilities()],
|
|
76
|
-
});
|
|
77
|
-
input.applyReadiness(snapshot.projectSkillsReady);
|
|
78
|
-
return snapshot.capabilities;
|
|
79
|
-
}
|
|
80
|
-
export function createProjectSkillControllerReadiness(input) {
|
|
81
|
-
let status = "initializing";
|
|
82
|
-
let initialization = null;
|
|
83
|
-
const ensure = () => {
|
|
84
|
-
if (status === "ready")
|
|
85
|
-
return Promise.resolve(true);
|
|
86
|
-
if (initialization !== null)
|
|
87
|
-
return initialization;
|
|
88
|
-
status = "initializing";
|
|
89
|
-
const attempt = input.initialize().then(() => {
|
|
90
|
-
status = "ready";
|
|
91
|
-
return true;
|
|
92
|
-
}, (error) => {
|
|
93
|
-
status = "unavailable";
|
|
94
|
-
input.onUnavailable(error);
|
|
95
|
-
return false;
|
|
96
|
-
}).finally(() => {
|
|
97
|
-
if (initialization === attempt)
|
|
98
|
-
initialization = null;
|
|
99
|
-
input.onSettled();
|
|
100
|
-
});
|
|
101
|
-
initialization = attempt;
|
|
102
|
-
return attempt;
|
|
103
|
-
};
|
|
104
|
-
return Object.freeze({
|
|
105
|
-
status: () => status,
|
|
106
|
-
ensure,
|
|
107
|
-
applyPreflight: (ready) => { status = ready ? "ready" : "unavailable"; },
|
|
108
|
-
});
|
|
109
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { ProjectProjectionError, } from "./reconciler.js";
|
|
2
|
-
export const MAX_REMEMBERED_PROJECT_SKILL_AGENTS = 64;
|
|
3
|
-
const compareBinary = (left, right) => {
|
|
4
|
-
if (left < right)
|
|
5
|
-
return -1;
|
|
6
|
-
if (left > right)
|
|
7
|
-
return 1;
|
|
8
|
-
return 0;
|
|
9
|
-
};
|
|
10
|
-
const copySnapshot = (snapshot) => Object.freeze({
|
|
11
|
-
bindings: Object.freeze(snapshot.bindings.map((binding) => Object.freeze({ ...binding }))),
|
|
12
|
-
generation: snapshot.generation,
|
|
13
|
-
});
|
|
14
|
-
export function createProjectSkillSnapshotEpoch(maxEntries = MAX_REMEMBERED_PROJECT_SKILL_AGENTS) {
|
|
15
|
-
if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
|
|
16
|
-
throw new ProjectProjectionError("skill_projection_failed");
|
|
17
|
-
}
|
|
18
|
-
const snapshots = new Map();
|
|
19
|
-
return Object.freeze({
|
|
20
|
-
begin() {
|
|
21
|
-
snapshots.clear();
|
|
22
|
-
},
|
|
23
|
-
remember(handle, snapshot) {
|
|
24
|
-
const previous = snapshots.get(handle);
|
|
25
|
-
if (previous !== undefined && snapshot.generation < previous.generation)
|
|
26
|
-
return;
|
|
27
|
-
if (previous === undefined && snapshots.size >= maxEntries) {
|
|
28
|
-
throw new ProjectProjectionError("skill_projection_failed");
|
|
29
|
-
}
|
|
30
|
-
snapshots.set(handle, copySnapshot(snapshot));
|
|
31
|
-
},
|
|
32
|
-
forget(handle) {
|
|
33
|
-
snapshots.delete(handle);
|
|
34
|
-
},
|
|
35
|
-
entries() {
|
|
36
|
-
return Object.freeze([...snapshots.entries()]
|
|
37
|
-
.sort(([left], [right]) => compareBinary(left, right))
|
|
38
|
-
.map(([handle, snapshot]) => Object.freeze([handle, copySnapshot(snapshot)])));
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
const stableErrorCode = (reason) => reason?.code === "skill_projection_snapshot_corrupt"
|
|
43
|
-
? "skill_projection_snapshot_corrupt"
|
|
44
|
-
: "skill_projection_failed";
|
|
45
|
-
export function toPathPrivateConvergenceResults(entries, results) {
|
|
46
|
-
return Object.freeze(entries.map(([handle], index) => {
|
|
47
|
-
const result = results[index];
|
|
48
|
-
if (result?.status === "fulfilled") {
|
|
49
|
-
return Object.freeze({ handle, status: "fulfilled" });
|
|
50
|
-
}
|
|
51
|
-
return Object.freeze({
|
|
52
|
-
handle,
|
|
53
|
-
status: "rejected",
|
|
54
|
-
errorCode: stableErrorCode(result?.reason),
|
|
55
|
-
});
|
|
56
|
-
}));
|
|
57
|
-
}
|