@nowcrew/daemon 0.6.87-beta.3 → 0.6.87-beta.5

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 (38) hide show
  1. package/README.md +22 -0
  2. package/dist/cloud-report-uploader.js +27 -25
  3. package/dist/execution-runner.js +6 -3
  4. package/dist/external-output.js +154 -68
  5. package/dist/local-execution-prompt.js +19 -25
  6. package/dist/local-executor.js +16 -44
  7. package/dist/local-memory/prompt.js +37 -0
  8. package/dist/local-memory/read-cli.js +232 -0
  9. package/dist/local-memory-diagnostics.js +79 -12
  10. package/dist/main.js +6 -2
  11. package/dist/prompt.js +24 -9
  12. package/dist/serve.js +17 -11
  13. package/dist/storage-identity.js +4 -1
  14. package/dist/storage-ownership.js +93 -5
  15. package/dist/storage-runtime-controller.js +40 -20
  16. package/dist/storage-runtime.js +38 -2
  17. package/dist/storage-serve-adapter.js +22 -5
  18. package/dist/workspace.js +15 -0
  19. package/package.json +1 -1
  20. package/dist/project-skills/capability.js +0 -109
  21. package/dist/project-skills/controller-convergence.js +0 -57
  22. package/dist/project-skills/projection-set-switch.js +0 -419
  23. package/dist/project-skills/projection-state-domain.js +0 -170
  24. package/dist/project-skills/projection-state-store.js +0 -842
  25. package/dist/project-skills/projection-state-transaction.js +0 -322
  26. package/dist/project-skills/projection-state.js +0 -3
  27. package/dist/project-skills/runtime-root-bootstrap.js +0 -47
  28. package/dist/project-skills/runtime-root-domain.js +0 -268
  29. package/dist/project-skills/runtime-root-gc.js +0 -293
  30. package/dist/project-skills/runtime-root-lease-artifact.js +0 -46
  31. package/dist/project-skills/runtime-root-leases.js +0 -487
  32. package/dist/project-skills/runtime-root-source-identity.js +0 -60
  33. package/dist/project-skills/runtime-root-startup.js +0 -49
  34. package/dist/project-skills/runtime-root-state-artifact-domain.js +0 -143
  35. package/dist/project-skills/runtime-root-state-index.js +0 -356
  36. package/dist/project-skills/runtime-root-store.js +0 -722
  37. package/dist/project-skills/serve-capability.js +0 -28
  38. package/dist/project-skills/serve-startup.js +0 -37
@@ -1,8 +1,11 @@
1
- import { createHash } from "node:crypto";
2
- import { lstat, readFile } from "node:fs/promises";
3
- import { join } from "node:path";
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { chmod, lstat, mkdir, open, readFile, rm } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
4
  import { z } from "zod";
5
+ import { atomicRenameNoReplace } from "./atomic-no-replace-rename.js";
6
+ import { durableDirectorySync } from "./atomic-private-write.js";
5
7
  const FingerprintSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/u);
8
+ const UuidSchema = z.string().uuid();
6
9
  const StorageOwnershipClaimSchema = z.object({
7
10
  version: z.literal(1),
8
11
  machineId: z.string().uuid(),
@@ -38,8 +41,10 @@ export function createStorageOwnershipClaim(input) {
38
41
  export function storageOwnershipClaimPath(nowworkHome) {
39
42
  return join(nowworkHome, "storage-layout.json");
40
43
  }
41
- export async function readStorageOwnershipClaim(nowworkHome) {
42
- const path = storageOwnershipClaimPath(nowworkHome);
44
+ export function workspaceStorageOwnershipClaimPath(nowworkHome, workspaceId) {
45
+ return join(nowworkHome, ".storage-ownership", `${UuidSchema.parse(workspaceId).toLowerCase()}.json`);
46
+ }
47
+ async function readClaim(path) {
43
48
  const metadata = await lstat(path);
44
49
  if (!metadata.isFile())
45
50
  throw new Error("storage ownership claim must be a file");
@@ -53,3 +58,86 @@ export async function readStorageOwnershipClaim(nowworkHome) {
53
58
  }
54
59
  return expected;
55
60
  }
61
+ function missingClaim(message) {
62
+ return Object.assign(new Error(message), { code: "ENOENT" });
63
+ }
64
+ export async function readStorageOwnershipClaim(nowworkHome, workspaceId) {
65
+ if (workspaceId !== undefined) {
66
+ const normalizedWorkspaceId = UuidSchema.parse(workspaceId).toLowerCase();
67
+ try {
68
+ return await readClaim(workspaceStorageOwnershipClaimPath(nowworkHome, normalizedWorkspaceId));
69
+ }
70
+ catch (error) {
71
+ if (error.code !== "ENOENT")
72
+ throw error;
73
+ }
74
+ const legacy = await readClaim(storageOwnershipClaimPath(nowworkHome));
75
+ if (legacy.workspaceId !== normalizedWorkspaceId) {
76
+ throw missingClaim(`storage ownership claim is not provisioned for workspace '${normalizedWorkspaceId}'`);
77
+ }
78
+ return legacy;
79
+ }
80
+ return readClaim(storageOwnershipClaimPath(nowworkHome));
81
+ }
82
+ export async function resolveWorkspaceStorageOwnershipClaim(input) {
83
+ const machineId = UuidSchema.parse(input.machineId).toLowerCase();
84
+ const workspaceId = UuidSchema.parse(input.workspaceId).toLowerCase();
85
+ try {
86
+ const existing = await readStorageOwnershipClaim(input.nowworkHome, workspaceId);
87
+ const expected = createStorageOwnershipClaim({
88
+ machineId,
89
+ workspaceId,
90
+ environment: input.environment,
91
+ volumeFingerprint: existing.volumeFingerprint,
92
+ workspaceRootFingerprint: input.workspaceRootFingerprint,
93
+ ownerEpoch: existing.ownerEpoch,
94
+ });
95
+ if (existing.claimFingerprint !== expected.claimFingerprint) {
96
+ throw new Error("storage ownership claim conflicts with the authenticated workspace identity");
97
+ }
98
+ return existing;
99
+ }
100
+ catch (error) {
101
+ if (error.code !== "ENOENT")
102
+ throw error;
103
+ }
104
+ const anchor = await readClaim(storageOwnershipClaimPath(input.nowworkHome));
105
+ if (anchor.environment !== input.environment.toLowerCase()) {
106
+ throw new Error("storage ownership volume anchor environment does not match");
107
+ }
108
+ const claim = createStorageOwnershipClaim({
109
+ machineId,
110
+ workspaceId,
111
+ environment: input.environment,
112
+ volumeFingerprint: anchor.volumeFingerprint,
113
+ workspaceRootFingerprint: input.workspaceRootFingerprint,
114
+ ownerEpoch: 1,
115
+ });
116
+ const path = workspaceStorageOwnershipClaimPath(input.nowworkHome, workspaceId);
117
+ const directory = dirname(path);
118
+ await mkdir(directory, { recursive: true, mode: 0o700 });
119
+ await chmod(directory, 0o700);
120
+ const temporary = `${path}.${randomUUID()}.tmp`;
121
+ let handle = null;
122
+ try {
123
+ handle = await open(temporary, "wx", 0o600);
124
+ await handle.writeFile(`${JSON.stringify({ version: 1, ...claim })}\n`, "utf8");
125
+ await handle.sync();
126
+ await handle.close();
127
+ handle = null;
128
+ const published = await atomicRenameNoReplace(temporary, path);
129
+ if (published === "published") {
130
+ await durableDirectorySync(directory);
131
+ return claim;
132
+ }
133
+ const raced = await readClaim(path);
134
+ if (raced.claimFingerprint !== claim.claimFingerprint) {
135
+ throw new Error("storage ownership claim was concurrently provisioned for another identity");
136
+ }
137
+ return raced;
138
+ }
139
+ finally {
140
+ await handle?.close().catch(() => undefined);
141
+ await rm(temporary, { force: true }).catch(() => undefined);
142
+ }
143
+ }
@@ -5,12 +5,13 @@ import { buildServiceSpec } from "./computer-service.js";
5
5
  import { buildManagedServiceRecord } from "./managed-service-lifecycle.js";
6
6
  import { createStorageControlController } from "./storage-control.js";
7
7
  import { runStorageDoctor } from "./storage-doctor.js";
8
- import { storageHelloIdentity } from "./storage-identity.js";
8
+ import { storageHelloIdentity, storageWorkspaceRootFingerprint, } from "./storage-identity.js";
9
9
  import { storageLayout, validateStorageEnvironment } from "./storage-layout.js";
10
10
  import { StorageMigrationFailure } from "./storage-migration-controller.js";
11
11
  import { launchStorageMigrationWorker, writeStorageWorkerHandoff } from "./storage-worker-handoff.js";
12
12
  import { writeStorageWorkerOperation } from "./storage-worker-operation.js";
13
13
  import { latestSourceStorageMigrationReceipt, monitorStorageMigrationStatus, storageMigrationStatusReplay, } from "./storage-status-replay.js";
14
+ import { resolveWorkspaceStorageOwnershipClaim, } from "./storage-ownership.js";
14
15
  const UUID = z.string().uuid();
15
16
  function fingerprint(value) {
16
17
  return `sha256:${createHash("sha256").update(value).digest("hex")}`;
@@ -22,13 +23,13 @@ function deterministicUuid(value) {
22
23
  const hex = chars.join("");
23
24
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
24
25
  }
25
- function storageId(input, workspaceId) {
26
- if (input.ownership.workspaceId !== workspaceId || input.ownership.environment !== input.environment) {
26
+ function storageId(input, workspaceId, ownership) {
27
+ if (ownership.workspaceId !== workspaceId || ownership.environment !== input.environment) {
27
28
  throw new Error("Storage ownership claim does not match the migration identity");
28
29
  }
29
- return `storage_${input.ownership.claimFingerprint.slice("sha256:".length)}`;
30
+ return `storage_${ownership.claimFingerprint.slice("sha256:".length)}`;
30
31
  }
31
- function installationBase(input, workspaceId, migrationId, createdAt) {
32
+ function installationBase(input, workspaceId, migrationId, createdAt, ownership) {
32
33
  const layout = storageLayout({
33
34
  nowworkHome: input.nowworkHome,
34
35
  environment: input.environment,
@@ -43,7 +44,7 @@ function installationBase(input, workspaceId, migrationId, createdAt) {
43
44
  serverUrl: input.serverUrl,
44
45
  environment: input.environment,
45
46
  workspaceId,
46
- storageId: storageId(input, workspaceId),
47
+ storageId: storageId(input, workspaceId, ownership),
47
48
  nowworkHome: input.nowworkHome,
48
49
  runtimeRoot: layout.runtimeRoot,
49
50
  credentialRef: `profile:${input.profile}`,
@@ -99,6 +100,7 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
99
100
  writeHandoff: writeStorageWorkerHandoff,
100
101
  launchWorker: launchStorageMigrationWorker,
101
102
  monitorWorker: monitorStorageMigrationStatus,
103
+ resolveOwnership: resolveWorkspaceStorageOwnershipClaim,
102
104
  now: () => new Date(),
103
105
  ...injected,
104
106
  };
@@ -108,6 +110,8 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
108
110
  let gate = null;
109
111
  let sendStatus = null;
110
112
  let control = null;
113
+ let ownership = input.ownership;
114
+ let identityBindTail = Promise.resolve();
111
115
  const pendingControlMessages = [];
112
116
  let drainingPendingControlMessages = false;
113
117
  const drainPendingControlMessages = () => {
@@ -197,8 +201,8 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
197
201
  backupsRoot: input.local.backupsRoot,
198
202
  backupArtifacts: input.backupArtifacts.map((artifact) => ({ ...artifact })),
199
203
  ownership: {
200
- ownerEpoch: input.ownership.ownerEpoch,
201
- ownershipClaimFingerprint: input.ownership.claimFingerprint,
204
+ ownerEpoch: ownership.ownerEpoch,
205
+ ownershipClaimFingerprint: ownership.claimFingerprint,
202
206
  },
203
207
  acceptedWarnings: [...input.policy.acceptedWarnings],
204
208
  },
@@ -214,7 +218,7 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
214
218
  installation: {
215
219
  configRoot: input.local.configRoot,
216
220
  target: {
217
- ...installationBase(input, workspaceId, doctor.manifest.migrationId, createdAt),
221
+ ...installationBase(input, workspaceId, doctor.manifest.migrationId, createdAt, ownership),
218
222
  storageMigrationId: doctor.manifest.migrationId,
219
223
  },
220
224
  },
@@ -236,8 +240,8 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
236
240
  sourceStorageFingerprint,
237
241
  targetStorageFingerprint: identity.storageIdFingerprint,
238
242
  ownership: {
239
- ownerEpoch: input.ownership.ownerEpoch,
240
- ownershipClaimFingerprint: input.ownership.claimFingerprint,
243
+ ownerEpoch: ownership.ownerEpoch,
244
+ ownershipClaimFingerprint: ownership.claimFingerprint,
241
245
  },
242
246
  runDoctor: async (migrationId) => dependencies.runDoctor(await doctorInput(migrationId)),
243
247
  runMigration: async (doctor) => {
@@ -294,7 +298,7 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
294
298
  sendStatus,
295
299
  });
296
300
  };
297
- const bindServerIdentity = (server) => {
301
+ const bindServerIdentityOnce = async (server) => {
298
302
  const nextWorkspaceId = UUID.parse(server.workspaceId).toLowerCase();
299
303
  const nextMachineId = UUID.parse(server.machineId).toLowerCase();
300
304
  if (workspaceId !== null && workspaceId !== nextWorkspaceId) {
@@ -303,13 +307,6 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
303
307
  if (machineId !== null && machineId !== nextMachineId) {
304
308
  throw new Error("Storage migration server machine identity changed");
305
309
  }
306
- if (input.ownership.workspaceId !== nextWorkspaceId
307
- || input.ownership.machineId !== nextMachineId
308
- || input.ownership.environment !== input.environment) {
309
- throw new Error("Storage ownership claim does not match authenticated server identity");
310
- }
311
- const createdAt = dependencies.now().toISOString();
312
- const installation = installationBase(input, nextWorkspaceId, undefined, createdAt);
313
310
  const layout = storageLayout({
314
311
  nowworkHome: input.nowworkHome,
315
312
  environment: input.environment,
@@ -317,13 +314,36 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
317
314
  runtimeBase: input.local.runtimeBase,
318
315
  platform: input.platform,
319
316
  });
320
- const nextIdentity = storageHelloIdentity({ installation, layout, ownership: input.ownership });
317
+ if (ownership.workspaceId !== nextWorkspaceId
318
+ || ownership.machineId !== nextMachineId
319
+ || ownership.environment !== input.environment) {
320
+ ownership = await dependencies.resolveOwnership({
321
+ nowworkHome: input.nowworkHome,
322
+ machineId: nextMachineId,
323
+ workspaceId: nextWorkspaceId,
324
+ environment: input.environment,
325
+ workspaceRootFingerprint: storageWorkspaceRootFingerprint(layout.workspaceRoot),
326
+ });
327
+ }
328
+ if (ownership.workspaceId !== nextWorkspaceId
329
+ || ownership.machineId !== nextMachineId
330
+ || ownership.environment !== input.environment) {
331
+ throw new Error("Storage ownership claim does not match authenticated server identity");
332
+ }
333
+ const createdAt = dependencies.now().toISOString();
334
+ const installation = installationBase(input, nextWorkspaceId, undefined, createdAt, ownership);
335
+ const nextIdentity = storageHelloIdentity({ installation, layout, ownership });
321
336
  workspaceId = nextWorkspaceId;
322
337
  machineId = nextMachineId;
323
338
  identity = nextIdentity;
324
339
  ensureControl();
325
340
  drainPendingControlMessages();
326
341
  };
342
+ const bindServerIdentity = (server) => {
343
+ const current = identityBindTail.then(() => bindServerIdentityOnce(server));
344
+ identityBindTail = current.catch(() => undefined);
345
+ return current;
346
+ };
327
347
  const attach = (runtime) => {
328
348
  gate = runtime.gate;
329
349
  sendStatus = runtime.sendStatus;
@@ -1,7 +1,7 @@
1
1
  import { findInstallationForProfile, readInstallations, resolveLocalStorageRoots, } from "./storage-config.js";
2
2
  import { storageHelloIdentity } from "./storage-identity.js";
3
3
  import { storageLayout } from "./storage-layout.js";
4
- import { readStorageOwnershipClaim } from "./storage-ownership.js";
4
+ import { readStorageOwnershipClaim, resolveWorkspaceStorageOwnershipClaim, } from "./storage-ownership.js";
5
5
  const canonicalServerUrl = (value) => value.replace(/\/+$/u, "");
6
6
  export function resolveStorageRuntime(input) {
7
7
  const installation = findInstallationForProfile(input.records, input.profile);
@@ -45,7 +45,7 @@ export async function loadStorageRuntime(input) {
45
45
  return null;
46
46
  let ownership;
47
47
  try {
48
- ownership = await readStorageOwnershipClaim(baseline.layout.persistentRoot);
48
+ ownership = await readStorageOwnershipClaim(baseline.layout.persistentRoot, baseline.layout.workspaceId);
49
49
  }
50
50
  catch (error) {
51
51
  if (error.code !== "ENOENT")
@@ -53,3 +53,39 @@ export async function loadStorageRuntime(input) {
53
53
  }
54
54
  return ownership === undefined ? baseline : resolveStorageRuntime({ ...input, records, ownership });
55
55
  }
56
+ export async function bindStorageRuntimeOwnership(runtime, server) {
57
+ const workspaceId = server.workspaceId.toLowerCase();
58
+ const machineId = server.machineId.toLowerCase();
59
+ if (runtime.installation.workspaceId !== workspaceId) {
60
+ throw new Error("Storage runtime server workspace identity changed");
61
+ }
62
+ const existing = runtime.identity.storageOwnership;
63
+ if (existing !== undefined) {
64
+ if (existing.machineId !== machineId)
65
+ throw new Error("Storage runtime server machine identity changed");
66
+ return runtime;
67
+ }
68
+ const ownership = await resolveWorkspaceStorageOwnershipClaim({
69
+ nowworkHome: runtime.layout.persistentRoot,
70
+ machineId,
71
+ workspaceId,
72
+ environment: runtime.installation.environment,
73
+ workspaceRootFingerprint: runtime.identity.workspaceRootFingerprint,
74
+ });
75
+ return Object.freeze({
76
+ ...runtime,
77
+ identity: storageHelloIdentity({ installation: runtime.installation, layout: runtime.layout, ownership }),
78
+ });
79
+ }
80
+ export function createStorageRuntimeOwnershipBinding(initial) {
81
+ let runtime = initial;
82
+ let bindTail = Promise.resolve();
83
+ const bind = (server) => {
84
+ const current = bindTail.then(async () => {
85
+ runtime = await bindStorageRuntimeOwnership(runtime, server);
86
+ });
87
+ bindTail = current.catch(() => undefined);
88
+ return current;
89
+ };
90
+ return Object.freeze({ identity: () => runtime.identity, bind });
91
+ }
@@ -9,6 +9,15 @@ export function storageMigrationCapabilities(configured, options) {
9
9
  ...(options.ready ? [STORAGE_MIGRATION_CAPABILITY] : []),
10
10
  ])]);
11
11
  }
12
+ export function bindStorageMigrationServerIdentity(options, identity, callbacks) {
13
+ const bind = options?.bindServerIdentity;
14
+ if (bind === undefined) {
15
+ if (options?.ready === true)
16
+ callbacks.onBound();
17
+ return;
18
+ }
19
+ void Promise.resolve().then(() => bind(identity)).then(callbacks.onBound).catch(callbacks.onError);
20
+ }
12
21
  export function createServeStorageStatusDelivery(options, dependencies) {
13
22
  return createStorageStatusDelivery({
14
23
  ...(options?.statusOutboxPath === undefined ? {} : { outboxPath: options.statusOutboxPath }),
@@ -17,28 +26,36 @@ export function createServeStorageStatusDelivery(options, dependencies) {
17
26
  });
18
27
  }
19
28
  export function attachStorageMigrationRuntime(options, dependencies) {
20
- let admissionClosed = false;
29
+ let migrationAdmissionClosed = false;
30
+ let identityAdmissionClosed = options?.bindServerIdentity !== undefined;
21
31
  if (options?.ready === true) {
22
32
  options.attach?.({
23
33
  gate: {
24
34
  isBusy: dependencies.isBusy,
25
35
  closeAndFlush: async () => {
26
- admissionClosed = true;
36
+ migrationAdmissionClosed = true;
27
37
  await dependencies.waitForExecutionFrames();
28
38
  if (dependencies.isBusy()) {
29
- admissionClosed = false;
39
+ migrationAdmissionClosed = false;
30
40
  throw Object.assign(new Error("active execution entered before storage migration quiesce"), {
31
41
  code: "runtime_busy",
32
42
  });
33
43
  }
34
44
  await dependencies.flushLocalState();
35
45
  },
36
- reopen: () => { admissionClosed = false; },
46
+ reopen: () => { migrationAdmissionClosed = false; },
37
47
  },
38
48
  sendStatus: dependencies.sendStatus,
39
49
  });
40
50
  }
41
- return Object.freeze({ isAdmissionClosed: () => admissionClosed });
51
+ return Object.freeze({
52
+ isAdmissionClosed: () => migrationAdmissionClosed || identityAdmissionClosed,
53
+ beginIdentityBinding: () => {
54
+ if (options?.bindServerIdentity !== undefined)
55
+ identityAdmissionClosed = true;
56
+ },
57
+ completeIdentityBinding: () => { identityAdmissionClosed = false; },
58
+ });
42
59
  }
43
60
  export function replayStorageMigrationStatus(options, socket, isCurrent, onError, deliver) {
44
61
  return options?.replayStatus?.((frame) => {
package/dist/workspace.js CHANGED
@@ -11,6 +11,8 @@
11
11
  import { mkdir, writeFile, readFile, chmod, access, lstat } from "node:fs/promises";
12
12
  import { createHash, randomUUID } from "node:crypto";
13
13
  import { dirname, join } from "node:path";
14
+ import { fileURLToPath, pathToFileURL } from "node:url";
15
+ import { createRequire } from "node:module";
14
16
  import { dslog } from "./slog.js";
15
17
  /** 文件系统安全的 taskKey:仅留 [\w.-],其余转 _,截断,避免路径穿越/超长。 */
16
18
  export function safeKey(key) {
@@ -96,6 +98,19 @@ export async function prepareWorkspace(input) {
96
98
  await chmod(wrapper, 0o755);
97
99
  // win32 侧 wrapper:cmd/PowerShell 不认 shebang 脚本,并存 crew.cmd(unix 下无害不会被命中)
98
100
  await writeFile(join(crewDir, "crew.cmd"), `@echo off\r\nnode ${JSON.stringify(input.cliPath)} %*\r\n`, "utf8");
101
+ if (!input.skipLocalMemory) {
102
+ const sourceMode = import.meta.url.endsWith(".ts");
103
+ const reader = fileURLToPath(new URL(`./local-memory/read-cli.${sourceMode ? "ts" : "js"}`, import.meta.url));
104
+ const readerArgs = sourceMode
105
+ ? ["--import", pathToFileURL(createRequire(import.meta.url).resolve("tsx/esm")).href, reader]
106
+ : [reader];
107
+ const readerWrapper = join(crewDir, "crew-memory");
108
+ const posixArgs = readerArgs.map((arg) => `'${arg.replace(/'/gu, "'\\''")}'`).join(" ");
109
+ const windowsArgs = readerArgs.map((arg) => `"${arg.replace(/%/gu, "%%")}"`).join(" ");
110
+ await writeFile(readerWrapper, `#!/bin/sh\nexec node ${posixArgs} "$@"\n`, "utf8");
111
+ await chmod(readerWrapper, 0o755);
112
+ await writeFile(join(crewDir, "crew-memory.cmd"), `@echo off\r\nnode ${windowsArgs} %*\r\n`, "utf8");
113
+ }
99
114
  // 每个 agent 独立的 XDG 配置根:隔离第三方 CLI 凭证,避免 agent 之间互相串号
100
115
  const homeDir = join(dir, ".home");
101
116
  await mkdir(join(homeDir, ".config"), { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.87-beta.3",
3
+ "version": "0.6.87-beta.5",
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
- }