@nowcrew/daemon 0.6.98-beta.5 → 0.6.98-beta.6
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 +9 -0
- package/dist/automatic-daemon-installer.js +31 -10
- package/dist/computer-main.js +0 -0
- package/dist/computer-setup-client.js +5 -0
- package/dist/main.js +0 -0
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -110,6 +110,15 @@ to confirm Workspace, Machine, environment, profile, and storage identity. It do
|
|
|
110
110
|
existing `.crew` data. Existing `crew-daemon install --server-url ... --api-key ...` and profile lifecycle
|
|
111
111
|
commands remain available as compatibility and recovery entry points.
|
|
112
112
|
|
|
113
|
+
Profiles created by older installers may omit `workspaceId` and `machineId`. During setup, the installer
|
|
114
|
+
uses the newly authorized machine credential to call `POST /daemon/computer-setup/legacy-profile` with
|
|
115
|
+
the SHA-256 of the previous profile token. The server checks historical ownership within that exact
|
|
116
|
+
Workspace and Machine, including revoked credentials, without reactivating them. After the server,
|
|
117
|
+
profile name, Agent root and any existing identity fields match, normal installation persists both UUIDs.
|
|
118
|
+
A mismatch or failed verification stops before changing the local profile or service; do not delete a
|
|
119
|
+
profile or fill in UUIDs by guessing. Deploy the server endpoint before publishing the compatible daemon;
|
|
120
|
+
an older server cannot verify legacy profiles, and already published daemon packages remain unchanged.
|
|
121
|
+
|
|
113
122
|
## Machine Environment
|
|
114
123
|
|
|
115
124
|
The daemon loads exported variables from its OS account’s bash/zsh configuration on macOS/Linux,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { posix, win32 } from "node:path";
|
|
3
4
|
import { z } from "zod";
|
|
4
|
-
import { assertProfileAgentsRootUnique, listProfiles, loadProfile, MachineTokenSchema, removeProfile, saveProfile, windowsDpapiProtector, } from "./computer-profile.js";
|
|
5
|
+
import { assertProfileAgentsRootUnique, listProfiles, loadProfile, MachineTokenSchema, removeProfile, resolveAgentsRoot, saveProfile, windowsDpapiProtector, } from "./computer-profile.js";
|
|
5
6
|
import { buildServiceSpec, captureExactService, hardenWindowsProfile, replaceExactService, restoreExactService, serviceAction, serviceStatus, systemCommandRunner, uninstallService, } from "./computer-service.js";
|
|
6
7
|
import { daemonGlobalInstallation } from "./daemon-installation.js";
|
|
7
8
|
import { buildManagedServiceRecord, installManagedService, uninstallManagedService, } from "./managed-service-lifecycle.js";
|
|
@@ -15,8 +16,8 @@ import { daemonRegistryCandidates } from "./daemon-registry.js";
|
|
|
15
16
|
import { npmInvocation } from "./npm-command.js";
|
|
16
17
|
import { discoverExactLegacyService, } from "./automatic-service-migration.js";
|
|
17
18
|
import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
|
|
18
|
-
import { serverHostIdentity } from "./server-url.js";
|
|
19
|
-
import { computerSetupRecoveryGuidance } from "./computer-setup-client.js";
|
|
19
|
+
import { serverHostIdentity, serverUrlsMatch } from "./server-url.js";
|
|
20
|
+
import { computerSetupRecoveryGuidance, createComputerSetupClient, } from "./computer-setup-client.js";
|
|
20
21
|
export const automaticDaemonInstallStageMessage = {
|
|
21
22
|
downloading: "Downloading the Daemon package...",
|
|
22
23
|
configuring: "Configuring the Daemon service...",
|
|
@@ -63,6 +64,7 @@ function defaultDependencies() {
|
|
|
63
64
|
version: daemonVersion(),
|
|
64
65
|
...(process.env.PATH ? { runtimePath: process.env.PATH } : {}),
|
|
65
66
|
runner,
|
|
67
|
+
verifyLegacyProfileIdentity: (serverUrl, input) => createComputerSetupClient({ serverUrl }).verifyLegacyProfileIdentity(input),
|
|
66
68
|
captureProfile: async (target) => (await listProfiles(target.daemonHome)).includes(target.profile)
|
|
67
69
|
? loadProfile(target.profile, target.daemonHome, storage)
|
|
68
70
|
: null,
|
|
@@ -266,18 +268,37 @@ function setupIdentity(input) {
|
|
|
266
268
|
machineId: z.string().uuid().parse(input.machineId),
|
|
267
269
|
};
|
|
268
270
|
}
|
|
269
|
-
function assertExistingProfileIdentity(previous, identity) {
|
|
271
|
+
async function assertExistingProfileIdentity(previous, identity, target, machineToken, dependencies) {
|
|
270
272
|
if (previous === null || identity === null)
|
|
271
273
|
return;
|
|
272
|
-
if (previous.workspaceId
|
|
273
|
-
throw new Error(`Profile '${previous.name}' does not record its Workspace and Machine identity`);
|
|
274
|
-
}
|
|
275
|
-
if (previous.workspaceId !== identity.workspaceId) {
|
|
274
|
+
if (previous.workspaceId !== undefined && previous.workspaceId !== identity.workspaceId) {
|
|
276
275
|
throw new Error(`Profile '${previous.name}' belongs to a different Workspace identity`);
|
|
277
276
|
}
|
|
278
|
-
if (previous.machineId !== identity.machineId) {
|
|
277
|
+
if (previous.machineId !== undefined && previous.machineId !== identity.machineId) {
|
|
279
278
|
throw new Error(`Profile '${previous.name}' belongs to a different Machine identity`);
|
|
280
279
|
}
|
|
280
|
+
if (previous.workspaceId !== undefined && previous.machineId !== undefined)
|
|
281
|
+
return;
|
|
282
|
+
const rootIdentity = (root) => {
|
|
283
|
+
const resolved = resolveAgentsRoot(root, dependencies.userHome, dependencies.platform);
|
|
284
|
+
return dependencies.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
285
|
+
};
|
|
286
|
+
if (previous.name !== target.profile || !serverUrlsMatch(previous.serverUrl, target.serverUrl)
|
|
287
|
+
|| rootIdentity(previous.agentsRoot) !== rootIdentity(target.agentsRoot)) {
|
|
288
|
+
throw new Error(`Legacy profile '${previous.name}' does not match the requested server, profile, or Agent root`);
|
|
289
|
+
}
|
|
290
|
+
if (dependencies.verifyLegacyProfileIdentity === undefined) {
|
|
291
|
+
throw new Error(`Cannot verify legacy profile '${previous.name}' Workspace and Machine identity`);
|
|
292
|
+
}
|
|
293
|
+
// The old credential may already have been revoked by setup. Only the current credential
|
|
294
|
+
// authenticates this read-only request; the old hash proves historical ownership.
|
|
295
|
+
const verified = await dependencies.verifyLegacyProfileIdentity(target.serverUrl, {
|
|
296
|
+
machineToken,
|
|
297
|
+
previousTokenHash: createHash("sha256").update(previous.machineToken).digest("hex"),
|
|
298
|
+
});
|
|
299
|
+
if (verified.workspaceId !== identity.workspaceId || verified.machineId !== identity.machineId) {
|
|
300
|
+
throw new Error(`Legacy profile '${previous.name}' belongs to a different Workspace or Machine identity`);
|
|
301
|
+
}
|
|
281
302
|
}
|
|
282
303
|
export async function installDaemonAutomatically(input, dependencies = defaultDependencies()) {
|
|
283
304
|
const machineToken = MachineTokenSchema.parse(input.machineToken);
|
|
@@ -314,7 +335,7 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
|
|
|
314
335
|
});
|
|
315
336
|
const previousProfile = await dependencies.captureProfile(target);
|
|
316
337
|
await dependencies.assertProfileTarget(target, previousProfile);
|
|
317
|
-
assertExistingProfileIdentity(previousProfile, identity);
|
|
338
|
+
await assertExistingProfileIdentity(previousProfile, identity, target, machineToken, dependencies);
|
|
318
339
|
const previousService = await dependencies.captureService(spec, target);
|
|
319
340
|
if (previousService.installed && !previousService.exact) {
|
|
320
341
|
throw new Error(`Installed service '${spec.id}' does not match the requested identity`);
|
package/dist/computer-main.js
CHANGED
|
File without changes
|
|
@@ -299,6 +299,11 @@ export function createComputerSetupClient(options) {
|
|
|
299
299
|
return receipt.data;
|
|
300
300
|
},
|
|
301
301
|
tokenTarget: async (machineToken) => SetupTokenTargetSchema.parse(await request("GET", "/daemon/computer-setup/target", undefined, machineToken)),
|
|
302
|
+
verifyLegacyProfileIdentity: async (input) => z.object({
|
|
303
|
+
workspaceId: z.string().uuid(), machineId: z.string().uuid(),
|
|
304
|
+
}).strict().parse(await request("POST", "/daemon/computer-setup/legacy-profile", {
|
|
305
|
+
previousTokenHash: input.previousTokenHash,
|
|
306
|
+
}, input.machineToken)),
|
|
302
307
|
start: async (input) => {
|
|
303
308
|
return parseStart(await request("POST", "/api/auth/computer-setup/start", {
|
|
304
309
|
...(input.workspace.kind === "id"
|
package/dist/main.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nowcrew/daemon",
|
|
3
|
-
"version": "0.6.98-beta.
|
|
3
|
+
"version": "0.6.98-beta.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
"publishConfig": {
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
23
|
+
"build": "tsc -p tsconfig.json",
|
|
24
|
+
"prepublishOnly": "pnpm build && node ../scripts/daemon-release-artifact.mjs --strict-registry",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"typecheck": "tsc --noEmit"
|
|
27
|
+
},
|
|
21
28
|
"dependencies": {
|
|
22
29
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
23
30
|
"@nowcrew/cli": "0.4.17",
|
|
@@ -39,11 +46,5 @@
|
|
|
39
46
|
"tsx": "^4.19.0",
|
|
40
47
|
"typescript": "^5.6.0",
|
|
41
48
|
"vitest": "^2.1.0"
|
|
42
|
-
},
|
|
43
|
-
"scripts": {
|
|
44
|
-
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
45
|
-
"build": "tsc -p tsconfig.json",
|
|
46
|
-
"test": "vitest run",
|
|
47
|
-
"typecheck": "tsc --noEmit"
|
|
48
49
|
}
|
|
49
|
-
}
|
|
50
|
+
}
|