poe-code 3.0.203 → 3.0.205

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 (110) hide show
  1. package/dist/cli/commands/braintrust.d.ts +3 -0
  2. package/dist/cli/commands/braintrust.js +77 -0
  3. package/dist/cli/commands/braintrust.js.map +1 -0
  4. package/dist/cli/commands/configure.d.ts +1 -0
  5. package/dist/cli/commands/configure.js +197 -0
  6. package/dist/cli/commands/configure.js.map +1 -1
  7. package/dist/cli/commands/experiment.js +42 -5
  8. package/dist/cli/commands/experiment.js.map +1 -1
  9. package/dist/cli/commands/harness.d.ts +3 -0
  10. package/dist/cli/commands/harness.js +260 -0
  11. package/dist/cli/commands/harness.js.map +1 -0
  12. package/dist/cli/commands/pipeline.js +58 -24
  13. package/dist/cli/commands/pipeline.js.map +1 -1
  14. package/dist/cli/commands/ralph.js +8 -3
  15. package/dist/cli/commands/ralph.js.map +1 -1
  16. package/dist/cli/commands/runtime/build.d.ts +7 -0
  17. package/dist/cli/commands/runtime/build.js +128 -0
  18. package/dist/cli/commands/runtime/build.js.map +1 -0
  19. package/dist/cli/commands/runtime/index.d.ts +3 -0
  20. package/dist/cli/commands/runtime/index.js +14 -0
  21. package/dist/cli/commands/runtime/index.js.map +1 -0
  22. package/dist/cli/commands/runtime/init.d.ts +7 -0
  23. package/dist/cli/commands/runtime/init.js +39 -0
  24. package/dist/cli/commands/runtime/init.js.map +1 -0
  25. package/dist/cli/commands/runtime/jobs/attach.d.ts +3 -0
  26. package/dist/cli/commands/runtime/jobs/attach.js +35 -0
  27. package/dist/cli/commands/runtime/jobs/attach.js.map +1 -0
  28. package/dist/cli/commands/runtime/jobs/index.d.ts +3 -0
  29. package/dist/cli/commands/runtime/jobs/index.js +16 -0
  30. package/dist/cli/commands/runtime/jobs/index.js.map +1 -0
  31. package/dist/cli/commands/runtime/jobs/logs.d.ts +3 -0
  32. package/dist/cli/commands/runtime/jobs/logs.js +27 -0
  33. package/dist/cli/commands/runtime/jobs/logs.js.map +1 -0
  34. package/dist/cli/commands/runtime/jobs/ls.d.ts +3 -0
  35. package/dist/cli/commands/runtime/jobs/ls.js +60 -0
  36. package/dist/cli/commands/runtime/jobs/ls.js.map +1 -0
  37. package/dist/cli/commands/runtime/jobs/sandbox.d.ts +3 -0
  38. package/dist/cli/commands/runtime/jobs/sandbox.js +15 -0
  39. package/dist/cli/commands/runtime/jobs/sandbox.js.map +1 -0
  40. package/dist/cli/commands/runtime/jobs/shared.d.ts +22 -0
  41. package/dist/cli/commands/runtime/jobs/shared.js +124 -0
  42. package/dist/cli/commands/runtime/jobs/shared.js.map +1 -0
  43. package/dist/cli/commands/runtime/jobs/stop.d.ts +3 -0
  44. package/dist/cli/commands/runtime/jobs/stop.js +31 -0
  45. package/dist/cli/commands/runtime/jobs/stop.js.map +1 -0
  46. package/dist/cli/commands/runtime/jobs/sync.d.ts +3 -0
  47. package/dist/cli/commands/runtime/jobs/sync.js +25 -0
  48. package/dist/cli/commands/runtime/jobs/sync.js.map +1 -0
  49. package/dist/cli/commands/runtime/shared.d.ts +20 -0
  50. package/dist/cli/commands/runtime/shared.js +69 -0
  51. package/dist/cli/commands/runtime/shared.js.map +1 -0
  52. package/dist/cli/commands/runtime/templates/clear.d.ts +3 -0
  53. package/dist/cli/commands/runtime/templates/clear.js +53 -0
  54. package/dist/cli/commands/runtime/templates/clear.js.map +1 -0
  55. package/dist/cli/commands/runtime/templates/index.d.ts +3 -0
  56. package/dist/cli/commands/runtime/templates/index.js +10 -0
  57. package/dist/cli/commands/runtime/templates/index.js.map +1 -0
  58. package/dist/cli/commands/runtime/templates/ls.d.ts +3 -0
  59. package/dist/cli/commands/runtime/templates/ls.js +52 -0
  60. package/dist/cli/commands/runtime/templates/ls.js.map +1 -0
  61. package/dist/cli/commands/runtime-options.d.ts +1 -0
  62. package/dist/cli/commands/runtime-options.js +5 -2
  63. package/dist/cli/commands/runtime-options.js.map +1 -1
  64. package/dist/cli/commands/spawn.js +27 -4
  65. package/dist/cli/commands/spawn.js.map +1 -1
  66. package/dist/cli/program.js +17 -1
  67. package/dist/cli/program.js.map +1 -1
  68. package/dist/index.js +24192 -2429
  69. package/dist/index.js.map +4 -4
  70. package/dist/providers/claude-code.js +1692 -93
  71. package/dist/providers/claude-code.js.map +4 -4
  72. package/dist/providers/codex.js +1692 -93
  73. package/dist/providers/codex.js.map +4 -4
  74. package/dist/providers/goose.js +1687 -88
  75. package/dist/providers/goose.js.map +4 -4
  76. package/dist/providers/kimi.js +1692 -93
  77. package/dist/providers/kimi.js.map +4 -4
  78. package/dist/providers/opencode.js +1692 -93
  79. package/dist/providers/opencode.js.map +4 -4
  80. package/dist/providers/poe-agent.js +1580 -308
  81. package/dist/providers/poe-agent.js.map +4 -4
  82. package/dist/providers/spawn-options.d.ts +4 -1
  83. package/dist/sdk/experiment.js +1 -0
  84. package/dist/sdk/experiment.js.map +1 -1
  85. package/dist/sdk/ralph.js +108 -16
  86. package/dist/sdk/ralph.js.map +1 -1
  87. package/dist/sdk/spawn.js +11 -4
  88. package/dist/sdk/spawn.js.map +1 -1
  89. package/dist/sdk/types.d.ts +12 -1
  90. package/dist/utils/command-checks.js +2 -29
  91. package/dist/utils/command-checks.js.map +1 -1
  92. package/package.json +12 -7
  93. package/packages/design-system/dist/components/help-formatter-plain.d.ts +4 -0
  94. package/packages/design-system/dist/components/help-formatter-plain.js +132 -0
  95. package/packages/design-system/dist/components/help-formatter.d.ts +13 -0
  96. package/packages/design-system/dist/components/help-formatter.js +116 -7
  97. package/packages/design-system/dist/components/index.d.ts +2 -2
  98. package/packages/design-system/dist/components/index.js +1 -1
  99. package/packages/design-system/dist/components/text.d.ts +1 -0
  100. package/packages/design-system/dist/components/text.js +8 -0
  101. package/packages/design-system/dist/index.d.ts +3 -2
  102. package/packages/design-system/dist/index.js +2 -1
  103. package/packages/memory/dist/index.js +1201 -115
  104. package/packages/memory/dist/index.js.map +4 -4
  105. package/packages/superintendent/dist/commands/run.d.ts +10 -0
  106. package/packages/superintendent/dist/commands/run.js +96 -49
  107. package/packages/superintendent/dist/commands/superintendent-group.d.ts +6 -0
  108. package/packages/superintendent/dist/runtime/agent-runner.d.ts +1 -0
  109. package/packages/superintendent/dist/runtime/agent-runner.js +4 -2
  110. package/packages/superintendent/dist/runtime/loop.d.ts +1 -0
@@ -206,10 +206,171 @@ import path2 from "node:path";
206
206
 
207
207
  // packages/agent-harness-tools/src/log-stream.ts
208
208
  import nodeFs from "node:fs";
209
+ var JOB_DIR = "/tmp/poe-jobs";
210
+ var POLL_INTERVAL_MS = 250;
211
+ async function* streamLogFile(env, jobId, opts) {
212
+ const fs = env.fs ?? nodeFs;
213
+ const file = jobLogPath(jobId);
214
+ let byteOffset = opts.sinceByte ?? 0;
215
+ while (true) {
216
+ if (opts.since !== void 0 && !await wasModifiedSince(fs, file, opts.since)) {
217
+ await waitForLogChange(fs, file);
218
+ continue;
219
+ }
220
+ const result = await readLogChunk(fs, file, byteOffset);
221
+ if (result !== null) {
222
+ byteOffset = result.nextByteOffset;
223
+ yield result.chunk;
224
+ continue;
225
+ }
226
+ await waitForLogChange(fs, file);
227
+ }
228
+ }
229
+ async function wasModifiedSince(fs, file, since) {
230
+ if (fs.promises.stat === void 0) {
231
+ return true;
232
+ }
233
+ try {
234
+ const stat2 = await fs.promises.stat(file);
235
+ return stat2.mtimeMs >= since.getTime();
236
+ } catch (error2) {
237
+ if (isNodeError(error2) && error2.code === "ENOENT") {
238
+ return false;
239
+ }
240
+ throw error2;
241
+ }
242
+ }
243
+ async function waitForExit(env, jobId, opts = {}) {
244
+ const fs = env.fs ?? nodeFs;
245
+ const file = jobExitPath(jobId);
246
+ while (true) {
247
+ throwIfAborted(opts.signal);
248
+ const contents = await readTextFileIfExists(fs, file);
249
+ if (contents !== null) {
250
+ const text4 = contents.trim();
251
+ const exitCode = Number(text4);
252
+ if (text4.length === 0 || !Number.isInteger(exitCode)) {
253
+ throw new Error(`Invalid exit code in ${file}: ${contents}`);
254
+ }
255
+ return { exitCode };
256
+ }
257
+ await sleep(POLL_INTERVAL_MS, opts.signal);
258
+ }
259
+ }
260
+ function jobLogPath(jobId) {
261
+ return `${JOB_DIR}/${jobId}.log`;
262
+ }
263
+ function jobExitPath(jobId) {
264
+ return `${JOB_DIR}/${jobId}.exit`;
265
+ }
266
+ async function readLogChunk(fs, file, byteOffset) {
267
+ const contents = await readFileIfExists(fs, file);
268
+ if (contents === null || byteOffset >= contents.byteLength) {
269
+ return null;
270
+ }
271
+ return {
272
+ chunk: {
273
+ byteOffset,
274
+ data: contents.subarray(byteOffset).toString("utf8")
275
+ },
276
+ nextByteOffset: contents.byteLength
277
+ };
278
+ }
279
+ async function readTextFileIfExists(fs, file) {
280
+ const contents = await readFileIfExists(fs, file);
281
+ return contents?.toString("utf8") ?? null;
282
+ }
283
+ async function readFileIfExists(fs, file) {
284
+ try {
285
+ const contents = await fs.promises.readFile(file);
286
+ return Buffer.isBuffer(contents) ? contents : Buffer.from(contents);
287
+ } catch (error2) {
288
+ if (isNodeError(error2) && error2.code === "ENOENT") {
289
+ return null;
290
+ }
291
+ throw error2;
292
+ }
293
+ }
294
+ async function waitForLogChange(fs, file) {
295
+ const watch = fs.watch;
296
+ if (typeof watch !== "function") {
297
+ await sleep(POLL_INTERVAL_MS);
298
+ return;
299
+ }
300
+ await new Promise((resolve2) => {
301
+ let watcher = null;
302
+ const timer = setTimeout(done, POLL_INTERVAL_MS);
303
+ function done() {
304
+ clearTimeout(timer);
305
+ watcher?.close();
306
+ resolve2();
307
+ }
308
+ try {
309
+ watcher = watch(file, done);
310
+ } catch {
311
+ done();
312
+ }
313
+ });
314
+ }
315
+ function sleep(ms, signal) {
316
+ return new Promise((resolve2, reject) => {
317
+ let timer = null;
318
+ const abort = () => {
319
+ if (timer !== null) {
320
+ clearTimeout(timer);
321
+ }
322
+ reject(new Error("waitForExit aborted."));
323
+ };
324
+ if (signal?.aborted) {
325
+ abort();
326
+ return;
327
+ }
328
+ timer = setTimeout(() => {
329
+ signal?.removeEventListener("abort", abort);
330
+ resolve2();
331
+ }, ms);
332
+ signal?.addEventListener("abort", abort, { once: true });
333
+ });
334
+ }
335
+ function throwIfAborted(signal) {
336
+ if (signal?.aborted) {
337
+ throw new Error("waitForExit aborted.");
338
+ }
339
+ }
340
+ function isNodeError(error2) {
341
+ return error2 instanceof Error && "code" in error2;
342
+ }
209
343
 
210
344
  // packages/agent-harness-tools/src/run-poe-command.ts
211
345
  import { randomBytes } from "node:crypto";
212
346
 
347
+ // packages/agent-harness-tools/src/binary-exists.ts
348
+ function createBinaryExistsDetectors(binaryName) {
349
+ const commonPaths = [
350
+ `/usr/local/bin/${binaryName}`,
351
+ `/usr/bin/${binaryName}`,
352
+ `$HOME/.local/bin/${binaryName}`,
353
+ `$HOME/.claude/local/bin/${binaryName}`
354
+ ];
355
+ return [
356
+ {
357
+ command: "which",
358
+ args: [binaryName],
359
+ validate: (result) => result.exitCode === 0
360
+ },
361
+ {
362
+ command: "where",
363
+ args: [binaryName],
364
+ validate: (result) => result.exitCode === 0 && result.stdout.trim().length > 0
365
+ },
366
+ {
367
+ command: "sh",
368
+ args: ["-c", commonPaths.map((p) => `test -f "${p}"`).join(" || ")],
369
+ validate: (result) => result.exitCode === 0
370
+ }
371
+ ];
372
+ }
373
+
213
374
  // packages/agent-harness-tools/src/poe-command-execution.ts
214
375
  import { existsSync as existsSync2, readFileSync } from "node:fs";
215
376
  import os3 from "node:os";
@@ -271,6 +432,11 @@ var runtimeConfigScope = {
271
432
  default: "",
272
433
  doc: "Path to the Docker build context"
273
434
  },
435
+ workspace_dir: {
436
+ type: "string",
437
+ default: "/workspace",
438
+ doc: "Sandbox-local workspace directory for E2B runtime upload, execution, and download"
439
+ },
274
440
  engine: {
275
441
  type: "string",
276
442
  default: "",
@@ -292,6 +458,11 @@ var runtimeConfigScope = {
292
458
  default: "",
293
459
  doc: "Prebuilt E2B template id"
294
460
  },
461
+ from_template: {
462
+ type: "string",
463
+ default: "",
464
+ doc: "Existing E2B template alias to extend instead of using the Dockerfile FROM image"
465
+ },
295
466
  cpu: {
296
467
  type: "json",
297
468
  default: void 0,
@@ -315,11 +486,6 @@ var runtimeConfigScope = {
315
486
  default: void 0,
316
487
  parse: parseOptionalNumber,
317
488
  doc: "Hours to keep an E2B sandbox alive after job exit"
318
- },
319
- api_key_env: {
320
- type: "string",
321
- default: "",
322
- doc: "Environment variable name containing the E2B API key"
323
489
  }
324
490
  }
325
491
  };
@@ -339,6 +505,7 @@ function parseRunner(raw) {
339
505
  detach: parseOptionalBoolean(record.detach, "runner.detach") ?? false,
340
506
  upload_max_file_mb: uploadMaxFileMb,
341
507
  download_conflict: parseDownloadConflict(record.download_conflict),
508
+ sync: parseRunnerSync(record.sync),
342
509
  workspace: parseRunnerWorkspace(record.workspace)
343
510
  });
344
511
  }
@@ -347,6 +514,7 @@ function createDefaultRunnerScope() {
347
514
  detach: false,
348
515
  upload_max_file_mb: 100,
349
516
  download_conflict: "refuse",
517
+ sync: "both",
350
518
  workspace: {
351
519
  exclude: [...defaultWorkspaceExclude]
352
520
  }
@@ -377,6 +545,15 @@ function parseDownloadConflict(value) {
377
545
  }
378
546
  throw new Error('runner.download_conflict: expected "refuse" or "overwrite".');
379
547
  }
548
+ function parseRunnerSync(value) {
549
+ if (value === void 0) {
550
+ return "both";
551
+ }
552
+ if (value === "both" || value === "upload" || value === "none") {
553
+ return value;
554
+ }
555
+ throw new Error('runner.sync: expected "both", "upload", or "none".');
556
+ }
380
557
  function parseBuildArgs(value) {
381
558
  if (value === void 0) {
382
559
  return {};
@@ -470,6 +647,43 @@ function defineScope(scope, schema) {
470
647
  schema
471
648
  };
472
649
  }
650
+ var integrationsConfigScope = defineScope("integrations", {
651
+ braintrust: {
652
+ type: "json",
653
+ default: {
654
+ enabled: false
655
+ },
656
+ parse: parseBraintrustIntegrationConfig,
657
+ doc: "Braintrust integration configuration"
658
+ }
659
+ });
660
+ function parseBraintrustIntegrationConfig(value) {
661
+ if (!isRecord(value)) {
662
+ throw new Error("expected an object");
663
+ }
664
+ const enabled = value.enabled === void 0 ? false : value.enabled;
665
+ if (typeof enabled !== "boolean") {
666
+ throw new Error("enabled must be a boolean");
667
+ }
668
+ return {
669
+ enabled,
670
+ ...optionalStringEntry("apiKey", value.apiKey),
671
+ ...optionalStringEntry("apiUrl", value.apiUrl),
672
+ ...optionalStringEntry("project", value.project)
673
+ };
674
+ }
675
+ function optionalStringEntry(key, value) {
676
+ if (value === void 0) {
677
+ return {};
678
+ }
679
+ if (typeof value !== "string") {
680
+ throw new Error(`${key} must be a string`);
681
+ }
682
+ return { [key]: value };
683
+ }
684
+ function isRecord(value) {
685
+ return typeof value === "object" && value !== null && !Array.isArray(value);
686
+ }
473
687
 
474
688
  // packages/poe-code-config/src/plan-scope.ts
475
689
  var planConfigScope = defineScope("plan", {
@@ -486,14 +700,427 @@ import path8 from "node:path";
486
700
 
487
701
  // packages/config-extends/src/discover.ts
488
702
  import path4 from "node:path";
703
+ async function findBase(name, bases, fs) {
704
+ const checkedPaths = [];
705
+ for (const basePath of bases) {
706
+ for (const extension of [".md", ".yaml", ".yml", ".json"]) {
707
+ const filePath = path4.join(basePath, `${name}${extension}`);
708
+ checkedPaths.push(filePath);
709
+ try {
710
+ return {
711
+ content: await fs.readFile(filePath, "utf8"),
712
+ filePath
713
+ };
714
+ } catch (error2) {
715
+ if (hasCode(error2, "ENOENT")) {
716
+ continue;
717
+ }
718
+ throw error2;
719
+ }
720
+ }
721
+ }
722
+ throw new Error(`Base "${name}" not found.
723
+ Checked paths:
724
+ - ${checkedPaths.join("\n- ")}`);
725
+ }
726
+ function hasCode(error2, code) {
727
+ return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === code;
728
+ }
489
729
 
490
730
  // packages/config-extends/src/parse.ts
491
731
  import path5 from "node:path";
492
732
  import matter from "gray-matter";
493
733
  import { parse as parseYaml } from "yaml";
734
+ function parseDocument(content, filePath) {
735
+ const normalizedContent = stripBom(content);
736
+ const format = detectFormat(normalizedContent, filePath);
737
+ const data = format === "markdown" ? parseMarkdown(normalizedContent) : toData(format === "json" ? JSON.parse(normalizedContent) : parseYaml(normalizedContent));
738
+ const hasExtendsField = Object.hasOwn(data, "extends");
739
+ const extendsValue = data.extends === true;
740
+ delete data.extends;
741
+ return {
742
+ data,
743
+ format,
744
+ extends: extendsValue,
745
+ hasExtendsField
746
+ };
747
+ }
748
+ function detectFormat(content, filePath) {
749
+ const extension = path5.extname(filePath).toLowerCase();
750
+ if (extension === ".md") {
751
+ return "markdown";
752
+ }
753
+ if (extension === ".yaml" || extension === ".yml") {
754
+ return "yaml";
755
+ }
756
+ if (extension === ".json") {
757
+ return "json";
758
+ }
759
+ if (content.startsWith("{")) {
760
+ return "json";
761
+ }
762
+ if (content.startsWith("---\n") || content.startsWith("---\r\n")) {
763
+ return "markdown";
764
+ }
765
+ return "yaml";
766
+ }
767
+ function parseMarkdown(content) {
768
+ const document = matter(content);
769
+ return {
770
+ ...toData(document.data),
771
+ prompt: document.content
772
+ };
773
+ }
774
+ function toData(value) {
775
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
776
+ return {};
777
+ }
778
+ return { ...value };
779
+ }
780
+ function stripBom(content) {
781
+ if (!content.startsWith("\uFEFF")) {
782
+ return content;
783
+ }
784
+ return content.slice(1);
785
+ }
786
+
787
+ // packages/config-extends/src/merge.ts
788
+ function mergeLayers(layers) {
789
+ return mergeObjectLayers(layers, []);
790
+ }
791
+ function mergeObjectLayers(layers, path22) {
792
+ const data = {};
793
+ const sources = {};
794
+ for (const key of collectKeys(layers)) {
795
+ const resolved = resolveKey(layers, key, path22);
796
+ if (resolved === void 0) {
797
+ continue;
798
+ }
799
+ data[key] = resolved.value;
800
+ Object.assign(sources, resolved.sources);
801
+ }
802
+ return { data, sources };
803
+ }
804
+ function collectKeys(layers) {
805
+ const keys = /* @__PURE__ */ new Set();
806
+ for (const layer of layers) {
807
+ for (const key of Object.keys(layer.data)) {
808
+ keys.add(key);
809
+ }
810
+ }
811
+ return [...keys];
812
+ }
813
+ function resolveKey(layers, key, path22) {
814
+ let winningSource;
815
+ let winningValue;
816
+ const objectLayers = [];
817
+ for (const layer of layers) {
818
+ const candidate = layer.data[key];
819
+ if (!isWinningCandidate(key, candidate)) {
820
+ continue;
821
+ }
822
+ if (winningSource === void 0) {
823
+ winningSource = layer.source;
824
+ winningValue = candidate;
825
+ if (isPlainObject(candidate)) {
826
+ objectLayers.push({
827
+ source: layer.source,
828
+ data: candidate
829
+ });
830
+ }
831
+ continue;
832
+ }
833
+ if (isPlainObject(winningValue) && isPlainObject(candidate)) {
834
+ objectLayers.push({
835
+ source: layer.source,
836
+ data: candidate
837
+ });
838
+ }
839
+ }
840
+ if (winningSource === void 0) {
841
+ return void 0;
842
+ }
843
+ const fullPath = buildPath(path22, key);
844
+ if (isPlainObject(winningValue)) {
845
+ const merged = mergeObjectLayers(objectLayers, [...path22, key]);
846
+ return {
847
+ value: merged.data,
848
+ sources: {
849
+ [fullPath]: winningSource,
850
+ ...merged.sources
851
+ }
852
+ };
853
+ }
854
+ return {
855
+ value: cloneValue(winningValue),
856
+ sources: {
857
+ [fullPath]: winningSource
858
+ }
859
+ };
860
+ }
861
+ function isWinningCandidate(key, value) {
862
+ if (value === void 0) {
863
+ return false;
864
+ }
865
+ if (key === "prompt" && value === "") {
866
+ return false;
867
+ }
868
+ return true;
869
+ }
870
+ function buildPath(path22, key) {
871
+ return [...path22, key].join(".");
872
+ }
873
+ function isPlainObject(value) {
874
+ if (value === null || Array.isArray(value) || typeof value !== "object") {
875
+ return false;
876
+ }
877
+ const prototype = Object.getPrototypeOf(value);
878
+ return prototype === Object.prototype || prototype === null;
879
+ }
880
+ function cloneValue(value) {
881
+ if (Array.isArray(value)) {
882
+ return value.map((entry) => cloneValue(entry));
883
+ }
884
+ if (!isPlainObject(value)) {
885
+ return value;
886
+ }
887
+ const clone = Object.create(Object.getPrototypeOf(value));
888
+ for (const [key, entry] of Object.entries(value)) {
889
+ clone[key] = cloneValue(entry);
890
+ }
891
+ return clone;
892
+ }
494
893
 
495
894
  // packages/config-extends/src/resolve.ts
496
895
  import path6 from "node:path";
896
+ var MAX_EXTENDS_DEPTH = 5;
897
+ var YIELD_TOKEN = "{{yield}}";
898
+ async function resolve(chain, options) {
899
+ const { baseLayers, documentIndex, documentLayer } = classifyChain(chain);
900
+ const parsedDocument = parseDocument(documentLayer.content, documentLayer.filePath);
901
+ const resolvedBase = shouldResolveBase(parsedDocument, options.autoExtend) ? await resolveBaseChain({
902
+ name: documentLayer.baseName ?? getBaseName(documentLayer.filePath),
903
+ baseLayers,
904
+ options,
905
+ optional: !parsedDocument.extends,
906
+ visited: /* @__PURE__ */ new Set([documentLayer.filePath]),
907
+ depth: 1
908
+ }) : void 0;
909
+ const composedPrompt = composePromptChain(
910
+ {
911
+ source: documentLayer.source,
912
+ data: parsedDocument.data
913
+ },
914
+ resolvedBase?.layers ?? []
915
+ );
916
+ const merged = mergeLayers([
917
+ ...collectDataLayers(chain.slice(0, documentIndex)),
918
+ {
919
+ source: documentLayer.source,
920
+ data: withResolvedPrompt(parsedDocument.data, composedPrompt?.prompt)
921
+ },
922
+ ...stripResolvedBasePrompts(resolvedBase?.layers ?? [], composedPrompt?.consumedBaseIndexes ?? /* @__PURE__ */ new Set()),
923
+ ...collectDataLayers(chain.slice(documentIndex + 1))
924
+ ]);
925
+ if (composedPrompt !== void 0 && merged.sources.prompt === documentLayer.source && composedPrompt.source !== void 0) {
926
+ merged.sources.prompt = composedPrompt.source;
927
+ }
928
+ return {
929
+ data: merged.data,
930
+ sources: merged.sources,
931
+ chain: [documentLayer.filePath, ...resolvedBase?.chain ?? []]
932
+ };
933
+ }
934
+ function classifyChain(chain) {
935
+ const baseLayers = [];
936
+ const documentLayers = [];
937
+ for (const [index, layer] of chain.entries()) {
938
+ if (isDataLayer(layer)) {
939
+ continue;
940
+ }
941
+ if (isDocumentLayer(layer)) {
942
+ documentLayers.push({ index, layer });
943
+ continue;
944
+ }
945
+ if (isBaseLayer(layer)) {
946
+ baseLayers.push(layer);
947
+ }
948
+ }
949
+ if (documentLayers.length !== 1) {
950
+ throw new Error(`Exactly one document layer is required, received ${documentLayers.length}.`);
951
+ }
952
+ return {
953
+ baseLayers,
954
+ documentIndex: documentLayers[0].index,
955
+ documentLayer: documentLayers[0].layer
956
+ };
957
+ }
958
+ async function resolveBaseChain({
959
+ name,
960
+ baseLayers,
961
+ options,
962
+ optional,
963
+ visited,
964
+ depth
965
+ }) {
966
+ if (depth > MAX_EXTENDS_DEPTH) {
967
+ throw new Error(`Maximum extends depth exceeded (${MAX_EXTENDS_DEPTH}).`);
968
+ }
969
+ let discoveredBase;
970
+ try {
971
+ discoveredBase = await findBase(
972
+ name,
973
+ baseLayers.map((layer) => layer.path),
974
+ options.fs
975
+ );
976
+ } catch (error2) {
977
+ if (optional && isBaseNotFoundError(error2)) {
978
+ return void 0;
979
+ }
980
+ throw error2;
981
+ }
982
+ if (visited.has(discoveredBase.filePath)) {
983
+ if (optional) {
984
+ return void 0;
985
+ }
986
+ throw new Error(
987
+ `Circular extends detected.
988
+ Visited files:
989
+ - ${[...visited, discoveredBase.filePath].join("\n- ")}`
990
+ );
991
+ }
992
+ const matchedBaseIndex = baseLayers.findIndex(
993
+ (layer) => layer.path === path6.dirname(discoveredBase.filePath)
994
+ );
995
+ if (matchedBaseIndex === -1) {
996
+ throw new Error(`Resolved base is outside configured base paths: ${discoveredBase.filePath}`);
997
+ }
998
+ const parsedBase = parseDocument(discoveredBase.content, discoveredBase.filePath);
999
+ const nextVisited = new Set(visited);
1000
+ nextVisited.add(discoveredBase.filePath);
1001
+ const nestedBase = parsedBase.extends ? await resolveBaseChain({
1002
+ name: getBaseName(discoveredBase.filePath),
1003
+ baseLayers: baseLayers.slice(matchedBaseIndex + 1),
1004
+ options,
1005
+ optional: false,
1006
+ visited: nextVisited,
1007
+ depth: depth + 1
1008
+ }) : void 0;
1009
+ return {
1010
+ layers: [
1011
+ {
1012
+ source: baseLayers[matchedBaseIndex].source,
1013
+ data: parsedBase.data
1014
+ },
1015
+ ...nestedBase?.layers ?? []
1016
+ ],
1017
+ chain: [discoveredBase.filePath, ...nestedBase?.chain ?? []]
1018
+ };
1019
+ }
1020
+ function collectDataLayers(chain) {
1021
+ return chain.filter(isDataLayer);
1022
+ }
1023
+ function composePromptChain(documentLayer, baseLayers) {
1024
+ const documentPrompt = documentLayer.data.prompt;
1025
+ if (documentPrompt !== void 0 && typeof documentPrompt !== "string") {
1026
+ return void 0;
1027
+ }
1028
+ if (documentPrompt !== void 0) {
1029
+ assertValidYieldCount(documentPrompt);
1030
+ }
1031
+ let prompt = documentPrompt;
1032
+ let source = prompt === void 0 || prompt === "" ? void 0 : documentLayer.source;
1033
+ const consumedBaseIndexes = /* @__PURE__ */ new Set();
1034
+ for (const [index, layer] of baseLayers.entries()) {
1035
+ const candidate = layer.data.prompt;
1036
+ if (candidate === void 0) {
1037
+ continue;
1038
+ }
1039
+ if (typeof candidate !== "string") {
1040
+ break;
1041
+ }
1042
+ assertValidYieldCount(candidate);
1043
+ consumedBaseIndexes.add(index);
1044
+ prompt = composeAdjacentPrompts(prompt, candidate);
1045
+ if (source === void 0 && candidate !== "") {
1046
+ source = layer.source;
1047
+ }
1048
+ }
1049
+ if (prompt !== void 0 && prompt.includes(YIELD_TOKEN)) {
1050
+ throw new Error('Final resolved prompt contains an unresolved "{{yield}}" token.');
1051
+ }
1052
+ if (prompt === void 0) {
1053
+ return void 0;
1054
+ }
1055
+ return {
1056
+ consumedBaseIndexes,
1057
+ prompt,
1058
+ source
1059
+ };
1060
+ }
1061
+ function composeAdjacentPrompts(high, low) {
1062
+ if (high === void 0 || high === "") {
1063
+ return low.includes(YIELD_TOKEN) ? replaceYield(low, "") : low;
1064
+ }
1065
+ if (high.includes(YIELD_TOKEN)) {
1066
+ return replaceYield(high, low);
1067
+ }
1068
+ if (low.includes(YIELD_TOKEN)) {
1069
+ return replaceYield(low, high);
1070
+ }
1071
+ return high;
1072
+ }
1073
+ function replaceYield(prompt, replacement) {
1074
+ return prompt.replace(YIELD_TOKEN, replacement);
1075
+ }
1076
+ function assertValidYieldCount(prompt) {
1077
+ if (countYieldTokens(prompt) > 1) {
1078
+ throw new Error('Prompt composition supports exactly one "{{yield}}" token per prompt.');
1079
+ }
1080
+ }
1081
+ function countYieldTokens(prompt) {
1082
+ return prompt.split(YIELD_TOKEN).length - 1;
1083
+ }
1084
+ function withResolvedPrompt(data, prompt) {
1085
+ if (prompt === void 0) {
1086
+ return data;
1087
+ }
1088
+ return {
1089
+ ...data,
1090
+ prompt
1091
+ };
1092
+ }
1093
+ function stripResolvedBasePrompts(layers, consumedBaseIndexes) {
1094
+ return layers.map((layer, index) => {
1095
+ if (!consumedBaseIndexes.has(index) || typeof layer.data.prompt !== "string") {
1096
+ return layer;
1097
+ }
1098
+ const { prompt: ignoredPrompt, ...data } = layer.data;
1099
+ void ignoredPrompt;
1100
+ return {
1101
+ source: layer.source,
1102
+ data
1103
+ };
1104
+ });
1105
+ }
1106
+ function getBaseName(filePath) {
1107
+ return path6.basename(filePath, path6.extname(filePath));
1108
+ }
1109
+ function shouldResolveBase(parsedDocument, autoExtend) {
1110
+ return parsedDocument.extends || autoExtend === true && !parsedDocument.hasExtendsField;
1111
+ }
1112
+ function isBaseNotFoundError(error2) {
1113
+ return error2 instanceof Error && error2.message.startsWith('Base "') && error2.message.includes('" not found.\nChecked paths:');
1114
+ }
1115
+ function isDataLayer(layer) {
1116
+ return "data" in layer;
1117
+ }
1118
+ function isDocumentLayer(layer) {
1119
+ return "filePath" in layer && "content" in layer;
1120
+ }
1121
+ function isBaseLayer(layer) {
1122
+ return "path" in layer;
1123
+ }
497
1124
 
498
1125
  // packages/config-mutations/src/mutations/config-mutation.ts
499
1126
  function merge(options) {
@@ -841,16 +1468,16 @@ function getConfigFormat(pathOrFormat) {
841
1468
  }
842
1469
  return formatRegistry[formatName];
843
1470
  }
844
- function detectFormat(path18) {
845
- const ext = getExtension(path18);
1471
+ function detectFormat2(path22) {
1472
+ const ext = getExtension(path22);
846
1473
  return extensionMap[ext];
847
1474
  }
848
- function getExtension(path18) {
849
- const lastDot = path18.lastIndexOf(".");
1475
+ function getExtension(path22) {
1476
+ const lastDot = path22.lastIndexOf(".");
850
1477
  if (lastDot === -1) {
851
1478
  return "";
852
1479
  }
853
- return path18.slice(lastDot).toLowerCase();
1480
+ return path22.slice(lastDot).toLowerCase();
854
1481
  }
855
1482
 
856
1483
  // packages/config-mutations/src/execution/path-utils.ts
@@ -901,7 +1528,7 @@ function resolvePath(rawPath, homeDir, pathMapper) {
901
1528
  function isNotFound(error2) {
902
1529
  return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
903
1530
  }
904
- async function readFileIfExists(fs, target) {
1531
+ async function readFileIfExists2(fs, target) {
905
1532
  try {
906
1533
  return await fs.readFile(target, "utf8");
907
1534
  } catch (error2) {
@@ -1185,7 +1812,7 @@ async function applyBackup(mutation, context, options) {
1185
1812
  label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1186
1813
  targetPath
1187
1814
  };
1188
- const content = await readFileIfExists(context.fs, targetPath);
1815
+ const content = await readFileIfExists2(context.fs, targetPath);
1189
1816
  if (content === null) {
1190
1817
  return {
1191
1818
  outcome: { changed: false, effect: "none", detail: "noop" },
@@ -1209,14 +1836,14 @@ async function applyConfigMerge(mutation, context, options) {
1209
1836
  label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1210
1837
  targetPath
1211
1838
  };
1212
- const formatName = mutation.format ?? detectFormat(rawPath);
1839
+ const formatName = mutation.format ?? detectFormat2(rawPath);
1213
1840
  if (!formatName) {
1214
1841
  throw new Error(
1215
1842
  `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1216
1843
  );
1217
1844
  }
1218
1845
  const format = getConfigFormat(formatName);
1219
- const rawContent = await readFileIfExists(context.fs, targetPath);
1846
+ const rawContent = await readFileIfExists2(context.fs, targetPath);
1220
1847
  let current;
1221
1848
  try {
1222
1849
  current = rawContent === null ? {} : format.parse(rawContent);
@@ -1255,14 +1882,14 @@ async function applyConfigPrune(mutation, context, options) {
1255
1882
  label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1256
1883
  targetPath
1257
1884
  };
1258
- const rawContent = await readFileIfExists(context.fs, targetPath);
1885
+ const rawContent = await readFileIfExists2(context.fs, targetPath);
1259
1886
  if (rawContent === null) {
1260
1887
  return {
1261
1888
  outcome: { changed: false, effect: "none", detail: "noop" },
1262
1889
  details
1263
1890
  };
1264
1891
  }
1265
- const formatName = mutation.format ?? detectFormat(rawPath);
1892
+ const formatName = mutation.format ?? detectFormat2(rawPath);
1266
1893
  if (!formatName) {
1267
1894
  throw new Error(
1268
1895
  `Cannot detect config format for "${rawPath}". Provide explicit format option.`
@@ -1318,14 +1945,14 @@ async function applyConfigTransform(mutation, context, options) {
1318
1945
  label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1319
1946
  targetPath
1320
1947
  };
1321
- const formatName = mutation.format ?? detectFormat(rawPath);
1948
+ const formatName = mutation.format ?? detectFormat2(rawPath);
1322
1949
  if (!formatName) {
1323
1950
  throw new Error(
1324
1951
  `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1325
1952
  );
1326
1953
  }
1327
1954
  const format = getConfigFormat(formatName);
1328
- const rawContent = await readFileIfExists(context.fs, targetPath);
1955
+ const rawContent = await readFileIfExists2(context.fs, targetPath);
1329
1956
  let current;
1330
1957
  try {
1331
1958
  current = rawContent === null ? {} : format.parse(rawContent);
@@ -1425,7 +2052,7 @@ async function applyTemplateMerge(mutation, context, options, formatName) {
1425
2052
  { cause: error2 }
1426
2053
  );
1427
2054
  }
1428
- const rawContent = await readFileIfExists(context.fs, targetPath);
2055
+ const rawContent = await readFileIfExists2(context.fs, targetPath);
1429
2056
  let current;
1430
2057
  try {
1431
2058
  current = rawContent === null ? {} : format.parse(rawContent);
@@ -1501,23 +2128,215 @@ import Mustache2 from "mustache";
1501
2128
  var originalEscape = Mustache2.escape;
1502
2129
 
1503
2130
  // packages/poe-code-config/src/store.ts
1504
- var EMPTY_DOCUMENT = `${JSON.stringify({}, null, 2)}
1505
- `;
1506
-
1507
- // packages/poe-code-config/src/inspect.ts
1508
- import path9 from "node:path";
1509
- var EMPTY_DOCUMENT2 = `${JSON.stringify({}, null, 2)}
1510
- `;
1511
-
1512
- // packages/poe-code-config/src/state/index.ts
1513
- import os2 from "node:os";
1514
-
1515
- // packages/poe-code-config/src/state/jobs.ts
1516
- import path10 from "node:path";
1517
-
1518
- // packages/poe-code-config/src/state/fs.ts
1519
- import * as nodeFs2 from "node:fs/promises";
1520
-
2131
+ async function readMergedDocument(fs, globalPath, projectPath) {
2132
+ const globalDocument = await readStoredDocument(fs, globalPath);
2133
+ if (!projectPath || projectPath === globalPath) {
2134
+ return globalDocument.data;
2135
+ }
2136
+ const projectDocument = await readStoredDocument(fs, projectPath);
2137
+ const resolved = await resolve(
2138
+ [
2139
+ {
2140
+ source: "project",
2141
+ filePath: projectPath,
2142
+ content: projectDocument.content
2143
+ },
2144
+ {
2145
+ source: "base",
2146
+ path: path8.dirname(globalPath)
2147
+ }
2148
+ ],
2149
+ {
2150
+ fs: createResolvedConfigFs(fs, globalPath, globalDocument.content),
2151
+ autoExtend: true
2152
+ }
2153
+ );
2154
+ return normalizeDocument(resolved.data);
2155
+ }
2156
+ async function readStoredDocument(fs, filePath) {
2157
+ try {
2158
+ const raw = await fs.readFile(filePath, "utf8");
2159
+ return await parseStoredDocument(fs, filePath, raw);
2160
+ } catch (error2) {
2161
+ if (isNotFound(error2)) {
2162
+ return {
2163
+ content: EMPTY_DOCUMENT,
2164
+ data: {}
2165
+ };
2166
+ }
2167
+ throw error2;
2168
+ }
2169
+ }
2170
+ async function parseStoredDocument(fs, filePath, raw) {
2171
+ try {
2172
+ return {
2173
+ content: raw,
2174
+ data: normalizeDocument(JSON.parse(raw))
2175
+ };
2176
+ } catch (error2) {
2177
+ if (error2 instanceof SyntaxError) {
2178
+ await recoverInvalidDocument(fs, filePath, raw);
2179
+ return {
2180
+ content: EMPTY_DOCUMENT,
2181
+ data: {}
2182
+ };
2183
+ }
2184
+ throw error2;
2185
+ }
2186
+ }
2187
+ function normalizeDocument(value) {
2188
+ if (!isRecord2(value)) {
2189
+ return {};
2190
+ }
2191
+ const document = {};
2192
+ for (const [scope, scopeValues] of Object.entries(value)) {
2193
+ const normalizedValues = normalizeScopeValues(scopeValues);
2194
+ if (Object.keys(normalizedValues).length > 0) {
2195
+ document[scope] = normalizedValues;
2196
+ }
2197
+ }
2198
+ return document;
2199
+ }
2200
+ function normalizeScopeValues(value) {
2201
+ if (!isRecord2(value)) {
2202
+ return {};
2203
+ }
2204
+ const normalized = {};
2205
+ for (const [key, entry] of Object.entries(value)) {
2206
+ if (entry !== void 0) {
2207
+ normalized[key] = entry;
2208
+ }
2209
+ }
2210
+ return normalized;
2211
+ }
2212
+ function createResolvedConfigFs(fs, globalPath, globalContent) {
2213
+ return {
2214
+ readFile(filePath, _encoding) {
2215
+ if (filePath === globalPath) {
2216
+ return Promise.resolve(globalContent);
2217
+ }
2218
+ return fs.readFile(filePath, "utf8");
2219
+ }
2220
+ };
2221
+ }
2222
+ async function recoverInvalidDocument(fs, filePath, content) {
2223
+ await fs.mkdir(path8.dirname(filePath), { recursive: true });
2224
+ const backupPath = createInvalidBackupPath(filePath);
2225
+ await fs.writeFile(backupPath, content, { encoding: "utf8" });
2226
+ await fs.writeFile(filePath, EMPTY_DOCUMENT, { encoding: "utf8" });
2227
+ }
2228
+ function createInvalidBackupPath(filePath) {
2229
+ const directory = path8.dirname(filePath);
2230
+ const baseName = path8.basename(filePath);
2231
+ return path8.join(directory, `${baseName}.invalid-${createTimestamp()}.json`);
2232
+ }
2233
+ function isRecord2(value) {
2234
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
2235
+ }
2236
+ function resolveConfigPath(homeDir) {
2237
+ return path8.join(homeDir, ".poe-code", "config.json");
2238
+ }
2239
+ function resolveProjectConfigPath(cwd) {
2240
+ return path8.join(cwd, ".poe-code", "config.json");
2241
+ }
2242
+ var EMPTY_DOCUMENT = `${JSON.stringify({}, null, 2)}
2243
+ `;
2244
+
2245
+ // packages/poe-code-config/src/resolve.ts
2246
+ function resolveScope(schema, fileValues, env = {}) {
2247
+ const resolved = {};
2248
+ for (const key of Object.keys(schema)) {
2249
+ const field = schema[key];
2250
+ const envValue = resolveEnvValue(field, env, key);
2251
+ const fileValue = resolveFileValue(field, fileValues?.[key], key);
2252
+ resolved[key] = envValue ?? fileValue ?? field.default;
2253
+ }
2254
+ return resolved;
2255
+ }
2256
+ function resolveEnvValue(field, env, key) {
2257
+ if (!field.env) {
2258
+ return void 0;
2259
+ }
2260
+ const raw = env[field.env];
2261
+ if (raw === void 0) {
2262
+ return void 0;
2263
+ }
2264
+ return coerceValue(field, raw, key);
2265
+ }
2266
+ function resolveFileValue(field, value, key) {
2267
+ return coerceValue(field, value, key);
2268
+ }
2269
+ function coerceValue(field, value, key) {
2270
+ switch (field.type) {
2271
+ case "string":
2272
+ return typeof value === "string" ? value : void 0;
2273
+ case "number":
2274
+ return coerceNumber(value);
2275
+ case "boolean":
2276
+ return coerceBoolean(value);
2277
+ case "json":
2278
+ return coerceJson(field, value, key);
2279
+ }
2280
+ }
2281
+ function coerceNumber(value) {
2282
+ if (typeof value === "number" && Number.isFinite(value)) {
2283
+ return value;
2284
+ }
2285
+ if (typeof value !== "string" || value.length === 0) {
2286
+ return void 0;
2287
+ }
2288
+ const parsed = Number(value);
2289
+ return Number.isNaN(parsed) ? void 0 : parsed;
2290
+ }
2291
+ function coerceBoolean(value) {
2292
+ if (typeof value === "boolean") {
2293
+ return value;
2294
+ }
2295
+ if (value === "true" || value === "1") {
2296
+ return true;
2297
+ }
2298
+ if (value === "false" || value === "0") {
2299
+ return false;
2300
+ }
2301
+ return void 0;
2302
+ }
2303
+ function coerceJson(field, value, key) {
2304
+ if (value === void 0) {
2305
+ return void 0;
2306
+ }
2307
+ const parsedValue = parseJsonValue(value, key);
2308
+ try {
2309
+ return field.parse(parsedValue);
2310
+ } catch (error2) {
2311
+ const message2 = error2 instanceof Error ? error2.message : "Invalid JSON value.";
2312
+ throw new Error(`Invalid config value for "${key}": ${message2}`);
2313
+ }
2314
+ }
2315
+ function parseJsonValue(value, key) {
2316
+ if (typeof value !== "string") {
2317
+ return value;
2318
+ }
2319
+ try {
2320
+ return JSON.parse(value);
2321
+ } catch {
2322
+ throw new Error(`Invalid config value for "${key}": expected valid JSON.`);
2323
+ }
2324
+ }
2325
+
2326
+ // packages/poe-code-config/src/inspect.ts
2327
+ import path9 from "node:path";
2328
+ var EMPTY_DOCUMENT2 = `${JSON.stringify({}, null, 2)}
2329
+ `;
2330
+
2331
+ // packages/poe-code-config/src/state/index.ts
2332
+ import os2 from "node:os";
2333
+
2334
+ // packages/poe-code-config/src/state/jobs.ts
2335
+ import path10 from "node:path";
2336
+
2337
+ // packages/poe-code-config/src/state/fs.ts
2338
+ import * as nodeFs2 from "node:fs/promises";
2339
+
1521
2340
  // packages/poe-code-config/src/state/templates.ts
1522
2341
  import path11 from "node:path";
1523
2342
 
@@ -1760,14 +2579,15 @@ var dockerExecutionEnvFactory = {
1760
2579
  context
1761
2580
  });
1762
2581
  },
1763
- async attach(envId) {
2582
+ async attach(envId, context) {
1764
2583
  const engine = detectEngine();
1765
2584
  return createDockerEnv({
1766
2585
  id: envId,
1767
- spec: createAttachedSpec(),
2586
+ spec: createAttachedSpec(context?.cwd),
1768
2587
  runner: createHostRunner(),
1769
2588
  engine,
1770
- context: detectContext()
2589
+ context: detectContext(),
2590
+ attachedJobId: context?.jobId
1771
2591
  });
1772
2592
  }
1773
2593
  };
@@ -1775,7 +2595,7 @@ function createDockerEnv(input) {
1775
2595
  const containerRef = input.id;
1776
2596
  return {
1777
2597
  id: containerRef,
1778
- job: null,
2598
+ job: input.attachedJobId === void 0 ? null : createContainerJob(containerRef, input.runner, input.engine, input.context, input.attachedJobId),
1779
2599
  async uploadWorkspace() {
1780
2600
  const tempDir = mkdtempSync(path14.join(tmpdir(), "poe-docker-upload-"));
1781
2601
  const archivePath = path14.join(tempDir, "workspace.tar");
@@ -1910,35 +2730,57 @@ async function resolveImage(input) {
1910
2730
  if (input.runtime.image !== void 0) {
1911
2731
  return input.runtime.image;
1912
2732
  }
2733
+ const result = await buildDockerRuntimeTemplate({
2734
+ cwd: input.spec.cwd,
2735
+ runtime: input.runtime,
2736
+ state: input.spec.state,
2737
+ runner: input.runner
2738
+ });
2739
+ return result.image;
2740
+ }
2741
+ async function buildDockerRuntimeTemplate(input) {
2742
+ const runner = input.runner ?? createHostRunner();
2743
+ const engine = input.runtime.engine ?? detectEngine();
2744
+ const context = detectContext();
1913
2745
  const dockerfilePath = path14.resolve(
1914
- input.spec.cwd,
2746
+ input.cwd,
1915
2747
  input.runtime.dockerfile ?? path14.join(".poe-code", "Dockerfile")
1916
2748
  );
1917
- const buildContext = path14.resolve(input.spec.cwd, input.runtime.build_context ?? ".");
2749
+ const buildContext = path14.resolve(input.cwd, input.runtime.build_context ?? ".");
1918
2750
  const dockerfileBytes = await readFile2(dockerfilePath);
1919
2751
  const hash = hashDockerTemplate(dockerfileBytes, input.runtime.build_args ?? {});
1920
- const cached2 = await input.spec.state?.templates.get("docker", hash);
2752
+ const cached2 = input.force ? null : await input.state?.templates.get("docker", hash);
1921
2753
  if (cached2?.image !== void 0) {
1922
- return cached2.image;
2754
+ return {
2755
+ backend: "docker",
2756
+ hash,
2757
+ image: cached2.image,
2758
+ cached: true
2759
+ };
1923
2760
  }
1924
2761
  const image = `poe-code/local:${hash}`;
1925
2762
  await buildImage({
1926
- runner: input.runner,
1927
- engine: input.engine,
1928
- context: input.context,
2763
+ runner,
2764
+ engine,
2765
+ context,
1929
2766
  image,
1930
2767
  dockerfilePath,
1931
2768
  buildContext,
1932
2769
  buildArgs: input.runtime.build_args ?? {}
1933
2770
  });
1934
- await input.spec.state?.templates.put("docker", {
2771
+ await input.state?.templates.put("docker", {
1935
2772
  hash,
1936
2773
  image,
1937
2774
  runtime_type: "docker",
1938
2775
  dockerfile_path: dockerfilePath,
1939
2776
  built_at: (/* @__PURE__ */ new Date()).toISOString()
1940
2777
  });
1941
- return image;
2778
+ return {
2779
+ backend: "docker",
2780
+ hash,
2781
+ image,
2782
+ cached: false
2783
+ };
1942
2784
  }
1943
2785
  function hashDockerTemplate(dockerfileBytes, buildArgs) {
1944
2786
  const hash = createHash2("sha256");
@@ -2023,9 +2865,9 @@ function buildEnvArgs(env) {
2023
2865
  function createContainerName() {
2024
2866
  return `poe-env-${randomBytes3(6).toString("hex")}`;
2025
2867
  }
2026
- async function createContainerJob(containerId, runner, engine, context) {
2868
+ function createContainerJob(containerId, runner, engine, context, jobId = containerId) {
2027
2869
  return {
2028
- id: containerId,
2870
+ id: jobId,
2029
2871
  envId: containerId,
2030
2872
  tool: "docker",
2031
2873
  argv: ["attach", containerId],
@@ -2049,7 +2891,25 @@ async function createContainerJob(containerId, runner, engine, context) {
2049
2891
  }
2050
2892
  return stdout.trim() === "running" ? "running" : "exited";
2051
2893
  },
2052
- async *stream() {
2894
+ async *stream(opts) {
2895
+ const handle = runner.exec({
2896
+ command: engine,
2897
+ args: [
2898
+ ...buildContextArgs(engine, context),
2899
+ "exec",
2900
+ containerId,
2901
+ "sh",
2902
+ "-c",
2903
+ `test -f ${shellQuote(`/tmp/poe-jobs/${jobId}.log`)} && tail -c +${(opts?.sinceByte ?? 0) + 1} ${shellQuote(`/tmp/poe-jobs/${jobId}.log`)} || true`
2904
+ ],
2905
+ stdout: "pipe",
2906
+ stderr: "pipe"
2907
+ });
2908
+ const stdout = await readStream(handle.stdout);
2909
+ await handle.result;
2910
+ if (stdout.length > 0) {
2911
+ yield { byteOffset: opts?.sinceByte ?? 0, data: stdout };
2912
+ }
2053
2913
  },
2054
2914
  async wait() {
2055
2915
  const handle = runner.exec({
@@ -2073,9 +2933,9 @@ async function createContainerJob(containerId, runner, engine, context) {
2073
2933
  }
2074
2934
  };
2075
2935
  }
2076
- function createAttachedSpec() {
2936
+ function createAttachedSpec(cwd = "/workspace") {
2077
2937
  return {
2078
- cwd: "/workspace",
2938
+ cwd,
2079
2939
  runtime: {
2080
2940
  type: "docker",
2081
2941
  image: "attached",
@@ -2147,12 +3007,778 @@ var hostExecutionEnvFactory = {
2147
3007
  // packages/process-runner/src/testing/mock-runner.ts
2148
3008
  import { Readable, Writable } from "node:stream";
2149
3009
 
3010
+ // packages/runner-e2b/src/factory.ts
3011
+ import path18 from "node:path";
3012
+
3013
+ // packages/runner-e2b/src/sdk.ts
3014
+ import { Template, Sandbox } from "e2b";
3015
+ async function createSandbox(opts) {
3016
+ return Sandbox.create(opts.templateId, {
3017
+ apiKey: opts.apiKey,
3018
+ envs: opts.env,
3019
+ ...opts.timeoutMinutes === void 0 ? {} : { timeoutMs: opts.timeoutMinutes * 6e4 }
3020
+ });
3021
+ }
3022
+ async function connectSandbox(id, apiKey) {
3023
+ return Sandbox.connect(id, apiKey === void 0 ? void 0 : { apiKey });
3024
+ }
3025
+ async function buildTemplate(opts) {
3026
+ const template = Template({ fileContextPath: opts.buildContext }).fromDockerfile(
3027
+ opts.dockerfilePath
3028
+ );
3029
+ if (opts.fromTemplate !== void 0 && opts.fromTemplate.length > 0) {
3030
+ template.fromTemplate(opts.fromTemplate);
3031
+ }
3032
+ const result = await Template.build(template, opts.name, {
3033
+ apiKey: opts.apiKey,
3034
+ ...opts.cpu === void 0 ? {} : { cpuCount: opts.cpu },
3035
+ ...opts.memoryMb === void 0 ? {} : { memoryMB: opts.memoryMb },
3036
+ ...opts.onLog ? { onBuildLogs: opts.onLog } : {}
3037
+ });
3038
+ return { templateId: result.templateId };
3039
+ }
3040
+ function toArrayBuffer(buffer) {
3041
+ const output = new ArrayBuffer(buffer.byteLength);
3042
+ new Uint8Array(output).set(buffer);
3043
+ return output;
3044
+ }
3045
+ async function readableToString(stream) {
3046
+ if (stream === null) {
3047
+ return "";
3048
+ }
3049
+ stream.setEncoding("utf8");
3050
+ const chunks = [];
3051
+ for await (const chunk of stream) {
3052
+ chunks.push(String(chunk));
3053
+ }
3054
+ return chunks.join("");
3055
+ }
3056
+
3057
+ // packages/runner-e2b/src/template-build.ts
3058
+ import { createHash as createHash3 } from "node:crypto";
3059
+ import { readdir, readFile as readFile3 } from "node:fs/promises";
3060
+ import path15 from "node:path";
3061
+ var BUILD_LOG_TAIL_SIZE = 30;
3062
+ async function buildE2bRuntimeTemplate(input) {
3063
+ const dockerfileBytes = await readFile3(input.dockerfilePath);
3064
+ const buildContextFiles = await readBuildContextFiles(input.buildContext);
3065
+ const hash = hashTemplate(dockerfileBytes, buildContextFiles, input.runtime.build_args);
3066
+ const cached2 = input.force === true ? null : await input.state?.templates.get("e2b", hash);
3067
+ if (cached2?.template_id !== void 0) {
3068
+ return { backend: "e2b", hash, templateId: cached2.template_id, cached: true };
3069
+ }
3070
+ const tail = [];
3071
+ const onLog = (entry) => {
3072
+ tail.push(entry.message);
3073
+ if (tail.length > BUILD_LOG_TAIL_SIZE) {
3074
+ tail.shift();
3075
+ }
3076
+ input.onLog?.(entry);
3077
+ };
3078
+ let built;
3079
+ try {
3080
+ built = await buildTemplate({
3081
+ apiKey: input.apiKey,
3082
+ name: `poe-code-${hash.slice(0, 32)}`,
3083
+ dockerfilePath: input.dockerfilePath,
3084
+ buildContext: input.buildContext,
3085
+ cpu: input.runtime.cpu,
3086
+ memoryMb: input.runtime.memory_mb,
3087
+ fromTemplate: input.runtime.from_template,
3088
+ onLog
3089
+ });
3090
+ } catch (error2) {
3091
+ throw decorateBuildError(error2, tail);
3092
+ }
3093
+ await input.state?.templates.put("e2b", {
3094
+ hash,
3095
+ template_id: built.templateId,
3096
+ runtime_type: "e2b",
3097
+ dockerfile_path: input.dockerfilePath,
3098
+ built_at: (/* @__PURE__ */ new Date()).toISOString()
3099
+ });
3100
+ return { backend: "e2b", hash, templateId: built.templateId, cached: false };
3101
+ }
3102
+ function decorateBuildError(error2, tail) {
3103
+ const original = error2 instanceof Error ? error2 : new Error(String(error2));
3104
+ if (tail.length === 0) {
3105
+ return original;
3106
+ }
3107
+ const decorated = new Error(`${original.message}
3108
+
3109
+ Last build output:
3110
+ ${tail.join("\n")}`);
3111
+ decorated.stack = original.stack;
3112
+ decorated.cause = original;
3113
+ return decorated;
3114
+ }
3115
+ function hashTemplate(dockerfileBytes, buildContextFiles, buildArgs) {
3116
+ const hash = createHash3("sha256");
3117
+ hash.update(dockerfileBytes);
3118
+ hash.update("\0");
3119
+ for (const file of buildContextFiles) {
3120
+ hash.update(file.relativePath);
3121
+ hash.update("\0");
3122
+ hash.update(file.bytes);
3123
+ hash.update("\0");
3124
+ }
3125
+ for (const [key, value] of Object.entries(buildArgs).sort(
3126
+ ([left], [right]) => left.localeCompare(right)
3127
+ )) {
3128
+ hash.update(key);
3129
+ hash.update("=");
3130
+ hash.update(value);
3131
+ hash.update("\0");
3132
+ }
3133
+ return hash.digest("hex");
3134
+ }
3135
+ async function readBuildContextFiles(buildContext) {
3136
+ const files = [];
3137
+ await collectBuildContextFiles(buildContext, "", files);
3138
+ return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
3139
+ }
3140
+ async function collectBuildContextFiles(buildContext, relativeDir, files) {
3141
+ const absoluteDir = path15.join(buildContext, relativeDir);
3142
+ const entries = await readdir(absoluteDir, { withFileTypes: true });
3143
+ for (const entry of entries) {
3144
+ const relativePath = path15.join(relativeDir, entry.name);
3145
+ if (entry.isDirectory()) {
3146
+ await collectBuildContextFiles(buildContext, relativePath, files);
3147
+ continue;
3148
+ }
3149
+ if (!entry.isFile()) {
3150
+ continue;
3151
+ }
3152
+ files.push({
3153
+ relativePath: relativePath.split(path15.sep).join("/"),
3154
+ bytes: await readFile3(path15.join(buildContext, relativePath))
3155
+ });
3156
+ }
3157
+ }
3158
+
3159
+ // packages/runner-e2b/src/opened-env.ts
3160
+ import { mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "node:fs";
3161
+ import { readFile as readFile4, writeFile } from "node:fs/promises";
3162
+ import { tmpdir as tmpdir2 } from "node:os";
3163
+ import path17 from "node:path";
3164
+ import { PassThrough, Writable as Writable2 } from "node:stream";
3165
+
3166
+ // packages/runner-e2b/src/job-handle.ts
3167
+ import path16 from "node:path";
3168
+ var JOB_DIR2 = "/tmp/poe-jobs";
3169
+ function createE2bJobHandle(input) {
3170
+ const fs = createE2bLogStreamFs(input.sandbox);
3171
+ return {
3172
+ id: input.jobId,
3173
+ envId: input.envId,
3174
+ tool: input.tool,
3175
+ argv: input.argv,
3176
+ async status() {
3177
+ const exit = await readExitCode(input.sandbox, input.jobId);
3178
+ if (exit !== null) {
3179
+ return "exited";
3180
+ }
3181
+ const processes = await input.sandbox.commands.list();
3182
+ const isRunning = input.pid === void 0 ? processes.some((process2) => processMentionsJob(process2, input.jobId)) : processes.some((process2) => process2.pid === input.pid);
3183
+ return isRunning ? "running" : "lost";
3184
+ },
3185
+ stream(opts = {}) {
3186
+ return streamLogFile({ fs }, input.jobId, opts);
3187
+ },
3188
+ async wait() {
3189
+ const result = await waitForExit({ fs }, input.jobId);
3190
+ const preserveMs = input.preserveAfterExitHours * 60 * 60 * 1e3;
3191
+ if (preserveMs > 0) {
3192
+ await input.sandbox.setTimeout(preserveMs);
3193
+ }
3194
+ return result;
3195
+ },
3196
+ async kill() {
3197
+ const pids = input.pid === void 0 ? (await input.sandbox.commands.list()).filter((process2) => processMentionsJob(process2, input.jobId)).map((process2) => process2.pid) : [input.pid];
3198
+ await Promise.all(pids.map((pid) => input.sandbox.commands.kill(pid)));
3199
+ }
3200
+ };
3201
+ }
3202
+ function createE2bLogStreamFs(sandbox) {
3203
+ return {
3204
+ promises: {
3205
+ async readFile(filePath) {
3206
+ return Buffer.from(await sandbox.files.read(filePath, { format: "bytes" }));
3207
+ },
3208
+ async stat(filePath) {
3209
+ const result = await sandbox.commands.run(
3210
+ `stat -c %Y ${shellQuote2(filePath)} 2>/dev/null || stat -f %m ${shellQuote2(filePath)}`
3211
+ );
3212
+ if (!("stdout" in result)) {
3213
+ throw new Error(`Unable to stat ${filePath}`);
3214
+ }
3215
+ const seconds = Number(result.stdout?.trim());
3216
+ if (!Number.isFinite(seconds)) {
3217
+ throw new Error(`Unable to stat ${filePath}`);
3218
+ }
3219
+ return { mtimeMs: seconds * 1e3 };
3220
+ }
3221
+ },
3222
+ watch(filePath, listener) {
3223
+ let closed = false;
3224
+ let stop = null;
3225
+ void sandbox.files.watchDir(path16.dirname(filePath), listener, { recursive: false }).then((handle) => {
3226
+ if (closed) {
3227
+ void handle.stop();
3228
+ return;
3229
+ }
3230
+ stop = () => {
3231
+ void handle.stop();
3232
+ };
3233
+ });
3234
+ return {
3235
+ close() {
3236
+ closed = true;
3237
+ stop?.();
3238
+ }
3239
+ };
3240
+ }
3241
+ };
3242
+ }
3243
+ function processMentionsJob(process2, jobId) {
3244
+ const needle = `/tmp/poe-jobs/${jobId}`;
3245
+ return process2.cmd.includes(needle) || process2.args.some((arg) => arg.includes(needle));
3246
+ }
3247
+ function shellQuote2(value) {
3248
+ return `'${value.replaceAll("'", "'\\''")}'`;
3249
+ }
3250
+ async function readExitCode(sandbox, jobId) {
3251
+ try {
3252
+ const contents = await sandbox.files.read(`${JOB_DIR2}/${jobId}.exit`);
3253
+ const exitCode = Number(contents.trim());
3254
+ return Number.isInteger(exitCode) ? exitCode : null;
3255
+ } catch {
3256
+ return null;
3257
+ }
3258
+ }
3259
+
3260
+ // packages/runner-e2b/src/opened-env.ts
3261
+ var REMOTE_COMMAND_STDERR_TAIL_SIZE = 30;
3262
+ function createOpenedE2bEnv(input) {
3263
+ const hostRunner = input.spec.hostRunner ?? createHostRunner();
3264
+ const hostWorkspaceDir = path17.resolve(input.spec.cwd);
3265
+ const sandboxWorkspaceDir = normalizeSandboxWorkspaceDir(input.runtime.workspace_dir);
3266
+ let lastProcess = null;
3267
+ let detachedJobContext = null;
3268
+ const mapWorkspaceCwd = (cwd) => {
3269
+ if (cwd === void 0) {
3270
+ return void 0;
3271
+ }
3272
+ if (path17.isAbsolute(cwd) && path17.resolve(cwd) === hostWorkspaceDir) {
3273
+ return sandboxWorkspaceDir;
3274
+ }
3275
+ return cwd;
3276
+ };
3277
+ const attachedJobId = input.spec.detachedJobId;
3278
+ const env = {
3279
+ id: input.sandbox.sandboxId,
3280
+ job: attachedJobId ? createE2bJobHandle({
3281
+ sandbox: input.sandbox,
3282
+ envId: input.sandbox.sandboxId,
3283
+ jobId: attachedJobId,
3284
+ tool: input.spec.jobLabel.tool,
3285
+ argv: input.spec.jobLabel.argv,
3286
+ preserveAfterExitHours: input.runtime.preserve_after_exit_hours ?? 24
3287
+ }) : null,
3288
+ fs: createE2bLogStreamFs(input.sandbox),
3289
+ setDetachedJobContext(context) {
3290
+ detachedJobContext = context;
3291
+ },
3292
+ async uploadWorkspace() {
3293
+ if (input.spec.runner?.sync === "none") {
3294
+ return { files: 0, bytes: 0, skipped: [] };
3295
+ }
3296
+ const tempDir = mkdtempSync2(path17.join(tmpdir2(), "poe-e2b-upload-"));
3297
+ const archivePath = path17.join(tempDir, "workspace.tar");
3298
+ try {
3299
+ await runOrThrow2(hostRunner, {
3300
+ command: "tar",
3301
+ args: [
3302
+ ...input.spec.uploadIgnoreFiles.flatMap((ignored) => ["--exclude", ignored]),
3303
+ "-cf",
3304
+ archivePath,
3305
+ "-C",
3306
+ input.spec.cwd,
3307
+ "."
3308
+ ],
3309
+ stdout: "pipe",
3310
+ stderr: "pipe"
3311
+ });
3312
+ await input.sandbox.files.write(
3313
+ "/tmp/poe-workspace-upload.tar",
3314
+ toArrayBuffer(await readFile4(archivePath))
3315
+ );
3316
+ await runRemoteOrThrow(
3317
+ input.sandbox,
3318
+ createUploadWorkspaceCommand(sandboxWorkspaceDir)
3319
+ );
3320
+ return { files: 0, bytes: 0, skipped: [] };
3321
+ } finally {
3322
+ rmSync2(tempDir, { recursive: true, force: true });
3323
+ }
3324
+ },
3325
+ async downloadWorkspace(opts) {
3326
+ if (input.spec.runner?.sync === "upload" || input.spec.runner?.sync === "none") {
3327
+ return { files: 0, bytes: 0, conflicts: [] };
3328
+ }
3329
+ const tempDir = mkdtempSync2(path17.join(tmpdir2(), "poe-e2b-download-"));
3330
+ const archivePath = path17.join(tempDir, "workspace.tar");
3331
+ try {
3332
+ await runRemoteOrThrow(
3333
+ input.sandbox,
3334
+ `tar -cf /tmp/poe-workspace-download.tar -C ${shellQuote3(sandboxWorkspaceDir)} .`
3335
+ );
3336
+ const archive = await input.sandbox.files.read("/tmp/poe-workspace-download.tar", {
3337
+ format: "bytes"
3338
+ });
3339
+ await writeFile(archivePath, Buffer.from(archive));
3340
+ await runOrThrow2(hostRunner, {
3341
+ command: "tar",
3342
+ args: [
3343
+ opts.conflictPolicy === "refuse" ? "-xkf" : "-xf",
3344
+ archivePath,
3345
+ "-C",
3346
+ input.spec.cwd
3347
+ ],
3348
+ stdout: "pipe",
3349
+ stderr: "pipe"
3350
+ });
3351
+ return { files: 0, bytes: archive.byteLength, conflicts: [] };
3352
+ } finally {
3353
+ rmSync2(tempDir, { recursive: true, force: true });
3354
+ }
3355
+ },
3356
+ exec(spec) {
3357
+ const handle = runE2bCommand(input.sandbox, {
3358
+ ...spec,
3359
+ cwd: mapWorkspaceCwd(spec.cwd),
3360
+ env: resolveSandboxCommandEnv(spec.env)
3361
+ });
3362
+ lastProcess = { started: handle.started };
3363
+ return handle;
3364
+ },
3365
+ async detach() {
3366
+ if (detachedJobContext === null) {
3367
+ throw new Error("Cannot detach E2B environment before a job context is registered.");
3368
+ }
3369
+ if (lastProcess === null) {
3370
+ throw new Error("Cannot detach E2B environment before a command is running.");
3371
+ }
3372
+ const command = await lastProcess.started;
3373
+ const preserveAfterExitHours = input.runtime.preserve_after_exit_hours ?? 24;
3374
+ const preserveMs = preserveAfterExitHours * 60 * 60 * 1e3;
3375
+ if (preserveMs > 0) {
3376
+ await input.sandbox.setTimeout(preserveMs);
3377
+ }
3378
+ return createE2bJobHandle({
3379
+ sandbox: input.sandbox,
3380
+ envId: input.sandbox.sandboxId,
3381
+ jobId: detachedJobContext.id,
3382
+ tool: detachedJobContext.tool,
3383
+ argv: detachedJobContext.argv,
3384
+ pid: command.pid,
3385
+ preserveAfterExitHours
3386
+ });
3387
+ },
3388
+ shell() {
3389
+ const shellSpec = input.spec.shellSpec;
3390
+ const command = shellSpec?.command ?? input.spec.env.SHELL ?? "sh";
3391
+ return runE2bPty(input.sandbox, {
3392
+ command,
3393
+ ...shellSpec?.args ? { args: shellSpec.args } : {},
3394
+ cwd: mapWorkspaceCwd(shellSpec?.cwd ?? input.spec.cwd),
3395
+ env: resolveSandboxCommandEnv(
3396
+ shellSpec && "env" in shellSpec ? shellSpec.env : input.spec.env
3397
+ ),
3398
+ stdin: "inherit",
3399
+ stdout: "inherit",
3400
+ stderr: "inherit",
3401
+ tty: true
3402
+ });
3403
+ },
3404
+ async close() {
3405
+ await input.sandbox.kill();
3406
+ }
3407
+ };
3408
+ return env;
3409
+ }
3410
+ function runE2bCommand(sandbox, spec) {
3411
+ const stdout = spec.stdout === "inherit" ? null : new PassThrough();
3412
+ const stderr = spec.stderr === "inherit" ? null : new PassThrough();
3413
+ let e2bHandle = null;
3414
+ const command = shellCommand([spec.command, ...spec.args ?? []]);
3415
+ const started = sandbox.commands.run(command, {
3416
+ background: true,
3417
+ cwd: spec.cwd,
3418
+ envs: spec.env,
3419
+ stdin: spec.stdin === "pipe",
3420
+ onStdout(data) {
3421
+ stdout?.write(data);
3422
+ if (spec.stdout === "inherit") {
3423
+ process.stdout.write(data);
3424
+ }
3425
+ },
3426
+ onStderr(data) {
3427
+ stderr?.write(data);
3428
+ if (spec.stderr === "inherit") {
3429
+ process.stderr.write(data);
3430
+ }
3431
+ }
3432
+ });
3433
+ const stdin = spec.stdin === "pipe" ? new Writable2({
3434
+ write(chunk, _encoding, callback) {
3435
+ started.then(
3436
+ (handle) => sandbox.commands.sendStdin(
3437
+ handle.pid,
3438
+ Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))
3439
+ )
3440
+ ).then(() => callback(), callback);
3441
+ },
3442
+ final(callback) {
3443
+ if (sandbox.commands.closeStdin === void 0) {
3444
+ callback();
3445
+ return;
3446
+ }
3447
+ started.then((handle) => sandbox.commands.closeStdin(handle.pid)).then(() => callback(), callback);
3448
+ }
3449
+ }) : null;
3450
+ const result = started.then((handle) => {
3451
+ e2bHandle = handle;
3452
+ return handle.wait();
3453
+ }).then(
3454
+ (result2) => {
3455
+ stdout?.end();
3456
+ stderr?.end();
3457
+ return { exitCode: result2.exitCode ?? 0 };
3458
+ },
3459
+ (error2) => {
3460
+ stdout?.end();
3461
+ stderr?.end();
3462
+ if (isExitError(error2)) {
3463
+ return { exitCode: error2.exitCode };
3464
+ }
3465
+ return { exitCode: 1 };
3466
+ }
3467
+ );
3468
+ return {
3469
+ get pid() {
3470
+ return e2bHandle?.pid ?? null;
3471
+ },
3472
+ stdin,
3473
+ stdout,
3474
+ stderr,
3475
+ result,
3476
+ kill() {
3477
+ void e2bHandle?.kill();
3478
+ },
3479
+ get e2bHandle() {
3480
+ return e2bHandle;
3481
+ },
3482
+ started
3483
+ };
3484
+ }
3485
+ function runE2bPty(sandbox, spec) {
3486
+ const stdout = new PassThrough();
3487
+ let handleRef = null;
3488
+ const stdin = new Writable2({
3489
+ write(chunk, _encoding, callback) {
3490
+ if (handleRef === null) {
3491
+ callback(new Error("E2B PTY stdin is not ready."));
3492
+ return;
3493
+ }
3494
+ sandbox.pty.sendInput(handleRef.pid, Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))).then(() => callback(), callback);
3495
+ }
3496
+ });
3497
+ const started = sandbox.pty.create({
3498
+ cols: process.stdout.columns || 80,
3499
+ rows: process.stdout.rows || 24,
3500
+ cwd: spec.cwd,
3501
+ envs: spec.env,
3502
+ onData(data) {
3503
+ stdout.write(Buffer.from(data));
3504
+ if (spec.stdout === "inherit") {
3505
+ process.stdout.write(Buffer.from(data));
3506
+ }
3507
+ }
3508
+ });
3509
+ const result = started.then((handle) => {
3510
+ handleRef = handle;
3511
+ return handle.wait();
3512
+ }).then(
3513
+ (result2) => {
3514
+ stdout.end();
3515
+ return { exitCode: result2.exitCode ?? 0 };
3516
+ },
3517
+ () => {
3518
+ stdout.end();
3519
+ return { exitCode: 1 };
3520
+ }
3521
+ );
3522
+ return {
3523
+ get pid() {
3524
+ return handleRef?.pid ?? null;
3525
+ },
3526
+ stdin: spec.stdin === "inherit" ? process.stdin : stdin,
3527
+ stdout: spec.stdout === "inherit" ? null : stdout,
3528
+ stderr: null,
3529
+ result,
3530
+ kill() {
3531
+ void (handleRef === null ? void 0 : sandbox.pty.kill(handleRef.pid));
3532
+ }
3533
+ };
3534
+ }
3535
+ async function runRemoteOrThrow(sandbox, command) {
3536
+ const stdoutTail = createLineTail(REMOTE_COMMAND_STDERR_TAIL_SIZE);
3537
+ const stderrTail = createLineTail(REMOTE_COMMAND_STDERR_TAIL_SIZE);
3538
+ let result;
3539
+ try {
3540
+ result = await sandbox.commands.run(command, {
3541
+ onStdout(data) {
3542
+ stdoutTail.push(data);
3543
+ },
3544
+ onStderr(data) {
3545
+ stderrTail.push(data);
3546
+ }
3547
+ });
3548
+ } catch (error2) {
3549
+ appendRemoteCommandOutput(error2, stdoutTail, stderrTail);
3550
+ if (isCommandExitError(error2)) {
3551
+ throw decorateRemoteCommandError(error2, command, stderrTail.values());
3552
+ }
3553
+ throw error2;
3554
+ }
3555
+ appendRemoteCommandOutput(result, stdoutTail, stderrTail);
3556
+ if ("exitCode" in result && result.exitCode !== 0) {
3557
+ throw decorateRemoteCommandError(
3558
+ new Error(`E2B command failed with exit code ${result.exitCode}`),
3559
+ command,
3560
+ stderrTail.values()
3561
+ );
3562
+ }
3563
+ }
3564
+ function appendRemoteCommandOutput(source, stdoutTail, stderrTail) {
3565
+ if (!source || typeof source !== "object") {
3566
+ return;
3567
+ }
3568
+ const output = source;
3569
+ if (typeof output.stdout === "string") {
3570
+ stdoutTail.push(output.stdout);
3571
+ }
3572
+ if (typeof output.stderr === "string") {
3573
+ stderrTail.push(output.stderr);
3574
+ }
3575
+ }
3576
+ function decorateRemoteCommandError(error2, command, stderrTail) {
3577
+ const original = error2 instanceof Error ? error2 : new Error(String(error2));
3578
+ const tail = stderrTail.length === 0 ? "" : `
3579
+
3580
+ Last stderr output:
3581
+ ${stderrTail.join("\n")}`;
3582
+ const decorated = new Error(`E2B command failed: ${command}
3583
+ ${original.message}${tail}`);
3584
+ decorated.stack = original.stack;
3585
+ decorated.cause = original;
3586
+ return decorated;
3587
+ }
3588
+ function createLineTail(maxLines) {
3589
+ const lines = [];
3590
+ let pending = "";
3591
+ const appendLine = (line) => {
3592
+ lines.push(trimTrailingCarriageReturn(line));
3593
+ while (lines.length > maxLines) {
3594
+ lines.shift();
3595
+ }
3596
+ };
3597
+ return {
3598
+ push(chunk) {
3599
+ pending += chunk;
3600
+ const parts = pending.split("\n");
3601
+ pending = parts.pop() ?? "";
3602
+ for (const line of parts) {
3603
+ appendLine(line);
3604
+ }
3605
+ },
3606
+ values() {
3607
+ const output = [...lines];
3608
+ if (pending.length > 0) {
3609
+ output.push(trimTrailingCarriageReturn(pending));
3610
+ }
3611
+ return output.slice(-maxLines);
3612
+ }
3613
+ };
3614
+ }
3615
+ function trimTrailingCarriageReturn(value) {
3616
+ return value.endsWith("\r") ? value.slice(0, -1) : value;
3617
+ }
3618
+ async function runOrThrow2(runner, spec) {
3619
+ const handle = runner.exec(spec);
3620
+ const stderr = readableToString(handle.stderr);
3621
+ const result = await handle.result;
3622
+ if (result.exitCode !== 0) {
3623
+ throw new Error(
3624
+ `Command failed with exit code ${result.exitCode}: ${spec.command} ${(spec.args ?? []).join(" ")}
3625
+ ${await stderr}`
3626
+ );
3627
+ }
3628
+ }
3629
+ function shellCommand(argv) {
3630
+ return argv.map(shellQuote3).join(" ");
3631
+ }
3632
+ function shellQuote3(value) {
3633
+ return `'${value.replaceAll("'", "'\\''")}'`;
3634
+ }
3635
+ function createUploadWorkspaceCommand(sandboxWorkspaceDir) {
3636
+ const quotedWorkspaceDir = shellQuote3(sandboxWorkspaceDir);
3637
+ return [
3638
+ `mkdir -p ${quotedWorkspaceDir} || { command -v sudo >/dev/null 2>&1 && sudo mkdir -p ${quotedWorkspaceDir} && sudo chown "$(id -u):$(id -g)" ${quotedWorkspaceDir}; }`,
3639
+ `test -w ${quotedWorkspaceDir} && tar -xf /tmp/poe-workspace-upload.tar -C ${quotedWorkspaceDir}`
3640
+ ].join("\n");
3641
+ }
3642
+ function resolveSandboxCommandEnv(env) {
3643
+ if (env === void 0) {
3644
+ return void 0;
3645
+ }
3646
+ return {
3647
+ ...env,
3648
+ HOME: "/home/user"
3649
+ };
3650
+ }
3651
+ function normalizeSandboxWorkspaceDir(workspaceDir) {
3652
+ const resolvedWorkspaceDir = workspaceDir ?? "/workspace";
3653
+ if (!path17.posix.isAbsolute(resolvedWorkspaceDir)) {
3654
+ throw new Error("E2B runtime workspace_dir must be an absolute sandbox path.");
3655
+ }
3656
+ let normalized = path17.posix.normalize(resolvedWorkspaceDir);
3657
+ while (normalized.length > 1 && normalized.endsWith("/")) {
3658
+ normalized = normalized.slice(0, -1);
3659
+ }
3660
+ return normalized;
3661
+ }
3662
+ function isExitError(error2) {
3663
+ return Boolean(
3664
+ error2 && typeof error2 === "object" && typeof error2.exitCode === "number"
3665
+ );
3666
+ }
3667
+ function isCommandExitError(error2) {
3668
+ return isExitError(error2) || Boolean(
3669
+ error2 && typeof error2 === "object" && error2.name === "CommandExitError"
3670
+ );
3671
+ }
3672
+
3673
+ // packages/runner-e2b/src/auth-scope.ts
3674
+ import os4 from "node:os";
3675
+ import { promises as nodeFs4 } from "node:fs";
3676
+ var e2bAuthScope = defineScope("e2b", {
3677
+ api_key: {
3678
+ type: "string",
3679
+ default: "",
3680
+ doc: "E2B API key",
3681
+ env: "E2B_API_KEY"
3682
+ }
3683
+ });
3684
+ async function resolveE2bApiKey(input) {
3685
+ const homeDir = input.homeDir ?? os4.homedir();
3686
+ const fs = input.fs ?? nodeFs4;
3687
+ const env = input.env ?? process.env;
3688
+ const document = await readMergedDocument(
3689
+ fs,
3690
+ resolveConfigPath(homeDir),
3691
+ resolveProjectConfigPath(input.cwd)
3692
+ );
3693
+ const resolved = resolveScope(e2bAuthScope.schema, document.e2b, env);
3694
+ if (resolved.api_key.length === 0) {
3695
+ throw new Error(
3696
+ "No E2B API key. Set E2B_API_KEY or e2b.api_key in ~/.poe-code/config.json."
3697
+ );
3698
+ }
3699
+ return resolved.api_key;
3700
+ }
3701
+
3702
+ // packages/runner-e2b/src/factory.ts
3703
+ var e2bExecutionEnvFactory = {
3704
+ type: "e2b",
3705
+ supportsDetach: true,
3706
+ async open(spec) {
3707
+ const runtime = parseE2bRuntime(spec.runtime);
3708
+ const runtimeCwd = spec.runtimeCwd ?? spec.cwd;
3709
+ const apiKey = await resolveE2bApiKey({ cwd: runtimeCwd });
3710
+ const templateId = runtime.template_id ?? (await buildE2bRuntimeTemplate({
3711
+ runtime,
3712
+ dockerfilePath: path18.resolve(
3713
+ runtimeCwd,
3714
+ runtime.dockerfile ?? path18.join(".poe-code", "Dockerfile")
3715
+ ),
3716
+ buildContext: path18.resolve(runtimeCwd, runtime.build_context ?? "."),
3717
+ state: spec.state,
3718
+ apiKey
3719
+ })).templateId;
3720
+ const sandbox = await createSandbox({
3721
+ apiKey,
3722
+ templateId,
3723
+ env: spec.env,
3724
+ timeoutMinutes: runtime.timeout_minutes
3725
+ });
3726
+ return createOpenedE2bEnv({ sandbox, spec, runtime });
3727
+ },
3728
+ async attach(envId, context) {
3729
+ const cwd = context?.cwd ?? process.cwd();
3730
+ const apiKey = await resolveE2bApiKey({ cwd });
3731
+ const sandbox = await connectSandbox(envId, apiKey);
3732
+ return createOpenedE2bEnv({
3733
+ sandbox,
3734
+ spec: {
3735
+ cwd: context?.cwd ?? "/workspace",
3736
+ runtime: {
3737
+ type: "e2b",
3738
+ build_args: {},
3739
+ mounts: [],
3740
+ workspace_dir: "/workspace",
3741
+ preserve_after_exit_hours: 24
3742
+ },
3743
+ env: {},
3744
+ uploadIgnoreFiles: [],
3745
+ jobLabel: { tool: context?.tool ?? "e2b", argv: context?.argv ?? [] },
3746
+ ...context?.jobId ? { detachedJobId: context.jobId } : {}
3747
+ },
3748
+ runtime: {
3749
+ type: "e2b",
3750
+ build_args: {},
3751
+ mounts: [],
3752
+ workspace_dir: "/workspace",
3753
+ preserve_after_exit_hours: 24
3754
+ }
3755
+ });
3756
+ }
3757
+ };
3758
+ function parseE2bRuntime(runtime) {
3759
+ if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) {
3760
+ throw new Error("e2b runtime must be an object");
3761
+ }
3762
+ const record = runtime;
3763
+ if (record.type !== "e2b") {
3764
+ throw new Error('e2b runtime type must be "e2b"');
3765
+ }
3766
+ return record;
3767
+ }
3768
+
3769
+ // packages/runner-e2b/src/index.ts
3770
+ var e2bExecutionEnvFactory2 = e2bExecutionEnvFactory;
3771
+
2150
3772
  // packages/agent-spawn/src/register-factories.ts
2151
3773
  registerExecutionEnvFactory(hostExecutionEnvFactory);
2152
3774
  registerExecutionEnvFactory(dockerExecutionEnvFactory);
2153
- if (process.env.VITEST === "true") {
3775
+ registerExecutionEnvFactory(e2bExecutionEnvFactory2);
3776
+ if (isVitest()) {
2154
3777
  registerExecutionEnvFactory(createTestHostExecutionEnvFactory());
2155
3778
  }
3779
+ function isVitest() {
3780
+ return process.env.VITEST !== void 0 || process.env.VITEST_POOL_ID !== void 0;
3781
+ }
2156
3782
  function createTestHostExecutionEnvFactory() {
2157
3783
  return {
2158
3784
  type: "host",
@@ -2486,7 +4112,7 @@ acpLookup.set(gooseAcpSpawnConfig.agentId, gooseAcpSpawnConfig);
2486
4112
 
2487
4113
  // packages/agent-spawn/src/spawn.ts
2488
4114
  import { mkdirSync, openSync, writeSync, closeSync } from "node:fs";
2489
- import path15 from "node:path";
4115
+ import path19 from "node:path";
2490
4116
 
2491
4117
  // packages/design-system/src/tokens/colors.ts
2492
4118
  import chalk from "chalk";
@@ -2863,7 +4489,7 @@ import chalk8 from "chalk";
2863
4489
 
2864
4490
  // packages/design-system/src/dashboard/terminal.ts
2865
4491
  import readline from "node:readline";
2866
- import { PassThrough } from "node:stream";
4492
+ import { PassThrough as PassThrough2 } from "node:stream";
2867
4493
 
2868
4494
  // packages/design-system/src/prompts/index.ts
2869
4495
  import chalk15 from "chalk";
@@ -2895,9 +4521,9 @@ import chalk16 from "chalk";
2895
4521
  var DEFAULT_ACTIVITY_TIMEOUT_MS = 10 * 60 * 1e3;
2896
4522
 
2897
4523
  // packages/agent-spawn/src/acp/replay.ts
2898
- import path16 from "node:path";
4524
+ import path20 from "node:path";
2899
4525
  import { homedir as homedir2 } from "node:os";
2900
- import { open as open2, readdir } from "node:fs/promises";
4526
+ import { open as open2, readdir as readdir2 } from "node:fs/promises";
2901
4527
  import { createInterface } from "node:readline";
2902
4528
 
2903
4529
  // packages/poe-acp-client/src/acp-client.ts
@@ -2914,7 +4540,7 @@ import { homedir } from "node:os";
2914
4540
  import { join } from "node:path";
2915
4541
 
2916
4542
  // packages/agent-spawn/src/acp/middlewares/spawn-log.ts
2917
- import path17 from "node:path";
4543
+ import path21 from "node:path";
2918
4544
  import { homedir as homedir3 } from "node:os";
2919
4545
  import { mkdir, open as open3 } from "node:fs/promises";
2920
4546
 
@@ -3013,34 +4639,7 @@ function createBinaryExistsCheck(binaryName, id, description) {
3013
4639
  id,
3014
4640
  description,
3015
4641
  async run({ runCommand: runCommand2 }) {
3016
- const commonPaths = [
3017
- `/usr/local/bin/${binaryName}`,
3018
- `/usr/bin/${binaryName}`,
3019
- `$HOME/.local/bin/${binaryName}`,
3020
- `$HOME/.claude/local/bin/${binaryName}`
3021
- ];
3022
- const detectors = [
3023
- {
3024
- command: "which",
3025
- args: [binaryName],
3026
- validate: (result) => result.exitCode === 0
3027
- },
3028
- {
3029
- command: "where",
3030
- args: [binaryName],
3031
- validate: (result) => result.exitCode === 0 && result.stdout.trim().length > 0
3032
- },
3033
- // Check common installation paths using shell expansion for $HOME
3034
- {
3035
- command: "sh",
3036
- args: [
3037
- "-c",
3038
- commonPaths.map((p) => `test -f "${p}"`).join(" || ")
3039
- ],
3040
- validate: (result) => result.exitCode === 0
3041
- }
3042
- ];
3043
- for (const detector of detectors) {
4642
+ for (const detector of createBinaryExistsDetectors(binaryName)) {
3044
4643
  const result = await runCommand2(detector.command, detector.args);
3045
4644
  if (detector.validate(result)) {
3046
4645
  return;