poe-code 3.0.203 → 3.0.204

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
@@ -248,10 +248,171 @@ import path2 from "node:path";
248
248
 
249
249
  // packages/agent-harness-tools/src/log-stream.ts
250
250
  import nodeFs from "node:fs";
251
+ var JOB_DIR = "/tmp/poe-jobs";
252
+ var POLL_INTERVAL_MS = 250;
253
+ async function* streamLogFile(env, jobId, opts) {
254
+ const fs = env.fs ?? nodeFs;
255
+ const file = jobLogPath(jobId);
256
+ let byteOffset = opts.sinceByte ?? 0;
257
+ while (true) {
258
+ if (opts.since !== void 0 && !await wasModifiedSince(fs, file, opts.since)) {
259
+ await waitForLogChange(fs, file);
260
+ continue;
261
+ }
262
+ const result = await readLogChunk(fs, file, byteOffset);
263
+ if (result !== null) {
264
+ byteOffset = result.nextByteOffset;
265
+ yield result.chunk;
266
+ continue;
267
+ }
268
+ await waitForLogChange(fs, file);
269
+ }
270
+ }
271
+ async function wasModifiedSince(fs, file, since) {
272
+ if (fs.promises.stat === void 0) {
273
+ return true;
274
+ }
275
+ try {
276
+ const stat2 = await fs.promises.stat(file);
277
+ return stat2.mtimeMs >= since.getTime();
278
+ } catch (error2) {
279
+ if (isNodeError(error2) && error2.code === "ENOENT") {
280
+ return false;
281
+ }
282
+ throw error2;
283
+ }
284
+ }
285
+ async function waitForExit(env, jobId, opts = {}) {
286
+ const fs = env.fs ?? nodeFs;
287
+ const file = jobExitPath(jobId);
288
+ while (true) {
289
+ throwIfAborted(opts.signal);
290
+ const contents = await readTextFileIfExists(fs, file);
291
+ if (contents !== null) {
292
+ const text4 = contents.trim();
293
+ const exitCode = Number(text4);
294
+ if (text4.length === 0 || !Number.isInteger(exitCode)) {
295
+ throw new Error(`Invalid exit code in ${file}: ${contents}`);
296
+ }
297
+ return { exitCode };
298
+ }
299
+ await sleep(POLL_INTERVAL_MS, opts.signal);
300
+ }
301
+ }
302
+ function jobLogPath(jobId) {
303
+ return `${JOB_DIR}/${jobId}.log`;
304
+ }
305
+ function jobExitPath(jobId) {
306
+ return `${JOB_DIR}/${jobId}.exit`;
307
+ }
308
+ async function readLogChunk(fs, file, byteOffset) {
309
+ const contents = await readFileIfExists(fs, file);
310
+ if (contents === null || byteOffset >= contents.byteLength) {
311
+ return null;
312
+ }
313
+ return {
314
+ chunk: {
315
+ byteOffset,
316
+ data: contents.subarray(byteOffset).toString("utf8")
317
+ },
318
+ nextByteOffset: contents.byteLength
319
+ };
320
+ }
321
+ async function readTextFileIfExists(fs, file) {
322
+ const contents = await readFileIfExists(fs, file);
323
+ return contents?.toString("utf8") ?? null;
324
+ }
325
+ async function readFileIfExists(fs, file) {
326
+ try {
327
+ const contents = await fs.promises.readFile(file);
328
+ return Buffer.isBuffer(contents) ? contents : Buffer.from(contents);
329
+ } catch (error2) {
330
+ if (isNodeError(error2) && error2.code === "ENOENT") {
331
+ return null;
332
+ }
333
+ throw error2;
334
+ }
335
+ }
336
+ async function waitForLogChange(fs, file) {
337
+ const watch = fs.watch;
338
+ if (typeof watch !== "function") {
339
+ await sleep(POLL_INTERVAL_MS);
340
+ return;
341
+ }
342
+ await new Promise((resolve2) => {
343
+ let watcher = null;
344
+ const timer = setTimeout(done, POLL_INTERVAL_MS);
345
+ function done() {
346
+ clearTimeout(timer);
347
+ watcher?.close();
348
+ resolve2();
349
+ }
350
+ try {
351
+ watcher = watch(file, done);
352
+ } catch {
353
+ done();
354
+ }
355
+ });
356
+ }
357
+ function sleep(ms, signal) {
358
+ return new Promise((resolve2, reject) => {
359
+ let timer = null;
360
+ const abort = () => {
361
+ if (timer !== null) {
362
+ clearTimeout(timer);
363
+ }
364
+ reject(new Error("waitForExit aborted."));
365
+ };
366
+ if (signal?.aborted) {
367
+ abort();
368
+ return;
369
+ }
370
+ timer = setTimeout(() => {
371
+ signal?.removeEventListener("abort", abort);
372
+ resolve2();
373
+ }, ms);
374
+ signal?.addEventListener("abort", abort, { once: true });
375
+ });
376
+ }
377
+ function throwIfAborted(signal) {
378
+ if (signal?.aborted) {
379
+ throw new Error("waitForExit aborted.");
380
+ }
381
+ }
382
+ function isNodeError(error2) {
383
+ return error2 instanceof Error && "code" in error2;
384
+ }
251
385
 
252
386
  // packages/agent-harness-tools/src/run-poe-command.ts
253
387
  import { randomBytes } from "node:crypto";
254
388
 
389
+ // packages/agent-harness-tools/src/binary-exists.ts
390
+ function createBinaryExistsDetectors(binaryName) {
391
+ const commonPaths = [
392
+ `/usr/local/bin/${binaryName}`,
393
+ `/usr/bin/${binaryName}`,
394
+ `$HOME/.local/bin/${binaryName}`,
395
+ `$HOME/.claude/local/bin/${binaryName}`
396
+ ];
397
+ return [
398
+ {
399
+ command: "which",
400
+ args: [binaryName],
401
+ validate: (result) => result.exitCode === 0
402
+ },
403
+ {
404
+ command: "where",
405
+ args: [binaryName],
406
+ validate: (result) => result.exitCode === 0 && result.stdout.trim().length > 0
407
+ },
408
+ {
409
+ command: "sh",
410
+ args: ["-c", commonPaths.map((p) => `test -f "${p}"`).join(" || ")],
411
+ validate: (result) => result.exitCode === 0
412
+ }
413
+ ];
414
+ }
415
+
255
416
  // packages/agent-harness-tools/src/poe-command-execution.ts
256
417
  import { existsSync as existsSync2, readFileSync } from "node:fs";
257
418
  import os3 from "node:os";
@@ -313,6 +474,11 @@ var runtimeConfigScope = {
313
474
  default: "",
314
475
  doc: "Path to the Docker build context"
315
476
  },
477
+ workspace_dir: {
478
+ type: "string",
479
+ default: "/workspace",
480
+ doc: "Sandbox-local workspace directory for E2B runtime upload, execution, and download"
481
+ },
316
482
  engine: {
317
483
  type: "string",
318
484
  default: "",
@@ -334,6 +500,11 @@ var runtimeConfigScope = {
334
500
  default: "",
335
501
  doc: "Prebuilt E2B template id"
336
502
  },
503
+ from_template: {
504
+ type: "string",
505
+ default: "",
506
+ doc: "Existing E2B template alias to extend instead of using the Dockerfile FROM image"
507
+ },
337
508
  cpu: {
338
509
  type: "json",
339
510
  default: void 0,
@@ -357,11 +528,6 @@ var runtimeConfigScope = {
357
528
  default: void 0,
358
529
  parse: parseOptionalNumber,
359
530
  doc: "Hours to keep an E2B sandbox alive after job exit"
360
- },
361
- api_key_env: {
362
- type: "string",
363
- default: "",
364
- doc: "Environment variable name containing the E2B API key"
365
531
  }
366
532
  }
367
533
  };
@@ -381,6 +547,7 @@ function parseRunner(raw) {
381
547
  detach: parseOptionalBoolean(record.detach, "runner.detach") ?? false,
382
548
  upload_max_file_mb: uploadMaxFileMb,
383
549
  download_conflict: parseDownloadConflict(record.download_conflict),
550
+ sync: parseRunnerSync(record.sync),
384
551
  workspace: parseRunnerWorkspace(record.workspace)
385
552
  });
386
553
  }
@@ -389,6 +556,7 @@ function createDefaultRunnerScope() {
389
556
  detach: false,
390
557
  upload_max_file_mb: 100,
391
558
  download_conflict: "refuse",
559
+ sync: "both",
392
560
  workspace: {
393
561
  exclude: [...defaultWorkspaceExclude]
394
562
  }
@@ -419,6 +587,15 @@ function parseDownloadConflict(value) {
419
587
  }
420
588
  throw new Error('runner.download_conflict: expected "refuse" or "overwrite".');
421
589
  }
590
+ function parseRunnerSync(value) {
591
+ if (value === void 0) {
592
+ return "both";
593
+ }
594
+ if (value === "both" || value === "upload" || value === "none") {
595
+ return value;
596
+ }
597
+ throw new Error('runner.sync: expected "both", "upload", or "none".');
598
+ }
422
599
  function parseBuildArgs(value) {
423
600
  if (value === void 0) {
424
601
  return {};
@@ -512,6 +689,43 @@ function defineScope(scope, schema) {
512
689
  schema
513
690
  };
514
691
  }
692
+ var integrationsConfigScope = defineScope("integrations", {
693
+ braintrust: {
694
+ type: "json",
695
+ default: {
696
+ enabled: false
697
+ },
698
+ parse: parseBraintrustIntegrationConfig,
699
+ doc: "Braintrust integration configuration"
700
+ }
701
+ });
702
+ function parseBraintrustIntegrationConfig(value) {
703
+ if (!isRecord(value)) {
704
+ throw new Error("expected an object");
705
+ }
706
+ const enabled = value.enabled === void 0 ? false : value.enabled;
707
+ if (typeof enabled !== "boolean") {
708
+ throw new Error("enabled must be a boolean");
709
+ }
710
+ return {
711
+ enabled,
712
+ ...optionalStringEntry("apiKey", value.apiKey),
713
+ ...optionalStringEntry("apiUrl", value.apiUrl),
714
+ ...optionalStringEntry("project", value.project)
715
+ };
716
+ }
717
+ function optionalStringEntry(key, value) {
718
+ if (value === void 0) {
719
+ return {};
720
+ }
721
+ if (typeof value !== "string") {
722
+ throw new Error(`${key} must be a string`);
723
+ }
724
+ return { [key]: value };
725
+ }
726
+ function isRecord(value) {
727
+ return typeof value === "object" && value !== null && !Array.isArray(value);
728
+ }
515
729
 
516
730
  // packages/poe-code-config/src/plan-scope.ts
517
731
  var planConfigScope = defineScope("plan", {
@@ -528,14 +742,427 @@ import path8 from "node:path";
528
742
 
529
743
  // packages/config-extends/src/discover.ts
530
744
  import path4 from "node:path";
745
+ async function findBase(name, bases, fs) {
746
+ const checkedPaths = [];
747
+ for (const basePath of bases) {
748
+ for (const extension of [".md", ".yaml", ".yml", ".json"]) {
749
+ const filePath = path4.join(basePath, `${name}${extension}`);
750
+ checkedPaths.push(filePath);
751
+ try {
752
+ return {
753
+ content: await fs.readFile(filePath, "utf8"),
754
+ filePath
755
+ };
756
+ } catch (error2) {
757
+ if (hasCode(error2, "ENOENT")) {
758
+ continue;
759
+ }
760
+ throw error2;
761
+ }
762
+ }
763
+ }
764
+ throw new Error(`Base "${name}" not found.
765
+ Checked paths:
766
+ - ${checkedPaths.join("\n- ")}`);
767
+ }
768
+ function hasCode(error2, code) {
769
+ return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === code;
770
+ }
531
771
 
532
772
  // packages/config-extends/src/parse.ts
533
773
  import path5 from "node:path";
534
774
  import matter from "gray-matter";
535
775
  import { parse as parseYaml } from "yaml";
776
+ function parseDocument(content, filePath) {
777
+ const normalizedContent = stripBom(content);
778
+ const format = detectFormat(normalizedContent, filePath);
779
+ const data = format === "markdown" ? parseMarkdown(normalizedContent) : toData(format === "json" ? JSON.parse(normalizedContent) : parseYaml(normalizedContent));
780
+ const hasExtendsField = Object.hasOwn(data, "extends");
781
+ const extendsValue = data.extends === true;
782
+ delete data.extends;
783
+ return {
784
+ data,
785
+ format,
786
+ extends: extendsValue,
787
+ hasExtendsField
788
+ };
789
+ }
790
+ function detectFormat(content, filePath) {
791
+ const extension = path5.extname(filePath).toLowerCase();
792
+ if (extension === ".md") {
793
+ return "markdown";
794
+ }
795
+ if (extension === ".yaml" || extension === ".yml") {
796
+ return "yaml";
797
+ }
798
+ if (extension === ".json") {
799
+ return "json";
800
+ }
801
+ if (content.startsWith("{")) {
802
+ return "json";
803
+ }
804
+ if (content.startsWith("---\n") || content.startsWith("---\r\n")) {
805
+ return "markdown";
806
+ }
807
+ return "yaml";
808
+ }
809
+ function parseMarkdown(content) {
810
+ const document = matter(content);
811
+ return {
812
+ ...toData(document.data),
813
+ prompt: document.content
814
+ };
815
+ }
816
+ function toData(value) {
817
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
818
+ return {};
819
+ }
820
+ return { ...value };
821
+ }
822
+ function stripBom(content) {
823
+ if (!content.startsWith("\uFEFF")) {
824
+ return content;
825
+ }
826
+ return content.slice(1);
827
+ }
828
+
829
+ // packages/config-extends/src/merge.ts
830
+ function mergeLayers(layers) {
831
+ return mergeObjectLayers(layers, []);
832
+ }
833
+ function mergeObjectLayers(layers, path22) {
834
+ const data = {};
835
+ const sources = {};
836
+ for (const key of collectKeys(layers)) {
837
+ const resolved = resolveKey(layers, key, path22);
838
+ if (resolved === void 0) {
839
+ continue;
840
+ }
841
+ data[key] = resolved.value;
842
+ Object.assign(sources, resolved.sources);
843
+ }
844
+ return { data, sources };
845
+ }
846
+ function collectKeys(layers) {
847
+ const keys = /* @__PURE__ */ new Set();
848
+ for (const layer of layers) {
849
+ for (const key of Object.keys(layer.data)) {
850
+ keys.add(key);
851
+ }
852
+ }
853
+ return [...keys];
854
+ }
855
+ function resolveKey(layers, key, path22) {
856
+ let winningSource;
857
+ let winningValue;
858
+ const objectLayers = [];
859
+ for (const layer of layers) {
860
+ const candidate = layer.data[key];
861
+ if (!isWinningCandidate(key, candidate)) {
862
+ continue;
863
+ }
864
+ if (winningSource === void 0) {
865
+ winningSource = layer.source;
866
+ winningValue = candidate;
867
+ if (isPlainObject(candidate)) {
868
+ objectLayers.push({
869
+ source: layer.source,
870
+ data: candidate
871
+ });
872
+ }
873
+ continue;
874
+ }
875
+ if (isPlainObject(winningValue) && isPlainObject(candidate)) {
876
+ objectLayers.push({
877
+ source: layer.source,
878
+ data: candidate
879
+ });
880
+ }
881
+ }
882
+ if (winningSource === void 0) {
883
+ return void 0;
884
+ }
885
+ const fullPath = buildPath(path22, key);
886
+ if (isPlainObject(winningValue)) {
887
+ const merged = mergeObjectLayers(objectLayers, [...path22, key]);
888
+ return {
889
+ value: merged.data,
890
+ sources: {
891
+ [fullPath]: winningSource,
892
+ ...merged.sources
893
+ }
894
+ };
895
+ }
896
+ return {
897
+ value: cloneValue(winningValue),
898
+ sources: {
899
+ [fullPath]: winningSource
900
+ }
901
+ };
902
+ }
903
+ function isWinningCandidate(key, value) {
904
+ if (value === void 0) {
905
+ return false;
906
+ }
907
+ if (key === "prompt" && value === "") {
908
+ return false;
909
+ }
910
+ return true;
911
+ }
912
+ function buildPath(path22, key) {
913
+ return [...path22, key].join(".");
914
+ }
915
+ function isPlainObject(value) {
916
+ if (value === null || Array.isArray(value) || typeof value !== "object") {
917
+ return false;
918
+ }
919
+ const prototype = Object.getPrototypeOf(value);
920
+ return prototype === Object.prototype || prototype === null;
921
+ }
922
+ function cloneValue(value) {
923
+ if (Array.isArray(value)) {
924
+ return value.map((entry) => cloneValue(entry));
925
+ }
926
+ if (!isPlainObject(value)) {
927
+ return value;
928
+ }
929
+ const clone = Object.create(Object.getPrototypeOf(value));
930
+ for (const [key, entry] of Object.entries(value)) {
931
+ clone[key] = cloneValue(entry);
932
+ }
933
+ return clone;
934
+ }
536
935
 
537
936
  // packages/config-extends/src/resolve.ts
538
937
  import path6 from "node:path";
938
+ var MAX_EXTENDS_DEPTH = 5;
939
+ var YIELD_TOKEN = "{{yield}}";
940
+ async function resolve(chain, options) {
941
+ const { baseLayers, documentIndex, documentLayer } = classifyChain(chain);
942
+ const parsedDocument = parseDocument(documentLayer.content, documentLayer.filePath);
943
+ const resolvedBase = shouldResolveBase(parsedDocument, options.autoExtend) ? await resolveBaseChain({
944
+ name: documentLayer.baseName ?? getBaseName(documentLayer.filePath),
945
+ baseLayers,
946
+ options,
947
+ optional: !parsedDocument.extends,
948
+ visited: /* @__PURE__ */ new Set([documentLayer.filePath]),
949
+ depth: 1
950
+ }) : void 0;
951
+ const composedPrompt = composePromptChain(
952
+ {
953
+ source: documentLayer.source,
954
+ data: parsedDocument.data
955
+ },
956
+ resolvedBase?.layers ?? []
957
+ );
958
+ const merged = mergeLayers([
959
+ ...collectDataLayers(chain.slice(0, documentIndex)),
960
+ {
961
+ source: documentLayer.source,
962
+ data: withResolvedPrompt(parsedDocument.data, composedPrompt?.prompt)
963
+ },
964
+ ...stripResolvedBasePrompts(resolvedBase?.layers ?? [], composedPrompt?.consumedBaseIndexes ?? /* @__PURE__ */ new Set()),
965
+ ...collectDataLayers(chain.slice(documentIndex + 1))
966
+ ]);
967
+ if (composedPrompt !== void 0 && merged.sources.prompt === documentLayer.source && composedPrompt.source !== void 0) {
968
+ merged.sources.prompt = composedPrompt.source;
969
+ }
970
+ return {
971
+ data: merged.data,
972
+ sources: merged.sources,
973
+ chain: [documentLayer.filePath, ...resolvedBase?.chain ?? []]
974
+ };
975
+ }
976
+ function classifyChain(chain) {
977
+ const baseLayers = [];
978
+ const documentLayers = [];
979
+ for (const [index, layer] of chain.entries()) {
980
+ if (isDataLayer(layer)) {
981
+ continue;
982
+ }
983
+ if (isDocumentLayer(layer)) {
984
+ documentLayers.push({ index, layer });
985
+ continue;
986
+ }
987
+ if (isBaseLayer(layer)) {
988
+ baseLayers.push(layer);
989
+ }
990
+ }
991
+ if (documentLayers.length !== 1) {
992
+ throw new Error(`Exactly one document layer is required, received ${documentLayers.length}.`);
993
+ }
994
+ return {
995
+ baseLayers,
996
+ documentIndex: documentLayers[0].index,
997
+ documentLayer: documentLayers[0].layer
998
+ };
999
+ }
1000
+ async function resolveBaseChain({
1001
+ name,
1002
+ baseLayers,
1003
+ options,
1004
+ optional,
1005
+ visited,
1006
+ depth
1007
+ }) {
1008
+ if (depth > MAX_EXTENDS_DEPTH) {
1009
+ throw new Error(`Maximum extends depth exceeded (${MAX_EXTENDS_DEPTH}).`);
1010
+ }
1011
+ let discoveredBase;
1012
+ try {
1013
+ discoveredBase = await findBase(
1014
+ name,
1015
+ baseLayers.map((layer) => layer.path),
1016
+ options.fs
1017
+ );
1018
+ } catch (error2) {
1019
+ if (optional && isBaseNotFoundError(error2)) {
1020
+ return void 0;
1021
+ }
1022
+ throw error2;
1023
+ }
1024
+ if (visited.has(discoveredBase.filePath)) {
1025
+ if (optional) {
1026
+ return void 0;
1027
+ }
1028
+ throw new Error(
1029
+ `Circular extends detected.
1030
+ Visited files:
1031
+ - ${[...visited, discoveredBase.filePath].join("\n- ")}`
1032
+ );
1033
+ }
1034
+ const matchedBaseIndex = baseLayers.findIndex(
1035
+ (layer) => layer.path === path6.dirname(discoveredBase.filePath)
1036
+ );
1037
+ if (matchedBaseIndex === -1) {
1038
+ throw new Error(`Resolved base is outside configured base paths: ${discoveredBase.filePath}`);
1039
+ }
1040
+ const parsedBase = parseDocument(discoveredBase.content, discoveredBase.filePath);
1041
+ const nextVisited = new Set(visited);
1042
+ nextVisited.add(discoveredBase.filePath);
1043
+ const nestedBase = parsedBase.extends ? await resolveBaseChain({
1044
+ name: getBaseName(discoveredBase.filePath),
1045
+ baseLayers: baseLayers.slice(matchedBaseIndex + 1),
1046
+ options,
1047
+ optional: false,
1048
+ visited: nextVisited,
1049
+ depth: depth + 1
1050
+ }) : void 0;
1051
+ return {
1052
+ layers: [
1053
+ {
1054
+ source: baseLayers[matchedBaseIndex].source,
1055
+ data: parsedBase.data
1056
+ },
1057
+ ...nestedBase?.layers ?? []
1058
+ ],
1059
+ chain: [discoveredBase.filePath, ...nestedBase?.chain ?? []]
1060
+ };
1061
+ }
1062
+ function collectDataLayers(chain) {
1063
+ return chain.filter(isDataLayer);
1064
+ }
1065
+ function composePromptChain(documentLayer, baseLayers) {
1066
+ const documentPrompt = documentLayer.data.prompt;
1067
+ if (documentPrompt !== void 0 && typeof documentPrompt !== "string") {
1068
+ return void 0;
1069
+ }
1070
+ if (documentPrompt !== void 0) {
1071
+ assertValidYieldCount(documentPrompt);
1072
+ }
1073
+ let prompt = documentPrompt;
1074
+ let source = prompt === void 0 || prompt === "" ? void 0 : documentLayer.source;
1075
+ const consumedBaseIndexes = /* @__PURE__ */ new Set();
1076
+ for (const [index, layer] of baseLayers.entries()) {
1077
+ const candidate = layer.data.prompt;
1078
+ if (candidate === void 0) {
1079
+ continue;
1080
+ }
1081
+ if (typeof candidate !== "string") {
1082
+ break;
1083
+ }
1084
+ assertValidYieldCount(candidate);
1085
+ consumedBaseIndexes.add(index);
1086
+ prompt = composeAdjacentPrompts(prompt, candidate);
1087
+ if (source === void 0 && candidate !== "") {
1088
+ source = layer.source;
1089
+ }
1090
+ }
1091
+ if (prompt !== void 0 && prompt.includes(YIELD_TOKEN)) {
1092
+ throw new Error('Final resolved prompt contains an unresolved "{{yield}}" token.');
1093
+ }
1094
+ if (prompt === void 0) {
1095
+ return void 0;
1096
+ }
1097
+ return {
1098
+ consumedBaseIndexes,
1099
+ prompt,
1100
+ source
1101
+ };
1102
+ }
1103
+ function composeAdjacentPrompts(high, low) {
1104
+ if (high === void 0 || high === "") {
1105
+ return low.includes(YIELD_TOKEN) ? replaceYield(low, "") : low;
1106
+ }
1107
+ if (high.includes(YIELD_TOKEN)) {
1108
+ return replaceYield(high, low);
1109
+ }
1110
+ if (low.includes(YIELD_TOKEN)) {
1111
+ return replaceYield(low, high);
1112
+ }
1113
+ return high;
1114
+ }
1115
+ function replaceYield(prompt, replacement) {
1116
+ return prompt.replace(YIELD_TOKEN, replacement);
1117
+ }
1118
+ function assertValidYieldCount(prompt) {
1119
+ if (countYieldTokens(prompt) > 1) {
1120
+ throw new Error('Prompt composition supports exactly one "{{yield}}" token per prompt.');
1121
+ }
1122
+ }
1123
+ function countYieldTokens(prompt) {
1124
+ return prompt.split(YIELD_TOKEN).length - 1;
1125
+ }
1126
+ function withResolvedPrompt(data, prompt) {
1127
+ if (prompt === void 0) {
1128
+ return data;
1129
+ }
1130
+ return {
1131
+ ...data,
1132
+ prompt
1133
+ };
1134
+ }
1135
+ function stripResolvedBasePrompts(layers, consumedBaseIndexes) {
1136
+ return layers.map((layer, index) => {
1137
+ if (!consumedBaseIndexes.has(index) || typeof layer.data.prompt !== "string") {
1138
+ return layer;
1139
+ }
1140
+ const { prompt: ignoredPrompt, ...data } = layer.data;
1141
+ void ignoredPrompt;
1142
+ return {
1143
+ source: layer.source,
1144
+ data
1145
+ };
1146
+ });
1147
+ }
1148
+ function getBaseName(filePath) {
1149
+ return path6.basename(filePath, path6.extname(filePath));
1150
+ }
1151
+ function shouldResolveBase(parsedDocument, autoExtend) {
1152
+ return parsedDocument.extends || autoExtend === true && !parsedDocument.hasExtendsField;
1153
+ }
1154
+ function isBaseNotFoundError(error2) {
1155
+ return error2 instanceof Error && error2.message.startsWith('Base "') && error2.message.includes('" not found.\nChecked paths:');
1156
+ }
1157
+ function isDataLayer(layer) {
1158
+ return "data" in layer;
1159
+ }
1160
+ function isDocumentLayer(layer) {
1161
+ return "filePath" in layer && "content" in layer;
1162
+ }
1163
+ function isBaseLayer(layer) {
1164
+ return "path" in layer;
1165
+ }
539
1166
 
540
1167
  // packages/config-mutations/src/mutations/config-mutation.ts
541
1168
  function merge(options) {
@@ -883,16 +1510,16 @@ function getConfigFormat(pathOrFormat) {
883
1510
  }
884
1511
  return formatRegistry[formatName];
885
1512
  }
886
- function detectFormat(path18) {
887
- const ext = getExtension(path18);
1513
+ function detectFormat2(path22) {
1514
+ const ext = getExtension(path22);
888
1515
  return extensionMap[ext];
889
1516
  }
890
- function getExtension(path18) {
891
- const lastDot = path18.lastIndexOf(".");
1517
+ function getExtension(path22) {
1518
+ const lastDot = path22.lastIndexOf(".");
892
1519
  if (lastDot === -1) {
893
1520
  return "";
894
1521
  }
895
- return path18.slice(lastDot).toLowerCase();
1522
+ return path22.slice(lastDot).toLowerCase();
896
1523
  }
897
1524
 
898
1525
  // packages/config-mutations/src/execution/path-utils.ts
@@ -943,7 +1570,7 @@ function resolvePath(rawPath, homeDir, pathMapper) {
943
1570
  function isNotFound(error2) {
944
1571
  return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
945
1572
  }
946
- async function readFileIfExists(fs, target) {
1573
+ async function readFileIfExists2(fs, target) {
947
1574
  try {
948
1575
  return await fs.readFile(target, "utf8");
949
1576
  } catch (error2) {
@@ -1227,7 +1854,7 @@ async function applyBackup(mutation, context, options) {
1227
1854
  label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1228
1855
  targetPath
1229
1856
  };
1230
- const content = await readFileIfExists(context.fs, targetPath);
1857
+ const content = await readFileIfExists2(context.fs, targetPath);
1231
1858
  if (content === null) {
1232
1859
  return {
1233
1860
  outcome: { changed: false, effect: "none", detail: "noop" },
@@ -1251,14 +1878,14 @@ async function applyConfigMerge(mutation, context, options) {
1251
1878
  label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1252
1879
  targetPath
1253
1880
  };
1254
- const formatName = mutation.format ?? detectFormat(rawPath);
1881
+ const formatName = mutation.format ?? detectFormat2(rawPath);
1255
1882
  if (!formatName) {
1256
1883
  throw new Error(
1257
1884
  `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1258
1885
  );
1259
1886
  }
1260
1887
  const format = getConfigFormat(formatName);
1261
- const rawContent = await readFileIfExists(context.fs, targetPath);
1888
+ const rawContent = await readFileIfExists2(context.fs, targetPath);
1262
1889
  let current;
1263
1890
  try {
1264
1891
  current = rawContent === null ? {} : format.parse(rawContent);
@@ -1297,14 +1924,14 @@ async function applyConfigPrune(mutation, context, options) {
1297
1924
  label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1298
1925
  targetPath
1299
1926
  };
1300
- const rawContent = await readFileIfExists(context.fs, targetPath);
1927
+ const rawContent = await readFileIfExists2(context.fs, targetPath);
1301
1928
  if (rawContent === null) {
1302
1929
  return {
1303
1930
  outcome: { changed: false, effect: "none", detail: "noop" },
1304
1931
  details
1305
1932
  };
1306
1933
  }
1307
- const formatName = mutation.format ?? detectFormat(rawPath);
1934
+ const formatName = mutation.format ?? detectFormat2(rawPath);
1308
1935
  if (!formatName) {
1309
1936
  throw new Error(
1310
1937
  `Cannot detect config format for "${rawPath}". Provide explicit format option.`
@@ -1360,14 +1987,14 @@ async function applyConfigTransform(mutation, context, options) {
1360
1987
  label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1361
1988
  targetPath
1362
1989
  };
1363
- const formatName = mutation.format ?? detectFormat(rawPath);
1990
+ const formatName = mutation.format ?? detectFormat2(rawPath);
1364
1991
  if (!formatName) {
1365
1992
  throw new Error(
1366
1993
  `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1367
1994
  );
1368
1995
  }
1369
1996
  const format = getConfigFormat(formatName);
1370
- const rawContent = await readFileIfExists(context.fs, targetPath);
1997
+ const rawContent = await readFileIfExists2(context.fs, targetPath);
1371
1998
  let current;
1372
1999
  try {
1373
2000
  current = rawContent === null ? {} : format.parse(rawContent);
@@ -1467,7 +2094,7 @@ async function applyTemplateMerge(mutation, context, options, formatName) {
1467
2094
  { cause: error2 }
1468
2095
  );
1469
2096
  }
1470
- const rawContent = await readFileIfExists(context.fs, targetPath);
2097
+ const rawContent = await readFileIfExists2(context.fs, targetPath);
1471
2098
  let current;
1472
2099
  try {
1473
2100
  current = rawContent === null ? {} : format.parse(rawContent);
@@ -1543,28 +2170,220 @@ import Mustache2 from "mustache";
1543
2170
  var originalEscape = Mustache2.escape;
1544
2171
 
1545
2172
  // packages/poe-code-config/src/store.ts
1546
- var EMPTY_DOCUMENT = `${JSON.stringify({}, null, 2)}
1547
- `;
1548
-
1549
- // packages/poe-code-config/src/inspect.ts
1550
- import path9 from "node:path";
1551
- var EMPTY_DOCUMENT2 = `${JSON.stringify({}, null, 2)}
1552
- `;
1553
-
1554
- // packages/poe-code-config/src/state/index.ts
1555
- import os2 from "node:os";
1556
-
1557
- // packages/poe-code-config/src/state/jobs.ts
1558
- import path10 from "node:path";
1559
-
1560
- // packages/poe-code-config/src/state/fs.ts
1561
- import * as nodeFs2 from "node:fs/promises";
1562
-
1563
- // packages/poe-code-config/src/state/templates.ts
1564
- import path11 from "node:path";
1565
-
1566
- // packages/agent-harness-tools/src/execution-env.ts
1567
- var executionEnvFactories = /* @__PURE__ */ new Map();
2173
+ async function readMergedDocument(fs, globalPath, projectPath) {
2174
+ const globalDocument = await readStoredDocument(fs, globalPath);
2175
+ if (!projectPath || projectPath === globalPath) {
2176
+ return globalDocument.data;
2177
+ }
2178
+ const projectDocument = await readStoredDocument(fs, projectPath);
2179
+ const resolved = await resolve(
2180
+ [
2181
+ {
2182
+ source: "project",
2183
+ filePath: projectPath,
2184
+ content: projectDocument.content
2185
+ },
2186
+ {
2187
+ source: "base",
2188
+ path: path8.dirname(globalPath)
2189
+ }
2190
+ ],
2191
+ {
2192
+ fs: createResolvedConfigFs(fs, globalPath, globalDocument.content),
2193
+ autoExtend: true
2194
+ }
2195
+ );
2196
+ return normalizeDocument(resolved.data);
2197
+ }
2198
+ async function readStoredDocument(fs, filePath) {
2199
+ try {
2200
+ const raw = await fs.readFile(filePath, "utf8");
2201
+ return await parseStoredDocument(fs, filePath, raw);
2202
+ } catch (error2) {
2203
+ if (isNotFound(error2)) {
2204
+ return {
2205
+ content: EMPTY_DOCUMENT,
2206
+ data: {}
2207
+ };
2208
+ }
2209
+ throw error2;
2210
+ }
2211
+ }
2212
+ async function parseStoredDocument(fs, filePath, raw) {
2213
+ try {
2214
+ return {
2215
+ content: raw,
2216
+ data: normalizeDocument(JSON.parse(raw))
2217
+ };
2218
+ } catch (error2) {
2219
+ if (error2 instanceof SyntaxError) {
2220
+ await recoverInvalidDocument(fs, filePath, raw);
2221
+ return {
2222
+ content: EMPTY_DOCUMENT,
2223
+ data: {}
2224
+ };
2225
+ }
2226
+ throw error2;
2227
+ }
2228
+ }
2229
+ function normalizeDocument(value) {
2230
+ if (!isRecord2(value)) {
2231
+ return {};
2232
+ }
2233
+ const document = {};
2234
+ for (const [scope, scopeValues] of Object.entries(value)) {
2235
+ const normalizedValues = normalizeScopeValues(scopeValues);
2236
+ if (Object.keys(normalizedValues).length > 0) {
2237
+ document[scope] = normalizedValues;
2238
+ }
2239
+ }
2240
+ return document;
2241
+ }
2242
+ function normalizeScopeValues(value) {
2243
+ if (!isRecord2(value)) {
2244
+ return {};
2245
+ }
2246
+ const normalized = {};
2247
+ for (const [key, entry] of Object.entries(value)) {
2248
+ if (entry !== void 0) {
2249
+ normalized[key] = entry;
2250
+ }
2251
+ }
2252
+ return normalized;
2253
+ }
2254
+ function createResolvedConfigFs(fs, globalPath, globalContent) {
2255
+ return {
2256
+ readFile(filePath, _encoding) {
2257
+ if (filePath === globalPath) {
2258
+ return Promise.resolve(globalContent);
2259
+ }
2260
+ return fs.readFile(filePath, "utf8");
2261
+ }
2262
+ };
2263
+ }
2264
+ async function recoverInvalidDocument(fs, filePath, content) {
2265
+ await fs.mkdir(path8.dirname(filePath), { recursive: true });
2266
+ const backupPath = createInvalidBackupPath(filePath);
2267
+ await fs.writeFile(backupPath, content, { encoding: "utf8" });
2268
+ await fs.writeFile(filePath, EMPTY_DOCUMENT, { encoding: "utf8" });
2269
+ }
2270
+ function createInvalidBackupPath(filePath) {
2271
+ const directory = path8.dirname(filePath);
2272
+ const baseName = path8.basename(filePath);
2273
+ return path8.join(directory, `${baseName}.invalid-${createTimestamp()}.json`);
2274
+ }
2275
+ function isRecord2(value) {
2276
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
2277
+ }
2278
+ function resolveConfigPath(homeDir) {
2279
+ return path8.join(homeDir, ".poe-code", "config.json");
2280
+ }
2281
+ function resolveProjectConfigPath(cwd) {
2282
+ return path8.join(cwd, ".poe-code", "config.json");
2283
+ }
2284
+ var EMPTY_DOCUMENT = `${JSON.stringify({}, null, 2)}
2285
+ `;
2286
+
2287
+ // packages/poe-code-config/src/resolve.ts
2288
+ function resolveScope(schema, fileValues, env = {}) {
2289
+ const resolved = {};
2290
+ for (const key of Object.keys(schema)) {
2291
+ const field = schema[key];
2292
+ const envValue = resolveEnvValue(field, env, key);
2293
+ const fileValue = resolveFileValue(field, fileValues?.[key], key);
2294
+ resolved[key] = envValue ?? fileValue ?? field.default;
2295
+ }
2296
+ return resolved;
2297
+ }
2298
+ function resolveEnvValue(field, env, key) {
2299
+ if (!field.env) {
2300
+ return void 0;
2301
+ }
2302
+ const raw = env[field.env];
2303
+ if (raw === void 0) {
2304
+ return void 0;
2305
+ }
2306
+ return coerceValue(field, raw, key);
2307
+ }
2308
+ function resolveFileValue(field, value, key) {
2309
+ return coerceValue(field, value, key);
2310
+ }
2311
+ function coerceValue(field, value, key) {
2312
+ switch (field.type) {
2313
+ case "string":
2314
+ return typeof value === "string" ? value : void 0;
2315
+ case "number":
2316
+ return coerceNumber(value);
2317
+ case "boolean":
2318
+ return coerceBoolean(value);
2319
+ case "json":
2320
+ return coerceJson(field, value, key);
2321
+ }
2322
+ }
2323
+ function coerceNumber(value) {
2324
+ if (typeof value === "number" && Number.isFinite(value)) {
2325
+ return value;
2326
+ }
2327
+ if (typeof value !== "string" || value.length === 0) {
2328
+ return void 0;
2329
+ }
2330
+ const parsed = Number(value);
2331
+ return Number.isNaN(parsed) ? void 0 : parsed;
2332
+ }
2333
+ function coerceBoolean(value) {
2334
+ if (typeof value === "boolean") {
2335
+ return value;
2336
+ }
2337
+ if (value === "true" || value === "1") {
2338
+ return true;
2339
+ }
2340
+ if (value === "false" || value === "0") {
2341
+ return false;
2342
+ }
2343
+ return void 0;
2344
+ }
2345
+ function coerceJson(field, value, key) {
2346
+ if (value === void 0) {
2347
+ return void 0;
2348
+ }
2349
+ const parsedValue = parseJsonValue(value, key);
2350
+ try {
2351
+ return field.parse(parsedValue);
2352
+ } catch (error2) {
2353
+ const message2 = error2 instanceof Error ? error2.message : "Invalid JSON value.";
2354
+ throw new Error(`Invalid config value for "${key}": ${message2}`);
2355
+ }
2356
+ }
2357
+ function parseJsonValue(value, key) {
2358
+ if (typeof value !== "string") {
2359
+ return value;
2360
+ }
2361
+ try {
2362
+ return JSON.parse(value);
2363
+ } catch {
2364
+ throw new Error(`Invalid config value for "${key}": expected valid JSON.`);
2365
+ }
2366
+ }
2367
+
2368
+ // packages/poe-code-config/src/inspect.ts
2369
+ import path9 from "node:path";
2370
+ var EMPTY_DOCUMENT2 = `${JSON.stringify({}, null, 2)}
2371
+ `;
2372
+
2373
+ // packages/poe-code-config/src/state/index.ts
2374
+ import os2 from "node:os";
2375
+
2376
+ // packages/poe-code-config/src/state/jobs.ts
2377
+ import path10 from "node:path";
2378
+
2379
+ // packages/poe-code-config/src/state/fs.ts
2380
+ import * as nodeFs2 from "node:fs/promises";
2381
+
2382
+ // packages/poe-code-config/src/state/templates.ts
2383
+ import path11 from "node:path";
2384
+
2385
+ // packages/agent-harness-tools/src/execution-env.ts
2386
+ var executionEnvFactories = /* @__PURE__ */ new Map();
1568
2387
  function registerExecutionEnvFactory(factory) {
1569
2388
  executionEnvFactories.set(factory.type, factory);
1570
2389
  }
@@ -1802,14 +2621,15 @@ var dockerExecutionEnvFactory = {
1802
2621
  context
1803
2622
  });
1804
2623
  },
1805
- async attach(envId) {
2624
+ async attach(envId, context) {
1806
2625
  const engine = detectEngine();
1807
2626
  return createDockerEnv({
1808
2627
  id: envId,
1809
- spec: createAttachedSpec(),
2628
+ spec: createAttachedSpec(context?.cwd),
1810
2629
  runner: createHostRunner(),
1811
2630
  engine,
1812
- context: detectContext()
2631
+ context: detectContext(),
2632
+ attachedJobId: context?.jobId
1813
2633
  });
1814
2634
  }
1815
2635
  };
@@ -1817,7 +2637,7 @@ function createDockerEnv(input) {
1817
2637
  const containerRef = input.id;
1818
2638
  return {
1819
2639
  id: containerRef,
1820
- job: null,
2640
+ job: input.attachedJobId === void 0 ? null : createContainerJob(containerRef, input.runner, input.engine, input.context, input.attachedJobId),
1821
2641
  async uploadWorkspace() {
1822
2642
  const tempDir = mkdtempSync(path14.join(tmpdir(), "poe-docker-upload-"));
1823
2643
  const archivePath = path14.join(tempDir, "workspace.tar");
@@ -1952,35 +2772,57 @@ async function resolveImage(input) {
1952
2772
  if (input.runtime.image !== void 0) {
1953
2773
  return input.runtime.image;
1954
2774
  }
2775
+ const result = await buildDockerRuntimeTemplate({
2776
+ cwd: input.spec.cwd,
2777
+ runtime: input.runtime,
2778
+ state: input.spec.state,
2779
+ runner: input.runner
2780
+ });
2781
+ return result.image;
2782
+ }
2783
+ async function buildDockerRuntimeTemplate(input) {
2784
+ const runner = input.runner ?? createHostRunner();
2785
+ const engine = input.runtime.engine ?? detectEngine();
2786
+ const context = detectContext();
1955
2787
  const dockerfilePath = path14.resolve(
1956
- input.spec.cwd,
2788
+ input.cwd,
1957
2789
  input.runtime.dockerfile ?? path14.join(".poe-code", "Dockerfile")
1958
2790
  );
1959
- const buildContext = path14.resolve(input.spec.cwd, input.runtime.build_context ?? ".");
2791
+ const buildContext = path14.resolve(input.cwd, input.runtime.build_context ?? ".");
1960
2792
  const dockerfileBytes = await readFile2(dockerfilePath);
1961
2793
  const hash = hashDockerTemplate(dockerfileBytes, input.runtime.build_args ?? {});
1962
- const cached2 = await input.spec.state?.templates.get("docker", hash);
2794
+ const cached2 = input.force ? null : await input.state?.templates.get("docker", hash);
1963
2795
  if (cached2?.image !== void 0) {
1964
- return cached2.image;
2796
+ return {
2797
+ backend: "docker",
2798
+ hash,
2799
+ image: cached2.image,
2800
+ cached: true
2801
+ };
1965
2802
  }
1966
2803
  const image = `poe-code/local:${hash}`;
1967
2804
  await buildImage({
1968
- runner: input.runner,
1969
- engine: input.engine,
1970
- context: input.context,
2805
+ runner,
2806
+ engine,
2807
+ context,
1971
2808
  image,
1972
2809
  dockerfilePath,
1973
2810
  buildContext,
1974
2811
  buildArgs: input.runtime.build_args ?? {}
1975
2812
  });
1976
- await input.spec.state?.templates.put("docker", {
2813
+ await input.state?.templates.put("docker", {
1977
2814
  hash,
1978
2815
  image,
1979
2816
  runtime_type: "docker",
1980
2817
  dockerfile_path: dockerfilePath,
1981
2818
  built_at: (/* @__PURE__ */ new Date()).toISOString()
1982
2819
  });
1983
- return image;
2820
+ return {
2821
+ backend: "docker",
2822
+ hash,
2823
+ image,
2824
+ cached: false
2825
+ };
1984
2826
  }
1985
2827
  function hashDockerTemplate(dockerfileBytes, buildArgs) {
1986
2828
  const hash = createHash2("sha256");
@@ -2065,9 +2907,9 @@ function buildEnvArgs(env) {
2065
2907
  function createContainerName() {
2066
2908
  return `poe-env-${randomBytes3(6).toString("hex")}`;
2067
2909
  }
2068
- async function createContainerJob(containerId, runner, engine, context) {
2910
+ function createContainerJob(containerId, runner, engine, context, jobId = containerId) {
2069
2911
  return {
2070
- id: containerId,
2912
+ id: jobId,
2071
2913
  envId: containerId,
2072
2914
  tool: "docker",
2073
2915
  argv: ["attach", containerId],
@@ -2091,7 +2933,25 @@ async function createContainerJob(containerId, runner, engine, context) {
2091
2933
  }
2092
2934
  return stdout.trim() === "running" ? "running" : "exited";
2093
2935
  },
2094
- async *stream() {
2936
+ async *stream(opts) {
2937
+ const handle = runner.exec({
2938
+ command: engine,
2939
+ args: [
2940
+ ...buildContextArgs(engine, context),
2941
+ "exec",
2942
+ containerId,
2943
+ "sh",
2944
+ "-c",
2945
+ `test -f ${shellQuote(`/tmp/poe-jobs/${jobId}.log`)} && tail -c +${(opts?.sinceByte ?? 0) + 1} ${shellQuote(`/tmp/poe-jobs/${jobId}.log`)} || true`
2946
+ ],
2947
+ stdout: "pipe",
2948
+ stderr: "pipe"
2949
+ });
2950
+ const stdout = await readStream(handle.stdout);
2951
+ await handle.result;
2952
+ if (stdout.length > 0) {
2953
+ yield { byteOffset: opts?.sinceByte ?? 0, data: stdout };
2954
+ }
2095
2955
  },
2096
2956
  async wait() {
2097
2957
  const handle = runner.exec({
@@ -2115,9 +2975,9 @@ async function createContainerJob(containerId, runner, engine, context) {
2115
2975
  }
2116
2976
  };
2117
2977
  }
2118
- function createAttachedSpec() {
2978
+ function createAttachedSpec(cwd = "/workspace") {
2119
2979
  return {
2120
- cwd: "/workspace",
2980
+ cwd,
2121
2981
  runtime: {
2122
2982
  type: "docker",
2123
2983
  image: "attached",
@@ -2189,12 +3049,778 @@ var hostExecutionEnvFactory = {
2189
3049
  // packages/process-runner/src/testing/mock-runner.ts
2190
3050
  import { Readable, Writable } from "node:stream";
2191
3051
 
3052
+ // packages/runner-e2b/src/factory.ts
3053
+ import path18 from "node:path";
3054
+
3055
+ // packages/runner-e2b/src/sdk.ts
3056
+ import { Template, Sandbox } from "e2b";
3057
+ async function createSandbox(opts) {
3058
+ return Sandbox.create(opts.templateId, {
3059
+ apiKey: opts.apiKey,
3060
+ envs: opts.env,
3061
+ ...opts.timeoutMinutes === void 0 ? {} : { timeoutMs: opts.timeoutMinutes * 6e4 }
3062
+ });
3063
+ }
3064
+ async function connectSandbox(id, apiKey) {
3065
+ return Sandbox.connect(id, apiKey === void 0 ? void 0 : { apiKey });
3066
+ }
3067
+ async function buildTemplate(opts) {
3068
+ const template = Template({ fileContextPath: opts.buildContext }).fromDockerfile(
3069
+ opts.dockerfilePath
3070
+ );
3071
+ if (opts.fromTemplate !== void 0 && opts.fromTemplate.length > 0) {
3072
+ template.fromTemplate(opts.fromTemplate);
3073
+ }
3074
+ const result = await Template.build(template, opts.name, {
3075
+ apiKey: opts.apiKey,
3076
+ ...opts.cpu === void 0 ? {} : { cpuCount: opts.cpu },
3077
+ ...opts.memoryMb === void 0 ? {} : { memoryMB: opts.memoryMb },
3078
+ ...opts.onLog ? { onBuildLogs: opts.onLog } : {}
3079
+ });
3080
+ return { templateId: result.templateId };
3081
+ }
3082
+ function toArrayBuffer(buffer) {
3083
+ const output = new ArrayBuffer(buffer.byteLength);
3084
+ new Uint8Array(output).set(buffer);
3085
+ return output;
3086
+ }
3087
+ async function readableToString(stream) {
3088
+ if (stream === null) {
3089
+ return "";
3090
+ }
3091
+ stream.setEncoding("utf8");
3092
+ const chunks = [];
3093
+ for await (const chunk of stream) {
3094
+ chunks.push(String(chunk));
3095
+ }
3096
+ return chunks.join("");
3097
+ }
3098
+
3099
+ // packages/runner-e2b/src/template-build.ts
3100
+ import { createHash as createHash3 } from "node:crypto";
3101
+ import { readdir, readFile as readFile3 } from "node:fs/promises";
3102
+ import path15 from "node:path";
3103
+ var BUILD_LOG_TAIL_SIZE = 30;
3104
+ async function buildE2bRuntimeTemplate(input) {
3105
+ const dockerfileBytes = await readFile3(input.dockerfilePath);
3106
+ const buildContextFiles = await readBuildContextFiles(input.buildContext);
3107
+ const hash = hashTemplate(dockerfileBytes, buildContextFiles, input.runtime.build_args);
3108
+ const cached2 = input.force === true ? null : await input.state?.templates.get("e2b", hash);
3109
+ if (cached2?.template_id !== void 0) {
3110
+ return { backend: "e2b", hash, templateId: cached2.template_id, cached: true };
3111
+ }
3112
+ const tail = [];
3113
+ const onLog = (entry) => {
3114
+ tail.push(entry.message);
3115
+ if (tail.length > BUILD_LOG_TAIL_SIZE) {
3116
+ tail.shift();
3117
+ }
3118
+ input.onLog?.(entry);
3119
+ };
3120
+ let built;
3121
+ try {
3122
+ built = await buildTemplate({
3123
+ apiKey: input.apiKey,
3124
+ name: `poe-code-${hash.slice(0, 32)}`,
3125
+ dockerfilePath: input.dockerfilePath,
3126
+ buildContext: input.buildContext,
3127
+ cpu: input.runtime.cpu,
3128
+ memoryMb: input.runtime.memory_mb,
3129
+ fromTemplate: input.runtime.from_template,
3130
+ onLog
3131
+ });
3132
+ } catch (error2) {
3133
+ throw decorateBuildError(error2, tail);
3134
+ }
3135
+ await input.state?.templates.put("e2b", {
3136
+ hash,
3137
+ template_id: built.templateId,
3138
+ runtime_type: "e2b",
3139
+ dockerfile_path: input.dockerfilePath,
3140
+ built_at: (/* @__PURE__ */ new Date()).toISOString()
3141
+ });
3142
+ return { backend: "e2b", hash, templateId: built.templateId, cached: false };
3143
+ }
3144
+ function decorateBuildError(error2, tail) {
3145
+ const original = error2 instanceof Error ? error2 : new Error(String(error2));
3146
+ if (tail.length === 0) {
3147
+ return original;
3148
+ }
3149
+ const decorated = new Error(`${original.message}
3150
+
3151
+ Last build output:
3152
+ ${tail.join("\n")}`);
3153
+ decorated.stack = original.stack;
3154
+ decorated.cause = original;
3155
+ return decorated;
3156
+ }
3157
+ function hashTemplate(dockerfileBytes, buildContextFiles, buildArgs) {
3158
+ const hash = createHash3("sha256");
3159
+ hash.update(dockerfileBytes);
3160
+ hash.update("\0");
3161
+ for (const file of buildContextFiles) {
3162
+ hash.update(file.relativePath);
3163
+ hash.update("\0");
3164
+ hash.update(file.bytes);
3165
+ hash.update("\0");
3166
+ }
3167
+ for (const [key, value] of Object.entries(buildArgs).sort(
3168
+ ([left], [right]) => left.localeCompare(right)
3169
+ )) {
3170
+ hash.update(key);
3171
+ hash.update("=");
3172
+ hash.update(value);
3173
+ hash.update("\0");
3174
+ }
3175
+ return hash.digest("hex");
3176
+ }
3177
+ async function readBuildContextFiles(buildContext) {
3178
+ const files = [];
3179
+ await collectBuildContextFiles(buildContext, "", files);
3180
+ return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
3181
+ }
3182
+ async function collectBuildContextFiles(buildContext, relativeDir, files) {
3183
+ const absoluteDir = path15.join(buildContext, relativeDir);
3184
+ const entries = await readdir(absoluteDir, { withFileTypes: true });
3185
+ for (const entry of entries) {
3186
+ const relativePath = path15.join(relativeDir, entry.name);
3187
+ if (entry.isDirectory()) {
3188
+ await collectBuildContextFiles(buildContext, relativePath, files);
3189
+ continue;
3190
+ }
3191
+ if (!entry.isFile()) {
3192
+ continue;
3193
+ }
3194
+ files.push({
3195
+ relativePath: relativePath.split(path15.sep).join("/"),
3196
+ bytes: await readFile3(path15.join(buildContext, relativePath))
3197
+ });
3198
+ }
3199
+ }
3200
+
3201
+ // packages/runner-e2b/src/opened-env.ts
3202
+ import { mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "node:fs";
3203
+ import { readFile as readFile4, writeFile } from "node:fs/promises";
3204
+ import { tmpdir as tmpdir2 } from "node:os";
3205
+ import path17 from "node:path";
3206
+ import { PassThrough, Writable as Writable2 } from "node:stream";
3207
+
3208
+ // packages/runner-e2b/src/job-handle.ts
3209
+ import path16 from "node:path";
3210
+ var JOB_DIR2 = "/tmp/poe-jobs";
3211
+ function createE2bJobHandle(input) {
3212
+ const fs = createE2bLogStreamFs(input.sandbox);
3213
+ return {
3214
+ id: input.jobId,
3215
+ envId: input.envId,
3216
+ tool: input.tool,
3217
+ argv: input.argv,
3218
+ async status() {
3219
+ const exit = await readExitCode(input.sandbox, input.jobId);
3220
+ if (exit !== null) {
3221
+ return "exited";
3222
+ }
3223
+ const processes = await input.sandbox.commands.list();
3224
+ const isRunning = input.pid === void 0 ? processes.some((process2) => processMentionsJob(process2, input.jobId)) : processes.some((process2) => process2.pid === input.pid);
3225
+ return isRunning ? "running" : "lost";
3226
+ },
3227
+ stream(opts = {}) {
3228
+ return streamLogFile({ fs }, input.jobId, opts);
3229
+ },
3230
+ async wait() {
3231
+ const result = await waitForExit({ fs }, input.jobId);
3232
+ const preserveMs = input.preserveAfterExitHours * 60 * 60 * 1e3;
3233
+ if (preserveMs > 0) {
3234
+ await input.sandbox.setTimeout(preserveMs);
3235
+ }
3236
+ return result;
3237
+ },
3238
+ async kill() {
3239
+ const pids = input.pid === void 0 ? (await input.sandbox.commands.list()).filter((process2) => processMentionsJob(process2, input.jobId)).map((process2) => process2.pid) : [input.pid];
3240
+ await Promise.all(pids.map((pid) => input.sandbox.commands.kill(pid)));
3241
+ }
3242
+ };
3243
+ }
3244
+ function createE2bLogStreamFs(sandbox) {
3245
+ return {
3246
+ promises: {
3247
+ async readFile(filePath) {
3248
+ return Buffer.from(await sandbox.files.read(filePath, { format: "bytes" }));
3249
+ },
3250
+ async stat(filePath) {
3251
+ const result = await sandbox.commands.run(
3252
+ `stat -c %Y ${shellQuote2(filePath)} 2>/dev/null || stat -f %m ${shellQuote2(filePath)}`
3253
+ );
3254
+ if (!("stdout" in result)) {
3255
+ throw new Error(`Unable to stat ${filePath}`);
3256
+ }
3257
+ const seconds = Number(result.stdout?.trim());
3258
+ if (!Number.isFinite(seconds)) {
3259
+ throw new Error(`Unable to stat ${filePath}`);
3260
+ }
3261
+ return { mtimeMs: seconds * 1e3 };
3262
+ }
3263
+ },
3264
+ watch(filePath, listener) {
3265
+ let closed = false;
3266
+ let stop = null;
3267
+ void sandbox.files.watchDir(path16.dirname(filePath), listener, { recursive: false }).then((handle) => {
3268
+ if (closed) {
3269
+ void handle.stop();
3270
+ return;
3271
+ }
3272
+ stop = () => {
3273
+ void handle.stop();
3274
+ };
3275
+ });
3276
+ return {
3277
+ close() {
3278
+ closed = true;
3279
+ stop?.();
3280
+ }
3281
+ };
3282
+ }
3283
+ };
3284
+ }
3285
+ function processMentionsJob(process2, jobId) {
3286
+ const needle = `/tmp/poe-jobs/${jobId}`;
3287
+ return process2.cmd.includes(needle) || process2.args.some((arg) => arg.includes(needle));
3288
+ }
3289
+ function shellQuote2(value) {
3290
+ return `'${value.replaceAll("'", "'\\''")}'`;
3291
+ }
3292
+ async function readExitCode(sandbox, jobId) {
3293
+ try {
3294
+ const contents = await sandbox.files.read(`${JOB_DIR2}/${jobId}.exit`);
3295
+ const exitCode = Number(contents.trim());
3296
+ return Number.isInteger(exitCode) ? exitCode : null;
3297
+ } catch {
3298
+ return null;
3299
+ }
3300
+ }
3301
+
3302
+ // packages/runner-e2b/src/opened-env.ts
3303
+ var REMOTE_COMMAND_STDERR_TAIL_SIZE = 30;
3304
+ function createOpenedE2bEnv(input) {
3305
+ const hostRunner = input.spec.hostRunner ?? createHostRunner();
3306
+ const hostWorkspaceDir = path17.resolve(input.spec.cwd);
3307
+ const sandboxWorkspaceDir = normalizeSandboxWorkspaceDir(input.runtime.workspace_dir);
3308
+ let lastProcess = null;
3309
+ let detachedJobContext = null;
3310
+ const mapWorkspaceCwd = (cwd) => {
3311
+ if (cwd === void 0) {
3312
+ return void 0;
3313
+ }
3314
+ if (path17.isAbsolute(cwd) && path17.resolve(cwd) === hostWorkspaceDir) {
3315
+ return sandboxWorkspaceDir;
3316
+ }
3317
+ return cwd;
3318
+ };
3319
+ const attachedJobId = input.spec.detachedJobId;
3320
+ const env = {
3321
+ id: input.sandbox.sandboxId,
3322
+ job: attachedJobId ? createE2bJobHandle({
3323
+ sandbox: input.sandbox,
3324
+ envId: input.sandbox.sandboxId,
3325
+ jobId: attachedJobId,
3326
+ tool: input.spec.jobLabel.tool,
3327
+ argv: input.spec.jobLabel.argv,
3328
+ preserveAfterExitHours: input.runtime.preserve_after_exit_hours ?? 24
3329
+ }) : null,
3330
+ fs: createE2bLogStreamFs(input.sandbox),
3331
+ setDetachedJobContext(context) {
3332
+ detachedJobContext = context;
3333
+ },
3334
+ async uploadWorkspace() {
3335
+ if (input.spec.runner?.sync === "none") {
3336
+ return { files: 0, bytes: 0, skipped: [] };
3337
+ }
3338
+ const tempDir = mkdtempSync2(path17.join(tmpdir2(), "poe-e2b-upload-"));
3339
+ const archivePath = path17.join(tempDir, "workspace.tar");
3340
+ try {
3341
+ await runOrThrow2(hostRunner, {
3342
+ command: "tar",
3343
+ args: [
3344
+ ...input.spec.uploadIgnoreFiles.flatMap((ignored) => ["--exclude", ignored]),
3345
+ "-cf",
3346
+ archivePath,
3347
+ "-C",
3348
+ input.spec.cwd,
3349
+ "."
3350
+ ],
3351
+ stdout: "pipe",
3352
+ stderr: "pipe"
3353
+ });
3354
+ await input.sandbox.files.write(
3355
+ "/tmp/poe-workspace-upload.tar",
3356
+ toArrayBuffer(await readFile4(archivePath))
3357
+ );
3358
+ await runRemoteOrThrow(
3359
+ input.sandbox,
3360
+ createUploadWorkspaceCommand(sandboxWorkspaceDir)
3361
+ );
3362
+ return { files: 0, bytes: 0, skipped: [] };
3363
+ } finally {
3364
+ rmSync2(tempDir, { recursive: true, force: true });
3365
+ }
3366
+ },
3367
+ async downloadWorkspace(opts) {
3368
+ if (input.spec.runner?.sync === "upload" || input.spec.runner?.sync === "none") {
3369
+ return { files: 0, bytes: 0, conflicts: [] };
3370
+ }
3371
+ const tempDir = mkdtempSync2(path17.join(tmpdir2(), "poe-e2b-download-"));
3372
+ const archivePath = path17.join(tempDir, "workspace.tar");
3373
+ try {
3374
+ await runRemoteOrThrow(
3375
+ input.sandbox,
3376
+ `tar -cf /tmp/poe-workspace-download.tar -C ${shellQuote3(sandboxWorkspaceDir)} .`
3377
+ );
3378
+ const archive = await input.sandbox.files.read("/tmp/poe-workspace-download.tar", {
3379
+ format: "bytes"
3380
+ });
3381
+ await writeFile(archivePath, Buffer.from(archive));
3382
+ await runOrThrow2(hostRunner, {
3383
+ command: "tar",
3384
+ args: [
3385
+ opts.conflictPolicy === "refuse" ? "-xkf" : "-xf",
3386
+ archivePath,
3387
+ "-C",
3388
+ input.spec.cwd
3389
+ ],
3390
+ stdout: "pipe",
3391
+ stderr: "pipe"
3392
+ });
3393
+ return { files: 0, bytes: archive.byteLength, conflicts: [] };
3394
+ } finally {
3395
+ rmSync2(tempDir, { recursive: true, force: true });
3396
+ }
3397
+ },
3398
+ exec(spec) {
3399
+ const handle = runE2bCommand(input.sandbox, {
3400
+ ...spec,
3401
+ cwd: mapWorkspaceCwd(spec.cwd),
3402
+ env: resolveSandboxCommandEnv(spec.env)
3403
+ });
3404
+ lastProcess = { started: handle.started };
3405
+ return handle;
3406
+ },
3407
+ async detach() {
3408
+ if (detachedJobContext === null) {
3409
+ throw new Error("Cannot detach E2B environment before a job context is registered.");
3410
+ }
3411
+ if (lastProcess === null) {
3412
+ throw new Error("Cannot detach E2B environment before a command is running.");
3413
+ }
3414
+ const command = await lastProcess.started;
3415
+ const preserveAfterExitHours = input.runtime.preserve_after_exit_hours ?? 24;
3416
+ const preserveMs = preserveAfterExitHours * 60 * 60 * 1e3;
3417
+ if (preserveMs > 0) {
3418
+ await input.sandbox.setTimeout(preserveMs);
3419
+ }
3420
+ return createE2bJobHandle({
3421
+ sandbox: input.sandbox,
3422
+ envId: input.sandbox.sandboxId,
3423
+ jobId: detachedJobContext.id,
3424
+ tool: detachedJobContext.tool,
3425
+ argv: detachedJobContext.argv,
3426
+ pid: command.pid,
3427
+ preserveAfterExitHours
3428
+ });
3429
+ },
3430
+ shell() {
3431
+ const shellSpec = input.spec.shellSpec;
3432
+ const command = shellSpec?.command ?? input.spec.env.SHELL ?? "sh";
3433
+ return runE2bPty(input.sandbox, {
3434
+ command,
3435
+ ...shellSpec?.args ? { args: shellSpec.args } : {},
3436
+ cwd: mapWorkspaceCwd(shellSpec?.cwd ?? input.spec.cwd),
3437
+ env: resolveSandboxCommandEnv(
3438
+ shellSpec && "env" in shellSpec ? shellSpec.env : input.spec.env
3439
+ ),
3440
+ stdin: "inherit",
3441
+ stdout: "inherit",
3442
+ stderr: "inherit",
3443
+ tty: true
3444
+ });
3445
+ },
3446
+ async close() {
3447
+ await input.sandbox.kill();
3448
+ }
3449
+ };
3450
+ return env;
3451
+ }
3452
+ function runE2bCommand(sandbox, spec) {
3453
+ const stdout = spec.stdout === "inherit" ? null : new PassThrough();
3454
+ const stderr = spec.stderr === "inherit" ? null : new PassThrough();
3455
+ let e2bHandle = null;
3456
+ const command = shellCommand([spec.command, ...spec.args ?? []]);
3457
+ const started = sandbox.commands.run(command, {
3458
+ background: true,
3459
+ cwd: spec.cwd,
3460
+ envs: spec.env,
3461
+ stdin: spec.stdin === "pipe",
3462
+ onStdout(data) {
3463
+ stdout?.write(data);
3464
+ if (spec.stdout === "inherit") {
3465
+ process.stdout.write(data);
3466
+ }
3467
+ },
3468
+ onStderr(data) {
3469
+ stderr?.write(data);
3470
+ if (spec.stderr === "inherit") {
3471
+ process.stderr.write(data);
3472
+ }
3473
+ }
3474
+ });
3475
+ const stdin = spec.stdin === "pipe" ? new Writable2({
3476
+ write(chunk, _encoding, callback) {
3477
+ started.then(
3478
+ (handle) => sandbox.commands.sendStdin(
3479
+ handle.pid,
3480
+ Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))
3481
+ )
3482
+ ).then(() => callback(), callback);
3483
+ },
3484
+ final(callback) {
3485
+ if (sandbox.commands.closeStdin === void 0) {
3486
+ callback();
3487
+ return;
3488
+ }
3489
+ started.then((handle) => sandbox.commands.closeStdin(handle.pid)).then(() => callback(), callback);
3490
+ }
3491
+ }) : null;
3492
+ const result = started.then((handle) => {
3493
+ e2bHandle = handle;
3494
+ return handle.wait();
3495
+ }).then(
3496
+ (result2) => {
3497
+ stdout?.end();
3498
+ stderr?.end();
3499
+ return { exitCode: result2.exitCode ?? 0 };
3500
+ },
3501
+ (error2) => {
3502
+ stdout?.end();
3503
+ stderr?.end();
3504
+ if (isExitError(error2)) {
3505
+ return { exitCode: error2.exitCode };
3506
+ }
3507
+ return { exitCode: 1 };
3508
+ }
3509
+ );
3510
+ return {
3511
+ get pid() {
3512
+ return e2bHandle?.pid ?? null;
3513
+ },
3514
+ stdin,
3515
+ stdout,
3516
+ stderr,
3517
+ result,
3518
+ kill() {
3519
+ void e2bHandle?.kill();
3520
+ },
3521
+ get e2bHandle() {
3522
+ return e2bHandle;
3523
+ },
3524
+ started
3525
+ };
3526
+ }
3527
+ function runE2bPty(sandbox, spec) {
3528
+ const stdout = new PassThrough();
3529
+ let handleRef = null;
3530
+ const stdin = new Writable2({
3531
+ write(chunk, _encoding, callback) {
3532
+ if (handleRef === null) {
3533
+ callback(new Error("E2B PTY stdin is not ready."));
3534
+ return;
3535
+ }
3536
+ sandbox.pty.sendInput(handleRef.pid, Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))).then(() => callback(), callback);
3537
+ }
3538
+ });
3539
+ const started = sandbox.pty.create({
3540
+ cols: process.stdout.columns || 80,
3541
+ rows: process.stdout.rows || 24,
3542
+ cwd: spec.cwd,
3543
+ envs: spec.env,
3544
+ onData(data) {
3545
+ stdout.write(Buffer.from(data));
3546
+ if (spec.stdout === "inherit") {
3547
+ process.stdout.write(Buffer.from(data));
3548
+ }
3549
+ }
3550
+ });
3551
+ const result = started.then((handle) => {
3552
+ handleRef = handle;
3553
+ return handle.wait();
3554
+ }).then(
3555
+ (result2) => {
3556
+ stdout.end();
3557
+ return { exitCode: result2.exitCode ?? 0 };
3558
+ },
3559
+ () => {
3560
+ stdout.end();
3561
+ return { exitCode: 1 };
3562
+ }
3563
+ );
3564
+ return {
3565
+ get pid() {
3566
+ return handleRef?.pid ?? null;
3567
+ },
3568
+ stdin: spec.stdin === "inherit" ? process.stdin : stdin,
3569
+ stdout: spec.stdout === "inherit" ? null : stdout,
3570
+ stderr: null,
3571
+ result,
3572
+ kill() {
3573
+ void (handleRef === null ? void 0 : sandbox.pty.kill(handleRef.pid));
3574
+ }
3575
+ };
3576
+ }
3577
+ async function runRemoteOrThrow(sandbox, command) {
3578
+ const stdoutTail = createLineTail(REMOTE_COMMAND_STDERR_TAIL_SIZE);
3579
+ const stderrTail = createLineTail(REMOTE_COMMAND_STDERR_TAIL_SIZE);
3580
+ let result;
3581
+ try {
3582
+ result = await sandbox.commands.run(command, {
3583
+ onStdout(data) {
3584
+ stdoutTail.push(data);
3585
+ },
3586
+ onStderr(data) {
3587
+ stderrTail.push(data);
3588
+ }
3589
+ });
3590
+ } catch (error2) {
3591
+ appendRemoteCommandOutput(error2, stdoutTail, stderrTail);
3592
+ if (isCommandExitError(error2)) {
3593
+ throw decorateRemoteCommandError(error2, command, stderrTail.values());
3594
+ }
3595
+ throw error2;
3596
+ }
3597
+ appendRemoteCommandOutput(result, stdoutTail, stderrTail);
3598
+ if ("exitCode" in result && result.exitCode !== 0) {
3599
+ throw decorateRemoteCommandError(
3600
+ new Error(`E2B command failed with exit code ${result.exitCode}`),
3601
+ command,
3602
+ stderrTail.values()
3603
+ );
3604
+ }
3605
+ }
3606
+ function appendRemoteCommandOutput(source, stdoutTail, stderrTail) {
3607
+ if (!source || typeof source !== "object") {
3608
+ return;
3609
+ }
3610
+ const output = source;
3611
+ if (typeof output.stdout === "string") {
3612
+ stdoutTail.push(output.stdout);
3613
+ }
3614
+ if (typeof output.stderr === "string") {
3615
+ stderrTail.push(output.stderr);
3616
+ }
3617
+ }
3618
+ function decorateRemoteCommandError(error2, command, stderrTail) {
3619
+ const original = error2 instanceof Error ? error2 : new Error(String(error2));
3620
+ const tail = stderrTail.length === 0 ? "" : `
3621
+
3622
+ Last stderr output:
3623
+ ${stderrTail.join("\n")}`;
3624
+ const decorated = new Error(`E2B command failed: ${command}
3625
+ ${original.message}${tail}`);
3626
+ decorated.stack = original.stack;
3627
+ decorated.cause = original;
3628
+ return decorated;
3629
+ }
3630
+ function createLineTail(maxLines) {
3631
+ const lines = [];
3632
+ let pending = "";
3633
+ const appendLine = (line) => {
3634
+ lines.push(trimTrailingCarriageReturn(line));
3635
+ while (lines.length > maxLines) {
3636
+ lines.shift();
3637
+ }
3638
+ };
3639
+ return {
3640
+ push(chunk) {
3641
+ pending += chunk;
3642
+ const parts = pending.split("\n");
3643
+ pending = parts.pop() ?? "";
3644
+ for (const line of parts) {
3645
+ appendLine(line);
3646
+ }
3647
+ },
3648
+ values() {
3649
+ const output = [...lines];
3650
+ if (pending.length > 0) {
3651
+ output.push(trimTrailingCarriageReturn(pending));
3652
+ }
3653
+ return output.slice(-maxLines);
3654
+ }
3655
+ };
3656
+ }
3657
+ function trimTrailingCarriageReturn(value) {
3658
+ return value.endsWith("\r") ? value.slice(0, -1) : value;
3659
+ }
3660
+ async function runOrThrow2(runner, spec) {
3661
+ const handle = runner.exec(spec);
3662
+ const stderr = readableToString(handle.stderr);
3663
+ const result = await handle.result;
3664
+ if (result.exitCode !== 0) {
3665
+ throw new Error(
3666
+ `Command failed with exit code ${result.exitCode}: ${spec.command} ${(spec.args ?? []).join(" ")}
3667
+ ${await stderr}`
3668
+ );
3669
+ }
3670
+ }
3671
+ function shellCommand(argv) {
3672
+ return argv.map(shellQuote3).join(" ");
3673
+ }
3674
+ function shellQuote3(value) {
3675
+ return `'${value.replaceAll("'", "'\\''")}'`;
3676
+ }
3677
+ function createUploadWorkspaceCommand(sandboxWorkspaceDir) {
3678
+ const quotedWorkspaceDir = shellQuote3(sandboxWorkspaceDir);
3679
+ return [
3680
+ `mkdir -p ${quotedWorkspaceDir} || { command -v sudo >/dev/null 2>&1 && sudo mkdir -p ${quotedWorkspaceDir} && sudo chown "$(id -u):$(id -g)" ${quotedWorkspaceDir}; }`,
3681
+ `test -w ${quotedWorkspaceDir} && tar -xf /tmp/poe-workspace-upload.tar -C ${quotedWorkspaceDir}`
3682
+ ].join("\n");
3683
+ }
3684
+ function resolveSandboxCommandEnv(env) {
3685
+ if (env === void 0) {
3686
+ return void 0;
3687
+ }
3688
+ return {
3689
+ ...env,
3690
+ HOME: "/home/user"
3691
+ };
3692
+ }
3693
+ function normalizeSandboxWorkspaceDir(workspaceDir) {
3694
+ const resolvedWorkspaceDir = workspaceDir ?? "/workspace";
3695
+ if (!path17.posix.isAbsolute(resolvedWorkspaceDir)) {
3696
+ throw new Error("E2B runtime workspace_dir must be an absolute sandbox path.");
3697
+ }
3698
+ let normalized = path17.posix.normalize(resolvedWorkspaceDir);
3699
+ while (normalized.length > 1 && normalized.endsWith("/")) {
3700
+ normalized = normalized.slice(0, -1);
3701
+ }
3702
+ return normalized;
3703
+ }
3704
+ function isExitError(error2) {
3705
+ return Boolean(
3706
+ error2 && typeof error2 === "object" && typeof error2.exitCode === "number"
3707
+ );
3708
+ }
3709
+ function isCommandExitError(error2) {
3710
+ return isExitError(error2) || Boolean(
3711
+ error2 && typeof error2 === "object" && error2.name === "CommandExitError"
3712
+ );
3713
+ }
3714
+
3715
+ // packages/runner-e2b/src/auth-scope.ts
3716
+ import os4 from "node:os";
3717
+ import { promises as nodeFs4 } from "node:fs";
3718
+ var e2bAuthScope = defineScope("e2b", {
3719
+ api_key: {
3720
+ type: "string",
3721
+ default: "",
3722
+ doc: "E2B API key",
3723
+ env: "E2B_API_KEY"
3724
+ }
3725
+ });
3726
+ async function resolveE2bApiKey(input) {
3727
+ const homeDir = input.homeDir ?? os4.homedir();
3728
+ const fs = input.fs ?? nodeFs4;
3729
+ const env = input.env ?? process.env;
3730
+ const document = await readMergedDocument(
3731
+ fs,
3732
+ resolveConfigPath(homeDir),
3733
+ resolveProjectConfigPath(input.cwd)
3734
+ );
3735
+ const resolved = resolveScope(e2bAuthScope.schema, document.e2b, env);
3736
+ if (resolved.api_key.length === 0) {
3737
+ throw new Error(
3738
+ "No E2B API key. Set E2B_API_KEY or e2b.api_key in ~/.poe-code/config.json."
3739
+ );
3740
+ }
3741
+ return resolved.api_key;
3742
+ }
3743
+
3744
+ // packages/runner-e2b/src/factory.ts
3745
+ var e2bExecutionEnvFactory = {
3746
+ type: "e2b",
3747
+ supportsDetach: true,
3748
+ async open(spec) {
3749
+ const runtime = parseE2bRuntime(spec.runtime);
3750
+ const runtimeCwd = spec.runtimeCwd ?? spec.cwd;
3751
+ const apiKey = await resolveE2bApiKey({ cwd: runtimeCwd });
3752
+ const templateId = runtime.template_id ?? (await buildE2bRuntimeTemplate({
3753
+ runtime,
3754
+ dockerfilePath: path18.resolve(
3755
+ runtimeCwd,
3756
+ runtime.dockerfile ?? path18.join(".poe-code", "Dockerfile")
3757
+ ),
3758
+ buildContext: path18.resolve(runtimeCwd, runtime.build_context ?? "."),
3759
+ state: spec.state,
3760
+ apiKey
3761
+ })).templateId;
3762
+ const sandbox = await createSandbox({
3763
+ apiKey,
3764
+ templateId,
3765
+ env: spec.env,
3766
+ timeoutMinutes: runtime.timeout_minutes
3767
+ });
3768
+ return createOpenedE2bEnv({ sandbox, spec, runtime });
3769
+ },
3770
+ async attach(envId, context) {
3771
+ const cwd = context?.cwd ?? process.cwd();
3772
+ const apiKey = await resolveE2bApiKey({ cwd });
3773
+ const sandbox = await connectSandbox(envId, apiKey);
3774
+ return createOpenedE2bEnv({
3775
+ sandbox,
3776
+ spec: {
3777
+ cwd: context?.cwd ?? "/workspace",
3778
+ runtime: {
3779
+ type: "e2b",
3780
+ build_args: {},
3781
+ mounts: [],
3782
+ workspace_dir: "/workspace",
3783
+ preserve_after_exit_hours: 24
3784
+ },
3785
+ env: {},
3786
+ uploadIgnoreFiles: [],
3787
+ jobLabel: { tool: context?.tool ?? "e2b", argv: context?.argv ?? [] },
3788
+ ...context?.jobId ? { detachedJobId: context.jobId } : {}
3789
+ },
3790
+ runtime: {
3791
+ type: "e2b",
3792
+ build_args: {},
3793
+ mounts: [],
3794
+ workspace_dir: "/workspace",
3795
+ preserve_after_exit_hours: 24
3796
+ }
3797
+ });
3798
+ }
3799
+ };
3800
+ function parseE2bRuntime(runtime) {
3801
+ if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) {
3802
+ throw new Error("e2b runtime must be an object");
3803
+ }
3804
+ const record = runtime;
3805
+ if (record.type !== "e2b") {
3806
+ throw new Error('e2b runtime type must be "e2b"');
3807
+ }
3808
+ return record;
3809
+ }
3810
+
3811
+ // packages/runner-e2b/src/index.ts
3812
+ var e2bExecutionEnvFactory2 = e2bExecutionEnvFactory;
3813
+
2192
3814
  // packages/agent-spawn/src/register-factories.ts
2193
3815
  registerExecutionEnvFactory(hostExecutionEnvFactory);
2194
3816
  registerExecutionEnvFactory(dockerExecutionEnvFactory);
2195
- if (process.env.VITEST === "true") {
3817
+ registerExecutionEnvFactory(e2bExecutionEnvFactory2);
3818
+ if (isVitest()) {
2196
3819
  registerExecutionEnvFactory(createTestHostExecutionEnvFactory());
2197
3820
  }
3821
+ function isVitest() {
3822
+ return process.env.VITEST !== void 0 || process.env.VITEST_POOL_ID !== void 0;
3823
+ }
2198
3824
  function createTestHostExecutionEnvFactory() {
2199
3825
  return {
2200
3826
  type: "host",
@@ -2556,7 +4182,7 @@ function listMcpSupportedAgents() {
2556
4182
 
2557
4183
  // packages/agent-spawn/src/spawn.ts
2558
4184
  import { mkdirSync, openSync, writeSync, closeSync } from "node:fs";
2559
- import path15 from "node:path";
4185
+ import path19 from "node:path";
2560
4186
 
2561
4187
  // packages/agent-spawn/src/configs/resolve-config.ts
2562
4188
  function resolveConfig(agentId) {
@@ -3055,7 +4681,7 @@ import chalk8 from "chalk";
3055
4681
 
3056
4682
  // packages/design-system/src/dashboard/terminal.ts
3057
4683
  import readline from "node:readline";
3058
- import { PassThrough } from "node:stream";
4684
+ import { PassThrough as PassThrough2 } from "node:stream";
3059
4685
 
3060
4686
  // packages/design-system/src/prompts/index.ts
3061
4687
  import chalk15 from "chalk";
@@ -3087,9 +4713,9 @@ import chalk16 from "chalk";
3087
4713
  var DEFAULT_ACTIVITY_TIMEOUT_MS = 10 * 60 * 1e3;
3088
4714
 
3089
4715
  // packages/agent-spawn/src/acp/replay.ts
3090
- import path16 from "node:path";
4716
+ import path20 from "node:path";
3091
4717
  import { homedir as homedir2 } from "node:os";
3092
- import { open as open2, readdir } from "node:fs/promises";
4718
+ import { open as open2, readdir as readdir2 } from "node:fs/promises";
3093
4719
  import { createInterface } from "node:readline";
3094
4720
 
3095
4721
  // packages/poe-acp-client/src/acp-client.ts
@@ -3106,7 +4732,7 @@ import { homedir } from "node:os";
3106
4732
  import { join } from "node:path";
3107
4733
 
3108
4734
  // packages/agent-spawn/src/acp/middlewares/spawn-log.ts
3109
- import path17 from "node:path";
4735
+ import path21 from "node:path";
3110
4736
  import { homedir as homedir3 } from "node:os";
3111
4737
  import { mkdir, open as open3 } from "node:fs/promises";
3112
4738
 
@@ -3157,34 +4783,7 @@ function createBinaryExistsCheck(binaryName, id, description) {
3157
4783
  id,
3158
4784
  description,
3159
4785
  async run({ runCommand: runCommand2 }) {
3160
- const commonPaths = [
3161
- `/usr/local/bin/${binaryName}`,
3162
- `/usr/bin/${binaryName}`,
3163
- `$HOME/.local/bin/${binaryName}`,
3164
- `$HOME/.claude/local/bin/${binaryName}`
3165
- ];
3166
- const detectors = [
3167
- {
3168
- command: "which",
3169
- args: [binaryName],
3170
- validate: (result) => result.exitCode === 0
3171
- },
3172
- {
3173
- command: "where",
3174
- args: [binaryName],
3175
- validate: (result) => result.exitCode === 0 && result.stdout.trim().length > 0
3176
- },
3177
- // Check common installation paths using shell expansion for $HOME
3178
- {
3179
- command: "sh",
3180
- args: [
3181
- "-c",
3182
- commonPaths.map((p) => `test -f "${p}"`).join(" || ")
3183
- ],
3184
- validate: (result) => result.exitCode === 0
3185
- }
3186
- ];
3187
- for (const detector of detectors) {
4786
+ for (const detector of createBinaryExistsDetectors(binaryName)) {
3188
4787
  const result = await runCommand2(detector.command, detector.args);
3189
4788
  if (detector.validate(result)) {
3190
4789
  return;