@castlemilk/omega 0.6.11 → 0.6.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/dist/cli.js +1230 -231
  2. package/dist/server/dist/app.d.ts.map +1 -1
  3. package/dist/server/dist/app.js +2 -0
  4. package/dist/server/dist/app.js.map +1 -1
  5. package/dist/server/dist/lib/run-task.js +2 -2
  6. package/dist/server/dist/lib/run-task.js.map +1 -1
  7. package/dist/server/dist/routes/metrics.d.ts.map +1 -1
  8. package/dist/server/dist/routes/metrics.js +76 -2
  9. package/dist/server/dist/routes/metrics.js.map +1 -1
  10. package/dist/server/dist/routes/prompt-versions.d.ts +4 -0
  11. package/dist/server/dist/routes/prompt-versions.d.ts.map +1 -0
  12. package/dist/server/dist/routes/prompt-versions.js +40 -0
  13. package/dist/server/dist/routes/prompt-versions.js.map +1 -0
  14. package/dist/server/dist/routes/tasks.js.map +1 -1
  15. package/dist/server/node_modules/@grpc/proto-loader/build/bin/proto-loader-gen-types.js +0 -0
  16. package/dist/server/node_modules/@omega/agent/dist/executor.d.ts.map +1 -1
  17. package/dist/server/node_modules/@omega/agent/dist/executor.js +40 -5
  18. package/dist/server/node_modules/@omega/agent/dist/executor.js.map +1 -1
  19. package/dist/server/node_modules/@omega/agent/dist/index.d.ts +1 -0
  20. package/dist/server/node_modules/@omega/agent/dist/index.d.ts.map +1 -1
  21. package/dist/server/node_modules/@omega/agent/dist/index.js +1 -0
  22. package/dist/server/node_modules/@omega/agent/dist/index.js.map +1 -1
  23. package/dist/server/node_modules/@omega/agent/dist/planner.d.ts +5 -0
  24. package/dist/server/node_modules/@omega/agent/dist/planner.d.ts.map +1 -1
  25. package/dist/server/node_modules/@omega/agent/dist/planner.js +21 -3
  26. package/dist/server/node_modules/@omega/agent/dist/planner.js.map +1 -1
  27. package/dist/server/node_modules/@omega/agent/dist/prompt-versioning.d.ts +22 -0
  28. package/dist/server/node_modules/@omega/agent/dist/prompt-versioning.d.ts.map +1 -0
  29. package/dist/server/node_modules/@omega/agent/dist/prompt-versioning.js +53 -0
  30. package/dist/server/node_modules/@omega/agent/dist/prompt-versioning.js.map +1 -0
  31. package/dist/server/node_modules/@omega/agent/dist/prompts.d.ts +2 -2
  32. package/dist/server/node_modules/@omega/agent/dist/prompts.d.ts.map +1 -1
  33. package/dist/server/node_modules/@omega/agent/dist/prompts.js +21 -2
  34. package/dist/server/node_modules/@omega/agent/dist/prompts.js.map +1 -1
  35. package/dist/server/node_modules/@omega/agent/package.json +4 -0
  36. package/dist/server/node_modules/@omega/core/dist/provider.d.ts +12 -2
  37. package/dist/server/node_modules/@omega/core/dist/provider.d.ts.map +1 -1
  38. package/dist/server/node_modules/@omega/db/dist/index.d.ts +1 -0
  39. package/dist/server/node_modules/@omega/db/dist/index.d.ts.map +1 -1
  40. package/dist/server/node_modules/@omega/db/dist/index.js +1 -0
  41. package/dist/server/node_modules/@omega/db/dist/index.js.map +1 -1
  42. package/dist/server/node_modules/@omega/db/dist/prompt-versions.d.ts +19 -0
  43. package/dist/server/node_modules/@omega/db/dist/prompt-versions.d.ts.map +1 -0
  44. package/dist/server/node_modules/@omega/db/dist/prompt-versions.js +32 -0
  45. package/dist/server/node_modules/@omega/db/dist/prompt-versions.js.map +1 -0
  46. package/dist/server/node_modules/@omega/db/generated/client/edge.js +19 -4
  47. package/dist/server/node_modules/@omega/db/generated/client/index-browser.js +16 -1
  48. package/dist/server/node_modules/@omega/db/generated/client/index.d.ts +1511 -93
  49. package/dist/server/node_modules/@omega/db/generated/client/index.js +19 -4
  50. package/dist/server/node_modules/@omega/db/generated/client/libquery_engine-darwin-arm64.dylib.node +0 -0
  51. package/dist/server/node_modules/@omega/db/generated/client/package.json +1 -1
  52. package/dist/server/node_modules/@omega/db/generated/client/schema.prisma +14 -0
  53. package/dist/server/node_modules/@omega/db/generated/client/wasm.js +19 -4
  54. package/dist/server/node_modules/@omega/db/prisma/migrations/20260703010000_add_agent_run_usage/migration.sql +4 -0
  55. package/dist/server/node_modules/@omega/db/prisma/migrations/20260703020000_add_prompt_versions/migration.sql +16 -0
  56. package/dist/server/node_modules/@omega/providers/dist/anthropic.d.ts +2 -2
  57. package/dist/server/node_modules/@omega/providers/dist/anthropic.d.ts.map +1 -1
  58. package/dist/server/node_modules/@omega/providers/dist/anthropic.js +16 -1
  59. package/dist/server/node_modules/@omega/providers/dist/anthropic.js.map +1 -1
  60. package/dist/server/node_modules/@omega/providers/dist/gemini.d.ts +2 -2
  61. package/dist/server/node_modules/@omega/providers/dist/gemini.d.ts.map +1 -1
  62. package/dist/server/node_modules/@omega/providers/dist/gemini.js +14 -1
  63. package/dist/server/node_modules/@omega/providers/dist/gemini.js.map +1 -1
  64. package/dist/server/node_modules/@omega/providers/dist/kimi.d.ts +3 -3
  65. package/dist/server/node_modules/@omega/providers/dist/kimi.d.ts.map +1 -1
  66. package/dist/server/node_modules/@omega/providers/dist/ollama.d.ts +2 -2
  67. package/dist/server/node_modules/@omega/providers/dist/ollama.d.ts.map +1 -1
  68. package/dist/server/node_modules/@omega/providers/dist/ollama.js +13 -1
  69. package/dist/server/node_modules/@omega/providers/dist/ollama.js.map +1 -1
  70. package/dist/server/node_modules/@omega/providers/dist/openai.d.ts +3 -3
  71. package/dist/server/node_modules/@omega/providers/dist/openai.d.ts.map +1 -1
  72. package/dist/server/node_modules/@omega/providers/dist/openai.js +24 -3
  73. package/dist/server/node_modules/@omega/providers/dist/openai.js.map +1 -1
  74. package/dist/server/node_modules/@omega/providers/dist/providers.test.js +13 -9
  75. package/dist/server/node_modules/@omega/providers/dist/providers.test.js.map +1 -1
  76. package/dist/server/node_modules/mime/cli.js +0 -0
  77. package/dist/server/node_modules/mime/src/build.js +0 -0
  78. package/dist/server/node_modules/wrap-ansi/index.js +0 -0
  79. package/dist/server/pglite-data/base/5/1247 +0 -0
  80. package/dist/server/pglite-data/base/5/1249 +0 -0
  81. package/dist/server/pglite-data/base/5/1259 +0 -0
  82. package/dist/server/pglite-data/base/5/1259_fsm +0 -0
  83. package/dist/server/pglite-data/base/5/1259_vm +0 -0
  84. package/dist/server/pglite-data/base/5/16384 +0 -0
  85. package/dist/server/pglite-data/base/5/16392 +0 -0
  86. package/dist/server/pglite-data/base/5/16394 +0 -0
  87. package/dist/server/pglite-data/base/5/16404 +0 -0
  88. package/dist/server/pglite-data/base/5/16406 +0 -0
  89. package/dist/server/pglite-data/base/5/16406_fsm +0 -0
  90. package/dist/server/pglite-data/base/5/16421 +0 -0
  91. package/dist/server/pglite-data/base/5/16423 +0 -0
  92. package/dist/server/pglite-data/base/5/16437 +0 -0
  93. package/dist/server/pglite-data/base/5/16439 +0 -0
  94. package/dist/server/pglite-data/base/5/16439_fsm +0 -0
  95. package/dist/server/pglite-data/base/5/16449 +0 -0
  96. package/dist/server/pglite-data/base/5/16451 +0 -0
  97. package/dist/server/pglite-data/base/5/16462 +0 -0
  98. package/dist/server/pglite-data/base/5/16464 +0 -0
  99. package/dist/server/pglite-data/base/5/16478 +0 -0
  100. package/dist/server/pglite-data/base/5/16480 +0 -0
  101. package/dist/server/pglite-data/base/5/16510 +0 -0
  102. package/dist/server/pglite-data/base/5/16511 +0 -0
  103. package/dist/server/pglite-data/base/5/16512 +0 -0
  104. package/dist/server/pglite-data/base/5/16513 +0 -0
  105. package/dist/server/pglite-data/base/5/16514 +0 -0
  106. package/dist/server/pglite-data/base/5/24576 +0 -0
  107. package/dist/server/pglite-data/base/5/24576_fsm +0 -0
  108. package/dist/server/pglite-data/base/5/24589 +0 -0
  109. package/dist/server/pglite-data/base/5/24591 +0 -0
  110. package/dist/server/pglite-data/base/5/24592 +0 -0
  111. package/dist/server/pglite-data/base/5/2579 +0 -0
  112. package/dist/server/pglite-data/base/5/2604 +0 -0
  113. package/dist/server/pglite-data/base/5/2606 +0 -0
  114. package/dist/server/pglite-data/base/5/2608 +0 -0
  115. package/dist/server/pglite-data/base/5/2610 +0 -0
  116. package/dist/server/pglite-data/base/5/2620 +0 -0
  117. package/dist/server/pglite-data/base/5/2656 +0 -0
  118. package/dist/server/pglite-data/base/5/2657 +0 -0
  119. package/dist/server/pglite-data/base/5/2658 +0 -0
  120. package/dist/server/pglite-data/base/5/2659 +0 -0
  121. package/dist/server/pglite-data/base/5/2662 +0 -0
  122. package/dist/server/pglite-data/base/5/2663 +0 -0
  123. package/dist/server/pglite-data/base/5/2664 +0 -0
  124. package/dist/server/pglite-data/base/5/2665 +0 -0
  125. package/dist/server/pglite-data/base/5/2666 +0 -0
  126. package/dist/server/pglite-data/base/5/2667 +0 -0
  127. package/dist/server/pglite-data/base/5/2673 +0 -0
  128. package/dist/server/pglite-data/base/5/2674 +0 -0
  129. package/dist/server/pglite-data/base/5/2678 +0 -0
  130. package/dist/server/pglite-data/base/5/2679 +0 -0
  131. package/dist/server/pglite-data/base/5/2703 +0 -0
  132. package/dist/server/pglite-data/base/5/2704 +0 -0
  133. package/dist/server/pglite-data/base/5/32768 +0 -0
  134. package/dist/server/pglite-data/base/5/32779 +0 -0
  135. package/dist/server/pglite-data/base/5/32780 +0 -0
  136. package/dist/server/pglite-data/base/5/32781 +0 -0
  137. package/dist/server/pglite-data/base/5/32783 +0 -0
  138. package/dist/server/pglite-data/base/5/3455 +0 -0
  139. package/dist/server/pglite-data/base/5/pg_internal.init +0 -0
  140. package/dist/server/pglite-data/global/pg_control +0 -0
  141. package/dist/server/pglite-data/global/pg_internal.init +0 -0
  142. package/dist/server/pglite-data/pg_wal/000000010000000000000001 +0 -0
  143. package/dist/server/pglite-data/pg_xact/0000 +0 -0
  144. package/dist/server/pglite-data/postmaster.pid +1 -1
  145. package/dist/server/src/app.ts +2 -0
  146. package/dist/server/src/lib/run-task.ts +2 -2
  147. package/dist/server/src/routes/metrics.ts +81 -2
  148. package/dist/server/src/routes/prompt-versions.ts +46 -0
  149. package/dist/server/src/routes/tasks.ts +2 -2
  150. package/dist/web/dist/assets/index-BVjHH_u_.js +40 -0
  151. package/dist/web/dist/assets/index-BkzWwMOJ.js +40 -0
  152. package/dist/web/dist/assets/index-D4rY_mY0.css +1 -0
  153. package/dist/web/dist/assets/index-DHQbiQAM.css +1 -0
  154. package/dist/web/dist/index.html +2 -2
  155. package/package.json +14 -13
package/dist/cli.js CHANGED
@@ -66,20 +66,20 @@ function useTasks(pollMs = 1e3) {
66
66
  const data = await res.json();
67
67
  setConnected(true);
68
68
  const current = {};
69
- for (const task of data) {
70
- current[task.id] = task;
71
- const prev = previousTasks.current[task.id];
69
+ for (const task2 of data) {
70
+ current[task2.id] = task2;
71
+ const prev = previousTasks.current[task2.id];
72
72
  if (!prev) {
73
- addLog(`New task queued: "${task.title}"`, "info");
74
- } else if (prev.status !== task.status) {
75
- if (task.status === "in_progress") {
76
- const provider = task.provider ?? "unknown";
77
- const model = task.model ?? "unknown";
78
- addLog(`LLM picked up "${task.title}" \u2192 ${provider}/${model}`, "info");
79
- } else if (task.status === "done") {
80
- addLog(`Completed: "${task.title}"`, "success");
81
- } else if (task.status === "failed") {
82
- addLog(`Failed: "${task.title}"${task.error ? ` \u2014 ${task.error}` : ""}`, "error");
73
+ addLog(`New task queued: "${task2.title}"`, "info");
74
+ } else if (prev.status !== task2.status) {
75
+ if (task2.status === "in_progress") {
76
+ const provider = task2.provider ?? "unknown";
77
+ const model = task2.model ?? "unknown";
78
+ addLog(`LLM picked up "${task2.title}" \u2192 ${provider}/${model}`, "info");
79
+ } else if (task2.status === "done") {
80
+ addLog(`Completed: "${task2.title}"`, "success");
81
+ } else if (task2.status === "failed") {
82
+ addLog(`Failed: "${task2.title}"${task2.error ? ` \u2014 ${task2.error}` : ""}`, "error");
83
83
  }
84
84
  }
85
85
  }
@@ -231,16 +231,16 @@ function TaskList({
231
231
  children: [
232
232
  header,
233
233
  visible.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No tasks yet. Create one in the web UI." }),
234
- visible.map((task) => {
235
- const providerText = task.provider ? `${task.provider}/${task.model ?? ""}` : "-";
234
+ visible.map((task2) => {
235
+ const providerText = task2.provider ? `${task2.provider}/${task2.model ?? ""}` : "-";
236
236
  const titleMax = Math.max(10, columns - 62);
237
237
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
238
- /* @__PURE__ */ jsx(Text, { color: statusColor(task.status), children: statusSymbol(task.status) }),
239
- /* @__PURE__ */ jsx(Box, { width: 20, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: formatTime(new Date(task.updatedAt)) }) }),
240
- /* @__PURE__ */ jsx(Box, { width: 12, children: /* @__PURE__ */ jsx(Text, { color: statusColor(task.status), bold: true, children: task.status.toUpperCase() }) }),
238
+ /* @__PURE__ */ jsx(Text, { color: statusColor(task2.status), children: statusSymbol(task2.status) }),
239
+ /* @__PURE__ */ jsx(Box, { width: 20, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: formatTime(new Date(task2.updatedAt)) }) }),
240
+ /* @__PURE__ */ jsx(Box, { width: 12, children: /* @__PURE__ */ jsx(Text, { color: statusColor(task2.status), bold: true, children: task2.status.toUpperCase() }) }),
241
241
  /* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncate(providerText, 23) }) }),
242
- /* @__PURE__ */ jsx(Box, { flexGrow: 1, children: /* @__PURE__ */ jsx(Text, { children: truncate(task.title, titleMax) }) })
243
- ] }, task.id);
242
+ /* @__PURE__ */ jsx(Box, { flexGrow: 1, children: /* @__PURE__ */ jsx(Text, { children: truncate(task2.title, titleMax) }) })
243
+ ] }, task2.id);
244
244
  })
245
245
  ]
246
246
  }
@@ -318,6 +318,76 @@ var init_tui = __esm({
318
318
  }
319
319
  });
320
320
 
321
+ // ../agent/dist/prompt-versioning.js
322
+ var prompt_versioning_exports = {};
323
+ __export(prompt_versioning_exports, {
324
+ envForPrompts: () => envForPrompts,
325
+ hashPrompts: () => hashPrompts,
326
+ loadCurrentPrompts: () => loadCurrentPrompts,
327
+ readPromptsSource: () => readPromptsSource,
328
+ saveCurrentPrompts: () => saveCurrentPrompts
329
+ });
330
+ import fs7 from "node:fs/promises";
331
+ import path10 from "node:path";
332
+ import crypto from "node:crypto";
333
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
334
+ function hashString(value) {
335
+ return crypto.createHash("sha256").update(value).digest("hex").slice(0, 16);
336
+ }
337
+ function hashPrompts(input) {
338
+ return hashString([input.systemPrompt, input.textToolsPrompt, input.planningPrompt ?? ""].join("\n---\n"));
339
+ }
340
+ async function readPromptsSource() {
341
+ const source = await fs7.readFile(PROMPTS_PATH, "utf-8");
342
+ const systemMatch = /export const AGENT_SYSTEM_PROMPT =\s*(?:loadPromptFromEnv\('OMEGA_SYSTEM_PROMPT'\) \?\?\s*)?`([\s\S]*?)`;/m.exec(source);
343
+ const textToolsMatch = /export const TEXT_TOOLS_SYSTEM_PROMPT =\s*(?:loadPromptFromEnv\('OMEGA_TEXT_TOOLS_PROMPT'\) \?\?\s*)?`([\s\S]*?)`;/m.exec(source);
344
+ return {
345
+ systemPrompt: systemMatch?.[1] ?? "",
346
+ textToolsPrompt: textToolsMatch?.[1] ?? ""
347
+ };
348
+ }
349
+ async function loadCurrentPrompts() {
350
+ const { systemPrompt, textToolsPrompt } = await readPromptsSource();
351
+ const plannerSource = await fs7.readFile(PLANNER_PATH, "utf-8");
352
+ const planningMatch = /const PLAN_PROMPT = `([\s\S]*?)`;/m.exec(plannerSource);
353
+ const planningPrompt = planningMatch?.[1];
354
+ return {
355
+ name: `auto-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`,
356
+ sourcePath: PROMPTS_PATH,
357
+ systemPrompt,
358
+ textToolsPrompt,
359
+ planningPrompt,
360
+ hash: hashPrompts({ systemPrompt, textToolsPrompt, planningPrompt })
361
+ };
362
+ }
363
+ async function saveCurrentPrompts(input) {
364
+ const source = await fs7.readFile(PROMPTS_PATH, "utf-8");
365
+ const replaceConst = (name, value) => {
366
+ const pattern = new RegExp(`export const ${name} =\\s*(?:process\\.env\\.OMEGA_[A-Z_]+_PROMPT \\?\\?\\s*)?\`[\\s\\S]*?\`;`, "m");
367
+ return source.replace(pattern, `export const ${name} =
368
+ process.env.OMEGA_${name.replace(/([A-Z])/g, "_$1").toUpperCase()}_PROMPT ??
369
+ \`${value}\`;`);
370
+ };
371
+ let updated = replaceConst("AGENT_SYSTEM_PROMPT", input.systemPrompt);
372
+ updated = replaceConst("TEXT_TOOLS_SYSTEM_PROMPT", input.textToolsPrompt);
373
+ await fs7.writeFile(PROMPTS_PATH, updated, "utf-8");
374
+ }
375
+ function envForPrompts(input) {
376
+ return {
377
+ OMEGA_SYSTEM_PROMPT: input.systemPrompt,
378
+ OMEGA_TEXT_TOOLS_PROMPT: input.textToolsPrompt
379
+ };
380
+ }
381
+ var __dirname3, PROMPTS_PATH, PLANNER_PATH;
382
+ var init_prompt_versioning = __esm({
383
+ "../agent/dist/prompt-versioning.js"() {
384
+ "use strict";
385
+ __dirname3 = path10.dirname(fileURLToPath4(import.meta.url));
386
+ PROMPTS_PATH = path10.resolve(__dirname3, "..", "src", "prompts.ts");
387
+ PLANNER_PATH = path10.resolve(__dirname3, "..", "src", "planner.ts");
388
+ }
389
+ });
390
+
321
391
  // ../../apps/cli/src/index.ts
322
392
  import { program as program2 } from "commander";
323
393
 
@@ -328,10 +398,10 @@ import { Command } from "commander";
328
398
  import { program } from "commander";
329
399
  function getApiUrl() {
330
400
  const opts = program.opts();
331
- return opts.api ?? "http://localhost:4000";
401
+ return opts.api ?? process.env.HARNESS_API_URL ?? "http://localhost:4000";
332
402
  }
333
- async function apiFetch(path10, init) {
334
- const url = `${getApiUrl()}${path10}`;
403
+ async function apiFetch(path12, init) {
404
+ const url = `${getApiUrl()}${path12}`;
335
405
  const res = await fetch(url, init);
336
406
  const data = await res.json().catch(() => ({}));
337
407
  if (!res.ok) {
@@ -446,7 +516,7 @@ var taskFeedCmd = new Command2("feed").description("Feed a task into the harness
446
516
  // ../../apps/cli/src/commands/task.ts
447
517
  var taskCmd = new Command3("task").description("Manage tasks");
448
518
  taskCmd.command("create").description("Create a task").requiredOption("--project <id>", "project id").requiredOption("--title <title>", "task title").option("--description <text>", "task description").option("--complexity <level>", "simple | medium | complex", "simple").option("--tags <tags>", "comma-separated tags").action(async (opts) => {
449
- const task = await apiFetch("/tasks", {
519
+ const task2 = await apiFetch("/tasks", {
450
520
  method: "POST",
451
521
  headers: { "Content-Type": "application/json" },
452
522
  body: JSON.stringify({
@@ -457,7 +527,7 @@ taskCmd.command("create").description("Create a task").requiredOption("--project
457
527
  tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : []
458
528
  })
459
529
  });
460
- console.log(JSON.stringify(task, null, 2));
530
+ console.log(JSON.stringify(task2, null, 2));
461
531
  });
462
532
  taskCmd.command("list").description("List tasks").option("--project <id>", "filter by project id").action(async (opts) => {
463
533
  const query = opts.project ? `?projectId=${opts.project}` : "";
@@ -472,14 +542,17 @@ taskCmd.addCommand(taskFeedCmd);
472
542
 
473
543
  // ../../apps/cli/src/commands/ui.ts
474
544
  import { Command as Command4 } from "commander";
545
+ import path3 from "path";
546
+ import open from "open";
547
+ import { render } from "ink";
548
+ import React2 from "react";
549
+
550
+ // ../../apps/cli/src/lib/server.ts
475
551
  import { spawn } from "child_process";
476
552
  import { existsSync as existsSync2 } from "fs";
477
553
  import net from "net";
478
554
  import path2 from "path";
479
555
  import { fileURLToPath as fileURLToPath2 } from "url";
480
- import open from "open";
481
- import { render } from "ink";
482
- import React2 from "react";
483
556
  var __filename2 = fileURLToPath2(import.meta.url);
484
557
  var __dirname2 = path2.dirname(__filename2);
485
558
  var DEFAULT_PORT = 4e3;
@@ -489,7 +562,7 @@ var MAX_GRPC_PORT = 50061;
489
562
  function findAvailablePort(preferred = DEFAULT_PORT, max = MAX_PORT) {
490
563
  return new Promise((resolve, reject) => {
491
564
  if (preferred > max) {
492
- reject(new Error(`No available ports between ${DEFAULT_PORT.toString()} and ${max.toString()}`));
565
+ reject(new Error(`No available ports between ${preferred.toString()} and ${max.toString()}`));
493
566
  return;
494
567
  }
495
568
  const tester = net.createServer();
@@ -511,11 +584,11 @@ function findAvailablePort(preferred = DEFAULT_PORT, max = MAX_PORT) {
511
584
  });
512
585
  }
513
586
  function startBundledServer(env) {
514
- const realServerPath = path2.resolve(__dirname2, "server/dist/index.js");
587
+ const realServerPath = path2.resolve(__dirname2, "../../server/dist/index.js");
515
588
  if (existsSync2(realServerPath)) {
516
589
  return spawn(process.execPath, [realServerPath], { stdio: "inherit", env });
517
590
  }
518
- const legacyServerPath = path2.resolve(__dirname2, "server.js");
591
+ const legacyServerPath = path2.resolve(__dirname2, "../../server.js");
519
592
  if (existsSync2(legacyServerPath)) {
520
593
  return spawn(process.execPath, [legacyServerPath], { stdio: "inherit", env });
521
594
  }
@@ -537,9 +610,29 @@ async function waitForApi(apiUrl, maxMs = 3e4) {
537
610
  }
538
611
  throw new Error("Server did not become ready in time");
539
612
  }
613
+ async function startHarnessServer(env, options = {}) {
614
+ const port = await findAvailablePort(options.preferredPort ?? DEFAULT_PORT, MAX_PORT);
615
+ const grpcPort = await findAvailablePort(options.preferredGrpcPort ?? DEFAULT_GRPC_PORT, MAX_GRPC_PORT);
616
+ const apiUrl = `http://localhost:${port.toString()}`;
617
+ const serverEnv = {
618
+ ...env,
619
+ PORT: port.toString(),
620
+ GRPC_PORT: grpcPort.toString(),
621
+ HARNESS_API_URL: apiUrl
622
+ };
623
+ const server = startBundledServer(serverEnv) ?? spawn("pnpm", ["--filter", "@omega/server", "dev"], {
624
+ stdio: "inherit",
625
+ shell: true,
626
+ env: serverEnv
627
+ });
628
+ await waitForApi(apiUrl);
629
+ return { process: server, apiUrl, port, grpcPort };
630
+ }
631
+
632
+ // ../../apps/cli/src/commands/ui.ts
540
633
  async function ensureProject(apiUrl) {
541
634
  const cwd = process.cwd();
542
- const name = path2.basename(cwd);
635
+ const name = path3.basename(cwd);
543
636
  try {
544
637
  const listRes = await fetch(`${apiUrl}/projects`);
545
638
  const projects = await listRes.json();
@@ -565,25 +658,16 @@ async function ensureProject(apiUrl) {
565
658
  var uiCmd = new Command4("ui").description("Open the harness web UI").option("--no-tui", "Do not open the terminal console alongside the web UI").action(async (options) => {
566
659
  const defaultApiUrl = process.env.HARNESS_API_URL ?? `http://localhost:${DEFAULT_PORT.toString()}`;
567
660
  let server;
661
+ let serverStarted = false;
568
662
  const alreadyRunning = await isApiReady(defaultApiUrl);
569
663
  const useTui = options.tui;
570
664
  let apiUrl = defaultApiUrl;
571
665
  if (!alreadyRunning) {
572
- const port = await findAvailablePort();
573
- const grpcPort = await findAvailablePort(DEFAULT_GRPC_PORT, MAX_GRPC_PORT);
574
- apiUrl = `http://localhost:${port.toString()}`;
666
+ const started = await startHarnessServer(process.env);
667
+ server = started.process;
668
+ apiUrl = started.apiUrl;
669
+ serverStarted = true;
575
670
  process.env.HARNESS_API_URL = apiUrl;
576
- const env = {
577
- ...process.env,
578
- PORT: port.toString(),
579
- GRPC_PORT: grpcPort.toString(),
580
- HARNESS_API_URL: apiUrl
581
- };
582
- server = startBundledServer(env) ?? spawn("pnpm", ["--filter", "@omega/server", "dev"], {
583
- stdio: "inherit",
584
- shell: true,
585
- env
586
- });
587
671
  server.on("exit", (code) => {
588
672
  process.exit(code ?? 0);
589
673
  });
@@ -607,7 +691,7 @@ var uiCmd = new Command4("ui").description("Open the harness web UI").option("--
607
691
  const { TuiApp: TuiApp2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
608
692
  const { waitUntilExit } = render(React2.createElement(TuiApp2));
609
693
  await waitUntilExit();
610
- if (server && !alreadyRunning) {
694
+ if (serverStarted && server) {
611
695
  server.kill();
612
696
  }
613
697
  }
@@ -628,7 +712,7 @@ var consoleCmd = new Command5("console").description("Open the harness TUI conso
628
712
 
629
713
  // ../../apps/cli/src/commands/skill.ts
630
714
  import { Command as Command6 } from "commander";
631
- import path3 from "path";
715
+ import path4 from "path";
632
716
  import { fileURLToPath as fileURLToPath3 } from "url";
633
717
 
634
718
  // ../skills/src/parse.ts
@@ -695,7 +779,8 @@ export class ${className} {
695
779
 
696
780
  async execute(args: Record<string, unknown>): Promise<string> {
697
781
  const prompt = ${instructionsLiteral} + '\\n\\nArgs:\\n' + JSON.stringify(args, null, 2);
698
- return this.provider.send(prompt, { system: ${descriptionLiteral} });
782
+ const response = await this.provider.send(prompt, { system: ${descriptionLiteral} });
783
+ return response.content;
699
784
  }
700
785
  }
701
786
  `;
@@ -730,22 +815,22 @@ async function registerSkill(sourcePath, outputDir) {
730
815
  // ../../apps/cli/src/commands/skill.ts
731
816
  function getOutputDir() {
732
817
  if (process.env.OMEGA_HARNESS_ROOT) {
733
- return path3.join(path3.resolve(process.env.OMEGA_HARNESS_ROOT), "packages/skills/src");
818
+ return path4.join(path4.resolve(process.env.OMEGA_HARNESS_ROOT), "packages/skills/src");
734
819
  }
735
820
  try {
736
821
  const __filename3 = fileURLToPath3(import.meta.url);
737
- const __dirname3 = path3.dirname(__filename3);
738
- return path3.join(path3.resolve(__dirname3, "../../../.."), "packages/skills/src");
822
+ const __dirname4 = path4.dirname(__filename3);
823
+ return path4.join(path4.resolve(__dirname4, "../../../.."), "packages/skills/src");
739
824
  } catch {
740
- return path3.join(process.cwd(), "harness-skills");
825
+ return path4.join(process.cwd(), "harness-skills");
741
826
  }
742
827
  }
743
828
  var skillCmd = new Command6("skill").description("Manage skill artifacts");
744
829
  skillCmd.command("generate").description("Generate a harness adapter from a SKILL.md file").argument("<source>", "path to SKILL.md").action(async (source) => {
745
830
  const outputDir = getOutputDir();
746
- const manifest = await registerSkill(path3.resolve(source), outputDir);
831
+ const manifest = await registerSkill(path4.resolve(source), outputDir);
747
832
  console.log(`Generated adapter for skill: ${manifest.name}`);
748
- console.log(`Output directory: ${path3.join(outputDir, "generated")}`);
833
+ console.log(`Output directory: ${path4.join(outputDir, "generated")}`);
749
834
  });
750
835
 
751
836
  // ../../apps/cli/src/commands/agent.ts
@@ -755,7 +840,7 @@ agentCmd.command("run").description("Run a single agent task").requiredOption("-
755
840
  async (opts) => {
756
841
  const tags = ["agent"];
757
842
  if (opts.autoPublish) tags.push("publish");
758
- const task = await apiFetch("/tasks", {
843
+ const task2 = await apiFetch("/tasks", {
759
844
  method: "POST",
760
845
  headers: { "Content-Type": "application/json" },
761
846
  body: JSON.stringify({
@@ -766,8 +851,8 @@ agentCmd.command("run").description("Run a single agent task").requiredOption("-
766
851
  tags
767
852
  })
768
853
  });
769
- console.log(JSON.stringify(task, null, 2));
770
- const result = await apiFetch(`/tasks/${task.id}/run`, { method: "POST" });
854
+ console.log(JSON.stringify(task2, null, 2));
855
+ const result = await apiFetch(`/tasks/${task2.id}/run`, { method: "POST" });
771
856
  console.log(JSON.stringify(result, null, 2));
772
857
  }
773
858
  );
@@ -779,7 +864,7 @@ agentCmd.command("loop").description("Continuously run agent tasks").requiredOpt
779
864
  const title = opts.prompt ?? "Improve the Omega harness: fix a TODO, add a test, refactor a function, or improve documentation";
780
865
  const tags = ["agent", "self-improve"];
781
866
  if (opts.autoPublish) tags.push("publish");
782
- const task = await apiFetch("/tasks", {
867
+ const task2 = await apiFetch("/tasks", {
783
868
  method: "POST",
784
869
  headers: { "Content-Type": "application/json" },
785
870
  body: JSON.stringify({
@@ -790,8 +875,8 @@ agentCmd.command("loop").description("Continuously run agent tasks").requiredOpt
790
875
  tags
791
876
  })
792
877
  });
793
- console.log(`Created task ${task.id}`);
794
- const result = await apiFetch(`/tasks/${task.id}/run`, { method: "POST" });
878
+ console.log(`Created task ${task2.id}`);
879
+ const result = await apiFetch(`/tasks/${task2.id}/run`, { method: "POST" });
795
880
  console.log(JSON.stringify(result, null, 2));
796
881
  return result;
797
882
  };
@@ -819,7 +904,9 @@ agentCmd.command("diffs").description("Show diffs for a task").argument("<id>",
819
904
 
820
905
  // ../../apps/cli/src/commands/bench.ts
821
906
  import { Command as Command8 } from "commander";
822
- import path9 from "node:path";
907
+ import fs8 from "node:fs/promises";
908
+ import path11 from "node:path";
909
+ import os from "node:os";
823
910
 
824
911
  // ../bench/dist/api-client.js
825
912
  var ApiError = class extends Error {
@@ -836,15 +923,15 @@ async function apiFetch2(url, init) {
836
923
  }
837
924
  return res.json();
838
925
  }
839
- async function ensureProject2(apiUrl, name, path10) {
926
+ async function ensureProject2(apiUrl, name, path12) {
840
927
  const res = await fetch(`${apiUrl}/projects`, {
841
928
  method: "POST",
842
929
  headers: { "Content-Type": "application/json" },
843
- body: JSON.stringify({ name, path: path10 })
930
+ body: JSON.stringify({ name, path: path12 })
844
931
  });
845
932
  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);
933
+ const projects = await fetch(`${apiUrl}/projects?path=${encodeURIComponent(path12)}`).then((r) => r.json());
934
+ const existing = projects.find((p) => p.path === path12);
848
935
  if (existing)
849
936
  return existing;
850
937
  }
@@ -874,9 +961,9 @@ async function getTask(apiUrl, taskId) {
874
961
  async function waitForTask(apiUrl, taskId, timeoutMs = 12e4) {
875
962
  const deadline = Date.now() + timeoutMs;
876
963
  while (Date.now() < deadline) {
877
- const task = await getTask(apiUrl, taskId);
878
- if (task.status === "done" || task.status === "failed")
879
- return task;
964
+ const task2 = await getTask(apiUrl, taskId);
965
+ if (task2.status === "done" || task2.status === "failed")
966
+ return task2;
880
967
  await new Promise((r) => setTimeout(r, 300));
881
968
  }
882
969
  return { status: "timeout", error: "Task did not finish in time" };
@@ -914,8 +1001,169 @@ function countSpans(node) {
914
1001
 
915
1002
  // ../bench/dist/runner.js
916
1003
  import fs from "node:fs/promises";
917
- import path4 from "node:path";
1004
+ import path5 from "node:path";
918
1005
  import { execSync } from "node:child_process";
1006
+
1007
+ // ../bench/dist/analyse.js
1008
+ function walkSpans(traceFlow, callback) {
1009
+ if (!traceFlow)
1010
+ return;
1011
+ function visit(span) {
1012
+ callback?.(span);
1013
+ for (const child of span.children) {
1014
+ visit(child);
1015
+ }
1016
+ }
1017
+ for (const span of traceFlow.spans) {
1018
+ visit(span);
1019
+ }
1020
+ }
1021
+ function findSpans(traceFlow, predicate) {
1022
+ const matches = [];
1023
+ walkSpans(traceFlow, (span) => {
1024
+ if (predicate(span))
1025
+ matches.push(span);
1026
+ });
1027
+ return matches;
1028
+ }
1029
+ function spanError(span) {
1030
+ if (span.status !== "error")
1031
+ return void 0;
1032
+ const err = span.attributes?.error;
1033
+ return typeof err === "string" ? err : void 0;
1034
+ }
1035
+ function toolSpans(traceFlow) {
1036
+ return findSpans(traceFlow, (s) => s.name.startsWith("agent.tool."));
1037
+ }
1038
+ function providerSpans(traceFlow) {
1039
+ return findSpans(traceFlow, (s) => s.name === "provider.send");
1040
+ }
1041
+ function planSpan(traceFlow) {
1042
+ return findSpans(traceFlow, (s) => s.name === "agent.plan").shift();
1043
+ }
1044
+ function validationSummary(result) {
1045
+ if (!result.agentRun?.validationSummary)
1046
+ return void 0;
1047
+ try {
1048
+ return JSON.parse(result.agentRun.validationSummary);
1049
+ } catch {
1050
+ return void 0;
1051
+ }
1052
+ }
1053
+ function classifyFailure(result, traceFlow) {
1054
+ const evidence = [];
1055
+ if (result.status === "timeout") {
1056
+ return {
1057
+ category: "timeout",
1058
+ rootCause: "Task did not finish within the timeout window.",
1059
+ evidence: [`durationMs: ${String(result.durationMs)}`]
1060
+ };
1061
+ }
1062
+ const validation = validationSummary(result);
1063
+ if (validation?.allPassed === false) {
1064
+ const failedSteps = [];
1065
+ for (const key of ["lint", "test", "build"]) {
1066
+ const step = validation[key];
1067
+ if (step && !step.success) {
1068
+ failedSteps.push(key);
1069
+ }
1070
+ }
1071
+ return {
1072
+ category: "validation_failure",
1073
+ rootCause: `Validation failed: ${failedSteps.length > 0 ? failedSteps.join(", ") : "unspecified step"}.`,
1074
+ evidence: failedSteps.map((s) => `${s} did not pass`)
1075
+ };
1076
+ }
1077
+ const tools = toolSpans(traceFlow);
1078
+ const failedTools = tools.filter((s) => s.status === "error");
1079
+ if (failedTools.length > 0) {
1080
+ const first = failedTools[0];
1081
+ const toolName = first.name.replace("agent.tool.", "");
1082
+ const err = spanError(first) ?? "unknown error";
1083
+ evidence.push(`${toolName} failed: ${err}`);
1084
+ if (toolName === "edit_file" || err.includes("old_string")) {
1085
+ return {
1086
+ category: "tool_misuse",
1087
+ rootCause: "edit_file failed because the old_string did not match. The agent may not have read the file first or copied the exact text.",
1088
+ evidence
1089
+ };
1090
+ }
1091
+ return {
1092
+ category: "tool_misuse",
1093
+ rootCause: `Tool ${toolName} was invoked but failed.`,
1094
+ evidence
1095
+ };
1096
+ }
1097
+ const providers = providerSpans(traceFlow);
1098
+ const failedProviders = providers.filter((s) => s.status === "error");
1099
+ if (failedProviders.length > 0) {
1100
+ const err = spanError(failedProviders[0]) ?? "provider error";
1101
+ if (err.includes("JSON") || err.includes("parse") || err.includes("Unexpected token")) {
1102
+ return {
1103
+ category: "parse_error",
1104
+ rootCause: "Provider response could not be parsed as the expected format.",
1105
+ evidence: [err]
1106
+ };
1107
+ }
1108
+ }
1109
+ const plan = planSpan(traceFlow);
1110
+ if (plan) {
1111
+ const planSteps = plan.attributes?.planSteps;
1112
+ const planStepsCount = typeof planSteps === "number" ? planSteps : 0;
1113
+ if (planStepsCount === 0) {
1114
+ return {
1115
+ category: "plan_error",
1116
+ rootCause: "The planner produced an empty plan with no actionable steps.",
1117
+ evidence: ["planSteps: 0"]
1118
+ };
1119
+ }
1120
+ const providerTurns = providers.length;
1121
+ if (providerTurns > 10 && !result.evaluation.passed) {
1122
+ return {
1123
+ category: "plan_error",
1124
+ rootCause: "The agent took many turns without finishing; the plan may be stuck or too vague.",
1125
+ evidence: [`provider turns: ${String(providerTurns)}`, `plan steps: ${String(planStepsCount)}`]
1126
+ };
1127
+ }
1128
+ }
1129
+ const message = result.evaluation.message ?? result.agentRun?.resultStatus ?? "unknown";
1130
+ return {
1131
+ category: "unknown",
1132
+ rootCause: `Failure cause could not be classified: ${message}.`,
1133
+ evidence: [message]
1134
+ };
1135
+ }
1136
+ function pickFocusResult(results, traceFlows) {
1137
+ const failed = results.filter((r) => !r.evaluation.passed);
1138
+ if (failed.length === 0)
1139
+ return void 0;
1140
+ const seenCategories = /* @__PURE__ */ new Set();
1141
+ for (const result of failed) {
1142
+ const analysis = result.failureAnalysis ?? classifyFailure(result, traceFlows.get(result.harnessTaskId));
1143
+ if (!seenCategories.has(analysis.category)) {
1144
+ seenCategories.add(analysis.category);
1145
+ return result;
1146
+ }
1147
+ }
1148
+ return failed[0];
1149
+ }
1150
+ function summariseFailures(results, traceFlows) {
1151
+ const counts = /* @__PURE__ */ new Map();
1152
+ for (const result of results) {
1153
+ if (result.evaluation.passed)
1154
+ continue;
1155
+ const analysis = result.failureAnalysis ?? classifyFailure(result, traceFlows.get(result.harnessTaskId));
1156
+ const entry = counts.get(analysis.category) ?? { count: 0, examples: [] };
1157
+ entry.count++;
1158
+ if (entry.examples.length < 3) {
1159
+ entry.examples.push(`${result.task.name}: ${analysis.rootCause}`);
1160
+ }
1161
+ counts.set(analysis.category, entry);
1162
+ }
1163
+ return Array.from(counts.entries()).map(([category, data]) => ({ category, ...data })).sort((a, b) => b.count - a.count);
1164
+ }
1165
+
1166
+ // ../bench/dist/runner.js
919
1167
  function countAllSpans(traceFlow) {
920
1168
  if (!traceFlow)
921
1169
  return 0;
@@ -933,6 +1181,13 @@ function ensureGitRepo(repoPath) {
933
1181
  execSync("git add .", { cwd: repoPath, stdio: "ignore" });
934
1182
  execSync('git commit -m "bench init"', { cwd: repoPath, stdio: "ignore" });
935
1183
  }
1184
+ function sumUsage(a, b) {
1185
+ return {
1186
+ promptTokens: (a.promptTokens ?? 0) + (b?.promptTokens ?? 0),
1187
+ completionTokens: (a.completionTokens ?? 0) + (b?.completionTokens ?? 0),
1188
+ totalTokens: (a.totalTokens ?? 0) + (b?.totalTokens ?? 0)
1189
+ };
1190
+ }
936
1191
  async function runBenchmark(tasks, options) {
937
1192
  const { apiUrl, suiteName, timeoutMs = 12e4, projectPrefix = "bench" } = options;
938
1193
  const report = {
@@ -943,11 +1198,12 @@ async function runBenchmark(tasks, options) {
943
1198
  failed: 0,
944
1199
  timeouts: 0,
945
1200
  totalDurationMs: 0,
1201
+ totalUsage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
946
1202
  results: []
947
1203
  };
948
- const baseDir = path4.join("/tmp", `omega-bench-${String(Date.now())}`);
1204
+ const baseDir = path5.join("/tmp", `omega-bench-${String(Date.now())}`);
949
1205
  await fs.mkdir(baseDir, { recursive: true });
950
- for (const task of tasks) {
1206
+ for (const task2 of tasks) {
951
1207
  const start = Date.now();
952
1208
  let harnessTaskId = "";
953
1209
  let status = "failed";
@@ -958,17 +1214,17 @@ async function runBenchmark(tasks, options) {
958
1214
  let projectId = "";
959
1215
  let projectPath = "";
960
1216
  try {
961
- projectPath = path4.join(baseDir, task.id);
1217
+ projectPath = path5.join(baseDir, task2.id);
962
1218
  await fs.mkdir(projectPath, { recursive: true });
963
- if (task.setup) {
964
- await task.setup(projectPath);
1219
+ if (task2.setup) {
1220
+ await task2.setup(projectPath);
965
1221
  }
966
1222
  ensureGitRepo(projectPath);
967
- const project = await ensureProject2(apiUrl, `${projectPrefix}-${task.id}`, projectPath);
1223
+ const project = await ensureProject2(apiUrl, `${projectPrefix}-${task2.id}`, projectPath);
968
1224
  projectId = project.id;
969
- const harnessTask = await createTask(apiUrl, project.id, task.title, {
970
- description: task.description,
971
- complexity: task.complexity ?? "simple",
1225
+ const harnessTask = await createTask(apiUrl, project.id, task2.title, {
1226
+ description: task2.description,
1227
+ complexity: task2.complexity ?? "simple",
972
1228
  tags: ["benchmark", "agent"]
973
1229
  });
974
1230
  harnessTaskId = harnessTask.id;
@@ -980,7 +1236,7 @@ async function runBenchmark(tasks, options) {
980
1236
  getDiffs(apiUrl, harnessTask.id),
981
1237
  getTraceFlow(apiUrl, harnessTask.id)
982
1238
  ]);
983
- evaluation = await task.evaluate({
1239
+ evaluation = await task2.evaluate({
984
1240
  apiUrl,
985
1241
  taskId: harnessTask.id,
986
1242
  projectPath,
@@ -996,14 +1252,21 @@ async function runBenchmark(tasks, options) {
996
1252
  };
997
1253
  }
998
1254
  const durationMs = Date.now() - start;
1255
+ const usage = agentRun ? {
1256
+ promptTokens: agentRun.promptTokens,
1257
+ completionTokens: agentRun.completionTokens,
1258
+ totalTokens: agentRun.totalTokens
1259
+ } : void 0;
999
1260
  const result = {
1000
- task,
1261
+ task: task2,
1001
1262
  harnessTaskId,
1002
1263
  durationMs,
1003
1264
  status,
1004
1265
  evaluation,
1005
1266
  agentRun,
1006
- spanCount: countAllSpans(traceFlow)
1267
+ spanCount: countAllSpans(traceFlow),
1268
+ usage,
1269
+ failureAnalysis: evaluation.passed ? void 0 : classifyFailure({ task: task2, harnessTaskId, durationMs, status, evaluation, agentRun, spanCount: countAllSpans(traceFlow), usage }, traceFlow)
1007
1270
  };
1008
1271
  if (status === "timeout")
1009
1272
  report.timeouts++;
@@ -1012,6 +1275,7 @@ async function runBenchmark(tasks, options) {
1012
1275
  else
1013
1276
  report.failed++;
1014
1277
  report.totalDurationMs += durationMs;
1278
+ report.totalUsage = sumUsage(report.totalUsage, usage);
1015
1279
  report.results.push(result);
1016
1280
  options.onProgress?.(result);
1017
1281
  }
@@ -1020,7 +1284,7 @@ async function runBenchmark(tasks, options) {
1020
1284
 
1021
1285
  // ../bench/dist/report.js
1022
1286
  import fs2 from "node:fs/promises";
1023
- import path5 from "node:path";
1287
+ import path6 from "node:path";
1024
1288
  function nowIso() {
1025
1289
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1026
1290
  }
@@ -1033,16 +1297,21 @@ function resultLine(r, idx) {
1033
1297
  const symbol = r.evaluation.passed ? "\u2713" : "\u2717";
1034
1298
  const status = r.status === "timeout" ? "timeout" : r.status;
1035
1299
  const score = r.evaluation.score !== void 0 ? ` (score ${String(r.evaluation.score)})` : "";
1300
+ const tokens = r.usage?.totalTokens !== void 0 ? ` (${String(r.usage.totalTokens)} tokens)` : "";
1301
+ const category = r.failureAnalysis ? ` [${r.failureAnalysis.category}]` : "";
1036
1302
  const msg = r.evaluation.message ? ` \u2014 ${r.evaluation.message}` : "";
1037
- return `${String(idx + 1)}. ${symbol} ${r.task.name} [${status}] ${formatDuration(r.durationMs)}${score}${msg}`;
1303
+ return `${String(idx + 1)}. ${symbol} ${r.task.name} [${status}]${category} ${formatDuration(r.durationMs)}${tokens}${score}${msg}`;
1304
+ }
1305
+ function usageLine(usage) {
1306
+ return `Tokens: ${String(usage.totalTokens ?? 0)} total (${String(usage.promptTokens ?? 0)} prompt, ${String(usage.completionTokens ?? 0)} completion)`;
1038
1307
  }
1039
1308
  async function writeReport(report, outputDir = ".omega/reports") {
1040
1309
  await fs2.mkdir(outputDir, { recursive: true });
1041
1310
  const ts = nowIso();
1042
- const jsonFile = path5.join(outputDir, `benchmark-${ts}.json`);
1043
- const mdFile = path5.join(outputDir, `benchmark-${ts}.md`);
1044
- await fs2.writeFile(jsonFile, JSON.stringify(report, null, 2), "utf-8");
1045
- const passRate = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
1311
+ const jsonFile2 = path6.join(outputDir, `benchmark-${ts}.json`);
1312
+ const mdFile = path6.join(outputDir, `benchmark-${ts}.md`);
1313
+ await fs2.writeFile(jsonFile2, JSON.stringify(report, null, 2), "utf-8");
1314
+ const passRate2 = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
1046
1315
  const md = [
1047
1316
  "# Omega Benchmark Report",
1048
1317
  "",
@@ -1052,8 +1321,9 @@ async function writeReport(report, outputDir = ".omega/reports") {
1052
1321
  `- Passed: ${String(report.passed)}`,
1053
1322
  `- Failed: ${String(report.failed)}`,
1054
1323
  `- Timeouts: ${String(report.timeouts)}`,
1055
- `- Pass rate: ${String(passRate)}%`,
1324
+ `- Pass rate: ${String(passRate2)}%`,
1056
1325
  `- Total duration: ${formatDuration(report.totalDurationMs)}`,
1326
+ `- ${usageLine(report.totalUsage)}`,
1057
1327
  "",
1058
1328
  "## Results",
1059
1329
  "",
@@ -1067,31 +1337,69 @@ async function writeReport(report, outputDir = ".omega/reports") {
1067
1337
  ""
1068
1338
  ].join("\n");
1069
1339
  await fs2.writeFile(mdFile, md, "utf-8");
1070
- return jsonFile;
1340
+ return jsonFile2;
1071
1341
  }
1072
1342
  function printSummary(report) {
1073
- const passRate = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
1343
+ const passRate2 = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
1074
1344
  console.log(`
1075
1345
  Benchmark: ${report.suite}`);
1076
1346
  console.log(`Total: ${String(report.total)} | Passed: ${String(report.passed)} | Failed: ${String(report.failed)} | Timeouts: ${String(report.timeouts)}`);
1077
- console.log(`Pass rate: ${String(passRate)}%`);
1347
+ console.log(`Pass rate: ${String(passRate2)}%`);
1078
1348
  console.log(`Duration: ${formatDuration(report.totalDurationMs)}`);
1349
+ console.log(usageLine(report.totalUsage));
1079
1350
  for (const r of report.results) {
1080
1351
  console.log(resultLine(r, report.results.indexOf(r)));
1081
1352
  }
1082
1353
  }
1083
1354
 
1084
1355
  // ../bench/dist/suites/synthetic.js
1356
+ import fs4 from "node:fs/promises";
1357
+
1358
+ // ../bench/dist/suites/builder.js
1085
1359
  import fs3 from "node:fs/promises";
1086
- import path6 from "node:path";
1087
- async function writeJson(projectPath, file, data) {
1088
- await fs3.writeFile(path6.join(projectPath, file), JSON.stringify(data, null, 2), "utf-8");
1360
+ import path7 from "node:path";
1361
+ function task(spec) {
1362
+ return {
1363
+ id: spec.id,
1364
+ name: spec.name,
1365
+ title: spec.title,
1366
+ description: spec.description,
1367
+ complexity: spec.complexity ?? "simple",
1368
+ setup: spec.setup,
1369
+ evaluate: spec.evaluate ?? expectValidationPassed()
1370
+ };
1371
+ }
1372
+ function basePackage(extraScripts = {}) {
1373
+ return {
1374
+ name: "synthetic-bench-project",
1375
+ version: "1.0.0",
1376
+ type: "module",
1377
+ scripts: {
1378
+ lint: "node lint.js",
1379
+ test: "node test.js",
1380
+ build: "node build.js",
1381
+ ...extraScripts
1382
+ }
1383
+ };
1384
+ }
1385
+ async function jsonFile(projectPath, file, data) {
1386
+ await fs3.mkdir(path7.dirname(path7.join(projectPath, file)), { recursive: true });
1387
+ await fs3.writeFile(path7.join(projectPath, file), JSON.stringify(data, null, 2), "utf-8");
1388
+ }
1389
+ async function codeFile(projectPath, file, content) {
1390
+ await fs3.mkdir(path7.dirname(path7.join(projectPath, file)), { recursive: true });
1391
+ await fs3.writeFile(path7.join(projectPath, file), content, "utf-8");
1089
1392
  }
1090
- async function writeFile2(projectPath, file, content) {
1091
- await fs3.mkdir(path6.dirname(path6.join(projectPath, file)), { recursive: true });
1092
- await fs3.writeFile(path6.join(projectPath, file), content, "utf-8");
1393
+ async function nodeProject(projectPath, extraScripts = {}) {
1394
+ await jsonFile(projectPath, "package.json", basePackage(extraScripts));
1395
+ await codeFile(projectPath, "lint.js", `console.log('lint ok');
1396
+ `);
1397
+ await codeFile(projectPath, "test.js", `console.log('test ok');
1398
+ `);
1399
+ await codeFile(projectPath, "build.js", `console.log('build ok');
1400
+ `);
1093
1401
  }
1094
- function parseValidation(ctx) {
1402
+ function readValidation(ctx) {
1095
1403
  if (!ctx.agentRun?.validationSummary)
1096
1404
  return { allPassed: false };
1097
1405
  try {
@@ -1101,142 +1409,598 @@ function parseValidation(ctx) {
1101
1409
  return { allPassed: false };
1102
1410
  }
1103
1411
  }
1104
- function validate(ctx) {
1105
- const v = parseValidation(ctx);
1106
- return {
1107
- passed: v.allPassed,
1108
- message: v.allPassed ? "Validation passed" : "Validation failed"
1412
+ function expectValidationPassed() {
1413
+ return (ctx) => {
1414
+ const v = readValidation(ctx);
1415
+ return {
1416
+ passed: v.allPassed,
1417
+ message: v.allPassed ? "Validation passed" : "Validation failed"
1418
+ };
1109
1419
  };
1110
1420
  }
1111
- function basePackage() {
1112
- return {
1113
- name: "synthetic-bench-project",
1114
- version: "1.0.0",
1115
- type: "module",
1116
- scripts: {
1117
- lint: "node lint.js",
1118
- test: "node test.js",
1119
- build: "node build.js"
1421
+ async function readFile2(projectPath, file) {
1422
+ return fs3.readFile(path7.join(projectPath, file), "utf-8");
1423
+ }
1424
+ function expectFileContains(file, needle) {
1425
+ return async (ctx) => {
1426
+ const content = await readFile2(ctx.projectPath, file).catch(() => "");
1427
+ const found = typeof needle === "string" ? content.includes(needle) : needle.test(content);
1428
+ return {
1429
+ passed: found,
1430
+ message: found ? `${file} contains required content` : `${file} missing required content`
1431
+ };
1432
+ };
1433
+ }
1434
+ function combined(...evaluators) {
1435
+ return async (ctx) => {
1436
+ for (const evaluator of evaluators) {
1437
+ const result = await evaluator(ctx);
1438
+ if (!result.passed)
1439
+ return result;
1120
1440
  }
1441
+ return { passed: true, message: "All checks passed" };
1121
1442
  };
1122
1443
  }
1123
- var TASKS = [
1124
- {
1125
- id: "greet-function",
1126
- name: "Add a greet function",
1127
- title: "Add greet.js with a greet function",
1128
- description: "Create a `greet.js` file that exports a function `greet(name)` returning `Hello, <name>!`. The existing test expects this function.",
1129
- complexity: "simple",
1130
- setup: async (projectPath) => {
1131
- await writeJson(projectPath, "package.json", basePackage());
1132
- await writeFile2(projectPath, "test.js", `import { greet } from './greet.js';
1133
- import assert from 'node:assert';
1134
- assert.strictEqual(greet('World'), 'Hello, World!');
1135
- console.log('greet test passed');
1444
+
1445
+ // ../bench/dist/suites/synthetic.js
1446
+ var lintAlwaysPass = `console.log('lint ok');
1447
+ `;
1448
+ var buildAlwaysPass = `console.log('build ok');
1449
+ `;
1450
+ function jsTest(name, body) {
1451
+ return `import assert from 'node:assert';
1452
+ ${body}
1453
+ console.log('${name} test passed');
1454
+ `;
1455
+ }
1456
+ function syntheticSuite() {
1457
+ return [
1458
+ // Simple edits
1459
+ task({
1460
+ id: "greet-function",
1461
+ name: "Add a greet function",
1462
+ title: "Add greet.js with a greet function",
1463
+ description: "Create a `greet.js` file that exports a function `greet(name)` returning `Hello, <name>!`.",
1464
+ complexity: "simple",
1465
+ setup: async (projectPath) => {
1466
+ await nodeProject(projectPath);
1467
+ await codeFile(projectPath, "test.js", jsTest("greet", `import { greet } from './greet.js';
1468
+ assert.strictEqual(greet('World'), 'Hello, World!');`));
1469
+ }
1470
+ }),
1471
+ task({
1472
+ id: "update-config",
1473
+ name: "Update a config value",
1474
+ title: "Update timeout value in config.json",
1475
+ description: "Change `config.json` so that `timeout` is `5000` instead of `1000`.",
1476
+ complexity: "simple",
1477
+ setup: async (projectPath) => {
1478
+ await nodeProject(projectPath);
1479
+ await jsonFile(projectPath, "config.json", { timeout: 1e3, retries: 3 });
1480
+ await codeFile(projectPath, "test.js", `import { readFile } from 'node:fs/promises';
1481
+ const cfg = JSON.parse(await readFile('./config.json', 'utf-8'));
1482
+ if (cfg.timeout !== 5000) { console.error('timeout is ' + cfg.timeout); process.exit(1); }
1483
+ console.log('config test passed');
1136
1484
  `);
1137
- await writeFile2(projectPath, "lint.js", `console.log('lint ok');
1485
+ }
1486
+ }),
1487
+ task({
1488
+ id: "add-readme",
1489
+ name: "Add a README",
1490
+ title: "Create README.md with a usage heading",
1491
+ description: "Create a README.md file that contains a `# Usage` heading.",
1492
+ complexity: "simple",
1493
+ setup: async (projectPath) => {
1494
+ await nodeProject(projectPath);
1495
+ await codeFile(projectPath, "test.js", `import { readFile } from 'node:fs/promises';
1496
+ const readme = await readFile('./README.md', 'utf-8').catch(() => '');
1497
+ if (!readme.includes('# Usage')) { console.error('README missing Usage heading'); process.exit(1); }
1498
+ console.log('readme test passed');
1138
1499
  `);
1139
- await writeFile2(projectPath, "build.js", `console.log('build ok');
1500
+ },
1501
+ evaluate: expectFileContains("README.md", "# Usage")
1502
+ }),
1503
+ task({
1504
+ id: "add-npm-script",
1505
+ name: "Add an npm script",
1506
+ title: "Add a `hello` script to package.json",
1507
+ description: 'Add a `hello` script to package.json that prints "hello world".',
1508
+ complexity: "simple",
1509
+ setup: async (projectPath) => {
1510
+ await nodeProject(projectPath);
1511
+ await codeFile(projectPath, "test.js", `import pkg from './package.json' assert { type: 'json' };
1512
+ if (pkg.scripts?.hello !== 'echo hello world') { console.error('missing hello script'); process.exit(1); }
1513
+ console.log('script test passed');
1140
1514
  `);
1141
- },
1142
- evaluate: (ctx) => Promise.resolve(validate(ctx))
1143
- },
1144
- {
1145
- id: "fix-lint-warning",
1146
- name: "Fix a lint warning",
1147
- title: "Remove unused variable flagged by linter",
1148
- description: "The custom linter flags any use of `var`. Update `src/index.js` so the lint script passes while keeping the greeting behavior.",
1149
- complexity: "simple",
1150
- setup: async (projectPath) => {
1151
- await writeJson(projectPath, "package.json", basePackage());
1152
- await writeFile2(projectPath, "src/index.js", `var greeting = 'Hello';
1153
- export function hi(name) { return greeting + ', ' + name + '!'; }
1515
+ }
1516
+ }),
1517
+ task({
1518
+ id: "fix-typo",
1519
+ name: "Fix a typo",
1520
+ title: 'Fix spelling of "recieve" to "receive"',
1521
+ description: 'The file src/message.js contains "recieve". Change it to "receive".',
1522
+ complexity: "simple",
1523
+ setup: async (projectPath) => {
1524
+ await nodeProject(projectPath);
1525
+ await codeFile(projectPath, "src/message.js", `export const message = 'Please recieve this letter';
1154
1526
  `);
1155
- await writeFile2(projectPath, "test.js", `import { hi } from './src/index.js';
1156
- import assert from 'node:assert';
1157
- assert.strictEqual(hi('Omega'), 'Hello, Omega!');
1158
- console.log('hi test passed');
1527
+ await codeFile(projectPath, "test.js", `import { readFile } from 'node:fs/promises';
1528
+ const code = await readFile('./src/message.js', 'utf-8');
1529
+ if (code.includes('recieve')) { console.error('typo remains'); process.exit(1); }
1530
+ if (!code.includes('receive')) { console.error('correct spelling missing'); process.exit(1); }
1531
+ console.log('typo test passed');
1159
1532
  `);
1160
- await writeFile2(projectPath, "lint.js", `import fs from 'node:fs/promises';
1533
+ }
1534
+ }),
1535
+ task({
1536
+ id: "add-gitignore",
1537
+ name: "Add a .gitignore",
1538
+ title: "Ignore node_modules and .env",
1539
+ description: "Create a .gitignore file that ignores node_modules and .env.",
1540
+ complexity: "simple",
1541
+ setup: async (projectPath) => {
1542
+ await nodeProject(projectPath);
1543
+ await codeFile(projectPath, "test.js", `import { readFile } from 'node:fs/promises';
1544
+ const gitignore = await readFile('./.gitignore', 'utf-8').catch(() => '');
1545
+ if (!gitignore.includes('node_modules') || !gitignore.includes('.env')) { console.error('.gitignore incomplete'); process.exit(1); }
1546
+ console.log('gitignore test passed');
1547
+ `);
1548
+ }
1549
+ }),
1550
+ // Refactoring
1551
+ task({
1552
+ id: "extract-constant",
1553
+ name: "Extract a magic constant",
1554
+ title: "Extract MAX_RETRIES constant",
1555
+ description: "Refactor `src/retry.js` so the magic number `3` is replaced by a top-level constant named `MAX_RETRIES`.",
1556
+ complexity: "medium",
1557
+ setup: async (projectPath) => {
1558
+ await nodeProject(projectPath);
1559
+ await codeFile(projectPath, "src/retry.js", `export function attempts() { return 3; }
1560
+ `);
1561
+ await codeFile(projectPath, "test.js", jsTest("constant", `import fs from 'node:fs/promises';
1562
+ import { attempts } from './src/retry.js';
1563
+ const code = await fs.readFile('./src/retry.js', 'utf-8');
1564
+ if (!code.includes('const MAX_RETRIES')) { console.error('MAX_RETRIES constant not found'); process.exit(1); }
1565
+ if (attempts() !== 3) { console.error('attempts() changed'); process.exit(1); }`));
1566
+ }
1567
+ }),
1568
+ task({
1569
+ id: "rename-variable",
1570
+ name: "Rename a variable",
1571
+ title: "Rename `usr` to `user` in src/index.js",
1572
+ description: "Rename all occurrences of the variable `usr` to `user` in src/index.js while preserving behavior.",
1573
+ complexity: "simple",
1574
+ setup: async (projectPath) => {
1575
+ await nodeProject(projectPath);
1576
+ await codeFile(projectPath, "src/index.js", `export function welcome(usr) { return 'Welcome, ' + usr; }
1577
+ `);
1578
+ await codeFile(projectPath, "test.js", jsTest("rename", `import { readFile } from 'node:fs/promises';
1579
+ import { welcome } from './src/index.js';
1580
+ const code = await readFile('./src/index.js', 'utf-8');
1581
+ if (code.includes('usr')) { console.error('old name remains'); process.exit(1); }
1582
+ if (!code.includes('user')) { console.error('new name missing'); process.exit(1); }
1583
+ assert.strictEqual(welcome('Ada'), 'Welcome, Ada');`));
1584
+ }
1585
+ }),
1586
+ task({
1587
+ id: "remove-dead-code",
1588
+ name: "Remove dead code",
1589
+ title: "Remove unused helper function",
1590
+ description: "Remove the unused `unusedHelper` function from src/utils.js.",
1591
+ complexity: "simple",
1592
+ setup: async (projectPath) => {
1593
+ await nodeProject(projectPath);
1594
+ await codeFile(projectPath, "src/utils.js", `export function usedHelper() { return 1; }
1595
+ function unusedHelper() { return 2; }
1596
+ `);
1597
+ await codeFile(projectPath, "test.js", jsTest("dead-code", `import { readFile } from 'node:fs/promises';
1598
+ import { usedHelper } from './src/utils.js';
1599
+ const code = await readFile('./src/utils.js', 'utf-8');
1600
+ if (code.includes('unusedHelper')) { console.error('dead code remains'); process.exit(1); }
1601
+ assert.strictEqual(usedHelper(), 1);`));
1602
+ }
1603
+ }),
1604
+ task({
1605
+ id: "split-function",
1606
+ name: "Split a function",
1607
+ title: "Split validateUser into smaller functions",
1608
+ description: "Refactor src/validate.js so there is a separate `isEmailValid` function used by `validateUser`.",
1609
+ complexity: "medium",
1610
+ setup: async (projectPath) => {
1611
+ await nodeProject(projectPath);
1612
+ await codeFile(projectPath, "src/validate.js", `export function validateUser(email) { return email.includes('@'); }
1613
+ `);
1614
+ await codeFile(projectPath, "test.js", jsTest("split", `import { readFile } from 'node:fs/promises';
1615
+ import { validateUser } from './src/validate.js';
1616
+ const code = await readFile('./src/validate.js', 'utf-8');
1617
+ if (!code.includes('function isEmailValid')) { console.error('isEmailValid not found'); process.exit(1); }
1618
+ assert.strictEqual(validateUser('a@b.com'), true);
1619
+ assert.strictEqual(validateUser('bad'), false);`));
1620
+ }
1621
+ }),
1622
+ task({
1623
+ id: "convert-var-to-const",
1624
+ name: "Convert var to const",
1625
+ title: "Replace var with const/let",
1626
+ description: "Update src/index.js to use const and let instead of var.",
1627
+ complexity: "simple",
1628
+ setup: async (projectPath) => {
1629
+ await nodeProject(projectPath, {
1630
+ lint: "node lint.js"
1631
+ });
1632
+ await codeFile(projectPath, "src/index.js", `var x = 1;
1633
+ var y = 2;
1634
+ export function sum() { return x + y; }
1635
+ `);
1636
+ await codeFile(projectPath, "lint.js", `import fs from 'node:fs/promises';
1161
1637
  const code = await fs.readFile('./src/index.js', 'utf-8');
1162
- if (code.includes('var ')) { console.error('lint failed: var keyword found'); process.exit(1); }
1638
+ if (code.includes('var ')) { console.error('var keyword found'); process.exit(1); }
1163
1639
  console.log('lint ok');
1164
1640
  `);
1165
- await writeFile2(projectPath, "build.js", `console.log('build ok');
1641
+ await codeFile(projectPath, "test.js", jsTest("var", `import { sum } from './src/index.js';
1642
+ assert.strictEqual(sum(), 3);`));
1643
+ }
1644
+ }),
1645
+ // Bug fixes
1646
+ task({
1647
+ id: "fix-null-guard",
1648
+ name: "Add null guard",
1649
+ title: "Guard against null input in getLength",
1650
+ description: "Fix src/strings.js so getLength returns 0 for null/undefined input instead of throwing.",
1651
+ complexity: "simple",
1652
+ setup: async (projectPath) => {
1653
+ await nodeProject(projectPath);
1654
+ await codeFile(projectPath, "src/strings.js", `export function getLength(value) { return value.length; }
1166
1655
  `);
1167
- },
1168
- evaluate: (ctx) => Promise.resolve(validate(ctx))
1169
- },
1170
- {
1171
- id: "update-config",
1172
- name: "Update a config value",
1173
- title: "Update timeout value in config.json",
1174
- description: "Change `config.json` so that `timeout` is `5000` instead of `1000`. The test will read the file and assert the value.",
1175
- complexity: "simple",
1176
- setup: async (projectPath) => {
1177
- await writeJson(projectPath, "package.json", basePackage());
1178
- await writeJson(projectPath, "config.json", { timeout: 1e3, retries: 3 });
1179
- await writeFile2(projectPath, "test.js", `import { readFile } from 'node:fs/promises';
1180
- const cfg = JSON.parse(await readFile('./config.json', 'utf-8'));
1181
- if (cfg.timeout !== 5000) { console.error('timeout is ' + cfg.timeout); process.exit(1); }
1182
- console.log('config test passed');
1656
+ await codeFile(projectPath, "test.js", jsTest("null-guard", `import { getLength } from './src/strings.js';
1657
+ assert.strictEqual(getLength(null), 0);
1658
+ assert.strictEqual(getLength(undefined), 0);
1659
+ assert.strictEqual(getLength('abc'), 3);`));
1660
+ }
1661
+ }),
1662
+ task({
1663
+ id: "fix-off-by-one",
1664
+ name: "Fix off-by-one",
1665
+ title: "Fix range to include end value",
1666
+ description: "Fix src/range.js so range(1, 3) returns [1, 2, 3].",
1667
+ complexity: "simple",
1668
+ setup: async (projectPath) => {
1669
+ await nodeProject(projectPath);
1670
+ await codeFile(projectPath, "src/range.js", `export function range(start, end) { const out = []; for (let i = start; i < end; i++) out.push(i); return out; }
1183
1671
  `);
1184
- await writeFile2(projectPath, "lint.js", `console.log('lint ok');
1672
+ await codeFile(projectPath, "test.js", jsTest("range", `import { range } from './src/range.js';
1673
+ assert.deepStrictEqual(range(1, 3), [1, 2, 3]);`));
1674
+ }
1675
+ }),
1676
+ task({
1677
+ id: "fix-async-await",
1678
+ name: "Fix async function",
1679
+ title: "Await the fetch call in loadData",
1680
+ description: "Fix src/api.js so loadData awaits the fetch response before returning JSON.",
1681
+ complexity: "medium",
1682
+ setup: async (projectPath) => {
1683
+ await nodeProject(projectPath);
1684
+ await codeFile(projectPath, "src/api.js", `export async function loadData() { return fetch('data.json').then((r) => r.json()); }
1185
1685
  `);
1186
- await writeFile2(projectPath, "build.js", `console.log('build ok');
1686
+ await codeFile(projectPath, "test.js", jsTest("async", `import { readFile } from 'node:fs/promises';
1687
+ import { loadData } from './src/api.js';
1688
+ const code = await readFile('./src/api.js', 'utf-8');
1689
+ if (!code.includes('await')) { console.error('missing await'); process.exit(1); }
1690
+ const result = await loadData();
1691
+ assert.strictEqual(result.ok, true);`));
1692
+ await jsonFile(projectPath, "data.json", { ok: true });
1693
+ },
1694
+ evaluate: combined(expectFileContains("src/api.js", "await"), expectValidationPassed())
1695
+ }),
1696
+ task({
1697
+ id: "fix-export",
1698
+ name: "Fix missing export",
1699
+ title: "Export the helper function",
1700
+ description: "The function helper in src/lib.js is not exported. Export it so the test can import it.",
1701
+ complexity: "simple",
1702
+ setup: async (projectPath) => {
1703
+ await nodeProject(projectPath);
1704
+ await codeFile(projectPath, "src/lib.js", `function helper() { return 42; }
1187
1705
  `);
1188
- },
1189
- evaluate: (ctx) => Promise.resolve(validate(ctx))
1190
- },
1191
- {
1192
- id: "extract-constant",
1193
- name: "Extract a magic constant",
1194
- title: "Extract MAX_RETRIES constant",
1195
- description: "Refactor `src/retry.js` so the magic number `3` is replaced by a top-level constant named `MAX_RETRIES`. The test checks for the constant.",
1196
- complexity: "medium",
1197
- setup: async (projectPath) => {
1198
- await writeJson(projectPath, "package.json", basePackage());
1199
- await writeFile2(projectPath, "src/retry.js", `export function attempts() { return 3; }
1706
+ await codeFile(projectPath, "test.js", jsTest("export", `import { helper } from './src/lib.js';
1707
+ assert.strictEqual(helper(), 42);`));
1708
+ }
1709
+ }),
1710
+ task({
1711
+ id: "fix-import-path",
1712
+ name: "Fix import path",
1713
+ title: "Correct the import path in src/main.js",
1714
+ description: "Fix the import path in src/main.js so it points to ./helpers.js.",
1715
+ complexity: "simple",
1716
+ setup: async (projectPath) => {
1717
+ await nodeProject(projectPath);
1718
+ await codeFile(projectPath, "src/helpers.js", `export function greet() { return 'hi'; }
1200
1719
  `);
1201
- await writeFile2(projectPath, "test.js", `import fs from 'node:fs/promises';
1202
- import { attempts } from './src/retry.js';
1203
- const code = await fs.readFile('./src/retry.js', 'utf-8');
1204
- if (!code.includes('const MAX_RETRIES')) { console.error('MAX_RETRIES constant not found'); process.exit(1); }
1205
- if (attempts() !== 3) { console.error('attempts() changed'); process.exit(1); }
1206
- console.log('constant test passed');
1720
+ await codeFile(projectPath, "src/main.js", `import { greet } from './helper.js';
1721
+ export { greet };
1207
1722
  `);
1208
- await writeFile2(projectPath, "lint.js", `console.log('lint ok');
1723
+ await codeFile(projectPath, "test.js", jsTest("import", `import { greet } from './src/main.js';
1724
+ assert.strictEqual(greet(), 'hi');`));
1725
+ }
1726
+ }),
1727
+ // Feature additions
1728
+ task({
1729
+ id: "add-cli-arg-parser",
1730
+ name: "Add CLI arg parser",
1731
+ title: "Add parseArgs function",
1732
+ description: "Create src/cli.js exporting parseArgs that returns an object from process.argv style array.",
1733
+ complexity: "medium",
1734
+ setup: async (projectPath) => {
1735
+ await nodeProject(projectPath);
1736
+ await codeFile(projectPath, "test.js", jsTest("cli", `import { parseArgs } from './src/cli.js';
1737
+ const args = parseArgs(['node', 'script.js', '--name', 'Ada']);
1738
+ assert.strictEqual(args.name, 'Ada');`));
1739
+ }
1740
+ }),
1741
+ task({
1742
+ id: "add-validator",
1743
+ name: "Add an email validator",
1744
+ title: "Add isEmail function",
1745
+ description: "Create src/validators.js exporting isEmail that returns true for valid emails.",
1746
+ complexity: "simple",
1747
+ setup: async (projectPath) => {
1748
+ await nodeProject(projectPath);
1749
+ await codeFile(projectPath, "test.js", jsTest("validator", `import { isEmail } from './src/validators.js';
1750
+ assert.strictEqual(isEmail('a@b.com'), true);
1751
+ assert.strictEqual(isEmail('not-an-email'), false);`));
1752
+ }
1753
+ }),
1754
+ task({
1755
+ id: "add-http-route",
1756
+ name: "Add a simple route",
1757
+ title: "Add /health route handler",
1758
+ description: 'Add a /health route to src/server.js that responds with { status: "ok" }.',
1759
+ complexity: "medium",
1760
+ setup: async (projectPath) => {
1761
+ await nodeProject(projectPath);
1762
+ await codeFile(projectPath, "src/server.js", `export function createServer() { return { handle: (req) => ({ statusCode: 404, body: 'not found' }) }; }
1209
1763
  `);
1210
- await writeFile2(projectPath, "build.js", `console.log('build ok');
1764
+ await codeFile(projectPath, "test.js", jsTest("route", `import { createServer } from './src/server.js';
1765
+ const server = createServer();
1766
+ const res = server.handle({ url: '/health', method: 'GET' });
1767
+ assert.strictEqual(res.statusCode, 200);
1768
+ assert.deepStrictEqual(JSON.parse(res.body), { status: 'ok' });`));
1769
+ }
1770
+ }),
1771
+ task({
1772
+ id: "add-sum-function",
1773
+ name: "Add a sum utility",
1774
+ title: "Add sum(arr) function",
1775
+ description: "Create src/math.js exporting sum that adds all numbers in an array.",
1776
+ complexity: "simple",
1777
+ setup: async (projectPath) => {
1778
+ await nodeProject(projectPath);
1779
+ await codeFile(projectPath, "test.js", jsTest("sum", `import { sum } from './src/math.js';
1780
+ assert.strictEqual(sum([1, 2, 3]), 6);
1781
+ assert.strictEqual(sum([]), 0);`));
1782
+ }
1783
+ }),
1784
+ task({
1785
+ id: "add-logger",
1786
+ name: "Add a logger",
1787
+ title: "Add createLogger function",
1788
+ description: "Create src/logger.js exporting createLogger that returns an object with info/error methods.",
1789
+ complexity: "simple",
1790
+ setup: async (projectPath) => {
1791
+ await nodeProject(projectPath);
1792
+ await codeFile(projectPath, "test.js", jsTest("logger", `import { createLogger } from './src/logger.js';
1793
+ const logger = createLogger();
1794
+ assert.strictEqual(typeof logger.info, 'function');
1795
+ assert.strictEqual(typeof logger.error, 'function');`));
1796
+ }
1797
+ }),
1798
+ // Tests
1799
+ task({
1800
+ id: "write-missing-test",
1801
+ name: "Write a missing test",
1802
+ title: "Write a test for double()",
1803
+ description: "Create test.js that tests a src/math.js double() function and make it pass.",
1804
+ complexity: "medium",
1805
+ setup: async (projectPath) => {
1806
+ await nodeProject(projectPath);
1807
+ await codeFile(projectPath, "src/math.js", `export function double(n) { return n * 2; }
1211
1808
  `);
1212
- },
1213
- evaluate: (ctx) => Promise.resolve(validate(ctx))
1214
- },
1215
- {
1216
- id: "noop-validation",
1217
- name: "Run validation on a clean project",
1218
- title: "Confirm the project already passes validation",
1219
- description: "This project already passes lint, test and build. Use the publish tool to confirm validation passes and finish.",
1220
- complexity: "simple",
1221
- setup: async (projectPath) => {
1222
- await writeJson(projectPath, "package.json", basePackage());
1223
- await writeFile2(projectPath, "test.js", `console.log('test ok');
1809
+ await codeFile(projectPath, "test.js", `import assert from 'node:assert';
1810
+ import { double } from './src/math.js';
1811
+ assert.strictEqual(double(3), 6);
1812
+ console.log('double test passed');
1224
1813
  `);
1225
- await writeFile2(projectPath, "lint.js", `console.log('lint ok');
1814
+ }
1815
+ }),
1816
+ task({
1817
+ id: "make-failing-test-pass",
1818
+ name: "Make failing test pass",
1819
+ title: "Implement capitalize()",
1820
+ description: "Implement src/strings.js capitalize() so the provided test passes.",
1821
+ complexity: "simple",
1822
+ setup: async (projectPath) => {
1823
+ await nodeProject(projectPath);
1824
+ await codeFile(projectPath, "src/strings.js", `export function capitalize(s) { return s; }
1226
1825
  `);
1227
- await writeFile2(projectPath, "build.js", `console.log('build ok');
1826
+ await codeFile(projectPath, "test.js", jsTest("capitalize", `import { capitalize } from './src/strings.js';
1827
+ assert.strictEqual(capitalize('hello'), 'Hello');`));
1828
+ }
1829
+ }),
1830
+ // Config/package
1831
+ task({
1832
+ id: "fix-package-type",
1833
+ name: "Fix package type",
1834
+ title: "Set package type to module",
1835
+ description: 'Update package.json so "type" is "module".',
1836
+ complexity: "simple",
1837
+ setup: async (projectPath) => {
1838
+ await jsonFile(projectPath, "package.json", {
1839
+ name: "synthetic-bench-project",
1840
+ version: "1.0.0",
1841
+ type: "commonjs",
1842
+ scripts: { lint: "node lint.js", test: "node test.js", build: "node build.js" }
1843
+ });
1844
+ await codeFile(projectPath, "lint.js", lintAlwaysPass);
1845
+ await codeFile(projectPath, "test.js", `console.log('test ok');
1228
1846
  `);
1229
- },
1230
- evaluate: (ctx) => Promise.resolve(validate(ctx))
1231
- }
1232
- ];
1233
- function syntheticSuite() {
1234
- return TASKS;
1847
+ await codeFile(projectPath, "build.js", buildAlwaysPass);
1848
+ },
1849
+ evaluate: combined(async (ctx) => {
1850
+ const pkg = JSON.parse(await fs4.readFile(`${ctx.projectPath}/package.json`, "utf-8"));
1851
+ return {
1852
+ passed: pkg.type === "module",
1853
+ message: pkg.type === "module" ? "package type is module" : "package type not module"
1854
+ };
1855
+ }, expectValidationPassed())
1856
+ }),
1857
+ task({
1858
+ id: "update-dependency",
1859
+ name: "Update dependency version",
1860
+ title: "Bump lodash to 4.17.21",
1861
+ description: "Update package.json so lodash dependency version is 4.17.21.",
1862
+ complexity: "simple",
1863
+ setup: async (projectPath) => {
1864
+ await jsonFile(projectPath, "package.json", {
1865
+ name: "synthetic-bench-project",
1866
+ version: "1.0.0",
1867
+ type: "module",
1868
+ dependencies: { lodash: "4.17.20" },
1869
+ scripts: { lint: "node lint.js", test: "node test.js", build: "node build.js" }
1870
+ });
1871
+ await codeFile(projectPath, "lint.js", lintAlwaysPass);
1872
+ await codeFile(projectPath, "test.js", `import pkg from './package.json' assert { type: 'json' };
1873
+ if (pkg.dependencies?.lodash !== '4.17.21') { console.error('lodash not updated'); process.exit(1); }
1874
+ console.log('dependency test passed');
1875
+ `);
1876
+ await codeFile(projectPath, "build.js", buildAlwaysPass);
1877
+ }
1878
+ }),
1879
+ // Multi-file
1880
+ task({
1881
+ id: "extract-shared-utility",
1882
+ name: "Extract shared utility",
1883
+ title: "Move formatDate to src/date.js and update imports",
1884
+ description: "Extract the formatDate function from src/a.js and src/b.js into src/date.js, then update imports.",
1885
+ complexity: "medium",
1886
+ setup: async (projectPath) => {
1887
+ await nodeProject(projectPath);
1888
+ await codeFile(projectPath, "src/a.js", `function formatDate(d) { return d.toISOString().split('T')[0]; }
1889
+ export function runA(d) { return formatDate(d); }
1890
+ `);
1891
+ await codeFile(projectPath, "src/b.js", `function formatDate(d) { return d.toISOString().split('T')[0]; }
1892
+ export function runB(d) { return formatDate(d); }
1893
+ `);
1894
+ await codeFile(projectPath, "test.js", jsTest("shared", `import { readFile } from 'node:fs/promises';
1895
+ import { runA } from './src/a.js';
1896
+ import { runB } from './src/b.js';
1897
+ const a = await readFile('./src/a.js', 'utf-8');
1898
+ const b = await readFile('./src/b.js', 'utf-8');
1899
+ if (!a.includes('./date.js') || !b.includes('./date.js')) { console.error('imports not updated'); process.exit(1); }
1900
+ const d = new Date('2024-01-15');
1901
+ assert.strictEqual(runA(d), '2024-01-15');
1902
+ assert.strictEqual(runB(d), '2024-01-15');`));
1903
+ }
1904
+ }),
1905
+ task({
1906
+ id: "fix-circular-import",
1907
+ name: "Fix circular import",
1908
+ title: "Break circular dependency between a.js and b.js",
1909
+ description: "Refactor src/a.js and src/b.js to remove the circular dependency while keeping both exports working.",
1910
+ complexity: "complex",
1911
+ setup: async (projectPath) => {
1912
+ await nodeProject(projectPath);
1913
+ await codeFile(projectPath, "src/a.js", `import { valueB } from './b.js';
1914
+ export const valueA = 'A' + valueB;
1915
+ `);
1916
+ await codeFile(projectPath, "src/b.js", `import { valueA } from './a.js';
1917
+ export const valueB = 'B' + valueA;
1918
+ `);
1919
+ await codeFile(projectPath, "test.js", jsTest("circular", `import { valueA, valueB } from './src/a.js';
1920
+ assert.ok(typeof valueA === 'string');
1921
+ assert.ok(typeof valueB === 'string');`));
1922
+ }
1923
+ }),
1924
+ // Documentation
1925
+ task({
1926
+ id: "update-readme-example",
1927
+ name: "Update README example",
1928
+ title: "Add a usage example to README.md",
1929
+ description: "Edit README.md to include a code block example showing how to use greet().",
1930
+ complexity: "simple",
1931
+ setup: async (projectPath) => {
1932
+ await nodeProject(projectPath);
1933
+ await codeFile(projectPath, "README.md", `# Project
1934
+
1935
+ A demo project.
1936
+ `);
1937
+ await codeFile(projectPath, "test.js", [
1938
+ "import { readFile } from 'node:fs/promises';",
1939
+ "const readme = await readFile('./README.md', 'utf-8');",
1940
+ "if (!readme.includes('```')) { console.error('README missing code block'); process.exit(1); }",
1941
+ "if (!readme.includes('greet')) { console.error('README missing greet example'); process.exit(1); }",
1942
+ "console.log('readme test passed');",
1943
+ ""
1944
+ ].join("\n"));
1945
+ }
1946
+ }),
1947
+ // Validation / noop
1948
+ task({
1949
+ id: "noop-validation",
1950
+ name: "Run validation on a clean project",
1951
+ title: "Confirm the project already passes validation",
1952
+ description: "This project already passes lint, test and build. Use the publish tool to confirm validation passes and finish.",
1953
+ complexity: "simple",
1954
+ setup: async (projectPath) => {
1955
+ await nodeProject(projectPath);
1956
+ }
1957
+ }),
1958
+ // Lint / format
1959
+ task({
1960
+ id: "fix-lint-warning",
1961
+ name: "Fix a lint warning",
1962
+ title: "Remove unused variable flagged by linter",
1963
+ description: "The custom linter flags any use of `var`. Update src/index.js so the lint script passes while keeping the greeting behavior.",
1964
+ complexity: "simple",
1965
+ setup: async (projectPath) => {
1966
+ await nodeProject(projectPath, {
1967
+ lint: "node lint.js"
1968
+ });
1969
+ await codeFile(projectPath, "src/index.js", `var greeting = 'Hello';
1970
+ export function hi(name) { return greeting + ', ' + name + '!'; }
1971
+ `);
1972
+ await codeFile(projectPath, "lint.js", `import fs from 'node:fs/promises';
1973
+ const code = await fs.readFile('./src/index.js', 'utf-8');
1974
+ if (code.includes('var ')) { console.error('lint failed: var keyword found'); process.exit(1); }
1975
+ console.log('lint ok');
1976
+ `);
1977
+ await codeFile(projectPath, "test.js", jsTest("hi", `import { hi } from './src/index.js';
1978
+ assert.strictEqual(hi('Omega'), 'Hello, Omega!');`));
1979
+ }
1980
+ }),
1981
+ // Complex
1982
+ task({
1983
+ id: "implement-todo-store",
1984
+ name: "Implement a todo store",
1985
+ title: "Implement TodoStore with add/complete/list",
1986
+ description: "Create src/todo.js exporting TodoStore class with add(text), complete(id), and list() methods.",
1987
+ complexity: "complex",
1988
+ setup: async (projectPath) => {
1989
+ await nodeProject(projectPath);
1990
+ await codeFile(projectPath, "test.js", jsTest("todo", `import { TodoStore } from './src/todo.js';
1991
+ const store = new TodoStore();
1992
+ const id = store.add('Buy milk');
1993
+ assert.deepStrictEqual(store.list(), [{ id, text: 'Buy milk', done: false }]);
1994
+ store.complete(id);
1995
+ assert.strictEqual(store.list()[0].done, true);`));
1996
+ }
1997
+ })
1998
+ ];
1235
1999
  }
1236
2000
 
1237
2001
  // ../bench/dist/adapters/deepswe.js
1238
- import fs4 from "node:fs/promises";
1239
- import path7 from "node:path";
2002
+ import fs5 from "node:fs/promises";
2003
+ import path8 from "node:path";
1240
2004
  import { execFile } from "node:child_process";
1241
2005
  import { promisify } from "node:util";
1242
2006
  var execFileAsync = promisify(execFile);
@@ -1278,18 +2042,18 @@ function mulberry32(seed) {
1278
2042
  };
1279
2043
  }
1280
2044
  async function readTask(taskDir) {
1281
- const tomlRaw = await fs4.readFile(path7.join(taskDir, "task.toml"), "utf-8");
1282
- const instructionRaw = await fs4.readFile(path7.join(taskDir, "instruction.md"), "utf-8");
2045
+ const tomlRaw = await fs5.readFile(path8.join(taskDir, "task.toml"), "utf-8");
2046
+ const instructionRaw = await fs5.readFile(path8.join(taskDir, "instruction.md"), "utf-8");
1283
2047
  return { toml: parseToml(tomlRaw), instruction: instructionRaw };
1284
2048
  }
1285
2049
  async function cloneRepo(repoUrl, commit, targetPath) {
1286
- await fs4.mkdir(path7.dirname(targetPath), { recursive: true });
2050
+ await fs5.mkdir(path8.dirname(targetPath), { recursive: true });
1287
2051
  await execFileAsync("git", ["clone", repoUrl, targetPath], { timeout: 12e4 });
1288
2052
  await execFileAsync("git", ["-C", targetPath, "checkout", commit], { timeout: 6e4 });
1289
2053
  }
1290
2054
  async function loadDeepSWESuite(options) {
1291
- const entries = await fs4.readdir(options.tasksDir, { withFileTypes: true });
1292
- const taskDirs = entries.filter((e) => e.isDirectory()).map((e) => path7.join(options.tasksDir, e.name));
2055
+ const entries = await fs5.readdir(options.tasksDir, { withFileTypes: true });
2056
+ const taskDirs = entries.filter((e) => e.isDirectory()).map((e) => path8.join(options.tasksDir, e.name));
1293
2057
  let loaded = [];
1294
2058
  for (const dir of taskDirs) {
1295
2059
  try {
@@ -1300,7 +2064,7 @@ async function loadDeepSWESuite(options) {
1300
2064
  }
1301
2065
  if (options.taskIds && options.taskIds.length > 0) {
1302
2066
  loaded = loaded.filter((t) => {
1303
- const id = t.toml.metadata?.task_id ?? path7.basename(t.dir);
2067
+ const id = t.toml.metadata?.task_id ?? path8.basename(t.dir);
1304
2068
  return options.taskIds?.includes(id);
1305
2069
  });
1306
2070
  }
@@ -1311,7 +2075,7 @@ async function loadDeepSWESuite(options) {
1311
2075
  }
1312
2076
  const tasks = [];
1313
2077
  for (const { dir, toml, instruction } of loaded) {
1314
- const id = toml.metadata?.task_id ?? path7.basename(dir);
2078
+ const id = toml.metadata?.task_id ?? path8.basename(dir);
1315
2079
  const title = toml.metadata?.display_title ?? toml.metadata?.original_title ?? id;
1316
2080
  const repo = toml.metadata?.repository_url;
1317
2081
  const commit = toml.metadata?.base_commit_hash;
@@ -1342,14 +2106,14 @@ async function loadDeepSWESuite(options) {
1342
2106
  }
1343
2107
 
1344
2108
  // ../bench/dist/optimise.js
1345
- import fs5 from "node:fs/promises";
1346
- import path8 from "node:path";
2109
+ import fs6 from "node:fs/promises";
2110
+ import path9 from "node:path";
1347
2111
  async function findLatestReport(outputDir) {
1348
- const entries = await fs5.readdir(outputDir).catch(() => []);
2112
+ const entries = await fs6.readdir(outputDir).catch(() => []);
1349
2113
  const files = entries.filter((f) => f.startsWith("benchmark-") && f.endsWith(".json")).sort().reverse();
1350
2114
  if (files.length === 0)
1351
2115
  return void 0;
1352
- const raw = await fs5.readFile(path8.join(outputDir, files[0]), "utf-8");
2116
+ const raw = await fs6.readFile(path9.join(outputDir, files[0]), "utf-8");
1353
2117
  return JSON.parse(raw);
1354
2118
  }
1355
2119
  function summariseTraceFlow(report, maxLines = 60) {
@@ -1359,22 +2123,41 @@ function summariseTraceFlow(report, maxLines = 60) {
1359
2123
  return lines.slice(0, maxLines).join("\n") + "\n... (truncated)";
1360
2124
  }
1361
2125
  function buildOptimisePrompt(report, failedResult, traceFlowText) {
1362
- const passRate = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
2126
+ const passRate2 = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
1363
2127
  const failed = report.results.filter((r) => !r.evaluation.passed);
1364
2128
  const lines = [];
1365
2129
  lines.push("# Prompt optimisation task");
1366
2130
  lines.push("");
1367
- lines.push(`The latest benchmark run achieved ${String(passRate)}% pass rate (${String(report.passed)}/${String(report.total)}).`);
2131
+ lines.push(`The latest benchmark run achieved ${String(passRate2)}% pass rate (${String(report.passed)}/${String(report.total)}).`);
2132
+ lines.push("");
2133
+ lines.push("## Failure analysis");
2134
+ const traceFlows = /* @__PURE__ */ new Map();
2135
+ const failureSummary = summariseFailures(report.results, traceFlows);
2136
+ for (const summary of failureSummary) {
2137
+ lines.push(`- ${summary.category}: ${String(summary.count)} task(s)`);
2138
+ for (const example of summary.examples) {
2139
+ lines.push(` - ${example}`);
2140
+ }
2141
+ }
1368
2142
  lines.push("");
1369
2143
  lines.push("## Failed tasks");
1370
2144
  for (const r of failed.slice(0, 5)) {
1371
- lines.push(`- ${r.task.name}: ${r.evaluation.message ?? "no message"}`);
2145
+ const analysis = r.failureAnalysis ?? classifyFailure(r, void 0);
2146
+ lines.push(`- ${r.task.name} [${analysis.category}]: ${r.evaluation.message ?? "no message"}`);
1372
2147
  }
1373
2148
  lines.push("");
1374
2149
  if (failedResult) {
2150
+ const analysis = failedResult.failureAnalysis ?? classifyFailure(failedResult, void 0);
1375
2151
  lines.push(`## Focus task: ${failedResult.task.name}`);
1376
2152
  lines.push(`Status: ${failedResult.status}`);
1377
- lines.push(`Evaluation: ${failedResult.evaluation.message ?? "none"}`);
2153
+ lines.push(`Category: ${analysis.category}`);
2154
+ lines.push(`Root cause: ${analysis.rootCause}`);
2155
+ if (analysis.evidence.length > 0) {
2156
+ lines.push("Evidence:");
2157
+ for (const ev of analysis.evidence) {
2158
+ lines.push(`- ${ev}`);
2159
+ }
2160
+ }
1378
2161
  lines.push("");
1379
2162
  }
1380
2163
  if (traceFlowText) {
@@ -1388,6 +2171,7 @@ function buildOptimisePrompt(report, failedResult, traceFlowText) {
1388
2171
  lines.push("Analyse the failures above and edit `packages/agent/src/prompts.ts` (and optionally `packages/agent/src/planner.ts`) to improve the Omega agent.");
1389
2172
  lines.push("Rules:");
1390
2173
  lines.push("- Keep changes minimal and targeted.");
2174
+ lines.push("- Address the diagnosed root cause, not just the symptom.");
1391
2175
  lines.push("- Do not break existing tool signatures or system behaviour.");
1392
2176
  lines.push("- Run `pnpm lint` and `pnpm test` after edits.");
1393
2177
  lines.push("- Finish with a summary of what changed and why it should help.");
@@ -1397,10 +2181,17 @@ async function loadOptimisationContext(apiUrl, outputDir) {
1397
2181
  const report = await findLatestReport(outputDir);
1398
2182
  if (!report)
1399
2183
  return void 0;
1400
- const failedResult = report.results.find((r) => !r.evaluation.passed);
2184
+ const traceFlows = /* @__PURE__ */ new Map();
2185
+ for (const result of report.results) {
2186
+ if (result.evaluation.passed)
2187
+ continue;
2188
+ const traceFlow = result.harnessTaskId ? await getTraceFlow(apiUrl, result.harnessTaskId) : void 0;
2189
+ traceFlows.set(result.harnessTaskId, traceFlow ?? void 0);
2190
+ }
2191
+ const failedResult = pickFocusResult(report.results, traceFlows);
1401
2192
  let traceFlowText;
1402
2193
  if (failedResult?.harnessTaskId) {
1403
- const traceFlow = await getTraceFlow(apiUrl, failedResult.harnessTaskId);
2194
+ const traceFlow = traceFlows.get(failedResult.harnessTaskId);
1404
2195
  if (traceFlow) {
1405
2196
  traceFlowText = JSON.stringify(traceFlow, null, 2);
1406
2197
  }
@@ -1425,6 +2216,92 @@ async function submitOptimiseTask(apiUrl, projectId, prompt) {
1425
2216
  return res.json();
1426
2217
  }
1427
2218
 
2219
+ // ../bench/dist/ab.js
2220
+ function passRate(report) {
2221
+ return report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
2222
+ }
2223
+ function findResult(report, taskId) {
2224
+ return report.results.find((r) => r.task.id === taskId);
2225
+ }
2226
+ function buildAbReport(baseline, candidate, baselineReport, candidateReport) {
2227
+ const baselineRate = passRate(baselineReport);
2228
+ const candidateRate = passRate(candidateReport);
2229
+ const taskIds = /* @__PURE__ */ new Set();
2230
+ for (const r of baselineReport.results)
2231
+ taskIds.add(r.task.id);
2232
+ for (const r of candidateReport.results)
2233
+ taskIds.add(r.task.id);
2234
+ const perTask = Array.from(taskIds).map((taskId) => {
2235
+ const base = findResult(baselineReport, taskId);
2236
+ const cand = findResult(candidateReport, taskId);
2237
+ return {
2238
+ taskId,
2239
+ taskName: base?.task.name ?? cand?.task.name ?? taskId,
2240
+ baselinePassed: base?.evaluation.passed ?? false,
2241
+ candidatePassed: cand?.evaluation.passed ?? false,
2242
+ baselineTokens: base?.usage?.totalTokens,
2243
+ candidateTokens: cand?.usage?.totalTokens
2244
+ };
2245
+ });
2246
+ return {
2247
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2248
+ baseline,
2249
+ candidate,
2250
+ baselineReport,
2251
+ candidateReport,
2252
+ delta: {
2253
+ passRate: candidateRate - baselineRate,
2254
+ passedDelta: candidateReport.passed - baselineReport.passed,
2255
+ durationMsDelta: candidateReport.totalDurationMs - baselineReport.totalDurationMs,
2256
+ promptTokensDelta: (candidateReport.totalUsage.promptTokens ?? 0) - (baselineReport.totalUsage.promptTokens ?? 0),
2257
+ completionTokensDelta: (candidateReport.totalUsage.completionTokens ?? 0) - (baselineReport.totalUsage.completionTokens ?? 0),
2258
+ totalTokensDelta: (candidateReport.totalUsage.totalTokens ?? 0) - (baselineReport.totalUsage.totalTokens ?? 0)
2259
+ },
2260
+ perTask
2261
+ };
2262
+ }
2263
+ function deltaSymbol(value) {
2264
+ if (value > 0)
2265
+ return "+";
2266
+ if (value < 0)
2267
+ return "";
2268
+ return "";
2269
+ }
2270
+ function formatDelta(value) {
2271
+ return `${deltaSymbol(value)}${String(value)}`;
2272
+ }
2273
+ function formatAbReport(report) {
2274
+ const lines = [];
2275
+ lines.push("# Omega A/B Benchmark Report");
2276
+ lines.push("");
2277
+ lines.push(`- Timestamp: ${report.timestamp}`);
2278
+ lines.push(`- Baseline: ${report.baseline.name}`);
2279
+ lines.push(`- Candidate: ${report.candidate.name}`);
2280
+ lines.push("");
2281
+ lines.push("## Summary");
2282
+ lines.push(`| Metric | Baseline | Candidate | Delta |`);
2283
+ lines.push(`|---|---|---|---|`);
2284
+ lines.push(`| Pass rate | ${String(passRate(report.baselineReport))}% | ${String(passRate(report.candidateReport))}% | ${formatDelta(report.delta.passRate)}% |`);
2285
+ lines.push(`| Passed | ${String(report.baselineReport.passed)}/${String(report.baselineReport.total)} | ${String(report.candidateReport.passed)}/${String(report.candidateReport.total)} | ${formatDelta(report.delta.passedDelta)} |`);
2286
+ lines.push(`| Duration | ${formatDuration2(report.baselineReport.totalDurationMs)} | ${formatDuration2(report.candidateReport.totalDurationMs)} | ${formatDuration2(report.delta.durationMsDelta)} |`);
2287
+ lines.push(`| Tokens | ${String(report.baselineReport.totalUsage.totalTokens ?? 0)} | ${String(report.candidateReport.totalUsage.totalTokens ?? 0)} | ${formatDelta(report.delta.totalTokensDelta)} |`);
2288
+ lines.push("");
2289
+ lines.push("## Per-task comparison");
2290
+ lines.push(`| Task | Baseline | Candidate | Token delta |`);
2291
+ lines.push(`|---|---|---|---|`);
2292
+ for (const t of report.perTask) {
2293
+ const tokenDelta = t.baselineTokens !== void 0 && t.candidateTokens !== void 0 ? formatDelta(t.candidateTokens - t.baselineTokens) : "-";
2294
+ lines.push(`| ${t.taskName} | ${t.baselinePassed ? "pass" : "fail"} | ${t.candidatePassed ? "pass" : "fail"} | ${tokenDelta} |`);
2295
+ }
2296
+ lines.push("");
2297
+ return lines.join("\n");
2298
+ }
2299
+ function formatDuration2(ms) {
2300
+ if (ms < 1e3)
2301
+ return `${String(Math.round(ms))}ms`;
2302
+ return `${(ms / 1e3).toFixed(1)}s`;
2303
+ }
2304
+
1428
2305
  // ../../apps/cli/src/commands/bench.ts
1429
2306
  async function waitForApi2(apiUrl, maxMs = 1e4) {
1430
2307
  const deadline = Date.now() + maxMs;
@@ -1440,10 +2317,10 @@ async function waitForApi2(apiUrl, maxMs = 1e4) {
1440
2317
  }
1441
2318
  function currentProject(apiUrl) {
1442
2319
  const cwd = process.cwd();
1443
- const name = path9.basename(cwd);
2320
+ const name = path11.basename(cwd);
1444
2321
  return ensureProject2(apiUrl, `bench-${name}`, cwd);
1445
2322
  }
1446
- 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").action(async (opts) => {
2323
+ 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").action(async (opts) => {
1447
2324
  const apiUrl = getApiUrl();
1448
2325
  await waitForApi2(apiUrl);
1449
2326
  const timeoutMs = Number(opts.timeout);
@@ -1461,6 +2338,9 @@ var runCmd = new Command8("run").description("Run a benchmark suite").option("--
1461
2338
  suiteName = "deep-swe";
1462
2339
  } else if (opts.suite === "synthetic") {
1463
2340
  tasks = syntheticSuite();
2341
+ if (opts.nTasks !== void 0 && opts.nTasks > 0) {
2342
+ tasks = tasks.slice(0, opts.nTasks);
2343
+ }
1464
2344
  suiteName = "synthetic";
1465
2345
  } else {
1466
2346
  throw new Error(`Unknown suite: ${opts.suite}`);
@@ -1492,17 +2372,136 @@ var optimiseCmd = new Command8("optimise").description("Create a self-improve ta
1492
2372
  console.log("No benchmark report found. Run `omega bench run` first.");
1493
2373
  return;
1494
2374
  }
2375
+ const { loadCurrentPrompts: loadCurrentPrompts2 } = await Promise.resolve().then(() => (init_prompt_versioning(), prompt_versioning_exports));
2376
+ const current = await loadCurrentPrompts2();
2377
+ const versionRes = await fetch(`${apiUrl}/prompt-versions`, {
2378
+ method: "POST",
2379
+ headers: { "Content-Type": "application/json" },
2380
+ body: JSON.stringify({
2381
+ name: current.name,
2382
+ sourcePath: current.sourcePath,
2383
+ systemPrompt: current.systemPrompt,
2384
+ textToolsPrompt: current.textToolsPrompt,
2385
+ hash: current.hash,
2386
+ metadata: { planningPrompt: current.planningPrompt }
2387
+ })
2388
+ });
2389
+ if (versionRes.ok) {
2390
+ const version = await versionRes.json();
2391
+ console.log(`Saved baseline prompt version: ${version.name} (${version.id})`);
2392
+ } else {
2393
+ console.warn("Could not save prompt version:", await versionRes.text());
2394
+ }
1495
2395
  const project = await currentProject(apiUrl);
1496
2396
  const prompt = buildOptimisePrompt(
1497
2397
  context.report,
1498
2398
  context.failedResult,
1499
2399
  context.traceFlowText
1500
2400
  );
1501
- const task = await submitOptimiseTask(apiUrl, project.id, prompt);
1502
- console.log(`Created self-improve task ${task.id}`);
1503
- console.log(`Run \`omega task run ${task.id}\` to execute it.`);
2401
+ const task2 = await submitOptimiseTask(apiUrl, project.id, prompt);
2402
+ console.log(`Created self-improve task ${task2.id}`);
2403
+ console.log(`Run \`omega task run ${task2.id}\` to execute it.`);
2404
+ });
2405
+ async function loadPromptSpec(source) {
2406
+ if (source === "current") {
2407
+ const { readPromptsSource: readPromptsSource2 } = await Promise.resolve().then(() => (init_prompt_versioning(), prompt_versioning_exports));
2408
+ const prompts = await readPromptsSource2();
2409
+ return { name: "current", ...prompts };
2410
+ }
2411
+ const resolved = path11.resolve(source);
2412
+ const raw = await fs8.readFile(resolved, "utf-8");
2413
+ const parsed = JSON.parse(raw);
2414
+ if (!parsed.name || !parsed.systemPrompt || !parsed.textToolsPrompt) {
2415
+ throw new Error("Prompt file must include name, systemPrompt, and textToolsPrompt");
2416
+ }
2417
+ return parsed;
2418
+ }
2419
+ async function writePromptFile(spec) {
2420
+ const tmpDir = await fs8.mkdtemp(path11.join(os.tmpdir(), "omega-prompt-"));
2421
+ const file = path11.join(tmpDir, "prompts.json");
2422
+ await fs8.writeFile(file, JSON.stringify(spec, null, 2), "utf-8");
2423
+ return file;
2424
+ }
2425
+ async function runBenchmarkWithPrompts(tasks, spec, options) {
2426
+ const promptFile = await writePromptFile(spec);
2427
+ const env = {
2428
+ ...process.env,
2429
+ OMEGA_SYSTEM_PROMPT_FILE: promptFile,
2430
+ OMEGA_TEXT_TOOLS_PROMPT_FILE: promptFile
2431
+ };
2432
+ let apiUrl = options.apiUrl;
2433
+ let server;
2434
+ if (!apiUrl) {
2435
+ const started = await startHarnessServer(env);
2436
+ apiUrl = started.apiUrl;
2437
+ server = started.process;
2438
+ }
2439
+ try {
2440
+ await waitForApi2(apiUrl, 3e4);
2441
+ const report = await runBenchmark(tasks, {
2442
+ apiUrl,
2443
+ suiteName: options.suiteName,
2444
+ timeoutMs: options.timeoutMs,
2445
+ onProgress: (result) => {
2446
+ const symbol = result.evaluation.passed ? "\u2713" : "\u2717";
2447
+ console.log(`${symbol} ${result.task.name} [${result.status}] ${String(result.durationMs)}ms`);
2448
+ }
2449
+ });
2450
+ return { report, apiUrl, server };
2451
+ } catch (err) {
2452
+ server?.kill();
2453
+ throw err;
2454
+ }
2455
+ }
2456
+ 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("--n-tasks <n>", "limit number of tasks", parseInt).option("--timeout <ms>", "per-task timeout in ms", "120000").option("--output-dir <dir>", "report output directory", ".omega/reports").action(async (opts) => {
2457
+ if (!opts.candidate) {
2458
+ throw new Error("--candidate is required");
2459
+ }
2460
+ const timeoutMs = Number(opts.timeout);
2461
+ const existingApiUrl = getApiUrl();
2462
+ let tasks;
2463
+ let suiteName;
2464
+ if (opts.suite === "deep-swe") {
2465
+ throw new Error("DeepSWE A/B not yet supported; use --suite synthetic");
2466
+ } else if (opts.suite === "synthetic") {
2467
+ tasks = syntheticSuite();
2468
+ if (opts.nTasks !== void 0 && opts.nTasks > 0) {
2469
+ tasks = tasks.slice(0, opts.nTasks);
2470
+ }
2471
+ suiteName = "synthetic";
2472
+ } else {
2473
+ throw new Error(`Unknown suite: ${opts.suite}`);
2474
+ }
2475
+ const baselineSpec = await loadPromptSpec(opts.baseline);
2476
+ const candidateSpec = await loadPromptSpec(opts.candidate);
2477
+ console.log(`Baseline: ${baselineSpec.name}`);
2478
+ console.log(`Candidate: ${candidateSpec.name}`);
2479
+ console.log(`Running ${String(tasks.length)} tasks for baseline...`);
2480
+ const { report: baselineReport, server: baseServer } = await runBenchmarkWithPrompts(tasks, baselineSpec, {
2481
+ apiUrl: existingApiUrl,
2482
+ suiteName: `${suiteName}-baseline`,
2483
+ timeoutMs
2484
+ });
2485
+ baseServer?.kill();
2486
+ console.log(`Running ${String(tasks.length)} tasks for candidate...`);
2487
+ const { report: candidateReport, server: candServer } = await runBenchmarkWithPrompts(tasks, candidateSpec, {
2488
+ apiUrl: existingApiUrl,
2489
+ suiteName: `${suiteName}-candidate`,
2490
+ timeoutMs
2491
+ });
2492
+ candServer?.kill();
2493
+ const abReport = buildAbReport(baselineSpec, candidateSpec, baselineReport, candidateReport);
2494
+ await fs8.mkdir(opts.outputDir, { recursive: true });
2495
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2496
+ const jsonFile2 = path11.join(opts.outputDir, `ab-${ts}.json`);
2497
+ const mdFile = path11.join(opts.outputDir, `ab-${ts}.md`);
2498
+ await fs8.writeFile(jsonFile2, JSON.stringify(abReport, null, 2), "utf-8");
2499
+ await fs8.writeFile(mdFile, formatAbReport(abReport), "utf-8");
2500
+ console.log("\n" + formatAbReport(abReport));
2501
+ console.log(`
2502
+ A/B report written to ${jsonFile2}`);
1504
2503
  });
1505
- var benchCmd = new Command8("bench").description("Run benchmarks and optimise prompts").addCommand(runCmd).addCommand(optimiseCmd);
2504
+ var benchCmd = new Command8("bench").description("Run benchmarks and optimise prompts").addCommand(runCmd).addCommand(abCmd).addCommand(optimiseCmd);
1506
2505
 
1507
2506
  // ../../apps/cli/src/index.ts
1508
2507
  program2.name("harness").description("Omega harness CLI").version("0.1.0").option("--api <url>", "API base URL", "http://localhost:4000");