@nowcrew/daemon 0.5.39 → 0.5.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -95,23 +95,18 @@ crew-daemon doctor --profile work
95
95
  crew-daemon status --profile work
96
96
  ```
97
97
 
98
- For a non-default daemon home, the bootstrap release changes the native service ID to include the
99
- daemon-home fingerprint. Run the first command with the currently installed daemon: it upgrades and
100
- restarts the exact legacy service ID. Then use the upgraded CLI to migrate that profile instead of adopting
101
- the old ID:
98
+ For a non-default daemon home, the daemon now recognizes the pre-registry service ID when its descriptor
99
+ exactly matches the running profile's node path, package entry, profile name, and `--daemon-home`. No
100
+ uninstall/reinstall migration is required. The first self-update uses that exact legacy service ID for the
101
+ restart; future service lifecycle commands can continue to migrate it explicitly if desired:
102
102
 
103
103
  ```bash
104
- CREW_DAEMON_HOME="$HOME/.crew/daemon-dev" crew-daemon upgrade --profile work
105
- CREW_DAEMON_HOME="$HOME/.crew/daemon-dev" crew-daemon uninstall --profile work
106
- CREW_DAEMON_HOME="$HOME/.crew/daemon-dev" crew-daemon install --profile work
107
- CREW_DAEMON_HOME="$HOME/.crew/daemon-dev" crew-daemon start --profile work
108
- CREW_DAEMON_HOME="$HOME/.crew/daemon-dev" crew-daemon doctor --profile work
109
- CREW_DAEMON_HOME="$HOME/.crew/daemon-dev" crew-daemon status --profile work
104
+ CREW_DAEMON_HOME="$HOME/.crew/daemon-dev" crew-daemon doctor --profile dev
105
+ CREW_DAEMON_HOME="$HOME/.crew/daemon-dev" crew-daemon status --profile dev
110
106
  ```
111
107
 
112
- If migration is interrupted after `uninstall`, resume at `install` and `start`. If it is interrupted before
113
- `uninstall` completes, verify the exact legacy service with `status`, then repeat `uninstall`; identity checks
114
- prevent either path from removing a drifted descriptor.
108
+ If the descriptor has drifted, the profile is stopped, or another host descriptor is not registered, the
109
+ daemon fails closed and remains manually upgradeable.
115
110
 
116
111
  The daemon advertises `daemon_update_v1` only when all of these conditions hold:
117
112
 
@@ -120,7 +115,7 @@ The daemon advertises `daemon_update_v1` only when all of these conditions hold:
120
115
  | Platform | macOS LaunchAgent or Linux systemd user service |
121
116
  | Entrypoint | global `@nowcrew/daemon/dist/main.js`, not npx or source/tsx |
122
117
  | Startup | `crew-daemon serve --profile <name>` through the installed service |
123
- | Registry | every NowCrew descriptor on the OS user account is registered and conflict-free |
118
+ | Registry | every other NowCrew descriptor on the OS user account is registered and conflict-free; the exact target legacy descriptor may be adopted once |
124
119
  | Identity | service ID, descriptor, daemon home, Agent root, entrypoint, package root, and npm prefix match |
125
120
  | Service | the selected profile is running and holds the installation lease |
126
121
  | Install root | a standard writable Unix global npm prefix can be derived from the running entrypoint |
@@ -53,9 +53,8 @@ export async function detectDaemonUpdateEligibility(profileName, overrides = {})
53
53
  entryPath: deps.entryPath,
54
54
  profileHome: deps.profileHome,
55
55
  });
56
- const status = await deps.serviceStatus(spec);
57
- if (!status.running)
58
- return { eligible: false, reason: "service_not_running" };
56
+ let selectedSpec = spec;
57
+ let status = await deps.serviceStatus(spec);
59
58
  let services;
60
59
  try {
61
60
  services = await deps.readManagedServices();
@@ -65,34 +64,66 @@ export async function detectDaemonUpdateEligibility(profileName, overrides = {})
65
64
  }
66
65
  const registered = services.find((record) => record.serviceId === spec.id
67
66
  || (record.daemonHome === deps.profileHome && record.profile === profileName));
68
- if (registered === undefined) {
69
- return { eligible: false, reason: "managed_service_not_registered" };
70
- }
71
- try {
72
- assertRegistryCoversDescriptors(services, await deps.listManagedDescriptorPaths());
73
- }
74
- catch {
75
- return { eligible: false, reason: "managed_service_registry_incomplete" };
67
+ if (registered !== undefined) {
68
+ try {
69
+ assertRegistryCoversDescriptors(services, await deps.listManagedDescriptorPaths());
70
+ }
71
+ catch {
72
+ return { eligible: false, reason: "managed_service_registry_incomplete" };
73
+ }
74
+ const descriptor = await deps.readServiceDescriptor(spec).catch(() => null);
75
+ if (descriptor === null || spec.descriptorPath === null || spec.descriptor === null
76
+ || !managedServiceIdentityMatches(registered, {
77
+ version: 1,
78
+ platform: deps.platform,
79
+ serviceId: spec.id,
80
+ profile: profileName,
81
+ daemonHome: deps.profileHome,
82
+ agentsRoot: resolveAgentsRoot(profile.agentsRoot, deps.userHome, deps.platform),
83
+ nodePath: deps.nodePath,
84
+ entryPath: deps.entryPath,
85
+ packageRoot: installation.packageRoot,
86
+ npmPrefix: installation.npmPrefix,
87
+ descriptorPath: spec.descriptorPath,
88
+ descriptorSha256: serviceDescriptorSha256(descriptor),
89
+ })
90
+ || descriptor !== spec.descriptor) {
91
+ return { eligible: false, reason: "managed_service_identity_mismatch" };
92
+ }
76
93
  }
77
- const descriptor = await deps.readServiceDescriptor(spec).catch(() => null);
78
- if (descriptor === null || spec.descriptorPath === null || spec.descriptor === null
79
- || !managedServiceIdentityMatches(registered, {
80
- version: 1,
94
+ else {
95
+ // Releases before the managed-service registry used the unsuffixed service id.
96
+ // Adopt only an exact descriptor for this profile; never infer ownership from a
97
+ // running process or accept a descriptor with a different entry/home.
98
+ const legacySpec = buildServiceSpec({
81
99
  platform: deps.platform,
82
- serviceId: spec.id,
83
100
  profile: profileName,
84
- daemonHome: deps.profileHome,
85
- agentsRoot: resolveAgentsRoot(profile.agentsRoot, deps.userHome, deps.platform),
101
+ userHome: deps.userHome,
102
+ uid: deps.uid,
86
103
  nodePath: deps.nodePath,
87
104
  entryPath: deps.entryPath,
88
- packageRoot: installation.packageRoot,
89
- npmPrefix: installation.npmPrefix,
90
- descriptorPath: spec.descriptorPath,
91
- descriptorSha256: serviceDescriptorSha256(descriptor),
92
- })
93
- || descriptor !== spec.descriptor) {
94
- return { eligible: false, reason: "managed_service_identity_mismatch" };
105
+ profileHome: deps.profileHome,
106
+ legacyServiceId: true,
107
+ });
108
+ const legacyDescriptor = await deps.readServiceDescriptor(legacySpec).catch(() => null);
109
+ if (legacyDescriptor !== legacySpec.descriptor) {
110
+ return { eligible: false, reason: "managed_service_not_registered" };
111
+ }
112
+ try {
113
+ const descriptorPaths = await deps.listManagedDescriptorPaths();
114
+ const targetPath = legacySpec.descriptorPath;
115
+ assertRegistryCoversDescriptors(services, descriptorPaths.filter((descriptorPath) => descriptorPath !== targetPath));
116
+ }
117
+ catch {
118
+ return { eligible: false, reason: "managed_service_registry_incomplete" };
119
+ }
120
+ status = await deps.serviceStatus(legacySpec);
121
+ if (!status.running)
122
+ return { eligible: false, reason: "service_not_running" };
123
+ selectedSpec = legacySpec;
95
124
  }
125
+ if (!status.running)
126
+ return { eligible: false, reason: "service_not_running" };
96
127
  if (!deps.installationLeaseHeld(installation.npmPrefix)) {
97
128
  return { eligible: false, reason: "installation_lease_missing" };
98
129
  }
@@ -106,6 +137,6 @@ export async function detectDaemonUpdateEligibility(profileName, overrides = {})
106
137
  eligible: true,
107
138
  profileName,
108
139
  ...installation,
109
- serviceSpec: spec,
140
+ serviceSpec: selectedSpec,
110
141
  };
111
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.5.39",
3
+ "version": "0.5.40",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",