agentv 4.44.0-next.1 → 5.0.0-next.1
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/README.md +2 -2
- package/dist/{artifact-writer-72MQQELS.js → artifact-writer-QD52FC56.js} +12 -10
- package/dist/{chunk-KAGZRUO6.js → chunk-6JVM4GJI.js} +3 -2
- package/dist/chunk-6JVM4GJI.js.map +1 -0
- package/dist/{chunk-2N3YSEMI.js → chunk-6Q56CEXV.js} +333 -99
- package/dist/chunk-6Q56CEXV.js.map +1 -0
- package/dist/{chunk-5DYGN547.js → chunk-GZJMODLW.js} +1008 -663
- package/dist/chunk-GZJMODLW.js.map +1 -0
- package/dist/{chunk-CXHYTYHU.js → chunk-IBI7A5RP.js} +621 -191
- package/dist/chunk-IBI7A5RP.js.map +1 -0
- package/dist/{chunk-NPVGBFF6.js → chunk-IJEZFZJR.js} +2 -43
- package/dist/chunk-IJEZFZJR.js.map +1 -0
- package/dist/{chunk-BPGJ4HBU.js → chunk-RCIEHFQ2.js} +3 -3
- package/dist/{chunk-NCNDA3F6.js → chunk-UG4JHZYM.js} +1176 -224
- package/dist/chunk-UG4JHZYM.js.map +1 -0
- package/dist/cli.js +7 -7
- package/dist/dashboard/assets/index-BVMrdN8a.js +119 -0
- package/dist/dashboard/assets/{index-DitffAxS.js → index-D5_8DE_B.js} +1 -1
- package/dist/dashboard/assets/index-DZWXfWsv.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/{dist-L4YCQOQ6.js → dist-KJ6PELQY.js} +45 -11
- package/dist/{docker-workspace-RPPXBT27-B4AQHVWA.js → docker-workspace-ZJ2ESVBI-6UDETQII.js} +2 -2
- package/dist/{exec-AR6JUUN5-6MBPURPR.js → exec-MYPAJJP7-C5HD5WG5.js} +2 -2
- package/dist/index.js +7 -7
- package/dist/{interactive-DRNRKLEQ.js → interactive-2WMVAASS.js} +7 -7
- package/dist/skills/agentv-bench/SKILL.md +3 -3
- package/dist/skills/agentv-bench/agents/analyzer.md +1 -1
- package/dist/skills/agentv-bench/references/autoresearch.md +1 -1
- package/dist/skills/agentv-bench/references/eval-yaml-spec.md +13 -3
- package/dist/skills/agentv-bench/references/migrating-from-skill-creator.md +2 -2
- package/dist/skills/agentv-bench/references/schemas.md +6 -6
- package/dist/skills/agentv-bench/references/subagent-pipeline.md +1 -1
- package/dist/skills/agentv-eval-writer/SKILL.md +10 -0
- package/dist/skills/agentv-eval-writer/references/eval-schema.json +3 -63
- package/dist/skills/agentv-eval-writer/references/experiment-schema.json +278 -0
- package/dist/{ts-eval-loader-S7ESROX2-6CB3HB6H.js → ts-eval-loader-RE43OAGX-AMRCUKBZ.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-2N3YSEMI.js.map +0 -1
- package/dist/chunk-5DYGN547.js.map +0 -1
- package/dist/chunk-CXHYTYHU.js.map +0 -1
- package/dist/chunk-KAGZRUO6.js.map +0 -1
- package/dist/chunk-NCNDA3F6.js.map +0 -1
- package/dist/chunk-NPVGBFF6.js.map +0 -1
- package/dist/dashboard/assets/index-1Hvy8sBy.js +0 -119
- package/dist/dashboard/assets/index-J5sPcQO3.css +0 -1
- /package/dist/{artifact-writer-72MQQELS.js.map → artifact-writer-QD52FC56.js.map} +0 -0
- /package/dist/{chunk-BPGJ4HBU.js.map → chunk-RCIEHFQ2.js.map} +0 -0
- /package/dist/{dist-L4YCQOQ6.js.map → dist-KJ6PELQY.js.map} +0 -0
- /package/dist/{docker-workspace-RPPXBT27-B4AQHVWA.js.map → docker-workspace-ZJ2ESVBI-6UDETQII.js.map} +0 -0
- /package/dist/{exec-AR6JUUN5-6MBPURPR.js.map → exec-MYPAJJP7-C5HD5WG5.js.map} +0 -0
- /package/dist/{interactive-DRNRKLEQ.js.map → interactive-2WMVAASS.js.map} +0 -0
- /package/dist/{ts-eval-loader-S7ESROX2-6CB3HB6H.js.map → ts-eval-loader-RE43OAGX-AMRCUKBZ.js.map} +0 -0
|
@@ -21,18 +21,22 @@ import {
|
|
|
21
21
|
readTranscriptJsonl,
|
|
22
22
|
releaseSharedWorkspaceSetup,
|
|
23
23
|
resolveRepoCloneUrl
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-GZJMODLW.js";
|
|
25
25
|
|
|
26
26
|
// ../../packages/core/dist/index.js
|
|
27
|
-
import {
|
|
27
|
+
import { createHash } from "node:crypto";
|
|
28
|
+
import { readFile } from "node:fs/promises";
|
|
28
29
|
import path from "node:path";
|
|
29
|
-
import {
|
|
30
|
+
import { pathToFileURL } from "node:url";
|
|
31
|
+
import { readFileSync } from "node:fs";
|
|
30
32
|
import path2 from "node:path";
|
|
31
|
-
import
|
|
32
|
-
import { readFile } from "node:fs/promises";
|
|
33
|
+
import { randomUUID } from "node:crypto";
|
|
33
34
|
import path3 from "node:path";
|
|
35
|
+
import micromatch from "micromatch";
|
|
36
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
37
|
+
import path4 from "node:path";
|
|
34
38
|
import { execFile, spawn } from "node:child_process";
|
|
35
|
-
import { createHash } from "node:crypto";
|
|
39
|
+
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
36
40
|
import {
|
|
37
41
|
existsSync,
|
|
38
42
|
mkdirSync,
|
|
@@ -44,17 +48,17 @@ import {
|
|
|
44
48
|
} from "node:fs";
|
|
45
49
|
import { cp, lstat, mkdtemp, readdir, rm, stat } from "node:fs/promises";
|
|
46
50
|
import os from "node:os";
|
|
47
|
-
import
|
|
51
|
+
import path5 from "node:path";
|
|
48
52
|
import { promisify } from "node:util";
|
|
49
53
|
import * as childProcess from "node:child_process";
|
|
50
54
|
import { existsSync as existsSync2 } from "node:fs";
|
|
51
55
|
import { spawnSync } from "node:child_process";
|
|
52
56
|
import { readdir as readdir2, stat as stat2 } from "node:fs/promises";
|
|
53
57
|
import { homedir } from "node:os";
|
|
54
|
-
import
|
|
58
|
+
import path6 from "node:path";
|
|
55
59
|
import { readdir as readdir3, stat as stat3 } from "node:fs/promises";
|
|
56
60
|
import { homedir as homedir2 } from "node:os";
|
|
57
|
-
import
|
|
61
|
+
import path7 from "node:path";
|
|
58
62
|
var TRACE_SESSION_SCHEMA_VERSION = "agentv.dashboard.trace_session.v1";
|
|
59
63
|
function isRecord(value) {
|
|
60
64
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -1478,6 +1482,429 @@ function isPrimitive(value) {
|
|
|
1478
1482
|
function dropUndefined2(value) {
|
|
1479
1483
|
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
|
|
1480
1484
|
}
|
|
1485
|
+
var EXPERIMENT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml", ".ts", ".js", ".mts", ".mjs"]);
|
|
1486
|
+
var VALID_SANDBOXES = /* @__PURE__ */ new Set(["auto", "docker", "vercel"]);
|
|
1487
|
+
var VALID_REPEAT_STRATEGIES = /* @__PURE__ */ new Set([
|
|
1488
|
+
"pass_at_k",
|
|
1489
|
+
"mean",
|
|
1490
|
+
"confidence_interval"
|
|
1491
|
+
]);
|
|
1492
|
+
function isExperimentFileReference(value) {
|
|
1493
|
+
const trimmed = value.trim();
|
|
1494
|
+
if (!trimmed) {
|
|
1495
|
+
return false;
|
|
1496
|
+
}
|
|
1497
|
+
return trimmed.includes("/") || trimmed.includes("\\") || EXPERIMENT_FILE_EXTENSIONS.has(path.extname(trimmed).toLowerCase());
|
|
1498
|
+
}
|
|
1499
|
+
function deriveExperimentNameFromPath(filePath) {
|
|
1500
|
+
return path.basename(filePath).replace(/\.experiment\.(ya?ml|[cm]?[jt]s)$/i, "").replace(/\.(ya?ml|[cm]?[jt]s)$/i, "");
|
|
1501
|
+
}
|
|
1502
|
+
async function loadExperimentConfig(filePath) {
|
|
1503
|
+
const resolvedPath = path.resolve(filePath);
|
|
1504
|
+
const ext = path.extname(resolvedPath).toLowerCase();
|
|
1505
|
+
let rawConfig;
|
|
1506
|
+
if (ext === ".yaml" || ext === ".yml") {
|
|
1507
|
+
rawConfig = parseYamlValue(await readFile(resolvedPath, "utf8"));
|
|
1508
|
+
} else if (EXPERIMENT_FILE_EXTENSIONS.has(ext)) {
|
|
1509
|
+
const moduleUrl = pathToFileURL(resolvedPath).href;
|
|
1510
|
+
const mod = await import(moduleUrl);
|
|
1511
|
+
rawConfig = mod.default ?? mod.config ?? mod;
|
|
1512
|
+
} else {
|
|
1513
|
+
throw new Error(
|
|
1514
|
+
`Unsupported experiment file extension '${ext}'. Use .yaml, .yml, .ts, .js, .mts, or .mjs.`
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
return normalizeExperimentConfig(rawConfig, { sourcePath: resolvedPath });
|
|
1518
|
+
}
|
|
1519
|
+
function normalizeExperimentConfig(rawConfig, options = {}) {
|
|
1520
|
+
if (!isRecord2(rawConfig)) {
|
|
1521
|
+
throw new Error("Experiment config must be an object.");
|
|
1522
|
+
}
|
|
1523
|
+
const name = readOptionalString(rawConfig.name, "name");
|
|
1524
|
+
const agent = readOptionalString(rawConfig.agent, "agent");
|
|
1525
|
+
const target = readOptionalString(rawConfig.target, "target");
|
|
1526
|
+
const targets = readTargets(rawConfig.targets);
|
|
1527
|
+
const model = readOptionalString(rawConfig.model, "model");
|
|
1528
|
+
const agentOptions = readOptionalRecord(rawConfig.agent_options ?? rawConfig.agentOptions);
|
|
1529
|
+
const suites = readSuites(rawConfig.suites);
|
|
1530
|
+
const scripts = readScriptArray(rawConfig.scripts, "scripts");
|
|
1531
|
+
const repeat = readRepeat(rawConfig.repeat);
|
|
1532
|
+
const runs = readOptionalPositiveInteger(rawConfig.runs, "runs");
|
|
1533
|
+
if (repeat !== void 0 && runs !== void 0) {
|
|
1534
|
+
throw new Error("Experiment repeat and runs cannot both be set. Use repeat for AgentV config.");
|
|
1535
|
+
}
|
|
1536
|
+
const earlyExit = readOptionalBoolean(rawConfig.early_exit ?? rawConfig.earlyExit, "early_exit");
|
|
1537
|
+
const timeoutSeconds = readOptionalPositiveNumber(
|
|
1538
|
+
rawConfig.timeout_seconds ?? rawConfig.timeoutSeconds,
|
|
1539
|
+
"timeout_seconds"
|
|
1540
|
+
);
|
|
1541
|
+
const workers = readOptionalPositiveInteger(rawConfig.workers, "workers");
|
|
1542
|
+
const budgetUsd = readOptionalPositiveNumber(
|
|
1543
|
+
rawConfig.budget_usd ?? rawConfig.budgetUsd,
|
|
1544
|
+
"budget_usd"
|
|
1545
|
+
);
|
|
1546
|
+
const sandbox = readOptionalSandbox(rawConfig.sandbox);
|
|
1547
|
+
const workspace = readOptionalRecord(rawConfig.workspace);
|
|
1548
|
+
const setup = readSetup(rawConfig.setup);
|
|
1549
|
+
const configWithoutFingerprint = {
|
|
1550
|
+
...name !== void 0 && { name },
|
|
1551
|
+
...agent !== void 0 && { agent },
|
|
1552
|
+
...target !== void 0 && { target },
|
|
1553
|
+
...targets !== void 0 && { targets },
|
|
1554
|
+
...model !== void 0 && { model },
|
|
1555
|
+
...agentOptions !== void 0 && { agentOptions },
|
|
1556
|
+
...suites !== void 0 && { suites },
|
|
1557
|
+
...scripts !== void 0 && { scripts },
|
|
1558
|
+
...repeat !== void 0 && { repeat },
|
|
1559
|
+
...runs !== void 0 && { runs },
|
|
1560
|
+
...earlyExit !== void 0 && { earlyExit },
|
|
1561
|
+
...timeoutSeconds !== void 0 && { timeoutSeconds },
|
|
1562
|
+
...workers !== void 0 && { workers },
|
|
1563
|
+
...budgetUsd !== void 0 && { budgetUsd },
|
|
1564
|
+
...sandbox !== void 0 && { sandbox },
|
|
1565
|
+
...workspace !== void 0 && { workspace },
|
|
1566
|
+
...setup !== void 0 && { setup },
|
|
1567
|
+
...options.sourcePath !== void 0 && { sourcePath: options.sourcePath }
|
|
1568
|
+
};
|
|
1569
|
+
return {
|
|
1570
|
+
...configWithoutFingerprint,
|
|
1571
|
+
fingerprint: fingerprintExperimentConfig(configWithoutFingerprint)
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
function fingerprintExperimentConfig(config) {
|
|
1575
|
+
const stablePayload = toStableJsonValue(config);
|
|
1576
|
+
return createHash("sha256").update(JSON.stringify(stablePayload)).digest("hex");
|
|
1577
|
+
}
|
|
1578
|
+
function buildExperimentArtifactMetadata(config) {
|
|
1579
|
+
if (!config) {
|
|
1580
|
+
return void 0;
|
|
1581
|
+
}
|
|
1582
|
+
const targets = config.targets?.map((target) => typeof target === "string" ? target : target.name).filter((target) => target.trim().length > 0);
|
|
1583
|
+
return {
|
|
1584
|
+
...config.name !== void 0 && { name: config.name },
|
|
1585
|
+
...config.sourcePath !== void 0 && { source_path: config.sourcePath },
|
|
1586
|
+
...config.fingerprint !== void 0 && { fingerprint: config.fingerprint },
|
|
1587
|
+
...config.agent !== void 0 && { agent: config.agent },
|
|
1588
|
+
...config.target !== void 0 && { target: config.target },
|
|
1589
|
+
...targets && targets.length > 0 && { targets },
|
|
1590
|
+
...config.model !== void 0 && { model: config.model },
|
|
1591
|
+
...config.suites !== void 0 && { suites: config.suites.map(toSuiteArtifactMetadata) },
|
|
1592
|
+
...config.repeat !== void 0 && {
|
|
1593
|
+
repeat: {
|
|
1594
|
+
count: config.repeat.count,
|
|
1595
|
+
strategy: config.repeat.strategy,
|
|
1596
|
+
...config.repeat.costLimitUsd !== void 0 && {
|
|
1597
|
+
cost_limit_usd: config.repeat.costLimitUsd
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
},
|
|
1601
|
+
...config.runs !== void 0 && { runs: config.runs },
|
|
1602
|
+
...config.earlyExit !== void 0 && { early_exit: config.earlyExit },
|
|
1603
|
+
...config.timeoutSeconds !== void 0 && { timeout_seconds: config.timeoutSeconds },
|
|
1604
|
+
...config.workers !== void 0 && { workers: config.workers },
|
|
1605
|
+
...config.budgetUsd !== void 0 && { budget_usd: config.budgetUsd },
|
|
1606
|
+
...config.sandbox !== void 0 && { sandbox: config.sandbox }
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
function toSuiteArtifactMetadata(suite) {
|
|
1610
|
+
return {
|
|
1611
|
+
ref: suite.ref,
|
|
1612
|
+
...suite.select !== void 0 && {
|
|
1613
|
+
select: {
|
|
1614
|
+
test_ids: suite.select.testIds
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
function readRepeat(raw) {
|
|
1620
|
+
if (raw === void 0) {
|
|
1621
|
+
return void 0;
|
|
1622
|
+
}
|
|
1623
|
+
if (!isRecord2(raw)) {
|
|
1624
|
+
throw new Error("Experiment repeat must be an object.");
|
|
1625
|
+
}
|
|
1626
|
+
const count = readRequiredPositiveInteger(raw.count, "repeat.count");
|
|
1627
|
+
const strategy = readOptionalRepeatStrategy(raw.strategy);
|
|
1628
|
+
const costLimitUsd = readOptionalNonNegativeNumber(
|
|
1629
|
+
raw.cost_limit_usd ?? raw.costLimitUsd,
|
|
1630
|
+
"repeat.cost_limit_usd"
|
|
1631
|
+
);
|
|
1632
|
+
return {
|
|
1633
|
+
count,
|
|
1634
|
+
strategy: strategy ?? "pass_at_k",
|
|
1635
|
+
...costLimitUsd !== void 0 && { costLimitUsd }
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
function readTargets(raw) {
|
|
1639
|
+
if (raw === void 0) {
|
|
1640
|
+
return void 0;
|
|
1641
|
+
}
|
|
1642
|
+
if (!Array.isArray(raw)) {
|
|
1643
|
+
throw new Error("Experiment targets must be an array.");
|
|
1644
|
+
}
|
|
1645
|
+
return raw.map((entry, index) => {
|
|
1646
|
+
if (typeof entry === "string" && entry.trim().length > 0) {
|
|
1647
|
+
return entry.trim();
|
|
1648
|
+
}
|
|
1649
|
+
if (!isRecord2(entry)) {
|
|
1650
|
+
throw new Error(`Experiment targets[${index}] must be a string or object.`);
|
|
1651
|
+
}
|
|
1652
|
+
const name = readRequiredString(entry.name, `targets[${index}].name`);
|
|
1653
|
+
const useTarget = readOptionalString(
|
|
1654
|
+
entry.use_target ?? entry.useTarget,
|
|
1655
|
+
`targets[${index}].use_target`
|
|
1656
|
+
);
|
|
1657
|
+
const hooks = readOptionalRecord(entry.hooks);
|
|
1658
|
+
return {
|
|
1659
|
+
name,
|
|
1660
|
+
...useTarget !== void 0 && { useTarget },
|
|
1661
|
+
...hooks !== void 0 && { hooks }
|
|
1662
|
+
};
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
function readSuites(raw) {
|
|
1666
|
+
if (raw === void 0) {
|
|
1667
|
+
return void 0;
|
|
1668
|
+
}
|
|
1669
|
+
if (!Array.isArray(raw)) {
|
|
1670
|
+
throw new Error("Experiment suites must be an array.");
|
|
1671
|
+
}
|
|
1672
|
+
if (raw.length === 0) {
|
|
1673
|
+
throw new Error("Experiment suites must not be empty.");
|
|
1674
|
+
}
|
|
1675
|
+
return raw.map((entry, index) => {
|
|
1676
|
+
if (!isRecord2(entry)) {
|
|
1677
|
+
throw new Error(`Experiment suites[${index}] must be an object.`);
|
|
1678
|
+
}
|
|
1679
|
+
const ref = readRequiredString(entry.ref, `suites[${index}].ref`);
|
|
1680
|
+
const select = readSuiteSelect(entry.select, `suites[${index}].select`);
|
|
1681
|
+
return {
|
|
1682
|
+
ref,
|
|
1683
|
+
...select !== void 0 && { select }
|
|
1684
|
+
};
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
function readSuiteSelect(raw, location) {
|
|
1688
|
+
if (raw === void 0) {
|
|
1689
|
+
return void 0;
|
|
1690
|
+
}
|
|
1691
|
+
if (!isRecord2(raw)) {
|
|
1692
|
+
throw new Error(`Experiment ${location} must be an object.`);
|
|
1693
|
+
}
|
|
1694
|
+
const testIds = readOptionalStringArray(raw.test_ids ?? raw.testIds, `${location}.test_ids`);
|
|
1695
|
+
if (testIds === void 0) {
|
|
1696
|
+
throw new Error(`Experiment ${location}.test_ids is required when select is set.`);
|
|
1697
|
+
}
|
|
1698
|
+
return { testIds };
|
|
1699
|
+
}
|
|
1700
|
+
function readScriptArray(raw, location) {
|
|
1701
|
+
if (raw === void 0) {
|
|
1702
|
+
return void 0;
|
|
1703
|
+
}
|
|
1704
|
+
if (!Array.isArray(raw)) {
|
|
1705
|
+
throw new Error(`Experiment ${location} must be an array.`);
|
|
1706
|
+
}
|
|
1707
|
+
return raw.map((entry, index) => readScript(entry, `${location}[${index}]`));
|
|
1708
|
+
}
|
|
1709
|
+
function readSetup(raw) {
|
|
1710
|
+
if (raw === void 0) {
|
|
1711
|
+
return void 0;
|
|
1712
|
+
}
|
|
1713
|
+
if (typeof raw === "function") {
|
|
1714
|
+
return raw;
|
|
1715
|
+
}
|
|
1716
|
+
return readScriptArray(raw, "setup");
|
|
1717
|
+
}
|
|
1718
|
+
function readScript(raw, location) {
|
|
1719
|
+
if (typeof raw === "string") {
|
|
1720
|
+
const script2 = raw.trim();
|
|
1721
|
+
if (!script2) {
|
|
1722
|
+
throw new Error(`Experiment ${location} must not be empty.`);
|
|
1723
|
+
}
|
|
1724
|
+
return { script: script2 };
|
|
1725
|
+
}
|
|
1726
|
+
if (!isRecord2(raw)) {
|
|
1727
|
+
throw new Error(`Experiment ${location} must be a string or object.`);
|
|
1728
|
+
}
|
|
1729
|
+
const command = readOptionalCommand(raw.command, `${location}.command`);
|
|
1730
|
+
const script = readOptionalStringOrStringArray(raw.script, `${location}.script`);
|
|
1731
|
+
if (command === void 0 && script === void 0) {
|
|
1732
|
+
throw new Error(`Experiment ${location} must define command or script.`);
|
|
1733
|
+
}
|
|
1734
|
+
const timeoutSeconds = readOptionalPositiveNumber(
|
|
1735
|
+
raw.timeout_seconds ?? raw.timeoutSeconds,
|
|
1736
|
+
`${location}.timeout_seconds`
|
|
1737
|
+
);
|
|
1738
|
+
const cwd = readOptionalString(raw.cwd, `${location}.cwd`);
|
|
1739
|
+
const env = readOptionalStringRecord(raw.env, `${location}.env`);
|
|
1740
|
+
return {
|
|
1741
|
+
...command !== void 0 && { command },
|
|
1742
|
+
...script !== void 0 && { script },
|
|
1743
|
+
...timeoutSeconds !== void 0 && { timeoutSeconds },
|
|
1744
|
+
...cwd !== void 0 && { cwd },
|
|
1745
|
+
...env !== void 0 && { env }
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
function readOptionalCommand(raw, location) {
|
|
1749
|
+
if (raw === void 0) {
|
|
1750
|
+
return void 0;
|
|
1751
|
+
}
|
|
1752
|
+
if (typeof raw === "string") {
|
|
1753
|
+
const command = raw.trim();
|
|
1754
|
+
if (!command) {
|
|
1755
|
+
throw new Error(`Experiment ${location} must not be empty.`);
|
|
1756
|
+
}
|
|
1757
|
+
return ["sh", "-c", command];
|
|
1758
|
+
}
|
|
1759
|
+
if (Array.isArray(raw) && raw.length > 0 && raw.every((entry) => typeof entry === "string" && entry.trim())) {
|
|
1760
|
+
return raw.map((entry) => entry.trim());
|
|
1761
|
+
}
|
|
1762
|
+
throw new Error(`Experiment ${location} must be a string or string array.`);
|
|
1763
|
+
}
|
|
1764
|
+
function readOptionalStringOrStringArray(raw, location) {
|
|
1765
|
+
if (raw === void 0) {
|
|
1766
|
+
return void 0;
|
|
1767
|
+
}
|
|
1768
|
+
if (typeof raw === "string") {
|
|
1769
|
+
const trimmed = raw.trim();
|
|
1770
|
+
if (!trimmed) {
|
|
1771
|
+
throw new Error(`Experiment ${location} must not be empty.`);
|
|
1772
|
+
}
|
|
1773
|
+
return trimmed;
|
|
1774
|
+
}
|
|
1775
|
+
if (Array.isArray(raw) && raw.length > 0 && raw.every((entry) => typeof entry === "string" && entry.trim())) {
|
|
1776
|
+
return raw.map((entry) => entry.trim());
|
|
1777
|
+
}
|
|
1778
|
+
throw new Error(`Experiment ${location} must be a string or string array.`);
|
|
1779
|
+
}
|
|
1780
|
+
function readOptionalStringArray(raw, location) {
|
|
1781
|
+
if (raw === void 0) {
|
|
1782
|
+
return void 0;
|
|
1783
|
+
}
|
|
1784
|
+
if (Array.isArray(raw) && raw.length > 0 && raw.every((entry) => typeof entry === "string" && entry.trim())) {
|
|
1785
|
+
return raw.map((entry) => entry.trim());
|
|
1786
|
+
}
|
|
1787
|
+
throw new Error(`Experiment ${location} must be a non-empty string array.`);
|
|
1788
|
+
}
|
|
1789
|
+
function readOptionalString(raw, location) {
|
|
1790
|
+
if (raw === void 0) {
|
|
1791
|
+
return void 0;
|
|
1792
|
+
}
|
|
1793
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
1794
|
+
throw new Error(`Experiment ${location} must be a non-empty string.`);
|
|
1795
|
+
}
|
|
1796
|
+
return raw.trim();
|
|
1797
|
+
}
|
|
1798
|
+
function readRequiredString(raw, location) {
|
|
1799
|
+
const value = readOptionalString(raw, location);
|
|
1800
|
+
if (value === void 0) {
|
|
1801
|
+
throw new Error(`Experiment ${location} is required.`);
|
|
1802
|
+
}
|
|
1803
|
+
return value;
|
|
1804
|
+
}
|
|
1805
|
+
function readRequiredPositiveInteger(raw, location) {
|
|
1806
|
+
const value = readOptionalPositiveInteger(raw, location);
|
|
1807
|
+
if (value === void 0) {
|
|
1808
|
+
throw new Error(`Experiment ${location} is required.`);
|
|
1809
|
+
}
|
|
1810
|
+
return value;
|
|
1811
|
+
}
|
|
1812
|
+
function readOptionalBoolean(raw, location) {
|
|
1813
|
+
if (raw === void 0) {
|
|
1814
|
+
return void 0;
|
|
1815
|
+
}
|
|
1816
|
+
if (typeof raw !== "boolean") {
|
|
1817
|
+
throw new Error(`Experiment ${location} must be a boolean.`);
|
|
1818
|
+
}
|
|
1819
|
+
return raw;
|
|
1820
|
+
}
|
|
1821
|
+
function readOptionalRepeatStrategy(raw) {
|
|
1822
|
+
if (raw === void 0) {
|
|
1823
|
+
return void 0;
|
|
1824
|
+
}
|
|
1825
|
+
if (typeof raw !== "string" || !VALID_REPEAT_STRATEGIES.has(raw)) {
|
|
1826
|
+
throw new Error(
|
|
1827
|
+
"Experiment repeat.strategy must be one of 'pass_at_k', 'mean', or 'confidence_interval'."
|
|
1828
|
+
);
|
|
1829
|
+
}
|
|
1830
|
+
return raw;
|
|
1831
|
+
}
|
|
1832
|
+
function readOptionalPositiveInteger(raw, location) {
|
|
1833
|
+
if (raw === void 0) {
|
|
1834
|
+
return void 0;
|
|
1835
|
+
}
|
|
1836
|
+
if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1) {
|
|
1837
|
+
throw new Error(`Experiment ${location} must be a positive integer.`);
|
|
1838
|
+
}
|
|
1839
|
+
return raw;
|
|
1840
|
+
}
|
|
1841
|
+
function readOptionalNonNegativeNumber(raw, location) {
|
|
1842
|
+
if (raw === void 0) {
|
|
1843
|
+
return void 0;
|
|
1844
|
+
}
|
|
1845
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0) {
|
|
1846
|
+
throw new Error(`Experiment ${location} must be a non-negative number.`);
|
|
1847
|
+
}
|
|
1848
|
+
return raw;
|
|
1849
|
+
}
|
|
1850
|
+
function readOptionalPositiveNumber(raw, location) {
|
|
1851
|
+
if (raw === void 0) {
|
|
1852
|
+
return void 0;
|
|
1853
|
+
}
|
|
1854
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) {
|
|
1855
|
+
throw new Error(`Experiment ${location} must be a positive number.`);
|
|
1856
|
+
}
|
|
1857
|
+
return raw;
|
|
1858
|
+
}
|
|
1859
|
+
function readOptionalSandbox(raw) {
|
|
1860
|
+
if (raw === void 0) {
|
|
1861
|
+
return void 0;
|
|
1862
|
+
}
|
|
1863
|
+
if (typeof raw !== "string" || !VALID_SANDBOXES.has(raw)) {
|
|
1864
|
+
throw new Error("Experiment sandbox must be one of 'auto', 'docker', or 'vercel'.");
|
|
1865
|
+
}
|
|
1866
|
+
return raw;
|
|
1867
|
+
}
|
|
1868
|
+
function readOptionalRecord(raw) {
|
|
1869
|
+
if (raw === void 0) {
|
|
1870
|
+
return void 0;
|
|
1871
|
+
}
|
|
1872
|
+
if (!isRecord2(raw)) {
|
|
1873
|
+
throw new Error("Experiment object field must be an object.");
|
|
1874
|
+
}
|
|
1875
|
+
return raw;
|
|
1876
|
+
}
|
|
1877
|
+
function readOptionalStringRecord(raw, location) {
|
|
1878
|
+
if (raw === void 0) {
|
|
1879
|
+
return void 0;
|
|
1880
|
+
}
|
|
1881
|
+
if (!isRecord2(raw)) {
|
|
1882
|
+
throw new Error(`Experiment ${location} must be an object.`);
|
|
1883
|
+
}
|
|
1884
|
+
const entries = Object.entries(raw);
|
|
1885
|
+
if (!entries.every((entry) => typeof entry[1] === "string")) {
|
|
1886
|
+
throw new Error(`Experiment ${location} values must be strings.`);
|
|
1887
|
+
}
|
|
1888
|
+
return Object.fromEntries(entries);
|
|
1889
|
+
}
|
|
1890
|
+
function isRecord2(value) {
|
|
1891
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1892
|
+
}
|
|
1893
|
+
function toStableJsonValue(value) {
|
|
1894
|
+
if (typeof value === "function") {
|
|
1895
|
+
return "[function]";
|
|
1896
|
+
}
|
|
1897
|
+
if (value === null || typeof value !== "object") {
|
|
1898
|
+
return value;
|
|
1899
|
+
}
|
|
1900
|
+
if (Array.isArray(value)) {
|
|
1901
|
+
return value.map(toStableJsonValue);
|
|
1902
|
+
}
|
|
1903
|
+
const record = value;
|
|
1904
|
+
return Object.fromEntries(
|
|
1905
|
+
Object.keys(record).sort().map((key) => [key, toStableJsonValue(record[key])])
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1481
1908
|
function codeGraderInstruction(graderName, description) {
|
|
1482
1909
|
const desc = description ? ` This grader: ${description}.` : "";
|
|
1483
1910
|
return `Run \`agentv eval assert ${graderName} --agent-output <agent_output> --agent-input <original_prompt>\` and check the result.${desc} The command accepts --agent-output (the agent's full response text) and --agent-input (the original user prompt). It returns JSON on stdout: {"score": 0-1, "reasoning": "..."}. A score >= 0.5 means pass (exit 0); below 0.5 means fail (exit 1).`;
|
|
@@ -1717,7 +2144,7 @@ function transpileEvalYaml(suite, source = "EVAL.yaml") {
|
|
|
1717
2144
|
function transpileEvalYamlFile(evalYamlPath) {
|
|
1718
2145
|
const content = readFileSync(evalYamlPath, "utf8");
|
|
1719
2146
|
const parsed = parseYamlValue(content);
|
|
1720
|
-
return transpileEvalYaml(parsed,
|
|
2147
|
+
return transpileEvalYaml(parsed, path2.basename(evalYamlPath));
|
|
1721
2148
|
}
|
|
1722
2149
|
function getOutputFilenames(result) {
|
|
1723
2150
|
const names = /* @__PURE__ */ new Map();
|
|
@@ -1782,7 +2209,7 @@ async function releaseUnselectedPoolSlots(setup, selectedSlotPath) {
|
|
|
1782
2209
|
}
|
|
1783
2210
|
}
|
|
1784
2211
|
async function prepareEvalWorkspace(options) {
|
|
1785
|
-
const evalPath =
|
|
2212
|
+
const evalPath = path3.resolve(options.testFilePath);
|
|
1786
2213
|
const evalRunId = randomUUID();
|
|
1787
2214
|
const evalCases = options.evalCases ?? await loadTests(evalPath, options.repoRoot, {
|
|
1788
2215
|
verbose: options.verbose,
|
|
@@ -1794,7 +2221,7 @@ async function prepareEvalWorkspace(options) {
|
|
|
1794
2221
|
filter: options.filter,
|
|
1795
2222
|
evalPath
|
|
1796
2223
|
});
|
|
1797
|
-
const evalDir =
|
|
2224
|
+
const evalDir = path3.dirname(evalPath);
|
|
1798
2225
|
const workers = options.maxConcurrency ?? 1;
|
|
1799
2226
|
const retainOnSuccess = options.retainOnSuccess ?? (options.keepWorkspaces ? "keep" : "cleanup");
|
|
1800
2227
|
const retainOnFailure = options.retainOnFailure ?? (options.cleanupWorkspaces ? "cleanup" : "keep");
|
|
@@ -1905,6 +2332,13 @@ var AgentVConfigSchema = external_exports.object({
|
|
|
1905
2332
|
/** Response cache directory */
|
|
1906
2333
|
path: external_exports.string().optional()
|
|
1907
2334
|
}).optional(),
|
|
2335
|
+
/** Compatibility shorthand for experiments.default */
|
|
2336
|
+
defaultExperiment: external_exports.string().optional(),
|
|
2337
|
+
/** Experiment defaults */
|
|
2338
|
+
experiments: external_exports.object({
|
|
2339
|
+
/** Default experiment label or path used when eval runs omit --experiment */
|
|
2340
|
+
default: external_exports.string().optional()
|
|
2341
|
+
}).strict().optional(),
|
|
1908
2342
|
/** Cost and duration limits */
|
|
1909
2343
|
limits: external_exports.object({
|
|
1910
2344
|
/** Maximum cost per run in USD */
|
|
@@ -1937,7 +2371,7 @@ var CONFIG_FILE_NAMES = [
|
|
|
1937
2371
|
];
|
|
1938
2372
|
async function loadTsConfig(projectRoot) {
|
|
1939
2373
|
const { existsSync: existsSync3 } = await import("node:fs");
|
|
1940
|
-
const { pathToFileURL } = await import("node:url");
|
|
2374
|
+
const { pathToFileURL: pathToFileURL2 } = await import("node:url");
|
|
1941
2375
|
const { join } = await import("node:path");
|
|
1942
2376
|
for (const fileName of CONFIG_FILE_NAMES) {
|
|
1943
2377
|
const filePath = join(projectRoot, fileName);
|
|
@@ -1945,7 +2379,7 @@ async function loadTsConfig(projectRoot) {
|
|
|
1945
2379
|
continue;
|
|
1946
2380
|
}
|
|
1947
2381
|
try {
|
|
1948
|
-
const fileUrl =
|
|
2382
|
+
const fileUrl = pathToFileURL2(filePath).href;
|
|
1949
2383
|
const mod = await import(fileUrl);
|
|
1950
2384
|
const config = mod.default ?? mod;
|
|
1951
2385
|
return AgentVConfigSchema.parse(config);
|
|
@@ -2062,11 +2496,11 @@ async function scanRepoDeps(evalFilePaths) {
|
|
|
2062
2496
|
return { repos: [...seen.values()], errors };
|
|
2063
2497
|
}
|
|
2064
2498
|
async function extractReposFromEvalFile(filePath) {
|
|
2065
|
-
const content = await
|
|
2499
|
+
const content = await readFile2(filePath, "utf8");
|
|
2066
2500
|
const parsed = interpolateEnv(parseYamlValue(content), process.env);
|
|
2067
2501
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return [];
|
|
2068
2502
|
const obj = parsed;
|
|
2069
|
-
const evalFileDir =
|
|
2503
|
+
const evalFileDir = path4.dirname(path4.resolve(filePath));
|
|
2070
2504
|
const repos = [];
|
|
2071
2505
|
const suiteRepos = await extractReposFromWorkspaceRaw(obj.workspace, evalFileDir);
|
|
2072
2506
|
repos.push(...suiteRepos);
|
|
@@ -2082,8 +2516,8 @@ async function extractReposFromEvalFile(filePath) {
|
|
|
2082
2516
|
}
|
|
2083
2517
|
async function extractReposFromWorkspaceRaw(raw, evalFileDir) {
|
|
2084
2518
|
if (typeof raw === "string") {
|
|
2085
|
-
const workspaceFilePath =
|
|
2086
|
-
const content = await
|
|
2519
|
+
const workspaceFilePath = path4.resolve(evalFileDir, raw);
|
|
2520
|
+
const content = await readFile2(workspaceFilePath, "utf8");
|
|
2087
2521
|
const parsed = interpolateEnv(parseYamlValue(content), process.env);
|
|
2088
2522
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return [];
|
|
2089
2523
|
return extractReposFromObject(parsed);
|
|
@@ -2125,11 +2559,126 @@ var GIT_ENV_INHERIT_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
2125
2559
|
"GIT_USERNAME"
|
|
2126
2560
|
]);
|
|
2127
2561
|
var DEFAULT_RESULTS_BRANCH = AGENTV_RESULTS_PRIMARY_REF;
|
|
2128
|
-
var MANAGED_RESULTS_REMOTE = "agentv-results";
|
|
2129
2562
|
var GIT_EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
2130
2563
|
var RESULTS_REPO_GENESIS_MESSAGE = "chore(results): initialize AgentV results branch";
|
|
2131
2564
|
var RESULTS_REPO_GENESIS_DATE = "@0 +0000";
|
|
2132
2565
|
var RESULT_INDEX_FILENAME2 = "index.jsonl";
|
|
2566
|
+
var RESULTS_REPO_GITATTRIBUTES_FILE = ".gitattributes";
|
|
2567
|
+
var RESULTS_REPO_GITATTRIBUTES_CONTENT = `# Managed by AgentV. Artifact-aware merge so results sync never force-pushes.
|
|
2568
|
+
# Append-only run index: union concurrent appends (lines are orthogonal).
|
|
2569
|
+
index.jsonl merge=union
|
|
2570
|
+
# Editable run overlay (tags/feedback): 3-way JSON set/field union via the
|
|
2571
|
+
# agentv-json driver; a genuine scalar conflict falls through to a human merge.
|
|
2572
|
+
metadata/runs/**/*.json merge=agentv-json
|
|
2573
|
+
`;
|
|
2574
|
+
var RESULTS_JSON_MERGE_DRIVER_NAME = "agentv-json";
|
|
2575
|
+
var RESULTS_JSON_MERGE_DRIVER_SCRIPT = `#!/usr/bin/env node
|
|
2576
|
+
// AgentV results overlay merge driver (merge=agentv-json).
|
|
2577
|
+
//
|
|
2578
|
+
// Performs a 3-way merge of AgentV's editable JSON overlay files (run tags /
|
|
2579
|
+
// feedback). Tag lists merge as a 3-way set so concurrent add/remove are
|
|
2580
|
+
// commutative; display-only bookkeeping scalars that always differ between
|
|
2581
|
+
// writers (updated_at) are regenerated to the larger value instead of
|
|
2582
|
+
// conflicting. Content-derived concurrency tokens (tag_revision) are dropped on
|
|
2583
|
+
// merge so the reader recomputes a token matching the merged content rather than
|
|
2584
|
+
// carrying a stale token that could bypass optimistic-concurrency checks. Any
|
|
2585
|
+
// other genuinely diverging scalar exits non-zero, leaving the file conflicted
|
|
2586
|
+
// so the caller can route it to a human GitHub merge.
|
|
2587
|
+
//
|
|
2588
|
+
// Invoked by git as: node json-merge-driver.mjs %O %A %B %P
|
|
2589
|
+
// %O ancestor, %A current (overwritten with the result), %B other, %P path.
|
|
2590
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2591
|
+
|
|
2592
|
+
const [, , ancestorFile, currentFile, otherFile] = process.argv;
|
|
2593
|
+
const CONFLICT = Symbol('conflict');
|
|
2594
|
+
// Scalars that legitimately differ on every write and carry no merge meaning.
|
|
2595
|
+
const REGENERABLE_SCALARS = new Set(['updated_at']);
|
|
2596
|
+
// Content-derived tokens (a hash of {tags, updated_at}) that guard optimistic
|
|
2597
|
+
// concurrency. Carrying either side's pre-merge token forward would let a stale
|
|
2598
|
+
// client whose token happens to match bypass the concurrency check and silently
|
|
2599
|
+
// overwrite the merged set, so we drop them and let the reader recompute a token
|
|
2600
|
+
// that matches the merged content.
|
|
2601
|
+
const DROP_ON_MERGE = new Set(['tag_revision']);
|
|
2602
|
+
|
|
2603
|
+
function readJson(file) {
|
|
2604
|
+
try {
|
|
2605
|
+
return JSON.parse(readFileSync(file, 'utf8'));
|
|
2606
|
+
} catch {
|
|
2607
|
+
return undefined;
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
function isPrimitiveArray(value) {
|
|
2612
|
+
return (
|
|
2613
|
+
Array.isArray(value) &&
|
|
2614
|
+
value.every((entry) => entry === null || ['string', 'number', 'boolean'].includes(typeof entry))
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
function isPlainObject(value) {
|
|
2619
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
function mergeSet(base, a, b) {
|
|
2623
|
+
const baseSet = new Set((isPrimitiveArray(base) ? base : []).map(String));
|
|
2624
|
+
const aSet = new Set(a.map(String));
|
|
2625
|
+
const bSet = new Set(b.map(String));
|
|
2626
|
+
const removed = new Set();
|
|
2627
|
+
for (const value of baseSet) {
|
|
2628
|
+
if (!aSet.has(value) || !bSet.has(value)) removed.add(value);
|
|
2629
|
+
}
|
|
2630
|
+
const merged = [];
|
|
2631
|
+
const seen = new Set();
|
|
2632
|
+
for (const value of [...a, ...b]) {
|
|
2633
|
+
const key = String(value);
|
|
2634
|
+
if (!removed.has(key) && !seen.has(key)) {
|
|
2635
|
+
seen.add(key);
|
|
2636
|
+
merged.push(value);
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
return merged;
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
function merge3(base, a, b, key) {
|
|
2643
|
+
if (JSON.stringify(a) === JSON.stringify(b)) return a;
|
|
2644
|
+
if (JSON.stringify(a) === JSON.stringify(base)) return b;
|
|
2645
|
+
if (JSON.stringify(b) === JSON.stringify(base)) return a;
|
|
2646
|
+
if (isPrimitiveArray(a) && isPrimitiveArray(b)) return mergeSet(base, a, b);
|
|
2647
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
2648
|
+
const keys = new Set([
|
|
2649
|
+
...Object.keys(a),
|
|
2650
|
+
...Object.keys(b),
|
|
2651
|
+
...(isPlainObject(base) ? Object.keys(base) : []),
|
|
2652
|
+
]);
|
|
2653
|
+
const out = {};
|
|
2654
|
+
for (const childKey of keys) {
|
|
2655
|
+
if (DROP_ON_MERGE.has(childKey)) continue;
|
|
2656
|
+
const merged = merge3(
|
|
2657
|
+
isPlainObject(base) ? base[childKey] : undefined,
|
|
2658
|
+
a[childKey],
|
|
2659
|
+
b[childKey],
|
|
2660
|
+
childKey,
|
|
2661
|
+
);
|
|
2662
|
+
if (merged === CONFLICT) return CONFLICT;
|
|
2663
|
+
if (merged !== undefined) out[childKey] = merged;
|
|
2664
|
+
}
|
|
2665
|
+
return out;
|
|
2666
|
+
}
|
|
2667
|
+
if (REGENERABLE_SCALARS.has(key)) {
|
|
2668
|
+
return [a, b].filter((value) => value !== undefined).sort().pop();
|
|
2669
|
+
}
|
|
2670
|
+
return CONFLICT;
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
const ancestor = readJson(ancestorFile) ?? {};
|
|
2674
|
+
const current = readJson(currentFile);
|
|
2675
|
+
const other = readJson(otherFile);
|
|
2676
|
+
if (current === undefined || other === undefined) process.exit(1);
|
|
2677
|
+
const result = merge3(ancestor, current, other, '');
|
|
2678
|
+
if (result === CONFLICT) process.exit(1);
|
|
2679
|
+
writeFileSync(currentFile, \`\${JSON.stringify(result, null, 2)}\\n\`);
|
|
2680
|
+
process.exit(0);
|
|
2681
|
+
`;
|
|
2133
2682
|
var activeResultsRepoSyncs = /* @__PURE__ */ new Set();
|
|
2134
2683
|
function sanitizeRepoSlug(repo) {
|
|
2135
2684
|
return repo.trim().replace(/[^A-Za-z0-9._-]+/g, "-");
|
|
@@ -2144,13 +2693,13 @@ function withFriendlyGitHubAuthError(error) {
|
|
|
2144
2693
|
}
|
|
2145
2694
|
function expandHome(p) {
|
|
2146
2695
|
if (p === "~" || p.startsWith("~/") || p.startsWith("~\\")) {
|
|
2147
|
-
return
|
|
2696
|
+
return path5.join(os.homedir(), p.slice(1));
|
|
2148
2697
|
}
|
|
2149
2698
|
return p;
|
|
2150
2699
|
}
|
|
2151
2700
|
function resolveLocalPath(p, baseDir) {
|
|
2152
2701
|
const expanded = expandHome(p);
|
|
2153
|
-
return
|
|
2702
|
+
return path5.isAbsolute(expanded) ? expanded : path5.resolve(baseDir, expanded);
|
|
2154
2703
|
}
|
|
2155
2704
|
function normalizeResultsConfig(config, options) {
|
|
2156
2705
|
const baseDir = options?.baseDir ?? process.cwd();
|
|
@@ -2160,12 +2709,21 @@ function normalizeResultsConfig(config, options) {
|
|
|
2160
2709
|
const repo = repoUrl ?? repoPath ?? "";
|
|
2161
2710
|
const branch = config.branch?.trim() || (repoPath ? DEFAULT_RESULTS_BRANCH : void 0);
|
|
2162
2711
|
const useStorageBranchWorktree = Boolean(repoPath || repoUrl && explicitClonePath && branch);
|
|
2163
|
-
const remote = config.remote?.trim() ||
|
|
2712
|
+
const remote = config.remote?.trim() || "origin";
|
|
2164
2713
|
const autoPush = config.sync?.auto_push ?? config.auto_push === true;
|
|
2165
2714
|
const requirePush = config.sync?.require_push === true;
|
|
2166
|
-
const
|
|
2715
|
+
const configuredPushConflictPolicy = config.sync?.push_conflict_policy;
|
|
2716
|
+
if (configuredPushConflictPolicy === "backup_and_force_push") {
|
|
2717
|
+
throw new Error(
|
|
2718
|
+
"results.sync.push_conflict_policy: 'backup_and_force_push' is no longer supported. Remove the field or set it to 'block'; AgentV never force-pushes result branches."
|
|
2719
|
+
);
|
|
2720
|
+
}
|
|
2721
|
+
if (configuredPushConflictPolicy !== void 0 && configuredPushConflictPolicy !== "block") {
|
|
2722
|
+
throw new Error("results.sync.push_conflict_policy must be 'block'");
|
|
2723
|
+
}
|
|
2724
|
+
const pushConflictPolicy = configuredPushConflictPolicy ?? "block";
|
|
2167
2725
|
const resolvedRepoPath = repoPath ? resolveLocalPath(repoPath, baseDir) : void 0;
|
|
2168
|
-
const resolvedPath = explicitClonePath ? resolveLocalPath(explicitClonePath, baseDir) : repoUrl ?
|
|
2726
|
+
const resolvedPath = explicitClonePath ? resolveLocalPath(explicitClonePath, baseDir) : repoUrl ? path5.join(getAgentvDataDir(), "results", sanitizeRepoSlug(repoUrl)) : resolvedRepoPath ?? path5.join(getAgentvDataDir(), "results", sanitizeRepoSlug(repo));
|
|
2169
2727
|
return {
|
|
2170
2728
|
mode: "github",
|
|
2171
2729
|
repo,
|
|
@@ -2181,18 +2739,26 @@ function normalizeResultsConfig(config, options) {
|
|
|
2181
2739
|
storageBranchWorktree: useStorageBranchWorktree
|
|
2182
2740
|
};
|
|
2183
2741
|
}
|
|
2742
|
+
var GITHUB_OWNER_REPO_SHORTHAND = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
2743
|
+
function isExplicitRemoteUrl(value) {
|
|
2744
|
+
return value.includes("://") || value.startsWith("git@");
|
|
2745
|
+
}
|
|
2184
2746
|
function resolveResultsRepoUrl(repo) {
|
|
2185
|
-
|
|
2186
|
-
|
|
2747
|
+
const trimmed = repo.trim();
|
|
2748
|
+
if (isExplicitRemoteUrl(trimmed)) {
|
|
2749
|
+
return trimmed;
|
|
2750
|
+
}
|
|
2751
|
+
if (GITHUB_OWNER_REPO_SHORTHAND.test(trimmed)) {
|
|
2752
|
+
return `https://github.com/${trimmed}.git`;
|
|
2187
2753
|
}
|
|
2188
|
-
return
|
|
2754
|
+
return trimmed;
|
|
2189
2755
|
}
|
|
2190
2756
|
function getResultsRepoLocalPaths(repo) {
|
|
2191
|
-
const rootDir =
|
|
2757
|
+
const rootDir = path5.join(getAgentvDataDir(), "cache", "results-repo", sanitizeRepoSlug(repo));
|
|
2192
2758
|
return {
|
|
2193
2759
|
rootDir,
|
|
2194
|
-
repoDir:
|
|
2195
|
-
statusFile:
|
|
2760
|
+
repoDir: path5.join(rootDir, "repo"),
|
|
2761
|
+
statusFile: path5.join(rootDir, "status.json")
|
|
2196
2762
|
};
|
|
2197
2763
|
}
|
|
2198
2764
|
function usesStorageBranchWorktree(config) {
|
|
@@ -2209,7 +2775,7 @@ function readPersistedStatus(statusFile) {
|
|
|
2209
2775
|
}
|
|
2210
2776
|
}
|
|
2211
2777
|
function writePersistedStatus(statusFile, status) {
|
|
2212
|
-
mkdirSync(
|
|
2778
|
+
mkdirSync(path5.dirname(statusFile), { recursive: true });
|
|
2213
2779
|
writeFileSync(statusFile, `${JSON.stringify(status, null, 2)}
|
|
2214
2780
|
`, "utf8");
|
|
2215
2781
|
}
|
|
@@ -2447,22 +3013,95 @@ async function resolveGitTopLevel(repoDir) {
|
|
|
2447
3013
|
const { stdout } = await runGit(["rev-parse", "--show-toplevel"], { cwd: repoDir });
|
|
2448
3014
|
return stdout.trim() || repoDir;
|
|
2449
3015
|
}
|
|
2450
|
-
async function
|
|
2451
|
-
|
|
3016
|
+
async function ensureResultsMergeConfig(repoDir) {
|
|
3017
|
+
let gitDir;
|
|
3018
|
+
try {
|
|
3019
|
+
const { stdout } = await runGit(["rev-parse", "--git-common-dir"], { cwd: repoDir });
|
|
3020
|
+
const resolved = stdout.trim();
|
|
3021
|
+
gitDir = resolved ? path5.isAbsolute(resolved) ? resolved : path5.join(repoDir, resolved) : path5.join(repoDir, ".git");
|
|
3022
|
+
} catch {
|
|
2452
3023
|
return;
|
|
2453
3024
|
}
|
|
2454
|
-
const
|
|
2455
|
-
|
|
3025
|
+
const driverScriptPath = path5.join(gitDir, "agentv", "json-merge-driver.mjs");
|
|
3026
|
+
let scriptCurrent = false;
|
|
3027
|
+
try {
|
|
3028
|
+
scriptCurrent = readFileSync2(driverScriptPath, "utf8") === RESULTS_JSON_MERGE_DRIVER_SCRIPT;
|
|
3029
|
+
} catch {
|
|
3030
|
+
scriptCurrent = false;
|
|
3031
|
+
}
|
|
3032
|
+
if (!scriptCurrent) {
|
|
3033
|
+
mkdirSync(path5.dirname(driverScriptPath), { recursive: true });
|
|
3034
|
+
writeFileSync(driverScriptPath, RESULTS_JSON_MERGE_DRIVER_SCRIPT, { mode: 493 });
|
|
3035
|
+
}
|
|
3036
|
+
const driverCommand = `node ${JSON.stringify(driverScriptPath)} %O %A %B %P`;
|
|
3037
|
+
await runGit(
|
|
3038
|
+
["config", `merge.${RESULTS_JSON_MERGE_DRIVER_NAME}.name`, "AgentV results overlay JSON union"],
|
|
3039
|
+
{ cwd: repoDir, check: false }
|
|
3040
|
+
);
|
|
3041
|
+
await runGit(["config", `merge.${RESULTS_JSON_MERGE_DRIVER_NAME}.driver`, driverCommand], {
|
|
2456
3042
|
cwd: repoDir,
|
|
2457
3043
|
check: false
|
|
2458
3044
|
});
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
3045
|
+
writeResultsInfoAttributes(gitDir);
|
|
3046
|
+
}
|
|
3047
|
+
var RESULTS_INFO_ATTRIBUTES_BEGIN = "# >>> agentv results merge attributes >>>";
|
|
3048
|
+
var RESULTS_INFO_ATTRIBUTES_END = "# <<< agentv results merge attributes <<<";
|
|
3049
|
+
function writeResultsInfoAttributes(gitDir) {
|
|
3050
|
+
const infoDir = path5.join(gitDir, "info");
|
|
3051
|
+
const attributesPath = path5.join(infoDir, "attributes");
|
|
3052
|
+
const managedBlock = `${RESULTS_INFO_ATTRIBUTES_BEGIN}
|
|
3053
|
+
${RESULTS_REPO_GITATTRIBUTES_CONTENT.trimEnd()}
|
|
3054
|
+
${RESULTS_INFO_ATTRIBUTES_END}
|
|
3055
|
+
`;
|
|
3056
|
+
let existing = "";
|
|
3057
|
+
try {
|
|
3058
|
+
existing = readFileSync2(attributesPath, "utf8");
|
|
3059
|
+
} catch {
|
|
3060
|
+
existing = "";
|
|
3061
|
+
}
|
|
3062
|
+
const blockPattern = new RegExp(
|
|
3063
|
+
`${escapeRegExp(RESULTS_INFO_ATTRIBUTES_BEGIN)}[\\s\\S]*?${escapeRegExp(
|
|
3064
|
+
RESULTS_INFO_ATTRIBUTES_END
|
|
3065
|
+
)}\\n?`
|
|
3066
|
+
);
|
|
3067
|
+
const withoutManaged = existing.replace(blockPattern, "");
|
|
3068
|
+
const next = withoutManaged.length > 0 && !withoutManaged.endsWith("\n") ? `${withoutManaged}
|
|
3069
|
+
${managedBlock}` : `${withoutManaged}${managedBlock}`;
|
|
3070
|
+
if (next === existing) {
|
|
3071
|
+
return;
|
|
3072
|
+
}
|
|
3073
|
+
mkdirSync(infoDir, { recursive: true });
|
|
3074
|
+
writeFileSync(attributesPath, next);
|
|
3075
|
+
}
|
|
3076
|
+
function escapeRegExp(value) {
|
|
3077
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3078
|
+
}
|
|
3079
|
+
async function addResultsGitattributesToIndex(repoDir, indexEnv) {
|
|
3080
|
+
const { stdout: existing } = await runGit(
|
|
3081
|
+
["cat-file", "-p", `:0:${RESULTS_REPO_GITATTRIBUTES_FILE}`],
|
|
3082
|
+
{ cwd: repoDir, env: indexEnv, check: false }
|
|
3083
|
+
);
|
|
3084
|
+
if (existing === RESULTS_REPO_GITATTRIBUTES_CONTENT) {
|
|
2462
3085
|
return;
|
|
2463
3086
|
}
|
|
2464
|
-
|
|
2465
|
-
|
|
3087
|
+
const scratchDir = mkdtempSync(path5.join(os.tmpdir(), "agentv-results-attrs-"));
|
|
3088
|
+
try {
|
|
3089
|
+
const scratchFile = path5.join(scratchDir, RESULTS_REPO_GITATTRIBUTES_FILE);
|
|
3090
|
+
writeFileSync(scratchFile, RESULTS_REPO_GITATTRIBUTES_CONTENT);
|
|
3091
|
+
const { stdout: blob } = await runGit(["hash-object", "-w", "--no-filters", scratchFile], {
|
|
3092
|
+
cwd: repoDir
|
|
3093
|
+
});
|
|
3094
|
+
await runGit(
|
|
3095
|
+
[
|
|
3096
|
+
"update-index",
|
|
3097
|
+
"--add",
|
|
3098
|
+
"--cacheinfo",
|
|
3099
|
+
`100644,${blob.trim()},${RESULTS_REPO_GITATTRIBUTES_FILE}`
|
|
3100
|
+
],
|
|
3101
|
+
{ cwd: repoDir, env: indexEnv }
|
|
3102
|
+
);
|
|
3103
|
+
} finally {
|
|
3104
|
+
rmSync(scratchDir, { recursive: true, force: true });
|
|
2466
3105
|
}
|
|
2467
3106
|
}
|
|
2468
3107
|
function updateStatusFile(config, patch) {
|
|
@@ -2480,24 +3119,27 @@ async function ensureResultsRepoClone(config) {
|
|
|
2480
3119
|
if (!await isGitRepository(normalized.repo_path)) {
|
|
2481
3120
|
throw new Error(`Results repo_path is not a git repository: ${normalized.repo_path}`);
|
|
2482
3121
|
}
|
|
2483
|
-
|
|
3122
|
+
const topLevel = await resolveGitTopLevel(normalized.repo_path);
|
|
3123
|
+
await ensureResultsMergeConfig(topLevel);
|
|
3124
|
+
return topLevel;
|
|
2484
3125
|
}
|
|
2485
3126
|
const cachePaths = getResultsRepoLocalPaths(normalized.repo);
|
|
2486
3127
|
const cloneDir = normalized.path;
|
|
2487
3128
|
mkdirSync(cachePaths.rootDir, { recursive: true });
|
|
2488
|
-
mkdirSync(
|
|
3129
|
+
mkdirSync(path5.dirname(cloneDir), { recursive: true });
|
|
2489
3130
|
const cloneMissing = !existsSync(cloneDir);
|
|
2490
|
-
const gitDir =
|
|
3131
|
+
const gitDir = path5.join(cloneDir, ".git");
|
|
2491
3132
|
const cloneEmpty = !cloneMissing && !existsSync(gitDir) && (await readdir(cloneDir)).length === 0;
|
|
2492
3133
|
if (cloneMissing || cloneEmpty) {
|
|
2493
3134
|
try {
|
|
2494
3135
|
await runGit([
|
|
2495
3136
|
"clone",
|
|
2496
3137
|
"--filter=blob:none",
|
|
3138
|
+
...normalized.remote === "origin" ? [] : ["--origin", normalized.remote],
|
|
2497
3139
|
resolveResultsRepoUrl(normalized.repo_url ?? normalized.repo),
|
|
2498
3140
|
cloneDir
|
|
2499
3141
|
]);
|
|
2500
|
-
await
|
|
3142
|
+
await ensureResultsMergeConfig(cloneDir);
|
|
2501
3143
|
return cloneDir;
|
|
2502
3144
|
} catch (error) {
|
|
2503
3145
|
updateStatusFile(normalized, { last_error: withFriendlyGitHubAuthError(error).message });
|
|
@@ -2507,7 +3149,7 @@ async function ensureResultsRepoClone(config) {
|
|
|
2507
3149
|
if (!existsSync(gitDir)) {
|
|
2508
3150
|
throw new Error(`Results repo clone path is not a git repository: ${cloneDir}`);
|
|
2509
3151
|
}
|
|
2510
|
-
await
|
|
3152
|
+
await ensureResultsMergeConfig(cloneDir);
|
|
2511
3153
|
return cloneDir;
|
|
2512
3154
|
}
|
|
2513
3155
|
function getResultsRepoStatus(config) {
|
|
@@ -2653,7 +3295,7 @@ async function getStorageBranchWorktreeDirtyPaths(repoDir, config) {
|
|
|
2653
3295
|
const comparisonRef = await gitRefExists(repoDir, localRef) ? localRef : await gitRefExists(repoDir, remoteRef) ? remoteRef : void 0;
|
|
2654
3296
|
const dirtyPaths = [];
|
|
2655
3297
|
for (const gitPath of safeDirtyPaths) {
|
|
2656
|
-
const absolutePath =
|
|
3298
|
+
const absolutePath = path5.join(repoDir, ...gitPath.split("/"));
|
|
2657
3299
|
if (!existsSync(absolutePath)) {
|
|
2658
3300
|
if (await readGitText(repoDir, comparisonRef, gitPath) !== void 0) {
|
|
2659
3301
|
dirtyPaths.push(gitPath);
|
|
@@ -2759,7 +3401,7 @@ async function hasInProgressGitConflict(repoDir) {
|
|
|
2759
3401
|
check: false
|
|
2760
3402
|
});
|
|
2761
3403
|
const markerPath = stdout.trim();
|
|
2762
|
-
const resolvedMarkerPath =
|
|
3404
|
+
const resolvedMarkerPath = path5.isAbsolute(markerPath) ? markerPath : path5.join(repoDir, markerPath);
|
|
2763
3405
|
if (markerPath && existsSync(resolvedMarkerPath)) {
|
|
2764
3406
|
return true;
|
|
2765
3407
|
}
|
|
@@ -2859,6 +3501,17 @@ var ResultsBranchPushConflictError = class extends Error {
|
|
|
2859
3501
|
this.name = "ResultsBranchPushConflictError";
|
|
2860
3502
|
}
|
|
2861
3503
|
};
|
|
3504
|
+
function pendingMergeToWire(details) {
|
|
3505
|
+
return {
|
|
3506
|
+
temp_branch: details.tempBranch,
|
|
3507
|
+
target_branch: details.targetBranch,
|
|
3508
|
+
...details.compareUrl !== void 0 && { compare_url: details.compareUrl },
|
|
3509
|
+
...details.contributedRunCount !== void 0 && {
|
|
3510
|
+
contributed_run_count: details.contributedRunCount
|
|
3511
|
+
},
|
|
3512
|
+
created_at: details.createdAt
|
|
3513
|
+
};
|
|
3514
|
+
}
|
|
2862
3515
|
function pushDetailsToWire(details) {
|
|
2863
3516
|
if (!details) {
|
|
2864
3517
|
return {};
|
|
@@ -2876,7 +3529,11 @@ function pushDetailsToWire(details) {
|
|
|
2876
3529
|
...details.forcePushedCommit !== void 0 && {
|
|
2877
3530
|
force_pushed_commit: details.forcePushedCommit
|
|
2878
3531
|
},
|
|
2879
|
-
...details.leaseCommit !== void 0 && { lease_commit: details.leaseCommit }
|
|
3532
|
+
...details.leaseCommit !== void 0 && { lease_commit: details.leaseCommit },
|
|
3533
|
+
...details.autoMergedRemote === true && { auto_merged_remote: true },
|
|
3534
|
+
...details.pendingMerge !== void 0 && {
|
|
3535
|
+
pending_merge: pendingMergeToWire(details.pendingMerge)
|
|
3536
|
+
}
|
|
2880
3537
|
};
|
|
2881
3538
|
}
|
|
2882
3539
|
function withBlockedStatus(status, blockReason, flags) {
|
|
@@ -2894,11 +3551,13 @@ function withPushConflictStatus(status, blockReason, details, flags) {
|
|
|
2894
3551
|
return withBlockedStatus(
|
|
2895
3552
|
{
|
|
2896
3553
|
...status,
|
|
2897
|
-
sync_status: "push_conflict"
|
|
3554
|
+
sync_status: flags.syncStatus ?? "push_conflict"
|
|
2898
3555
|
},
|
|
2899
3556
|
blockReason,
|
|
2900
3557
|
{
|
|
2901
|
-
|
|
3558
|
+
pullPerformed: flags.pullPerformed,
|
|
3559
|
+
pushPerformed: flags.pushPerformed,
|
|
3560
|
+
commitCreated: flags.commitCreated,
|
|
2902
3561
|
pushDetails: details
|
|
2903
3562
|
}
|
|
2904
3563
|
);
|
|
@@ -2914,12 +3573,12 @@ function withActionFlags(status, flags) {
|
|
|
2914
3573
|
};
|
|
2915
3574
|
}
|
|
2916
3575
|
function isSafeResultsRepoPath(p) {
|
|
2917
|
-
return RESULTS_REPO_TRACKED_DIRS.some((dir) => p === dir || p.startsWith(`${dir}/`));
|
|
3576
|
+
return p === RESULTS_REPO_GITATTRIBUTES_FILE || RESULTS_REPO_TRACKED_DIRS.some((dir) => p === dir || p.startsWith(`${dir}/`));
|
|
2918
3577
|
}
|
|
2919
3578
|
async function existingTrackedResultsDirs(repoDir) {
|
|
2920
3579
|
const targets = [];
|
|
2921
3580
|
for (const dir of RESULTS_REPO_TRACKED_DIRS) {
|
|
2922
|
-
if (existsSync(
|
|
3581
|
+
if (existsSync(path5.join(repoDir, dir))) {
|
|
2923
3582
|
targets.push(dir);
|
|
2924
3583
|
continue;
|
|
2925
3584
|
}
|
|
@@ -2947,19 +3606,8 @@ function getPushTargetBranch(upstream, baseBranch, remote = "origin") {
|
|
|
2947
3606
|
const prefix = `${remote}/`;
|
|
2948
3607
|
return upstream?.startsWith(prefix) ? upstream.slice(prefix.length) : baseBranch;
|
|
2949
3608
|
}
|
|
2950
|
-
function
|
|
2951
|
-
|
|
2952
|
-
return `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}T${pad(
|
|
2953
|
-
date.getUTCHours()
|
|
2954
|
-
)}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}Z`;
|
|
2955
|
-
}
|
|
2956
|
-
function slugifyBackupTargetBranch(branch) {
|
|
2957
|
-
return branch.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "results";
|
|
2958
|
-
}
|
|
2959
|
-
function buildResultsBackupRef(targetBranch, remoteCommit) {
|
|
2960
|
-
return `agentv/backups/${timestampForBackupRef()}-${slugifyBackupTargetBranch(
|
|
2961
|
-
targetBranch
|
|
2962
|
-
)}-${remoteCommit.slice(0, 7)}`;
|
|
3609
|
+
function formatShortSha(sha) {
|
|
3610
|
+
return sha ? sha.slice(0, 12) : "unknown";
|
|
2963
3611
|
}
|
|
2964
3612
|
async function getCommitSha(repoDir, ref) {
|
|
2965
3613
|
if (!ref) {
|
|
@@ -2976,91 +3624,309 @@ function isNonFastForwardPushError(error) {
|
|
|
2976
3624
|
const text = gitErrorText(error);
|
|
2977
3625
|
return text.includes("non-fast-forward") || text.includes("fetch first") || text.includes("tip is behind its remote") || text.includes("note about fast-forwards") || text.includes("stale info");
|
|
2978
3626
|
}
|
|
2979
|
-
|
|
2980
|
-
|
|
3627
|
+
var RESULTS_PUSH_MERGE_MAX_ATTEMPTS = 5;
|
|
3628
|
+
async function isAncestorCommit(repoDir, ancestor, descendant) {
|
|
3629
|
+
const { exitCode } = await runGit(["merge-base", "--is-ancestor", ancestor, descendant], {
|
|
3630
|
+
cwd: repoDir,
|
|
3631
|
+
check: false
|
|
3632
|
+
});
|
|
3633
|
+
return exitCode === 0;
|
|
2981
3634
|
}
|
|
2982
|
-
function
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
3635
|
+
async function getMergeBaseCommit(repoDir, a, b) {
|
|
3636
|
+
const { stdout, exitCode } = await runGit(["merge-base", a, b], { cwd: repoDir, check: false });
|
|
3637
|
+
const sha = stdout.trim();
|
|
3638
|
+
return exitCode === 0 && sha.length > 0 ? sha : void 0;
|
|
2986
3639
|
}
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3640
|
+
function buildNeedsHumanMergeReason(targetBranch, remoteCommit, localCommit) {
|
|
3641
|
+
return `Results branch ${targetBranch} diverged from the remote and could not be auto-merged: a genuine content conflict in the editable overlay remains (remote ${formatShortSha(
|
|
3642
|
+
remoteCommit
|
|
3643
|
+
)}, local ${formatShortSha(
|
|
3644
|
+
localCommit
|
|
3645
|
+
)}). The remote branch is unchanged and no history was rewritten; resolve it with a GitHub pull request.`;
|
|
3646
|
+
}
|
|
3647
|
+
function timestampForResultsRef(date = /* @__PURE__ */ new Date()) {
|
|
3648
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
3649
|
+
return `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}T${pad(
|
|
3650
|
+
date.getUTCHours()
|
|
3651
|
+
)}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}Z`;
|
|
3652
|
+
}
|
|
3653
|
+
function slugifyResultsTargetBranch(branch) {
|
|
3654
|
+
return branch.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "results";
|
|
3655
|
+
}
|
|
3656
|
+
function randomResultsRefToken() {
|
|
3657
|
+
return randomBytes(4).toString("hex").slice(0, 6);
|
|
3658
|
+
}
|
|
3659
|
+
function buildResultsSyncBranchName(targetBranch, token = randomResultsRefToken()) {
|
|
3660
|
+
return `agentv/results-sync/${timestampForResultsRef()}-${slugifyResultsTargetBranch(
|
|
3661
|
+
targetBranch
|
|
3662
|
+
)}-${token}`;
|
|
3663
|
+
}
|
|
3664
|
+
function buildResultsCompareUrl(remoteUrl, targetBranch, syncBranch) {
|
|
3665
|
+
if (!remoteUrl) {
|
|
3666
|
+
return void 0;
|
|
3008
3667
|
}
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
blockReason: buildBlockedPushConflictReason(baseDetails),
|
|
3013
|
-
details: baseDetails
|
|
3014
|
-
};
|
|
3668
|
+
const match = remoteUrl.trim().match(/github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);
|
|
3669
|
+
if (!match) {
|
|
3670
|
+
return void 0;
|
|
3015
3671
|
}
|
|
3016
|
-
const
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
);
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
blockReason: `Results branch backup creation failed for ${params.targetBranch} at ${formatShortSha(
|
|
3032
|
-
remoteCommit
|
|
3033
|
-
)}: ${getStatusMessage(error)}`,
|
|
3034
|
-
details: backupDetails
|
|
3035
|
-
};
|
|
3672
|
+
const [, owner, repo] = match;
|
|
3673
|
+
return `https://github.com/${owner}/${repo}/compare/${encodeURIComponent(
|
|
3674
|
+
targetBranch
|
|
3675
|
+
)}...${encodeURIComponent(syncBranch)}?expand=1`;
|
|
3676
|
+
}
|
|
3677
|
+
async function resolveRemotePushUrl(repoDir, remote) {
|
|
3678
|
+
for (const args of [
|
|
3679
|
+
["remote", "get-url", "--push", remote],
|
|
3680
|
+
["remote", "get-url", remote]
|
|
3681
|
+
]) {
|
|
3682
|
+
const { stdout, exitCode } = await runGit(args, { cwd: repoDir, check: false });
|
|
3683
|
+
const url = stdout.trim();
|
|
3684
|
+
if (exitCode === 0 && url.length > 0) {
|
|
3685
|
+
return url;
|
|
3686
|
+
}
|
|
3036
3687
|
}
|
|
3688
|
+
return void 0;
|
|
3689
|
+
}
|
|
3690
|
+
async function countContributedRunDirs(repoDir, remoteTargetCommit, sourceCommit) {
|
|
3691
|
+
if (!remoteTargetCommit) {
|
|
3692
|
+
return void 0;
|
|
3693
|
+
}
|
|
3694
|
+
const { stdout, exitCode } = await runGit(
|
|
3695
|
+
[
|
|
3696
|
+
"diff",
|
|
3697
|
+
"--name-only",
|
|
3698
|
+
`${remoteTargetCommit}...${sourceCommit}`,
|
|
3699
|
+
"--",
|
|
3700
|
+
`${RESULTS_REPO_RUNS_DIR}/`
|
|
3701
|
+
],
|
|
3702
|
+
{ cwd: repoDir, check: false }
|
|
3703
|
+
);
|
|
3704
|
+
if (exitCode !== 0) {
|
|
3705
|
+
return void 0;
|
|
3706
|
+
}
|
|
3707
|
+
const runDirs = /* @__PURE__ */ new Set();
|
|
3708
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
3709
|
+
const file = line.trim();
|
|
3710
|
+
if (!file) {
|
|
3711
|
+
continue;
|
|
3712
|
+
}
|
|
3713
|
+
const segments = file.split("/");
|
|
3714
|
+
if (segments[0] === RESULTS_REPO_RUNS_DIR && segments.length >= 3) {
|
|
3715
|
+
runDirs.add(`${segments[0]}/${segments[1]}/${segments[2]}`);
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
return runDirs.size;
|
|
3719
|
+
}
|
|
3720
|
+
async function pushResultsSyncBranch(params) {
|
|
3721
|
+
const { repoDir, remote, sourceCommit, targetBranch } = params;
|
|
3722
|
+
const remoteTargetCommit = await getCommitSha(repoDir, remoteBranchRef(targetBranch, remote));
|
|
3723
|
+
let syncBranch = buildResultsSyncBranchName(targetBranch);
|
|
3724
|
+
await assertValidResultsBranchName(repoDir, syncBranch);
|
|
3037
3725
|
try {
|
|
3038
|
-
await runGit(
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
"--porcelain",
|
|
3042
|
-
`--force-with-lease=refs/heads/${params.targetBranch}:${remoteCommit}`,
|
|
3043
|
-
params.normalized.remote,
|
|
3044
|
-
`${params.sourceRef}:refs/heads/${params.targetBranch}`
|
|
3045
|
-
],
|
|
3046
|
-
{ cwd: params.repoDir }
|
|
3047
|
-
);
|
|
3726
|
+
await runGit(["push", "--porcelain", remote, `${sourceCommit}:refs/heads/${syncBranch}`], {
|
|
3727
|
+
cwd: repoDir
|
|
3728
|
+
});
|
|
3048
3729
|
} catch (error) {
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3730
|
+
if (!isRefAlreadyExistsPushError(error)) {
|
|
3731
|
+
throw error;
|
|
3732
|
+
}
|
|
3733
|
+
syncBranch = buildResultsSyncBranchName(targetBranch);
|
|
3734
|
+
await assertValidResultsBranchName(repoDir, syncBranch);
|
|
3735
|
+
await runGit(["push", "--porcelain", remote, `${sourceCommit}:refs/heads/${syncBranch}`], {
|
|
3736
|
+
cwd: repoDir
|
|
3737
|
+
});
|
|
3056
3738
|
}
|
|
3739
|
+
const compareUrl = buildResultsCompareUrl(
|
|
3740
|
+
await resolveRemotePushUrl(repoDir, remote),
|
|
3741
|
+
targetBranch,
|
|
3742
|
+
syncBranch
|
|
3743
|
+
);
|
|
3744
|
+
const contributedRunCount = await countContributedRunDirs(
|
|
3745
|
+
repoDir,
|
|
3746
|
+
remoteTargetCommit,
|
|
3747
|
+
sourceCommit
|
|
3748
|
+
).catch(() => void 0);
|
|
3057
3749
|
return {
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3750
|
+
tempBranch: syncBranch,
|
|
3751
|
+
targetBranch,
|
|
3752
|
+
...compareUrl !== void 0 && { compareUrl },
|
|
3753
|
+
...contributedRunCount !== void 0 && { contributedRunCount },
|
|
3754
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3757
|
+
function isRefAlreadyExistsPushError(error) {
|
|
3758
|
+
const text = gitErrorText(error);
|
|
3759
|
+
return text.includes("already exists") || text.includes("cannot lock ref") || text.includes("reference already exists");
|
|
3760
|
+
}
|
|
3761
|
+
async function mergeRemoteIntoCheckedOutBranch(repoDir, remoteCommit) {
|
|
3762
|
+
const args = ["merge", "--no-edit", "-m", "chore(results): merge remote results", remoteCommit];
|
|
3763
|
+
let result = await runGit(args, {
|
|
3764
|
+
cwd: repoDir,
|
|
3765
|
+
check: false,
|
|
3766
|
+
env: configuredGitCommitIdentityEnv()
|
|
3767
|
+
});
|
|
3768
|
+
if (result.exitCode !== 0 && isMissingGitIdentityError({ stderr: result.stderr })) {
|
|
3769
|
+
await runGit(["merge", "--abort"], { cwd: repoDir, check: false });
|
|
3770
|
+
result = await runGit(args, {
|
|
3771
|
+
cwd: repoDir,
|
|
3772
|
+
check: false,
|
|
3773
|
+
env: { ...configuredGitCommitIdentityEnv(), ...fallbackResultsRepoCommitEnv() }
|
|
3774
|
+
});
|
|
3775
|
+
}
|
|
3776
|
+
return result.exitCode;
|
|
3777
|
+
}
|
|
3778
|
+
async function resolveResultBranchPushConflict(params) {
|
|
3779
|
+
await ensureResultsMergeConfig(params.repoDir);
|
|
3780
|
+
const { repoDir, targetBranch } = params;
|
|
3781
|
+
const remote = params.normalized.remote;
|
|
3782
|
+
const localRef = params.sourceRef;
|
|
3783
|
+
const remoteRef = remoteBranchRef(targetBranch, remote);
|
|
3784
|
+
const branchCheckedOut = params.sourceRef === "HEAD";
|
|
3785
|
+
let lastDetails = {
|
|
3786
|
+
pushConflictPolicy: params.normalized.push_conflict_policy,
|
|
3787
|
+
targetBranch
|
|
3788
|
+
};
|
|
3789
|
+
const buildHumanMergeOutcome = async (reason, details) => {
|
|
3790
|
+
if (!details.localCommit) {
|
|
3791
|
+
return { blocked: true, blockReason: reason, details, syncStatus: "needs_human_merge" };
|
|
3792
|
+
}
|
|
3793
|
+
try {
|
|
3794
|
+
const pendingMerge = await pushResultsSyncBranch({
|
|
3795
|
+
repoDir,
|
|
3796
|
+
remote,
|
|
3797
|
+
sourceCommit: details.localCommit,
|
|
3798
|
+
targetBranch: details.targetBranch
|
|
3799
|
+
});
|
|
3800
|
+
return {
|
|
3801
|
+
blocked: true,
|
|
3802
|
+
blockReason: reason,
|
|
3803
|
+
details: { ...details, pendingMerge },
|
|
3804
|
+
syncStatus: "needs_human_merge"
|
|
3805
|
+
};
|
|
3806
|
+
} catch (error) {
|
|
3807
|
+
return {
|
|
3808
|
+
blocked: true,
|
|
3809
|
+
blockReason: `${reason} (could not create the temp branch for the GitHub merge: ${getStatusMessage(
|
|
3810
|
+
error
|
|
3811
|
+
)})`,
|
|
3812
|
+
details,
|
|
3813
|
+
syncStatus: "needs_human_merge"
|
|
3814
|
+
};
|
|
3062
3815
|
}
|
|
3063
3816
|
};
|
|
3817
|
+
for (let attempt = 1; attempt <= RESULTS_PUSH_MERGE_MAX_ATTEMPTS; attempt += 1) {
|
|
3818
|
+
await fetchResultsRepo(repoDir, remote, targetBranch);
|
|
3819
|
+
const remoteCommit = await getCommitSha(repoDir, remoteRef);
|
|
3820
|
+
const localCommit = await getCommitSha(repoDir, localRef);
|
|
3821
|
+
const details = {
|
|
3822
|
+
pushConflictPolicy: params.normalized.push_conflict_policy,
|
|
3823
|
+
targetBranch,
|
|
3824
|
+
...remoteCommit !== void 0 && { remoteCommit, previousRemoteCommit: remoteCommit },
|
|
3825
|
+
...localCommit !== void 0 && { localCommit }
|
|
3826
|
+
};
|
|
3827
|
+
lastDetails = details;
|
|
3828
|
+
if (!remoteCommit) {
|
|
3829
|
+
return {
|
|
3830
|
+
blocked: true,
|
|
3831
|
+
blockReason: `Results branch push conflict on ${targetBranch}: remote commit could not be resolved after fetch`,
|
|
3832
|
+
details,
|
|
3833
|
+
syncStatus: "push_conflict"
|
|
3834
|
+
};
|
|
3835
|
+
}
|
|
3836
|
+
if (!localCommit) {
|
|
3837
|
+
return {
|
|
3838
|
+
blocked: true,
|
|
3839
|
+
blockReason: `Results branch push conflict on ${targetBranch}: local commit could not be resolved`,
|
|
3840
|
+
details,
|
|
3841
|
+
syncStatus: "push_conflict"
|
|
3842
|
+
};
|
|
3843
|
+
}
|
|
3844
|
+
if (await isAncestorCommit(repoDir, localCommit, remoteCommit)) {
|
|
3845
|
+
if (branchCheckedOut) {
|
|
3846
|
+
await runGit(["merge", "--ff-only", remoteCommit], { cwd: repoDir, check: false });
|
|
3847
|
+
} else {
|
|
3848
|
+
await runGit(["update-ref", localRef, remoteCommit, localCommit], {
|
|
3849
|
+
cwd: repoDir,
|
|
3850
|
+
check: false
|
|
3851
|
+
});
|
|
3852
|
+
}
|
|
3853
|
+
return { blocked: false, details };
|
|
3854
|
+
}
|
|
3855
|
+
let pushSpec = branchCheckedOut ? "HEAD" : localCommit;
|
|
3856
|
+
let autoMergedRemote = false;
|
|
3857
|
+
if (!await isAncestorCommit(repoDir, remoteCommit, localCommit)) {
|
|
3858
|
+
if (branchCheckedOut) {
|
|
3859
|
+
const exitCode = await mergeRemoteIntoCheckedOutBranch(repoDir, remoteCommit);
|
|
3860
|
+
if (exitCode !== 0) {
|
|
3861
|
+
await runGit(["merge", "--abort"], { cwd: repoDir, check: false });
|
|
3862
|
+
return await buildHumanMergeOutcome(
|
|
3863
|
+
buildNeedsHumanMergeReason(targetBranch, remoteCommit, localCommit),
|
|
3864
|
+
details
|
|
3865
|
+
);
|
|
3866
|
+
}
|
|
3867
|
+
pushSpec = "HEAD";
|
|
3868
|
+
autoMergedRemote = true;
|
|
3869
|
+
} else {
|
|
3870
|
+
const base = await getMergeBaseCommit(repoDir, localCommit, remoteCommit);
|
|
3871
|
+
if (!base) {
|
|
3872
|
+
return await buildHumanMergeOutcome(
|
|
3873
|
+
buildNeedsHumanMergeReason(targetBranch, remoteCommit, localCommit),
|
|
3874
|
+
details
|
|
3875
|
+
);
|
|
3876
|
+
}
|
|
3877
|
+
const merge = await runGit(
|
|
3878
|
+
["merge-tree", "--write-tree", `--merge-base=${base}`, localCommit, remoteCommit],
|
|
3879
|
+
{ cwd: repoDir, check: false }
|
|
3880
|
+
);
|
|
3881
|
+
const mergedTree = merge.stdout.trim().split(/\r?\n/)[0]?.trim();
|
|
3882
|
+
if (merge.exitCode !== 0 || !mergedTree) {
|
|
3883
|
+
return await buildHumanMergeOutcome(
|
|
3884
|
+
buildNeedsHumanMergeReason(targetBranch, remoteCommit, localCommit),
|
|
3885
|
+
details
|
|
3886
|
+
);
|
|
3887
|
+
}
|
|
3888
|
+
const { stdout: mergeCommitOut } = await runGitWithFallbackCommitIdentity(
|
|
3889
|
+
[
|
|
3890
|
+
"commit-tree",
|
|
3891
|
+
mergedTree,
|
|
3892
|
+
"-p",
|
|
3893
|
+
localCommit,
|
|
3894
|
+
"-p",
|
|
3895
|
+
remoteCommit,
|
|
3896
|
+
"-m",
|
|
3897
|
+
"chore(results): merge remote results"
|
|
3898
|
+
],
|
|
3899
|
+
{ cwd: repoDir }
|
|
3900
|
+
);
|
|
3901
|
+
pushSpec = mergeCommitOut.trim();
|
|
3902
|
+
await runGit(["update-ref", localRef, pushSpec, localCommit], { cwd: repoDir });
|
|
3903
|
+
autoMergedRemote = true;
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
try {
|
|
3907
|
+
await runGit(["push", "--porcelain", remote, `${pushSpec}:refs/heads/${targetBranch}`], {
|
|
3908
|
+
cwd: repoDir
|
|
3909
|
+
});
|
|
3910
|
+
} catch (error) {
|
|
3911
|
+
if (isNonFastForwardPushError(error)) {
|
|
3912
|
+
continue;
|
|
3913
|
+
}
|
|
3914
|
+
throw error;
|
|
3915
|
+
}
|
|
3916
|
+
const pushedCommit = await getCommitSha(repoDir, pushSpec) ?? localCommit;
|
|
3917
|
+
return {
|
|
3918
|
+
blocked: false,
|
|
3919
|
+
details: {
|
|
3920
|
+
...details,
|
|
3921
|
+
localCommit: pushedCommit,
|
|
3922
|
+
...autoMergedRemote && { autoMergedRemote }
|
|
3923
|
+
}
|
|
3924
|
+
};
|
|
3925
|
+
}
|
|
3926
|
+
return await buildHumanMergeOutcome(
|
|
3927
|
+
`Results branch ${targetBranch} could not be reconciled after ${RESULTS_PUSH_MERGE_MAX_ATTEMPTS} attempts because the remote kept advancing; retry sync. The remote branch is unchanged.`,
|
|
3928
|
+
lastDetails
|
|
3929
|
+
);
|
|
3064
3930
|
}
|
|
3065
3931
|
async function statusFromInspection(normalized, repoDir) {
|
|
3066
3932
|
return withGitInspection(
|
|
@@ -3087,7 +3953,6 @@ async function getResultsRepoSyncStatus(config) {
|
|
|
3087
3953
|
};
|
|
3088
3954
|
}
|
|
3089
3955
|
try {
|
|
3090
|
-
await ensureResultsRepoRemote(normalized.path, normalized);
|
|
3091
3956
|
if (usesStorageBranchWorktree(normalized)) {
|
|
3092
3957
|
await fetchResultsRepo(normalized.path, normalized.remote, normalized.branch).catch(
|
|
3093
3958
|
() => void 0
|
|
@@ -3252,7 +4117,8 @@ async function syncResultsRepoForProject(config) {
|
|
|
3252
4117
|
return withPushConflictStatus(status2, outcome.blockReason, outcome.details, {
|
|
3253
4118
|
pullPerformed,
|
|
3254
4119
|
pushPerformed,
|
|
3255
|
-
commitCreated
|
|
4120
|
+
commitCreated,
|
|
4121
|
+
syncStatus: outcome.syncStatus
|
|
3256
4122
|
});
|
|
3257
4123
|
}
|
|
3258
4124
|
pushPerformed = true;
|
|
@@ -3369,7 +4235,8 @@ async function syncResultsRepoForProject(config) {
|
|
|
3369
4235
|
return withPushConflictStatus(status2, outcome.blockReason, outcome.details, {
|
|
3370
4236
|
pullPerformed,
|
|
3371
4237
|
pushPerformed,
|
|
3372
|
-
commitCreated
|
|
4238
|
+
commitCreated,
|
|
4239
|
+
syncStatus: outcome.syncStatus
|
|
3373
4240
|
});
|
|
3374
4241
|
}
|
|
3375
4242
|
} else {
|
|
@@ -3502,7 +4369,8 @@ async function syncResultsRepoForProject(config) {
|
|
|
3502
4369
|
return withPushConflictStatus(status, outcome.blockReason, outcome.details, {
|
|
3503
4370
|
pullPerformed,
|
|
3504
4371
|
pushPerformed,
|
|
3505
|
-
commitCreated
|
|
4372
|
+
commitCreated,
|
|
4373
|
+
syncStatus: outcome.syncStatus
|
|
3506
4374
|
});
|
|
3507
4375
|
}
|
|
3508
4376
|
pushPerformed = true;
|
|
@@ -3587,7 +4455,8 @@ async function syncResultsRepoForProject(config) {
|
|
|
3587
4455
|
return withPushConflictStatus(status, outcome.blockReason, outcome.details, {
|
|
3588
4456
|
pullPerformed,
|
|
3589
4457
|
pushPerformed,
|
|
3590
|
-
commitCreated
|
|
4458
|
+
commitCreated,
|
|
4459
|
+
syncStatus: outcome.syncStatus
|
|
3591
4460
|
});
|
|
3592
4461
|
}
|
|
3593
4462
|
} else {
|
|
@@ -3625,6 +4494,44 @@ async function syncResultsRepoForProject(config) {
|
|
|
3625
4494
|
activeResultsRepoSyncs.delete(syncKey);
|
|
3626
4495
|
}
|
|
3627
4496
|
}
|
|
4497
|
+
async function fastForwardResultsTowardTarget(repoDir, normalized, targetBranch) {
|
|
4498
|
+
const upstreamRef = remoteBranchRef(targetBranch, normalized.remote);
|
|
4499
|
+
if (!await gitRefExists(repoDir, upstreamRef)) {
|
|
4500
|
+
return;
|
|
4501
|
+
}
|
|
4502
|
+
const upstreamCommit = await getCommitSha(repoDir, upstreamRef);
|
|
4503
|
+
if (!upstreamCommit) {
|
|
4504
|
+
return;
|
|
4505
|
+
}
|
|
4506
|
+
if (usesStorageBranchWorktree(normalized) && normalized.branch) {
|
|
4507
|
+
const localRef = `refs/heads/${normalized.branch}`;
|
|
4508
|
+
if (!await gitRefExists(repoDir, localRef)) {
|
|
4509
|
+
await runGit(["update-ref", localRef, upstreamCommit], { cwd: repoDir, check: false });
|
|
4510
|
+
await runGit(["branch", "--set-upstream-to", upstreamRef, normalized.branch], {
|
|
4511
|
+
cwd: repoDir,
|
|
4512
|
+
check: false
|
|
4513
|
+
});
|
|
4514
|
+
return;
|
|
4515
|
+
}
|
|
4516
|
+
const localCommit = await getCommitSha(repoDir, localRef);
|
|
4517
|
+
if (localCommit && await isAncestorCommit(repoDir, localCommit, upstreamCommit)) {
|
|
4518
|
+
await runGit(["update-ref", localRef, upstreamCommit, localCommit], {
|
|
4519
|
+
cwd: repoDir,
|
|
4520
|
+
check: false
|
|
4521
|
+
});
|
|
4522
|
+
}
|
|
4523
|
+
return;
|
|
4524
|
+
}
|
|
4525
|
+
await runGit(["merge", "--ff-only", upstreamRef], { cwd: repoDir, check: false });
|
|
4526
|
+
}
|
|
4527
|
+
async function confirmResultsMergeAndPull(config) {
|
|
4528
|
+
const normalized = normalizeResultsConfig(config);
|
|
4529
|
+
const repoDir = await ensureResultsRepoClone(normalized);
|
|
4530
|
+
const targetBranch = normalized.branch ?? await resolveDefaultBranch(repoDir);
|
|
4531
|
+
await fetchResultsRepo(repoDir, normalized.remote, normalized.branch).catch(() => void 0);
|
|
4532
|
+
await fastForwardResultsTowardTarget(repoDir, normalized, targetBranch);
|
|
4533
|
+
return syncResultsRepoForProject(config);
|
|
4534
|
+
}
|
|
3628
4535
|
async function checkoutResultsRepoBranch(config, branchName) {
|
|
3629
4536
|
const normalized = normalizeResultsConfig(config);
|
|
3630
4537
|
const repoDir = await ensureResultsRepoClone(normalized);
|
|
@@ -3645,8 +4552,8 @@ async function prepareResultsRepoBranch(config, branchName) {
|
|
|
3645
4552
|
const cloneDir = await ensureResultsRepoClone(normalized);
|
|
3646
4553
|
const baseBranch = await resolveDefaultBranch(cloneDir);
|
|
3647
4554
|
await fetchResultsRepo(cloneDir, normalized.remote);
|
|
3648
|
-
const worktreeRoot = await mkdtemp(
|
|
3649
|
-
const worktreeDir =
|
|
4555
|
+
const worktreeRoot = await mkdtemp(path5.join(os.tmpdir(), "agentv-results-repo-"));
|
|
4556
|
+
const worktreeDir = path5.join(worktreeRoot, "repo");
|
|
3650
4557
|
await runGit(
|
|
3651
4558
|
["worktree", "add", "-B", branchName, worktreeDir, `${normalized.remote}/${baseBranch}`],
|
|
3652
4559
|
{
|
|
@@ -3668,12 +4575,12 @@ async function prepareResultsRepoBranch(config, branchName) {
|
|
|
3668
4575
|
}
|
|
3669
4576
|
async function stageResultsArtifacts(params) {
|
|
3670
4577
|
rmSync(params.destinationDir, { recursive: true, force: true });
|
|
3671
|
-
mkdirSync(
|
|
4578
|
+
mkdirSync(path5.dirname(params.destinationDir), { recursive: true });
|
|
3672
4579
|
await cp(params.sourceDir, params.destinationDir, { recursive: true });
|
|
3673
4580
|
}
|
|
3674
4581
|
function resolveResultsRepoRunsDir(config) {
|
|
3675
4582
|
const normalized = normalizeResultsConfig(config);
|
|
3676
|
-
return
|
|
4583
|
+
return path5.join(normalized.path, RESULTS_REPO_RESULTS_DIR);
|
|
3677
4584
|
}
|
|
3678
4585
|
async function directorySizeBytes(targetPath) {
|
|
3679
4586
|
const entry = await stat(targetPath);
|
|
@@ -3682,7 +4589,7 @@ async function directorySizeBytes(targetPath) {
|
|
|
3682
4589
|
}
|
|
3683
4590
|
let total = 0;
|
|
3684
4591
|
for (const child of await readdir(targetPath, { withFileTypes: true })) {
|
|
3685
|
-
total += await directorySizeBytes(
|
|
4592
|
+
total += await directorySizeBytes(path5.join(targetPath, child.name));
|
|
3686
4593
|
}
|
|
3687
4594
|
return total;
|
|
3688
4595
|
}
|
|
@@ -3749,7 +4656,7 @@ async function assertValidResultsBranchName(repoDir, branch) {
|
|
|
3749
4656
|
await runGit(["check-ref-format", "--branch", branch], { cwd: repoDir });
|
|
3750
4657
|
}
|
|
3751
4658
|
function normalizeDestinationPath(destinationPath) {
|
|
3752
|
-
const normalized = destinationPath.split(
|
|
4659
|
+
const normalized = destinationPath.split(path5.sep).join("/");
|
|
3753
4660
|
const segments = normalized.split("/").filter(Boolean);
|
|
3754
4661
|
if (segments.length === 0 || normalized.startsWith("/") || segments.some((segment) => segment === "..") || normalized.split("").some((char) => {
|
|
3755
4662
|
const code = char.charCodeAt(0);
|
|
@@ -3763,7 +4670,7 @@ async function listSourceFiles(sourceDir) {
|
|
|
3763
4670
|
const entries = [];
|
|
3764
4671
|
async function visit(dir) {
|
|
3765
4672
|
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
3766
|
-
const absolutePath =
|
|
4673
|
+
const absolutePath = path5.join(dir, entry.name);
|
|
3767
4674
|
if (entry.isDirectory()) {
|
|
3768
4675
|
await visit(absolutePath);
|
|
3769
4676
|
} else if (entry.isFile() || entry.isSymbolicLink()) {
|
|
@@ -3775,16 +4682,16 @@ async function listSourceFiles(sourceDir) {
|
|
|
3775
4682
|
entries.sort();
|
|
3776
4683
|
return entries;
|
|
3777
4684
|
}
|
|
3778
|
-
function
|
|
4685
|
+
function isRecord3(value) {
|
|
3779
4686
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
3780
4687
|
}
|
|
3781
4688
|
function artifactSidecarPointers(record) {
|
|
3782
|
-
if (!
|
|
4689
|
+
if (!isRecord3(record) || !isRecord3(record.artifact_pointers)) {
|
|
3783
4690
|
return [];
|
|
3784
4691
|
}
|
|
3785
4692
|
const pointers = [];
|
|
3786
4693
|
for (const pointer of Object.values(record.artifact_pointers)) {
|
|
3787
|
-
if (!
|
|
4694
|
+
if (!isRecord3(pointer)) {
|
|
3788
4695
|
continue;
|
|
3789
4696
|
}
|
|
3790
4697
|
if (pointer.ref !== AGENTV_RESULTS_ARTIFACTS_REF || typeof pointer.path !== "string") {
|
|
@@ -3799,14 +4706,14 @@ function artifactSidecarPointers(record) {
|
|
|
3799
4706
|
return pointers;
|
|
3800
4707
|
}
|
|
3801
4708
|
function artifactSidecarKey(destinationPath, pointerPath) {
|
|
3802
|
-
return
|
|
4709
|
+
return path5.posix.join(
|
|
3803
4710
|
RESULTS_REPO_RUNS_DIR,
|
|
3804
4711
|
normalizeDestinationPath(destinationPath),
|
|
3805
4712
|
normalizeDestinationPath(pointerPath)
|
|
3806
4713
|
);
|
|
3807
4714
|
}
|
|
3808
4715
|
function collectArtifactSidecarPointers(sourceDir) {
|
|
3809
|
-
const indexPath =
|
|
4716
|
+
const indexPath = path5.join(sourceDir, RESULT_INDEX_FILENAME2);
|
|
3810
4717
|
if (!existsSync(indexPath)) {
|
|
3811
4718
|
return [];
|
|
3812
4719
|
}
|
|
@@ -3825,16 +4732,16 @@ function collectArtifactSidecarPointers(sourceDir) {
|
|
|
3825
4732
|
}
|
|
3826
4733
|
function resolveArtifactPointerSourcePath(sourceDir, pointerPath) {
|
|
3827
4734
|
const normalizedPointerPath = normalizeDestinationPath(pointerPath);
|
|
3828
|
-
const sourceRoot =
|
|
3829
|
-
const sourcePath =
|
|
3830
|
-
const relative =
|
|
3831
|
-
if (relative.startsWith("..") ||
|
|
4735
|
+
const sourceRoot = path5.resolve(sourceDir);
|
|
4736
|
+
const sourcePath = path5.resolve(sourceRoot, ...normalizedPointerPath.split("/"));
|
|
4737
|
+
const relative = path5.relative(sourceRoot, sourcePath);
|
|
4738
|
+
if (relative.startsWith("..") || path5.isAbsolute(relative)) {
|
|
3832
4739
|
throw new Error(`Artifact pointer path escapes run directory: ${pointerPath}`);
|
|
3833
4740
|
}
|
|
3834
4741
|
return sourcePath;
|
|
3835
4742
|
}
|
|
3836
4743
|
function verifyArtifactPointerChecksum(pointer, content) {
|
|
3837
|
-
const sha256 =
|
|
4744
|
+
const sha256 = createHash2("sha256").update(content).digest("hex");
|
|
3838
4745
|
if (pointer.sha256 && pointer.sha256 !== sha256) {
|
|
3839
4746
|
throw new Error(`Artifact pointer checksum mismatch for ${pointer.path}`);
|
|
3840
4747
|
}
|
|
@@ -3846,7 +4753,7 @@ async function prepareArtifactSidecar(params) {
|
|
|
3846
4753
|
if (params.pointers.length === 0) {
|
|
3847
4754
|
return void 0;
|
|
3848
4755
|
}
|
|
3849
|
-
const sidecarRoot = await mkdtemp(
|
|
4756
|
+
const sidecarRoot = await mkdtemp(path5.join(os.tmpdir(), "agentv-artifact-sidecar-"));
|
|
3850
4757
|
const copied = /* @__PURE__ */ new Set();
|
|
3851
4758
|
const contentByPath = /* @__PURE__ */ new Map();
|
|
3852
4759
|
try {
|
|
@@ -3862,8 +4769,8 @@ async function prepareArtifactSidecar(params) {
|
|
|
3862
4769
|
if (copied.has(relativePointerPath)) {
|
|
3863
4770
|
continue;
|
|
3864
4771
|
}
|
|
3865
|
-
const destinationPath =
|
|
3866
|
-
mkdirSync(
|
|
4772
|
+
const destinationPath = path5.join(sidecarRoot, ...relativePointerPath.split("/"));
|
|
4773
|
+
mkdirSync(path5.dirname(destinationPath), { recursive: true });
|
|
3867
4774
|
writeFileSync(destinationPath, content);
|
|
3868
4775
|
copied.add(relativePointerPath);
|
|
3869
4776
|
}
|
|
@@ -3890,12 +4797,12 @@ function rewritePublishedIndexLine(line, destinationPath) {
|
|
|
3890
4797
|
} catch {
|
|
3891
4798
|
return line;
|
|
3892
4799
|
}
|
|
3893
|
-
if (!
|
|
4800
|
+
if (!isRecord3(record) || !isRecord3(record.artifact_pointers)) {
|
|
3894
4801
|
return line;
|
|
3895
4802
|
}
|
|
3896
4803
|
let changed = false;
|
|
3897
4804
|
for (const pointer of Object.values(record.artifact_pointers)) {
|
|
3898
|
-
if (!
|
|
4805
|
+
if (!isRecord3(pointer)) {
|
|
3899
4806
|
continue;
|
|
3900
4807
|
}
|
|
3901
4808
|
if (pointer.ref !== AGENTV_RESULTS_ARTIFACTS_REF || typeof pointer.path !== "string") {
|
|
@@ -3913,26 +4820,26 @@ async function preparePublishedResultsSource(params) {
|
|
|
3913
4820
|
if (params.pointers.length === 0) {
|
|
3914
4821
|
return void 0;
|
|
3915
4822
|
}
|
|
3916
|
-
const publishedRoot = await mkdtemp(
|
|
4823
|
+
const publishedRoot = await mkdtemp(path5.join(os.tmpdir(), "agentv-results-published-"));
|
|
3917
4824
|
const omittedPaths = new Set(
|
|
3918
4825
|
params.pointers.map((pointer) => normalizeDestinationPath(pointer.path))
|
|
3919
4826
|
);
|
|
3920
4827
|
try {
|
|
3921
4828
|
const sourceFiles = await listSourceFiles(params.sourceDir);
|
|
3922
4829
|
for (const sourceFile of sourceFiles) {
|
|
3923
|
-
const relativeFile =
|
|
3924
|
-
const destinationFile =
|
|
4830
|
+
const relativeFile = path5.relative(params.sourceDir, sourceFile).split(path5.sep).join("/");
|
|
4831
|
+
const destinationFile = path5.join(publishedRoot, ...relativeFile.split("/"));
|
|
3925
4832
|
if (relativeFile === RESULT_INDEX_FILENAME2) {
|
|
3926
4833
|
const original = readFileSync2(sourceFile, "utf8");
|
|
3927
4834
|
const rewritten = original.split(/\r?\n/).map((line) => rewritePublishedIndexLine(line, params.destinationPath)).join("\n");
|
|
3928
|
-
mkdirSync(
|
|
4835
|
+
mkdirSync(path5.dirname(destinationFile), { recursive: true });
|
|
3929
4836
|
writeFileSync(destinationFile, rewritten);
|
|
3930
4837
|
continue;
|
|
3931
4838
|
}
|
|
3932
4839
|
if (omittedPaths.has(relativeFile)) {
|
|
3933
4840
|
continue;
|
|
3934
4841
|
}
|
|
3935
|
-
mkdirSync(
|
|
4842
|
+
mkdirSync(path5.dirname(destinationFile), { recursive: true });
|
|
3936
4843
|
await cp(sourceFile, destinationFile, { dereference: false });
|
|
3937
4844
|
}
|
|
3938
4845
|
} catch (error) {
|
|
@@ -3999,8 +4906,8 @@ async function commitStorageBranchWorktreePaths(params) {
|
|
|
3999
4906
|
normalized
|
|
4000
4907
|
});
|
|
4001
4908
|
}
|
|
4002
|
-
const indexRoot = await mkdtemp(
|
|
4003
|
-
const indexFile =
|
|
4909
|
+
const indexRoot = await mkdtemp(path5.join(os.tmpdir(), "agentv-results-index-"));
|
|
4910
|
+
const indexFile = path5.join(indexRoot, "index");
|
|
4004
4911
|
const indexEnv = { GIT_INDEX_FILE: indexFile };
|
|
4005
4912
|
try {
|
|
4006
4913
|
if (base.baseRef) {
|
|
@@ -4008,8 +4915,9 @@ async function commitStorageBranchWorktreePaths(params) {
|
|
|
4008
4915
|
} else {
|
|
4009
4916
|
await runGit(["read-tree", "--empty"], { cwd: params.repoDir, env: indexEnv });
|
|
4010
4917
|
}
|
|
4918
|
+
await addResultsGitattributesToIndex(params.repoDir, indexEnv);
|
|
4011
4919
|
for (const gitPath of paths) {
|
|
4012
|
-
const sourcePath =
|
|
4920
|
+
const sourcePath = path5.join(params.repoDir, ...gitPath.split("/"));
|
|
4013
4921
|
if (!existsSync(sourcePath)) {
|
|
4014
4922
|
await runGit(["update-index", "--force-remove", "--", gitPath], {
|
|
4015
4923
|
cwd: params.repoDir,
|
|
@@ -4075,7 +4983,7 @@ async function commitResultsRunWithTemporaryIndex(params) {
|
|
|
4075
4983
|
await assertValidResultsBranchName(params.repoDir, normalized.branch);
|
|
4076
4984
|
await ensureResultsBranchNotCheckedOut(params.repoDir, normalized);
|
|
4077
4985
|
const destinationRunPath = normalizeDestinationPath(params.destinationPath);
|
|
4078
|
-
const destinationTreePath =
|
|
4986
|
+
const destinationTreePath = path5.posix.join(RESULTS_REPO_RUNS_DIR, destinationRunPath);
|
|
4079
4987
|
let base = await resolveStorageBranchBase({
|
|
4080
4988
|
repoDir: params.repoDir,
|
|
4081
4989
|
normalized,
|
|
@@ -4089,8 +4997,8 @@ async function commitResultsRunWithTemporaryIndex(params) {
|
|
|
4089
4997
|
preferRemote: params.preferRemoteBase
|
|
4090
4998
|
});
|
|
4091
4999
|
}
|
|
4092
|
-
const indexRoot = await mkdtemp(
|
|
4093
|
-
const indexFile =
|
|
5000
|
+
const indexRoot = await mkdtemp(path5.join(os.tmpdir(), "agentv-results-index-"));
|
|
5001
|
+
const indexFile = path5.join(indexRoot, "index");
|
|
4094
5002
|
const indexEnv = { GIT_INDEX_FILE: indexFile };
|
|
4095
5003
|
try {
|
|
4096
5004
|
if (base.baseRef) {
|
|
@@ -4098,6 +5006,7 @@ async function commitResultsRunWithTemporaryIndex(params) {
|
|
|
4098
5006
|
} else {
|
|
4099
5007
|
await runGit(["read-tree", "--empty"], { cwd: params.repoDir, env: indexEnv });
|
|
4100
5008
|
}
|
|
5009
|
+
await addResultsGitattributesToIndex(params.repoDir, indexEnv);
|
|
4101
5010
|
const existingPaths = await getExistingRunTreePaths(
|
|
4102
5011
|
params.repoDir,
|
|
4103
5012
|
base.baseRef,
|
|
@@ -4112,8 +5021,8 @@ async function commitResultsRunWithTemporaryIndex(params) {
|
|
|
4112
5021
|
}
|
|
4113
5022
|
const sourceFiles = await listSourceFiles(params.sourceDir);
|
|
4114
5023
|
for (const sourceFile of sourceFiles) {
|
|
4115
|
-
const relativeFile =
|
|
4116
|
-
const destinationFile =
|
|
5024
|
+
const relativeFile = path5.relative(params.sourceDir, sourceFile).split(path5.sep).join("/");
|
|
5025
|
+
const destinationFile = path5.posix.join(destinationTreePath, relativeFile);
|
|
4117
5026
|
const fileStat = await lstat(sourceFile);
|
|
4118
5027
|
let mode = fileStat.mode & 73 ? "100755" : "100644";
|
|
4119
5028
|
let hashInputPath = sourceFile;
|
|
@@ -4189,7 +5098,7 @@ function buildDirectPushResult(normalized, changed, outcome) {
|
|
|
4189
5098
|
...outcome?.blocked === true && {
|
|
4190
5099
|
blocked: true,
|
|
4191
5100
|
block_reason: outcome.blockReason,
|
|
4192
|
-
sync_status: "push_conflict"
|
|
5101
|
+
sync_status: outcome.syncStatus ?? "push_conflict"
|
|
4193
5102
|
},
|
|
4194
5103
|
...pushDetailsToWire(details)
|
|
4195
5104
|
};
|
|
@@ -4199,10 +5108,11 @@ function mergeDirectPushResults(normalized, results) {
|
|
|
4199
5108
|
if (blocked) {
|
|
4200
5109
|
return blocked;
|
|
4201
5110
|
}
|
|
4202
|
-
const detailed = [...results].reverse().find((result) => result.backup_ref !== void 0);
|
|
5111
|
+
const detailed = [...results].reverse().find((result) => result.backup_ref !== void 0 || result.auto_merged_remote === true);
|
|
4203
5112
|
return {
|
|
4204
5113
|
changed: results.some((result) => result.changed),
|
|
4205
5114
|
push_conflict_policy: normalized.push_conflict_policy,
|
|
5115
|
+
...detailed?.auto_merged_remote === true && { auto_merged_remote: true },
|
|
4206
5116
|
...detailed?.backup_ref !== void 0 && {
|
|
4207
5117
|
backup_ref: detailed.backup_ref
|
|
4208
5118
|
},
|
|
@@ -4386,7 +5296,7 @@ async function directPushResultsWithDetails(params) {
|
|
|
4386
5296
|
}
|
|
4387
5297
|
}
|
|
4388
5298
|
function buildGitRunId(relativeRunPath) {
|
|
4389
|
-
const normalized = relativeRunPath.split(
|
|
5299
|
+
const normalized = relativeRunPath.split(path5.sep).join("/");
|
|
4390
5300
|
const segments = normalized.split("/").filter(Boolean);
|
|
4391
5301
|
if (segments.length >= 2) {
|
|
4392
5302
|
const experiment = segments.slice(0, -1).join("/");
|
|
@@ -4398,8 +5308,8 @@ function buildGitRunId(relativeRunPath) {
|
|
|
4398
5308
|
}
|
|
4399
5309
|
return segments[0] ?? relativeRunPath;
|
|
4400
5310
|
}
|
|
4401
|
-
function getRunExperiment(runId,
|
|
4402
|
-
const experiment =
|
|
5311
|
+
function getRunExperiment(runId, summary) {
|
|
5312
|
+
const experiment = summary?.metadata?.experiment?.trim();
|
|
4403
5313
|
if (experiment) {
|
|
4404
5314
|
return experiment;
|
|
4405
5315
|
}
|
|
@@ -4523,7 +5433,7 @@ async function readGitResultArtifact(params) {
|
|
|
4523
5433
|
}
|
|
4524
5434
|
function buildWipBranchName(runDir) {
|
|
4525
5435
|
const hostname = os.hostname().replace(/[^A-Za-z0-9._-]+/g, "-").slice(0, 40);
|
|
4526
|
-
const runBasename =
|
|
5436
|
+
const runBasename = path5.basename(runDir).replace(/[^A-Za-z0-9._-]+/g, "-").slice(0, 60);
|
|
4527
5437
|
return `agentv/wip/${hostname}/${runBasename}`;
|
|
4528
5438
|
}
|
|
4529
5439
|
async function setupWipWorktree(params) {
|
|
@@ -4542,8 +5452,8 @@ async function setupWipWorktree(params) {
|
|
|
4542
5452
|
if (!baseRef) {
|
|
4543
5453
|
throw new Error("Could not resolve a base ref for the WIP results branch");
|
|
4544
5454
|
}
|
|
4545
|
-
const worktreeRoot = await mkdtemp(
|
|
4546
|
-
const worktreeDir =
|
|
5455
|
+
const worktreeRoot = await mkdtemp(path5.join(os.tmpdir(), "agentv-wip-"));
|
|
5456
|
+
const worktreeDir = path5.join(worktreeRoot, "repo");
|
|
4547
5457
|
await runGit(["worktree", "add", "-B", params.wipBranch, worktreeDir, baseRef], {
|
|
4548
5458
|
cwd: cloneDir
|
|
4549
5459
|
});
|
|
@@ -4562,7 +5472,7 @@ async function setupWipWorktree(params) {
|
|
|
4562
5472
|
};
|
|
4563
5473
|
}
|
|
4564
5474
|
async function pushWipCheckpoint(params) {
|
|
4565
|
-
const destinationDir =
|
|
5475
|
+
const destinationDir = path5.join(
|
|
4566
5476
|
params.handle.worktreeDir,
|
|
4567
5477
|
RESULTS_REPO_RUNS_DIR,
|
|
4568
5478
|
params.destinationPath
|
|
@@ -4625,40 +5535,74 @@ async function listGitRuns(repoDir, ref = "origin/main") {
|
|
|
4625
5535
|
}
|
|
4626
5536
|
throw error;
|
|
4627
5537
|
}
|
|
4628
|
-
const
|
|
4629
|
-
|
|
5538
|
+
const treePaths = treeOut.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
5539
|
+
const indexPaths = treePaths.filter((line) => line.endsWith("/index.jsonl"));
|
|
5540
|
+
if (indexPaths.length === 0) {
|
|
4630
5541
|
return [];
|
|
4631
5542
|
}
|
|
4632
|
-
const batchInput = `${
|
|
5543
|
+
const batchInput = `${indexPaths.map((indexPath) => `${ref}:${indexPath}`).join("\n")}
|
|
4633
5544
|
`;
|
|
4634
5545
|
const blobs = parseGitBatchBlobs(await runGitBatch(repoDir, batchInput));
|
|
4635
|
-
if (blobs.length !==
|
|
5546
|
+
if (blobs.length !== indexPaths.length) {
|
|
4636
5547
|
throw new Error(
|
|
4637
|
-
`Expected ${
|
|
5548
|
+
`Expected ${indexPaths.length} git blobs but received ${blobs.length} while listing results runs`
|
|
4638
5549
|
);
|
|
4639
5550
|
}
|
|
5551
|
+
const summaryPaths = indexPaths.map((indexPath) => path5.posix.join(path5.posix.dirname(indexPath), "summary.json")).filter((summaryPath) => treePaths.includes(summaryPath));
|
|
5552
|
+
const summaryByPath = /* @__PURE__ */ new Map();
|
|
5553
|
+
if (summaryPaths.length > 0) {
|
|
5554
|
+
const summaryBatchInput = `${summaryPaths.map((summaryPath) => `${ref}:${summaryPath}`).join("\n")}
|
|
5555
|
+
`;
|
|
5556
|
+
const summaryBlobs = parseGitBatchBlobs(await runGitBatch(repoDir, summaryBatchInput));
|
|
5557
|
+
for (let i = 0; i < summaryBlobs.length; i++) {
|
|
5558
|
+
const summaryPath = summaryPaths[i];
|
|
5559
|
+
const blob = summaryBlobs[i];
|
|
5560
|
+
if (!summaryPath || !blob) continue;
|
|
5561
|
+
summaryByPath.set(summaryPath, JSON.parse(blob.content.toString("utf8")));
|
|
5562
|
+
}
|
|
5563
|
+
}
|
|
4640
5564
|
const runs = blobs.flatMap((blob, index) => {
|
|
4641
|
-
const
|
|
4642
|
-
const
|
|
4643
|
-
const
|
|
4644
|
-
const
|
|
5565
|
+
const manifestPath = indexPaths[index];
|
|
5566
|
+
const runDir = path5.posix.dirname(manifestPath);
|
|
5567
|
+
const summaryPath = path5.posix.join(runDir, "summary.json");
|
|
5568
|
+
const summary = summaryByPath.get(summaryPath);
|
|
5569
|
+
const relativeRunPath = path5.posix.relative(RESULTS_REPO_RUNS_DIR, runDir);
|
|
4645
5570
|
const runId = buildGitRunId(relativeRunPath);
|
|
4646
|
-
const
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
5571
|
+
const rows = blob.content.toString("utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).flatMap(
|
|
5572
|
+
(line) => {
|
|
5573
|
+
try {
|
|
5574
|
+
return [
|
|
5575
|
+
JSON.parse(line)
|
|
5576
|
+
];
|
|
5577
|
+
} catch {
|
|
5578
|
+
return [];
|
|
5579
|
+
}
|
|
5580
|
+
}
|
|
5581
|
+
);
|
|
5582
|
+
const rowTargets = [
|
|
5583
|
+
...new Set(rows.map((row) => row.target).filter((target) => !!target))
|
|
5584
|
+
];
|
|
5585
|
+
const rowTestIds = [
|
|
5586
|
+
...new Set(rows.map((row) => row.test_id).filter((testId) => !!testId))
|
|
5587
|
+
];
|
|
5588
|
+
const rowScores = rows.map((row) => row.score).filter((score) => typeof score === "number" && Number.isFinite(score));
|
|
5589
|
+
const avgScore = rowScores.length > 0 ? rowScores.reduce((sum, score) => sum + score, 0) / rowScores.length : 0;
|
|
5590
|
+
const timestamp = summary?.metadata?.timestamp?.trim() || rows[0]?.timestamp || path5.posix.basename(runDir);
|
|
5591
|
+
const displayName = summary?.metadata?.display_name?.trim() || path5.posix.basename(runDir);
|
|
5592
|
+
const targets = summary?.metadata?.targets ?? rowTargets;
|
|
5593
|
+
const passRate = computeAveragePassRate(summary?.run_summary) ?? avgScore;
|
|
4650
5594
|
return [
|
|
4651
5595
|
{
|
|
4652
5596
|
run_id: runId,
|
|
4653
|
-
experiment: getRunExperiment(runId,
|
|
5597
|
+
experiment: getRunExperiment(runId, summary),
|
|
4654
5598
|
timestamp,
|
|
4655
5599
|
...passRate !== void 0 && { pass_rate: passRate },
|
|
4656
5600
|
...targets.length === 1 && targets[0] ? { target: targets[0] } : {},
|
|
4657
|
-
manifest_path:
|
|
4658
|
-
|
|
5601
|
+
manifest_path: manifestPath,
|
|
5602
|
+
...summaryByPath.has(summaryPath) && { summary_path: summaryPath },
|
|
4659
5603
|
display_name: displayName,
|
|
4660
|
-
test_count:
|
|
4661
|
-
avg_score:
|
|
5604
|
+
test_count: summary?.metadata?.tests_run?.length ?? rowTestIds.length,
|
|
5605
|
+
avg_score: avgScore,
|
|
4662
5606
|
size_bytes: blob.size
|
|
4663
5607
|
}
|
|
4664
5608
|
];
|
|
@@ -4667,9 +5611,9 @@ async function listGitRuns(repoDir, ref = "origin/main") {
|
|
|
4667
5611
|
return runs;
|
|
4668
5612
|
}
|
|
4669
5613
|
async function materializeGitRun(repoDir, relativeRunPath, ref = "origin/main") {
|
|
4670
|
-
const normalizedRunPath = relativeRunPath.split(
|
|
4671
|
-
const runTreePath =
|
|
4672
|
-
const targetRunDir =
|
|
5614
|
+
const normalizedRunPath = relativeRunPath.split(path5.sep).join("/");
|
|
5615
|
+
const runTreePath = path5.posix.join(RESULTS_REPO_RUNS_DIR, normalizedRunPath);
|
|
5616
|
+
const targetRunDir = path5.join(repoDir, ...runTreePath.split("/"));
|
|
4673
5617
|
const { stdout: treeOut } = await runGit(["ls-tree", "-r", "--name-only", ref, runTreePath], {
|
|
4674
5618
|
cwd: repoDir
|
|
4675
5619
|
});
|
|
@@ -4685,16 +5629,16 @@ async function materializeGitRun(repoDir, relativeRunPath, ref = "origin/main")
|
|
|
4685
5629
|
`Expected ${filePaths.length} git blobs but received ${blobs.length} while materializing results run`
|
|
4686
5630
|
);
|
|
4687
5631
|
}
|
|
4688
|
-
const tempRoot = mkdtempSync(
|
|
4689
|
-
const tempRunDir =
|
|
5632
|
+
const tempRoot = mkdtempSync(path5.join(repoDir, ".agentv-run-"));
|
|
5633
|
+
const tempRunDir = path5.join(tempRoot, "run");
|
|
4690
5634
|
try {
|
|
4691
5635
|
for (const [index, filePath] of filePaths.entries()) {
|
|
4692
|
-
const relativeFilePath =
|
|
4693
|
-
const absolutePath =
|
|
4694
|
-
mkdirSync(
|
|
5636
|
+
const relativeFilePath = path5.posix.relative(runTreePath, filePath);
|
|
5637
|
+
const absolutePath = path5.join(tempRunDir, ...relativeFilePath.split("/"));
|
|
5638
|
+
mkdirSync(path5.dirname(absolutePath), { recursive: true });
|
|
4695
5639
|
writeFileSync(absolutePath, blobs[index].content);
|
|
4696
5640
|
}
|
|
4697
|
-
mkdirSync(
|
|
5641
|
+
mkdirSync(path5.dirname(targetRunDir), { recursive: true });
|
|
4698
5642
|
try {
|
|
4699
5643
|
renameSync(tempRunDir, targetRunDir);
|
|
4700
5644
|
} catch (error) {
|
|
@@ -5679,7 +6623,7 @@ function extractResponseItemContent(content) {
|
|
|
5679
6623
|
}
|
|
5680
6624
|
return parts.length > 0 ? parts.join("") : void 0;
|
|
5681
6625
|
}
|
|
5682
|
-
var DEFAULT_SESSIONS_DIR = () =>
|
|
6626
|
+
var DEFAULT_SESSIONS_DIR = () => path6.join(homedir(), ".codex", "sessions");
|
|
5683
6627
|
async function discoverCodexSessions(opts) {
|
|
5684
6628
|
const sessionsDir = opts?.sessionsDir ?? DEFAULT_SESSIONS_DIR();
|
|
5685
6629
|
const limit = opts?.latest ? 1 : opts?.limit ?? 10;
|
|
@@ -5691,7 +6635,7 @@ async function discoverCodexSessions(opts) {
|
|
|
5691
6635
|
return [];
|
|
5692
6636
|
}
|
|
5693
6637
|
for (const year of yearDirs) {
|
|
5694
|
-
const yearPath =
|
|
6638
|
+
const yearPath = path6.join(sessionsDir, year);
|
|
5695
6639
|
let monthDirs;
|
|
5696
6640
|
try {
|
|
5697
6641
|
monthDirs = await readdir2(yearPath);
|
|
@@ -5699,7 +6643,7 @@ async function discoverCodexSessions(opts) {
|
|
|
5699
6643
|
continue;
|
|
5700
6644
|
}
|
|
5701
6645
|
for (const month of monthDirs) {
|
|
5702
|
-
const monthPath =
|
|
6646
|
+
const monthPath = path6.join(yearPath, month);
|
|
5703
6647
|
let dayDirs;
|
|
5704
6648
|
try {
|
|
5705
6649
|
dayDirs = await readdir2(monthPath);
|
|
@@ -5711,7 +6655,7 @@ async function discoverCodexSessions(opts) {
|
|
|
5711
6655
|
const dirDate = `${year}-${month}-${day}`;
|
|
5712
6656
|
if (dirDate !== opts.date) continue;
|
|
5713
6657
|
}
|
|
5714
|
-
const dayPath =
|
|
6658
|
+
const dayPath = path6.join(monthPath, day);
|
|
5715
6659
|
let files;
|
|
5716
6660
|
try {
|
|
5717
6661
|
files = await readdir2(dayPath);
|
|
@@ -5720,7 +6664,7 @@ async function discoverCodexSessions(opts) {
|
|
|
5720
6664
|
}
|
|
5721
6665
|
for (const file of files) {
|
|
5722
6666
|
if (!file.startsWith("rollout-") || !file.endsWith(".jsonl")) continue;
|
|
5723
|
-
const filePath =
|
|
6667
|
+
const filePath = path6.join(dayPath, file);
|
|
5724
6668
|
const nameWithoutExt = file.replace(/\.jsonl$/, "");
|
|
5725
6669
|
const parts = nameWithoutExt.split("-");
|
|
5726
6670
|
const sessionId = parts.length >= 6 ? parts.slice(-5).join("-") : nameWithoutExt;
|
|
@@ -5739,7 +6683,7 @@ async function discoverCodexSessions(opts) {
|
|
|
5739
6683
|
sessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
5740
6684
|
return sessions.slice(0, limit);
|
|
5741
6685
|
}
|
|
5742
|
-
var DEFAULT_PROJECTS_DIR = () =>
|
|
6686
|
+
var DEFAULT_PROJECTS_DIR = () => path7.join(homedir2(), ".claude", "projects");
|
|
5743
6687
|
function encodeProjectPath(projectPath) {
|
|
5744
6688
|
return projectPath.replace(/\//g, "-");
|
|
5745
6689
|
}
|
|
@@ -5758,7 +6702,7 @@ async function discoverClaudeSessions(opts) {
|
|
|
5758
6702
|
}
|
|
5759
6703
|
const sessions = [];
|
|
5760
6704
|
for (const projectDir of projectDirs) {
|
|
5761
|
-
const dirPath =
|
|
6705
|
+
const dirPath = path7.join(projectsDir, projectDir);
|
|
5762
6706
|
let entries;
|
|
5763
6707
|
try {
|
|
5764
6708
|
entries = await readdir3(dirPath);
|
|
@@ -5769,7 +6713,7 @@ async function discoverClaudeSessions(opts) {
|
|
|
5769
6713
|
if (!entry.endsWith(".jsonl")) continue;
|
|
5770
6714
|
const sessionId = entry.replace(/\.jsonl$/, "");
|
|
5771
6715
|
if (opts?.sessionId && sessionId !== opts.sessionId) continue;
|
|
5772
|
-
const filePath =
|
|
6716
|
+
const filePath = path7.join(dirPath, entry);
|
|
5773
6717
|
let updatedAt;
|
|
5774
6718
|
try {
|
|
5775
6719
|
const fileStat = await stat3(filePath);
|
|
@@ -5844,6 +6788,12 @@ export {
|
|
|
5844
6788
|
traceEnvelopeToTraceSessionResponse,
|
|
5845
6789
|
buildTraceSpanTree,
|
|
5846
6790
|
normalizeTraceArtifactToTraceSessionResponse,
|
|
6791
|
+
isExperimentFileReference,
|
|
6792
|
+
deriveExperimentNameFromPath,
|
|
6793
|
+
loadExperimentConfig,
|
|
6794
|
+
normalizeExperimentConfig,
|
|
6795
|
+
fingerprintExperimentConfig,
|
|
6796
|
+
buildExperimentArtifactMetadata,
|
|
5847
6797
|
transpileEvalYaml,
|
|
5848
6798
|
transpileEvalYamlFile,
|
|
5849
6799
|
getOutputFilenames,
|
|
@@ -5857,9 +6807,11 @@ export {
|
|
|
5857
6807
|
getResultsRepoLocalPaths,
|
|
5858
6808
|
ensureResultsRepoClone,
|
|
5859
6809
|
getResultsRepoStatus,
|
|
6810
|
+
buildResultsCompareUrl,
|
|
5860
6811
|
getResultsRepoSyncStatus,
|
|
5861
6812
|
syncResultsRepo,
|
|
5862
6813
|
syncResultsRepoForProject,
|
|
6814
|
+
confirmResultsMergeAndPull,
|
|
5863
6815
|
checkoutResultsRepoBranch,
|
|
5864
6816
|
prepareResultsRepoBranch,
|
|
5865
6817
|
stageResultsArtifacts,
|
|
@@ -5894,4 +6846,4 @@ export {
|
|
|
5894
6846
|
TranscriptProvider,
|
|
5895
6847
|
createAgentKernel
|
|
5896
6848
|
};
|
|
5897
|
-
//# sourceMappingURL=chunk-
|
|
6849
|
+
//# sourceMappingURL=chunk-UG4JHZYM.js.map
|