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.
- package/dist/cli/commands/braintrust.d.ts +3 -0
- package/dist/cli/commands/braintrust.js +77 -0
- package/dist/cli/commands/braintrust.js.map +1 -0
- package/dist/cli/commands/configure.d.ts +1 -0
- package/dist/cli/commands/configure.js +197 -0
- package/dist/cli/commands/configure.js.map +1 -1
- package/dist/cli/commands/experiment.js +42 -5
- package/dist/cli/commands/experiment.js.map +1 -1
- package/dist/cli/commands/harness.d.ts +3 -0
- package/dist/cli/commands/harness.js +260 -0
- package/dist/cli/commands/harness.js.map +1 -0
- package/dist/cli/commands/pipeline.js +58 -24
- package/dist/cli/commands/pipeline.js.map +1 -1
- package/dist/cli/commands/ralph.js +8 -3
- package/dist/cli/commands/ralph.js.map +1 -1
- package/dist/cli/commands/runtime/build.d.ts +7 -0
- package/dist/cli/commands/runtime/build.js +128 -0
- package/dist/cli/commands/runtime/build.js.map +1 -0
- package/dist/cli/commands/runtime/index.d.ts +3 -0
- package/dist/cli/commands/runtime/index.js +14 -0
- package/dist/cli/commands/runtime/index.js.map +1 -0
- package/dist/cli/commands/runtime/init.d.ts +7 -0
- package/dist/cli/commands/runtime/init.js +39 -0
- package/dist/cli/commands/runtime/init.js.map +1 -0
- package/dist/cli/commands/runtime/jobs/attach.d.ts +3 -0
- package/dist/cli/commands/runtime/jobs/attach.js +35 -0
- package/dist/cli/commands/runtime/jobs/attach.js.map +1 -0
- package/dist/cli/commands/runtime/jobs/index.d.ts +3 -0
- package/dist/cli/commands/runtime/jobs/index.js +16 -0
- package/dist/cli/commands/runtime/jobs/index.js.map +1 -0
- package/dist/cli/commands/runtime/jobs/logs.d.ts +3 -0
- package/dist/cli/commands/runtime/jobs/logs.js +27 -0
- package/dist/cli/commands/runtime/jobs/logs.js.map +1 -0
- package/dist/cli/commands/runtime/jobs/ls.d.ts +3 -0
- package/dist/cli/commands/runtime/jobs/ls.js +60 -0
- package/dist/cli/commands/runtime/jobs/ls.js.map +1 -0
- package/dist/cli/commands/runtime/jobs/sandbox.d.ts +3 -0
- package/dist/cli/commands/runtime/jobs/sandbox.js +15 -0
- package/dist/cli/commands/runtime/jobs/sandbox.js.map +1 -0
- package/dist/cli/commands/runtime/jobs/shared.d.ts +22 -0
- package/dist/cli/commands/runtime/jobs/shared.js +124 -0
- package/dist/cli/commands/runtime/jobs/shared.js.map +1 -0
- package/dist/cli/commands/runtime/jobs/stop.d.ts +3 -0
- package/dist/cli/commands/runtime/jobs/stop.js +31 -0
- package/dist/cli/commands/runtime/jobs/stop.js.map +1 -0
- package/dist/cli/commands/runtime/jobs/sync.d.ts +3 -0
- package/dist/cli/commands/runtime/jobs/sync.js +25 -0
- package/dist/cli/commands/runtime/jobs/sync.js.map +1 -0
- package/dist/cli/commands/runtime/shared.d.ts +20 -0
- package/dist/cli/commands/runtime/shared.js +69 -0
- package/dist/cli/commands/runtime/shared.js.map +1 -0
- package/dist/cli/commands/runtime/templates/clear.d.ts +3 -0
- package/dist/cli/commands/runtime/templates/clear.js +53 -0
- package/dist/cli/commands/runtime/templates/clear.js.map +1 -0
- package/dist/cli/commands/runtime/templates/index.d.ts +3 -0
- package/dist/cli/commands/runtime/templates/index.js +10 -0
- package/dist/cli/commands/runtime/templates/index.js.map +1 -0
- package/dist/cli/commands/runtime/templates/ls.d.ts +3 -0
- package/dist/cli/commands/runtime/templates/ls.js +52 -0
- package/dist/cli/commands/runtime/templates/ls.js.map +1 -0
- package/dist/cli/commands/runtime-options.d.ts +1 -0
- package/dist/cli/commands/runtime-options.js +5 -2
- package/dist/cli/commands/runtime-options.js.map +1 -1
- package/dist/cli/commands/spawn.js +27 -4
- package/dist/cli/commands/spawn.js.map +1 -1
- package/dist/cli/program.js +17 -1
- package/dist/cli/program.js.map +1 -1
- package/dist/index.js +24192 -2429
- package/dist/index.js.map +4 -4
- package/dist/providers/claude-code.js +1692 -93
- package/dist/providers/claude-code.js.map +4 -4
- package/dist/providers/codex.js +1692 -93
- package/dist/providers/codex.js.map +4 -4
- package/dist/providers/goose.js +1687 -88
- package/dist/providers/goose.js.map +4 -4
- package/dist/providers/kimi.js +1692 -93
- package/dist/providers/kimi.js.map +4 -4
- package/dist/providers/opencode.js +1692 -93
- package/dist/providers/opencode.js.map +4 -4
- package/dist/providers/poe-agent.js +1580 -308
- package/dist/providers/poe-agent.js.map +4 -4
- package/dist/providers/spawn-options.d.ts +4 -1
- package/dist/sdk/experiment.js +1 -0
- package/dist/sdk/experiment.js.map +1 -1
- package/dist/sdk/ralph.js +108 -16
- package/dist/sdk/ralph.js.map +1 -1
- package/dist/sdk/spawn.js +11 -4
- package/dist/sdk/spawn.js.map +1 -1
- package/dist/sdk/types.d.ts +12 -1
- package/dist/utils/command-checks.js +2 -29
- package/dist/utils/command-checks.js.map +1 -1
- package/package.json +12 -7
- package/packages/design-system/dist/components/help-formatter-plain.d.ts +4 -0
- package/packages/design-system/dist/components/help-formatter-plain.js +132 -0
- package/packages/design-system/dist/components/help-formatter.d.ts +13 -0
- package/packages/design-system/dist/components/help-formatter.js +116 -7
- package/packages/design-system/dist/components/index.d.ts +2 -2
- package/packages/design-system/dist/components/index.js +1 -1
- package/packages/design-system/dist/components/text.d.ts +1 -0
- package/packages/design-system/dist/components/text.js +8 -0
- package/packages/design-system/dist/index.d.ts +3 -2
- package/packages/design-system/dist/index.js +2 -1
- package/packages/memory/dist/index.js +1201 -115
- package/packages/memory/dist/index.js.map +4 -4
- package/packages/superintendent/dist/commands/run.d.ts +10 -0
- package/packages/superintendent/dist/commands/run.js +96 -49
- package/packages/superintendent/dist/commands/superintendent-group.d.ts +6 -0
- package/packages/superintendent/dist/runtime/agent-runner.d.ts +1 -0
- package/packages/superintendent/dist/runtime/agent-runner.js +4 -2
- package/packages/superintendent/dist/runtime/loop.d.ts +1 -0
package/dist/providers/codex.js
CHANGED
|
@@ -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) {
|
|
@@ -881,16 +1508,16 @@ function getConfigFormat(pathOrFormat) {
|
|
|
881
1508
|
}
|
|
882
1509
|
return formatRegistry[formatName];
|
|
883
1510
|
}
|
|
884
|
-
function
|
|
885
|
-
const ext = getExtension(
|
|
1511
|
+
function detectFormat2(path22) {
|
|
1512
|
+
const ext = getExtension(path22);
|
|
886
1513
|
return extensionMap[ext];
|
|
887
1514
|
}
|
|
888
|
-
function getExtension(
|
|
889
|
-
const lastDot =
|
|
1515
|
+
function getExtension(path22) {
|
|
1516
|
+
const lastDot = path22.lastIndexOf(".");
|
|
890
1517
|
if (lastDot === -1) {
|
|
891
1518
|
return "";
|
|
892
1519
|
}
|
|
893
|
-
return
|
|
1520
|
+
return path22.slice(lastDot).toLowerCase();
|
|
894
1521
|
}
|
|
895
1522
|
|
|
896
1523
|
// packages/config-mutations/src/execution/path-utils.ts
|
|
@@ -941,7 +1568,7 @@ function resolvePath(rawPath, homeDir, pathMapper) {
|
|
|
941
1568
|
function isNotFound(error2) {
|
|
942
1569
|
return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
|
|
943
1570
|
}
|
|
944
|
-
async function
|
|
1571
|
+
async function readFileIfExists2(fs, target) {
|
|
945
1572
|
try {
|
|
946
1573
|
return await fs.readFile(target, "utf8");
|
|
947
1574
|
} catch (error2) {
|
|
@@ -1225,7 +1852,7 @@ async function applyBackup(mutation, context, options) {
|
|
|
1225
1852
|
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
1226
1853
|
targetPath
|
|
1227
1854
|
};
|
|
1228
|
-
const content = await
|
|
1855
|
+
const content = await readFileIfExists2(context.fs, targetPath);
|
|
1229
1856
|
if (content === null) {
|
|
1230
1857
|
return {
|
|
1231
1858
|
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
@@ -1249,14 +1876,14 @@ async function applyConfigMerge(mutation, context, options) {
|
|
|
1249
1876
|
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
1250
1877
|
targetPath
|
|
1251
1878
|
};
|
|
1252
|
-
const formatName = mutation.format ??
|
|
1879
|
+
const formatName = mutation.format ?? detectFormat2(rawPath);
|
|
1253
1880
|
if (!formatName) {
|
|
1254
1881
|
throw new Error(
|
|
1255
1882
|
`Cannot detect config format for "${rawPath}". Provide explicit format option.`
|
|
1256
1883
|
);
|
|
1257
1884
|
}
|
|
1258
1885
|
const format = getConfigFormat(formatName);
|
|
1259
|
-
const rawContent = await
|
|
1886
|
+
const rawContent = await readFileIfExists2(context.fs, targetPath);
|
|
1260
1887
|
let current;
|
|
1261
1888
|
try {
|
|
1262
1889
|
current = rawContent === null ? {} : format.parse(rawContent);
|
|
@@ -1295,14 +1922,14 @@ async function applyConfigPrune(mutation, context, options) {
|
|
|
1295
1922
|
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
1296
1923
|
targetPath
|
|
1297
1924
|
};
|
|
1298
|
-
const rawContent = await
|
|
1925
|
+
const rawContent = await readFileIfExists2(context.fs, targetPath);
|
|
1299
1926
|
if (rawContent === null) {
|
|
1300
1927
|
return {
|
|
1301
1928
|
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1302
1929
|
details
|
|
1303
1930
|
};
|
|
1304
1931
|
}
|
|
1305
|
-
const formatName = mutation.format ??
|
|
1932
|
+
const formatName = mutation.format ?? detectFormat2(rawPath);
|
|
1306
1933
|
if (!formatName) {
|
|
1307
1934
|
throw new Error(
|
|
1308
1935
|
`Cannot detect config format for "${rawPath}". Provide explicit format option.`
|
|
@@ -1358,14 +1985,14 @@ async function applyConfigTransform(mutation, context, options) {
|
|
|
1358
1985
|
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
1359
1986
|
targetPath
|
|
1360
1987
|
};
|
|
1361
|
-
const formatName = mutation.format ??
|
|
1988
|
+
const formatName = mutation.format ?? detectFormat2(rawPath);
|
|
1362
1989
|
if (!formatName) {
|
|
1363
1990
|
throw new Error(
|
|
1364
1991
|
`Cannot detect config format for "${rawPath}". Provide explicit format option.`
|
|
1365
1992
|
);
|
|
1366
1993
|
}
|
|
1367
1994
|
const format = getConfigFormat(formatName);
|
|
1368
|
-
const rawContent = await
|
|
1995
|
+
const rawContent = await readFileIfExists2(context.fs, targetPath);
|
|
1369
1996
|
let current;
|
|
1370
1997
|
try {
|
|
1371
1998
|
current = rawContent === null ? {} : format.parse(rawContent);
|
|
@@ -1465,7 +2092,7 @@ async function applyTemplateMerge(mutation, context, options, formatName) {
|
|
|
1465
2092
|
{ cause: error2 }
|
|
1466
2093
|
);
|
|
1467
2094
|
}
|
|
1468
|
-
const rawContent = await
|
|
2095
|
+
const rawContent = await readFileIfExists2(context.fs, targetPath);
|
|
1469
2096
|
let current;
|
|
1470
2097
|
try {
|
|
1471
2098
|
current = rawContent === null ? {} : format.parse(rawContent);
|
|
@@ -1546,28 +2173,220 @@ function isConfigObject5(value) {
|
|
|
1546
2173
|
}
|
|
1547
2174
|
|
|
1548
2175
|
// packages/poe-code-config/src/store.ts
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
2176
|
+
async function readMergedDocument(fs, globalPath, projectPath) {
|
|
2177
|
+
const globalDocument = await readStoredDocument(fs, globalPath);
|
|
2178
|
+
if (!projectPath || projectPath === globalPath) {
|
|
2179
|
+
return globalDocument.data;
|
|
2180
|
+
}
|
|
2181
|
+
const projectDocument = await readStoredDocument(fs, projectPath);
|
|
2182
|
+
const resolved = await resolve(
|
|
2183
|
+
[
|
|
2184
|
+
{
|
|
2185
|
+
source: "project",
|
|
2186
|
+
filePath: projectPath,
|
|
2187
|
+
content: projectDocument.content
|
|
2188
|
+
},
|
|
2189
|
+
{
|
|
2190
|
+
source: "base",
|
|
2191
|
+
path: path8.dirname(globalPath)
|
|
2192
|
+
}
|
|
2193
|
+
],
|
|
2194
|
+
{
|
|
2195
|
+
fs: createResolvedConfigFs(fs, globalPath, globalDocument.content),
|
|
2196
|
+
autoExtend: true
|
|
2197
|
+
}
|
|
2198
|
+
);
|
|
2199
|
+
return normalizeDocument(resolved.data);
|
|
2200
|
+
}
|
|
2201
|
+
async function readStoredDocument(fs, filePath) {
|
|
2202
|
+
try {
|
|
2203
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
2204
|
+
return await parseStoredDocument(fs, filePath, raw);
|
|
2205
|
+
} catch (error2) {
|
|
2206
|
+
if (isNotFound(error2)) {
|
|
2207
|
+
return {
|
|
2208
|
+
content: EMPTY_DOCUMENT,
|
|
2209
|
+
data: {}
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
throw error2;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
async function parseStoredDocument(fs, filePath, raw) {
|
|
2216
|
+
try {
|
|
2217
|
+
return {
|
|
2218
|
+
content: raw,
|
|
2219
|
+
data: normalizeDocument(JSON.parse(raw))
|
|
2220
|
+
};
|
|
2221
|
+
} catch (error2) {
|
|
2222
|
+
if (error2 instanceof SyntaxError) {
|
|
2223
|
+
await recoverInvalidDocument(fs, filePath, raw);
|
|
2224
|
+
return {
|
|
2225
|
+
content: EMPTY_DOCUMENT,
|
|
2226
|
+
data: {}
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
throw error2;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
function normalizeDocument(value) {
|
|
2233
|
+
if (!isRecord2(value)) {
|
|
2234
|
+
return {};
|
|
2235
|
+
}
|
|
2236
|
+
const document = {};
|
|
2237
|
+
for (const [scope, scopeValues] of Object.entries(value)) {
|
|
2238
|
+
const normalizedValues = normalizeScopeValues(scopeValues);
|
|
2239
|
+
if (Object.keys(normalizedValues).length > 0) {
|
|
2240
|
+
document[scope] = normalizedValues;
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
return document;
|
|
2244
|
+
}
|
|
2245
|
+
function normalizeScopeValues(value) {
|
|
2246
|
+
if (!isRecord2(value)) {
|
|
2247
|
+
return {};
|
|
2248
|
+
}
|
|
2249
|
+
const normalized = {};
|
|
2250
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
2251
|
+
if (entry !== void 0) {
|
|
2252
|
+
normalized[key] = entry;
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
return normalized;
|
|
2256
|
+
}
|
|
2257
|
+
function createResolvedConfigFs(fs, globalPath, globalContent) {
|
|
2258
|
+
return {
|
|
2259
|
+
readFile(filePath, _encoding) {
|
|
2260
|
+
if (filePath === globalPath) {
|
|
2261
|
+
return Promise.resolve(globalContent);
|
|
2262
|
+
}
|
|
2263
|
+
return fs.readFile(filePath, "utf8");
|
|
2264
|
+
}
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
async function recoverInvalidDocument(fs, filePath, content) {
|
|
2268
|
+
await fs.mkdir(path8.dirname(filePath), { recursive: true });
|
|
2269
|
+
const backupPath = createInvalidBackupPath(filePath);
|
|
2270
|
+
await fs.writeFile(backupPath, content, { encoding: "utf8" });
|
|
2271
|
+
await fs.writeFile(filePath, EMPTY_DOCUMENT, { encoding: "utf8" });
|
|
2272
|
+
}
|
|
2273
|
+
function createInvalidBackupPath(filePath) {
|
|
2274
|
+
const directory = path8.dirname(filePath);
|
|
2275
|
+
const baseName = path8.basename(filePath);
|
|
2276
|
+
return path8.join(directory, `${baseName}.invalid-${createTimestamp()}.json`);
|
|
2277
|
+
}
|
|
2278
|
+
function isRecord2(value) {
|
|
2279
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2280
|
+
}
|
|
2281
|
+
function resolveConfigPath(homeDir) {
|
|
2282
|
+
return path8.join(homeDir, ".poe-code", "config.json");
|
|
2283
|
+
}
|
|
2284
|
+
function resolveProjectConfigPath(cwd) {
|
|
2285
|
+
return path8.join(cwd, ".poe-code", "config.json");
|
|
2286
|
+
}
|
|
2287
|
+
var EMPTY_DOCUMENT = `${JSON.stringify({}, null, 2)}
|
|
2288
|
+
`;
|
|
2289
|
+
|
|
2290
|
+
// packages/poe-code-config/src/resolve.ts
|
|
2291
|
+
function resolveScope(schema, fileValues, env = {}) {
|
|
2292
|
+
const resolved = {};
|
|
2293
|
+
for (const key of Object.keys(schema)) {
|
|
2294
|
+
const field = schema[key];
|
|
2295
|
+
const envValue = resolveEnvValue(field, env, key);
|
|
2296
|
+
const fileValue = resolveFileValue(field, fileValues?.[key], key);
|
|
2297
|
+
resolved[key] = envValue ?? fileValue ?? field.default;
|
|
2298
|
+
}
|
|
2299
|
+
return resolved;
|
|
2300
|
+
}
|
|
2301
|
+
function resolveEnvValue(field, env, key) {
|
|
2302
|
+
if (!field.env) {
|
|
2303
|
+
return void 0;
|
|
2304
|
+
}
|
|
2305
|
+
const raw = env[field.env];
|
|
2306
|
+
if (raw === void 0) {
|
|
2307
|
+
return void 0;
|
|
2308
|
+
}
|
|
2309
|
+
return coerceValue(field, raw, key);
|
|
2310
|
+
}
|
|
2311
|
+
function resolveFileValue(field, value, key) {
|
|
2312
|
+
return coerceValue(field, value, key);
|
|
2313
|
+
}
|
|
2314
|
+
function coerceValue(field, value, key) {
|
|
2315
|
+
switch (field.type) {
|
|
2316
|
+
case "string":
|
|
2317
|
+
return typeof value === "string" ? value : void 0;
|
|
2318
|
+
case "number":
|
|
2319
|
+
return coerceNumber(value);
|
|
2320
|
+
case "boolean":
|
|
2321
|
+
return coerceBoolean(value);
|
|
2322
|
+
case "json":
|
|
2323
|
+
return coerceJson(field, value, key);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
function coerceNumber(value) {
|
|
2327
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2328
|
+
return value;
|
|
2329
|
+
}
|
|
2330
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
2331
|
+
return void 0;
|
|
2332
|
+
}
|
|
2333
|
+
const parsed = Number(value);
|
|
2334
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
2335
|
+
}
|
|
2336
|
+
function coerceBoolean(value) {
|
|
2337
|
+
if (typeof value === "boolean") {
|
|
2338
|
+
return value;
|
|
2339
|
+
}
|
|
2340
|
+
if (value === "true" || value === "1") {
|
|
2341
|
+
return true;
|
|
2342
|
+
}
|
|
2343
|
+
if (value === "false" || value === "0") {
|
|
2344
|
+
return false;
|
|
2345
|
+
}
|
|
2346
|
+
return void 0;
|
|
2347
|
+
}
|
|
2348
|
+
function coerceJson(field, value, key) {
|
|
2349
|
+
if (value === void 0) {
|
|
2350
|
+
return void 0;
|
|
2351
|
+
}
|
|
2352
|
+
const parsedValue = parseJsonValue(value, key);
|
|
2353
|
+
try {
|
|
2354
|
+
return field.parse(parsedValue);
|
|
2355
|
+
} catch (error2) {
|
|
2356
|
+
const message2 = error2 instanceof Error ? error2.message : "Invalid JSON value.";
|
|
2357
|
+
throw new Error(`Invalid config value for "${key}": ${message2}`);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
function parseJsonValue(value, key) {
|
|
2361
|
+
if (typeof value !== "string") {
|
|
2362
|
+
return value;
|
|
2363
|
+
}
|
|
2364
|
+
try {
|
|
2365
|
+
return JSON.parse(value);
|
|
2366
|
+
} catch {
|
|
2367
|
+
throw new Error(`Invalid config value for "${key}": expected valid JSON.`);
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
// packages/poe-code-config/src/inspect.ts
|
|
2372
|
+
import path9 from "node:path";
|
|
2373
|
+
var EMPTY_DOCUMENT2 = `${JSON.stringify({}, null, 2)}
|
|
2374
|
+
`;
|
|
2375
|
+
|
|
2376
|
+
// packages/poe-code-config/src/state/index.ts
|
|
2377
|
+
import os2 from "node:os";
|
|
2378
|
+
|
|
2379
|
+
// packages/poe-code-config/src/state/jobs.ts
|
|
2380
|
+
import path10 from "node:path";
|
|
2381
|
+
|
|
2382
|
+
// packages/poe-code-config/src/state/fs.ts
|
|
2383
|
+
import * as nodeFs2 from "node:fs/promises";
|
|
2384
|
+
|
|
2385
|
+
// packages/poe-code-config/src/state/templates.ts
|
|
2386
|
+
import path11 from "node:path";
|
|
2387
|
+
|
|
2388
|
+
// packages/agent-harness-tools/src/execution-env.ts
|
|
2389
|
+
var executionEnvFactories = /* @__PURE__ */ new Map();
|
|
1571
2390
|
function registerExecutionEnvFactory(factory) {
|
|
1572
2391
|
executionEnvFactories.set(factory.type, factory);
|
|
1573
2392
|
}
|
|
@@ -1805,14 +2624,15 @@ var dockerExecutionEnvFactory = {
|
|
|
1805
2624
|
context
|
|
1806
2625
|
});
|
|
1807
2626
|
},
|
|
1808
|
-
async attach(envId) {
|
|
2627
|
+
async attach(envId, context) {
|
|
1809
2628
|
const engine = detectEngine();
|
|
1810
2629
|
return createDockerEnv({
|
|
1811
2630
|
id: envId,
|
|
1812
|
-
spec: createAttachedSpec(),
|
|
2631
|
+
spec: createAttachedSpec(context?.cwd),
|
|
1813
2632
|
runner: createHostRunner(),
|
|
1814
2633
|
engine,
|
|
1815
|
-
context: detectContext()
|
|
2634
|
+
context: detectContext(),
|
|
2635
|
+
attachedJobId: context?.jobId
|
|
1816
2636
|
});
|
|
1817
2637
|
}
|
|
1818
2638
|
};
|
|
@@ -1820,7 +2640,7 @@ function createDockerEnv(input) {
|
|
|
1820
2640
|
const containerRef = input.id;
|
|
1821
2641
|
return {
|
|
1822
2642
|
id: containerRef,
|
|
1823
|
-
job: null,
|
|
2643
|
+
job: input.attachedJobId === void 0 ? null : createContainerJob(containerRef, input.runner, input.engine, input.context, input.attachedJobId),
|
|
1824
2644
|
async uploadWorkspace() {
|
|
1825
2645
|
const tempDir = mkdtempSync(path14.join(tmpdir(), "poe-docker-upload-"));
|
|
1826
2646
|
const archivePath = path14.join(tempDir, "workspace.tar");
|
|
@@ -1955,35 +2775,57 @@ async function resolveImage(input) {
|
|
|
1955
2775
|
if (input.runtime.image !== void 0) {
|
|
1956
2776
|
return input.runtime.image;
|
|
1957
2777
|
}
|
|
2778
|
+
const result = await buildDockerRuntimeTemplate({
|
|
2779
|
+
cwd: input.spec.cwd,
|
|
2780
|
+
runtime: input.runtime,
|
|
2781
|
+
state: input.spec.state,
|
|
2782
|
+
runner: input.runner
|
|
2783
|
+
});
|
|
2784
|
+
return result.image;
|
|
2785
|
+
}
|
|
2786
|
+
async function buildDockerRuntimeTemplate(input) {
|
|
2787
|
+
const runner = input.runner ?? createHostRunner();
|
|
2788
|
+
const engine = input.runtime.engine ?? detectEngine();
|
|
2789
|
+
const context = detectContext();
|
|
1958
2790
|
const dockerfilePath = path14.resolve(
|
|
1959
|
-
input.
|
|
2791
|
+
input.cwd,
|
|
1960
2792
|
input.runtime.dockerfile ?? path14.join(".poe-code", "Dockerfile")
|
|
1961
2793
|
);
|
|
1962
|
-
const buildContext = path14.resolve(input.
|
|
2794
|
+
const buildContext = path14.resolve(input.cwd, input.runtime.build_context ?? ".");
|
|
1963
2795
|
const dockerfileBytes = await readFile2(dockerfilePath);
|
|
1964
2796
|
const hash = hashDockerTemplate(dockerfileBytes, input.runtime.build_args ?? {});
|
|
1965
|
-
const cached2 = await input.
|
|
2797
|
+
const cached2 = input.force ? null : await input.state?.templates.get("docker", hash);
|
|
1966
2798
|
if (cached2?.image !== void 0) {
|
|
1967
|
-
return
|
|
2799
|
+
return {
|
|
2800
|
+
backend: "docker",
|
|
2801
|
+
hash,
|
|
2802
|
+
image: cached2.image,
|
|
2803
|
+
cached: true
|
|
2804
|
+
};
|
|
1968
2805
|
}
|
|
1969
2806
|
const image = `poe-code/local:${hash}`;
|
|
1970
2807
|
await buildImage({
|
|
1971
|
-
runner
|
|
1972
|
-
engine
|
|
1973
|
-
context
|
|
2808
|
+
runner,
|
|
2809
|
+
engine,
|
|
2810
|
+
context,
|
|
1974
2811
|
image,
|
|
1975
2812
|
dockerfilePath,
|
|
1976
2813
|
buildContext,
|
|
1977
2814
|
buildArgs: input.runtime.build_args ?? {}
|
|
1978
2815
|
});
|
|
1979
|
-
await input.
|
|
2816
|
+
await input.state?.templates.put("docker", {
|
|
1980
2817
|
hash,
|
|
1981
2818
|
image,
|
|
1982
2819
|
runtime_type: "docker",
|
|
1983
2820
|
dockerfile_path: dockerfilePath,
|
|
1984
2821
|
built_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1985
2822
|
});
|
|
1986
|
-
return
|
|
2823
|
+
return {
|
|
2824
|
+
backend: "docker",
|
|
2825
|
+
hash,
|
|
2826
|
+
image,
|
|
2827
|
+
cached: false
|
|
2828
|
+
};
|
|
1987
2829
|
}
|
|
1988
2830
|
function hashDockerTemplate(dockerfileBytes, buildArgs) {
|
|
1989
2831
|
const hash = createHash2("sha256");
|
|
@@ -2068,9 +2910,9 @@ function buildEnvArgs(env) {
|
|
|
2068
2910
|
function createContainerName() {
|
|
2069
2911
|
return `poe-env-${randomBytes3(6).toString("hex")}`;
|
|
2070
2912
|
}
|
|
2071
|
-
|
|
2913
|
+
function createContainerJob(containerId, runner, engine, context, jobId = containerId) {
|
|
2072
2914
|
return {
|
|
2073
|
-
id:
|
|
2915
|
+
id: jobId,
|
|
2074
2916
|
envId: containerId,
|
|
2075
2917
|
tool: "docker",
|
|
2076
2918
|
argv: ["attach", containerId],
|
|
@@ -2094,7 +2936,25 @@ async function createContainerJob(containerId, runner, engine, context) {
|
|
|
2094
2936
|
}
|
|
2095
2937
|
return stdout.trim() === "running" ? "running" : "exited";
|
|
2096
2938
|
},
|
|
2097
|
-
async *stream() {
|
|
2939
|
+
async *stream(opts) {
|
|
2940
|
+
const handle = runner.exec({
|
|
2941
|
+
command: engine,
|
|
2942
|
+
args: [
|
|
2943
|
+
...buildContextArgs(engine, context),
|
|
2944
|
+
"exec",
|
|
2945
|
+
containerId,
|
|
2946
|
+
"sh",
|
|
2947
|
+
"-c",
|
|
2948
|
+
`test -f ${shellQuote(`/tmp/poe-jobs/${jobId}.log`)} && tail -c +${(opts?.sinceByte ?? 0) + 1} ${shellQuote(`/tmp/poe-jobs/${jobId}.log`)} || true`
|
|
2949
|
+
],
|
|
2950
|
+
stdout: "pipe",
|
|
2951
|
+
stderr: "pipe"
|
|
2952
|
+
});
|
|
2953
|
+
const stdout = await readStream(handle.stdout);
|
|
2954
|
+
await handle.result;
|
|
2955
|
+
if (stdout.length > 0) {
|
|
2956
|
+
yield { byteOffset: opts?.sinceByte ?? 0, data: stdout };
|
|
2957
|
+
}
|
|
2098
2958
|
},
|
|
2099
2959
|
async wait() {
|
|
2100
2960
|
const handle = runner.exec({
|
|
@@ -2118,9 +2978,9 @@ async function createContainerJob(containerId, runner, engine, context) {
|
|
|
2118
2978
|
}
|
|
2119
2979
|
};
|
|
2120
2980
|
}
|
|
2121
|
-
function createAttachedSpec() {
|
|
2981
|
+
function createAttachedSpec(cwd = "/workspace") {
|
|
2122
2982
|
return {
|
|
2123
|
-
cwd
|
|
2983
|
+
cwd,
|
|
2124
2984
|
runtime: {
|
|
2125
2985
|
type: "docker",
|
|
2126
2986
|
image: "attached",
|
|
@@ -2192,12 +3052,778 @@ var hostExecutionEnvFactory = {
|
|
|
2192
3052
|
// packages/process-runner/src/testing/mock-runner.ts
|
|
2193
3053
|
import { Readable, Writable } from "node:stream";
|
|
2194
3054
|
|
|
3055
|
+
// packages/runner-e2b/src/factory.ts
|
|
3056
|
+
import path18 from "node:path";
|
|
3057
|
+
|
|
3058
|
+
// packages/runner-e2b/src/sdk.ts
|
|
3059
|
+
import { Template, Sandbox } from "e2b";
|
|
3060
|
+
async function createSandbox(opts) {
|
|
3061
|
+
return Sandbox.create(opts.templateId, {
|
|
3062
|
+
apiKey: opts.apiKey,
|
|
3063
|
+
envs: opts.env,
|
|
3064
|
+
...opts.timeoutMinutes === void 0 ? {} : { timeoutMs: opts.timeoutMinutes * 6e4 }
|
|
3065
|
+
});
|
|
3066
|
+
}
|
|
3067
|
+
async function connectSandbox(id, apiKey) {
|
|
3068
|
+
return Sandbox.connect(id, apiKey === void 0 ? void 0 : { apiKey });
|
|
3069
|
+
}
|
|
3070
|
+
async function buildTemplate(opts) {
|
|
3071
|
+
const template = Template({ fileContextPath: opts.buildContext }).fromDockerfile(
|
|
3072
|
+
opts.dockerfilePath
|
|
3073
|
+
);
|
|
3074
|
+
if (opts.fromTemplate !== void 0 && opts.fromTemplate.length > 0) {
|
|
3075
|
+
template.fromTemplate(opts.fromTemplate);
|
|
3076
|
+
}
|
|
3077
|
+
const result = await Template.build(template, opts.name, {
|
|
3078
|
+
apiKey: opts.apiKey,
|
|
3079
|
+
...opts.cpu === void 0 ? {} : { cpuCount: opts.cpu },
|
|
3080
|
+
...opts.memoryMb === void 0 ? {} : { memoryMB: opts.memoryMb },
|
|
3081
|
+
...opts.onLog ? { onBuildLogs: opts.onLog } : {}
|
|
3082
|
+
});
|
|
3083
|
+
return { templateId: result.templateId };
|
|
3084
|
+
}
|
|
3085
|
+
function toArrayBuffer(buffer) {
|
|
3086
|
+
const output = new ArrayBuffer(buffer.byteLength);
|
|
3087
|
+
new Uint8Array(output).set(buffer);
|
|
3088
|
+
return output;
|
|
3089
|
+
}
|
|
3090
|
+
async function readableToString(stream) {
|
|
3091
|
+
if (stream === null) {
|
|
3092
|
+
return "";
|
|
3093
|
+
}
|
|
3094
|
+
stream.setEncoding("utf8");
|
|
3095
|
+
const chunks = [];
|
|
3096
|
+
for await (const chunk of stream) {
|
|
3097
|
+
chunks.push(String(chunk));
|
|
3098
|
+
}
|
|
3099
|
+
return chunks.join("");
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
// packages/runner-e2b/src/template-build.ts
|
|
3103
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
3104
|
+
import { readdir, readFile as readFile3 } from "node:fs/promises";
|
|
3105
|
+
import path15 from "node:path";
|
|
3106
|
+
var BUILD_LOG_TAIL_SIZE = 30;
|
|
3107
|
+
async function buildE2bRuntimeTemplate(input) {
|
|
3108
|
+
const dockerfileBytes = await readFile3(input.dockerfilePath);
|
|
3109
|
+
const buildContextFiles = await readBuildContextFiles(input.buildContext);
|
|
3110
|
+
const hash = hashTemplate(dockerfileBytes, buildContextFiles, input.runtime.build_args);
|
|
3111
|
+
const cached2 = input.force === true ? null : await input.state?.templates.get("e2b", hash);
|
|
3112
|
+
if (cached2?.template_id !== void 0) {
|
|
3113
|
+
return { backend: "e2b", hash, templateId: cached2.template_id, cached: true };
|
|
3114
|
+
}
|
|
3115
|
+
const tail = [];
|
|
3116
|
+
const onLog = (entry) => {
|
|
3117
|
+
tail.push(entry.message);
|
|
3118
|
+
if (tail.length > BUILD_LOG_TAIL_SIZE) {
|
|
3119
|
+
tail.shift();
|
|
3120
|
+
}
|
|
3121
|
+
input.onLog?.(entry);
|
|
3122
|
+
};
|
|
3123
|
+
let built;
|
|
3124
|
+
try {
|
|
3125
|
+
built = await buildTemplate({
|
|
3126
|
+
apiKey: input.apiKey,
|
|
3127
|
+
name: `poe-code-${hash.slice(0, 32)}`,
|
|
3128
|
+
dockerfilePath: input.dockerfilePath,
|
|
3129
|
+
buildContext: input.buildContext,
|
|
3130
|
+
cpu: input.runtime.cpu,
|
|
3131
|
+
memoryMb: input.runtime.memory_mb,
|
|
3132
|
+
fromTemplate: input.runtime.from_template,
|
|
3133
|
+
onLog
|
|
3134
|
+
});
|
|
3135
|
+
} catch (error2) {
|
|
3136
|
+
throw decorateBuildError(error2, tail);
|
|
3137
|
+
}
|
|
3138
|
+
await input.state?.templates.put("e2b", {
|
|
3139
|
+
hash,
|
|
3140
|
+
template_id: built.templateId,
|
|
3141
|
+
runtime_type: "e2b",
|
|
3142
|
+
dockerfile_path: input.dockerfilePath,
|
|
3143
|
+
built_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3144
|
+
});
|
|
3145
|
+
return { backend: "e2b", hash, templateId: built.templateId, cached: false };
|
|
3146
|
+
}
|
|
3147
|
+
function decorateBuildError(error2, tail) {
|
|
3148
|
+
const original = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
3149
|
+
if (tail.length === 0) {
|
|
3150
|
+
return original;
|
|
3151
|
+
}
|
|
3152
|
+
const decorated = new Error(`${original.message}
|
|
3153
|
+
|
|
3154
|
+
Last build output:
|
|
3155
|
+
${tail.join("\n")}`);
|
|
3156
|
+
decorated.stack = original.stack;
|
|
3157
|
+
decorated.cause = original;
|
|
3158
|
+
return decorated;
|
|
3159
|
+
}
|
|
3160
|
+
function hashTemplate(dockerfileBytes, buildContextFiles, buildArgs) {
|
|
3161
|
+
const hash = createHash3("sha256");
|
|
3162
|
+
hash.update(dockerfileBytes);
|
|
3163
|
+
hash.update("\0");
|
|
3164
|
+
for (const file of buildContextFiles) {
|
|
3165
|
+
hash.update(file.relativePath);
|
|
3166
|
+
hash.update("\0");
|
|
3167
|
+
hash.update(file.bytes);
|
|
3168
|
+
hash.update("\0");
|
|
3169
|
+
}
|
|
3170
|
+
for (const [key, value] of Object.entries(buildArgs).sort(
|
|
3171
|
+
([left], [right]) => left.localeCompare(right)
|
|
3172
|
+
)) {
|
|
3173
|
+
hash.update(key);
|
|
3174
|
+
hash.update("=");
|
|
3175
|
+
hash.update(value);
|
|
3176
|
+
hash.update("\0");
|
|
3177
|
+
}
|
|
3178
|
+
return hash.digest("hex");
|
|
3179
|
+
}
|
|
3180
|
+
async function readBuildContextFiles(buildContext) {
|
|
3181
|
+
const files = [];
|
|
3182
|
+
await collectBuildContextFiles(buildContext, "", files);
|
|
3183
|
+
return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
3184
|
+
}
|
|
3185
|
+
async function collectBuildContextFiles(buildContext, relativeDir, files) {
|
|
3186
|
+
const absoluteDir = path15.join(buildContext, relativeDir);
|
|
3187
|
+
const entries = await readdir(absoluteDir, { withFileTypes: true });
|
|
3188
|
+
for (const entry of entries) {
|
|
3189
|
+
const relativePath = path15.join(relativeDir, entry.name);
|
|
3190
|
+
if (entry.isDirectory()) {
|
|
3191
|
+
await collectBuildContextFiles(buildContext, relativePath, files);
|
|
3192
|
+
continue;
|
|
3193
|
+
}
|
|
3194
|
+
if (!entry.isFile()) {
|
|
3195
|
+
continue;
|
|
3196
|
+
}
|
|
3197
|
+
files.push({
|
|
3198
|
+
relativePath: relativePath.split(path15.sep).join("/"),
|
|
3199
|
+
bytes: await readFile3(path15.join(buildContext, relativePath))
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
// packages/runner-e2b/src/opened-env.ts
|
|
3205
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "node:fs";
|
|
3206
|
+
import { readFile as readFile4, writeFile } from "node:fs/promises";
|
|
3207
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
3208
|
+
import path17 from "node:path";
|
|
3209
|
+
import { PassThrough, Writable as Writable2 } from "node:stream";
|
|
3210
|
+
|
|
3211
|
+
// packages/runner-e2b/src/job-handle.ts
|
|
3212
|
+
import path16 from "node:path";
|
|
3213
|
+
var JOB_DIR2 = "/tmp/poe-jobs";
|
|
3214
|
+
function createE2bJobHandle(input) {
|
|
3215
|
+
const fs = createE2bLogStreamFs(input.sandbox);
|
|
3216
|
+
return {
|
|
3217
|
+
id: input.jobId,
|
|
3218
|
+
envId: input.envId,
|
|
3219
|
+
tool: input.tool,
|
|
3220
|
+
argv: input.argv,
|
|
3221
|
+
async status() {
|
|
3222
|
+
const exit = await readExitCode(input.sandbox, input.jobId);
|
|
3223
|
+
if (exit !== null) {
|
|
3224
|
+
return "exited";
|
|
3225
|
+
}
|
|
3226
|
+
const processes = await input.sandbox.commands.list();
|
|
3227
|
+
const isRunning = input.pid === void 0 ? processes.some((process2) => processMentionsJob(process2, input.jobId)) : processes.some((process2) => process2.pid === input.pid);
|
|
3228
|
+
return isRunning ? "running" : "lost";
|
|
3229
|
+
},
|
|
3230
|
+
stream(opts = {}) {
|
|
3231
|
+
return streamLogFile({ fs }, input.jobId, opts);
|
|
3232
|
+
},
|
|
3233
|
+
async wait() {
|
|
3234
|
+
const result = await waitForExit({ fs }, input.jobId);
|
|
3235
|
+
const preserveMs = input.preserveAfterExitHours * 60 * 60 * 1e3;
|
|
3236
|
+
if (preserveMs > 0) {
|
|
3237
|
+
await input.sandbox.setTimeout(preserveMs);
|
|
3238
|
+
}
|
|
3239
|
+
return result;
|
|
3240
|
+
},
|
|
3241
|
+
async kill() {
|
|
3242
|
+
const pids = input.pid === void 0 ? (await input.sandbox.commands.list()).filter((process2) => processMentionsJob(process2, input.jobId)).map((process2) => process2.pid) : [input.pid];
|
|
3243
|
+
await Promise.all(pids.map((pid) => input.sandbox.commands.kill(pid)));
|
|
3244
|
+
}
|
|
3245
|
+
};
|
|
3246
|
+
}
|
|
3247
|
+
function createE2bLogStreamFs(sandbox) {
|
|
3248
|
+
return {
|
|
3249
|
+
promises: {
|
|
3250
|
+
async readFile(filePath) {
|
|
3251
|
+
return Buffer.from(await sandbox.files.read(filePath, { format: "bytes" }));
|
|
3252
|
+
},
|
|
3253
|
+
async stat(filePath) {
|
|
3254
|
+
const result = await sandbox.commands.run(
|
|
3255
|
+
`stat -c %Y ${shellQuote2(filePath)} 2>/dev/null || stat -f %m ${shellQuote2(filePath)}`
|
|
3256
|
+
);
|
|
3257
|
+
if (!("stdout" in result)) {
|
|
3258
|
+
throw new Error(`Unable to stat ${filePath}`);
|
|
3259
|
+
}
|
|
3260
|
+
const seconds = Number(result.stdout?.trim());
|
|
3261
|
+
if (!Number.isFinite(seconds)) {
|
|
3262
|
+
throw new Error(`Unable to stat ${filePath}`);
|
|
3263
|
+
}
|
|
3264
|
+
return { mtimeMs: seconds * 1e3 };
|
|
3265
|
+
}
|
|
3266
|
+
},
|
|
3267
|
+
watch(filePath, listener) {
|
|
3268
|
+
let closed = false;
|
|
3269
|
+
let stop = null;
|
|
3270
|
+
void sandbox.files.watchDir(path16.dirname(filePath), listener, { recursive: false }).then((handle) => {
|
|
3271
|
+
if (closed) {
|
|
3272
|
+
void handle.stop();
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
stop = () => {
|
|
3276
|
+
void handle.stop();
|
|
3277
|
+
};
|
|
3278
|
+
});
|
|
3279
|
+
return {
|
|
3280
|
+
close() {
|
|
3281
|
+
closed = true;
|
|
3282
|
+
stop?.();
|
|
3283
|
+
}
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
3286
|
+
};
|
|
3287
|
+
}
|
|
3288
|
+
function processMentionsJob(process2, jobId) {
|
|
3289
|
+
const needle = `/tmp/poe-jobs/${jobId}`;
|
|
3290
|
+
return process2.cmd.includes(needle) || process2.args.some((arg) => arg.includes(needle));
|
|
3291
|
+
}
|
|
3292
|
+
function shellQuote2(value) {
|
|
3293
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
3294
|
+
}
|
|
3295
|
+
async function readExitCode(sandbox, jobId) {
|
|
3296
|
+
try {
|
|
3297
|
+
const contents = await sandbox.files.read(`${JOB_DIR2}/${jobId}.exit`);
|
|
3298
|
+
const exitCode = Number(contents.trim());
|
|
3299
|
+
return Number.isInteger(exitCode) ? exitCode : null;
|
|
3300
|
+
} catch {
|
|
3301
|
+
return null;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
// packages/runner-e2b/src/opened-env.ts
|
|
3306
|
+
var REMOTE_COMMAND_STDERR_TAIL_SIZE = 30;
|
|
3307
|
+
function createOpenedE2bEnv(input) {
|
|
3308
|
+
const hostRunner = input.spec.hostRunner ?? createHostRunner();
|
|
3309
|
+
const hostWorkspaceDir = path17.resolve(input.spec.cwd);
|
|
3310
|
+
const sandboxWorkspaceDir = normalizeSandboxWorkspaceDir(input.runtime.workspace_dir);
|
|
3311
|
+
let lastProcess = null;
|
|
3312
|
+
let detachedJobContext = null;
|
|
3313
|
+
const mapWorkspaceCwd = (cwd) => {
|
|
3314
|
+
if (cwd === void 0) {
|
|
3315
|
+
return void 0;
|
|
3316
|
+
}
|
|
3317
|
+
if (path17.isAbsolute(cwd) && path17.resolve(cwd) === hostWorkspaceDir) {
|
|
3318
|
+
return sandboxWorkspaceDir;
|
|
3319
|
+
}
|
|
3320
|
+
return cwd;
|
|
3321
|
+
};
|
|
3322
|
+
const attachedJobId = input.spec.detachedJobId;
|
|
3323
|
+
const env = {
|
|
3324
|
+
id: input.sandbox.sandboxId,
|
|
3325
|
+
job: attachedJobId ? createE2bJobHandle({
|
|
3326
|
+
sandbox: input.sandbox,
|
|
3327
|
+
envId: input.sandbox.sandboxId,
|
|
3328
|
+
jobId: attachedJobId,
|
|
3329
|
+
tool: input.spec.jobLabel.tool,
|
|
3330
|
+
argv: input.spec.jobLabel.argv,
|
|
3331
|
+
preserveAfterExitHours: input.runtime.preserve_after_exit_hours ?? 24
|
|
3332
|
+
}) : null,
|
|
3333
|
+
fs: createE2bLogStreamFs(input.sandbox),
|
|
3334
|
+
setDetachedJobContext(context) {
|
|
3335
|
+
detachedJobContext = context;
|
|
3336
|
+
},
|
|
3337
|
+
async uploadWorkspace() {
|
|
3338
|
+
if (input.spec.runner?.sync === "none") {
|
|
3339
|
+
return { files: 0, bytes: 0, skipped: [] };
|
|
3340
|
+
}
|
|
3341
|
+
const tempDir = mkdtempSync2(path17.join(tmpdir2(), "poe-e2b-upload-"));
|
|
3342
|
+
const archivePath = path17.join(tempDir, "workspace.tar");
|
|
3343
|
+
try {
|
|
3344
|
+
await runOrThrow2(hostRunner, {
|
|
3345
|
+
command: "tar",
|
|
3346
|
+
args: [
|
|
3347
|
+
...input.spec.uploadIgnoreFiles.flatMap((ignored) => ["--exclude", ignored]),
|
|
3348
|
+
"-cf",
|
|
3349
|
+
archivePath,
|
|
3350
|
+
"-C",
|
|
3351
|
+
input.spec.cwd,
|
|
3352
|
+
"."
|
|
3353
|
+
],
|
|
3354
|
+
stdout: "pipe",
|
|
3355
|
+
stderr: "pipe"
|
|
3356
|
+
});
|
|
3357
|
+
await input.sandbox.files.write(
|
|
3358
|
+
"/tmp/poe-workspace-upload.tar",
|
|
3359
|
+
toArrayBuffer(await readFile4(archivePath))
|
|
3360
|
+
);
|
|
3361
|
+
await runRemoteOrThrow(
|
|
3362
|
+
input.sandbox,
|
|
3363
|
+
createUploadWorkspaceCommand(sandboxWorkspaceDir)
|
|
3364
|
+
);
|
|
3365
|
+
return { files: 0, bytes: 0, skipped: [] };
|
|
3366
|
+
} finally {
|
|
3367
|
+
rmSync2(tempDir, { recursive: true, force: true });
|
|
3368
|
+
}
|
|
3369
|
+
},
|
|
3370
|
+
async downloadWorkspace(opts) {
|
|
3371
|
+
if (input.spec.runner?.sync === "upload" || input.spec.runner?.sync === "none") {
|
|
3372
|
+
return { files: 0, bytes: 0, conflicts: [] };
|
|
3373
|
+
}
|
|
3374
|
+
const tempDir = mkdtempSync2(path17.join(tmpdir2(), "poe-e2b-download-"));
|
|
3375
|
+
const archivePath = path17.join(tempDir, "workspace.tar");
|
|
3376
|
+
try {
|
|
3377
|
+
await runRemoteOrThrow(
|
|
3378
|
+
input.sandbox,
|
|
3379
|
+
`tar -cf /tmp/poe-workspace-download.tar -C ${shellQuote3(sandboxWorkspaceDir)} .`
|
|
3380
|
+
);
|
|
3381
|
+
const archive = await input.sandbox.files.read("/tmp/poe-workspace-download.tar", {
|
|
3382
|
+
format: "bytes"
|
|
3383
|
+
});
|
|
3384
|
+
await writeFile(archivePath, Buffer.from(archive));
|
|
3385
|
+
await runOrThrow2(hostRunner, {
|
|
3386
|
+
command: "tar",
|
|
3387
|
+
args: [
|
|
3388
|
+
opts.conflictPolicy === "refuse" ? "-xkf" : "-xf",
|
|
3389
|
+
archivePath,
|
|
3390
|
+
"-C",
|
|
3391
|
+
input.spec.cwd
|
|
3392
|
+
],
|
|
3393
|
+
stdout: "pipe",
|
|
3394
|
+
stderr: "pipe"
|
|
3395
|
+
});
|
|
3396
|
+
return { files: 0, bytes: archive.byteLength, conflicts: [] };
|
|
3397
|
+
} finally {
|
|
3398
|
+
rmSync2(tempDir, { recursive: true, force: true });
|
|
3399
|
+
}
|
|
3400
|
+
},
|
|
3401
|
+
exec(spec) {
|
|
3402
|
+
const handle = runE2bCommand(input.sandbox, {
|
|
3403
|
+
...spec,
|
|
3404
|
+
cwd: mapWorkspaceCwd(spec.cwd),
|
|
3405
|
+
env: resolveSandboxCommandEnv(spec.env)
|
|
3406
|
+
});
|
|
3407
|
+
lastProcess = { started: handle.started };
|
|
3408
|
+
return handle;
|
|
3409
|
+
},
|
|
3410
|
+
async detach() {
|
|
3411
|
+
if (detachedJobContext === null) {
|
|
3412
|
+
throw new Error("Cannot detach E2B environment before a job context is registered.");
|
|
3413
|
+
}
|
|
3414
|
+
if (lastProcess === null) {
|
|
3415
|
+
throw new Error("Cannot detach E2B environment before a command is running.");
|
|
3416
|
+
}
|
|
3417
|
+
const command = await lastProcess.started;
|
|
3418
|
+
const preserveAfterExitHours = input.runtime.preserve_after_exit_hours ?? 24;
|
|
3419
|
+
const preserveMs = preserveAfterExitHours * 60 * 60 * 1e3;
|
|
3420
|
+
if (preserveMs > 0) {
|
|
3421
|
+
await input.sandbox.setTimeout(preserveMs);
|
|
3422
|
+
}
|
|
3423
|
+
return createE2bJobHandle({
|
|
3424
|
+
sandbox: input.sandbox,
|
|
3425
|
+
envId: input.sandbox.sandboxId,
|
|
3426
|
+
jobId: detachedJobContext.id,
|
|
3427
|
+
tool: detachedJobContext.tool,
|
|
3428
|
+
argv: detachedJobContext.argv,
|
|
3429
|
+
pid: command.pid,
|
|
3430
|
+
preserveAfterExitHours
|
|
3431
|
+
});
|
|
3432
|
+
},
|
|
3433
|
+
shell() {
|
|
3434
|
+
const shellSpec = input.spec.shellSpec;
|
|
3435
|
+
const command = shellSpec?.command ?? input.spec.env.SHELL ?? "sh";
|
|
3436
|
+
return runE2bPty(input.sandbox, {
|
|
3437
|
+
command,
|
|
3438
|
+
...shellSpec?.args ? { args: shellSpec.args } : {},
|
|
3439
|
+
cwd: mapWorkspaceCwd(shellSpec?.cwd ?? input.spec.cwd),
|
|
3440
|
+
env: resolveSandboxCommandEnv(
|
|
3441
|
+
shellSpec && "env" in shellSpec ? shellSpec.env : input.spec.env
|
|
3442
|
+
),
|
|
3443
|
+
stdin: "inherit",
|
|
3444
|
+
stdout: "inherit",
|
|
3445
|
+
stderr: "inherit",
|
|
3446
|
+
tty: true
|
|
3447
|
+
});
|
|
3448
|
+
},
|
|
3449
|
+
async close() {
|
|
3450
|
+
await input.sandbox.kill();
|
|
3451
|
+
}
|
|
3452
|
+
};
|
|
3453
|
+
return env;
|
|
3454
|
+
}
|
|
3455
|
+
function runE2bCommand(sandbox, spec) {
|
|
3456
|
+
const stdout = spec.stdout === "inherit" ? null : new PassThrough();
|
|
3457
|
+
const stderr = spec.stderr === "inherit" ? null : new PassThrough();
|
|
3458
|
+
let e2bHandle = null;
|
|
3459
|
+
const command = shellCommand([spec.command, ...spec.args ?? []]);
|
|
3460
|
+
const started = sandbox.commands.run(command, {
|
|
3461
|
+
background: true,
|
|
3462
|
+
cwd: spec.cwd,
|
|
3463
|
+
envs: spec.env,
|
|
3464
|
+
stdin: spec.stdin === "pipe",
|
|
3465
|
+
onStdout(data) {
|
|
3466
|
+
stdout?.write(data);
|
|
3467
|
+
if (spec.stdout === "inherit") {
|
|
3468
|
+
process.stdout.write(data);
|
|
3469
|
+
}
|
|
3470
|
+
},
|
|
3471
|
+
onStderr(data) {
|
|
3472
|
+
stderr?.write(data);
|
|
3473
|
+
if (spec.stderr === "inherit") {
|
|
3474
|
+
process.stderr.write(data);
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
});
|
|
3478
|
+
const stdin = spec.stdin === "pipe" ? new Writable2({
|
|
3479
|
+
write(chunk, _encoding, callback) {
|
|
3480
|
+
started.then(
|
|
3481
|
+
(handle) => sandbox.commands.sendStdin(
|
|
3482
|
+
handle.pid,
|
|
3483
|
+
Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))
|
|
3484
|
+
)
|
|
3485
|
+
).then(() => callback(), callback);
|
|
3486
|
+
},
|
|
3487
|
+
final(callback) {
|
|
3488
|
+
if (sandbox.commands.closeStdin === void 0) {
|
|
3489
|
+
callback();
|
|
3490
|
+
return;
|
|
3491
|
+
}
|
|
3492
|
+
started.then((handle) => sandbox.commands.closeStdin(handle.pid)).then(() => callback(), callback);
|
|
3493
|
+
}
|
|
3494
|
+
}) : null;
|
|
3495
|
+
const result = started.then((handle) => {
|
|
3496
|
+
e2bHandle = handle;
|
|
3497
|
+
return handle.wait();
|
|
3498
|
+
}).then(
|
|
3499
|
+
(result2) => {
|
|
3500
|
+
stdout?.end();
|
|
3501
|
+
stderr?.end();
|
|
3502
|
+
return { exitCode: result2.exitCode ?? 0 };
|
|
3503
|
+
},
|
|
3504
|
+
(error2) => {
|
|
3505
|
+
stdout?.end();
|
|
3506
|
+
stderr?.end();
|
|
3507
|
+
if (isExitError(error2)) {
|
|
3508
|
+
return { exitCode: error2.exitCode };
|
|
3509
|
+
}
|
|
3510
|
+
return { exitCode: 1 };
|
|
3511
|
+
}
|
|
3512
|
+
);
|
|
3513
|
+
return {
|
|
3514
|
+
get pid() {
|
|
3515
|
+
return e2bHandle?.pid ?? null;
|
|
3516
|
+
},
|
|
3517
|
+
stdin,
|
|
3518
|
+
stdout,
|
|
3519
|
+
stderr,
|
|
3520
|
+
result,
|
|
3521
|
+
kill() {
|
|
3522
|
+
void e2bHandle?.kill();
|
|
3523
|
+
},
|
|
3524
|
+
get e2bHandle() {
|
|
3525
|
+
return e2bHandle;
|
|
3526
|
+
},
|
|
3527
|
+
started
|
|
3528
|
+
};
|
|
3529
|
+
}
|
|
3530
|
+
function runE2bPty(sandbox, spec) {
|
|
3531
|
+
const stdout = new PassThrough();
|
|
3532
|
+
let handleRef = null;
|
|
3533
|
+
const stdin = new Writable2({
|
|
3534
|
+
write(chunk, _encoding, callback) {
|
|
3535
|
+
if (handleRef === null) {
|
|
3536
|
+
callback(new Error("E2B PTY stdin is not ready."));
|
|
3537
|
+
return;
|
|
3538
|
+
}
|
|
3539
|
+
sandbox.pty.sendInput(handleRef.pid, Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))).then(() => callback(), callback);
|
|
3540
|
+
}
|
|
3541
|
+
});
|
|
3542
|
+
const started = sandbox.pty.create({
|
|
3543
|
+
cols: process.stdout.columns || 80,
|
|
3544
|
+
rows: process.stdout.rows || 24,
|
|
3545
|
+
cwd: spec.cwd,
|
|
3546
|
+
envs: spec.env,
|
|
3547
|
+
onData(data) {
|
|
3548
|
+
stdout.write(Buffer.from(data));
|
|
3549
|
+
if (spec.stdout === "inherit") {
|
|
3550
|
+
process.stdout.write(Buffer.from(data));
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
});
|
|
3554
|
+
const result = started.then((handle) => {
|
|
3555
|
+
handleRef = handle;
|
|
3556
|
+
return handle.wait();
|
|
3557
|
+
}).then(
|
|
3558
|
+
(result2) => {
|
|
3559
|
+
stdout.end();
|
|
3560
|
+
return { exitCode: result2.exitCode ?? 0 };
|
|
3561
|
+
},
|
|
3562
|
+
() => {
|
|
3563
|
+
stdout.end();
|
|
3564
|
+
return { exitCode: 1 };
|
|
3565
|
+
}
|
|
3566
|
+
);
|
|
3567
|
+
return {
|
|
3568
|
+
get pid() {
|
|
3569
|
+
return handleRef?.pid ?? null;
|
|
3570
|
+
},
|
|
3571
|
+
stdin: spec.stdin === "inherit" ? process.stdin : stdin,
|
|
3572
|
+
stdout: spec.stdout === "inherit" ? null : stdout,
|
|
3573
|
+
stderr: null,
|
|
3574
|
+
result,
|
|
3575
|
+
kill() {
|
|
3576
|
+
void (handleRef === null ? void 0 : sandbox.pty.kill(handleRef.pid));
|
|
3577
|
+
}
|
|
3578
|
+
};
|
|
3579
|
+
}
|
|
3580
|
+
async function runRemoteOrThrow(sandbox, command) {
|
|
3581
|
+
const stdoutTail = createLineTail(REMOTE_COMMAND_STDERR_TAIL_SIZE);
|
|
3582
|
+
const stderrTail = createLineTail(REMOTE_COMMAND_STDERR_TAIL_SIZE);
|
|
3583
|
+
let result;
|
|
3584
|
+
try {
|
|
3585
|
+
result = await sandbox.commands.run(command, {
|
|
3586
|
+
onStdout(data) {
|
|
3587
|
+
stdoutTail.push(data);
|
|
3588
|
+
},
|
|
3589
|
+
onStderr(data) {
|
|
3590
|
+
stderrTail.push(data);
|
|
3591
|
+
}
|
|
3592
|
+
});
|
|
3593
|
+
} catch (error2) {
|
|
3594
|
+
appendRemoteCommandOutput(error2, stdoutTail, stderrTail);
|
|
3595
|
+
if (isCommandExitError(error2)) {
|
|
3596
|
+
throw decorateRemoteCommandError(error2, command, stderrTail.values());
|
|
3597
|
+
}
|
|
3598
|
+
throw error2;
|
|
3599
|
+
}
|
|
3600
|
+
appendRemoteCommandOutput(result, stdoutTail, stderrTail);
|
|
3601
|
+
if ("exitCode" in result && result.exitCode !== 0) {
|
|
3602
|
+
throw decorateRemoteCommandError(
|
|
3603
|
+
new Error(`E2B command failed with exit code ${result.exitCode}`),
|
|
3604
|
+
command,
|
|
3605
|
+
stderrTail.values()
|
|
3606
|
+
);
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
function appendRemoteCommandOutput(source, stdoutTail, stderrTail) {
|
|
3610
|
+
if (!source || typeof source !== "object") {
|
|
3611
|
+
return;
|
|
3612
|
+
}
|
|
3613
|
+
const output = source;
|
|
3614
|
+
if (typeof output.stdout === "string") {
|
|
3615
|
+
stdoutTail.push(output.stdout);
|
|
3616
|
+
}
|
|
3617
|
+
if (typeof output.stderr === "string") {
|
|
3618
|
+
stderrTail.push(output.stderr);
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
function decorateRemoteCommandError(error2, command, stderrTail) {
|
|
3622
|
+
const original = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
3623
|
+
const tail = stderrTail.length === 0 ? "" : `
|
|
3624
|
+
|
|
3625
|
+
Last stderr output:
|
|
3626
|
+
${stderrTail.join("\n")}`;
|
|
3627
|
+
const decorated = new Error(`E2B command failed: ${command}
|
|
3628
|
+
${original.message}${tail}`);
|
|
3629
|
+
decorated.stack = original.stack;
|
|
3630
|
+
decorated.cause = original;
|
|
3631
|
+
return decorated;
|
|
3632
|
+
}
|
|
3633
|
+
function createLineTail(maxLines) {
|
|
3634
|
+
const lines = [];
|
|
3635
|
+
let pending = "";
|
|
3636
|
+
const appendLine = (line) => {
|
|
3637
|
+
lines.push(trimTrailingCarriageReturn(line));
|
|
3638
|
+
while (lines.length > maxLines) {
|
|
3639
|
+
lines.shift();
|
|
3640
|
+
}
|
|
3641
|
+
};
|
|
3642
|
+
return {
|
|
3643
|
+
push(chunk) {
|
|
3644
|
+
pending += chunk;
|
|
3645
|
+
const parts = pending.split("\n");
|
|
3646
|
+
pending = parts.pop() ?? "";
|
|
3647
|
+
for (const line of parts) {
|
|
3648
|
+
appendLine(line);
|
|
3649
|
+
}
|
|
3650
|
+
},
|
|
3651
|
+
values() {
|
|
3652
|
+
const output = [...lines];
|
|
3653
|
+
if (pending.length > 0) {
|
|
3654
|
+
output.push(trimTrailingCarriageReturn(pending));
|
|
3655
|
+
}
|
|
3656
|
+
return output.slice(-maxLines);
|
|
3657
|
+
}
|
|
3658
|
+
};
|
|
3659
|
+
}
|
|
3660
|
+
function trimTrailingCarriageReturn(value) {
|
|
3661
|
+
return value.endsWith("\r") ? value.slice(0, -1) : value;
|
|
3662
|
+
}
|
|
3663
|
+
async function runOrThrow2(runner, spec) {
|
|
3664
|
+
const handle = runner.exec(spec);
|
|
3665
|
+
const stderr = readableToString(handle.stderr);
|
|
3666
|
+
const result = await handle.result;
|
|
3667
|
+
if (result.exitCode !== 0) {
|
|
3668
|
+
throw new Error(
|
|
3669
|
+
`Command failed with exit code ${result.exitCode}: ${spec.command} ${(spec.args ?? []).join(" ")}
|
|
3670
|
+
${await stderr}`
|
|
3671
|
+
);
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
function shellCommand(argv) {
|
|
3675
|
+
return argv.map(shellQuote3).join(" ");
|
|
3676
|
+
}
|
|
3677
|
+
function shellQuote3(value) {
|
|
3678
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
3679
|
+
}
|
|
3680
|
+
function createUploadWorkspaceCommand(sandboxWorkspaceDir) {
|
|
3681
|
+
const quotedWorkspaceDir = shellQuote3(sandboxWorkspaceDir);
|
|
3682
|
+
return [
|
|
3683
|
+
`mkdir -p ${quotedWorkspaceDir} || { command -v sudo >/dev/null 2>&1 && sudo mkdir -p ${quotedWorkspaceDir} && sudo chown "$(id -u):$(id -g)" ${quotedWorkspaceDir}; }`,
|
|
3684
|
+
`test -w ${quotedWorkspaceDir} && tar -xf /tmp/poe-workspace-upload.tar -C ${quotedWorkspaceDir}`
|
|
3685
|
+
].join("\n");
|
|
3686
|
+
}
|
|
3687
|
+
function resolveSandboxCommandEnv(env) {
|
|
3688
|
+
if (env === void 0) {
|
|
3689
|
+
return void 0;
|
|
3690
|
+
}
|
|
3691
|
+
return {
|
|
3692
|
+
...env,
|
|
3693
|
+
HOME: "/home/user"
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
function normalizeSandboxWorkspaceDir(workspaceDir) {
|
|
3697
|
+
const resolvedWorkspaceDir = workspaceDir ?? "/workspace";
|
|
3698
|
+
if (!path17.posix.isAbsolute(resolvedWorkspaceDir)) {
|
|
3699
|
+
throw new Error("E2B runtime workspace_dir must be an absolute sandbox path.");
|
|
3700
|
+
}
|
|
3701
|
+
let normalized = path17.posix.normalize(resolvedWorkspaceDir);
|
|
3702
|
+
while (normalized.length > 1 && normalized.endsWith("/")) {
|
|
3703
|
+
normalized = normalized.slice(0, -1);
|
|
3704
|
+
}
|
|
3705
|
+
return normalized;
|
|
3706
|
+
}
|
|
3707
|
+
function isExitError(error2) {
|
|
3708
|
+
return Boolean(
|
|
3709
|
+
error2 && typeof error2 === "object" && typeof error2.exitCode === "number"
|
|
3710
|
+
);
|
|
3711
|
+
}
|
|
3712
|
+
function isCommandExitError(error2) {
|
|
3713
|
+
return isExitError(error2) || Boolean(
|
|
3714
|
+
error2 && typeof error2 === "object" && error2.name === "CommandExitError"
|
|
3715
|
+
);
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
// packages/runner-e2b/src/auth-scope.ts
|
|
3719
|
+
import os4 from "node:os";
|
|
3720
|
+
import { promises as nodeFs4 } from "node:fs";
|
|
3721
|
+
var e2bAuthScope = defineScope("e2b", {
|
|
3722
|
+
api_key: {
|
|
3723
|
+
type: "string",
|
|
3724
|
+
default: "",
|
|
3725
|
+
doc: "E2B API key",
|
|
3726
|
+
env: "E2B_API_KEY"
|
|
3727
|
+
}
|
|
3728
|
+
});
|
|
3729
|
+
async function resolveE2bApiKey(input) {
|
|
3730
|
+
const homeDir = input.homeDir ?? os4.homedir();
|
|
3731
|
+
const fs = input.fs ?? nodeFs4;
|
|
3732
|
+
const env = input.env ?? process.env;
|
|
3733
|
+
const document = await readMergedDocument(
|
|
3734
|
+
fs,
|
|
3735
|
+
resolveConfigPath(homeDir),
|
|
3736
|
+
resolveProjectConfigPath(input.cwd)
|
|
3737
|
+
);
|
|
3738
|
+
const resolved = resolveScope(e2bAuthScope.schema, document.e2b, env);
|
|
3739
|
+
if (resolved.api_key.length === 0) {
|
|
3740
|
+
throw new Error(
|
|
3741
|
+
"No E2B API key. Set E2B_API_KEY or e2b.api_key in ~/.poe-code/config.json."
|
|
3742
|
+
);
|
|
3743
|
+
}
|
|
3744
|
+
return resolved.api_key;
|
|
3745
|
+
}
|
|
3746
|
+
|
|
3747
|
+
// packages/runner-e2b/src/factory.ts
|
|
3748
|
+
var e2bExecutionEnvFactory = {
|
|
3749
|
+
type: "e2b",
|
|
3750
|
+
supportsDetach: true,
|
|
3751
|
+
async open(spec) {
|
|
3752
|
+
const runtime = parseE2bRuntime(spec.runtime);
|
|
3753
|
+
const runtimeCwd = spec.runtimeCwd ?? spec.cwd;
|
|
3754
|
+
const apiKey = await resolveE2bApiKey({ cwd: runtimeCwd });
|
|
3755
|
+
const templateId = runtime.template_id ?? (await buildE2bRuntimeTemplate({
|
|
3756
|
+
runtime,
|
|
3757
|
+
dockerfilePath: path18.resolve(
|
|
3758
|
+
runtimeCwd,
|
|
3759
|
+
runtime.dockerfile ?? path18.join(".poe-code", "Dockerfile")
|
|
3760
|
+
),
|
|
3761
|
+
buildContext: path18.resolve(runtimeCwd, runtime.build_context ?? "."),
|
|
3762
|
+
state: spec.state,
|
|
3763
|
+
apiKey
|
|
3764
|
+
})).templateId;
|
|
3765
|
+
const sandbox = await createSandbox({
|
|
3766
|
+
apiKey,
|
|
3767
|
+
templateId,
|
|
3768
|
+
env: spec.env,
|
|
3769
|
+
timeoutMinutes: runtime.timeout_minutes
|
|
3770
|
+
});
|
|
3771
|
+
return createOpenedE2bEnv({ sandbox, spec, runtime });
|
|
3772
|
+
},
|
|
3773
|
+
async attach(envId, context) {
|
|
3774
|
+
const cwd = context?.cwd ?? process.cwd();
|
|
3775
|
+
const apiKey = await resolveE2bApiKey({ cwd });
|
|
3776
|
+
const sandbox = await connectSandbox(envId, apiKey);
|
|
3777
|
+
return createOpenedE2bEnv({
|
|
3778
|
+
sandbox,
|
|
3779
|
+
spec: {
|
|
3780
|
+
cwd: context?.cwd ?? "/workspace",
|
|
3781
|
+
runtime: {
|
|
3782
|
+
type: "e2b",
|
|
3783
|
+
build_args: {},
|
|
3784
|
+
mounts: [],
|
|
3785
|
+
workspace_dir: "/workspace",
|
|
3786
|
+
preserve_after_exit_hours: 24
|
|
3787
|
+
},
|
|
3788
|
+
env: {},
|
|
3789
|
+
uploadIgnoreFiles: [],
|
|
3790
|
+
jobLabel: { tool: context?.tool ?? "e2b", argv: context?.argv ?? [] },
|
|
3791
|
+
...context?.jobId ? { detachedJobId: context.jobId } : {}
|
|
3792
|
+
},
|
|
3793
|
+
runtime: {
|
|
3794
|
+
type: "e2b",
|
|
3795
|
+
build_args: {},
|
|
3796
|
+
mounts: [],
|
|
3797
|
+
workspace_dir: "/workspace",
|
|
3798
|
+
preserve_after_exit_hours: 24
|
|
3799
|
+
}
|
|
3800
|
+
});
|
|
3801
|
+
}
|
|
3802
|
+
};
|
|
3803
|
+
function parseE2bRuntime(runtime) {
|
|
3804
|
+
if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) {
|
|
3805
|
+
throw new Error("e2b runtime must be an object");
|
|
3806
|
+
}
|
|
3807
|
+
const record = runtime;
|
|
3808
|
+
if (record.type !== "e2b") {
|
|
3809
|
+
throw new Error('e2b runtime type must be "e2b"');
|
|
3810
|
+
}
|
|
3811
|
+
return record;
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
// packages/runner-e2b/src/index.ts
|
|
3815
|
+
var e2bExecutionEnvFactory2 = e2bExecutionEnvFactory;
|
|
3816
|
+
|
|
2195
3817
|
// packages/agent-spawn/src/register-factories.ts
|
|
2196
3818
|
registerExecutionEnvFactory(hostExecutionEnvFactory);
|
|
2197
3819
|
registerExecutionEnvFactory(dockerExecutionEnvFactory);
|
|
2198
|
-
|
|
3820
|
+
registerExecutionEnvFactory(e2bExecutionEnvFactory2);
|
|
3821
|
+
if (isVitest()) {
|
|
2199
3822
|
registerExecutionEnvFactory(createTestHostExecutionEnvFactory());
|
|
2200
3823
|
}
|
|
3824
|
+
function isVitest() {
|
|
3825
|
+
return process.env.VITEST !== void 0 || process.env.VITEST_POOL_ID !== void 0;
|
|
3826
|
+
}
|
|
2201
3827
|
function createTestHostExecutionEnvFactory() {
|
|
2202
3828
|
return {
|
|
2203
3829
|
type: "host",
|
|
@@ -2559,7 +4185,7 @@ function listMcpSupportedAgents() {
|
|
|
2559
4185
|
|
|
2560
4186
|
// packages/agent-spawn/src/spawn.ts
|
|
2561
4187
|
import { mkdirSync, openSync, writeSync, closeSync } from "node:fs";
|
|
2562
|
-
import
|
|
4188
|
+
import path19 from "node:path";
|
|
2563
4189
|
|
|
2564
4190
|
// packages/agent-spawn/src/configs/resolve-config.ts
|
|
2565
4191
|
function resolveConfig(agentId) {
|
|
@@ -3058,7 +4684,7 @@ import chalk8 from "chalk";
|
|
|
3058
4684
|
|
|
3059
4685
|
// packages/design-system/src/dashboard/terminal.ts
|
|
3060
4686
|
import readline from "node:readline";
|
|
3061
|
-
import { PassThrough } from "node:stream";
|
|
4687
|
+
import { PassThrough as PassThrough2 } from "node:stream";
|
|
3062
4688
|
|
|
3063
4689
|
// packages/design-system/src/prompts/index.ts
|
|
3064
4690
|
import chalk15 from "chalk";
|
|
@@ -3090,9 +4716,9 @@ import chalk16 from "chalk";
|
|
|
3090
4716
|
var DEFAULT_ACTIVITY_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
3091
4717
|
|
|
3092
4718
|
// packages/agent-spawn/src/acp/replay.ts
|
|
3093
|
-
import
|
|
4719
|
+
import path20 from "node:path";
|
|
3094
4720
|
import { homedir as homedir2 } from "node:os";
|
|
3095
|
-
import { open as open2, readdir } from "node:fs/promises";
|
|
4721
|
+
import { open as open2, readdir as readdir2 } from "node:fs/promises";
|
|
3096
4722
|
import { createInterface } from "node:readline";
|
|
3097
4723
|
|
|
3098
4724
|
// packages/poe-acp-client/src/acp-client.ts
|
|
@@ -3109,7 +4735,7 @@ import { homedir } from "node:os";
|
|
|
3109
4735
|
import { join } from "node:path";
|
|
3110
4736
|
|
|
3111
4737
|
// packages/agent-spawn/src/acp/middlewares/spawn-log.ts
|
|
3112
|
-
import
|
|
4738
|
+
import path21 from "node:path";
|
|
3113
4739
|
import { homedir as homedir3 } from "node:os";
|
|
3114
4740
|
import { mkdir, open as open3 } from "node:fs/promises";
|
|
3115
4741
|
|
|
@@ -3160,34 +4786,7 @@ function createBinaryExistsCheck(binaryName, id, description) {
|
|
|
3160
4786
|
id,
|
|
3161
4787
|
description,
|
|
3162
4788
|
async run({ runCommand: runCommand2 }) {
|
|
3163
|
-
const
|
|
3164
|
-
`/usr/local/bin/${binaryName}`,
|
|
3165
|
-
`/usr/bin/${binaryName}`,
|
|
3166
|
-
`$HOME/.local/bin/${binaryName}`,
|
|
3167
|
-
`$HOME/.claude/local/bin/${binaryName}`
|
|
3168
|
-
];
|
|
3169
|
-
const detectors = [
|
|
3170
|
-
{
|
|
3171
|
-
command: "which",
|
|
3172
|
-
args: [binaryName],
|
|
3173
|
-
validate: (result) => result.exitCode === 0
|
|
3174
|
-
},
|
|
3175
|
-
{
|
|
3176
|
-
command: "where",
|
|
3177
|
-
args: [binaryName],
|
|
3178
|
-
validate: (result) => result.exitCode === 0 && result.stdout.trim().length > 0
|
|
3179
|
-
},
|
|
3180
|
-
// Check common installation paths using shell expansion for $HOME
|
|
3181
|
-
{
|
|
3182
|
-
command: "sh",
|
|
3183
|
-
args: [
|
|
3184
|
-
"-c",
|
|
3185
|
-
commonPaths.map((p) => `test -f "${p}"`).join(" || ")
|
|
3186
|
-
],
|
|
3187
|
-
validate: (result) => result.exitCode === 0
|
|
3188
|
-
}
|
|
3189
|
-
];
|
|
3190
|
-
for (const detector of detectors) {
|
|
4789
|
+
for (const detector of createBinaryExistsDetectors(binaryName)) {
|
|
3191
4790
|
const result = await runCommand2(detector.command, detector.args);
|
|
3192
4791
|
if (detector.validate(result)) {
|
|
3193
4792
|
return;
|