@nowcrew/daemon 0.6.77 → 0.6.79-beta.1

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 (71) hide show
  1. package/README.md +27 -0
  2. package/dist/automatic-daemon-installer.js +44 -24
  3. package/dist/automatic-install-target.js +34 -9
  4. package/dist/computer-cli.js +43 -14
  5. package/dist/computer-profile.js +15 -2
  6. package/dist/computer-service.js +26 -5
  7. package/dist/config.js +28 -8
  8. package/dist/control-plane-url.js +3 -9
  9. package/dist/daemon-installation-lease.js +7 -4
  10. package/dist/daemon-local-home.js +108 -0
  11. package/dist/daemon-migration-controller.js +15 -4
  12. package/dist/daemon-registry.js +20 -0
  13. package/dist/daemon-update-controller.js +1 -0
  14. package/dist/daemon-update-eligibility.js +17 -7
  15. package/dist/daemon-updater.js +26 -26
  16. package/dist/execution-runner.js +7 -7
  17. package/dist/host-execution-coordinator.js +4 -3
  18. package/dist/local-executor.js +23 -17
  19. package/dist/local-memory/runtime.js +5 -3
  20. package/dist/machine-info.js +3 -0
  21. package/dist/main.js +13 -2
  22. package/dist/managed-service-diagnostics.js +1 -0
  23. package/dist/managed-service-registry.js +12 -7
  24. package/dist/managed-service-startup.js +4 -1
  25. package/dist/npm-command.js +9 -0
  26. package/dist/project-skills/controller.js +6 -71
  27. package/dist/project-skills/execution-adapter.js +7 -0
  28. package/dist/project-skills/execution-projection.js +163 -0
  29. package/dist/project-skills/initialized-reconciler.js +10 -8
  30. package/dist/project-skills/legacy-cleanup.js +224 -0
  31. package/dist/project-skills/machine-hello-publisher.js +20 -0
  32. package/dist/project-skills/reconciler.js +114 -500
  33. package/dist/project-skills/resolution-records.js +63 -0
  34. package/dist/project-skills/runtime-launch.js +0 -42
  35. package/dist/project-skills/runtime-warning.js +1 -3
  36. package/dist/project-skills/scanner.js +1 -17
  37. package/dist/project-skills/startup-cleanup.js +24 -0
  38. package/dist/runner.js +2 -1
  39. package/dist/serve.js +49 -49
  40. package/dist/slog.js +14 -1
  41. package/dist/storage-control.js +12 -1
  42. package/dist/storage-identity.js +15 -1
  43. package/dist/storage-migration-controller.js +25 -4
  44. package/dist/storage-ownership.js +55 -0
  45. package/dist/storage-runtime-controller.js +29 -7
  46. package/dist/storage-runtime-readiness.js +26 -4
  47. package/dist/storage-runtime.js +18 -2
  48. package/dist/storage-status-delivery.js +4 -0
  49. package/dist/storage-status-replay.js +16 -1
  50. package/dist/storage-worker-executor.js +1 -0
  51. package/dist/storage-worker-operation.js +4 -0
  52. package/package.json +1 -1
  53. package/dist/project-skills/capability.js +0 -109
  54. package/dist/project-skills/controller-convergence.js +0 -57
  55. package/dist/project-skills/projection-set-switch.js +0 -419
  56. package/dist/project-skills/projection-state-domain.js +0 -170
  57. package/dist/project-skills/projection-state-store.js +0 -842
  58. package/dist/project-skills/projection-state-transaction.js +0 -322
  59. package/dist/project-skills/projection-state.js +0 -3
  60. package/dist/project-skills/runtime-root-bootstrap.js +0 -47
  61. package/dist/project-skills/runtime-root-domain.js +0 -268
  62. package/dist/project-skills/runtime-root-gc.js +0 -293
  63. package/dist/project-skills/runtime-root-lease-artifact.js +0 -46
  64. package/dist/project-skills/runtime-root-leases.js +0 -487
  65. package/dist/project-skills/runtime-root-source-identity.js +0 -60
  66. package/dist/project-skills/runtime-root-startup.js +0 -49
  67. package/dist/project-skills/runtime-root-state-artifact-domain.js +0 -143
  68. package/dist/project-skills/runtime-root-state-index.js +0 -356
  69. package/dist/project-skills/runtime-root-store.js +0 -722
  70. package/dist/project-skills/serve-capability.js +0 -28
  71. package/dist/project-skills/serve-startup.js +0 -37
@@ -0,0 +1,55 @@
1
+ import { createHash } from "node:crypto";
2
+ import { lstat, readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { z } from "zod";
5
+ const FingerprintSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/u);
6
+ const StorageOwnershipClaimSchema = z.object({
7
+ version: z.literal(1),
8
+ machineId: z.string().uuid(),
9
+ workspaceId: z.string().uuid(),
10
+ environment: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,31}$/u),
11
+ volumeFingerprint: FingerprintSchema,
12
+ workspaceRootFingerprint: FingerprintSchema,
13
+ ownerEpoch: z.number().int().positive().safe(),
14
+ claimFingerprint: FingerprintSchema,
15
+ }).strict();
16
+ function claimFingerprint(identity) {
17
+ const digest = createHash("sha256").update([
18
+ "nowwork-storage-ownership-v1",
19
+ identity.machineId,
20
+ identity.workspaceId,
21
+ identity.environment,
22
+ identity.volumeFingerprint,
23
+ identity.workspaceRootFingerprint,
24
+ String(identity.ownerEpoch),
25
+ ].join("\0")).digest("hex");
26
+ return `sha256:${digest}`;
27
+ }
28
+ export function createStorageOwnershipClaim(input) {
29
+ const parsed = StorageOwnershipClaimSchema.omit({ version: true, claimFingerprint: true }).parse(input);
30
+ const identity = Object.freeze({
31
+ ...parsed,
32
+ machineId: parsed.machineId.toLowerCase(),
33
+ workspaceId: parsed.workspaceId.toLowerCase(),
34
+ environment: parsed.environment.toLowerCase(),
35
+ });
36
+ return Object.freeze({ ...identity, claimFingerprint: claimFingerprint(identity) });
37
+ }
38
+ export function storageOwnershipClaimPath(nowworkHome) {
39
+ return join(nowworkHome, "storage-layout.json");
40
+ }
41
+ export async function readStorageOwnershipClaim(nowworkHome) {
42
+ const path = storageOwnershipClaimPath(nowworkHome);
43
+ const metadata = await lstat(path);
44
+ if (!metadata.isFile())
45
+ throw new Error("storage ownership claim must be a file");
46
+ if (metadata.size > 16 * 1024)
47
+ throw new Error("storage ownership claim file is too large");
48
+ const parsed = StorageOwnershipClaimSchema.parse(JSON.parse(await readFile(path, "utf8")));
49
+ const { version: _version, claimFingerprint: reportedFingerprint, ...identity } = parsed;
50
+ const expected = createStorageOwnershipClaim(identity);
51
+ if (reportedFingerprint !== expected.claimFingerprint) {
52
+ throw new Error("storage ownership claim fingerprint is invalid");
53
+ }
54
+ return expected;
55
+ }
@@ -23,9 +23,10 @@ function deterministicUuid(value) {
23
23
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
24
24
  }
25
25
  function storageId(input, workspaceId) {
26
- return `storage_${createHash("sha256").update([
27
- input.serverUrl.replace(/\/+$/u, ""), input.profile, workspaceId, input.nowworkHome,
28
- ].join("\0")).digest("hex").slice(0, 32)}`;
26
+ if (input.ownership.workspaceId !== workspaceId || input.ownership.environment !== input.environment) {
27
+ throw new Error("Storage ownership claim does not match the migration identity");
28
+ }
29
+ return `storage_${input.ownership.claimFingerprint.slice("sha256:".length)}`;
29
30
  }
30
31
  function installationBase(input, workspaceId, migrationId, createdAt) {
31
32
  const layout = storageLayout({
@@ -102,6 +103,7 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
102
103
  ...injected,
103
104
  };
104
105
  let workspaceId = null;
106
+ let machineId = null;
105
107
  let identity;
106
108
  let gate = null;
107
109
  let sendStatus = null;
@@ -194,6 +196,10 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
194
196
  targetWorkspaceRoot: target.workspaceRoot,
195
197
  backupsRoot: input.local.backupsRoot,
196
198
  backupArtifacts: input.backupArtifacts.map((artifact) => ({ ...artifact })),
199
+ ownership: {
200
+ ownerEpoch: input.ownership.ownerEpoch,
201
+ ownershipClaimFingerprint: input.ownership.claimFingerprint,
202
+ },
197
203
  acceptedWarnings: [...input.policy.acceptedWarnings],
198
204
  },
199
205
  service: {
@@ -229,6 +235,10 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
229
235
  profile: input.profile,
230
236
  sourceStorageFingerprint,
231
237
  targetStorageFingerprint: identity.storageIdFingerprint,
238
+ ownership: {
239
+ ownerEpoch: input.ownership.ownerEpoch,
240
+ ownershipClaimFingerprint: input.ownership.claimFingerprint,
241
+ },
232
242
  runDoctor: async (migrationId) => dependencies.runDoctor(await doctorInput(migrationId)),
233
243
  runMigration: async (doctor) => {
234
244
  await gate.closeAndFlush();
@@ -264,6 +274,7 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
264
274
  environment: operation.environment,
265
275
  workspaceId: operation.workspaceId,
266
276
  profile: operation.profile,
277
+ ...(operation.migration.ownership === undefined ? {} : operation.migration.ownership),
267
278
  }, sendStatus);
268
279
  if (outcome.reopenAdmission)
269
280
  gate.reopen();
@@ -285,20 +296,31 @@ export function createStorageMigrationRuntimeController(rawInput, injected = {})
285
296
  };
286
297
  const bindServerIdentity = (server) => {
287
298
  const nextWorkspaceId = UUID.parse(server.workspaceId).toLowerCase();
299
+ const nextMachineId = UUID.parse(server.machineId).toLowerCase();
288
300
  if (workspaceId !== null && workspaceId !== nextWorkspaceId) {
289
301
  throw new Error("Storage migration server workspace identity changed");
290
302
  }
291
- workspaceId = nextWorkspaceId;
303
+ if (machineId !== null && machineId !== nextMachineId) {
304
+ throw new Error("Storage migration server machine identity changed");
305
+ }
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
+ }
292
311
  const createdAt = dependencies.now().toISOString();
293
- const installation = installationBase(input, workspaceId, undefined, createdAt);
312
+ const installation = installationBase(input, nextWorkspaceId, undefined, createdAt);
294
313
  const layout = storageLayout({
295
314
  nowworkHome: input.nowworkHome,
296
315
  environment: input.environment,
297
- workspaceId,
316
+ workspaceId: nextWorkspaceId,
298
317
  runtimeBase: input.local.runtimeBase,
299
318
  platform: input.platform,
300
319
  });
301
- identity = storageHelloIdentity({ installation, layout });
320
+ const nextIdentity = storageHelloIdentity({ installation, layout, ownership: input.ownership });
321
+ workspaceId = nextWorkspaceId;
322
+ machineId = nextMachineId;
323
+ identity = nextIdentity;
302
324
  ensureControl();
303
325
  drainPendingControlMessages();
304
326
  };
@@ -5,10 +5,12 @@ import { buildServiceSpec } from "./computer-service.js";
5
5
  import { profilePath } from "./computer-profile.js";
6
6
  import { assertManagedServiceIdentity } from "./managed-service-lifecycle.js";
7
7
  import { managedServiceRegistryPath, readManagedServiceRegistry, } from "./managed-service-registry.js";
8
- import { resolveLocalStorageRoots } from "./storage-config.js";
8
+ import { installationsPath, resolveLocalStorageRoots } from "./storage-config.js";
9
9
  import { readStorageMigrationPolicy } from "./storage-migration-policy.js";
10
10
  import { createStorageMigrationRuntimeController, } from "./storage-runtime-controller.js";
11
11
  import { resolveEnvTag, resolveNowworkHome } from "./storage-layout.js";
12
+ import { readStorageOwnershipClaim } from "./storage-ownership.js";
13
+ import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
12
14
  const notReady = (reason) => Object.freeze({ ready: false, reason });
13
15
  function canonicalPath(value) {
14
16
  const absolute = resolve(value);
@@ -73,6 +75,12 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
73
75
  serverUrl: z.string().url().parse(rawInput.serverUrl),
74
76
  platform: rawInput.platform,
75
77
  };
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
+ });
76
84
  let nowworkHome;
77
85
  let environment;
78
86
  try {
@@ -106,11 +114,12 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
106
114
  assertManagedServiceIdentity,
107
115
  readPolicy: readStorageMigrationPolicy,
108
116
  createController: createStorageMigrationRuntimeController,
117
+ readOwnership: readStorageOwnershipClaim,
109
118
  ...injected,
110
119
  };
111
120
  let sourceRecord;
112
121
  try {
113
- const records = await dependencies.readManagedServices({ userHome: input.userHome });
122
+ const records = await dependencies.readManagedServices({ userHome: input.userHome, localHome });
114
123
  const found = findSourceRecord(records, input);
115
124
  if (found === null)
116
125
  return notReady("source_service_record_missing");
@@ -132,7 +141,7 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
132
141
  const assertSourceIdentity = () => dependencies.assertManagedServiceIdentity({
133
142
  spec: sourceSpec,
134
143
  record: sourceRecord,
135
- registry: { userHome: input.userHome },
144
+ registry: { userHome: input.userHome, localHome },
136
145
  });
137
146
  try {
138
147
  await assertSourceIdentity();
@@ -153,6 +162,17 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
153
162
  catch {
154
163
  return notReady("migration_policy_invalid");
155
164
  }
165
+ let ownership;
166
+ try {
167
+ ownership = await dependencies.readOwnership(nowworkHome);
168
+ }
169
+ catch (error) {
170
+ return notReady(error.code === "ENOENT"
171
+ ? "storage_ownership_claim_missing"
172
+ : "storage_ownership_claim_invalid");
173
+ }
174
+ if (ownership.environment !== environment)
175
+ return notReady("storage_ownership_claim_invalid");
156
176
  const local = resolveLocalStorageRoots({
157
177
  platform: input.platform,
158
178
  userHome: input.userHome,
@@ -169,6 +189,7 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
169
189
  sourceRecord,
170
190
  local,
171
191
  policy,
192
+ ownership,
172
193
  serviceEnvironment: {
173
194
  NOWWORK_HOME: nowworkHome,
174
195
  NOWWORK_ENV_TAG: environment,
@@ -177,7 +198,8 @@ export async function probeStorageMigrationRuntime(rawInput, injected = {}) {
177
198
  backupArtifacts: [
178
199
  { name: "profile.json", path: profilePath(input.profile, input.currentDaemonHome) },
179
200
  { name: "service-descriptor", path: sourceRecord.descriptorPath },
180
- { name: "managed-service-registry.json", path: managedServiceRegistryPath(input.userHome) },
201
+ { name: "managed-service-registry.json", path: managedServiceRegistryPath(input.userHome, localHome) },
202
+ { name: "installations.json", path: installationsPath(local.configRoot) },
181
203
  ],
182
204
  }, { assertSourceIdentity });
183
205
  return Object.freeze({ ready: true, runtime });
@@ -1,6 +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
5
  const canonicalServerUrl = (value) => value.replace(/\/+$/u, "");
5
6
  export function resolveStorageRuntime(input) {
6
7
  const installation = findInstallationForProfile(input.records, input.profile);
@@ -24,7 +25,11 @@ export function resolveStorageRuntime(input) {
24
25
  return Object.freeze({
25
26
  installation,
26
27
  layout,
27
- identity: storageHelloIdentity({ installation, layout }),
28
+ identity: storageHelloIdentity({
29
+ installation,
30
+ layout,
31
+ ...(input.ownership === undefined ? {} : { ownership: input.ownership }),
32
+ }),
28
33
  local,
29
34
  });
30
35
  }
@@ -35,5 +40,16 @@ export async function loadStorageRuntime(input) {
35
40
  environment: input.environment,
36
41
  });
37
42
  const records = await readInstallations({ configRoot: local.configRoot });
38
- return resolveStorageRuntime({ ...input, records });
43
+ const baseline = resolveStorageRuntime({ ...input, records });
44
+ if (baseline === null)
45
+ return null;
46
+ let ownership;
47
+ try {
48
+ ownership = await readStorageOwnershipClaim(baseline.layout.persistentRoot);
49
+ }
50
+ catch (error) {
51
+ if (error.code !== "ENOENT")
52
+ throw error;
53
+ }
54
+ return ownership === undefined ? baseline : resolveStorageRuntime({ ...input, records, ownership });
39
55
  }
@@ -18,6 +18,8 @@ const DeliveredFrameSchema = z.object({
18
18
  environment: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,31}$/u),
19
19
  workspaceId: z.string().uuid(),
20
20
  profile: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,47}$/u),
21
+ ownerEpoch: z.number().int().positive().safe().optional(),
22
+ ownershipClaimFingerprint: z.string().regex(/^sha256:[0-9a-f]{64}$/u).optional(),
21
23
  sourceStorageFingerprint: z.string().optional(),
22
24
  targetStorageFingerprint: z.string().optional(),
23
25
  manifestDigest: z.string().optional(),
@@ -45,6 +47,8 @@ export function storageStatusEventId(frame) {
45
47
  frame.environment,
46
48
  frame.workspaceId,
47
49
  frame.profile,
50
+ frame.ownerEpoch ?? null,
51
+ frame.ownershipClaimFingerprint ?? null,
48
52
  frame.sourceStorageFingerprint ?? null,
49
53
  frame.targetStorageFingerprint ?? null,
50
54
  frame.manifestDigest ?? null,
@@ -35,7 +35,10 @@ export function storageMigrationStatusReplay(receipt, expected) {
35
35
  if (expected !== undefined && (receipt.migrationId !== expected.migrationId
36
36
  || receipt.environment !== expected.environment
37
37
  || receipt.workspaceId !== expected.workspaceId
38
- || receipt.profile !== expected.profile)) {
38
+ || receipt.profile !== expected.profile
39
+ || (expected.ownerEpoch !== undefined && receipt.ownerEpoch !== expected.ownerEpoch)
40
+ || (expected.ownershipClaimFingerprint !== undefined
41
+ && receipt.ownershipClaimFingerprint !== expected.ownershipClaimFingerprint))) {
39
42
  throw new Error("storage migration receipt identity mismatch");
40
43
  }
41
44
  return Object.freeze(storageMigrationReplayStatuses(receipt).map((status) => Object.freeze({
@@ -45,6 +48,10 @@ export function storageMigrationStatusReplay(receipt, expected) {
45
48
  environment: receipt.environment,
46
49
  workspaceId: receipt.workspaceId,
47
50
  profile: receipt.profile,
51
+ ...(receipt.ownerEpoch === undefined ? {} : { ownerEpoch: receipt.ownerEpoch }),
52
+ ...(receipt.ownershipClaimFingerprint === undefined ? {} : {
53
+ ownershipClaimFingerprint: receipt.ownershipClaimFingerprint,
54
+ }),
48
55
  ...(status === "verified" ? { finalManifestDigest: receipt.manifestDigest } : {}),
49
56
  ...(status === "failed"
50
57
  ? {
@@ -98,6 +105,10 @@ export async function monitorStorageMigrationStatus(input, send, injected = {})
98
105
  environment: input.environment,
99
106
  workspaceId: input.workspaceId,
100
107
  profile: input.profile,
108
+ ...(input.ownerEpoch === undefined ? {} : { ownerEpoch: input.ownerEpoch }),
109
+ ...(input.ownershipClaimFingerprint === undefined ? {} : {
110
+ ownershipClaimFingerprint: input.ownershipClaimFingerprint,
111
+ }),
101
112
  errorCode: workerFailure.errorCode,
102
113
  errorMessage: workerFailure.errorMessage,
103
114
  ...(receipt?.sourceStopAttempted === false || receipt === null ? { reopenAdmission: true } : {}),
@@ -114,6 +125,10 @@ export async function monitorStorageMigrationStatus(input, send, injected = {})
114
125
  environment: input.environment,
115
126
  workspaceId: input.workspaceId,
116
127
  profile: input.profile,
128
+ ...(input.ownerEpoch === undefined ? {} : { ownerEpoch: input.ownerEpoch }),
129
+ ...(input.ownershipClaimFingerprint === undefined ? {} : {
130
+ ownershipClaimFingerprint: input.ownershipClaimFingerprint,
131
+ }),
117
132
  errorCode: "worker_heartbeat_stale_recovery_required",
118
133
  errorMessage: "storage migration worker heartbeat expired; explicit recovery is required",
119
134
  ...(receipt?.sourceStopAttempted === false || receipt === null ? { reopenAdmission: true } : {}),
@@ -367,6 +367,7 @@ export async function executeStorageMigrationWorker(handoffPath, injected = {})
367
367
  targetWorkspaceRoot: operation.migration.targetWorkspaceRoot,
368
368
  backupsRoot: operation.migration.backupsRoot,
369
369
  backupArtifacts: operation.migration.backupArtifacts,
370
+ ...(operation.migration.ownership === undefined ? {} : { ownership: operation.migration.ownership }),
370
371
  acceptedWarnings: operation.migration.acceptedWarnings,
371
372
  onStatus: () => assertHeartbeatHealthy(),
372
373
  assertHealthy: assertHeartbeatHealthy,
@@ -118,6 +118,10 @@ const OperationSchema = z.object({
118
118
  name: z.string().regex(/^[a-z0-9][a-z0-9_.-]{0,127}$/u),
119
119
  path: AbsolutePathSchema,
120
120
  }).strict()).max(32),
121
+ ownership: z.object({
122
+ ownerEpoch: z.number().int().positive().safe(),
123
+ ownershipClaimFingerprint: z.string().regex(/^sha256:[0-9a-f]{64}$/u),
124
+ }).strict().optional(),
121
125
  acceptedWarnings: z.array(StorageMigrationWarningCodeSchema)
122
126
  .max(StorageMigrationWarningCodeSchema.options.length),
123
127
  }).strict(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.77",
3
+ "version": "0.6.79-beta.1",
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
- }