@nowcrew/daemon 0.6.19 → 0.6.21
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/dist/atomic-no-replace-rename.js +91 -0
- package/dist/completion-retransmitter-logging.js +16 -0
- package/dist/completion-retransmitter.js +39 -4
- package/dist/control-plane-url.js +4 -2
- package/dist/directory-projection-publication.js +105 -0
- package/dist/directory-projection.js +20 -4
- package/dist/execution-journal.js +40 -4
- package/dist/execution-posix-stop-proof.js +82 -0
- package/dist/execution-runner.js +68 -8
- package/dist/local-executor.js +67 -52
- package/dist/machine-info.js +8 -5
- package/dist/project-skills/capability.js +109 -0
- package/dist/project-skills/controller-convergence.js +57 -0
- package/dist/project-skills/controller.js +80 -24
- package/dist/project-skills/initialized-reconciler.js +4 -4
- package/dist/project-skills/projection-state-domain.js +19 -2
- package/dist/project-skills/projection-state-store.js +3 -2
- package/dist/project-skills/projection-state-transaction.js +5 -1
- package/dist/project-skills/projection-state.js +1 -1
- package/dist/project-skills/reconciler.js +275 -102
- package/dist/project-skills/runtime-launch.js +102 -0
- package/dist/project-skills/runtime-root-bootstrap.js +47 -0
- package/dist/project-skills/runtime-root-domain.js +268 -0
- package/dist/project-skills/runtime-root-gc.js +293 -0
- package/dist/project-skills/runtime-root-lease-artifact.js +46 -0
- package/dist/project-skills/runtime-root-leases.js +487 -0
- package/dist/project-skills/runtime-root-source-identity.js +60 -0
- package/dist/project-skills/runtime-root-startup.js +49 -0
- package/dist/project-skills/runtime-root-state-artifact-domain.js +143 -0
- package/dist/project-skills/runtime-root-state-index.js +356 -0
- package/dist/project-skills/runtime-root-store.js +722 -0
- package/dist/project-skills/serve-capability.js +28 -0
- package/dist/project-skills/serve-startup.js +22 -0
- package/dist/project-skills/types.js +1 -0
- package/dist/runtimes/codex-home-migration-cli.js +26 -0
- package/dist/runtimes/codex-home-migration.js +112 -0
- package/dist/runtimes/codex-home.js +200 -17
- package/dist/serve.js +60 -79
- package/dist/supervised-runtime.js +1 -5
- package/package.json +2 -1
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { chmod, lstat, mkdir, open, opendir, readFile, readlink, readdir, rm, stat, } from "node:fs/promises";
|
|
3
|
+
import { posix, win32 } from "node:path";
|
|
4
|
+
import { durableDirectorySync } from "../atomic-private-write.js";
|
|
5
|
+
import { AtomicNoReplaceRenameError, atomicRenameNoReplace, probeAtomicNoReplaceRenameReadiness, } from "../atomic-no-replace-rename.js";
|
|
6
|
+
import { DirectoryProjectionError, isManagedDirectoryProjectionCopy, projectDirectory, } from "../directory-projection.js";
|
|
7
|
+
import { DirectoryProjectionPublicationError, mapDirectoryProjectionFinalTarget, } from "../directory-projection-publication.js";
|
|
8
|
+
import { readExactDirectoryIdentity, sameExactDirectoryIdentity, } from "../directory-projection-identity.js";
|
|
9
|
+
import { ProjectSkillRuntimeStoreError, createProjectSkillRuntimeRootManifest, createProjectSkillRuntimeRootRecord, isCanonicalProjectSkillRuntimeRootId, parseProjectSkillRuntimeRootManifest, } from "./runtime-root-domain.js";
|
|
10
|
+
import { ProjectSkillProjectionStateError, normalizeProjectSkillResolutionRecords, } from "./projection-state-domain.js";
|
|
11
|
+
import { checkpointRuntimeRootNextSequence, reconstructRuntimeRootState, } from "./runtime-root-state-index.js";
|
|
12
|
+
import { captureResolvedProjectSkillSourceIdentity, sameResolvedProjectSkillSourceIdentity, } from "./runtime-root-source-identity.js";
|
|
13
|
+
const STORE_DIRECTORY_NAME = "project-skill-runtime";
|
|
14
|
+
const ROOTS_DIRECTORY_NAME = "runtime-roots";
|
|
15
|
+
const LEASES_DIRECTORY_NAME = "leases";
|
|
16
|
+
const STATE_INDEX_DIRECTORY_NAME = "state-index";
|
|
17
|
+
const STATE_FILE_NAME = "state.json";
|
|
18
|
+
const MANIFEST_FILE_NAME = "MANIFEST.json";
|
|
19
|
+
const MAX_MANIFEST_BYTES = 256 * 1024;
|
|
20
|
+
const MAX_RUNTIME_ROOT_ENTRIES = 2_048;
|
|
21
|
+
const STAGING_NAME = /^\.([0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})-next-([A-Za-z0-9-]{1,128})$/u;
|
|
22
|
+
const GC_QUARANTINE_NAME = /^\.gc-[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
|
|
23
|
+
/** Stable path owner seam shared by the Runtime-root store and its garbage collector. */
|
|
24
|
+
export const projectSkillRuntimeRootDirectory = (agentRoot, platform = process.platform) => (platform === "win32" ? win32 : posix).join(agentRoot, ".crew", STORE_DIRECTORY_NAME, ROOTS_DIRECTORY_NAME);
|
|
25
|
+
const codeOf = (error) => error.code;
|
|
26
|
+
const isMissing = (error) => codeOf(error) === "ENOENT";
|
|
27
|
+
const isExisting = (error) => codeOf(error) === "EEXIST";
|
|
28
|
+
const fail = (code) => { throw new ProjectSkillRuntimeStoreError(code); };
|
|
29
|
+
const ownedByCurrentUser = (uid) => process.getuid === undefined || uid === BigInt(process.getuid());
|
|
30
|
+
const mapStoreError = (error) => {
|
|
31
|
+
if (error instanceof ProjectSkillRuntimeStoreError)
|
|
32
|
+
throw error;
|
|
33
|
+
if (error instanceof DirectoryProjectionError) {
|
|
34
|
+
if (error.code === "directory_projection_limit_exceeded") {
|
|
35
|
+
return fail("skill_projection_runtime_store_limit_exceeded");
|
|
36
|
+
}
|
|
37
|
+
if (error.code === "directory_projection_artifact_unmanaged"
|
|
38
|
+
|| error.code === "directory_projection_target_unmanaged") {
|
|
39
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
40
|
+
}
|
|
41
|
+
return fail("skill_projection_failed");
|
|
42
|
+
}
|
|
43
|
+
if (error instanceof DirectoryProjectionPublicationError) {
|
|
44
|
+
return fail("skill_projection_failed");
|
|
45
|
+
}
|
|
46
|
+
if (error instanceof AtomicNoReplaceRenameError) {
|
|
47
|
+
return fail("skill_projection_failed");
|
|
48
|
+
}
|
|
49
|
+
if (error instanceof ProjectSkillProjectionStateError) {
|
|
50
|
+
return fail("skill_projection_snapshot_corrupt");
|
|
51
|
+
}
|
|
52
|
+
return fail("skill_projection_failed");
|
|
53
|
+
};
|
|
54
|
+
const immutableDescriptor = (rootId, rootDirectory, path) => Object.freeze({
|
|
55
|
+
rootId,
|
|
56
|
+
rootDirectory,
|
|
57
|
+
codexSkillRoot: path.join(rootDirectory, "runtime", ".claude", "skills"),
|
|
58
|
+
claudeAdditionalDirectory: path.join(rootDirectory, "runtime"),
|
|
59
|
+
});
|
|
60
|
+
const sameRecord = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
61
|
+
const recordFromManifest = (manifest) => createProjectSkillRuntimeRootRecord({
|
|
62
|
+
rootId: manifest.rootId,
|
|
63
|
+
publishedSequence: manifest.publishedSequence,
|
|
64
|
+
materializationRevision: manifest.materializationRevision,
|
|
65
|
+
directoryIdentity: manifest.directoryIdentity,
|
|
66
|
+
bindingDigest: manifest.bindingDigest,
|
|
67
|
+
resolutionDigest: manifest.resolutionDigest,
|
|
68
|
+
});
|
|
69
|
+
const realOwnedDirectory = async (directory) => {
|
|
70
|
+
let info;
|
|
71
|
+
try {
|
|
72
|
+
info = await lstat(directory, { bigint: true });
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
76
|
+
}
|
|
77
|
+
if (!info.isDirectory() || info.isSymbolicLink() || !ownedByCurrentUser(info.uid)) {
|
|
78
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
79
|
+
}
|
|
80
|
+
return Object.freeze({
|
|
81
|
+
dev: String(info.dev),
|
|
82
|
+
ino: String(info.ino),
|
|
83
|
+
birthtimeNs: String(info.birthtimeNs),
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
const ensurePrivateDirectory = async (directory, parent, syncDirectory) => {
|
|
87
|
+
let created = false;
|
|
88
|
+
try {
|
|
89
|
+
await mkdir(directory, { mode: 0o700 });
|
|
90
|
+
created = true;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (!isExisting(error))
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
await realOwnedDirectory(directory);
|
|
97
|
+
await chmod(directory, 0o700).catch(() => fail("skill_projection_runtime_root_unmanaged"));
|
|
98
|
+
if (created)
|
|
99
|
+
await syncDirectory(parent);
|
|
100
|
+
};
|
|
101
|
+
const boundedNames = async (directory) => {
|
|
102
|
+
let handle;
|
|
103
|
+
try {
|
|
104
|
+
handle = await opendir(directory);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
108
|
+
}
|
|
109
|
+
const names = [];
|
|
110
|
+
try {
|
|
111
|
+
for (;;) {
|
|
112
|
+
const entry = await handle.read();
|
|
113
|
+
if (entry === null)
|
|
114
|
+
break;
|
|
115
|
+
names.push(entry.name);
|
|
116
|
+
if (names.length > MAX_RUNTIME_ROOT_ENTRIES) {
|
|
117
|
+
return fail("skill_projection_runtime_store_limit_exceeded");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (error instanceof ProjectSkillRuntimeStoreError)
|
|
123
|
+
throw error;
|
|
124
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
await handle.close().catch(() => undefined);
|
|
128
|
+
}
|
|
129
|
+
return Object.freeze(names.sort());
|
|
130
|
+
};
|
|
131
|
+
const readBoundedOwnedFileSnapshot = async (filePath, maximumBytes) => {
|
|
132
|
+
let before;
|
|
133
|
+
try {
|
|
134
|
+
before = await lstat(filePath, { bigint: true });
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (isMissing(error))
|
|
138
|
+
return null;
|
|
139
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
140
|
+
}
|
|
141
|
+
if (!before.isFile() || before.isSymbolicLink() || !ownedByCurrentUser(before.uid)
|
|
142
|
+
|| before.size > BigInt(maximumBytes)) {
|
|
143
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
144
|
+
}
|
|
145
|
+
let handle = null;
|
|
146
|
+
try {
|
|
147
|
+
handle = await open(filePath, "r");
|
|
148
|
+
const opened = await handle.stat({ bigint: true });
|
|
149
|
+
if (!opened.isFile() || opened.isSymbolicLink() || !ownedByCurrentUser(opened.uid)
|
|
150
|
+
|| opened.size > BigInt(maximumBytes)
|
|
151
|
+
|| opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size) {
|
|
152
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
153
|
+
}
|
|
154
|
+
const raw = await handle.readFile("utf8");
|
|
155
|
+
const after = await handle.stat({ bigint: true });
|
|
156
|
+
const pathAfter = await lstat(filePath, { bigint: true });
|
|
157
|
+
if (Buffer.byteLength(raw, "utf8") !== Number(after.size)
|
|
158
|
+
|| after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size
|
|
159
|
+
|| pathAfter.dev !== after.dev || pathAfter.ino !== after.ino || pathAfter.size !== after.size) {
|
|
160
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
161
|
+
}
|
|
162
|
+
return Object.freeze({
|
|
163
|
+
identity: Object.freeze({
|
|
164
|
+
dev: after.dev,
|
|
165
|
+
ino: after.ino,
|
|
166
|
+
size: after.size,
|
|
167
|
+
mtimeNs: after.mtimeNs,
|
|
168
|
+
}),
|
|
169
|
+
raw,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (error instanceof ProjectSkillRuntimeStoreError)
|
|
174
|
+
throw error;
|
|
175
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
await handle?.close().catch(() => undefined);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const readBoundedOwnedFile = async (filePath, maximumBytes) => (await readBoundedOwnedFileSnapshot(filePath, maximumBytes))?.raw ?? null;
|
|
182
|
+
const writeNewDurableFile = async (filePath, raw, parent, syncDirectory) => {
|
|
183
|
+
let handle = null;
|
|
184
|
+
try {
|
|
185
|
+
handle = await open(filePath, "wx", 0o600);
|
|
186
|
+
await handle.writeFile(raw, "utf8");
|
|
187
|
+
await handle.sync();
|
|
188
|
+
await handle.close();
|
|
189
|
+
handle = null;
|
|
190
|
+
await syncDirectory(parent);
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
await handle?.close().catch(() => undefined);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const sameDirectoryIdentity = (left, right) => left.dev === right.dev && left.ino === right.ino && left.birthtimeNs === right.birthtimeNs;
|
|
197
|
+
const exactNames = async (directory, expected) => {
|
|
198
|
+
const actual = (await readdir(directory)).sort();
|
|
199
|
+
if (actual.length !== expected.length || actual.some((name, index) => name !== expected[index])) {
|
|
200
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
const syncTree = async (root, path, syncDirectory) => {
|
|
204
|
+
const pending = [{ path: root, visited: false }];
|
|
205
|
+
let entries = 0;
|
|
206
|
+
while (pending.length > 0) {
|
|
207
|
+
const current = pending.pop();
|
|
208
|
+
const info = await lstat(current.path);
|
|
209
|
+
if (info.isSymbolicLink())
|
|
210
|
+
continue;
|
|
211
|
+
if (info.isDirectory() && !current.visited) {
|
|
212
|
+
pending.push({ path: current.path, visited: true });
|
|
213
|
+
for (const name of await readdir(current.path)) {
|
|
214
|
+
entries += 1;
|
|
215
|
+
if (entries > MAX_RUNTIME_ROOT_ENTRIES * 100) {
|
|
216
|
+
return fail("skill_projection_runtime_store_limit_exceeded");
|
|
217
|
+
}
|
|
218
|
+
pending.push({ path: path.join(current.path, name), visited: false });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else if (info.isDirectory()) {
|
|
222
|
+
await syncDirectory(current.path);
|
|
223
|
+
}
|
|
224
|
+
else if (info.isFile()) {
|
|
225
|
+
const handle = await open(current.path, "r");
|
|
226
|
+
try {
|
|
227
|
+
await handle.sync();
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
await handle.close();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
const makeTreeWritable = async (root, path) => {
|
|
239
|
+
const pending = [root];
|
|
240
|
+
let entries = 0;
|
|
241
|
+
while (pending.length > 0) {
|
|
242
|
+
const current = pending.pop();
|
|
243
|
+
const info = await lstat(current);
|
|
244
|
+
if (info.isSymbolicLink())
|
|
245
|
+
continue;
|
|
246
|
+
if (!info.isDirectory()) {
|
|
247
|
+
if (!info.isFile())
|
|
248
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
249
|
+
await chmod(current, 0o600);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
await chmod(current, 0o700);
|
|
253
|
+
for (const name of await readdir(current)) {
|
|
254
|
+
entries += 1;
|
|
255
|
+
if (entries > MAX_RUNTIME_ROOT_ENTRIES * 100) {
|
|
256
|
+
return fail("skill_projection_runtime_store_limit_exceeded");
|
|
257
|
+
}
|
|
258
|
+
pending.push(path.join(current, name));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
export function createProjectSkillRuntimeRootStore(options = {}) {
|
|
263
|
+
const platform = options.platform ?? process.platform;
|
|
264
|
+
const path = platform === "win32" ? win32 : posix;
|
|
265
|
+
const syncDirectory = platform === "win32"
|
|
266
|
+
? options.directorySync ?? (async () => undefined)
|
|
267
|
+
: options.directorySync ?? durableDirectorySync;
|
|
268
|
+
const project = options.project ?? projectDirectory;
|
|
269
|
+
const atomicRename = options.atomicRename ?? atomicRenameNoReplace;
|
|
270
|
+
const atomicRenamePrimitiveReadinessProbe = options.atomicRenamePrimitiveReadinessProbe
|
|
271
|
+
?? probeAtomicNoReplaceRenameReadiness;
|
|
272
|
+
const stateIndexContext = (context) => Object.freeze({
|
|
273
|
+
indexDirectory: context.stateIndexDirectory,
|
|
274
|
+
statePath: context.statePath,
|
|
275
|
+
path,
|
|
276
|
+
syncDirectory,
|
|
277
|
+
atomicRename,
|
|
278
|
+
...(options.stateIndexRandomId === undefined ? {} : { randomId: options.stateIndexRandomId }),
|
|
279
|
+
hooks: Object.freeze({
|
|
280
|
+
...(options.afterLegacyStateValidatedBeforeImport === undefined ? {} : {
|
|
281
|
+
afterLegacyStateValidatedBeforeImport: options.afterLegacyStateValidatedBeforeImport,
|
|
282
|
+
}),
|
|
283
|
+
...(options.afterStateIndexStagingDurableBeforeRename === undefined ? {} : {
|
|
284
|
+
afterStagingDurableBeforeRename: options.afterStateIndexStagingDurableBeforeRename,
|
|
285
|
+
}),
|
|
286
|
+
...(options.afterStateIndexRenamedBeforeParentSync === undefined ? {} : {
|
|
287
|
+
afterRenamedBeforeParentSync: options.afterStateIndexRenamedBeforeParentSync,
|
|
288
|
+
}),
|
|
289
|
+
...(options.afterStateIndexParentSyncedBeforeRevalidate === undefined ? {} : {
|
|
290
|
+
afterParentSyncedBeforeRevalidate: options.afterStateIndexParentSyncedBeforeRevalidate,
|
|
291
|
+
}),
|
|
292
|
+
}),
|
|
293
|
+
});
|
|
294
|
+
const contextFor = async (agentRoot) => {
|
|
295
|
+
await realOwnedDirectory(agentRoot);
|
|
296
|
+
const crewDirectory = path.join(agentRoot, ".crew");
|
|
297
|
+
const storeDirectory = path.join(crewDirectory, STORE_DIRECTORY_NAME);
|
|
298
|
+
const rootsDirectory = path.join(storeDirectory, ROOTS_DIRECTORY_NAME);
|
|
299
|
+
const leasesDirectory = path.join(storeDirectory, LEASES_DIRECTORY_NAME);
|
|
300
|
+
const stateIndexDirectory = path.join(storeDirectory, STATE_INDEX_DIRECTORY_NAME);
|
|
301
|
+
await ensurePrivateDirectory(crewDirectory, agentRoot, syncDirectory);
|
|
302
|
+
await ensurePrivateDirectory(storeDirectory, crewDirectory, syncDirectory);
|
|
303
|
+
await ensurePrivateDirectory(rootsDirectory, storeDirectory, syncDirectory);
|
|
304
|
+
await ensurePrivateDirectory(leasesDirectory, storeDirectory, syncDirectory);
|
|
305
|
+
await ensurePrivateDirectory(stateIndexDirectory, storeDirectory, syncDirectory);
|
|
306
|
+
const allowedStoreEntries = new Set([
|
|
307
|
+
ROOTS_DIRECTORY_NAME,
|
|
308
|
+
LEASES_DIRECTORY_NAME,
|
|
309
|
+
STATE_INDEX_DIRECTORY_NAME,
|
|
310
|
+
STATE_FILE_NAME,
|
|
311
|
+
]);
|
|
312
|
+
for (const name of await boundedNames(storeDirectory)) {
|
|
313
|
+
if (!allowedStoreEntries.has(name)) {
|
|
314
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return Object.freeze({
|
|
318
|
+
storeDirectory,
|
|
319
|
+
rootsDirectory,
|
|
320
|
+
leasesDirectory,
|
|
321
|
+
stateIndexDirectory,
|
|
322
|
+
statePath: path.join(storeDirectory, STATE_FILE_NAME),
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
|
+
const validateProjectedChildren = async (rootDirectory, manifest, location) => {
|
|
326
|
+
await exactNames(rootDirectory, [MANIFEST_FILE_NAME, "runtime"]);
|
|
327
|
+
const runtime = path.join(rootDirectory, "runtime");
|
|
328
|
+
const claude = path.join(runtime, ".claude");
|
|
329
|
+
const skills = path.join(claude, "skills");
|
|
330
|
+
await realOwnedDirectory(runtime);
|
|
331
|
+
await realOwnedDirectory(claude);
|
|
332
|
+
await realOwnedDirectory(skills);
|
|
333
|
+
await exactNames(runtime, [".claude"]);
|
|
334
|
+
await exactNames(claude, ["skills"]);
|
|
335
|
+
const materialized = manifest.projectionModes.filter(({ mode }) => mode !== "missing");
|
|
336
|
+
const uniqueSkillNames = new Set(materialized.map(({ skillName }) => skillName));
|
|
337
|
+
if (uniqueSkillNames.size !== materialized.length)
|
|
338
|
+
return fail("skill_projection_snapshot_corrupt");
|
|
339
|
+
await exactNames(skills, [...uniqueSkillNames].sort());
|
|
340
|
+
for (const projection of manifest.projectionModes) {
|
|
341
|
+
const child = path.join(skills, projection.skillName);
|
|
342
|
+
if (projection.mode === "missing") {
|
|
343
|
+
try {
|
|
344
|
+
await lstat(child);
|
|
345
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
if (error instanceof ProjectSkillRuntimeStoreError)
|
|
349
|
+
throw error;
|
|
350
|
+
if (!isMissing(error))
|
|
351
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
352
|
+
}
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const info = await lstat(child, { bigint: true }).catch(() => fail("skill_projection_runtime_root_unmanaged"));
|
|
356
|
+
if (!ownedByCurrentUser(info.uid))
|
|
357
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
358
|
+
if (projection.mode === "symlink" || projection.mode === "junction") {
|
|
359
|
+
if (!info.isSymbolicLink())
|
|
360
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
361
|
+
await readlink(child).catch(() => fail("skill_projection_runtime_root_unmanaged"));
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
if (!info.isDirectory() || info.isSymbolicLink()) {
|
|
365
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
366
|
+
}
|
|
367
|
+
if (platform === "win32" || options.validateCopy !== undefined) {
|
|
368
|
+
const finalChild = location === "final"
|
|
369
|
+
? child
|
|
370
|
+
: mapDirectoryProjectionFinalTarget(child, {
|
|
371
|
+
stagingRoot: rootDirectory,
|
|
372
|
+
finalRoot: path.join(path.dirname(rootDirectory), manifest.rootId),
|
|
373
|
+
}, platform);
|
|
374
|
+
const managed = options.validateCopy === undefined
|
|
375
|
+
? location === "final"
|
|
376
|
+
? await isManagedDirectoryProjectionCopy(child, platform, options.projectionOptions)
|
|
377
|
+
: await isManagedDirectoryProjectionCopy(child, platform, {
|
|
378
|
+
...options.projectionOptions,
|
|
379
|
+
finalTarget: finalChild,
|
|
380
|
+
})
|
|
381
|
+
: await options.validateCopy(child, location === "final" ? undefined : finalChild);
|
|
382
|
+
if (!managed)
|
|
383
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
const validateRoot = async (rootDirectory, expectedRootId, location) => {
|
|
389
|
+
const identity = await realOwnedDirectory(rootDirectory);
|
|
390
|
+
const raw = await readBoundedOwnedFile(path.join(rootDirectory, MANIFEST_FILE_NAME), MAX_MANIFEST_BYTES);
|
|
391
|
+
if (raw === null)
|
|
392
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
393
|
+
let candidate;
|
|
394
|
+
try {
|
|
395
|
+
candidate = JSON.parse(raw);
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
return fail("skill_projection_snapshot_corrupt");
|
|
399
|
+
}
|
|
400
|
+
const manifest = parseProjectSkillRuntimeRootManifest(candidate);
|
|
401
|
+
if (manifest.rootId !== expectedRootId || !sameDirectoryIdentity(manifest.directoryIdentity, identity)) {
|
|
402
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
403
|
+
}
|
|
404
|
+
await validateProjectedChildren(rootDirectory, manifest, location);
|
|
405
|
+
return Object.freeze({ path: rootDirectory, manifest, record: recordFromManifest(manifest) });
|
|
406
|
+
};
|
|
407
|
+
const scanRoots = async (context) => {
|
|
408
|
+
const finals = [];
|
|
409
|
+
const staging = [];
|
|
410
|
+
for (const name of await boundedNames(context.rootsDirectory)) {
|
|
411
|
+
const rootPath = path.join(context.rootsDirectory, name);
|
|
412
|
+
if (isCanonicalProjectSkillRuntimeRootId(name)) {
|
|
413
|
+
finals.push(await validateRoot(rootPath, name, "final"));
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (GC_QUARANTINE_NAME.test(name))
|
|
417
|
+
continue;
|
|
418
|
+
const match = STAGING_NAME.exec(name);
|
|
419
|
+
if (match !== null) {
|
|
420
|
+
staging.push(await validateRoot(rootPath, match[1], "staging"));
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
424
|
+
}
|
|
425
|
+
return Object.freeze({ finals: Object.freeze(finals), staging: Object.freeze(staging) });
|
|
426
|
+
};
|
|
427
|
+
const recoverInternal = async (agentRoot) => {
|
|
428
|
+
const context = await contextFor(agentRoot);
|
|
429
|
+
const initial = await scanRoots(context);
|
|
430
|
+
const preflightRootIds = new Set();
|
|
431
|
+
const preflightSequences = new Set();
|
|
432
|
+
for (const root of [...initial.finals, ...initial.staging]) {
|
|
433
|
+
if (preflightRootIds.has(root.record.rootId)
|
|
434
|
+
|| preflightSequences.has(root.record.publishedSequence)) {
|
|
435
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
436
|
+
}
|
|
437
|
+
preflightRootIds.add(root.record.rootId);
|
|
438
|
+
preflightSequences.add(root.record.publishedSequence);
|
|
439
|
+
}
|
|
440
|
+
const occupied = new Set(initial.finals.map(({ manifest }) => manifest.rootId));
|
|
441
|
+
for (const staged of [...initial.staging].sort((left, right) => left.record.publishedSequence - right.record.publishedSequence)) {
|
|
442
|
+
if (occupied.has(staged.manifest.rootId))
|
|
443
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
444
|
+
const finalRoot = path.join(context.rootsDirectory, staged.manifest.rootId);
|
|
445
|
+
try {
|
|
446
|
+
await lstat(finalRoot);
|
|
447
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
if (error instanceof ProjectSkillRuntimeStoreError)
|
|
451
|
+
throw error;
|
|
452
|
+
if (!isMissing(error))
|
|
453
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
454
|
+
}
|
|
455
|
+
await options.beforeRecoveryRootRename?.(staged.path, finalRoot);
|
|
456
|
+
const publication = await atomicRename(staged.path, finalRoot);
|
|
457
|
+
if (publication !== "published")
|
|
458
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
459
|
+
await syncDirectory(context.rootsDirectory);
|
|
460
|
+
await validateRoot(finalRoot, staged.manifest.rootId, "final");
|
|
461
|
+
occupied.add(staged.manifest.rootId);
|
|
462
|
+
}
|
|
463
|
+
const complete = await scanRoots(context);
|
|
464
|
+
if (complete.staging.length !== 0)
|
|
465
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
466
|
+
const state = await reconstructRuntimeRootState(stateIndexContext(context), complete.finals.map(({ record }) => record));
|
|
467
|
+
return Object.freeze({ context, state });
|
|
468
|
+
};
|
|
469
|
+
const cleanupCreatedStaging = async (stagingRoot, expectedIdentity, rootsDirectory) => {
|
|
470
|
+
const current = await readExactDirectoryIdentity(stagingRoot);
|
|
471
|
+
if (current === null)
|
|
472
|
+
return;
|
|
473
|
+
if (!sameExactDirectoryIdentity(current, expectedIdentity)) {
|
|
474
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
475
|
+
}
|
|
476
|
+
await makeTreeWritable(stagingRoot, path);
|
|
477
|
+
await rm(stagingRoot, { recursive: true, force: true });
|
|
478
|
+
await syncDirectory(rootsDirectory);
|
|
479
|
+
};
|
|
480
|
+
const recoverPublic = async (agentRoot) => {
|
|
481
|
+
try {
|
|
482
|
+
await recoverInternal(agentRoot);
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
mapStoreError(error);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
const probeNativePublicationPrimitiveReadiness = async () => {
|
|
489
|
+
if (platform !== "darwin" && platform !== "linux" && platform !== "win32")
|
|
490
|
+
return false;
|
|
491
|
+
try {
|
|
492
|
+
return await atomicRenamePrimitiveReadinessProbe() === true;
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
const publish = async (input) => {
|
|
499
|
+
let injectedCrash;
|
|
500
|
+
let stagingRoot = null;
|
|
501
|
+
let stagingIdentity = null;
|
|
502
|
+
let renamed = false;
|
|
503
|
+
let preserveStaging = false;
|
|
504
|
+
try {
|
|
505
|
+
const { context, state } = await recoverInternal(input.agentRoot);
|
|
506
|
+
await options.afterInventoryLoadedBeforeRootStaging?.();
|
|
507
|
+
if (state.nextSequence >= Number.MAX_SAFE_INTEGER
|
|
508
|
+
|| state.roots.length >= MAX_RUNTIME_ROOT_ENTRIES) {
|
|
509
|
+
return fail("skill_projection_runtime_store_limit_exceeded");
|
|
510
|
+
}
|
|
511
|
+
const resolutions = normalizeProjectSkillResolutionRecords(input.resolutions, platform);
|
|
512
|
+
const materializedNames = resolutions
|
|
513
|
+
.filter(({ mode }) => mode === "resolved")
|
|
514
|
+
.map(({ skillName }) => skillName);
|
|
515
|
+
if (new Set(materializedNames).size !== materializedNames.length) {
|
|
516
|
+
return fail("skill_projection_snapshot_corrupt");
|
|
517
|
+
}
|
|
518
|
+
const sourceIdentities = await Promise.all(resolutions.map((resolution) => resolution.mode === "missing"
|
|
519
|
+
? null
|
|
520
|
+
: captureResolvedProjectSkillSourceIdentity(resolution.sourcePath, path)));
|
|
521
|
+
const randomId = options.randomId ?? randomUUID;
|
|
522
|
+
const rootId = randomId();
|
|
523
|
+
const operationNonce = randomId();
|
|
524
|
+
if (!isCanonicalProjectSkillRuntimeRootId(rootId)
|
|
525
|
+
|| !/^[A-Za-z0-9-]{1,128}$/u.test(operationNonce)) {
|
|
526
|
+
return fail("skill_projection_snapshot_corrupt");
|
|
527
|
+
}
|
|
528
|
+
const finalRoot = path.join(context.rootsDirectory, rootId);
|
|
529
|
+
stagingRoot = path.join(context.rootsDirectory, `.${rootId}-next-${operationNonce}`);
|
|
530
|
+
for (const candidate of [finalRoot, stagingRoot]) {
|
|
531
|
+
try {
|
|
532
|
+
await lstat(candidate);
|
|
533
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
534
|
+
}
|
|
535
|
+
catch (error) {
|
|
536
|
+
if (error instanceof ProjectSkillRuntimeStoreError)
|
|
537
|
+
throw error;
|
|
538
|
+
if (!isMissing(error))
|
|
539
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
await mkdir(stagingRoot, { mode: 0o700 });
|
|
543
|
+
await chmod(stagingRoot, 0o700);
|
|
544
|
+
stagingIdentity = await realOwnedDirectory(stagingRoot);
|
|
545
|
+
await syncDirectory(context.rootsDirectory);
|
|
546
|
+
const runtime = path.join(stagingRoot, "runtime");
|
|
547
|
+
const claude = path.join(runtime, ".claude");
|
|
548
|
+
const skills = path.join(claude, "skills");
|
|
549
|
+
await mkdir(skills, { recursive: true, mode: 0o700 });
|
|
550
|
+
await chmod(runtime, 0o700);
|
|
551
|
+
await chmod(claude, 0o700);
|
|
552
|
+
await chmod(skills, 0o700);
|
|
553
|
+
const projectionModes = [];
|
|
554
|
+
for (const [index, resolution] of resolutions.entries()) {
|
|
555
|
+
if (resolution.mode === "missing") {
|
|
556
|
+
projectionModes.push(Object.freeze({
|
|
557
|
+
projectId: resolution.projectId,
|
|
558
|
+
skillName: resolution.skillName,
|
|
559
|
+
mode: "missing",
|
|
560
|
+
}));
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
const sourceIdentity = sourceIdentities[index];
|
|
564
|
+
if (sourceIdentity === null || sourceIdentity === undefined
|
|
565
|
+
|| !sameResolvedProjectSkillSourceIdentity(sourceIdentity, await captureResolvedProjectSkillSourceIdentity(resolution.sourcePath, path))) {
|
|
566
|
+
return fail("skill_projection_failed");
|
|
567
|
+
}
|
|
568
|
+
const target = path.join(skills, resolution.skillName);
|
|
569
|
+
const finalTarget = mapDirectoryProjectionFinalTarget(target, { stagingRoot, finalRoot }, platform);
|
|
570
|
+
const result = await project(resolution.sourcePath, target, platform, {
|
|
571
|
+
...options.projectionOptions,
|
|
572
|
+
finalTarget,
|
|
573
|
+
...(options.projectionRandomId === undefined
|
|
574
|
+
? {}
|
|
575
|
+
: { randomId: options.projectionRandomId }),
|
|
576
|
+
syncDirectory,
|
|
577
|
+
});
|
|
578
|
+
if (!sameResolvedProjectSkillSourceIdentity(sourceIdentity, await captureResolvedProjectSkillSourceIdentity(resolution.sourcePath, path))) {
|
|
579
|
+
return fail("skill_projection_failed");
|
|
580
|
+
}
|
|
581
|
+
if ((result.mode === "symlink" || result.mode === "junction")
|
|
582
|
+
&& !(await stat(target)).isDirectory()) {
|
|
583
|
+
return fail("skill_projection_failed");
|
|
584
|
+
}
|
|
585
|
+
if (!(await stat(path.join(target, "SKILL.md"))).isFile()) {
|
|
586
|
+
return fail("skill_projection_failed");
|
|
587
|
+
}
|
|
588
|
+
projectionModes.push(Object.freeze({
|
|
589
|
+
projectId: resolution.projectId,
|
|
590
|
+
skillName: resolution.skillName,
|
|
591
|
+
mode: result.mode,
|
|
592
|
+
}));
|
|
593
|
+
}
|
|
594
|
+
await syncTree(runtime, path, syncDirectory);
|
|
595
|
+
await options.beforeManifestWrite?.(stagingRoot);
|
|
596
|
+
const manifest = createProjectSkillRuntimeRootManifest({
|
|
597
|
+
rootId,
|
|
598
|
+
publishedSequence: state.nextSequence,
|
|
599
|
+
directoryIdentity: stagingIdentity,
|
|
600
|
+
bindingDigest: input.bindingDigest,
|
|
601
|
+
resolutionDigest: input.resolutionDigest,
|
|
602
|
+
projectionModes,
|
|
603
|
+
});
|
|
604
|
+
await writeNewDurableFile(path.join(stagingRoot, MANIFEST_FILE_NAME), `${JSON.stringify(manifest)}\n`, stagingRoot, syncDirectory);
|
|
605
|
+
await validateRoot(stagingRoot, rootId, "staging");
|
|
606
|
+
try {
|
|
607
|
+
await options.afterStagingDurableBeforeRename?.(Object.freeze({ stagingRoot, finalRoot }));
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
injectedCrash = error;
|
|
611
|
+
throw error;
|
|
612
|
+
}
|
|
613
|
+
try {
|
|
614
|
+
await lstat(finalRoot);
|
|
615
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
if (error instanceof ProjectSkillRuntimeStoreError)
|
|
619
|
+
throw error;
|
|
620
|
+
if (!isMissing(error))
|
|
621
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
622
|
+
}
|
|
623
|
+
await options.beforeRootRename?.(stagingRoot, finalRoot);
|
|
624
|
+
const publication = await atomicRename(stagingRoot, finalRoot);
|
|
625
|
+
if (publication !== "published") {
|
|
626
|
+
preserveStaging = true;
|
|
627
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
628
|
+
}
|
|
629
|
+
renamed = true;
|
|
630
|
+
await syncDirectory(context.rootsDirectory);
|
|
631
|
+
const published = await validateRoot(finalRoot, rootId, "final");
|
|
632
|
+
try {
|
|
633
|
+
await options.afterRootRenamedBeforeInventoryRescan?.(Object.freeze({ stagingRoot, finalRoot }));
|
|
634
|
+
}
|
|
635
|
+
catch (error) {
|
|
636
|
+
injectedCrash = error;
|
|
637
|
+
throw error;
|
|
638
|
+
}
|
|
639
|
+
const complete = await scanRoots(context);
|
|
640
|
+
if (complete.staging.length !== 0) {
|
|
641
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
642
|
+
}
|
|
643
|
+
const rescanned = await reconstructRuntimeRootState(stateIndexContext(context), complete.finals.map(({ record }) => record));
|
|
644
|
+
const committed = rescanned.roots.find((record) => record.rootId === rootId);
|
|
645
|
+
if (committed === undefined || !sameRecord(committed, published.record)
|
|
646
|
+
|| committed.publishedSequence !== state.nextSequence) {
|
|
647
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
648
|
+
}
|
|
649
|
+
return immutableDescriptor(rootId, finalRoot, path);
|
|
650
|
+
}
|
|
651
|
+
catch (error) {
|
|
652
|
+
if (!renamed && !preserveStaging
|
|
653
|
+
&& stagingRoot !== null && stagingIdentity !== null && injectedCrash === undefined) {
|
|
654
|
+
await cleanupCreatedStaging(stagingRoot, stagingIdentity, path.dirname(stagingRoot)).catch(() => fail("skill_projection_runtime_root_unmanaged"));
|
|
655
|
+
}
|
|
656
|
+
if (error === injectedCrash)
|
|
657
|
+
throw error;
|
|
658
|
+
return mapStoreError(error);
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
const list = async (agentRoot) => {
|
|
662
|
+
try {
|
|
663
|
+
return (await recoverInternal(agentRoot)).state.roots;
|
|
664
|
+
}
|
|
665
|
+
catch (error) {
|
|
666
|
+
return mapStoreError(error);
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
const inspect = async (agentRoot, rootId) => {
|
|
670
|
+
try {
|
|
671
|
+
if (!isCanonicalProjectSkillRuntimeRootId(rootId)) {
|
|
672
|
+
return fail("skill_projection_snapshot_corrupt");
|
|
673
|
+
}
|
|
674
|
+
const { context, state } = await recoverInternal(agentRoot);
|
|
675
|
+
const rootDirectory = path.join(context.rootsDirectory, rootId);
|
|
676
|
+
const validated = await validateRoot(rootDirectory, rootId, "final");
|
|
677
|
+
const stateRecord = state.roots.find((record) => record.rootId === rootId);
|
|
678
|
+
if (stateRecord === undefined || !sameRecord(stateRecord, validated.record)) {
|
|
679
|
+
return fail("skill_projection_runtime_root_unmanaged");
|
|
680
|
+
}
|
|
681
|
+
return Object.freeze({
|
|
682
|
+
descriptor: immutableDescriptor(rootId, rootDirectory, path),
|
|
683
|
+
record: validated.record,
|
|
684
|
+
containsCopyProjection: validated.manifest.projectionModes.some(({ mode }) => mode === "copy"),
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
catch (error) {
|
|
688
|
+
return mapStoreError(error);
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
const load = async (agentRoot, rootId) => (await inspect(agentRoot, rootId)).descriptor;
|
|
692
|
+
const checkpointNextSequence = async (agentRoot) => {
|
|
693
|
+
try {
|
|
694
|
+
const { context, state } = await recoverInternal(agentRoot);
|
|
695
|
+
return await checkpointRuntimeRootNextSequence(stateIndexContext(context), state);
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
return mapStoreError(error);
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
const checkpointNextSequenceForGc = async (agentRoot) => {
|
|
702
|
+
try {
|
|
703
|
+
const { context, state } = await recoverInternal(agentRoot);
|
|
704
|
+
return await checkpointRuntimeRootNextSequence(stateIndexContext(context), state, {
|
|
705
|
+
allowCapacityCompaction: true,
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
return mapStoreError(error);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
return Object.freeze({
|
|
713
|
+
probeNativePublicationPrimitiveReadiness,
|
|
714
|
+
recover: recoverPublic,
|
|
715
|
+
publish,
|
|
716
|
+
load,
|
|
717
|
+
inspect,
|
|
718
|
+
list,
|
|
719
|
+
checkpointNextSequence,
|
|
720
|
+
checkpointNextSequenceForGc,
|
|
721
|
+
});
|
|
722
|
+
}
|