@castlemilk/omega 0.6.10 → 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 +1748 -61
- 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 +52 -10
- 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 +6 -1
- package/dist/server/node_modules/@omega/agent/dist/planner.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/planner.js +24 -5
- package/dist/server/node_modules/@omega/agent/dist/planner.js.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/prompt-context.d.ts +10 -0
- package/dist/server/node_modules/@omega/agent/dist/prompt-context.d.ts.map +1 -0
- package/dist/server/node_modules/@omega/agent/dist/prompt-context.js +138 -0
- package/dist/server/node_modules/@omega/agent/dist/prompt-context.js.map +1 -0
- 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 +4 -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 +31 -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/1249_fsm +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/2604_fsm +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/2608_fsm +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/2699 +0 -0
- package/dist/server/pglite-data/base/5/2701 +0 -0
- package/dist/server/pglite-data/base/5/2702 +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
|
};
|
|
@@ -817,6 +902,1607 @@ agentCmd.command("diffs").description("Show diffs for a task").argument("<id>",
|
|
|
817
902
|
console.log(JSON.stringify(diffs, null, 2));
|
|
818
903
|
});
|
|
819
904
|
|
|
905
|
+
// ../../apps/cli/src/commands/bench.ts
|
|
906
|
+
import { Command as Command8 } from "commander";
|
|
907
|
+
import fs8 from "node:fs/promises";
|
|
908
|
+
import path11 from "node:path";
|
|
909
|
+
import os from "node:os";
|
|
910
|
+
|
|
911
|
+
// ../bench/dist/api-client.js
|
|
912
|
+
var ApiError = class extends Error {
|
|
913
|
+
status;
|
|
914
|
+
constructor(status, message) {
|
|
915
|
+
super(message);
|
|
916
|
+
this.status = status;
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
async function apiFetch2(url, init) {
|
|
920
|
+
const res = await fetch(url, init);
|
|
921
|
+
if (!res.ok) {
|
|
922
|
+
throw new ApiError(res.status, `${init?.method ?? "GET"} ${url} -> ${String(res.status)}`);
|
|
923
|
+
}
|
|
924
|
+
return res.json();
|
|
925
|
+
}
|
|
926
|
+
async function ensureProject2(apiUrl, name, path12) {
|
|
927
|
+
const res = await fetch(`${apiUrl}/projects`, {
|
|
928
|
+
method: "POST",
|
|
929
|
+
headers: { "Content-Type": "application/json" },
|
|
930
|
+
body: JSON.stringify({ name, path: path12 })
|
|
931
|
+
});
|
|
932
|
+
if (res.status === 409) {
|
|
933
|
+
const projects = await fetch(`${apiUrl}/projects?path=${encodeURIComponent(path12)}`).then((r) => r.json());
|
|
934
|
+
const existing = projects.find((p) => p.path === path12);
|
|
935
|
+
if (existing)
|
|
936
|
+
return existing;
|
|
937
|
+
}
|
|
938
|
+
if (!res.ok)
|
|
939
|
+
throw new ApiError(res.status, `create project failed: ${String(res.status)}`);
|
|
940
|
+
return res.json();
|
|
941
|
+
}
|
|
942
|
+
async function createTask(apiUrl, projectId, title, options = {}) {
|
|
943
|
+
return apiFetch2(`${apiUrl}/tasks`, {
|
|
944
|
+
method: "POST",
|
|
945
|
+
headers: { "Content-Type": "application/json" },
|
|
946
|
+
body: JSON.stringify({
|
|
947
|
+
projectId,
|
|
948
|
+
title,
|
|
949
|
+
description: options.description,
|
|
950
|
+
complexity: options.complexity ?? "simple",
|
|
951
|
+
tags: options.tags ?? ["benchmark"]
|
|
952
|
+
})
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
async function runTask(apiUrl, taskId) {
|
|
956
|
+
await apiFetch2(`${apiUrl}/tasks/${taskId}/run`, { method: "POST" });
|
|
957
|
+
}
|
|
958
|
+
async function getTask(apiUrl, taskId) {
|
|
959
|
+
return apiFetch2(`${apiUrl}/tasks/${taskId}`);
|
|
960
|
+
}
|
|
961
|
+
async function waitForTask(apiUrl, taskId, timeoutMs = 12e4) {
|
|
962
|
+
const deadline = Date.now() + timeoutMs;
|
|
963
|
+
while (Date.now() < deadline) {
|
|
964
|
+
const task2 = await getTask(apiUrl, taskId);
|
|
965
|
+
if (task2.status === "done" || task2.status === "failed")
|
|
966
|
+
return task2;
|
|
967
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
968
|
+
}
|
|
969
|
+
return { status: "timeout", error: "Task did not finish in time" };
|
|
970
|
+
}
|
|
971
|
+
async function getAgentRun(apiUrl, taskId) {
|
|
972
|
+
try {
|
|
973
|
+
return await apiFetch2(`${apiUrl}/tasks/${taskId}/agent-run`);
|
|
974
|
+
} catch {
|
|
975
|
+
return void 0;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
async function getDiffs(apiUrl, taskId) {
|
|
979
|
+
try {
|
|
980
|
+
return await apiFetch2(`${apiUrl}/tasks/${taskId}/diffs`);
|
|
981
|
+
} catch {
|
|
982
|
+
return [];
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
async function getTraceFlow(apiUrl, taskId) {
|
|
986
|
+
try {
|
|
987
|
+
return await apiFetch2(`${apiUrl}/tasks/${taskId}/trace-flow`);
|
|
988
|
+
} catch {
|
|
989
|
+
return void 0;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
function countSpans(node) {
|
|
993
|
+
let count = 1;
|
|
994
|
+
if (Array.isArray(node.children)) {
|
|
995
|
+
for (const child of node.children) {
|
|
996
|
+
count += countSpans(child);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
return count;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// ../bench/dist/runner.js
|
|
1003
|
+
import fs from "node:fs/promises";
|
|
1004
|
+
import path5 from "node:path";
|
|
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
|
|
1167
|
+
function countAllSpans(traceFlow) {
|
|
1168
|
+
if (!traceFlow)
|
|
1169
|
+
return 0;
|
|
1170
|
+
return traceFlow.spans.reduce((acc, span) => acc + countSpans(span), 0);
|
|
1171
|
+
}
|
|
1172
|
+
function ensureGitRepo(repoPath) {
|
|
1173
|
+
try {
|
|
1174
|
+
execSync("git rev-parse --git-dir", { cwd: repoPath, stdio: "ignore" });
|
|
1175
|
+
return;
|
|
1176
|
+
} catch {
|
|
1177
|
+
}
|
|
1178
|
+
execSync("git init", { cwd: repoPath, stdio: "ignore" });
|
|
1179
|
+
execSync('git config user.email "bench@omega.local"', { cwd: repoPath, stdio: "ignore" });
|
|
1180
|
+
execSync('git config user.name "Omega Bench"', { cwd: repoPath, stdio: "ignore" });
|
|
1181
|
+
execSync("git add .", { cwd: repoPath, stdio: "ignore" });
|
|
1182
|
+
execSync('git commit -m "bench init"', { cwd: repoPath, stdio: "ignore" });
|
|
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
|
+
}
|
|
1191
|
+
async function runBenchmark(tasks, options) {
|
|
1192
|
+
const { apiUrl, suiteName, timeoutMs = 12e4, projectPrefix = "bench" } = options;
|
|
1193
|
+
const report = {
|
|
1194
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1195
|
+
suite: suiteName,
|
|
1196
|
+
total: tasks.length,
|
|
1197
|
+
passed: 0,
|
|
1198
|
+
failed: 0,
|
|
1199
|
+
timeouts: 0,
|
|
1200
|
+
totalDurationMs: 0,
|
|
1201
|
+
totalUsage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
1202
|
+
results: []
|
|
1203
|
+
};
|
|
1204
|
+
const baseDir = path5.join("/tmp", `omega-bench-${String(Date.now())}`);
|
|
1205
|
+
await fs.mkdir(baseDir, { recursive: true });
|
|
1206
|
+
for (const task2 of tasks) {
|
|
1207
|
+
const start = Date.now();
|
|
1208
|
+
let harnessTaskId = "";
|
|
1209
|
+
let status = "failed";
|
|
1210
|
+
let agentRun;
|
|
1211
|
+
let diffs = [];
|
|
1212
|
+
let traceFlow;
|
|
1213
|
+
let evaluation = { passed: false, message: "Task did not complete" };
|
|
1214
|
+
let projectId = "";
|
|
1215
|
+
let projectPath = "";
|
|
1216
|
+
try {
|
|
1217
|
+
projectPath = path5.join(baseDir, task2.id);
|
|
1218
|
+
await fs.mkdir(projectPath, { recursive: true });
|
|
1219
|
+
if (task2.setup) {
|
|
1220
|
+
await task2.setup(projectPath);
|
|
1221
|
+
}
|
|
1222
|
+
ensureGitRepo(projectPath);
|
|
1223
|
+
const project = await ensureProject2(apiUrl, `${projectPrefix}-${task2.id}`, projectPath);
|
|
1224
|
+
projectId = project.id;
|
|
1225
|
+
const harnessTask = await createTask(apiUrl, project.id, task2.title, {
|
|
1226
|
+
description: task2.description,
|
|
1227
|
+
complexity: task2.complexity ?? "simple",
|
|
1228
|
+
tags: ["benchmark", "agent"]
|
|
1229
|
+
});
|
|
1230
|
+
harnessTaskId = harnessTask.id;
|
|
1231
|
+
await runTask(apiUrl, harnessTask.id);
|
|
1232
|
+
const finished = await waitForTask(apiUrl, harnessTask.id, timeoutMs);
|
|
1233
|
+
status = finished.status === "timeout" ? "timeout" : finished.status;
|
|
1234
|
+
[agentRun, diffs, traceFlow] = await Promise.all([
|
|
1235
|
+
getAgentRun(apiUrl, harnessTask.id),
|
|
1236
|
+
getDiffs(apiUrl, harnessTask.id),
|
|
1237
|
+
getTraceFlow(apiUrl, harnessTask.id)
|
|
1238
|
+
]);
|
|
1239
|
+
evaluation = await task2.evaluate({
|
|
1240
|
+
apiUrl,
|
|
1241
|
+
taskId: harnessTask.id,
|
|
1242
|
+
projectPath,
|
|
1243
|
+
projectId,
|
|
1244
|
+
agentRun,
|
|
1245
|
+
diffs,
|
|
1246
|
+
traceFlow
|
|
1247
|
+
});
|
|
1248
|
+
} catch (err) {
|
|
1249
|
+
evaluation = {
|
|
1250
|
+
passed: false,
|
|
1251
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
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;
|
|
1260
|
+
const result = {
|
|
1261
|
+
task: task2,
|
|
1262
|
+
harnessTaskId,
|
|
1263
|
+
durationMs,
|
|
1264
|
+
status,
|
|
1265
|
+
evaluation,
|
|
1266
|
+
agentRun,
|
|
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)
|
|
1270
|
+
};
|
|
1271
|
+
if (status === "timeout")
|
|
1272
|
+
report.timeouts++;
|
|
1273
|
+
else if (evaluation.passed)
|
|
1274
|
+
report.passed++;
|
|
1275
|
+
else
|
|
1276
|
+
report.failed++;
|
|
1277
|
+
report.totalDurationMs += durationMs;
|
|
1278
|
+
report.totalUsage = sumUsage(report.totalUsage, usage);
|
|
1279
|
+
report.results.push(result);
|
|
1280
|
+
options.onProgress?.(result);
|
|
1281
|
+
}
|
|
1282
|
+
return report;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// ../bench/dist/report.js
|
|
1286
|
+
import fs2 from "node:fs/promises";
|
|
1287
|
+
import path6 from "node:path";
|
|
1288
|
+
function nowIso() {
|
|
1289
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1290
|
+
}
|
|
1291
|
+
function formatDuration(ms) {
|
|
1292
|
+
if (ms < 1e3)
|
|
1293
|
+
return `${String(Math.round(ms))}ms`;
|
|
1294
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
1295
|
+
}
|
|
1296
|
+
function resultLine(r, idx) {
|
|
1297
|
+
const symbol = r.evaluation.passed ? "\u2713" : "\u2717";
|
|
1298
|
+
const status = r.status === "timeout" ? "timeout" : r.status;
|
|
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}]` : "";
|
|
1302
|
+
const msg = r.evaluation.message ? ` \u2014 ${r.evaluation.message}` : "";
|
|
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)`;
|
|
1307
|
+
}
|
|
1308
|
+
async function writeReport(report, outputDir = ".omega/reports") {
|
|
1309
|
+
await fs2.mkdir(outputDir, { recursive: true });
|
|
1310
|
+
const ts = nowIso();
|
|
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;
|
|
1315
|
+
const md = [
|
|
1316
|
+
"# Omega Benchmark Report",
|
|
1317
|
+
"",
|
|
1318
|
+
`- Suite: ${report.suite}`,
|
|
1319
|
+
`- Timestamp: ${report.timestamp}`,
|
|
1320
|
+
`- Total: ${String(report.total)}`,
|
|
1321
|
+
`- Passed: ${String(report.passed)}`,
|
|
1322
|
+
`- Failed: ${String(report.failed)}`,
|
|
1323
|
+
`- Timeouts: ${String(report.timeouts)}`,
|
|
1324
|
+
`- Pass rate: ${String(passRate2)}%`,
|
|
1325
|
+
`- Total duration: ${formatDuration(report.totalDurationMs)}`,
|
|
1326
|
+
`- ${usageLine(report.totalUsage)}`,
|
|
1327
|
+
"",
|
|
1328
|
+
"## Results",
|
|
1329
|
+
"",
|
|
1330
|
+
...report.results.map((r, i) => resultLine(r, i)),
|
|
1331
|
+
"",
|
|
1332
|
+
"## Details",
|
|
1333
|
+
"",
|
|
1334
|
+
"```json",
|
|
1335
|
+
JSON.stringify(report, null, 2),
|
|
1336
|
+
"```",
|
|
1337
|
+
""
|
|
1338
|
+
].join("\n");
|
|
1339
|
+
await fs2.writeFile(mdFile, md, "utf-8");
|
|
1340
|
+
return jsonFile2;
|
|
1341
|
+
}
|
|
1342
|
+
function printSummary(report) {
|
|
1343
|
+
const passRate2 = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
|
|
1344
|
+
console.log(`
|
|
1345
|
+
Benchmark: ${report.suite}`);
|
|
1346
|
+
console.log(`Total: ${String(report.total)} | Passed: ${String(report.passed)} | Failed: ${String(report.failed)} | Timeouts: ${String(report.timeouts)}`);
|
|
1347
|
+
console.log(`Pass rate: ${String(passRate2)}%`);
|
|
1348
|
+
console.log(`Duration: ${formatDuration(report.totalDurationMs)}`);
|
|
1349
|
+
console.log(usageLine(report.totalUsage));
|
|
1350
|
+
for (const r of report.results) {
|
|
1351
|
+
console.log(resultLine(r, report.results.indexOf(r)));
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// ../bench/dist/suites/synthetic.js
|
|
1356
|
+
import fs4 from "node:fs/promises";
|
|
1357
|
+
|
|
1358
|
+
// ../bench/dist/suites/builder.js
|
|
1359
|
+
import fs3 from "node:fs/promises";
|
|
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");
|
|
1392
|
+
}
|
|
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
|
+
`);
|
|
1401
|
+
}
|
|
1402
|
+
function readValidation(ctx) {
|
|
1403
|
+
if (!ctx.agentRun?.validationSummary)
|
|
1404
|
+
return { allPassed: false };
|
|
1405
|
+
try {
|
|
1406
|
+
const summary = JSON.parse(ctx.agentRun.validationSummary);
|
|
1407
|
+
return { allPassed: Boolean(summary.allPassed), summary };
|
|
1408
|
+
} catch {
|
|
1409
|
+
return { allPassed: false };
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
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
|
+
};
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
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;
|
|
1440
|
+
}
|
|
1441
|
+
return { passed: true, message: "All checks passed" };
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
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');
|
|
1484
|
+
`);
|
|
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');
|
|
1499
|
+
`);
|
|
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');
|
|
1514
|
+
`);
|
|
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';
|
|
1526
|
+
`);
|
|
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');
|
|
1532
|
+
`);
|
|
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';
|
|
1637
|
+
const code = await fs.readFile('./src/index.js', 'utf-8');
|
|
1638
|
+
if (code.includes('var ')) { console.error('var keyword found'); process.exit(1); }
|
|
1639
|
+
console.log('lint ok');
|
|
1640
|
+
`);
|
|
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; }
|
|
1655
|
+
`);
|
|
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; }
|
|
1671
|
+
`);
|
|
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()); }
|
|
1685
|
+
`);
|
|
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; }
|
|
1705
|
+
`);
|
|
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'; }
|
|
1719
|
+
`);
|
|
1720
|
+
await codeFile(projectPath, "src/main.js", `import { greet } from './helper.js';
|
|
1721
|
+
export { greet };
|
|
1722
|
+
`);
|
|
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' }) }; }
|
|
1763
|
+
`);
|
|
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; }
|
|
1808
|
+
`);
|
|
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');
|
|
1813
|
+
`);
|
|
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; }
|
|
1825
|
+
`);
|
|
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');
|
|
1846
|
+
`);
|
|
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
|
+
];
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
// ../bench/dist/adapters/deepswe.js
|
|
2002
|
+
import fs5 from "node:fs/promises";
|
|
2003
|
+
import path8 from "node:path";
|
|
2004
|
+
import { execFile } from "node:child_process";
|
|
2005
|
+
import { promisify } from "node:util";
|
|
2006
|
+
var execFileAsync = promisify(execFile);
|
|
2007
|
+
function parseToml(raw) {
|
|
2008
|
+
const result = { task: {}, metadata: {} };
|
|
2009
|
+
let section;
|
|
2010
|
+
for (const line of raw.split("\n")) {
|
|
2011
|
+
const trimmed = line.trim();
|
|
2012
|
+
if (trimmed.startsWith("[")) {
|
|
2013
|
+
const name = trimmed.slice(1, -1).split(".")[0];
|
|
2014
|
+
if (name === "task" || name === "metadata")
|
|
2015
|
+
section = name;
|
|
2016
|
+
continue;
|
|
2017
|
+
}
|
|
2018
|
+
if (!section || trimmed.startsWith("#") || !trimmed.includes("="))
|
|
2019
|
+
continue;
|
|
2020
|
+
const eq = trimmed.indexOf("=");
|
|
2021
|
+
const key = trimmed.slice(0, eq).trim();
|
|
2022
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
2023
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
2024
|
+
value = value.slice(1, -1);
|
|
2025
|
+
}
|
|
2026
|
+
if (section === "task") {
|
|
2027
|
+
result.task ??= {};
|
|
2028
|
+
result.task[key] = value;
|
|
2029
|
+
} else {
|
|
2030
|
+
result.metadata ??= {};
|
|
2031
|
+
result.metadata[key] = value;
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
return result;
|
|
2035
|
+
}
|
|
2036
|
+
function mulberry32(seed) {
|
|
2037
|
+
return () => {
|
|
2038
|
+
let t = seed += 1831565813;
|
|
2039
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
2040
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
2041
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
async function readTask(taskDir) {
|
|
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");
|
|
2047
|
+
return { toml: parseToml(tomlRaw), instruction: instructionRaw };
|
|
2048
|
+
}
|
|
2049
|
+
async function cloneRepo(repoUrl, commit, targetPath) {
|
|
2050
|
+
await fs5.mkdir(path8.dirname(targetPath), { recursive: true });
|
|
2051
|
+
await execFileAsync("git", ["clone", repoUrl, targetPath], { timeout: 12e4 });
|
|
2052
|
+
await execFileAsync("git", ["-C", targetPath, "checkout", commit], { timeout: 6e4 });
|
|
2053
|
+
}
|
|
2054
|
+
async function loadDeepSWESuite(options) {
|
|
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));
|
|
2057
|
+
let loaded = [];
|
|
2058
|
+
for (const dir of taskDirs) {
|
|
2059
|
+
try {
|
|
2060
|
+
const data = await readTask(dir);
|
|
2061
|
+
loaded.push({ dir, ...data });
|
|
2062
|
+
} catch {
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
if (options.taskIds && options.taskIds.length > 0) {
|
|
2066
|
+
loaded = loaded.filter((t) => {
|
|
2067
|
+
const id = t.toml.metadata?.task_id ?? path8.basename(t.dir);
|
|
2068
|
+
return options.taskIds?.includes(id);
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
if (options.nTasks && options.nTasks > 0 && options.nTasks < loaded.length) {
|
|
2072
|
+
const seed = options.sampleSeed ?? 0;
|
|
2073
|
+
const rnd = mulberry32(seed);
|
|
2074
|
+
loaded = loaded.map((t) => ({ t, sort: rnd() })).sort((a, b) => a.sort - b.sort).slice(0, options.nTasks).map((x) => x.t);
|
|
2075
|
+
}
|
|
2076
|
+
const tasks = [];
|
|
2077
|
+
for (const { dir, toml, instruction } of loaded) {
|
|
2078
|
+
const id = toml.metadata?.task_id ?? path8.basename(dir);
|
|
2079
|
+
const title = toml.metadata?.display_title ?? toml.metadata?.original_title ?? id;
|
|
2080
|
+
const repo = toml.metadata?.repository_url;
|
|
2081
|
+
const commit = toml.metadata?.base_commit_hash;
|
|
2082
|
+
tasks.push({
|
|
2083
|
+
id: `deepswe-${id}`,
|
|
2084
|
+
name: id,
|
|
2085
|
+
title,
|
|
2086
|
+
description: instruction,
|
|
2087
|
+
complexity: "complex",
|
|
2088
|
+
setup: async (projectPath) => {
|
|
2089
|
+
if (!repo || !commit) {
|
|
2090
|
+
throw new Error(`DeepSWE task ${id} is missing repository_url or base_commit_hash`);
|
|
2091
|
+
}
|
|
2092
|
+
await cloneRepo(repo, commit, projectPath);
|
|
2093
|
+
},
|
|
2094
|
+
evaluate: (ctx) => {
|
|
2095
|
+
const hasDiff = ctx.diffs.length > 0 && ctx.diffs.some((d) => d.patch.trim().length > 0);
|
|
2096
|
+
const done = ctx.agentRun?.resultStatus === "done";
|
|
2097
|
+
const passed = done && hasDiff;
|
|
2098
|
+
return Promise.resolve({
|
|
2099
|
+
passed,
|
|
2100
|
+
message: passed ? `Agent finished and produced a diff (${String(ctx.diffs[0]?.patch.length ?? 0)} chars)` : `Agent status=${ctx.agentRun?.resultStatus ?? "unknown"}, diff=${hasDiff ? "yes" : "no"}`
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
return tasks;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
// ../bench/dist/optimise.js
|
|
2109
|
+
import fs6 from "node:fs/promises";
|
|
2110
|
+
import path9 from "node:path";
|
|
2111
|
+
async function findLatestReport(outputDir) {
|
|
2112
|
+
const entries = await fs6.readdir(outputDir).catch(() => []);
|
|
2113
|
+
const files = entries.filter((f) => f.startsWith("benchmark-") && f.endsWith(".json")).sort().reverse();
|
|
2114
|
+
if (files.length === 0)
|
|
2115
|
+
return void 0;
|
|
2116
|
+
const raw = await fs6.readFile(path9.join(outputDir, files[0]), "utf-8");
|
|
2117
|
+
return JSON.parse(raw);
|
|
2118
|
+
}
|
|
2119
|
+
function summariseTraceFlow(report, maxLines = 60) {
|
|
2120
|
+
const lines = report.split("\n");
|
|
2121
|
+
if (lines.length <= maxLines)
|
|
2122
|
+
return report;
|
|
2123
|
+
return lines.slice(0, maxLines).join("\n") + "\n... (truncated)";
|
|
2124
|
+
}
|
|
2125
|
+
function buildOptimisePrompt(report, failedResult, traceFlowText) {
|
|
2126
|
+
const passRate2 = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
|
|
2127
|
+
const failed = report.results.filter((r) => !r.evaluation.passed);
|
|
2128
|
+
const lines = [];
|
|
2129
|
+
lines.push("# Prompt optimisation task");
|
|
2130
|
+
lines.push("");
|
|
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
|
+
}
|
|
2142
|
+
lines.push("");
|
|
2143
|
+
lines.push("## Failed tasks");
|
|
2144
|
+
for (const r of failed.slice(0, 5)) {
|
|
2145
|
+
const analysis = r.failureAnalysis ?? classifyFailure(r, void 0);
|
|
2146
|
+
lines.push(`- ${r.task.name} [${analysis.category}]: ${r.evaluation.message ?? "no message"}`);
|
|
2147
|
+
}
|
|
2148
|
+
lines.push("");
|
|
2149
|
+
if (failedResult) {
|
|
2150
|
+
const analysis = failedResult.failureAnalysis ?? classifyFailure(failedResult, void 0);
|
|
2151
|
+
lines.push(`## Focus task: ${failedResult.task.name}`);
|
|
2152
|
+
lines.push(`Status: ${failedResult.status}`);
|
|
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
|
+
}
|
|
2161
|
+
lines.push("");
|
|
2162
|
+
}
|
|
2163
|
+
if (traceFlowText) {
|
|
2164
|
+
lines.push("## Trace flow snapshot");
|
|
2165
|
+
lines.push("```json");
|
|
2166
|
+
lines.push(summariseTraceFlow(traceFlowText));
|
|
2167
|
+
lines.push("```");
|
|
2168
|
+
lines.push("");
|
|
2169
|
+
}
|
|
2170
|
+
lines.push("## Instructions");
|
|
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.");
|
|
2172
|
+
lines.push("Rules:");
|
|
2173
|
+
lines.push("- Keep changes minimal and targeted.");
|
|
2174
|
+
lines.push("- Address the diagnosed root cause, not just the symptom.");
|
|
2175
|
+
lines.push("- Do not break existing tool signatures or system behaviour.");
|
|
2176
|
+
lines.push("- Run `pnpm lint` and `pnpm test` after edits.");
|
|
2177
|
+
lines.push("- Finish with a summary of what changed and why it should help.");
|
|
2178
|
+
return lines.join("\n");
|
|
2179
|
+
}
|
|
2180
|
+
async function loadOptimisationContext(apiUrl, outputDir) {
|
|
2181
|
+
const report = await findLatestReport(outputDir);
|
|
2182
|
+
if (!report)
|
|
2183
|
+
return void 0;
|
|
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);
|
|
2192
|
+
let traceFlowText;
|
|
2193
|
+
if (failedResult?.harnessTaskId) {
|
|
2194
|
+
const traceFlow = traceFlows.get(failedResult.harnessTaskId);
|
|
2195
|
+
if (traceFlow) {
|
|
2196
|
+
traceFlowText = JSON.stringify(traceFlow, null, 2);
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
return { report, failedResult, traceFlowText };
|
|
2200
|
+
}
|
|
2201
|
+
async function submitOptimiseTask(apiUrl, projectId, prompt) {
|
|
2202
|
+
const title = "Optimise agent prompts from benchmark trace analysis";
|
|
2203
|
+
const res = await fetch(`${apiUrl}/tasks`, {
|
|
2204
|
+
method: "POST",
|
|
2205
|
+
headers: { "Content-Type": "application/json" },
|
|
2206
|
+
body: JSON.stringify({
|
|
2207
|
+
projectId,
|
|
2208
|
+
title,
|
|
2209
|
+
description: prompt,
|
|
2210
|
+
complexity: "complex",
|
|
2211
|
+
tags: ["self-improve", "benchmark"]
|
|
2212
|
+
})
|
|
2213
|
+
});
|
|
2214
|
+
if (!res.ok)
|
|
2215
|
+
throw new Error(`submit optimise task failed: ${String(res.status)}`);
|
|
2216
|
+
return res.json();
|
|
2217
|
+
}
|
|
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
|
+
|
|
2305
|
+
// ../../apps/cli/src/commands/bench.ts
|
|
2306
|
+
async function waitForApi2(apiUrl, maxMs = 1e4) {
|
|
2307
|
+
const deadline = Date.now() + maxMs;
|
|
2308
|
+
while (Date.now() < deadline) {
|
|
2309
|
+
try {
|
|
2310
|
+
const res = await fetch(`${apiUrl}/projects`);
|
|
2311
|
+
if (res.ok) return;
|
|
2312
|
+
} catch {
|
|
2313
|
+
}
|
|
2314
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
2315
|
+
}
|
|
2316
|
+
throw new Error(`Harness API is not ready at ${apiUrl}. Run \`omega ui\` or \`omega console\` first.`);
|
|
2317
|
+
}
|
|
2318
|
+
function currentProject(apiUrl) {
|
|
2319
|
+
const cwd = process.cwd();
|
|
2320
|
+
const name = path11.basename(cwd);
|
|
2321
|
+
return ensureProject2(apiUrl, `bench-${name}`, cwd);
|
|
2322
|
+
}
|
|
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) => {
|
|
2324
|
+
const apiUrl = getApiUrl();
|
|
2325
|
+
await waitForApi2(apiUrl);
|
|
2326
|
+
const timeoutMs = Number(opts.timeout);
|
|
2327
|
+
let tasks;
|
|
2328
|
+
let suiteName;
|
|
2329
|
+
if (opts.suite === "deep-swe") {
|
|
2330
|
+
if (!opts.path) {
|
|
2331
|
+
throw new Error("--path is required for the deep-swe suite");
|
|
2332
|
+
}
|
|
2333
|
+
tasks = await loadDeepSWESuite({
|
|
2334
|
+
tasksDir: opts.path,
|
|
2335
|
+
nTasks: opts.nTasks,
|
|
2336
|
+
sampleSeed: opts.sampleSeed
|
|
2337
|
+
});
|
|
2338
|
+
suiteName = "deep-swe";
|
|
2339
|
+
} else if (opts.suite === "synthetic") {
|
|
2340
|
+
tasks = syntheticSuite();
|
|
2341
|
+
if (opts.nTasks !== void 0 && opts.nTasks > 0) {
|
|
2342
|
+
tasks = tasks.slice(0, opts.nTasks);
|
|
2343
|
+
}
|
|
2344
|
+
suiteName = "synthetic";
|
|
2345
|
+
} else {
|
|
2346
|
+
throw new Error(`Unknown suite: ${opts.suite}`);
|
|
2347
|
+
}
|
|
2348
|
+
if (tasks.length === 0) {
|
|
2349
|
+
console.log("No benchmark tasks to run.");
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
console.log(`Running ${String(tasks.length)} benchmark tasks against ${apiUrl}`);
|
|
2353
|
+
const report = await runBenchmark(tasks, {
|
|
2354
|
+
apiUrl,
|
|
2355
|
+
suiteName,
|
|
2356
|
+
timeoutMs,
|
|
2357
|
+
onProgress: (result) => {
|
|
2358
|
+
const symbol = result.evaluation.passed ? "\u2713" : "\u2717";
|
|
2359
|
+
console.log(`${symbol} ${result.task.name} [${result.status}] ${String(result.durationMs)}ms`);
|
|
2360
|
+
}
|
|
2361
|
+
});
|
|
2362
|
+
const reportFile = await writeReport(report, opts.outputDir);
|
|
2363
|
+
printSummary(report);
|
|
2364
|
+
console.log(`
|
|
2365
|
+
Report written to ${reportFile}`);
|
|
2366
|
+
});
|
|
2367
|
+
var optimiseCmd = new Command8("optimise").description("Create a self-improve task from the latest benchmark report").option("--output-dir <dir>", "report output directory", ".omega/reports").action(async (opts) => {
|
|
2368
|
+
const apiUrl = getApiUrl();
|
|
2369
|
+
await waitForApi2(apiUrl);
|
|
2370
|
+
const context = await loadOptimisationContext(apiUrl, opts.outputDir);
|
|
2371
|
+
if (!context) {
|
|
2372
|
+
console.log("No benchmark report found. Run `omega bench run` first.");
|
|
2373
|
+
return;
|
|
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
|
+
}
|
|
2395
|
+
const project = await currentProject(apiUrl);
|
|
2396
|
+
const prompt = buildOptimisePrompt(
|
|
2397
|
+
context.report,
|
|
2398
|
+
context.failedResult,
|
|
2399
|
+
context.traceFlowText
|
|
2400
|
+
);
|
|
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}`);
|
|
2503
|
+
});
|
|
2504
|
+
var benchCmd = new Command8("bench").description("Run benchmarks and optimise prompts").addCommand(runCmd).addCommand(abCmd).addCommand(optimiseCmd);
|
|
2505
|
+
|
|
820
2506
|
// ../../apps/cli/src/index.ts
|
|
821
2507
|
program2.name("harness").description("Omega harness CLI").version("0.1.0").option("--api <url>", "API base URL", "http://localhost:4000");
|
|
822
2508
|
program2.addCommand(projectCmd);
|
|
@@ -825,4 +2511,5 @@ program2.addCommand(uiCmd);
|
|
|
825
2511
|
program2.addCommand(consoleCmd);
|
|
826
2512
|
program2.addCommand(skillCmd);
|
|
827
2513
|
program2.addCommand(agentCmd);
|
|
2514
|
+
program2.addCommand(benchCmd);
|
|
828
2515
|
program2.parse();
|