@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.
- package/README.md +22 -0
- package/dist/cloud-report-uploader.js +27 -25
- package/dist/execution-runner.js +6 -3
- package/dist/external-output.js +154 -68
- package/dist/local-execution-prompt.js +19 -25
- package/dist/local-executor.js +16 -44
- package/dist/local-memory/prompt.js +37 -0
- package/dist/local-memory/read-cli.js +232 -0
- package/dist/local-memory-diagnostics.js +79 -12
- package/dist/main.js +6 -2
- package/dist/prompt.js +24 -9
- package/dist/serve.js +17 -11
- package/dist/storage-identity.js +4 -1
- package/dist/storage-ownership.js +93 -5
- package/dist/storage-runtime-controller.js +40 -20
- package/dist/storage-runtime.js +38 -2
- package/dist/storage-serve-adapter.js +22 -5
- package/dist/workspace.js +15 -0
- package/package.json +1 -1
- package/dist/project-skills/capability.js +0 -109
- package/dist/project-skills/controller-convergence.js +0 -57
- package/dist/project-skills/projection-set-switch.js +0 -419
- package/dist/project-skills/projection-state-domain.js +0 -170
- package/dist/project-skills/projection-state-store.js +0 -842
- package/dist/project-skills/projection-state-transaction.js +0 -322
- package/dist/project-skills/projection-state.js +0 -3
- package/dist/project-skills/runtime-root-bootstrap.js +0 -47
- package/dist/project-skills/runtime-root-domain.js +0 -268
- package/dist/project-skills/runtime-root-gc.js +0 -293
- package/dist/project-skills/runtime-root-lease-artifact.js +0 -46
- package/dist/project-skills/runtime-root-leases.js +0 -487
- package/dist/project-skills/runtime-root-source-identity.js +0 -60
- package/dist/project-skills/runtime-root-startup.js +0 -49
- package/dist/project-skills/runtime-root-state-artifact-domain.js +0 -143
- package/dist/project-skills/runtime-root-state-index.js +0 -356
- package/dist/project-skills/runtime-root-store.js +0 -722
- package/dist/project-skills/serve-capability.js +0 -28
- 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
|
|
42
|
-
|
|
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 (
|
|
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_${
|
|
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:
|
|
201
|
-
ownershipClaimFingerprint:
|
|
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:
|
|
240
|
-
ownershipClaimFingerprint:
|
|
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
|
|
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
|
-
|
|
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;
|
package/dist/storage-runtime.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
36
|
+
migrationAdmissionClosed = true;
|
|
27
37
|
await dependencies.waitForExecutionFrames();
|
|
28
38
|
if (dependencies.isBusy()) {
|
|
29
|
-
|
|
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: () => {
|
|
46
|
+
reopen: () => { migrationAdmissionClosed = false; },
|
|
37
47
|
},
|
|
38
48
|
sendStatus: dependencies.sendStatus,
|
|
39
49
|
});
|
|
40
50
|
}
|
|
41
|
-
return Object.freeze({
|
|
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,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
|
-
}
|