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