@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.
Files changed (66) hide show
  1. package/README.md +130 -8
  2. package/agents/design-explorer.md +17 -11
  3. package/agents/diagnosis-worker.md +9 -1
  4. package/agents/implementation-worker.md +3 -1
  5. package/config/plan-draft.example.json +122 -0
  6. package/config/plan-draft.multiple-tasks.example.json +345 -0
  7. package/config/plan-draft.quick.example.json +78 -0
  8. package/package.json +10 -4
  9. package/prompts/abel-design.md +101 -44
  10. package/prompts/abel-implement.md +9 -0
  11. package/src/activity-contracts.ts +82 -0
  12. package/src/candidate-artifact.ts +143 -0
  13. package/src/candidate-patch.ts +31 -11
  14. package/src/candidate-workspace.ts +74 -0
  15. package/src/change-contract-schema.ts +191 -0
  16. package/src/change-contract.ts +5 -0
  17. package/src/change-verification.ts +920 -0
  18. package/src/child-budget.ts +58 -0
  19. package/src/child-model.ts +142 -0
  20. package/src/child-session.ts +289 -326
  21. package/src/contracts.ts +200 -76
  22. package/src/delivery-compiler.ts +105 -120
  23. package/src/delivery-revision.ts +42 -0
  24. package/src/design-control.ts +113 -4
  25. package/src/design-diagnostics.ts +150 -0
  26. package/src/durable-contracts.ts +213 -0
  27. package/src/durable-workflow.ts +148 -3570
  28. package/src/evidence-draft.ts +122 -0
  29. package/src/execution-profile.ts +38 -0
  30. package/src/implement-graph.ts +3 -0
  31. package/src/implement-plan.ts +70 -0
  32. package/src/index.ts +59 -522
  33. package/src/isolation-backend.ts +62 -40
  34. package/src/model-source.ts +22 -0
  35. package/src/operator-cli-entry.mjs +24 -0
  36. package/src/operator-cli.mjs +1421 -0
  37. package/src/operator-tools.ts +249 -0
  38. package/src/package-candidate.ts +227 -0
  39. package/src/package-verification.ts +86 -8
  40. package/src/package-workflow.ts +291 -0
  41. package/src/packet-runtime.ts +27 -48
  42. package/src/parent-provider.ts +86 -198
  43. package/src/phase-execution.ts +2248 -0
  44. package/src/pi-adapter.ts +19 -0
  45. package/src/plan-draft-summary.ts +149 -0
  46. package/src/plan-draft.ts +493 -0
  47. package/src/single-task-draft.ts +142 -0
  48. package/src/subagent-activity.ts +32 -66
  49. package/src/submit-tool.ts +20 -59
  50. package/src/transport-budget.ts +52 -0
  51. package/src/verification-capability.ts +5 -3
  52. package/src/verification-diagnostics.ts +76 -0
  53. package/src/verification-environment-io.ts +280 -0
  54. package/src/verification-environment.ts +56 -154
  55. package/src/verification-scan.ts +64 -0
  56. package/src/worker-broker.ts +39 -57
  57. package/src/workflow-policy.ts +14 -53
  58. package/src/workflow-recovery-policy.ts +95 -0
  59. package/src/workflow-scheduling.ts +105 -0
  60. package/src/workflow-state-machine.ts +105 -497
  61. package/src/workflow-status.ts +384 -0
  62. package/src/workspace-io-entry.mjs +19 -1
  63. package/src/workspace-io-worker.mjs +275 -50
  64. package/src/workspace-io.ts +6 -1
  65. package/src/empty-resource-loader.ts +0 -38
  66. 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 constants2,
414
+ constants as constants3,
204
415
  fstatSync,
205
- lstatSync as lstatSync2,
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 path2 from "node:path";
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 = lstatSync2(file, { throwIfNoEntry: false });
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 readdirSync(file).sort()) {
445
+ for (const name of readdirSync2(file).sort()) {
235
446
  if (dependencyRoot && [".vite", ".vite-temp"].includes(name))
236
447
  continue;
237
- visit(path2.join(file, name), `${relative}/${name}`, false);
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, constants2.O_RDONLY | constants2.O_NOFOLLOW);
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, path2.basename(root) === "node_modules");
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 constants3,
489
+ constants as constants4,
279
490
  fsyncSync as fsyncSync2,
280
- lstatSync as lstatSync4,
281
- mkdirSync as mkdirSync2,
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 writeFileSync2
497
+ writeFileSync as writeFileSync3
287
498
  } from "node:fs";
288
- import path4 from "node:path";
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 lstatSync3 } from "node:fs";
401
- import path3 from "node:path";
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 = path3.resolve(root);
408
- const rootStat = lstatSync3(resolvedRoot, { throwIfNoEntry: false });
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 = path3.join(current, part);
418
- if (!current.startsWith(`${resolvedRoot}${path3.sep}`)) {
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 = lstatSync3(current, { throwIfNoEntry: false });
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 = lstatSync4(directory, { throwIfNoEntry: false });
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 = path4.dirname(directory);
533
- while (cursor !== path4.dirname(cursor)) {
534
- const ancestor = lstatSync4(cursor, { throwIfNoEntry: false });
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 = path4.dirname(cursor);
759
+ cursor = path5.dirname(cursor);
542
760
  }
543
- mkdirSync2(directory, { recursive: true, mode: 448 });
544
- const created = lstatSync4(directory);
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, constants3.O_RDONLY);
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 = path4.dirname(target);
777
+ const directory = path5.dirname(target);
560
778
  ensureDirectory2(directory);
561
- const temporary = path4.join(directory, `.${path4.basename(target)}.${process.pid}.${randomUUID2()}.tmp`);
562
- const descriptor = openSync3(temporary, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
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
- writeFileSync2(descriptor, bytes);
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 (!path4.isAbsolute(root))
863
+ if (!path5.isAbsolute(root))
646
864
  throw new Error("workspace-store-root-invalid");
647
- this.root = path4.resolve(root);
648
- this.#revisions = path4.join(this.root, "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 path4.join(this.#revisions, id.slice(0, 2), `${id}.json`);
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 = lstatSync4(target, { throwIfNoEntry: false });
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 = path4.resolve(input.consumerRoot);
702
- const rootStat = lstatSync4(consumerRoot, { throwIfNoEntry: false });
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 = path4.resolve(input.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 = path4.join(consumerRoot, ...relative.split("/"));
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 = lstatSync4(absolute);
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 = lstatSync4(target, { throwIfNoEntry: false });
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 = path4.resolve(destination);
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 = path4.join(parent, segment);
1089
+ parent = path5.join(parent, segment);
872
1090
  ensureDirectory2(parent);
873
1091
  }
874
- const target = path4.join(root, ...segments);
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 = path4.resolve(input.consumerRoot);
887
- const stat = lstatSync4(consumerRoot, { throwIfNoEntry: false });
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 === "verificationIdentity") {
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 {
@@ -1,7 +1,12 @@
1
1
  import { Worker } from "node:worker_threads";
2
2
 
3
3
  export interface WorkspaceIoMetrics {
4
- operation: "captureBaseline" | "materialize" | "verificationIdentity";
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
- }