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