@pome-sh/cli 0.42.4 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +3 -3
- package/dist/{checks-BKMTBJSO.js → checks-QYUPKBPT.js} +9 -14
- package/dist/{chunk-VPXIGCZW.js → chunk-3YCX3KUL.js} +1 -1
- package/dist/{chunk-5NPGY73F.js → chunk-5A6HHA54.js} +1 -1
- package/dist/{chunk-6KJC4BTO.js → chunk-5KFDRR53.js} +2 -2
- package/dist/{chunk-NNXVR46L.js → chunk-CS7O2ZXB.js} +114 -2
- package/dist/{chunk-RPPF2KDQ.js → chunk-ELIEDNF3.js} +7 -7
- package/dist/{chunk-STMGQ7GU.js → chunk-OBFHOACQ.js} +1404 -171
- package/dist/{runTrialGroup-BP446HIE.js → runTrialGroup-4QL6S7QO.js} +19 -9
- package/dist/{server-GDK4TBTL.js → server-KC57K5AC.js} +1 -1
- package/dist/src/cli/main.js +487 -225
- package/dist/{src-FUPOFZI4.js → src-5F5OTVYT.js} +1 -1
- package/dist/{src-SNSK5ENY.js → src-7X62AGW7.js} +1 -1
- package/dist/{src-UY4Y75BD.js → src-E7NTZL2F.js} +1 -1
- package/dist/{src-V3RUKDMQ.js → src-ESRY2MC7.js} +1 -1
- package/dist/{src-Z4C4SSDR.js → src-Z63IOSCJ.js} +1 -1
- package/dist/twinHarness-WJFDLEAO.js +6 -0
- package/dist/{twinSeed-X6VHBOLM.js → twinSeed-4VQ7RRCL.js} +1 -1
- package/dist/{twinStart-QQU6OIO4.js → twinStart-QQ64EV3P.js} +4 -4
- package/package.json +1 -2
- package/dist/agent-Q2UTRJYL.js +0 -408
- package/dist/chunk-46POYRLY.js +0 -1240
- package/dist/chunk-DFOQGAKS.js +0 -116
- package/dist/chunk-JNXZBK3O.js +0 -17
- package/dist/chunk-WERG4AUH.js +0 -462
- package/dist/chunk-ZX4WNSZ5.js +0 -71
- package/dist/runDemo-DRDMCG25.js +0 -408
- package/dist/twinHarness-ATYQTDA5.js +0 -6
|
@@ -1,19 +1,24 @@
|
|
|
1
|
+
import { readSeedFileText, parseSeedFileText, soleTwinOf, twinsNamedBy, seedsForTwins } from './chunk-3YCX3KUL.js';
|
|
1
2
|
import { gmailSeedSchema, defaultSeedState } from './chunk-PASFBRK4.js';
|
|
2
3
|
import { linearSeedSchema, defaultSeedState as defaultSeedState$1 } from './chunk-3FZY376K.js';
|
|
3
|
-
import { isTwinName, TWIN_REGISTRY } from './chunk-
|
|
4
|
-
import { criterionSchema, normalizeTaskConfigKeys, finalizeResponseSchema, HostedDiscardRefusedError, HostedOrchError, HostedAuthError, HostedQuotaError, submitResultResponseSchema, createEvalSessionResponseSchema, createSessionResponseSchema, sessionPublicSchema } from './chunk-
|
|
4
|
+
import { isTwinName, TWIN_REGISTRY } from './chunk-ELIEDNF3.js';
|
|
5
|
+
import { criterionSchema, normalizeTaskConfigKeys, finalizeResponseSchema, MOUNTED_TWINS, HostedDiscardRefusedError, HostedOrchError, HostedAuthError, readManifest, normalizeManifestTwins, HostedQuotaError, HostedTrialError, submitResultResponseSchema, createEvalSessionResponseSchema, createSessionResponseSchema, agentResponseSchema, isMultiTwinSeedEnvelope, sessionPublicSchema } from './chunk-CS7O2ZXB.js';
|
|
5
6
|
import { seedSchema as seedSchema$1, parseSeed, defaultSeedState as defaultSeedState$2 } from './chunk-NBOQN5VX.js';
|
|
6
7
|
import { seedSchema } from './chunk-YBWG5JK2.js';
|
|
7
8
|
import { toTwinHttpEventRow } from './chunk-HRAD7MRX.js';
|
|
8
9
|
import { redactEvent, redactSecrets } from './chunk-SG6ZTIMT.js';
|
|
9
10
|
import { seedSchema as seedSchema$2 } from './chunk-2K6BJ3PI.js';
|
|
10
|
-
import { mkdir, appendFile, writeFile, readFile } from 'node:fs/promises';
|
|
11
|
+
import { mkdir, appendFile, writeFile, readFile, rm, stat, chmod, mkdtemp, readdir, rename } from 'node:fs/promises';
|
|
11
12
|
import { existsSync } from 'node:fs';
|
|
12
|
-
import { join, basename, extname } from 'node:path';
|
|
13
|
+
import { join, basename, extname, dirname } from 'node:path';
|
|
13
14
|
import { parse } from 'yaml';
|
|
14
15
|
import { z } from 'zod';
|
|
16
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
17
|
+
import { tmpdir, homedir } from 'node:os';
|
|
18
|
+
import { execFile, spawn } from 'node:child_process';
|
|
19
|
+
import { promisify } from 'node:util';
|
|
20
|
+
import { createInterface } from 'node:readline';
|
|
15
21
|
import { gzipSync } from 'node:zlib';
|
|
16
|
-
import { spawn } from 'node:child_process';
|
|
17
22
|
|
|
18
23
|
// src/recorder/specMeta.ts
|
|
19
24
|
var META_SPEC_VERSION = 1;
|
|
@@ -497,6 +502,189 @@ function stripFence(input) {
|
|
|
497
502
|
function slugFromPath(path) {
|
|
498
503
|
return basename(path, extname(path)).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
499
504
|
}
|
|
505
|
+
|
|
506
|
+
// src/hosted/runSets.ts
|
|
507
|
+
function groupRunSets(trials) {
|
|
508
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
509
|
+
for (const trial of trials) {
|
|
510
|
+
const key = trial.verdict.group_id ?? `solo:${trial.verdict.session_id}`;
|
|
511
|
+
const bucket = byKey.get(key);
|
|
512
|
+
if (bucket) bucket.push(trial);
|
|
513
|
+
else byKey.set(key, [trial]);
|
|
514
|
+
}
|
|
515
|
+
const sets = [];
|
|
516
|
+
for (const bucket of byKey.values()) {
|
|
517
|
+
bucket.sort(
|
|
518
|
+
(a, b) => a.verdict.finalized_at.localeCompare(b.verdict.finalized_at)
|
|
519
|
+
);
|
|
520
|
+
const last = bucket[bucket.length - 1];
|
|
521
|
+
const hasFailed = bucket.some((t) => t.verdict.state === "fail");
|
|
522
|
+
const allPassed = bucket.every((t) => t.verdict.state === "pass");
|
|
523
|
+
sets.push({
|
|
524
|
+
groupId: bucket[0].verdict.group_id,
|
|
525
|
+
taskName: bucket[0].verdict.task_name,
|
|
526
|
+
taskPath: bucket[0].verdict.task_path,
|
|
527
|
+
trials: bucket,
|
|
528
|
+
latestFinalizedAt: last.verdict.finalized_at,
|
|
529
|
+
outcome: hasFailed ? "fail" : allPassed ? "pass" : "incomplete"
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
sets.sort((a, b) => a.latestFinalizedAt.localeCompare(b.latestFinalizedAt));
|
|
533
|
+
return sets;
|
|
534
|
+
}
|
|
535
|
+
function latestFailedRunSet(sets) {
|
|
536
|
+
for (let i = sets.length - 1; i >= 0; i -= 1) {
|
|
537
|
+
if (sets[i].outcome === "fail") return sets[i];
|
|
538
|
+
}
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
function latestIncompleteRunSet(sets) {
|
|
542
|
+
for (let i = sets.length - 1; i >= 0; i -= 1) {
|
|
543
|
+
if (sets[i].outcome === "incomplete") return sets[i];
|
|
544
|
+
}
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/hosted/evalResultCache.ts
|
|
549
|
+
var VERDICT_ARTIFACT_VERSION = 2;
|
|
550
|
+
var VERDICT_FILENAME = "verdict.json";
|
|
551
|
+
var VALID_STATES = /* @__PURE__ */ new Set([
|
|
552
|
+
"pass",
|
|
553
|
+
"fail",
|
|
554
|
+
"incomplete"
|
|
555
|
+
]);
|
|
556
|
+
async function writeVerdictArtifact(runDir, verdict) {
|
|
557
|
+
const tmpPath = join(runDir, `.${VERDICT_FILENAME}.${process.pid}.${randomUUID()}.tmp`);
|
|
558
|
+
try {
|
|
559
|
+
await writeFile(tmpPath, `${JSON.stringify(verdict, null, 2)}
|
|
560
|
+
`, "utf8");
|
|
561
|
+
await rename(tmpPath, join(runDir, VERDICT_FILENAME));
|
|
562
|
+
} catch (err) {
|
|
563
|
+
await rm(tmpPath, { force: true }).catch(() => {
|
|
564
|
+
});
|
|
565
|
+
throw err;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function isVerdictArtifact(parsed) {
|
|
569
|
+
if (typeof parsed !== "object" || parsed === null) return false;
|
|
570
|
+
const v = parsed;
|
|
571
|
+
if (v.version !== VERDICT_ARTIFACT_VERSION) return false;
|
|
572
|
+
if (v.source !== "cloud-finalize") return false;
|
|
573
|
+
if (typeof v.session_id !== "string") return false;
|
|
574
|
+
if (typeof v.task_name !== "string") return false;
|
|
575
|
+
if (typeof v.task_path !== "string") return false;
|
|
576
|
+
if (v.group_id !== null && typeof v.group_id !== "string") return false;
|
|
577
|
+
if (typeof v.finalized_at !== "string") return false;
|
|
578
|
+
if (typeof v.passed !== "boolean") return false;
|
|
579
|
+
if (typeof v.score !== "number") return false;
|
|
580
|
+
if (typeof v.state !== "string" || !VALID_STATES.has(v.state)) return false;
|
|
581
|
+
if (typeof v.evaluated !== "number") return false;
|
|
582
|
+
if (typeof v.not_evaluated !== "number") return false;
|
|
583
|
+
if (typeof v.pre_satisfied !== "number") return false;
|
|
584
|
+
if (typeof v.total !== "number") return false;
|
|
585
|
+
if (!Array.isArray(v.criteria_results)) return false;
|
|
586
|
+
return v.criteria_results.every((r) => {
|
|
587
|
+
if (typeof r !== "object" || r === null) return false;
|
|
588
|
+
const result = r;
|
|
589
|
+
const criterion = result.criterion;
|
|
590
|
+
return typeof criterion === "object" && criterion !== null && typeof criterion.text === "string" && typeof result.reason === "string" && typeof result.passed === "boolean" && typeof result.skipped === "boolean";
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
var MISSING_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
594
|
+
"ENOENT",
|
|
595
|
+
"ENOTDIR",
|
|
596
|
+
"ELOOP",
|
|
597
|
+
"ENAMETOOLONG"
|
|
598
|
+
]);
|
|
599
|
+
function isMissingFileError(err) {
|
|
600
|
+
const code = err?.code;
|
|
601
|
+
return typeof code === "string" && MISSING_ERROR_CODES.has(code);
|
|
602
|
+
}
|
|
603
|
+
async function readVerdictArtifactDetailed(runDir) {
|
|
604
|
+
const path = join(runDir, VERDICT_FILENAME);
|
|
605
|
+
let raw;
|
|
606
|
+
try {
|
|
607
|
+
raw = await readFile(path, "utf8");
|
|
608
|
+
} catch (err) {
|
|
609
|
+
return isMissingFileError(err) ? { status: "missing" } : { status: "unreadable" };
|
|
610
|
+
}
|
|
611
|
+
let parsed;
|
|
612
|
+
try {
|
|
613
|
+
parsed = JSON.parse(raw);
|
|
614
|
+
} catch {
|
|
615
|
+
return { status: "unreadable" };
|
|
616
|
+
}
|
|
617
|
+
if (!isVerdictArtifact(parsed)) return { status: "unreadable" };
|
|
618
|
+
return { status: "ok", trial: { runDir, verdict: parsed } };
|
|
619
|
+
}
|
|
620
|
+
async function scanVerdictArtifactsDetailed(artifactsRoot) {
|
|
621
|
+
const trials = [];
|
|
622
|
+
const unreadableDirs = [];
|
|
623
|
+
let slugs;
|
|
624
|
+
try {
|
|
625
|
+
slugs = await readdir(artifactsRoot);
|
|
626
|
+
} catch {
|
|
627
|
+
return { trials, unreadableDirs };
|
|
628
|
+
}
|
|
629
|
+
for (const slug of slugs) {
|
|
630
|
+
const slugDir = join(artifactsRoot, slug);
|
|
631
|
+
let runIds;
|
|
632
|
+
try {
|
|
633
|
+
runIds = await readdir(slugDir);
|
|
634
|
+
} catch {
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
for (const runId of runIds) {
|
|
638
|
+
const runDir = join(slugDir, runId);
|
|
639
|
+
const result = await readVerdictArtifactDetailed(runDir);
|
|
640
|
+
if (result.status === "ok") trials.push(result.trial);
|
|
641
|
+
else if (result.status === "unreadable") unreadableDirs.push(runDir);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
unreadableDirs.sort();
|
|
645
|
+
return { trials, unreadableDirs };
|
|
646
|
+
}
|
|
647
|
+
async function discoverRunSet(target) {
|
|
648
|
+
const anchorResult = await readVerdictArtifactDetailed(target);
|
|
649
|
+
if (anchorResult.status === "unreadable") {
|
|
650
|
+
return {
|
|
651
|
+
kind: "trial-dir",
|
|
652
|
+
set: null,
|
|
653
|
+
incompleteSet: null,
|
|
654
|
+
totalSets: 0,
|
|
655
|
+
unreadableCount: 1,
|
|
656
|
+
unreadablePaths: [target]
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
if (anchorResult.status === "ok") {
|
|
660
|
+
const anchor = anchorResult.trial;
|
|
661
|
+
const root = join(target, "..", "..");
|
|
662
|
+
const { trials: trials2, unreadableDirs: unreadableDirs2 } = await scanVerdictArtifactsDetailed(root);
|
|
663
|
+
const sets2 = groupRunSets(trials2);
|
|
664
|
+
const own = sets2.find(
|
|
665
|
+
(s) => anchor.verdict.group_id !== null && s.groupId === anchor.verdict.group_id || anchor.verdict.group_id === null && s.trials.length === 1 && s.trials[0].verdict.session_id === anchor.verdict.session_id
|
|
666
|
+
) ?? groupRunSets([anchor])[0];
|
|
667
|
+
return {
|
|
668
|
+
kind: "trial-dir",
|
|
669
|
+
set: own,
|
|
670
|
+
incompleteSet: null,
|
|
671
|
+
totalSets: Math.max(sets2.length, 1),
|
|
672
|
+
unreadableCount: unreadableDirs2.length,
|
|
673
|
+
unreadablePaths: unreadableDirs2
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
const { trials, unreadableDirs } = await scanVerdictArtifactsDetailed(target);
|
|
677
|
+
const sets = groupRunSets(trials);
|
|
678
|
+
const failedSet = latestFailedRunSet(sets);
|
|
679
|
+
return {
|
|
680
|
+
kind: "root",
|
|
681
|
+
set: failedSet,
|
|
682
|
+
incompleteSet: failedSet ? null : latestIncompleteRunSet(sets),
|
|
683
|
+
totalSets: sets.length,
|
|
684
|
+
unreadableCount: unreadableDirs.length,
|
|
685
|
+
unreadablePaths: unreadableDirs
|
|
686
|
+
};
|
|
687
|
+
}
|
|
500
688
|
var PER_TWIN_PROVENANCE = /* @__PURE__ */ Symbol("pome.perTwinFromCloud");
|
|
501
689
|
function markPerTwinProvenance(session, fromCloud) {
|
|
502
690
|
Object.defineProperty(session, PER_TWIN_PROVENANCE, {
|
|
@@ -1215,6 +1403,645 @@ function normalizeAgentSdk(value) {
|
|
|
1215
1403
|
const trimmed = value.trim();
|
|
1216
1404
|
return trimmed.length > 0 ? trimmed : null;
|
|
1217
1405
|
}
|
|
1406
|
+
var execFileAsync = promisify(execFile);
|
|
1407
|
+
var SERVICE = "sh.pome.cli";
|
|
1408
|
+
var ACCOUNT = "hosted";
|
|
1409
|
+
function keychainSupported() {
|
|
1410
|
+
if (process.env.POME_CLI_DISABLE_KEYCHAIN === "1") return false;
|
|
1411
|
+
return process.platform === "darwin";
|
|
1412
|
+
}
|
|
1413
|
+
async function writeKeychainCredentials(creds) {
|
|
1414
|
+
if (!keychainSupported()) return;
|
|
1415
|
+
const payload = JSON.stringify({
|
|
1416
|
+
api_key: creds.api_key,
|
|
1417
|
+
api_url: creds.api_url,
|
|
1418
|
+
dashboard_url: creds.dashboard_url,
|
|
1419
|
+
team_id: creds.team_id,
|
|
1420
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1421
|
+
});
|
|
1422
|
+
await execFileAsync("security", [
|
|
1423
|
+
"add-generic-password",
|
|
1424
|
+
"-U",
|
|
1425
|
+
"-s",
|
|
1426
|
+
SERVICE,
|
|
1427
|
+
"-a",
|
|
1428
|
+
ACCOUNT,
|
|
1429
|
+
"-w",
|
|
1430
|
+
payload
|
|
1431
|
+
]);
|
|
1432
|
+
}
|
|
1433
|
+
async function readKeychainCredentials() {
|
|
1434
|
+
if (!keychainSupported()) return null;
|
|
1435
|
+
try {
|
|
1436
|
+
const { stdout } = await execFileAsync("security", [
|
|
1437
|
+
"find-generic-password",
|
|
1438
|
+
"-s",
|
|
1439
|
+
SERVICE,
|
|
1440
|
+
"-a",
|
|
1441
|
+
ACCOUNT,
|
|
1442
|
+
"-w"
|
|
1443
|
+
]);
|
|
1444
|
+
const text = stdout.trim();
|
|
1445
|
+
if (!text) return null;
|
|
1446
|
+
const parsed = JSON.parse(text);
|
|
1447
|
+
if (typeof parsed.api_key !== "string" || !parsed.api_key.trim()) {
|
|
1448
|
+
return null;
|
|
1449
|
+
}
|
|
1450
|
+
return parsed;
|
|
1451
|
+
} catch {
|
|
1452
|
+
return null;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
async function deleteKeychainCredentials() {
|
|
1456
|
+
if (!keychainSupported()) return false;
|
|
1457
|
+
try {
|
|
1458
|
+
await execFileAsync("security", [
|
|
1459
|
+
"delete-generic-password",
|
|
1460
|
+
"-s",
|
|
1461
|
+
SERVICE,
|
|
1462
|
+
"-a",
|
|
1463
|
+
ACCOUNT
|
|
1464
|
+
]);
|
|
1465
|
+
return true;
|
|
1466
|
+
} catch {
|
|
1467
|
+
return false;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// src/cli/credentials.ts
|
|
1472
|
+
var DEFAULT_PATH = () => join(homedir(), ".pome", "credentials.json");
|
|
1473
|
+
async function persistCredentialsAfterLogin(credentials, path = DEFAULT_PATH()) {
|
|
1474
|
+
if (keychainSupported()) {
|
|
1475
|
+
try {
|
|
1476
|
+
await writeKeychainCredentials(credentials);
|
|
1477
|
+
try {
|
|
1478
|
+
await rm(path, { force: true });
|
|
1479
|
+
} catch {
|
|
1480
|
+
}
|
|
1481
|
+
return { stored: "keychain" };
|
|
1482
|
+
} catch {
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
await writeCredentialsFile(credentials, path);
|
|
1486
|
+
return { stored: "file", path };
|
|
1487
|
+
}
|
|
1488
|
+
async function clearLocalCredentials(path = DEFAULT_PATH()) {
|
|
1489
|
+
await deleteKeychainCredentials();
|
|
1490
|
+
try {
|
|
1491
|
+
await rm(path, { force: true });
|
|
1492
|
+
} catch {
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
async function resolveCredentials(input) {
|
|
1496
|
+
const envKey = process.env.POME_API_KEY;
|
|
1497
|
+
if (envKey && envKey.trim().length > 0) {
|
|
1498
|
+
const apiBaseUrl = resolveApiBaseUrl(input.apiBaseUrl, void 0);
|
|
1499
|
+
return { apiKey: envKey.trim(), apiBaseUrl };
|
|
1500
|
+
}
|
|
1501
|
+
const keychain = await readKeychainCredentials();
|
|
1502
|
+
if (keychain && typeof keychain.api_key === "string" && keychain.api_key.trim().length > 0) {
|
|
1503
|
+
return {
|
|
1504
|
+
apiKey: keychain.api_key.trim(),
|
|
1505
|
+
apiBaseUrl: resolveApiBaseUrl(input.apiBaseUrl, keychain.api_url),
|
|
1506
|
+
teamId: normalizeTeamId(keychain.team_id)
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
const path = input.credentialsPath ?? DEFAULT_PATH();
|
|
1510
|
+
let raw;
|
|
1511
|
+
try {
|
|
1512
|
+
await assertSafeCredentialFileMode(path);
|
|
1513
|
+
raw = await readFile(path, "utf8");
|
|
1514
|
+
} catch (err) {
|
|
1515
|
+
if (err instanceof Error && err.code === "ENOENT") {
|
|
1516
|
+
throw new HostedAuthError(
|
|
1517
|
+
`Hosted mode requires authentication. Run \`pome login\`, set POME_API_KEY in your environment, or sign in on macOS to store credentials in Keychain. Expected file: ${path}.`
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
throw err;
|
|
1521
|
+
}
|
|
1522
|
+
let parsed;
|
|
1523
|
+
try {
|
|
1524
|
+
parsed = JSON.parse(raw);
|
|
1525
|
+
} catch {
|
|
1526
|
+
throw new Error(`${path} is not valid JSON.`);
|
|
1527
|
+
}
|
|
1528
|
+
if (typeof parsed.api_key !== "string" || parsed.api_key.trim().length === 0) {
|
|
1529
|
+
throw new Error(`${path} is missing "api_key".`);
|
|
1530
|
+
}
|
|
1531
|
+
return {
|
|
1532
|
+
apiKey: parsed.api_key.trim(),
|
|
1533
|
+
apiBaseUrl: resolveApiBaseUrl(input.apiBaseUrl, parsed.api_url),
|
|
1534
|
+
teamId: normalizeTeamId(parsed.team_id)
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
function normalizeTeamId(raw) {
|
|
1538
|
+
if (typeof raw !== "string") return void 0;
|
|
1539
|
+
const trimmed = raw.trim();
|
|
1540
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
1541
|
+
}
|
|
1542
|
+
function resolveApiBaseUrl(callerInput, storedApiUrl) {
|
|
1543
|
+
const candidate = callerInput ?? storedApiUrl;
|
|
1544
|
+
if (typeof candidate !== "string" || candidate.trim().length === 0) {
|
|
1545
|
+
throw new Error(
|
|
1546
|
+
"Refusing to resolve hosted credentials: no apiBaseUrl supplied and no stored api_url found."
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
return normalizeBaseUrl(candidate);
|
|
1550
|
+
}
|
|
1551
|
+
async function assertSafeCredentialFileMode(path) {
|
|
1552
|
+
const st = await stat(path);
|
|
1553
|
+
const perm = st.mode & 511;
|
|
1554
|
+
if ((perm & 63) !== 0) {
|
|
1555
|
+
throw new Error(
|
|
1556
|
+
`Refusing to read credentials at ${path}: mode 0${perm.toString(8)} is too permissive (group/other bits must be 0). Run: chmod 600 ${path}`
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
async function writeCredentialsFile(credentials, path = DEFAULT_PATH()) {
|
|
1561
|
+
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
1562
|
+
await writeFile(
|
|
1563
|
+
path,
|
|
1564
|
+
`${JSON.stringify(
|
|
1565
|
+
{
|
|
1566
|
+
...credentials,
|
|
1567
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1568
|
+
},
|
|
1569
|
+
null,
|
|
1570
|
+
2
|
|
1571
|
+
)}
|
|
1572
|
+
`,
|
|
1573
|
+
{ mode: 384 }
|
|
1574
|
+
);
|
|
1575
|
+
await chmod(path, 384);
|
|
1576
|
+
}
|
|
1577
|
+
function normalizeBaseUrl(raw) {
|
|
1578
|
+
return new URL(raw).toString().replace(/\/$/, "");
|
|
1579
|
+
}
|
|
1580
|
+
function resolveSeams(opts) {
|
|
1581
|
+
return {
|
|
1582
|
+
stdinIsTTY: opts.stdinIsTTY ?? Boolean(process.stdin.isTTY),
|
|
1583
|
+
confirm: opts.confirm ?? promptYesNo,
|
|
1584
|
+
ask: opts.ask ?? promptLine
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
async function requestAgent(creds, body) {
|
|
1588
|
+
const res = await fetch(`${creds.apiBaseUrl}/v1/agents`, {
|
|
1589
|
+
method: "POST",
|
|
1590
|
+
headers: { "x-api-key": creds.apiKey, "content-type": "application/json" },
|
|
1591
|
+
body: JSON.stringify(prunePostBody(body))
|
|
1592
|
+
}).catch((err) => {
|
|
1593
|
+
throw new HostedOrchError(err instanceof Error ? err.message : "network error");
|
|
1594
|
+
});
|
|
1595
|
+
const text = await res.text();
|
|
1596
|
+
let json = {};
|
|
1597
|
+
try {
|
|
1598
|
+
json = text.length ? JSON.parse(text) : {};
|
|
1599
|
+
} catch {
|
|
1600
|
+
throw new HostedOrchError(`non-JSON response (status ${res.status})`);
|
|
1601
|
+
}
|
|
1602
|
+
return { ok: res.ok, status: res.status, json };
|
|
1603
|
+
}
|
|
1604
|
+
function prunePostBody(body) {
|
|
1605
|
+
const out = { name: body.name };
|
|
1606
|
+
if (body.slug !== void 0) out.slug = body.slug;
|
|
1607
|
+
if (body.description !== void 0) out.description = body.description;
|
|
1608
|
+
if (body.version !== void 0) out.version = body.version;
|
|
1609
|
+
if (body.framework !== void 0) out.framework = body.framework;
|
|
1610
|
+
if (body.twins && body.twins.length > 0) out.twins = body.twins;
|
|
1611
|
+
if (body.confirm !== void 0) out.confirm = body.confirm;
|
|
1612
|
+
return out;
|
|
1613
|
+
}
|
|
1614
|
+
function parseOkAgent(json) {
|
|
1615
|
+
const parsed = agentResponseSchema.safeParse(json);
|
|
1616
|
+
if (!parsed.success) {
|
|
1617
|
+
throw new HostedOrchError("POST /v1/agents returned an unexpected shape");
|
|
1618
|
+
}
|
|
1619
|
+
return parsed.data;
|
|
1620
|
+
}
|
|
1621
|
+
function mapHttpError(status, json) {
|
|
1622
|
+
const err = json.error;
|
|
1623
|
+
if (status === 401 || status === 403) {
|
|
1624
|
+
return new HostedAuthError(err?.message ?? `HTTP ${status}`);
|
|
1625
|
+
}
|
|
1626
|
+
if (status === 404) {
|
|
1627
|
+
return new HostedOrchError(
|
|
1628
|
+
err?.message ?? "POST /v1/agents returned 404 \u2014 check that --api-url/POME_API_URL points at a Pome control plane."
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
return new HostedOrchError(err?.message ?? `POST /v1/agents \u2192 HTTP ${status}`);
|
|
1632
|
+
}
|
|
1633
|
+
function nearMissSuggestion(json) {
|
|
1634
|
+
const error = json.error;
|
|
1635
|
+
if (error?.type !== "conflict") return void 0;
|
|
1636
|
+
const details = error.details;
|
|
1637
|
+
if (!details) return void 0;
|
|
1638
|
+
const candidate = details.suggestion ?? details.slug ?? (Array.isArray(details.suggestions) ? details.suggestions[0] : void 0);
|
|
1639
|
+
return typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
|
|
1640
|
+
}
|
|
1641
|
+
async function postAgentResolver(creds, body, seams) {
|
|
1642
|
+
const first = await requestAgent(creds, body);
|
|
1643
|
+
if (first.ok) return parseOkAgent(first.json);
|
|
1644
|
+
if (first.status === 409) {
|
|
1645
|
+
const suggestion = nearMissSuggestion(first.json);
|
|
1646
|
+
if (suggestion) {
|
|
1647
|
+
const interactive = seams.stdinIsTTY && !process.env.CI;
|
|
1648
|
+
let retry;
|
|
1649
|
+
if (interactive) {
|
|
1650
|
+
const yes = await seams.confirm(
|
|
1651
|
+
`An agent named "${suggestion}" already exists on your team. Did you mean "${suggestion}"? [y/N] `
|
|
1652
|
+
);
|
|
1653
|
+
retry = yes ? { ...body, slug: suggestion } : { ...body, confirm: true };
|
|
1654
|
+
} else {
|
|
1655
|
+
console.error(
|
|
1656
|
+
`Near-miss: this slug is close to your team's existing "${suggestion}". Registering the new agent anyway (run interactively to link the existing one instead).`
|
|
1657
|
+
);
|
|
1658
|
+
retry = { ...body, confirm: true };
|
|
1659
|
+
}
|
|
1660
|
+
const second = await requestAgent(creds, retry);
|
|
1661
|
+
if (second.ok) return parseOkAgent(second.json);
|
|
1662
|
+
throw mapHttpError(second.status, second.json);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
throw mapHttpError(first.status, first.json);
|
|
1666
|
+
}
|
|
1667
|
+
async function promptYesNo(question) {
|
|
1668
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
1669
|
+
try {
|
|
1670
|
+
const answer = await new Promise((resolve) => rl.question(question, resolve));
|
|
1671
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
1672
|
+
} finally {
|
|
1673
|
+
rl.close();
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
async function promptLine(question) {
|
|
1677
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
1678
|
+
try {
|
|
1679
|
+
return (await new Promise((resolve) => rl.question(question, resolve))).trim();
|
|
1680
|
+
} finally {
|
|
1681
|
+
rl.close();
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
var POME_DIR = ".pome";
|
|
1685
|
+
var LINK_FILE = "link.json";
|
|
1686
|
+
var GITIGNORE_ENTRY = `${POME_DIR}/`;
|
|
1687
|
+
function linkPath(projectDir) {
|
|
1688
|
+
return join(projectDir, POME_DIR, LINK_FILE);
|
|
1689
|
+
}
|
|
1690
|
+
async function readLinkCache(projectDir) {
|
|
1691
|
+
let text;
|
|
1692
|
+
try {
|
|
1693
|
+
text = await readFile(linkPath(projectDir), "utf8");
|
|
1694
|
+
} catch (err) {
|
|
1695
|
+
if (err.code === "ENOENT") return null;
|
|
1696
|
+
return null;
|
|
1697
|
+
}
|
|
1698
|
+
let parsed;
|
|
1699
|
+
try {
|
|
1700
|
+
parsed = JSON.parse(text);
|
|
1701
|
+
} catch {
|
|
1702
|
+
return null;
|
|
1703
|
+
}
|
|
1704
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed.agent_id !== "string" || typeof parsed.team_id !== "string") {
|
|
1705
|
+
return null;
|
|
1706
|
+
}
|
|
1707
|
+
const { agent_id, team_id } = parsed;
|
|
1708
|
+
const resolved_at = typeof parsed.resolved_at === "string" ? parsed.resolved_at : "";
|
|
1709
|
+
return { agent_id, team_id, resolved_at };
|
|
1710
|
+
}
|
|
1711
|
+
async function writeLinkCache(projectDir, entry) {
|
|
1712
|
+
await mkdir(join(projectDir, POME_DIR), { recursive: true });
|
|
1713
|
+
const body = {
|
|
1714
|
+
agent_id: entry.agent_id,
|
|
1715
|
+
team_id: entry.team_id,
|
|
1716
|
+
resolved_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1717
|
+
};
|
|
1718
|
+
await writeFile(linkPath(projectDir), `${JSON.stringify(body, null, 2)}
|
|
1719
|
+
`);
|
|
1720
|
+
}
|
|
1721
|
+
function resolveCachedAgentId(cache, callerTeamId) {
|
|
1722
|
+
if (!cache || !callerTeamId) return void 0;
|
|
1723
|
+
return cache.team_id === callerTeamId ? cache.agent_id : void 0;
|
|
1724
|
+
}
|
|
1725
|
+
async function ensurePomeGitignored(projectDir) {
|
|
1726
|
+
const path = join(projectDir, ".gitignore");
|
|
1727
|
+
let existing;
|
|
1728
|
+
try {
|
|
1729
|
+
existing = await readFile(path, "utf8");
|
|
1730
|
+
} catch (err) {
|
|
1731
|
+
if (err.code !== "ENOENT") throw err;
|
|
1732
|
+
existing = null;
|
|
1733
|
+
}
|
|
1734
|
+
if (existing === null) {
|
|
1735
|
+
await writeFile(path, `${GITIGNORE_ENTRY}
|
|
1736
|
+
`);
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
const alreadyIgnored = existing.split(/\r?\n/).some((line) => line.trim() === GITIGNORE_ENTRY || line.trim() === POME_DIR);
|
|
1740
|
+
if (alreadyIgnored) return;
|
|
1741
|
+
const separator = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
1742
|
+
await writeFile(path, `${existing}${separator}${GITIGNORE_ENTRY}
|
|
1743
|
+
`);
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// src/cli/agent-identity.ts
|
|
1747
|
+
async function resolveRunAgentIdentity(input) {
|
|
1748
|
+
const manifestRead = await readManifest(input.startDir);
|
|
1749
|
+
if (!manifestRead) {
|
|
1750
|
+
return input.agentVersionOverride ? { agentVersion: input.agentVersionOverride } : {};
|
|
1751
|
+
}
|
|
1752
|
+
const { agent } = manifestRead.manifest;
|
|
1753
|
+
const agentVersion = input.agentVersionOverride ?? agent.version;
|
|
1754
|
+
const base = {
|
|
1755
|
+
agentVersion,
|
|
1756
|
+
framework: agent.framework,
|
|
1757
|
+
agentSlug: agent.slug
|
|
1758
|
+
};
|
|
1759
|
+
const projectDir = dirname(manifestRead.path);
|
|
1760
|
+
try {
|
|
1761
|
+
const creds = await resolveCredentials({
|
|
1762
|
+
apiBaseUrl: input.apiBaseUrl,
|
|
1763
|
+
credentialsPath: input.credentialsPath
|
|
1764
|
+
});
|
|
1765
|
+
const cachedId = resolveCachedAgentId(await readLinkCache(projectDir), creds.teamId);
|
|
1766
|
+
if (cachedId) {
|
|
1767
|
+
return { ...base, agentId: cachedId };
|
|
1768
|
+
}
|
|
1769
|
+
const resolved = await postAgentResolver(
|
|
1770
|
+
creds,
|
|
1771
|
+
{
|
|
1772
|
+
name: agent.name ?? agent.slug,
|
|
1773
|
+
slug: agent.slug,
|
|
1774
|
+
description: agent.description,
|
|
1775
|
+
version: agent.version,
|
|
1776
|
+
framework: agent.framework,
|
|
1777
|
+
twins: normalizeManifestTwins(manifestRead.manifest.twins)
|
|
1778
|
+
},
|
|
1779
|
+
resolveSeams({ stdinIsTTY: false })
|
|
1780
|
+
);
|
|
1781
|
+
if (creds.teamId) {
|
|
1782
|
+
await writeLinkCache(projectDir, { agent_id: resolved.id, team_id: creds.teamId });
|
|
1783
|
+
await ensurePomeGitignored(projectDir);
|
|
1784
|
+
}
|
|
1785
|
+
return { ...base, agentId: resolved.id };
|
|
1786
|
+
} catch (err) {
|
|
1787
|
+
console.error(
|
|
1788
|
+
`pome: could not resolve agent identity (${err instanceof Error ? err.message : String(err)}); running unattributed.`
|
|
1789
|
+
);
|
|
1790
|
+
return base;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
// src/cli/defaults.ts
|
|
1795
|
+
var DEFAULT_CONTROL_PLANE_URL = "https://api.pome.sh";
|
|
1796
|
+
var DEFAULT_DASHBOARD_URL = "https://app.pome.sh";
|
|
1797
|
+
var DEFAULT_DOCS_SITE_ORIGIN = "https://docs.pome.sh";
|
|
1798
|
+
function ensureMcpSuffix(url) {
|
|
1799
|
+
return /\/mcp\/?$/.test(url) ? url : `${url.replace(/\/$/, "")}/mcp`;
|
|
1800
|
+
}
|
|
1801
|
+
var ALLOWED_TWINS = new Set(MOUNTED_TWINS);
|
|
1802
|
+
var SESSION_TWIN_NAMES = [...ALLOWED_TWINS];
|
|
1803
|
+
function redactSession(res) {
|
|
1804
|
+
const pcIn = res.provider_credentials;
|
|
1805
|
+
const pcOut = {};
|
|
1806
|
+
if (pcIn.github) {
|
|
1807
|
+
pcOut.github = { ...pcIn.github, token: "***redacted***" };
|
|
1808
|
+
}
|
|
1809
|
+
if (pcIn.stripe) {
|
|
1810
|
+
pcOut.stripe = { ...pcIn.stripe, api_key: "***redacted***" };
|
|
1811
|
+
}
|
|
1812
|
+
if (pcIn.slack) {
|
|
1813
|
+
pcOut.slack = { ...pcIn.slack, token: "***redacted***" };
|
|
1814
|
+
}
|
|
1815
|
+
return {
|
|
1816
|
+
session_id: res.session_id,
|
|
1817
|
+
session_token: res.session_token ? "***redacted***" : res.session_id,
|
|
1818
|
+
twin_url: res.twin_url,
|
|
1819
|
+
expires_at: res.expires_at,
|
|
1820
|
+
openapi_url: res.openapi_url,
|
|
1821
|
+
per_twin: res.per_twin,
|
|
1822
|
+
provider_credentials: pcOut,
|
|
1823
|
+
agent_token: "***redacted***"
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
function formatEnvExport(res, twins) {
|
|
1827
|
+
const lines = [
|
|
1828
|
+
`# Pome hosted sandbox \u2014 treat as secret. Twins: ${twins.join(", ")}`,
|
|
1829
|
+
`export POME_AUTH_TOKEN=${JSON.stringify(res.agent_token)}`,
|
|
1830
|
+
`export POME_SESSION_ID=${JSON.stringify(res.session_id)}`,
|
|
1831
|
+
// Legacy single-endpoint URL (= the primary twin's api_url). Kept for
|
|
1832
|
+
// agents written against the pre-multi-twin contract.
|
|
1833
|
+
`export POME_TWIN_URL=${JSON.stringify(res.twin_url)}`,
|
|
1834
|
+
`export POME_TWIN_NAMES=${JSON.stringify(twins.join(","))}`
|
|
1835
|
+
];
|
|
1836
|
+
const fromCloud = perTwinReturnedByCloud(res);
|
|
1837
|
+
for (const twin of twins) {
|
|
1838
|
+
const upper = twin.toUpperCase();
|
|
1839
|
+
const pt = res.per_twin?.[twin];
|
|
1840
|
+
if (pt?.api_url) {
|
|
1841
|
+
const mcpUrl = fromCloud ? ensureMcpSuffix(pt.mcp_url) : ensureMcpSuffix(pt.api_url);
|
|
1842
|
+
lines.push(`export POME_${upper}_REST_URL=${JSON.stringify(pt.api_url)}`);
|
|
1843
|
+
lines.push(
|
|
1844
|
+
`export POME_${upper}_MCP_URL=${JSON.stringify(mcpUrl)}`
|
|
1845
|
+
);
|
|
1846
|
+
}
|
|
1847
|
+
if (twin === "github") {
|
|
1848
|
+
const gh = res.provider_credentials.github;
|
|
1849
|
+
if (gh) {
|
|
1850
|
+
lines.push(`export POME_GITHUB_TOKEN=${JSON.stringify(gh.token)}`);
|
|
1851
|
+
}
|
|
1852
|
+
} else if (twin === "stripe") {
|
|
1853
|
+
const st = res.provider_credentials.stripe;
|
|
1854
|
+
if (st) {
|
|
1855
|
+
lines.push(`export POME_STRIPE_API_KEY=${JSON.stringify(st.api_key)}`);
|
|
1856
|
+
}
|
|
1857
|
+
if (pt?.api_url) {
|
|
1858
|
+
lines.push(`export POME_STRIPE_API_BASE=${JSON.stringify(pt.api_url)}`);
|
|
1859
|
+
}
|
|
1860
|
+
} else if (twin === "slack") {
|
|
1861
|
+
lines.push(`export POME_SLACK_TOKEN=${JSON.stringify(res.agent_token)}`);
|
|
1862
|
+
} else if (twin === "gmail") {
|
|
1863
|
+
lines.push(`export POME_GMAIL_TOKEN=${JSON.stringify(res.agent_token)}`);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
return `${lines.join("\n")}
|
|
1867
|
+
`;
|
|
1868
|
+
}
|
|
1869
|
+
function normalizeSessionTwins(raw) {
|
|
1870
|
+
const twins = raw.map((t) => t.trim().toLowerCase()).filter((t) => t.length > 0);
|
|
1871
|
+
for (const twin of twins) {
|
|
1872
|
+
if (!ALLOWED_TWINS.has(twin)) {
|
|
1873
|
+
throw new Error(
|
|
1874
|
+
`Unknown twin "${twin}". Supported: ${[...ALLOWED_TWINS].join(", ")}.`
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
const deduped = [...new Set(twins)];
|
|
1879
|
+
if (deduped.length === 0) {
|
|
1880
|
+
throw new Error(
|
|
1881
|
+
`No twin specified. Pass --twin <name> (repeat for multi-twin). Supported: ${[...ALLOWED_TWINS].join(", ")}.`
|
|
1882
|
+
);
|
|
1883
|
+
}
|
|
1884
|
+
return deduped;
|
|
1885
|
+
}
|
|
1886
|
+
async function resolveSessionSeed(seedPath, requestedTwins) {
|
|
1887
|
+
const raw = readSeedFileText(seedPath, "pome sandbox create --seed");
|
|
1888
|
+
const origin = `--seed ${seedPath}`;
|
|
1889
|
+
const file = parseSeedFileText(raw, origin);
|
|
1890
|
+
const sole = soleTwinOf(file);
|
|
1891
|
+
if (requestedTwins.length === 0 && sole === void 0) {
|
|
1892
|
+
const named = twinsNamedBy(file);
|
|
1893
|
+
throw new Error(
|
|
1894
|
+
named.length === 0 ? `${origin} is a flat seed, so it does not name a twin. Pass the name: pome sandbox create --twin <${SESSION_TWIN_NAMES.join("|")}> --seed ${seedPath}` : `${origin} names ${named.length} twins (${named.join(", ")}), so it does not say which the sandbox is for. Pass the name: ${named.map((twin) => `--twin ${twin}`).join(" ")} --seed ${seedPath}`
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1897
|
+
const twins = normalizeSessionTwins(
|
|
1898
|
+
requestedTwins.length > 0 ? requestedTwins : [sole]
|
|
1899
|
+
);
|
|
1900
|
+
const byTwin = await seedsForTwins(file, twins, origin);
|
|
1901
|
+
return {
|
|
1902
|
+
twins,
|
|
1903
|
+
seed: isMultiTwinSeedEnvelope(twins) ? byTwin : byTwin[twins[0]]
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
async function runSessionCreate(opts) {
|
|
1907
|
+
const resolvedSeed = opts.seedPath === void 0 ? void 0 : await resolveSessionSeed(opts.seedPath, opts.twins);
|
|
1908
|
+
const twins = resolvedSeed?.twins ?? normalizeSessionTwins(opts.twins);
|
|
1909
|
+
const creds = await resolveCredentials({ apiBaseUrl: opts.apiBaseUrl });
|
|
1910
|
+
const client = createHostedClient({
|
|
1911
|
+
baseUrl: creds.apiBaseUrl,
|
|
1912
|
+
apiKey: creds.apiKey
|
|
1913
|
+
});
|
|
1914
|
+
const identity = await resolveRunAgentIdentity({
|
|
1915
|
+
startDir: process.cwd(),
|
|
1916
|
+
apiBaseUrl: creds.apiBaseUrl
|
|
1917
|
+
});
|
|
1918
|
+
const session = await client.createSession({
|
|
1919
|
+
twins,
|
|
1920
|
+
taskSource: "# ..\n",
|
|
1921
|
+
idempotencyKey: randomUUID(),
|
|
1922
|
+
agentId: identity.agentId,
|
|
1923
|
+
agentVersion: identity.agentVersion,
|
|
1924
|
+
...resolvedSeed ? { seed: resolvedSeed.seed } : {}
|
|
1925
|
+
});
|
|
1926
|
+
if (opts.secretsFile) {
|
|
1927
|
+
await writeSecretsFile(opts.secretsFile, formatEnvExport(session, twins));
|
|
1928
|
+
console.error(`Wrote sandbox secrets to ${opts.secretsFile} (mode 0600).`);
|
|
1929
|
+
}
|
|
1930
|
+
if (opts.json) {
|
|
1931
|
+
const payload = redactSession(session);
|
|
1932
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
console.error(`Sandbox: ${session.session_id}`);
|
|
1936
|
+
console.error(`Expires: ${session.expires_at}`);
|
|
1937
|
+
if (opts.seedPath !== void 0) {
|
|
1938
|
+
console.error(`Seed: ${opts.seedPath} (replaces the default for ${twins.join(", ")}).`);
|
|
1939
|
+
}
|
|
1940
|
+
let printedAny = false;
|
|
1941
|
+
const mcpFromCloud = perTwinReturnedByCloud(session);
|
|
1942
|
+
for (const twin of twins) {
|
|
1943
|
+
const pt = session.per_twin?.[twin];
|
|
1944
|
+
if (pt) {
|
|
1945
|
+
const label = twins.length > 1 ? `${twin} ` : "";
|
|
1946
|
+
const mcpUrl = mcpFromCloud ? ensureMcpSuffix(pt.mcp_url) : ensureMcpSuffix(pt.api_url);
|
|
1947
|
+
console.error(`${label}API: ${pt.api_url}`);
|
|
1948
|
+
console.error(`${label}MCP: ${mcpUrl}`);
|
|
1949
|
+
printedAny = true;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
if (!printedAny) {
|
|
1953
|
+
console.error(`Twin URL: ${session.twin_url}`);
|
|
1954
|
+
}
|
|
1955
|
+
console.error("Secrets redacted.");
|
|
1956
|
+
console.error(`Dashboard: ${DEFAULT_DASHBOARD_URL}/twins/${session.session_id}`);
|
|
1957
|
+
}
|
|
1958
|
+
async function writeSecretsFile(path, contents) {
|
|
1959
|
+
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
1960
|
+
await writeFile(path, contents, { mode: 384 });
|
|
1961
|
+
await chmod(path, 384);
|
|
1962
|
+
}
|
|
1963
|
+
function displayState(serverState) {
|
|
1964
|
+
if (serverState === "ready") return "running";
|
|
1965
|
+
return serverState;
|
|
1966
|
+
}
|
|
1967
|
+
function matchesStateFilter(serverState, filter) {
|
|
1968
|
+
if (filter === "all") return true;
|
|
1969
|
+
if (filter === "running") return serverState === "ready" || serverState === "running";
|
|
1970
|
+
return serverState === filter;
|
|
1971
|
+
}
|
|
1972
|
+
async function runSessionList(opts) {
|
|
1973
|
+
const creds = await resolveCredentials({ apiBaseUrl: opts.apiBaseUrl });
|
|
1974
|
+
const client = createHostedClient({
|
|
1975
|
+
baseUrl: creds.apiBaseUrl,
|
|
1976
|
+
apiKey: creds.apiKey
|
|
1977
|
+
});
|
|
1978
|
+
const fetchLimit = opts.state === "all" ? opts.limit : Math.min(opts.limit * 4, 200);
|
|
1979
|
+
const all = await client.listSessions({ limit: fetchLimit });
|
|
1980
|
+
const filtered = all.filter((r) => matchesStateFilter(r.state, opts.state));
|
|
1981
|
+
const rows = filtered.slice(0, opts.limit);
|
|
1982
|
+
if (opts.json) {
|
|
1983
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
if (rows.length === 0) {
|
|
1987
|
+
const scope = opts.state === "all" ? "any" : `state=${opts.state}`;
|
|
1988
|
+
console.error(`No sandboxes returned (${scope}).`);
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
for (const r of rows) {
|
|
1992
|
+
console.error(
|
|
1993
|
+
`${r.id} ${displayState(r.state)} ${r.twins.join(",")} expires ${r.expires_at}`
|
|
1994
|
+
);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
async function runSessionStop(opts) {
|
|
1998
|
+
const creds = await resolveCredentials({ apiBaseUrl: opts.apiBaseUrl });
|
|
1999
|
+
const client = createHostedClient({
|
|
2000
|
+
baseUrl: creds.apiBaseUrl,
|
|
2001
|
+
apiKey: creds.apiKey
|
|
2002
|
+
});
|
|
2003
|
+
try {
|
|
2004
|
+
await client.deleteSession(opts.sessionId, false, {
|
|
2005
|
+
discard: opts.discard === true
|
|
2006
|
+
});
|
|
2007
|
+
} catch (err) {
|
|
2008
|
+
if (err instanceof HostedDiscardRefusedError) {
|
|
2009
|
+
console.error(
|
|
2010
|
+
`Refused to stop ${err.sessionId}: it is still open (${err.state}), so its run has not been graded \u2014 Pome creates the run row at finalize, and stopping now discards it.`
|
|
2011
|
+
);
|
|
2012
|
+
if (err.taskName) console.error(` Task: ${err.taskName}`);
|
|
2013
|
+
console.error(` Open for ${err.openSeconds}s.`);
|
|
2014
|
+
console.error(
|
|
2015
|
+
` To keep the run, finalize it instead. To discard it anyway: pome sandbox stop ${err.sessionId} --discard`
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
2018
|
+
throw err;
|
|
2019
|
+
}
|
|
2020
|
+
console.error(`Stopped sandbox ${opts.sessionId}.`);
|
|
2021
|
+
}
|
|
2022
|
+
function friendlyHostedError(err) {
|
|
2023
|
+
if (err instanceof HostedDiscardRefusedError) {
|
|
2024
|
+
return "";
|
|
2025
|
+
}
|
|
2026
|
+
if (err instanceof HostedAuthError) {
|
|
2027
|
+
return `${err.message} \xB7 Run \`pome login\` or set a valid POME_API_KEY.`;
|
|
2028
|
+
}
|
|
2029
|
+
if (err instanceof HostedQuotaError) {
|
|
2030
|
+
return `${err.message} \xB7 Quota or billing limit \u2014 check your team plan in the dashboard.`;
|
|
2031
|
+
}
|
|
2032
|
+
if (err instanceof HostedOrchError) {
|
|
2033
|
+
const m = err.message;
|
|
2034
|
+
if (/422|validation/i.test(m)) {
|
|
2035
|
+
return `${m} \xB7 Check twin name and request body.`;
|
|
2036
|
+
}
|
|
2037
|
+
if (/503|spawn/i.test(m)) {
|
|
2038
|
+
return `${m} \xB7 Control plane could not start the sandbox \u2014 retry later.`;
|
|
2039
|
+
}
|
|
2040
|
+
return m;
|
|
2041
|
+
}
|
|
2042
|
+
if (err instanceof Error) return err.message;
|
|
2043
|
+
return String(err);
|
|
2044
|
+
}
|
|
1218
2045
|
|
|
1219
2046
|
// ../packages/wire/dist/run-completeness.js
|
|
1220
2047
|
var PRE_SATISFIED_REASON = "already_true_in_seed";
|
|
@@ -1330,36 +2157,186 @@ function runScoreLine(score, passThreshold, unevaluatedNumericLabel) {
|
|
|
1330
2157
|
}
|
|
1331
2158
|
return `score: ${score.satisfaction}/100`;
|
|
1332
2159
|
}
|
|
1333
|
-
async function
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
const
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
);
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
2160
|
+
async function runAgentCommand(input) {
|
|
2161
|
+
return new Promise((resolve) => {
|
|
2162
|
+
const inheritAllEnv = process.env.POME_INHERIT_AGENT_ENV === "1";
|
|
2163
|
+
const useShell = process.env.POME_TRUST_AGENT_COMMAND === "1";
|
|
2164
|
+
const childEnv = buildAgentEnv(input.env, inheritAllEnv, Boolean(input.preflight));
|
|
2165
|
+
const command = useShell ? null : splitCommand(input.command);
|
|
2166
|
+
if (!useShell && !command) {
|
|
2167
|
+
resolve({
|
|
2168
|
+
stdout: "",
|
|
2169
|
+
stderr: "Could not parse agent command. Use POME_TRUST_AGENT_COMMAND=1 to run it through a shell.\n",
|
|
2170
|
+
exitCode: 2,
|
|
2171
|
+
timedOut: false
|
|
2172
|
+
});
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
if (useShell) {
|
|
2176
|
+
console.warn("[pome] POME_TRUST_AGENT_COMMAND=1: running agent command through a shell.");
|
|
2177
|
+
}
|
|
2178
|
+
if (inheritAllEnv) {
|
|
2179
|
+
console.warn("[pome] POME_INHERIT_AGENT_ENV=1: passing the full parent environment to the agent.");
|
|
2180
|
+
}
|
|
2181
|
+
const child = useShell ? spawn(input.command, {
|
|
2182
|
+
shell: true,
|
|
2183
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2184
|
+
env: childEnv
|
|
2185
|
+
}) : spawn(command.file, command.args, {
|
|
2186
|
+
shell: false,
|
|
2187
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2188
|
+
env: childEnv
|
|
2189
|
+
});
|
|
2190
|
+
let stdout = "";
|
|
2191
|
+
let stderr = "";
|
|
2192
|
+
let timedOut = false;
|
|
2193
|
+
let settled = false;
|
|
2194
|
+
const timer = setTimeout(() => {
|
|
2195
|
+
timedOut = true;
|
|
2196
|
+
child.kill("SIGTERM");
|
|
2197
|
+
setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
|
|
2198
|
+
}, input.timeoutSeconds * 1e3);
|
|
2199
|
+
child.stdout?.on("data", (chunk) => {
|
|
2200
|
+
stdout += chunk.toString();
|
|
2201
|
+
});
|
|
2202
|
+
child.stderr?.on("data", (chunk) => {
|
|
2203
|
+
stderr += chunk.toString();
|
|
2204
|
+
});
|
|
2205
|
+
child.on("error", (error) => {
|
|
2206
|
+
if (settled) return;
|
|
2207
|
+
settled = true;
|
|
2208
|
+
clearTimeout(timer);
|
|
2209
|
+
resolve({
|
|
2210
|
+
stdout,
|
|
2211
|
+
stderr: `${stderr}${error.message}
|
|
2212
|
+
`,
|
|
2213
|
+
exitCode: 2,
|
|
2214
|
+
timedOut
|
|
2215
|
+
});
|
|
2216
|
+
});
|
|
2217
|
+
child.on("close", (exitCode) => {
|
|
2218
|
+
if (settled) return;
|
|
2219
|
+
settled = true;
|
|
2220
|
+
clearTimeout(timer);
|
|
2221
|
+
resolve({ stdout, stderr, exitCode, timedOut });
|
|
2222
|
+
});
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2225
|
+
var SAFE_PARENT_ENV = /* @__PURE__ */ new Set([
|
|
2226
|
+
"PATH",
|
|
2227
|
+
"HOME",
|
|
2228
|
+
"USERPROFILE",
|
|
2229
|
+
"SystemRoot",
|
|
2230
|
+
"COMSPEC",
|
|
2231
|
+
"TMPDIR",
|
|
2232
|
+
"TEMP",
|
|
2233
|
+
"TMP",
|
|
2234
|
+
"LANG",
|
|
2235
|
+
"LC_ALL"
|
|
2236
|
+
]);
|
|
2237
|
+
var DEFAULT_AGENT_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
2238
|
+
"AI_GATEWAY_API_KEY",
|
|
2239
|
+
"ANTHROPIC_API_KEY",
|
|
2240
|
+
// Claude subscription auth (`claude setup-token`). Without
|
|
2241
|
+
// this the Claude Agent SDK inside the agent subprocess only sees an API
|
|
2242
|
+
// key, and subscription-only users fail auth under `pome run` while the
|
|
2243
|
+
// same agent works when launched by hand.
|
|
2244
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
2245
|
+
"GOOGLE_API_KEY",
|
|
2246
|
+
"GOOGLE_GENERATIVE_AI_API_KEY",
|
|
2247
|
+
"OPENAI_API_KEY",
|
|
2248
|
+
"OPENROUTER_API_KEY"
|
|
2249
|
+
]);
|
|
2250
|
+
function buildAgentEnv(inputEnv, inheritAll, preflight) {
|
|
2251
|
+
const base = {};
|
|
2252
|
+
if (inheritAll) {
|
|
2253
|
+
Object.assign(base, process.env);
|
|
2254
|
+
} else {
|
|
2255
|
+
for (const key of [...SAFE_PARENT_ENV, ...DEFAULT_AGENT_ENV_ALLOWLIST]) {
|
|
2256
|
+
const value = process.env[key];
|
|
2257
|
+
if (value !== void 0) base[key] = value;
|
|
2258
|
+
}
|
|
2259
|
+
for (const key of (process.env.POME_AGENT_ENV_ALLOWLIST ?? "").split(",")) {
|
|
2260
|
+
const trimmed = key.trim();
|
|
2261
|
+
if (!trimmed) continue;
|
|
2262
|
+
const value = process.env[trimmed];
|
|
2263
|
+
if (value !== void 0) base[trimmed] = value;
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
return {
|
|
2267
|
+
...base,
|
|
2268
|
+
...inputEnv,
|
|
2269
|
+
...preflight ? { POME_PREFLIGHT: "1" } : {}
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
function splitCommand(command) {
|
|
2273
|
+
const parts = [];
|
|
2274
|
+
let current = "";
|
|
2275
|
+
let quote = null;
|
|
2276
|
+
let escaping = false;
|
|
2277
|
+
for (const char of command.trim()) {
|
|
2278
|
+
if (escaping) {
|
|
2279
|
+
current += char;
|
|
2280
|
+
escaping = false;
|
|
2281
|
+
continue;
|
|
2282
|
+
}
|
|
2283
|
+
if (char === "\\") {
|
|
2284
|
+
escaping = true;
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
if (quote) {
|
|
2288
|
+
if (char === quote) quote = null;
|
|
2289
|
+
else current += char;
|
|
2290
|
+
continue;
|
|
2291
|
+
}
|
|
2292
|
+
if (char === "'" || char === '"') {
|
|
2293
|
+
quote = char;
|
|
2294
|
+
continue;
|
|
2295
|
+
}
|
|
2296
|
+
if (/\s/.test(char)) {
|
|
2297
|
+
if (current.length > 0) {
|
|
2298
|
+
parts.push(current);
|
|
2299
|
+
current = "";
|
|
2300
|
+
}
|
|
2301
|
+
continue;
|
|
2302
|
+
}
|
|
2303
|
+
current += char;
|
|
2304
|
+
}
|
|
2305
|
+
if (escaping || quote) return null;
|
|
2306
|
+
if (current.length > 0) parts.push(current);
|
|
2307
|
+
const [file, ...args] = parts;
|
|
2308
|
+
return file ? { file, args } : null;
|
|
2309
|
+
}
|
|
2310
|
+
async function putBlob(url, body, contentType, label) {
|
|
2311
|
+
const ctrl = new AbortController();
|
|
2312
|
+
const timer = setTimeout(() => ctrl.abort(), 3e4);
|
|
2313
|
+
try {
|
|
2314
|
+
const putRes = await fetch(url, {
|
|
2315
|
+
method: "PUT",
|
|
2316
|
+
headers: {
|
|
2317
|
+
"content-type": contentType,
|
|
2318
|
+
"content-encoding": "gzip"
|
|
2319
|
+
},
|
|
2320
|
+
body: gzipSync(Buffer.from(body, "utf8")),
|
|
2321
|
+
signal: ctrl.signal
|
|
2322
|
+
});
|
|
2323
|
+
if (!putRes.ok) {
|
|
2324
|
+
console.warn(
|
|
2325
|
+
`[pome] ${label} upload PUT ${url} \u2192 ${putRes.status}; continuing with key=null`
|
|
2326
|
+
);
|
|
2327
|
+
return false;
|
|
2328
|
+
}
|
|
2329
|
+
return true;
|
|
2330
|
+
} finally {
|
|
2331
|
+
clearTimeout(timer);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
async function uploadRunBlobs(client, sessionId, blobs) {
|
|
2335
|
+
async function uploadEvents() {
|
|
2336
|
+
try {
|
|
2337
|
+
const upload = await client.requestEventsUploadUrl(sessionId);
|
|
2338
|
+
const ok = await putBlob(
|
|
2339
|
+
upload.url,
|
|
1363
2340
|
blobs.eventsJsonl,
|
|
1364
2341
|
"application/x-ndjson",
|
|
1365
2342
|
"events.jsonl"
|
|
@@ -1535,155 +2512,411 @@ function redactJsonl(body) {
|
|
|
1535
2512
|
return redacted.length > 0 ? `${redacted.join("\n")}
|
|
1536
2513
|
` : "";
|
|
1537
2514
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
2515
|
+
|
|
2516
|
+
// src/runner/runTaskHosted.ts
|
|
2517
|
+
function buildAgentEnv2(params) {
|
|
2518
|
+
const {
|
|
2519
|
+
session,
|
|
2520
|
+
twins,
|
|
2521
|
+
perTwinFromCloud,
|
|
2522
|
+
prompt,
|
|
2523
|
+
otlpEndpoint,
|
|
2524
|
+
apiKey,
|
|
2525
|
+
agentId,
|
|
2526
|
+
runId,
|
|
2527
|
+
artifactsDir,
|
|
2528
|
+
slug,
|
|
2529
|
+
signalsPath
|
|
2530
|
+
} = params;
|
|
2531
|
+
const env = {
|
|
2532
|
+
POME_TASK: prompt,
|
|
2533
|
+
POME_TWIN_NAMES: twins.join(","),
|
|
2534
|
+
POME_OTEL_EXPORTER_OTLP_ENDPOINT: otlpEndpoint,
|
|
2535
|
+
POME_OTEL_EXPORTER_OTLP_HEADERS: `x-api-key=${apiKey}`,
|
|
2536
|
+
OTEL_SERVICE_NAME: agentId ?? "pome-agent",
|
|
2537
|
+
OTEL_RESOURCE_ATTRIBUTES: `pome.session_id=${session.session_id},pome.run_id=${runId}${agentId ? `,pome.agent_id=${agentId}` : ""}`,
|
|
2538
|
+
// The twin base URL, same value the self-host docs teach agents to fall
|
|
2539
|
+
// back on. Agent code written against the standalone docker twin
|
|
2540
|
+
// reads POME_TWIN_BASE_URL with a 127.0.0.1:3333 fallback; without this
|
|
2541
|
+
// injection that fallback fires on hosted runs and the agent probes a
|
|
2542
|
+
// loopback port nothing listens on. Legacy = the primary twin's bare url.
|
|
2543
|
+
POME_TWIN_BASE_URL: session.twin_url,
|
|
2544
|
+
// Legacy unconditional block (origin/main parity). Injected on EVERY hosted
|
|
2545
|
+
// run regardless of twin; the per-twin overlay below overwrites these in
|
|
2546
|
+
// place for github/stripe when those twins are in the session.
|
|
2547
|
+
POME_GITHUB_REST_URL: session.twin_url,
|
|
2548
|
+
POME_GITHUB_MCP_URL: `${session.twin_url}/mcp`,
|
|
2549
|
+
POME_GITHUB_TOKEN: session.provider_credentials.github?.token ?? session.agent_token,
|
|
2550
|
+
POME_STRIPE_API_BASE: session.twin_url,
|
|
2551
|
+
// Always use the JWT agent_token as the Stripe SDK's Bearer in hosted mode.
|
|
2552
|
+
// The cloud also returns `provider_credentials.stripe.api_key` (an
|
|
2553
|
+
// HMAC-signed `sk_test_pome_*`), but the pome-cloud proxy only accepts
|
|
2554
|
+
// JWTs — it routes by verifying the bearer against `agent_token`. A
|
|
2555
|
+
// stripe-style key short-circuits to the proxy's opaque 404 before the
|
|
2556
|
+
// request reaches the twin pod. The Stripe SDK sends any opaque string as
|
|
2557
|
+
// `Authorization: Bearer …`, and twin-stripe's dual-auth accepts JWTs
|
|
2558
|
+
// natively, so the JWT round-trips cleanly end-to-end.
|
|
2559
|
+
POME_STRIPE_API_KEY: session.agent_token,
|
|
2560
|
+
POME_AUTH_TOKEN: session.agent_token,
|
|
2561
|
+
POME_RUN_ID: runId,
|
|
2562
|
+
POME_ARTIFACTS_DIR: join(artifactsDir, slug, runId),
|
|
2563
|
+
POME_ADAPTER_SIGNALS_PATH: signalsPath
|
|
2564
|
+
};
|
|
2565
|
+
for (const twin of twins) {
|
|
2566
|
+
const upper = twin.toUpperCase();
|
|
2567
|
+
const pt = session.per_twin?.[twin];
|
|
2568
|
+
const apiUrl = pt?.api_url ?? session.twin_url;
|
|
2569
|
+
const mcpUrl = perTwinFromCloud && pt?.mcp_url ? ensureMcpSuffix(pt.mcp_url) : `${apiUrl.replace(/\/$/, "")}/mcp`;
|
|
2570
|
+
env[`POME_${upper}_REST_URL`] = apiUrl;
|
|
2571
|
+
env[`POME_${upper}_MCP_URL`] = mcpUrl;
|
|
2572
|
+
if (twin === "github") {
|
|
2573
|
+
env.POME_GITHUB_TOKEN = session.provider_credentials.github?.token ?? session.agent_token;
|
|
2574
|
+
} else if (twin === "stripe") {
|
|
2575
|
+
env.POME_STRIPE_API_BASE = apiUrl;
|
|
2576
|
+
env.POME_STRIPE_API_KEY = session.agent_token;
|
|
2577
|
+
} else if (twin === "slack") {
|
|
2578
|
+
env.POME_SLACK_TOKEN = session.agent_token;
|
|
2579
|
+
} else if (twin === "gmail") {
|
|
2580
|
+
env.POME_GMAIL_TOKEN = session.agent_token;
|
|
1555
2581
|
}
|
|
1556
|
-
|
|
1557
|
-
|
|
2582
|
+
}
|
|
2583
|
+
return env;
|
|
2584
|
+
}
|
|
2585
|
+
async function runTaskHosted(options) {
|
|
2586
|
+
const scenario = await parseTaskFile(options.taskPath);
|
|
2587
|
+
const taskSource = await readFile(options.taskPath, "utf8");
|
|
2588
|
+
const taskHash = createHash("sha256").update(taskSource).digest("hex");
|
|
2589
|
+
const artifactsDir = options.artifactsDir ?? "runs";
|
|
2590
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2591
|
+
const client = options.client ?? createHostedClient({ baseUrl: options.hosted.baseUrl, apiKey: options.hosted.apiKey });
|
|
2592
|
+
const identity = await resolveRunAgentIdentity({
|
|
2593
|
+
startDir: dirname(options.taskPath),
|
|
2594
|
+
apiBaseUrl: options.hosted.baseUrl,
|
|
2595
|
+
agentVersionOverride: options.agentVersion
|
|
2596
|
+
});
|
|
2597
|
+
const agentId = identity.agentId;
|
|
2598
|
+
const agentVersion = identity.agentVersion;
|
|
2599
|
+
const agentSdk = identity.framework ?? null;
|
|
2600
|
+
const signalsDir = await mkdtemp(join(tmpdir(), "pome-signals-"));
|
|
2601
|
+
const signalsPath = join(signalsDir, `${randomUUID()}.jsonl`);
|
|
2602
|
+
await writeFile(signalsPath, "");
|
|
2603
|
+
const session = await createSessionOrExplain(
|
|
2604
|
+
client,
|
|
2605
|
+
options.premintedSession,
|
|
2606
|
+
{
|
|
2607
|
+
taskSource,
|
|
2608
|
+
twins: scenario.config.twins,
|
|
2609
|
+
agentId,
|
|
2610
|
+
agentVersion,
|
|
2611
|
+
seed: scenario.seedState
|
|
1558
2612
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
const
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
2613
|
+
);
|
|
2614
|
+
const twins = scenario.config.twins;
|
|
2615
|
+
const primaryTwin = twins[0];
|
|
2616
|
+
const isMultiTwin = twins.length > 1;
|
|
2617
|
+
const twinApiUrl = (twin) => session.per_twin?.[twin]?.api_url ?? session.twin_url;
|
|
2618
|
+
const runId = session.session_id;
|
|
2619
|
+
try {
|
|
2620
|
+
const stateInitialByTwin = await fetchStateForTwins(
|
|
2621
|
+
client,
|
|
2622
|
+
twins,
|
|
2623
|
+
twinApiUrl,
|
|
2624
|
+
session.agent_token
|
|
2625
|
+
);
|
|
2626
|
+
const stateInitial = stateInitialByTwin[primaryTwin];
|
|
2627
|
+
const otlpEndpoint = process.env.POME_OTEL_COLLECTOR_URL?.trim() || new URL(
|
|
2628
|
+
`/v1/sessions/${session.session_id}/traces`,
|
|
2629
|
+
options.hosted.baseUrl
|
|
2630
|
+
).toString();
|
|
2631
|
+
const env = buildAgentEnv2({
|
|
2632
|
+
session,
|
|
2633
|
+
twins,
|
|
2634
|
+
perTwinFromCloud: perTwinReturnedByCloud(session),
|
|
2635
|
+
prompt: scenario.prompt,
|
|
2636
|
+
otlpEndpoint,
|
|
2637
|
+
apiKey: options.hosted.apiKey,
|
|
2638
|
+
agentId,
|
|
2639
|
+
runId,
|
|
2640
|
+
artifactsDir,
|
|
2641
|
+
slug: scenario.slug,
|
|
2642
|
+
signalsPath
|
|
1579
2643
|
});
|
|
1580
|
-
|
|
1581
|
-
|
|
2644
|
+
const preflightTimeoutSeconds = Math.min(10, scenario.config.timeout);
|
|
2645
|
+
const preflight = await runAgentCommand({
|
|
2646
|
+
command: options.agentCommand,
|
|
2647
|
+
env,
|
|
2648
|
+
timeoutSeconds: preflightTimeoutSeconds,
|
|
2649
|
+
preflight: true
|
|
1582
2650
|
});
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
`,
|
|
1591
|
-
exitCode: 2,
|
|
1592
|
-
timedOut
|
|
2651
|
+
let agentResult = preflight;
|
|
2652
|
+
if (preflight.exitCode === 0) {
|
|
2653
|
+
await writeFile(signalsPath, "");
|
|
2654
|
+
agentResult = await runAgentCommand({
|
|
2655
|
+
command: options.agentCommand,
|
|
2656
|
+
env,
|
|
2657
|
+
timeoutSeconds: scenario.config.timeout
|
|
1593
2658
|
});
|
|
2659
|
+
}
|
|
2660
|
+
if (options.abandonOnFailure && (agentResult.timedOut || agentResult.exitCode !== 0)) {
|
|
2661
|
+
const failed = preflight.exitCode !== 0 ? preflight : agentResult;
|
|
2662
|
+
const cause = stderrTail(failed.stderr);
|
|
2663
|
+
const failure = preflight.exitCode !== 0 ? {
|
|
2664
|
+
errorCode: "preflight_failed",
|
|
2665
|
+
reason: preflight.timedOut ? `agent preflight timed out after ${preflightTimeoutSeconds}s` : `agent preflight failed${cause ? ` \u2014 ${cause}` : ""}`
|
|
2666
|
+
} : agentResult.timedOut ? {
|
|
2667
|
+
errorCode: "agent_timeout",
|
|
2668
|
+
reason: `agent timed out after ${scenario.config.timeout}s`
|
|
2669
|
+
} : {
|
|
2670
|
+
errorCode: "agent_exit_nonzero",
|
|
2671
|
+
reason: `agent exited non-zero (exit ${agentResult.exitCode})${cause ? ` \u2014 ${cause}` : ""}`
|
|
2672
|
+
};
|
|
2673
|
+
try {
|
|
2674
|
+
const runDir = join(artifactsDir, scenario.slug, runId);
|
|
2675
|
+
await mkdir(runDir, { recursive: true });
|
|
2676
|
+
await writeFile(join(runDir, "stdout.txt"), redactSecrets(failed.stdout));
|
|
2677
|
+
await writeFile(join(runDir, "stderr.log"), redactSecrets(failed.stderr));
|
|
2678
|
+
} catch {
|
|
2679
|
+
}
|
|
2680
|
+
await abandonBestEffort(client, session.session_id, failure.errorCode);
|
|
2681
|
+
throw new HostedTrialError(failure.reason, failure.errorCode);
|
|
2682
|
+
}
|
|
2683
|
+
const stateFinalByTwin = {};
|
|
2684
|
+
const eventsByTwin = {};
|
|
2685
|
+
await Promise.all(
|
|
2686
|
+
twins.map(async (twin) => {
|
|
2687
|
+
const [sf, ev] = await Promise.all([
|
|
2688
|
+
client.fetchState({ twinUrl: twinApiUrl(twin), agentToken: session.agent_token }),
|
|
2689
|
+
client.fetchEvents({ twinUrl: twinApiUrl(twin), agentToken: session.agent_token })
|
|
2690
|
+
]);
|
|
2691
|
+
stateFinalByTwin[twin] = sf;
|
|
2692
|
+
eventsByTwin[twin] = isMultiTwin ? ev.map((e) => tagEventTwin(e, twin)) : ev;
|
|
2693
|
+
})
|
|
2694
|
+
);
|
|
2695
|
+
const stateFinal = stateFinalByTwin[primaryTwin];
|
|
2696
|
+
const events = isMultiTwin ? mergeEventsByTimestamp(twins.flatMap((t) => eventsByTwin[t] ?? [])) : eventsByTwin[primaryTwin] ?? [];
|
|
2697
|
+
const legacyEvents = events;
|
|
2698
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2699
|
+
const durationMs = Date.parse(completedAt) - Date.parse(startedAt);
|
|
2700
|
+
const artifacts = await writeRunArtifactsCore({
|
|
2701
|
+
artifactsDir,
|
|
2702
|
+
runId,
|
|
2703
|
+
scenario,
|
|
2704
|
+
startedAt,
|
|
2705
|
+
completedAt,
|
|
2706
|
+
stdout: agentResult.stdout,
|
|
2707
|
+
stderr: agentResult.stderr,
|
|
2708
|
+
exitCode: agentResult.exitCode,
|
|
2709
|
+
events: legacyEvents,
|
|
2710
|
+
stateInitial,
|
|
2711
|
+
stateFinal
|
|
1594
2712
|
});
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
2713
|
+
if (isMultiTwin) {
|
|
2714
|
+
for (const twin of twins) {
|
|
2715
|
+
if (twin === primaryTwin) continue;
|
|
2716
|
+
try {
|
|
2717
|
+
await writeFile(
|
|
2718
|
+
join(artifacts.runDir, `state_final.${twin}.json`),
|
|
2719
|
+
`${JSON.stringify(redactSecrets(stateFinalByTwin[twin]), null, 2)}
|
|
2720
|
+
`
|
|
2721
|
+
);
|
|
2722
|
+
} catch (err) {
|
|
2723
|
+
console.warn(
|
|
2724
|
+
`[pome] state_final.${twin}.json not written (${err instanceof Error ? err.message : String(err)})`
|
|
2725
|
+
);
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
const eventsJsonl = legacyEvents.map((e) => JSON.stringify(redactEvent(toTwinHttpEvent(e)))).join("\n") + "\n";
|
|
2730
|
+
const stateInitialJson = JSON.stringify(redactSecrets(stateInitial));
|
|
2731
|
+
const stateFinalJson = JSON.stringify(redactSecrets(stateFinal));
|
|
2732
|
+
let signalsJsonl = "";
|
|
2733
|
+
try {
|
|
2734
|
+
signalsJsonl = redactJsonl(
|
|
2735
|
+
await readFile(signalsPath, "utf8").catch(() => "")
|
|
2736
|
+
);
|
|
2737
|
+
} catch (err) {
|
|
2738
|
+
console.warn(
|
|
2739
|
+
`[pome] signals.jsonl upload skipped (${err instanceof Error ? err.message : String(err)}); continuing with signals_storage_key=null`
|
|
2740
|
+
);
|
|
2741
|
+
}
|
|
2742
|
+
const metaJson = await readFile(join(artifacts.runDir, "meta.json"), "utf8").catch(
|
|
2743
|
+
() => "{}"
|
|
2744
|
+
);
|
|
2745
|
+
const perTwinState = isMultiTwin ? Object.fromEntries(
|
|
2746
|
+
twins.map((twin) => [
|
|
2747
|
+
twin,
|
|
2748
|
+
{
|
|
2749
|
+
initialJson: JSON.stringify(redactSecrets(stateInitialByTwin[twin])),
|
|
2750
|
+
finalJson: JSON.stringify(redactSecrets(stateFinalByTwin[twin]))
|
|
2751
|
+
}
|
|
2752
|
+
])
|
|
2753
|
+
) : void 0;
|
|
2754
|
+
const uploaded = await uploadRunBlobs(client, session.session_id, {
|
|
2755
|
+
eventsJsonl,
|
|
2756
|
+
stateInitialJson,
|
|
2757
|
+
stateFinalJson,
|
|
2758
|
+
signalsJsonl,
|
|
2759
|
+
metaJson,
|
|
2760
|
+
twins: isMultiTwin ? twins : void 0,
|
|
2761
|
+
perTwinState
|
|
1600
2762
|
});
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
2763
|
+
const eventsJsonlUrl = uploaded.eventsKey;
|
|
2764
|
+
const stateKeys = {
|
|
2765
|
+
initialKey: uploaded.stateInitialKey,
|
|
2766
|
+
finalKey: uploaded.stateFinalKey
|
|
2767
|
+
};
|
|
2768
|
+
const signalsKey = uploaded.signalsKey;
|
|
2769
|
+
const criteriaDefs = scenario.criteria.map((c, idx) => ({
|
|
2770
|
+
id: `crit_${idx}`,
|
|
2771
|
+
text: c.text,
|
|
2772
|
+
kind: c.type,
|
|
2773
|
+
...c.twin ? { twin: c.twin } : {},
|
|
2774
|
+
...c.alwaysScored ? { always_scored: true } : {}
|
|
2775
|
+
}));
|
|
2776
|
+
const stopReason = agentResult.timedOut ? "agent_timeout" : agentResult.exitCode === 0 ? "agent_exit_0" : "agent_exit_nonzero";
|
|
2777
|
+
const finalized = await client.finalize(session.session_id, {
|
|
2778
|
+
stopReason,
|
|
2779
|
+
exitCode: agentResult.exitCode ?? 0,
|
|
2780
|
+
durationMs,
|
|
2781
|
+
agentModel: options.agentModel ?? "unknown",
|
|
2782
|
+
agentSdk,
|
|
2783
|
+
criteria: criteriaDefs,
|
|
2784
|
+
taskName: scenario.slug,
|
|
2785
|
+
taskHash,
|
|
2786
|
+
taskPrompt: scenario.prompt,
|
|
2787
|
+
expectedBehavior: scenario.expectedBehavior,
|
|
2788
|
+
// Explicit overrides when uploads succeeded; otherwise let cloud fall
|
|
2789
|
+
// back. For events.jsonl the conventional `team-<>/session-<>/events.jsonl`
|
|
2790
|
+
// path matches what /result-upload-url signs, so omitting traceStorageKey
|
|
2791
|
+
// still resolves correctly. For state blobs there is no conventional
|
|
2792
|
+
// fallback today — omitting them means cloud's judge sees
|
|
2793
|
+
// "{}", which we accept as best-effort degradation.
|
|
2794
|
+
traceStorageKey: eventsJsonlUrl ?? void 0,
|
|
2795
|
+
stateInitialStorageKey: stateKeys.initialKey ?? void 0,
|
|
2796
|
+
stateFinalStorageKey: stateKeys.finalKey ?? void 0,
|
|
2797
|
+
signalsStorageKey: signalsKey ?? void 0,
|
|
2798
|
+
// Multi-twin (M3): per-twin state storage keys (>=1 key per entry).
|
|
2799
|
+
// Undefined for single-twin / an older cloud that returned no per_twin
|
|
2800
|
+
// upload block — the cloud then scores the primary twin from the flat keys.
|
|
2801
|
+
perTwinStateKeys: uploaded.perTwinStateKeys
|
|
2802
|
+
});
|
|
2803
|
+
const score = scoreFromFinalizeResponse(finalized);
|
|
2804
|
+
const verdict = scoreStatus(score, scenario.config.passThreshold);
|
|
2805
|
+
const exitCode = verdict === "pass" ? 0 : 1;
|
|
2806
|
+
try {
|
|
2807
|
+
const counts = evaluationCounts(score);
|
|
2808
|
+
await writeVerdictArtifact(artifacts.runDir, {
|
|
2809
|
+
version: VERDICT_ARTIFACT_VERSION,
|
|
2810
|
+
source: "cloud-finalize",
|
|
2811
|
+
task_name: scenario.slug,
|
|
2812
|
+
task_path: options.taskPath,
|
|
2813
|
+
group_id: options.groupId ?? null,
|
|
2814
|
+
session_id: session.session_id,
|
|
2815
|
+
cloud_run_id: finalized.run_id,
|
|
2816
|
+
cloud_dashboard_url: finalized.dashboard_url,
|
|
2817
|
+
judge_model: score.judge_model,
|
|
2818
|
+
score: finalized.score,
|
|
2819
|
+
pass_threshold: scenario.config.passThreshold,
|
|
2820
|
+
state: verdict,
|
|
2821
|
+
passed: exitCode === 0,
|
|
2822
|
+
evaluated: counts.evaluated,
|
|
2823
|
+
not_evaluated: counts.notEvaluated,
|
|
2824
|
+
pre_satisfied: counts.preSatisfied,
|
|
2825
|
+
total: counts.total,
|
|
2826
|
+
criteria_results: score.results,
|
|
2827
|
+
duration_ms: durationMs,
|
|
2828
|
+
finalized_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2829
|
+
});
|
|
2830
|
+
} catch (err) {
|
|
2831
|
+
console.warn(
|
|
2832
|
+
`[pome] verdict.json not written (${err instanceof Error ? err.message : String(err)}); pome fix-prompt won't see this trial`
|
|
2833
|
+
);
|
|
1636
2834
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
2835
|
+
return {
|
|
2836
|
+
scenario,
|
|
2837
|
+
runId,
|
|
2838
|
+
cloudRunId: finalized.run_id,
|
|
2839
|
+
cloudDashboardUrl: finalized.dashboard_url,
|
|
2840
|
+
artifacts,
|
|
2841
|
+
score,
|
|
2842
|
+
verdict,
|
|
2843
|
+
exitCode,
|
|
2844
|
+
durationMs
|
|
2845
|
+
};
|
|
2846
|
+
} catch (err) {
|
|
2847
|
+
if (options.abandonOnFailure && !(err instanceof HostedTrialError)) {
|
|
2848
|
+
await abandonBestEffort(client, session.session_id, "trial_crashed");
|
|
1642
2849
|
}
|
|
2850
|
+
throw err;
|
|
2851
|
+
} finally {
|
|
2852
|
+
await client.deleteSession(session.session_id, true, { discard: true }).catch(() => void 0);
|
|
2853
|
+
await rm(signalsDir, { recursive: true, force: true }).catch(() => void 0);
|
|
1643
2854
|
}
|
|
1644
|
-
return {
|
|
1645
|
-
...base,
|
|
1646
|
-
...inputEnv,
|
|
1647
|
-
...preflight ? { POME_PREFLIGHT: "1" } : {}
|
|
1648
|
-
};
|
|
1649
2855
|
}
|
|
1650
|
-
function
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
escaping = true;
|
|
1663
|
-
continue;
|
|
1664
|
-
}
|
|
1665
|
-
if (quote) {
|
|
1666
|
-
if (char === quote) quote = null;
|
|
1667
|
-
else current += char;
|
|
1668
|
-
continue;
|
|
1669
|
-
}
|
|
1670
|
-
if (char === "'" || char === '"') {
|
|
1671
|
-
quote = char;
|
|
1672
|
-
continue;
|
|
2856
|
+
async function createSessionOrExplain(client, preminted, input) {
|
|
2857
|
+
if (preminted) return preminted;
|
|
2858
|
+
try {
|
|
2859
|
+
return await client.createSession(input);
|
|
2860
|
+
} catch (err) {
|
|
2861
|
+
if (err instanceof HostedOrchError && err.status === 422 && (err.type === "multi_twin_unsupported" || /multi_twin_unsupported/i.test(err.message))) {
|
|
2862
|
+
throw new HostedOrchError(
|
|
2863
|
+
"this pome cloud does not support multi-twin sessions yet",
|
|
2864
|
+
err.requestId,
|
|
2865
|
+
err.status,
|
|
2866
|
+
err.type
|
|
2867
|
+
);
|
|
1673
2868
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
2869
|
+
throw err;
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
async function fetchStateForTwins(client, twins, twinApiUrl, agentToken) {
|
|
2873
|
+
const byTwin = {};
|
|
2874
|
+
await Promise.all(
|
|
2875
|
+
twins.map(async (twin) => {
|
|
2876
|
+
byTwin[twin] = await client.fetchState({
|
|
2877
|
+
twinUrl: twinApiUrl(twin),
|
|
2878
|
+
agentToken
|
|
2879
|
+
});
|
|
2880
|
+
})
|
|
2881
|
+
);
|
|
2882
|
+
return byTwin;
|
|
2883
|
+
}
|
|
2884
|
+
function tagEventTwin(event, twin) {
|
|
2885
|
+
if (event && typeof event === "object") {
|
|
2886
|
+
const existing = event.twin;
|
|
2887
|
+
if (existing === void 0 || existing === null || existing === "") {
|
|
2888
|
+
return { ...event, twin };
|
|
1680
2889
|
}
|
|
1681
|
-
current += char;
|
|
1682
2890
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
2891
|
+
return event;
|
|
2892
|
+
}
|
|
2893
|
+
function mergeEventsByTimestamp(events) {
|
|
2894
|
+
const tsOf = (e) => {
|
|
2895
|
+
const ts = e.ts;
|
|
2896
|
+
return typeof ts === "string" ? ts : "";
|
|
2897
|
+
};
|
|
2898
|
+
return events.map((event, index) => ({ event, index })).sort((a, b) => {
|
|
2899
|
+
const ta = tsOf(a.event);
|
|
2900
|
+
const tb = tsOf(b.event);
|
|
2901
|
+
if (ta < tb) return -1;
|
|
2902
|
+
if (ta > tb) return 1;
|
|
2903
|
+
return a.index - b.index;
|
|
2904
|
+
}).map(({ event }) => event);
|
|
2905
|
+
}
|
|
2906
|
+
function stderrTail(stderr) {
|
|
2907
|
+
const last = stderr.split("\n").map((line) => line.trim()).filter(Boolean).at(-1);
|
|
2908
|
+
if (!last) return null;
|
|
2909
|
+
const flat = redactSecrets(last.replace(/\s+/g, " ")).trim();
|
|
2910
|
+
return flat.length > 160 ? `${flat.slice(0, 157)}\u2026` : flat;
|
|
2911
|
+
}
|
|
2912
|
+
async function abandonBestEffort(client, sessionId, errorCode) {
|
|
2913
|
+
try {
|
|
2914
|
+
await client.abandonSession(sessionId, { errorCode });
|
|
2915
|
+
} catch (err) {
|
|
2916
|
+
console.warn(
|
|
2917
|
+
`[pome] session abandon skipped (${err instanceof Error ? err.message : String(err)}); the expiry sweeper will close ${sessionId}`
|
|
2918
|
+
);
|
|
2919
|
+
}
|
|
1687
2920
|
}
|
|
1688
2921
|
|
|
1689
|
-
export { createHostedClient, criterionPhrase, criterionRowLine,
|
|
2922
|
+
export { DEFAULT_CONTROL_PLANE_URL, DEFAULT_DASHBOARD_URL, DEFAULT_DOCS_SITE_ORIGIN, SESSION_TWIN_NAMES, clearLocalCredentials, createHostedClient, criterionPhrase, criterionRowLine, discoverRunSet, ensurePomeGitignored, friendlyHostedError, isNarrated, isPreSatisfied, narratorReadingLines, outcomeOf, parseGitHubSeedState, parseTaskFile, persistCredentialsAfterLogin, postAgentResolver, readCodeCriteria, readConfigTwins, readLatestRun, readLinkCache, readMetaSummary, redactJsonl, resolveCachedAgentId, resolveCredentials, resolveRunAgentIdentity, resolveSeams, runAgentCommand, runScoreLine, runSessionCreate, runSessionList, runSessionStop, runTaskHosted, scoreCountsSummary, scoreFromFinalizeResponse, scoreStatus, seedStateForTwin, uploadRunBlobs, writeLinkCache, writeRunArtifactsCore };
|