@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.
- package/dist/cli.js +1230 -231
- package/dist/server/dist/app.d.ts.map +1 -1
- package/dist/server/dist/app.js +2 -0
- package/dist/server/dist/app.js.map +1 -1
- package/dist/server/dist/lib/run-task.js +2 -2
- package/dist/server/dist/lib/run-task.js.map +1 -1
- package/dist/server/dist/routes/metrics.d.ts.map +1 -1
- package/dist/server/dist/routes/metrics.js +76 -2
- package/dist/server/dist/routes/metrics.js.map +1 -1
- package/dist/server/dist/routes/prompt-versions.d.ts +4 -0
- package/dist/server/dist/routes/prompt-versions.d.ts.map +1 -0
- package/dist/server/dist/routes/prompt-versions.js +40 -0
- package/dist/server/dist/routes/prompt-versions.js.map +1 -0
- package/dist/server/dist/routes/tasks.js.map +1 -1
- package/dist/server/node_modules/@grpc/proto-loader/build/bin/proto-loader-gen-types.js +0 -0
- package/dist/server/node_modules/@omega/agent/dist/executor.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/executor.js +40 -5
- package/dist/server/node_modules/@omega/agent/dist/executor.js.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/index.d.ts +1 -0
- package/dist/server/node_modules/@omega/agent/dist/index.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/index.js +1 -0
- package/dist/server/node_modules/@omega/agent/dist/index.js.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/planner.d.ts +5 -0
- package/dist/server/node_modules/@omega/agent/dist/planner.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/planner.js +21 -3
- package/dist/server/node_modules/@omega/agent/dist/planner.js.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/prompt-versioning.d.ts +22 -0
- package/dist/server/node_modules/@omega/agent/dist/prompt-versioning.d.ts.map +1 -0
- package/dist/server/node_modules/@omega/agent/dist/prompt-versioning.js +53 -0
- package/dist/server/node_modules/@omega/agent/dist/prompt-versioning.js.map +1 -0
- package/dist/server/node_modules/@omega/agent/dist/prompts.d.ts +2 -2
- package/dist/server/node_modules/@omega/agent/dist/prompts.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/prompts.js +21 -2
- package/dist/server/node_modules/@omega/agent/dist/prompts.js.map +1 -1
- package/dist/server/node_modules/@omega/agent/package.json +4 -0
- package/dist/server/node_modules/@omega/core/dist/provider.d.ts +12 -2
- package/dist/server/node_modules/@omega/core/dist/provider.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/db/dist/index.d.ts +1 -0
- package/dist/server/node_modules/@omega/db/dist/index.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/db/dist/index.js +1 -0
- package/dist/server/node_modules/@omega/db/dist/index.js.map +1 -1
- package/dist/server/node_modules/@omega/db/dist/prompt-versions.d.ts +19 -0
- package/dist/server/node_modules/@omega/db/dist/prompt-versions.d.ts.map +1 -0
- package/dist/server/node_modules/@omega/db/dist/prompt-versions.js +32 -0
- package/dist/server/node_modules/@omega/db/dist/prompt-versions.js.map +1 -0
- package/dist/server/node_modules/@omega/db/generated/client/edge.js +19 -4
- package/dist/server/node_modules/@omega/db/generated/client/index-browser.js +16 -1
- package/dist/server/node_modules/@omega/db/generated/client/index.d.ts +1511 -93
- package/dist/server/node_modules/@omega/db/generated/client/index.js +19 -4
- package/dist/server/node_modules/@omega/db/generated/client/libquery_engine-darwin-arm64.dylib.node +0 -0
- package/dist/server/node_modules/@omega/db/generated/client/package.json +1 -1
- package/dist/server/node_modules/@omega/db/generated/client/schema.prisma +14 -0
- package/dist/server/node_modules/@omega/db/generated/client/wasm.js +19 -4
- package/dist/server/node_modules/@omega/db/prisma/migrations/20260703010000_add_agent_run_usage/migration.sql +4 -0
- package/dist/server/node_modules/@omega/db/prisma/migrations/20260703020000_add_prompt_versions/migration.sql +16 -0
- package/dist/server/node_modules/@omega/providers/dist/anthropic.d.ts +2 -2
- package/dist/server/node_modules/@omega/providers/dist/anthropic.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/providers/dist/anthropic.js +16 -1
- package/dist/server/node_modules/@omega/providers/dist/anthropic.js.map +1 -1
- package/dist/server/node_modules/@omega/providers/dist/gemini.d.ts +2 -2
- package/dist/server/node_modules/@omega/providers/dist/gemini.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/providers/dist/gemini.js +14 -1
- package/dist/server/node_modules/@omega/providers/dist/gemini.js.map +1 -1
- package/dist/server/node_modules/@omega/providers/dist/kimi.d.ts +3 -3
- package/dist/server/node_modules/@omega/providers/dist/kimi.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/providers/dist/ollama.d.ts +2 -2
- package/dist/server/node_modules/@omega/providers/dist/ollama.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/providers/dist/ollama.js +13 -1
- package/dist/server/node_modules/@omega/providers/dist/ollama.js.map +1 -1
- package/dist/server/node_modules/@omega/providers/dist/openai.d.ts +3 -3
- package/dist/server/node_modules/@omega/providers/dist/openai.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/providers/dist/openai.js +24 -3
- package/dist/server/node_modules/@omega/providers/dist/openai.js.map +1 -1
- package/dist/server/node_modules/@omega/providers/dist/providers.test.js +13 -9
- package/dist/server/node_modules/@omega/providers/dist/providers.test.js.map +1 -1
- package/dist/server/node_modules/mime/cli.js +0 -0
- package/dist/server/node_modules/mime/src/build.js +0 -0
- package/dist/server/node_modules/wrap-ansi/index.js +0 -0
- package/dist/server/pglite-data/base/5/1247 +0 -0
- package/dist/server/pglite-data/base/5/1249 +0 -0
- package/dist/server/pglite-data/base/5/1259 +0 -0
- package/dist/server/pglite-data/base/5/1259_fsm +0 -0
- package/dist/server/pglite-data/base/5/1259_vm +0 -0
- package/dist/server/pglite-data/base/5/16384 +0 -0
- package/dist/server/pglite-data/base/5/16392 +0 -0
- package/dist/server/pglite-data/base/5/16394 +0 -0
- package/dist/server/pglite-data/base/5/16404 +0 -0
- package/dist/server/pglite-data/base/5/16406 +0 -0
- package/dist/server/pglite-data/base/5/16406_fsm +0 -0
- package/dist/server/pglite-data/base/5/16421 +0 -0
- package/dist/server/pglite-data/base/5/16423 +0 -0
- package/dist/server/pglite-data/base/5/16437 +0 -0
- package/dist/server/pglite-data/base/5/16439 +0 -0
- package/dist/server/pglite-data/base/5/16439_fsm +0 -0
- package/dist/server/pglite-data/base/5/16449 +0 -0
- package/dist/server/pglite-data/base/5/16451 +0 -0
- package/dist/server/pglite-data/base/5/16462 +0 -0
- package/dist/server/pglite-data/base/5/16464 +0 -0
- package/dist/server/pglite-data/base/5/16478 +0 -0
- package/dist/server/pglite-data/base/5/16480 +0 -0
- package/dist/server/pglite-data/base/5/16510 +0 -0
- package/dist/server/pglite-data/base/5/16511 +0 -0
- package/dist/server/pglite-data/base/5/16512 +0 -0
- package/dist/server/pglite-data/base/5/16513 +0 -0
- package/dist/server/pglite-data/base/5/16514 +0 -0
- package/dist/server/pglite-data/base/5/24576 +0 -0
- package/dist/server/pglite-data/base/5/24576_fsm +0 -0
- package/dist/server/pglite-data/base/5/24589 +0 -0
- package/dist/server/pglite-data/base/5/24591 +0 -0
- package/dist/server/pglite-data/base/5/24592 +0 -0
- package/dist/server/pglite-data/base/5/2579 +0 -0
- package/dist/server/pglite-data/base/5/2604 +0 -0
- package/dist/server/pglite-data/base/5/2606 +0 -0
- package/dist/server/pglite-data/base/5/2608 +0 -0
- package/dist/server/pglite-data/base/5/2610 +0 -0
- package/dist/server/pglite-data/base/5/2620 +0 -0
- package/dist/server/pglite-data/base/5/2656 +0 -0
- package/dist/server/pglite-data/base/5/2657 +0 -0
- package/dist/server/pglite-data/base/5/2658 +0 -0
- package/dist/server/pglite-data/base/5/2659 +0 -0
- package/dist/server/pglite-data/base/5/2662 +0 -0
- package/dist/server/pglite-data/base/5/2663 +0 -0
- package/dist/server/pglite-data/base/5/2664 +0 -0
- package/dist/server/pglite-data/base/5/2665 +0 -0
- package/dist/server/pglite-data/base/5/2666 +0 -0
- package/dist/server/pglite-data/base/5/2667 +0 -0
- package/dist/server/pglite-data/base/5/2673 +0 -0
- package/dist/server/pglite-data/base/5/2674 +0 -0
- package/dist/server/pglite-data/base/5/2678 +0 -0
- package/dist/server/pglite-data/base/5/2679 +0 -0
- package/dist/server/pglite-data/base/5/2703 +0 -0
- package/dist/server/pglite-data/base/5/2704 +0 -0
- package/dist/server/pglite-data/base/5/32768 +0 -0
- package/dist/server/pglite-data/base/5/32779 +0 -0
- package/dist/server/pglite-data/base/5/32780 +0 -0
- package/dist/server/pglite-data/base/5/32781 +0 -0
- package/dist/server/pglite-data/base/5/32783 +0 -0
- package/dist/server/pglite-data/base/5/3455 +0 -0
- package/dist/server/pglite-data/base/5/pg_internal.init +0 -0
- package/dist/server/pglite-data/global/pg_control +0 -0
- package/dist/server/pglite-data/global/pg_internal.init +0 -0
- package/dist/server/pglite-data/pg_wal/000000010000000000000001 +0 -0
- package/dist/server/pglite-data/pg_xact/0000 +0 -0
- package/dist/server/pglite-data/postmaster.pid +1 -1
- package/dist/server/src/app.ts +2 -0
- package/dist/server/src/lib/run-task.ts +2 -2
- package/dist/server/src/routes/metrics.ts +81 -2
- package/dist/server/src/routes/prompt-versions.ts +46 -0
- package/dist/server/src/routes/tasks.ts +2 -2
- package/dist/web/dist/assets/index-BVjHH_u_.js +40 -0
- package/dist/web/dist/assets/index-BkzWwMOJ.js +40 -0
- package/dist/web/dist/assets/index-D4rY_mY0.css +1 -0
- package/dist/web/dist/assets/index-DHQbiQAM.css +1 -0
- package/dist/web/dist/index.html +2 -2
- 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
|
|
70
|
-
current[
|
|
71
|
-
const prev = previousTasks.current[
|
|
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: "${
|
|
74
|
-
} else if (prev.status !==
|
|
75
|
-
if (
|
|
76
|
-
const provider =
|
|
77
|
-
const model =
|
|
78
|
-
addLog(`LLM picked up "${
|
|
79
|
-
} else if (
|
|
80
|
-
addLog(`Completed: "${
|
|
81
|
-
} else if (
|
|
82
|
-
addLog(`Failed: "${
|
|
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((
|
|
235
|
-
const providerText =
|
|
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(
|
|
239
|
-
/* @__PURE__ */ jsx(Box, { width: 20, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: formatTime(new Date(
|
|
240
|
-
/* @__PURE__ */ jsx(Box, { width: 12, children: /* @__PURE__ */ jsx(Text, { color: statusColor(
|
|
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(
|
|
243
|
-
] },
|
|
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(
|
|
334
|
-
const url = `${getApiUrl()}${
|
|
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
|
|
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(
|
|
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 ${
|
|
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 =
|
|
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
|
|
573
|
-
|
|
574
|
-
apiUrl =
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
738
|
-
return
|
|
822
|
+
const __dirname4 = path4.dirname(__filename3);
|
|
823
|
+
return path4.join(path4.resolve(__dirname4, "../../../.."), "packages/skills/src");
|
|
739
824
|
} catch {
|
|
740
|
-
return
|
|
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(
|
|
831
|
+
const manifest = await registerSkill(path4.resolve(source), outputDir);
|
|
747
832
|
console.log(`Generated adapter for skill: ${manifest.name}`);
|
|
748
|
-
console.log(`Output directory: ${
|
|
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
|
|
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(
|
|
770
|
-
const result = await apiFetch(`/tasks/${
|
|
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
|
|
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 ${
|
|
794
|
-
const result = await apiFetch(`/tasks/${
|
|
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
|
|
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,
|
|
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:
|
|
930
|
+
body: JSON.stringify({ name, path: path12 })
|
|
844
931
|
});
|
|
845
932
|
if (res.status === 409) {
|
|
846
|
-
const projects = await fetch(`${apiUrl}/projects?path=${encodeURIComponent(
|
|
847
|
-
const existing = projects.find((p) => p.path ===
|
|
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
|
|
878
|
-
if (
|
|
879
|
-
return
|
|
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
|
|
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 =
|
|
1204
|
+
const baseDir = path5.join("/tmp", `omega-bench-${String(Date.now())}`);
|
|
949
1205
|
await fs.mkdir(baseDir, { recursive: true });
|
|
950
|
-
for (const
|
|
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 =
|
|
1217
|
+
projectPath = path5.join(baseDir, task2.id);
|
|
962
1218
|
await fs.mkdir(projectPath, { recursive: true });
|
|
963
|
-
if (
|
|
964
|
-
await
|
|
1219
|
+
if (task2.setup) {
|
|
1220
|
+
await task2.setup(projectPath);
|
|
965
1221
|
}
|
|
966
1222
|
ensureGitRepo(projectPath);
|
|
967
|
-
const project = await ensureProject2(apiUrl, `${projectPrefix}-${
|
|
1223
|
+
const project = await ensureProject2(apiUrl, `${projectPrefix}-${task2.id}`, projectPath);
|
|
968
1224
|
projectId = project.id;
|
|
969
|
-
const harnessTask = await createTask(apiUrl, project.id,
|
|
970
|
-
description:
|
|
971
|
-
complexity:
|
|
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
|
|
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
|
|
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
|
|
1043
|
-
const mdFile =
|
|
1044
|
-
await fs2.writeFile(
|
|
1045
|
-
const
|
|
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(
|
|
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
|
|
1340
|
+
return jsonFile2;
|
|
1071
1341
|
}
|
|
1072
1342
|
function printSummary(report) {
|
|
1073
|
-
const
|
|
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(
|
|
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
|
|
1087
|
-
|
|
1088
|
-
|
|
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
|
|
1091
|
-
await
|
|
1092
|
-
await
|
|
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
|
|
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
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
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
|
|
1112
|
-
return
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
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
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
console.
|
|
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
|
-
|
|
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('
|
|
1638
|
+
if (code.includes('var ')) { console.error('var keyword found'); process.exit(1); }
|
|
1163
1639
|
console.log('lint ok');
|
|
1164
1640
|
`);
|
|
1165
|
-
|
|
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
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
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
|
-
|
|
1202
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
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
|
|
1239
|
-
import
|
|
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
|
|
1282
|
-
const instructionRaw = await
|
|
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
|
|
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
|
|
1292
|
-
const taskDirs = entries.filter((e) => e.isDirectory()).map((e) =>
|
|
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 ??
|
|
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 ??
|
|
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
|
|
1346
|
-
import
|
|
2109
|
+
import fs6 from "node:fs/promises";
|
|
2110
|
+
import path9 from "node:path";
|
|
1347
2111
|
async function findLatestReport(outputDir) {
|
|
1348
|
-
const entries = await
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(`
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
1502
|
-
console.log(`Created self-improve task ${
|
|
1503
|
-
console.log(`Run \`omega task run ${
|
|
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");
|