@castlemilk/omega 0.6.14 → 0.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -330,8 +330,8 @@ function getApiUrl() {
330
330
  const opts = program.opts();
331
331
  return opts.api ?? "http://localhost:4000";
332
332
  }
333
- async function apiFetch(path10, init) {
334
- const url = `${getApiUrl()}${path10}`;
333
+ async function apiFetch(path11, init) {
334
+ const url = `${getApiUrl()}${path11}`;
335
335
  const res = await fetch(url, init);
336
336
  const data = await res.json().catch(() => ({}));
337
337
  if (!res.ok) {
@@ -734,8 +734,8 @@ function getOutputDir() {
734
734
  }
735
735
  try {
736
736
  const __filename3 = fileURLToPath3(import.meta.url);
737
- const __dirname3 = path3.dirname(__filename3);
738
- return path3.join(path3.resolve(__dirname3, "../../../.."), "packages/skills/src");
737
+ const __dirname4 = path3.dirname(__filename3);
738
+ return path3.join(path3.resolve(__dirname4, "../../../.."), "packages/skills/src");
739
739
  } catch {
740
740
  return path3.join(process.cwd(), "harness-skills");
741
741
  }
@@ -819,7 +819,8 @@ agentCmd.command("diffs").description("Show diffs for a task").argument("<id>",
819
819
 
820
820
  // ../../apps/cli/src/commands/bench.ts
821
821
  import { Command as Command8 } from "commander";
822
- import path9 from "node:path";
822
+ import fs8 from "node:fs/promises";
823
+ import path10 from "node:path";
823
824
 
824
825
  // ../bench/dist/api-client.js
825
826
  var ApiError = class extends Error {
@@ -836,15 +837,15 @@ async function apiFetch2(url, init) {
836
837
  }
837
838
  return res.json();
838
839
  }
839
- async function ensureProject2(apiUrl, name, path10) {
840
+ async function ensureProject2(apiUrl, name, path11) {
840
841
  const res = await fetch(`${apiUrl}/projects`, {
841
842
  method: "POST",
842
843
  headers: { "Content-Type": "application/json" },
843
- body: JSON.stringify({ name, path: path10 })
844
+ body: JSON.stringify({ name, path: path11 })
844
845
  });
845
846
  if (res.status === 409) {
846
- const projects = await fetch(`${apiUrl}/projects?path=${encodeURIComponent(path10)}`).then((r) => r.json());
847
- const existing = projects.find((p) => p.path === path10);
847
+ const projects = await fetch(`${apiUrl}/projects?path=${encodeURIComponent(path11)}`).then((r) => r.json());
848
+ const existing = projects.find((p) => p.path === path11);
848
849
  if (existing)
849
850
  return existing;
850
851
  }
@@ -1049,7 +1050,7 @@ async function writeReport(report, outputDir = ".omega/reports") {
1049
1050
  const jsonFile = path5.join(outputDir, `benchmark-${ts}.json`);
1050
1051
  const mdFile = path5.join(outputDir, `benchmark-${ts}.md`);
1051
1052
  await fs2.writeFile(jsonFile, JSON.stringify(report, null, 2), "utf-8");
1052
- const passRate = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
1053
+ const passRate2 = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
1053
1054
  const md = [
1054
1055
  "# Omega Benchmark Report",
1055
1056
  "",
@@ -1059,7 +1060,7 @@ async function writeReport(report, outputDir = ".omega/reports") {
1059
1060
  `- Passed: ${String(report.passed)}`,
1060
1061
  `- Failed: ${String(report.failed)}`,
1061
1062
  `- Timeouts: ${String(report.timeouts)}`,
1062
- `- Pass rate: ${String(passRate)}%`,
1063
+ `- Pass rate: ${String(passRate2)}%`,
1063
1064
  `- Total duration: ${formatDuration(report.totalDurationMs)}`,
1064
1065
  "",
1065
1066
  "## Results",
@@ -1077,11 +1078,11 @@ async function writeReport(report, outputDir = ".omega/reports") {
1077
1078
  return jsonFile;
1078
1079
  }
1079
1080
  function printSummary(report) {
1080
- const passRate = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
1081
+ const passRate2 = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
1081
1082
  console.log(`
1082
1083
  Benchmark: ${report.suite}`);
1083
1084
  console.log(`Total: ${String(report.total)} | Passed: ${String(report.passed)} | Failed: ${String(report.failed)} | Timeouts: ${String(report.timeouts)}`);
1084
- console.log(`Pass rate: ${String(passRate)}%`);
1085
+ console.log(`Pass rate: ${String(passRate2)}%`);
1085
1086
  console.log(`Duration: ${formatDuration(report.totalDurationMs)}`);
1086
1087
  for (const r of report.results) {
1087
1088
  console.log(resultLine(r, report.results.indexOf(r)));
@@ -1289,10 +1290,10 @@ async function readTask(taskDir) {
1289
1290
  const instructionRaw = await fs4.readFile(path7.join(taskDir, "instruction.md"), "utf-8");
1290
1291
  return { toml: parseToml(tomlRaw), instruction: instructionRaw };
1291
1292
  }
1292
- async function cloneRepo(repoUrl, commit, targetPath) {
1293
+ async function cloneRepo(repoUrl, commit2, targetPath) {
1293
1294
  await fs4.mkdir(path7.dirname(targetPath), { recursive: true });
1294
1295
  await execFileAsync("git", ["clone", repoUrl, targetPath], { timeout: 12e4 });
1295
- await execFileAsync("git", ["-C", targetPath, "checkout", commit], { timeout: 6e4 });
1296
+ await execFileAsync("git", ["-C", targetPath, "checkout", commit2], { timeout: 6e4 });
1296
1297
  }
1297
1298
  async function loadDeepSWESuite(options) {
1298
1299
  const entries = await fs4.readdir(options.tasksDir, { withFileTypes: true });
@@ -1321,7 +1322,7 @@ async function loadDeepSWESuite(options) {
1321
1322
  const id = toml.metadata?.task_id ?? path7.basename(dir);
1322
1323
  const title = toml.metadata?.display_title ?? toml.metadata?.original_title ?? id;
1323
1324
  const repo = toml.metadata?.repository_url;
1324
- const commit = toml.metadata?.base_commit_hash;
1325
+ const commit2 = toml.metadata?.base_commit_hash;
1325
1326
  tasks.push({
1326
1327
  id: `deepswe-${id}`,
1327
1328
  name: id,
@@ -1329,10 +1330,10 @@ async function loadDeepSWESuite(options) {
1329
1330
  description: instruction,
1330
1331
  complexity: "complex",
1331
1332
  setup: async (projectPath) => {
1332
- if (!repo || !commit) {
1333
+ if (!repo || !commit2) {
1333
1334
  throw new Error(`DeepSWE task ${id} is missing repository_url or base_commit_hash`);
1334
1335
  }
1335
- await cloneRepo(repo, commit, projectPath);
1336
+ await cloneRepo(repo, commit2, projectPath);
1336
1337
  },
1337
1338
  evaluate: (ctx) => {
1338
1339
  const hasDiff = ctx.diffs.length > 0 && ctx.diffs.some((d) => d.patch.trim().length > 0);
@@ -1366,12 +1367,12 @@ function summariseTraceFlow(report, maxLines = 60) {
1366
1367
  return lines.slice(0, maxLines).join("\n") + "\n... (truncated)";
1367
1368
  }
1368
1369
  function buildOptimisePrompt(report, failedResult, traceFlowText) {
1369
- const passRate = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
1370
+ const passRate2 = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
1370
1371
  const failed = report.results.filter((r) => !r.evaluation.passed);
1371
1372
  const lines = [];
1372
1373
  lines.push("# Prompt optimisation task");
1373
1374
  lines.push("");
1374
- lines.push(`The latest benchmark run achieved ${String(passRate)}% pass rate (${String(report.passed)}/${String(report.total)}).`);
1375
+ lines.push(`The latest benchmark run achieved ${String(passRate2)}% pass rate (${String(report.passed)}/${String(report.total)}).`);
1375
1376
  lines.push("");
1376
1377
  lines.push("## Failed tasks");
1377
1378
  for (const r of failed.slice(0, 5)) {
@@ -1432,6 +1433,335 @@ async function submitOptimiseTask(apiUrl, projectId, prompt) {
1432
1433
  return res.json();
1433
1434
  }
1434
1435
 
1436
+ // ../bench/dist/ab.js
1437
+ function passRate(report) {
1438
+ return report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
1439
+ }
1440
+ function findResult(report, taskId) {
1441
+ return report.results.find((r) => r.task.id === taskId);
1442
+ }
1443
+ function buildAbReport(baseline, candidate, baselineReport, candidateReport) {
1444
+ const baselineRate = passRate(baselineReport);
1445
+ const candidateRate = passRate(candidateReport);
1446
+ const taskIds = /* @__PURE__ */ new Set();
1447
+ for (const r of baselineReport.results)
1448
+ taskIds.add(r.task.id);
1449
+ for (const r of candidateReport.results)
1450
+ taskIds.add(r.task.id);
1451
+ const perTask = Array.from(taskIds).map((taskId) => {
1452
+ const base = findResult(baselineReport, taskId);
1453
+ const cand = findResult(candidateReport, taskId);
1454
+ return {
1455
+ taskId,
1456
+ taskName: base?.task.name ?? cand?.task.name ?? taskId,
1457
+ baselinePassed: base?.evaluation.passed ?? false,
1458
+ candidatePassed: cand?.evaluation.passed ?? false,
1459
+ baselineTokens: base?.usage?.totalTokens,
1460
+ candidateTokens: cand?.usage?.totalTokens
1461
+ };
1462
+ });
1463
+ return {
1464
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1465
+ baseline,
1466
+ candidate,
1467
+ baselineReport,
1468
+ candidateReport,
1469
+ delta: {
1470
+ passRate: candidateRate - baselineRate,
1471
+ passedDelta: candidateReport.passed - baselineReport.passed,
1472
+ durationMsDelta: candidateReport.totalDurationMs - baselineReport.totalDurationMs,
1473
+ promptTokensDelta: (candidateReport.totalUsage?.promptTokens ?? 0) - (baselineReport.totalUsage?.promptTokens ?? 0),
1474
+ completionTokensDelta: (candidateReport.totalUsage?.completionTokens ?? 0) - (baselineReport.totalUsage?.completionTokens ?? 0),
1475
+ totalTokensDelta: (candidateReport.totalUsage?.totalTokens ?? 0) - (baselineReport.totalUsage?.totalTokens ?? 0)
1476
+ },
1477
+ perTask
1478
+ };
1479
+ }
1480
+ async function runAbBenchmark(tasks, baseline, candidate, options) {
1481
+ const suiteName = options.suiteName ?? "ab";
1482
+ const baselineReport = await runBenchmark(tasks, {
1483
+ ...options,
1484
+ suiteName: `${suiteName}-baseline`
1485
+ });
1486
+ const candidateReport = await runBenchmark(tasks, {
1487
+ ...options,
1488
+ suiteName: `${suiteName}-candidate`
1489
+ });
1490
+ return buildAbReport(baseline, candidate, baselineReport, candidateReport);
1491
+ }
1492
+ function deltaSymbol(value) {
1493
+ if (value > 0)
1494
+ return "+";
1495
+ if (value < 0)
1496
+ return "";
1497
+ return "";
1498
+ }
1499
+ function formatDelta(value) {
1500
+ return `${deltaSymbol(value)}${String(value)}`;
1501
+ }
1502
+ function formatAbReport(report) {
1503
+ const lines = [];
1504
+ lines.push("# Omega A/B Benchmark Report");
1505
+ lines.push("");
1506
+ lines.push(`- Timestamp: ${report.timestamp}`);
1507
+ lines.push(`- Baseline: ${report.baseline.name}`);
1508
+ lines.push(`- Candidate: ${report.candidate.name}`);
1509
+ lines.push("");
1510
+ lines.push("## Summary");
1511
+ lines.push(`| Metric | Baseline | Candidate | Delta |`);
1512
+ lines.push(`|---|---|---|---|`);
1513
+ lines.push(`| Pass rate | ${String(passRate(report.baselineReport))}% | ${String(passRate(report.candidateReport))}% | ${formatDelta(report.delta.passRate)}% |`);
1514
+ lines.push(`| Passed | ${String(report.baselineReport.passed)}/${String(report.baselineReport.total)} | ${String(report.candidateReport.passed)}/${String(report.candidateReport.total)} | ${formatDelta(report.delta.passedDelta)} |`);
1515
+ lines.push(`| Duration | ${formatDuration2(report.baselineReport.totalDurationMs)} | ${formatDuration2(report.candidateReport.totalDurationMs)} | ${formatDuration2(report.delta.durationMsDelta)} |`);
1516
+ lines.push(`| Tokens | ${String(report.baselineReport.totalUsage?.totalTokens ?? 0)} | ${String(report.candidateReport.totalUsage?.totalTokens ?? 0)} | ${formatDelta(report.delta.totalTokensDelta)} |`);
1517
+ lines.push("");
1518
+ lines.push("## Per-task comparison");
1519
+ lines.push(`| Task | Baseline | Candidate | Token delta |`);
1520
+ lines.push(`|---|---|---|---|`);
1521
+ for (const t of report.perTask) {
1522
+ const tokenDelta = t.baselineTokens !== void 0 && t.candidateTokens !== void 0 ? formatDelta(t.candidateTokens - t.baselineTokens) : "-";
1523
+ lines.push(`| ${t.taskName} | ${t.baselinePassed ? "pass" : "fail"} | ${t.candidatePassed ? "pass" : "fail"} | ${tokenDelta} |`);
1524
+ }
1525
+ lines.push("");
1526
+ return lines.join("\n");
1527
+ }
1528
+ function formatDuration2(ms) {
1529
+ if (ms < 1e3)
1530
+ return `${String(Math.round(ms))}ms`;
1531
+ return `${(ms / 1e3).toFixed(1)}s`;
1532
+ }
1533
+
1534
+ // ../agent/dist/tool-definitions.js
1535
+ var AGENT_TOOLS = [
1536
+ {
1537
+ name: "read_file",
1538
+ description: "Read a file relative to project root.",
1539
+ parameters: {
1540
+ type: "object",
1541
+ properties: { path: { type: "string" } },
1542
+ required: ["path"]
1543
+ }
1544
+ },
1545
+ {
1546
+ name: "write_file",
1547
+ description: "Write content to a file relative to project root.",
1548
+ parameters: {
1549
+ type: "object",
1550
+ properties: { path: { type: "string" }, content: { type: "string" } },
1551
+ required: ["path", "content"]
1552
+ }
1553
+ },
1554
+ {
1555
+ name: "edit_file",
1556
+ description: "Apply a targeted edit to an existing file relative to project root. Replaces one occurrence of old_string with new_string.",
1557
+ parameters: {
1558
+ type: "object",
1559
+ properties: {
1560
+ path: { type: "string" },
1561
+ old_string: { type: "string" },
1562
+ new_string: { type: "string" }
1563
+ },
1564
+ required: ["path", "old_string", "new_string"]
1565
+ }
1566
+ },
1567
+ {
1568
+ name: "run_command",
1569
+ description: "Run a shell command in the project root.",
1570
+ parameters: {
1571
+ type: "object",
1572
+ properties: { command: { type: "string" } },
1573
+ required: ["command"]
1574
+ }
1575
+ },
1576
+ {
1577
+ name: "think",
1578
+ description: "Record reasoning.",
1579
+ parameters: {
1580
+ type: "object",
1581
+ properties: { thought: { type: "string" } },
1582
+ required: ["thought"]
1583
+ }
1584
+ },
1585
+ {
1586
+ name: "finish",
1587
+ description: "Mark the task complete.",
1588
+ parameters: {
1589
+ type: "object",
1590
+ properties: { summary: { type: "string" }, success: { type: "boolean" } },
1591
+ required: ["summary", "success"]
1592
+ }
1593
+ },
1594
+ {
1595
+ name: "publish",
1596
+ description: "Build, validate, and publish the project.",
1597
+ parameters: {
1598
+ type: "object",
1599
+ properties: { version: { type: "string" } },
1600
+ required: []
1601
+ }
1602
+ }
1603
+ ];
1604
+
1605
+ // ../agent/dist/planner.js
1606
+ var toolDescriptions = AGENT_TOOLS.map((t) => `- ${t.name}: ${t.description}`).join("\n");
1607
+ var PLAN_PROMPT = `You are a planning assistant. Given a task, produce a concise step-by-step plan.
1608
+
1609
+ Respond with strict JSON in this exact shape (no markdown):
1610
+ {
1611
+ "reasoning": "brief reasoning",
1612
+ "plan": [
1613
+ { "name": "step name", "tool": "optional tool name", "input": { optional tool args } }
1614
+ ]
1615
+ }
1616
+
1617
+ Available tools:
1618
+ ${toolDescriptions}
1619
+
1620
+ If a step does not need a tool, omit tool/input. Use edit_file for small file changes.`;
1621
+
1622
+ // ../agent/dist/tools.js
1623
+ import { execFile as execFile2 } from "node:child_process";
1624
+ import { promisify as promisify2 } from "node:util";
1625
+ var execFileAsync2 = promisify2(execFile2);
1626
+
1627
+ // ../agent/dist/validator.js
1628
+ import { execFile as execFile3 } from "node:child_process";
1629
+ import { promisify as promisify3 } from "node:util";
1630
+ var execFileAsync3 = promisify3(execFile3);
1631
+
1632
+ // ../agent/dist/publisher.js
1633
+ import { execFile as execFile5 } from "node:child_process";
1634
+ import { promisify as promisify5 } from "node:util";
1635
+
1636
+ // ../agent/dist/git.js
1637
+ import { execFile as execFile4 } from "node:child_process";
1638
+ import { promisify as promisify4 } from "node:util";
1639
+ var execFileAsync4 = promisify4(execFile4);
1640
+
1641
+ // ../agent/dist/publisher.js
1642
+ var execFileAsync5 = promisify5(execFile5);
1643
+
1644
+ // ../agent/dist/prompts.js
1645
+ import fs6 from "node:fs";
1646
+ function loadPromptFromEnv(name) {
1647
+ const direct = process.env[name];
1648
+ if (direct)
1649
+ return direct;
1650
+ const fileKey = `${name}_FILE`;
1651
+ const filePath = process.env[fileKey];
1652
+ if (filePath) {
1653
+ try {
1654
+ return fs6.readFileSync(filePath, "utf-8");
1655
+ } catch {
1656
+ }
1657
+ }
1658
+ return void 0;
1659
+ }
1660
+ var AGENT_SYSTEM_PROMPT = loadPromptFromEnv("OMEGA_SYSTEM_PROMPT") ?? `You are Omega, an autonomous software engineering agent running inside a project repository.
1661
+
1662
+ Your job is to complete the user's task by calling tools. Do not write prose or explanations outside tool calls. Prefer targeted edits over full rewrites.
1663
+
1664
+ Available tools:
1665
+
1666
+ - read_file: Read a file relative to project root. Arguments: { "path": "relative/path" }
1667
+ - write_file: Overwrite or create a file. Arguments: { "path": "relative/path", "content": "full file content" }
1668
+ - edit_file: Replace one exact occurrence of old_string with new_string in an existing file. Use this for small changes. Arguments: { "path": "relative/path", "old_string": "...", "new_string": "..." }
1669
+ - run_command: Run a single simple command. No pipes (|), &&, ;, redirects, or globs. Prefer pnpm/npm/node. Arguments: { "command": "pnpm lint" }
1670
+ - think: Record a reasoning step. Arguments: { "thought": "..." }
1671
+ - finish: Mark the task complete. Arguments: { "summary": "what was done", "success": true }. Use summary, not message.
1672
+ - publish: Run build/test/publish validation. Only after your edits pass validation. Arguments: { "version": "optional" }
1673
+
1674
+ Rules:
1675
+ 1. Read the task, then use think to plan.
1676
+ 2. Use edit_file for small changes; write_file only when creating a file or rewriting most of it.
1677
+ 3. ALWAYS read a file with read_file before using edit_file on it. Do NOT read a file before write_file when creating a new file.
1678
+ 4. Before creating JavaScript files, read package.json to check whether "type" is "module" and use ESM (export) or CommonJS (module.exports) accordingly.
1679
+ 5. You MUST run validation (pnpm lint, pnpm test, or the project's validation command) after every edit using publish or run_command.
1680
+ 6. Do NOT call finish until validation passes. If validation fails, fix the issue and re-run validation.
1681
+ 7. Do not expose secrets or run destructive commands.
1682
+ 8. Finish only when the task is done. Always include summary and success.
1683
+ 9. Do not repeat the same tool call with the same arguments. If something fails, change your approach.
1684
+ 10. Do not ask the user for clarification. Do not say "No task was provided". Use the Task and Description above to proceed.
1685
+
1686
+ Example tool call sequence for "Add a greet function":
1687
+ 1. think: { "thought": "I need to create greet.js and export a greet function." }
1688
+ 2. write_file: { "path": "greet.js", "content": "export function greet(name) { return 'Hello, ' + name + '!'; }
1689
+ " }
1690
+ 3. publish: { }
1691
+ 4. finish: { "summary": "Created greet.js with greet(name) function and verified it passes tests.", "success": true }`;
1692
+ var TEXT_TOOLS_SYSTEM_PROMPT = loadPromptFromEnv("OMEGA_TEXT_TOOLS_PROMPT") ?? `You are Omega, an autonomous software engineering agent running inside a project repository.
1693
+
1694
+ You MUST respond with a single JSON object containing a "tool_calls" array. Do not output markdown, explanations, or reasoning outside the JSON.
1695
+
1696
+ Available tools (use ONLY these exact names):
1697
+
1698
+ - read_file: { "path": "relative/path" }
1699
+ - write_file: { "path": "relative/path", "content": "full file content" }
1700
+ - edit_file: { "path": "relative/path", "old_string": "...", "new_string": "..." }
1701
+ - run_command: { "command": "simple command, no pipes/&&/;/redirects" }
1702
+ - think: { "thought": "reasoning text" }
1703
+ - finish: { "summary": "what was done", "success": true | false }
1704
+ - publish: { "version": "optional" }
1705
+
1706
+ Rules:
1707
+ - Plan with think, then act.
1708
+ - Use edit_file for small changes; write_file only for new files or large rewrites.
1709
+ - ALWAYS read a file with read_file before using edit_file on it. Do NOT read a file before write_file when creating a new file.
1710
+ - Before creating JavaScript files, read package.json to check whether "type" is "module" and use ESM (export) or CommonJS (module.exports) accordingly.
1711
+ - You MUST run validation (pnpm lint, pnpm test, or the project's validation command) after every edit using publish or run_command.
1712
+ - Do NOT call finish until validation passes. If validation fails, fix the issue and re-run validation.
1713
+ - Do not expose secrets or run destructive commands.
1714
+ - Finish only when done. Use summary, not message.
1715
+ - Do not repeat the same tool call with the same arguments.
1716
+ - Do not ask the user for clarification. Do not say "No task was provided". Use the Task and Description above to proceed.
1717
+
1718
+ Example response for "Add a greet function":
1719
+ { "tool_calls": [
1720
+ { "id": "1", "name": "think", "arguments": { "thought": "Create greet.js with greet(name)." } },
1721
+ { "id": "2", "name": "write_file", "arguments": { "path": "greet.js", "content": "export function greet(name) { return 'Hello, ' + name + '!'; }
1722
+ " } },
1723
+ { "id": "3", "name": "publish", "arguments": {} },
1724
+ { "id": "4", "name": "finish", "arguments": { "summary": "Created greet.js and verified tests pass.", "success": true } }
1725
+ ] }`;
1726
+
1727
+ // ../agent/dist/prompt-versioning.js
1728
+ import fs7 from "node:fs/promises";
1729
+ import path9 from "node:path";
1730
+ import crypto from "node:crypto";
1731
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
1732
+ var __dirname3 = path9.dirname(fileURLToPath4(import.meta.url));
1733
+ var PROMPTS_PATH = path9.resolve(__dirname3, "..", "src", "prompts.ts");
1734
+ var PLANNER_PATH = path9.resolve(__dirname3, "..", "src", "planner.ts");
1735
+ function hashString(value) {
1736
+ return crypto.createHash("sha256").update(value).digest("hex").slice(0, 16);
1737
+ }
1738
+ function hashPrompts(input) {
1739
+ return hashString([input.systemPrompt, input.textToolsPrompt, input.planningPrompt ?? ""].join("\n---\n"));
1740
+ }
1741
+ async function readPromptsSource() {
1742
+ const source = await fs7.readFile(PROMPTS_PATH, "utf-8");
1743
+ const systemMatch = /export const AGENT_SYSTEM_PROMPT =\s*(?:loadPromptFromEnv\('OMEGA_SYSTEM_PROMPT'\) \?\?\s*)?`([\s\S]*?)`;/m.exec(source);
1744
+ const textToolsMatch = /export const TEXT_TOOLS_SYSTEM_PROMPT =\s*(?:loadPromptFromEnv\('OMEGA_TEXT_TOOLS_PROMPT'\) \?\?\s*)?`([\s\S]*?)`;/m.exec(source);
1745
+ return {
1746
+ systemPrompt: systemMatch?.[1] ?? "",
1747
+ textToolsPrompt: textToolsMatch?.[1] ?? ""
1748
+ };
1749
+ }
1750
+ async function loadCurrentPrompts() {
1751
+ const { systemPrompt, textToolsPrompt } = await readPromptsSource();
1752
+ const plannerSource = await fs7.readFile(PLANNER_PATH, "utf-8");
1753
+ const planningMatch = /const PLAN_PROMPT = `([\s\S]*?)`;/m.exec(plannerSource);
1754
+ const planningPrompt = planningMatch?.[1];
1755
+ return {
1756
+ name: `auto-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`,
1757
+ sourcePath: PROMPTS_PATH,
1758
+ systemPrompt,
1759
+ textToolsPrompt,
1760
+ planningPrompt,
1761
+ hash: hashPrompts({ systemPrompt, textToolsPrompt, planningPrompt })
1762
+ };
1763
+ }
1764
+
1435
1765
  // ../../apps/cli/src/commands/bench.ts
1436
1766
  async function waitForApi2(apiUrl, maxMs = 1e4) {
1437
1767
  const deadline = Date.now() + maxMs;
@@ -1447,38 +1777,75 @@ async function waitForApi2(apiUrl, maxMs = 1e4) {
1447
1777
  }
1448
1778
  function currentProject(apiUrl) {
1449
1779
  const cwd = process.cwd();
1450
- const name = path9.basename(cwd);
1780
+ const name = path10.basename(cwd);
1451
1781
  return ensureProject2(apiUrl, `bench-${name}`, cwd);
1452
1782
  }
1453
- var runCmd = new Command8("run").description("Run a benchmark suite").option("--suite <name>", "suite name: synthetic | deep-swe", "synthetic").option("--path <dir>", "path to DeepSWE tasks directory (for deep-swe suite)").option("--n-tasks <n>", "limit number of tasks (for deep-swe)", parseInt).option("--sample-seed <n>", "seed for deterministic sampling (for deep-swe)", parseInt).option("--timeout <ms>", "per-task timeout in ms", "120000").option("--output-dir <dir>", "report output directory", ".omega/reports").option("--provider <name>", "provider to use for benchmark tasks").option("--model <model>", "model to use for benchmark tasks").action(async (opts) => {
1454
- const apiUrl = getApiUrl();
1455
- await waitForApi2(apiUrl);
1456
- const timeoutMs = Number(opts.timeout);
1457
- let tasks;
1458
- let suiteName;
1783
+ async function savePromptVersion(apiUrl, name) {
1784
+ const current = await loadCurrentPrompts();
1785
+ const versionRes = await fetch(`${apiUrl}/prompt-versions`, {
1786
+ method: "POST",
1787
+ headers: { "Content-Type": "application/json" },
1788
+ body: JSON.stringify({
1789
+ name: name ?? current.name,
1790
+ sourcePath: current.sourcePath,
1791
+ systemPrompt: current.systemPrompt,
1792
+ textToolsPrompt: current.textToolsPrompt,
1793
+ hash: current.hash,
1794
+ metadata: { planningPrompt: current.planningPrompt }
1795
+ })
1796
+ });
1797
+ if (!versionRes.ok) {
1798
+ console.warn("Could not save prompt version:", await versionRes.text());
1799
+ return void 0;
1800
+ }
1801
+ return versionRes.json();
1802
+ }
1803
+ async function loadPromptSpec(source) {
1804
+ if (source === "current") {
1805
+ const current = await loadCurrentPrompts();
1806
+ return {
1807
+ name: "current",
1808
+ systemPrompt: current.systemPrompt,
1809
+ textToolsPrompt: current.textToolsPrompt
1810
+ };
1811
+ }
1812
+ const raw = await fs8.readFile(path10.resolve(source), "utf-8");
1813
+ const parsed = JSON.parse(raw);
1814
+ if (!parsed.systemPrompt || !parsed.textToolsPrompt) {
1815
+ throw new Error("Prompt spec must include systemPrompt and textToolsPrompt");
1816
+ }
1817
+ return parsed;
1818
+ }
1819
+ async function resolveTasks(opts) {
1459
1820
  if (opts.suite === "deep-swe") {
1460
1821
  if (!opts.path) {
1461
1822
  throw new Error("--path is required for the deep-swe suite");
1462
1823
  }
1463
- tasks = await loadDeepSWESuite({
1824
+ const tasks = await loadDeepSWESuite({
1464
1825
  tasksDir: opts.path,
1465
1826
  nTasks: opts.nTasks,
1466
1827
  sampleSeed: opts.sampleSeed
1467
1828
  });
1468
- suiteName = "deep-swe";
1469
- } else if (opts.suite === "synthetic") {
1470
- tasks = syntheticSuite();
1829
+ return { tasks, suiteName: "deep-swe" };
1830
+ }
1831
+ if (opts.suite === "synthetic") {
1832
+ let tasks = syntheticSuite();
1471
1833
  if (opts.nTasks !== void 0 && opts.nTasks > 0) {
1472
1834
  tasks = tasks.slice(0, opts.nTasks);
1473
1835
  }
1474
- suiteName = "synthetic";
1475
- } else {
1476
- throw new Error(`Unknown suite: ${opts.suite}`);
1836
+ return { tasks, suiteName: "synthetic" };
1477
1837
  }
1838
+ throw new Error(`Unknown suite: ${opts.suite}`);
1839
+ }
1840
+ var runCmd = new Command8("run").description("Run a benchmark suite").option("--suite <name>", "suite name: synthetic | deep-swe", "synthetic").option("--path <dir>", "path to DeepSWE tasks directory (for deep-swe suite)").option("--n-tasks <n>", "limit number of tasks", parseInt).option("--sample-seed <n>", "seed for deterministic sampling (for deep-swe)", parseInt).option("--timeout <ms>", "per-task timeout in ms", "120000").option("--output-dir <dir>", "report output directory", ".omega/reports").option("--provider <name>", "provider to use for benchmark tasks").option("--model <model>", "model to use for benchmark tasks").action(async (opts) => {
1841
+ const apiUrl = getApiUrl();
1842
+ await waitForApi2(apiUrl);
1843
+ const { tasks, suiteName } = await resolveTasks(opts);
1478
1844
  if (tasks.length === 0) {
1479
1845
  console.log("No benchmark tasks to run.");
1480
1846
  return;
1481
1847
  }
1848
+ const timeoutMs = Number(opts.timeout);
1482
1849
  console.log(`Running ${String(tasks.length)} benchmark tasks against ${apiUrl}`);
1483
1850
  const report = await runBenchmark(tasks, {
1484
1851
  apiUrl,
@@ -1514,7 +1881,113 @@ var optimiseCmd = new Command8("optimise").description("Create a self-improve ta
1514
1881
  console.log(`Created self-improve task ${task.id}`);
1515
1882
  console.log(`Run \`omega task run ${task.id}\` to execute it.`);
1516
1883
  });
1517
- var benchCmd = new Command8("bench").description("Run benchmarks and optimise prompts").addCommand(runCmd).addCommand(optimiseCmd);
1884
+ var abCmd = new Command8("ab").description("Run an A/B benchmark comparing two prompt versions").option("--baseline <source>", 'baseline prompts: "current" or path to JSON file', "current").option("--candidate <source>", "candidate prompts: path to JSON file").option("--suite <name>", "suite name: synthetic | deep-swe", "synthetic").option("--path <dir>", "path to DeepSWE tasks directory (for deep-swe suite)").option("--n-tasks <n>", "limit number of tasks", parseInt).option("--sample-seed <n>", "seed for deterministic sampling (for deep-swe)", parseInt).option("--timeout <ms>", "per-task timeout in ms", "120000").option("--output-dir <dir>", "report output directory", ".omega/reports").option("--provider <name>", "provider to use for benchmark tasks").option("--model <model>", "model to use for benchmark tasks").action(async (opts) => {
1885
+ if (!opts.candidate) {
1886
+ throw new Error("--candidate is required");
1887
+ }
1888
+ if (opts.suite === "deep-swe") {
1889
+ throw new Error("DeepSWE A/B not yet supported; use --suite synthetic");
1890
+ }
1891
+ const apiUrl = getApiUrl();
1892
+ await waitForApi2(apiUrl);
1893
+ const { tasks, suiteName } = await resolveTasks(opts);
1894
+ if (tasks.length === 0) {
1895
+ console.log("No benchmark tasks to run.");
1896
+ return;
1897
+ }
1898
+ const timeoutMs = Number(opts.timeout);
1899
+ const baselineSpec = await loadPromptSpec(opts.baseline);
1900
+ const candidateSpec = await loadPromptSpec(opts.candidate);
1901
+ console.log(`Baseline: ${baselineSpec.name}`);
1902
+ console.log(`Candidate: ${candidateSpec.name}`);
1903
+ const report = await runAbBenchmark(tasks, baselineSpec, candidateSpec, {
1904
+ apiUrl,
1905
+ suiteName,
1906
+ timeoutMs,
1907
+ provider: opts.provider,
1908
+ model: opts.model,
1909
+ onProgress: (result) => {
1910
+ const symbol = result.evaluation.passed ? "\u2713" : "\u2717";
1911
+ console.log(`${symbol} ${result.task.name} [${result.status}] ${String(result.durationMs)}ms`);
1912
+ }
1913
+ });
1914
+ await fs8.mkdir(opts.outputDir, { recursive: true });
1915
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1916
+ const jsonFile = path10.join(opts.outputDir, `ab-${ts}.json`);
1917
+ const mdFile = path10.join(opts.outputDir, `ab-${ts}.md`);
1918
+ await fs8.writeFile(jsonFile, JSON.stringify(report, null, 2), "utf-8");
1919
+ await fs8.writeFile(mdFile, formatAbReport(report), "utf-8");
1920
+ console.log("\n" + formatAbReport(report));
1921
+ console.log(`
1922
+ A/B report written to ${jsonFile}`);
1923
+ });
1924
+ var loopCmd = new Command8("loop").description("Run a self-improve benchmark loop").option("--suite <name>", "suite name: synthetic | deep-swe", "synthetic").option("--path <dir>", "path to DeepSWE tasks directory (for deep-swe suite)").option("--n-tasks <n>", "limit number of tasks", parseInt).option("--sample-seed <n>", "seed for deterministic sampling (for deep-swe)", parseInt).option("--iterations <n>", "max iterations", "1").option("--timeout <ms>", "per-task timeout in ms", "120000").option("--output-dir <dir>", "report output directory", ".omega/reports").option("--provider <name>", "provider to use for self-improve tasks").option("--model <model>", "model to use for self-improve tasks").action(async (opts) => {
1925
+ const apiUrl = getApiUrl();
1926
+ await waitForApi2(apiUrl);
1927
+ const { tasks, suiteName } = await resolveTasks(opts);
1928
+ if (tasks.length === 0) {
1929
+ console.log("No benchmark tasks to run.");
1930
+ return;
1931
+ }
1932
+ const timeoutMs = Number(opts.timeout);
1933
+ const maxIterations = Number(opts.iterations);
1934
+ const project = await currentProject(apiUrl);
1935
+ for (let i = 1; i <= maxIterations; i++) {
1936
+ console.log(`
1937
+ === Iteration ${String(i)}/${String(maxIterations)} ===`);
1938
+ const beforeVersion = await savePromptVersion(apiUrl, `loop-before-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`);
1939
+ if (beforeVersion) {
1940
+ console.log(`Saved before-loop prompt version: ${beforeVersion.name} (${beforeVersion.id})`);
1941
+ }
1942
+ console.log(`
1943
+ Running ${String(tasks.length)} benchmark tasks...`);
1944
+ const report = await runBenchmark(tasks, {
1945
+ apiUrl,
1946
+ suiteName,
1947
+ timeoutMs,
1948
+ provider: opts.provider,
1949
+ model: opts.model,
1950
+ onProgress: (result) => {
1951
+ const symbol = result.evaluation.passed ? "\u2713" : "\u2717";
1952
+ console.log(`${symbol} ${result.task.name} [${result.status}] ${String(result.durationMs)}ms`);
1953
+ }
1954
+ });
1955
+ await writeReport(report, opts.outputDir);
1956
+ printSummary(report);
1957
+ const passRate2 = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
1958
+ if (passRate2 === 100) {
1959
+ console.log("\nAll tasks passed. Loop complete.");
1960
+ break;
1961
+ }
1962
+ const context = await loadOptimisationContext(apiUrl, opts.outputDir);
1963
+ if (!context) {
1964
+ throw new Error("Benchmark report could not be loaded for optimisation");
1965
+ }
1966
+ const prompt = buildOptimisePrompt(context.report, context.failedResult, context.traceFlowText);
1967
+ const task = await submitOptimiseTask(apiUrl, project.id, prompt);
1968
+ if (opts.provider || opts.model) {
1969
+ await fetch(`${apiUrl}/tasks/${task.id}`, {
1970
+ method: "PATCH",
1971
+ headers: { "Content-Type": "application/json" },
1972
+ body: JSON.stringify({ provider: opts.provider, model: opts.model })
1973
+ });
1974
+ }
1975
+ console.log(`
1976
+ Created self-improve task ${task.id}${opts.provider ? ` (provider: ${opts.provider})` : ""}${opts.model ? ` (model: ${opts.model})` : ""}`);
1977
+ console.log("Running agent...");
1978
+ await runTask(apiUrl, task.id);
1979
+ const finished = await waitForTask(apiUrl, task.id, timeoutMs * 5);
1980
+ console.log(`Agent finished: ${finished.status}`);
1981
+ if (finished.error) {
1982
+ console.error(`Agent error: ${finished.error}`);
1983
+ }
1984
+ const afterVersion = await savePromptVersion(apiUrl, `loop-after-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`);
1985
+ if (afterVersion) {
1986
+ console.log(`Saved after-loop prompt version: ${afterVersion.name} (${afterVersion.id})`);
1987
+ }
1988
+ }
1989
+ });
1990
+ var benchCmd = new Command8("bench").description("Run benchmarks and optimise prompts").addCommand(runCmd).addCommand(abCmd).addCommand(optimiseCmd).addCommand(loopCmd);
1518
1991
 
1519
1992
  // ../../apps/cli/src/index.ts
1520
1993
  program2.name("harness").description("Omega harness CLI").version("0.1.0").option("--api <url>", "API base URL", "http://localhost:4000");
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  -42
2
2
  /pglite/data
3
- 1783134189
3
+ 1783143868
4
4
  5432
5
5
 
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@castlemilk/omega",
3
- "version": "0.6.14",
3
+ "version": "0.6.15",
4
4
  "description": "Omega Harness CLI - installable via npx",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",