@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.
Files changed (28) hide show
  1. package/dist/automatic-daemon-installer.js +22 -6
  2. package/dist/computer-service.js +12 -1
  3. package/dist/daemon-migration-controller.js +4 -3
  4. package/dist/storage-config.js +26 -0
  5. package/dist/storage-migration-controller.js +0 -1
  6. package/dist/storage-runtime-readiness.js +50 -16
  7. package/dist/storage-worker-executor.js +29 -11
  8. package/dist/storage-worker-operation.js +6 -0
  9. package/package.json +1 -1
  10. package/dist/project-skills/capability.js +0 -109
  11. package/dist/project-skills/controller-convergence.js +0 -57
  12. package/dist/project-skills/projection-set-switch.js +0 -419
  13. package/dist/project-skills/projection-state-domain.js +0 -170
  14. package/dist/project-skills/projection-state-store.js +0 -842
  15. package/dist/project-skills/projection-state-transaction.js +0 -322
  16. package/dist/project-skills/projection-state.js +0 -3
  17. package/dist/project-skills/runtime-root-bootstrap.js +0 -47
  18. package/dist/project-skills/runtime-root-domain.js +0 -268
  19. package/dist/project-skills/runtime-root-gc.js +0 -293
  20. package/dist/project-skills/runtime-root-lease-artifact.js +0 -46
  21. package/dist/project-skills/runtime-root-leases.js +0 -487
  22. package/dist/project-skills/runtime-root-source-identity.js +0 -60
  23. package/dist/project-skills/runtime-root-startup.js +0 -49
  24. package/dist/project-skills/runtime-root-state-artifact-domain.js +0 -143
  25. package/dist/project-skills/runtime-root-state-index.js +0 -356
  26. package/dist/project-skills/runtime-root-store.js +0 -722
  27. package/dist/project-skills/serve-capability.js +0 -28
  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, { userHome, localHome }),
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 current = (await readManagedServiceRegistry({ userHome, localHome })).find((record) => record.serviceId === spec.id);
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);
@@ -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\nExecStart=${daemonCommand.map(systemdArg).join(" ")}\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=default.target\n`,
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
- // This controller migrates the legacy service layout; the new local-home
137
- // standard is handled by the storage migration controller.
138
- requestedLocalHomeMode: "legacy",
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) {
@@ -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");
@@ -54,7 +54,6 @@ const REQUIRED_BACKUP_ARTIFACTS = Object.freeze([
54
54
  "profile.json",
55
55
  "service-descriptor",
56
56
  "managed-service-registry.json",
57
- "installations.json",
58
57
  ]);
59
58
  export class StorageMigrationFailure extends Error {
60
59
  code;
@@ -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
- 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
- });
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 local = resolveLocalStorageRoots({
177
- platform: input.platform,
178
- userHome: input.userHome,
179
- environment: input.environment,
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, localHome) },
202
- { name: "installations.json", path: installationsPath(local.configRoot) },
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 registry = { userHome: operation.service.userHome };
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(registry);
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",
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",
@@ -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
- }