@abelxiaoxing/cadence 1.4.0 → 1.5.0
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 +130 -8
- package/agents/design-explorer.md +17 -11
- package/agents/diagnosis-worker.md +9 -1
- package/agents/implementation-worker.md +3 -1
- package/config/plan-draft.example.json +122 -0
- package/config/plan-draft.multiple-tasks.example.json +345 -0
- package/config/plan-draft.quick.example.json +78 -0
- package/package.json +10 -4
- package/prompts/abel-design.md +101 -44
- package/prompts/abel-implement.md +9 -0
- package/src/activity-contracts.ts +82 -0
- package/src/candidate-artifact.ts +143 -0
- package/src/candidate-patch.ts +31 -11
- package/src/candidate-workspace.ts +74 -0
- package/src/change-contract-schema.ts +191 -0
- package/src/change-contract.ts +5 -0
- package/src/change-verification.ts +920 -0
- package/src/child-budget.ts +58 -0
- package/src/child-model.ts +142 -0
- package/src/child-session.ts +289 -326
- package/src/contracts.ts +200 -76
- package/src/delivery-compiler.ts +105 -120
- package/src/delivery-revision.ts +42 -0
- package/src/design-control.ts +113 -4
- package/src/design-diagnostics.ts +150 -0
- package/src/durable-contracts.ts +213 -0
- package/src/durable-workflow.ts +148 -3570
- package/src/evidence-draft.ts +122 -0
- package/src/execution-profile.ts +38 -0
- package/src/implement-graph.ts +3 -0
- package/src/implement-plan.ts +70 -0
- package/src/index.ts +59 -522
- package/src/isolation-backend.ts +62 -40
- package/src/model-source.ts +22 -0
- package/src/operator-cli-entry.mjs +24 -0
- package/src/operator-cli.mjs +1421 -0
- package/src/operator-tools.ts +249 -0
- package/src/package-candidate.ts +227 -0
- package/src/package-verification.ts +86 -8
- package/src/package-workflow.ts +291 -0
- package/src/packet-runtime.ts +27 -48
- package/src/parent-provider.ts +86 -198
- package/src/phase-execution.ts +2248 -0
- package/src/pi-adapter.ts +19 -0
- package/src/plan-draft-summary.ts +149 -0
- package/src/plan-draft.ts +493 -0
- package/src/single-task-draft.ts +142 -0
- package/src/subagent-activity.ts +32 -66
- package/src/submit-tool.ts +20 -59
- package/src/transport-budget.ts +52 -0
- package/src/verification-capability.ts +5 -3
- package/src/verification-diagnostics.ts +76 -0
- package/src/verification-environment-io.ts +280 -0
- package/src/verification-environment.ts +56 -154
- package/src/verification-scan.ts +64 -0
- package/src/worker-broker.ts +39 -57
- package/src/workflow-policy.ts +14 -53
- package/src/workflow-recovery-policy.ts +95 -0
- package/src/workflow-scheduling.ts +105 -0
- package/src/workflow-state-machine.ts +105 -497
- package/src/workflow-status.ts +384 -0
- package/src/workspace-io-entry.mjs +19 -1
- package/src/workspace-io-worker.mjs +275 -50
- package/src/workspace-io.ts +6 -1
- package/src/empty-resource-loader.ts +0 -38
- package/src/parent-payload-bridge.ts +0 -523
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by scripts/build-workspace-io.mjs; edit workspace-io-entry.mjs and its source modules.
|
|
2
2
|
// src/workspace-io-entry.mjs
|
|
3
|
+
import { rmSync as rmSync2 } from "node:fs";
|
|
3
4
|
import { parentPort, workerData } from "node:worker_threads";
|
|
4
5
|
|
|
5
6
|
// src/artifact-store.ts
|
|
@@ -196,19 +197,229 @@ class ArtifactStore {
|
|
|
196
197
|
}
|
|
197
198
|
}
|
|
198
199
|
|
|
200
|
+
// src/verification-environment-io.ts
|
|
201
|
+
import {
|
|
202
|
+
constants as constants2,
|
|
203
|
+
cpSync,
|
|
204
|
+
lstatSync as lstatSync2,
|
|
205
|
+
mkdirSync as mkdirSync2,
|
|
206
|
+
readdirSync,
|
|
207
|
+
realpathSync,
|
|
208
|
+
rmSync,
|
|
209
|
+
symlinkSync,
|
|
210
|
+
writeFileSync as writeFileSync2
|
|
211
|
+
} from "node:fs";
|
|
212
|
+
import path2 from "node:path";
|
|
213
|
+
var CACHES = [".vite", ".vite-temp"];
|
|
214
|
+
function within(root, candidate) {
|
|
215
|
+
const relative = path2.relative(root, candidate);
|
|
216
|
+
return relative !== ".." && !relative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative);
|
|
217
|
+
}
|
|
218
|
+
function prepareVerificationEnvironmentIo(root, dependencyOwner, runnerBindings, profile, environment, checkCancelled, privateRoot) {
|
|
219
|
+
const cleanup = () => rmSync(privateRoot, { recursive: true, force: true });
|
|
220
|
+
try {
|
|
221
|
+
const local = profile.mode === "local-trusted";
|
|
222
|
+
if (local && realpathSync(root) === realpathSync(dependencyOwner))
|
|
223
|
+
throw new Error("trusted-candidate-required");
|
|
224
|
+
const mounts = [
|
|
225
|
+
{ source: privateRoot, target: "/cadence", writable: true }
|
|
226
|
+
];
|
|
227
|
+
for (const directory of [
|
|
228
|
+
"home",
|
|
229
|
+
"cache",
|
|
230
|
+
"tmp",
|
|
231
|
+
"reports",
|
|
232
|
+
"dependencies"
|
|
233
|
+
]) {
|
|
234
|
+
mkdirSync2(path2.join(privateRoot, directory), { mode: 448 });
|
|
235
|
+
}
|
|
236
|
+
writeFileSync2(path2.join(privateRoot, "home/user.npmrc"), "");
|
|
237
|
+
writeFileSync2(path2.join(privateRoot, "home/global.npmrc"), "");
|
|
238
|
+
const dependencies = path2.join(dependencyOwner, "node_modules");
|
|
239
|
+
const stat = lstatSync2(dependencies, { throwIfNoEntry: false });
|
|
240
|
+
if (stat) {
|
|
241
|
+
const target = path2.join(root, "node_modules");
|
|
242
|
+
const targetStat = lstatSync2(target, { throwIfNoEntry: false });
|
|
243
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || targetStat && (!targetStat.isDirectory() || targetStat.isSymbolicLink())) {
|
|
244
|
+
throw new Error("dependency-path-unsafe");
|
|
245
|
+
}
|
|
246
|
+
if (!targetStat)
|
|
247
|
+
mkdirSync2(target, { mode: 448 });
|
|
248
|
+
const view = path2.join(privateRoot, "dependencies");
|
|
249
|
+
mounts.push({ source: view, target: "/workspace/node_modules" });
|
|
250
|
+
const entries = readdirSync(dependencies);
|
|
251
|
+
if (entries.length > 20000)
|
|
252
|
+
throw new Error("dependency-view-too-large");
|
|
253
|
+
const canonical = realpathSync(dependencies);
|
|
254
|
+
let viewEntries = 0;
|
|
255
|
+
const projectEntry = (name) => {
|
|
256
|
+
checkCancelled();
|
|
257
|
+
if (++viewEntries > 20000)
|
|
258
|
+
throw new Error("dependency-view-too-large");
|
|
259
|
+
if (CACHES.includes(name))
|
|
260
|
+
return;
|
|
261
|
+
const source = path2.join(dependencies, name);
|
|
262
|
+
const placeholder = path2.join(view, name);
|
|
263
|
+
const entry = lstatSync2(source);
|
|
264
|
+
if (entry.isSymbolicLink()) {
|
|
265
|
+
const resolved = realpathSync(source);
|
|
266
|
+
if (within(canonical, resolved)) {
|
|
267
|
+
symlinkSync(path2.relative(path2.dirname(source), resolved), placeholder);
|
|
268
|
+
} else if (within(realpathSync(dependencyOwner), resolved)) {
|
|
269
|
+
const relative = path2.relative(realpathSync(dependencyOwner), resolved);
|
|
270
|
+
const candidate = path2.join(root, relative);
|
|
271
|
+
if (!lstatSync2(candidate, { throwIfNoEntry: false }))
|
|
272
|
+
throw new Error("workspace-dependency-missing");
|
|
273
|
+
symlinkSync(local ? candidate : `/workspace/${relative.split(path2.sep).join("/")}`, placeholder);
|
|
274
|
+
} else
|
|
275
|
+
throw new Error("dependency-path-unsafe");
|
|
276
|
+
} else if (entry.isDirectory() && name.startsWith("@") && !name.includes(path2.sep)) {
|
|
277
|
+
mkdirSync2(placeholder);
|
|
278
|
+
for (const child of readdirSync(source))
|
|
279
|
+
projectEntry(path2.join(name, child));
|
|
280
|
+
} else {
|
|
281
|
+
if (entry.isDirectory())
|
|
282
|
+
mkdirSync2(placeholder);
|
|
283
|
+
else if (entry.isFile())
|
|
284
|
+
writeFileSync2(placeholder, "");
|
|
285
|
+
else
|
|
286
|
+
throw new Error("dependency-path-unsafe");
|
|
287
|
+
mounts.push({
|
|
288
|
+
source,
|
|
289
|
+
target: `/workspace/node_modules/${name.split(path2.sep).join("/")}`
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
for (const name of entries)
|
|
294
|
+
projectEntry(name);
|
|
295
|
+
for (const cache of CACHES) {
|
|
296
|
+
mkdirSync2(path2.join(view, cache));
|
|
297
|
+
const source = path2.join(privateRoot, "cache", cache);
|
|
298
|
+
mkdirSync2(source);
|
|
299
|
+
mounts.push({
|
|
300
|
+
source,
|
|
301
|
+
target: `/workspace/node_modules/${cache}`,
|
|
302
|
+
writable: true
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (local && stat) {
|
|
307
|
+
const copyEntry = (name) => {
|
|
308
|
+
checkCancelled();
|
|
309
|
+
const source = path2.join(dependencies, name);
|
|
310
|
+
const destination = path2.join(root, "node_modules", name);
|
|
311
|
+
if (CACHES.includes(name)) {
|
|
312
|
+
mkdirSync2(destination, { recursive: true });
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
removeEntry(destination, checkCancelled);
|
|
316
|
+
const stat2 = lstatSync2(source);
|
|
317
|
+
if (stat2.isSymbolicLink()) {
|
|
318
|
+
const resolved = realpathSync(source);
|
|
319
|
+
const translated = within(canonicalRoot(dependencyOwner), resolved) ? path2.join(root, path2.relative(canonicalRoot(dependencyOwner), resolved)) : resolved;
|
|
320
|
+
symlinkSync(translated, destination);
|
|
321
|
+
} else if (stat2.isDirectory() && name.startsWith("@") && !name.includes(path2.sep)) {
|
|
322
|
+
mkdirSync2(destination);
|
|
323
|
+
for (const child of readdirSync(source))
|
|
324
|
+
copyEntry(path2.join(name, child));
|
|
325
|
+
} else
|
|
326
|
+
cpSync(source, destination, {
|
|
327
|
+
recursive: true,
|
|
328
|
+
filter: () => {
|
|
329
|
+
checkCancelled();
|
|
330
|
+
return true;
|
|
331
|
+
},
|
|
332
|
+
mode: constants2.COPYFILE_FICLONE,
|
|
333
|
+
verbatimSymlinks: true
|
|
334
|
+
});
|
|
335
|
+
};
|
|
336
|
+
for (const name of readdirSync(path2.join(privateRoot, "dependencies")))
|
|
337
|
+
copyEntry(name);
|
|
338
|
+
}
|
|
339
|
+
const sources = new Map;
|
|
340
|
+
const bindings = runnerBindings.map((binding) => {
|
|
341
|
+
if (local || !binding.mountSource)
|
|
342
|
+
return { ...binding };
|
|
343
|
+
const source = path2.resolve(binding.mountSource);
|
|
344
|
+
const stat2 = lstatSync2(source);
|
|
345
|
+
const executable = realpathSync(binding.executablePath);
|
|
346
|
+
if (!stat2.isDirectory() || stat2.isSymbolicLink() || !within(source, executable)) {
|
|
347
|
+
throw new Error("runner-mount-unavailable");
|
|
348
|
+
}
|
|
349
|
+
let target = sources.get(source);
|
|
350
|
+
if (!target) {
|
|
351
|
+
target = `/cadence-runners/${sources.size}`;
|
|
352
|
+
sources.set(source, target);
|
|
353
|
+
mounts.push({ source, target });
|
|
354
|
+
}
|
|
355
|
+
const translate = (file) => path2.posix.join(target, ...path2.relative(source, file).split(path2.sep));
|
|
356
|
+
return {
|
|
357
|
+
...binding,
|
|
358
|
+
executablePath: translate(executable),
|
|
359
|
+
...binding.fixedArgs ? {
|
|
360
|
+
fixedArgs: binding.fixedArgs.map((arg) => path2.isAbsolute(arg) && within(source, arg) ? translate(arg) : arg)
|
|
361
|
+
} : {}
|
|
362
|
+
};
|
|
363
|
+
});
|
|
364
|
+
checkCancelled();
|
|
365
|
+
return {
|
|
366
|
+
privateRoot,
|
|
367
|
+
mounts,
|
|
368
|
+
bindings,
|
|
369
|
+
reportRoot: local ? privateRoot : "/cadence",
|
|
370
|
+
environment: {
|
|
371
|
+
...local ? Object.fromEntries(profile.inheritEnvironment.flatMap((name) => environment[name] === undefined ? [] : [[name, environment[name] ?? ""]])) : {},
|
|
372
|
+
CI: "1",
|
|
373
|
+
HOME: local ? path2.join(privateRoot, "home") : "/cadence/home",
|
|
374
|
+
XDG_CACHE_HOME: local ? path2.join(privateRoot, "cache") : "/cadence/cache",
|
|
375
|
+
TMPDIR: local ? path2.join(privateRoot, "tmp") : "/cadence/tmp",
|
|
376
|
+
npm_config_cache: local ? path2.join(privateRoot, "cache/npm") : "/cadence/cache/npm",
|
|
377
|
+
npm_config_userconfig: local ? path2.join(privateRoot, "home/user.npmrc") : "/cadence/home/user.npmrc",
|
|
378
|
+
npm_config_globalconfig: local ? path2.join(privateRoot, "home/global.npmrc") : "/cadence/home/global.npmrc",
|
|
379
|
+
PATH: [
|
|
380
|
+
...new Set([
|
|
381
|
+
...bindings.filter((binding) => binding.mountSource),
|
|
382
|
+
...bindings.filter((binding) => !binding.mountSource)
|
|
383
|
+
].map((binding) => path2.posix.dirname(binding.executablePath))),
|
|
384
|
+
...local ? (environment.PATH ?? "").split(path2.delimiter) : [],
|
|
385
|
+
"/usr/local/bin",
|
|
386
|
+
"/usr/bin",
|
|
387
|
+
"/bin"
|
|
388
|
+
].join(":")
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
} catch (error) {
|
|
392
|
+
cleanup();
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function canonicalRoot(root) {
|
|
397
|
+
return realpathSync(root);
|
|
398
|
+
}
|
|
399
|
+
function removeEntry(file, checkCancelled) {
|
|
400
|
+
checkCancelled();
|
|
401
|
+
const stat = lstatSync2(file, { throwIfNoEntry: false });
|
|
402
|
+
if (!stat)
|
|
403
|
+
return;
|
|
404
|
+
if (stat.isDirectory())
|
|
405
|
+
for (const name of readdirSync(file))
|
|
406
|
+
removeEntry(path2.join(file, name), checkCancelled);
|
|
407
|
+
rmSync(file, { recursive: stat.isDirectory(), force: true });
|
|
408
|
+
}
|
|
409
|
+
|
|
199
410
|
// src/verification-identity.ts
|
|
200
411
|
import { createHash as createHash2 } from "node:crypto";
|
|
201
412
|
import {
|
|
202
413
|
closeSync as closeSync2,
|
|
203
|
-
constants as
|
|
414
|
+
constants as constants3,
|
|
204
415
|
fstatSync,
|
|
205
|
-
lstatSync as
|
|
416
|
+
lstatSync as lstatSync3,
|
|
206
417
|
openSync as openSync2,
|
|
207
|
-
readdirSync,
|
|
418
|
+
readdirSync as readdirSync2,
|
|
208
419
|
readlinkSync,
|
|
209
420
|
readSync
|
|
210
421
|
} from "node:fs";
|
|
211
|
-
import
|
|
422
|
+
import path3 from "node:path";
|
|
212
423
|
function verificationEnvironmentDigest(roots, checkCancelled = () => {}) {
|
|
213
424
|
const digest2 = createHash2("sha256");
|
|
214
425
|
let entries = 0;
|
|
@@ -219,7 +430,7 @@ function verificationEnvironmentDigest(roots, checkCancelled = () => {}) {
|
|
|
219
430
|
checkCancelled();
|
|
220
431
|
if (++entries > 500000 || Date.now() - started > 60000)
|
|
221
432
|
throw new Error("verification-environment-limit");
|
|
222
|
-
const stat =
|
|
433
|
+
const stat = lstatSync3(file, { throwIfNoEntry: false });
|
|
223
434
|
digest2.update(JSON.stringify([relative, stat ? stat.mode & 511 : null]));
|
|
224
435
|
if (!stat) {
|
|
225
436
|
digest2.update("absent");
|
|
@@ -231,16 +442,16 @@ function verificationEnvironmentDigest(roots, checkCancelled = () => {}) {
|
|
|
231
442
|
}
|
|
232
443
|
if (stat.isDirectory()) {
|
|
233
444
|
digest2.update("directory");
|
|
234
|
-
for (const name of
|
|
445
|
+
for (const name of readdirSync2(file).sort()) {
|
|
235
446
|
if (dependencyRoot && [".vite", ".vite-temp"].includes(name))
|
|
236
447
|
continue;
|
|
237
|
-
visit(
|
|
448
|
+
visit(path3.join(file, name), `${relative}/${name}`, false);
|
|
238
449
|
}
|
|
239
450
|
return;
|
|
240
451
|
}
|
|
241
452
|
if (!stat.isFile())
|
|
242
453
|
throw new Error("verification-environment-unsafe");
|
|
243
|
-
const fd = openSync2(file,
|
|
454
|
+
const fd = openSync2(file, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
244
455
|
try {
|
|
245
456
|
const before = fstatSync(fd);
|
|
246
457
|
if (before.ino !== stat.ino || before.dev !== stat.dev)
|
|
@@ -265,7 +476,7 @@ function verificationEnvironmentDigest(roots, checkCancelled = () => {}) {
|
|
|
265
476
|
}
|
|
266
477
|
};
|
|
267
478
|
for (const root of [...new Set(roots)].sort())
|
|
268
|
-
visit(root, root,
|
|
479
|
+
visit(root, root, path3.basename(root) === "node_modules");
|
|
269
480
|
return digest2.digest("hex");
|
|
270
481
|
}
|
|
271
482
|
|
|
@@ -275,17 +486,17 @@ import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypt
|
|
|
275
486
|
import {
|
|
276
487
|
chmodSync as chmodSync2,
|
|
277
488
|
closeSync as closeSync3,
|
|
278
|
-
constants as
|
|
489
|
+
constants as constants4,
|
|
279
490
|
fsyncSync as fsyncSync2,
|
|
280
|
-
lstatSync as
|
|
281
|
-
mkdirSync as
|
|
491
|
+
lstatSync as lstatSync5,
|
|
492
|
+
mkdirSync as mkdirSync3,
|
|
282
493
|
openSync as openSync3,
|
|
283
494
|
readFileSync as readFileSync2,
|
|
284
495
|
renameSync as renameSync2,
|
|
285
496
|
unlinkSync as unlinkSync2,
|
|
286
|
-
writeFileSync as
|
|
497
|
+
writeFileSync as writeFileSync3
|
|
287
498
|
} from "node:fs";
|
|
288
|
-
import
|
|
499
|
+
import path5 from "node:path";
|
|
289
500
|
import { promisify } from "node:util";
|
|
290
501
|
|
|
291
502
|
// src/canonical.ts
|
|
@@ -391,21 +602,28 @@ var LIMITS = {
|
|
|
391
602
|
phaseTimeoutMs: 20 * 60 * 1000,
|
|
392
603
|
maxCompleteResultBytes: 64 * 1024
|
|
393
604
|
};
|
|
605
|
+
var VERIFICATION_LIMITS = Object.freeze({
|
|
606
|
+
minSteps: 1,
|
|
607
|
+
maxSteps: 8,
|
|
608
|
+
minTestFiles: 1,
|
|
609
|
+
maxTestFiles: 64
|
|
610
|
+
});
|
|
394
611
|
var NONCANONICAL = /(^|\/)\.\.(\/|$)|(^|\/)\/|^\//;
|
|
612
|
+
var RELATIVE_PATH_PATTERN = String.raw`^(?:\.|(?!/)(?![a-zA-Z]:/)(?!\./)(?![\s\S]*\.\.)(?![\s\S]*[\\\u0000])(?![\s\S]*//)(?![\s\S]*/\.(?:/|$))(?![\s\S]*/$)[\s\S]+)$`;
|
|
395
613
|
function isValidRelativePath(p) {
|
|
396
614
|
return typeof p === "string" && p.length > 0 && p.length <= 512 && !NONCANONICAL.test(p) && !p.startsWith("/") && !/^[a-z]:\//iu.test(p) && (p === "." || !p.startsWith("./") && !p.endsWith("/") && !p.split("/").includes(".")) && !p.includes("..") && !p.includes("\\") && !p.includes("\x00");
|
|
397
615
|
}
|
|
398
616
|
|
|
399
617
|
// src/safe-path.ts
|
|
400
|
-
import { lstatSync as
|
|
401
|
-
import
|
|
618
|
+
import { lstatSync as lstatSync4 } from "node:fs";
|
|
619
|
+
import path4 from "node:path";
|
|
402
620
|
function observeSafePath(root, relative) {
|
|
403
621
|
if (!isValidRelativePath(relative)) {
|
|
404
622
|
return { kind: "unsafe", reason: "invalid-path" };
|
|
405
623
|
}
|
|
406
624
|
try {
|
|
407
|
-
const resolvedRoot =
|
|
408
|
-
const rootStat =
|
|
625
|
+
const resolvedRoot = path4.resolve(root);
|
|
626
|
+
const rootStat = lstatSync4(resolvedRoot, { throwIfNoEntry: false });
|
|
409
627
|
if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
|
|
410
628
|
return { kind: "unsafe", reason: "root-unavailable" };
|
|
411
629
|
}
|
|
@@ -414,11 +632,11 @@ function observeSafePath(root, relative) {
|
|
|
414
632
|
let current = resolvedRoot;
|
|
415
633
|
const parts = relative.split("/");
|
|
416
634
|
for (const [index, part] of parts.entries()) {
|
|
417
|
-
current =
|
|
418
|
-
if (!current.startsWith(`${resolvedRoot}${
|
|
635
|
+
current = path4.join(current, part);
|
|
636
|
+
if (!current.startsWith(`${resolvedRoot}${path4.sep}`)) {
|
|
419
637
|
return { kind: "unsafe", reason: "invalid-path" };
|
|
420
638
|
}
|
|
421
|
-
const stat =
|
|
639
|
+
const stat = lstatSync4(current, { throwIfNoEntry: false });
|
|
422
640
|
if (!stat)
|
|
423
641
|
return { kind: "absent" };
|
|
424
642
|
if (stat.isSymbolicLink()) {
|
|
@@ -521,7 +739,7 @@ function revisionId(parentRevisionId, manifest) {
|
|
|
521
739
|
return hash("cadence-workspace-revision", parentRevisionId ?? "", manifest);
|
|
522
740
|
}
|
|
523
741
|
function ensureDirectory2(directory) {
|
|
524
|
-
const stat =
|
|
742
|
+
const stat = lstatSync5(directory, { throwIfNoEntry: false });
|
|
525
743
|
if (stat) {
|
|
526
744
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
527
745
|
throw new Error("unsafe-workspace-path");
|
|
@@ -529,26 +747,26 @@ function ensureDirectory2(directory) {
|
|
|
529
747
|
chmodSync2(directory, 448);
|
|
530
748
|
return;
|
|
531
749
|
}
|
|
532
|
-
let cursor =
|
|
533
|
-
while (cursor !==
|
|
534
|
-
const ancestor =
|
|
750
|
+
let cursor = path5.dirname(directory);
|
|
751
|
+
while (cursor !== path5.dirname(cursor)) {
|
|
752
|
+
const ancestor = lstatSync5(cursor, { throwIfNoEntry: false });
|
|
535
753
|
if (ancestor) {
|
|
536
754
|
if (ancestor.isSymbolicLink() || !ancestor.isDirectory()) {
|
|
537
755
|
throw new Error("unsafe-workspace-path");
|
|
538
756
|
}
|
|
539
757
|
break;
|
|
540
758
|
}
|
|
541
|
-
cursor =
|
|
759
|
+
cursor = path5.dirname(cursor);
|
|
542
760
|
}
|
|
543
|
-
|
|
544
|
-
const created =
|
|
761
|
+
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
762
|
+
const created = lstatSync5(directory);
|
|
545
763
|
if (created.isSymbolicLink() || !created.isDirectory()) {
|
|
546
764
|
throw new Error("unsafe-workspace-path");
|
|
547
765
|
}
|
|
548
766
|
chmodSync2(directory, 448);
|
|
549
767
|
}
|
|
550
768
|
function syncDirectory2(directory) {
|
|
551
|
-
const descriptor = openSync3(directory,
|
|
769
|
+
const descriptor = openSync3(directory, constants4.O_RDONLY);
|
|
552
770
|
try {
|
|
553
771
|
fsyncSync2(descriptor);
|
|
554
772
|
} finally {
|
|
@@ -556,12 +774,12 @@ function syncDirectory2(directory) {
|
|
|
556
774
|
}
|
|
557
775
|
}
|
|
558
776
|
function writeAtomic2(target, bytes) {
|
|
559
|
-
const directory =
|
|
777
|
+
const directory = path5.dirname(target);
|
|
560
778
|
ensureDirectory2(directory);
|
|
561
|
-
const temporary =
|
|
562
|
-
const descriptor = openSync3(temporary,
|
|
779
|
+
const temporary = path5.join(directory, `.${path5.basename(target)}.${process.pid}.${randomUUID2()}.tmp`);
|
|
780
|
+
const descriptor = openSync3(temporary, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY, 384);
|
|
563
781
|
try {
|
|
564
|
-
|
|
782
|
+
writeFileSync3(descriptor, bytes);
|
|
565
783
|
fsyncSync2(descriptor);
|
|
566
784
|
} finally {
|
|
567
785
|
closeSync3(descriptor);
|
|
@@ -642,10 +860,10 @@ class WorkspaceStore {
|
|
|
642
860
|
constructor(root, artifacts, options = {}) {
|
|
643
861
|
this.#checkCancelled = options.checkCancelled ?? (() => {});
|
|
644
862
|
this.#onMetrics = options.onMetrics;
|
|
645
|
-
if (!
|
|
863
|
+
if (!path5.isAbsolute(root))
|
|
646
864
|
throw new Error("workspace-store-root-invalid");
|
|
647
|
-
this.root =
|
|
648
|
-
this.#revisions =
|
|
865
|
+
this.root = path5.resolve(root);
|
|
866
|
+
this.#revisions = path5.join(this.root, "revisions");
|
|
649
867
|
this.#artifacts = artifacts;
|
|
650
868
|
ensureDirectory2(this.root);
|
|
651
869
|
ensureDirectory2(this.#revisions);
|
|
@@ -653,7 +871,7 @@ class WorkspaceStore {
|
|
|
653
871
|
#revisionPath(id) {
|
|
654
872
|
if (!SHA2562.test(id))
|
|
655
873
|
throw new Error("workspace-revision-id-invalid");
|
|
656
|
-
return
|
|
874
|
+
return path5.join(this.#revisions, id.slice(0, 2), `${id}.json`);
|
|
657
875
|
}
|
|
658
876
|
#storeRevision(parentRevisionId, rawEntries) {
|
|
659
877
|
const entries = stableEntries(rawEntries);
|
|
@@ -666,7 +884,7 @@ class WorkspaceStore {
|
|
|
666
884
|
entries
|
|
667
885
|
};
|
|
668
886
|
const target = this.#revisionPath(id);
|
|
669
|
-
const existing =
|
|
887
|
+
const existing = lstatSync5(target, { throwIfNoEntry: false });
|
|
670
888
|
if (existing) {
|
|
671
889
|
const stored = this.getRevision(id);
|
|
672
890
|
if (JSON.stringify(stored) !== JSON.stringify(revision)) {
|
|
@@ -698,8 +916,8 @@ class WorkspaceStore {
|
|
|
698
916
|
return false;
|
|
699
917
|
}
|
|
700
918
|
captureBaseline(input) {
|
|
701
|
-
const consumerRoot =
|
|
702
|
-
const rootStat =
|
|
919
|
+
const consumerRoot = path5.resolve(input.consumerRoot);
|
|
920
|
+
const rootStat = lstatSync5(consumerRoot, { throwIfNoEntry: false });
|
|
703
921
|
if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
|
|
704
922
|
throw new Error("unsafe-workspace-path");
|
|
705
923
|
}
|
|
@@ -712,7 +930,7 @@ class WorkspaceStore {
|
|
|
712
930
|
const timeout = input.gitTimeoutMs ?? 30000;
|
|
713
931
|
if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 30000)
|
|
714
932
|
throw new Error("workspace-git-timeout-invalid");
|
|
715
|
-
const consumerRoot =
|
|
933
|
+
const consumerRoot = path5.resolve(input.consumerRoot);
|
|
716
934
|
let tracked;
|
|
717
935
|
try {
|
|
718
936
|
tracked = parseNullSeparated(execFileSync("git", ["ls-files", "-z", "--cached"], {
|
|
@@ -746,10 +964,10 @@ class WorkspaceStore {
|
|
|
746
964
|
}
|
|
747
965
|
if (observation.kind !== "file")
|
|
748
966
|
throw new Error("unsafe-workspace-path");
|
|
749
|
-
const absolute =
|
|
967
|
+
const absolute = path5.join(consumerRoot, ...relative.split("/"));
|
|
750
968
|
const bytes = readFileSync2(absolute);
|
|
751
969
|
const artifact = this.#artifacts.put(bytes);
|
|
752
|
-
const stat =
|
|
970
|
+
const stat = lstatSync5(absolute);
|
|
753
971
|
entries[relative] = {
|
|
754
972
|
kind: "file",
|
|
755
973
|
hash: artifact.hash,
|
|
@@ -770,7 +988,7 @@ class WorkspaceStore {
|
|
|
770
988
|
}
|
|
771
989
|
getRevision(id) {
|
|
772
990
|
const target = this.#revisionPath(id);
|
|
773
|
-
const stat =
|
|
991
|
+
const stat = lstatSync5(target, { throwIfNoEntry: false });
|
|
774
992
|
if (!stat?.isFile() || stat.isSymbolicLink()) {
|
|
775
993
|
throw new Error("workspace-revision-unavailable");
|
|
776
994
|
}
|
|
@@ -849,7 +1067,7 @@ class WorkspaceStore {
|
|
|
849
1067
|
}
|
|
850
1068
|
materialize(revisionIdValue, destination) {
|
|
851
1069
|
const revision = this.getRevision(revisionIdValue);
|
|
852
|
-
const root =
|
|
1070
|
+
const root = path5.resolve(destination);
|
|
853
1071
|
ensureDirectory2(root);
|
|
854
1072
|
for (const [relative, entry] of Object.entries(revision.entries)) {
|
|
855
1073
|
this.#checkCancelled();
|
|
@@ -868,10 +1086,10 @@ class WorkspaceStore {
|
|
|
868
1086
|
const segments = relative.split("/");
|
|
869
1087
|
let parent = root;
|
|
870
1088
|
for (const segment of segments.slice(0, -1)) {
|
|
871
|
-
parent =
|
|
1089
|
+
parent = path5.join(parent, segment);
|
|
872
1090
|
ensureDirectory2(parent);
|
|
873
1091
|
}
|
|
874
|
-
const target =
|
|
1092
|
+
const target = path5.join(root, ...segments);
|
|
875
1093
|
if (observation.kind !== "absent") {
|
|
876
1094
|
throw new Error("workspace-materialize-conflict");
|
|
877
1095
|
}
|
|
@@ -883,8 +1101,8 @@ class WorkspaceStore {
|
|
|
883
1101
|
}
|
|
884
1102
|
async captureBaselineWithGit(input, signal) {
|
|
885
1103
|
signal?.throwIfAborted();
|
|
886
|
-
const consumerRoot =
|
|
887
|
-
const stat =
|
|
1104
|
+
const consumerRoot = path5.resolve(input.consumerRoot);
|
|
1105
|
+
const stat = lstatSync5(consumerRoot, { throwIfNoEntry: false });
|
|
888
1106
|
if (!stat?.isDirectory() || stat.isSymbolicLink())
|
|
889
1107
|
throw new Error("unsafe-workspace-path");
|
|
890
1108
|
const excluded = [".git", "node_modules", ...input.excludedPaths ?? []];
|
|
@@ -944,7 +1162,14 @@ var checkCancelled = () => {
|
|
|
944
1162
|
throw new Error("cancelled");
|
|
945
1163
|
};
|
|
946
1164
|
try {
|
|
947
|
-
if (workerData.operation === "
|
|
1165
|
+
if (workerData.operation === "prepareVerification") {
|
|
1166
|
+
const [root, owner, bindings, profile, environment, scratch] = workerData.args;
|
|
1167
|
+
const result = prepareVerificationEnvironmentIo(root, owner, bindings, profile, environment, checkCancelled, scratch);
|
|
1168
|
+
parentPort.postMessage({ ok: true, result, files: 0, bytes: 0 });
|
|
1169
|
+
} else if (workerData.operation === "cleanupVerification") {
|
|
1170
|
+
rmSync2(workerData.args[0], { recursive: true, force: true });
|
|
1171
|
+
parentPort.postMessage({ ok: true, files: 0, bytes: 0 });
|
|
1172
|
+
} else if (workerData.operation === "verificationIdentity") {
|
|
948
1173
|
const result = verificationEnvironmentDigest(workerData.args[0], checkCancelled);
|
|
949
1174
|
parentPort.postMessage({ ok: true, result, files: 0, bytes: 0 });
|
|
950
1175
|
} else {
|
package/src/workspace-io.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { Worker } from "node:worker_threads";
|
|
2
2
|
|
|
3
3
|
export interface WorkspaceIoMetrics {
|
|
4
|
-
operation:
|
|
4
|
+
operation:
|
|
5
|
+
| "captureBaseline"
|
|
6
|
+
| "materialize"
|
|
7
|
+
| "verificationIdentity"
|
|
8
|
+
| "prepareVerification"
|
|
9
|
+
| "cleanupVerification";
|
|
5
10
|
elapsedMs: number;
|
|
6
11
|
files: number;
|
|
7
12
|
bytes: number;
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createExtensionRuntime,
|
|
3
|
-
type ResourceLoader,
|
|
4
|
-
} from "@earendil-works/pi-coding-agent";
|
|
5
|
-
|
|
6
|
-
/** ResourceLoader that discovers nothing and supplies only the approved prompt. */
|
|
7
|
-
export class EmptyResourceLoader implements ResourceLoader {
|
|
8
|
-
constructor(private readonly systemPrompt?: string) {}
|
|
9
|
-
getExtensions() {
|
|
10
|
-
return { extensions: [], errors: [], runtime: createExtensionRuntime() };
|
|
11
|
-
}
|
|
12
|
-
getSkills() {
|
|
13
|
-
return { skills: [], diagnostics: [] };
|
|
14
|
-
}
|
|
15
|
-
getPrompts() {
|
|
16
|
-
return { prompts: [], diagnostics: [] };
|
|
17
|
-
}
|
|
18
|
-
getThemes() {
|
|
19
|
-
return { themes: [], diagnostics: [] };
|
|
20
|
-
}
|
|
21
|
-
getAgentsFiles() {
|
|
22
|
-
return { agentsFiles: [] };
|
|
23
|
-
}
|
|
24
|
-
getSystemPrompt() {
|
|
25
|
-
return this.systemPrompt;
|
|
26
|
-
}
|
|
27
|
-
getSystemPromptSource() {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
getAppendSystemPrompt() {
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
getAppendSystemPromptSources() {
|
|
34
|
-
return [];
|
|
35
|
-
}
|
|
36
|
-
extendResources() {}
|
|
37
|
-
async reload() {}
|
|
38
|
-
}
|