@castlemilk/omega 0.6.13 → 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +630 -1282
- 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 +2 -3
- 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 +2 -76
- package/dist/server/dist/routes/metrics.js.map +1 -1
- package/dist/server/dist/routes/reports.d.ts +3 -0
- package/dist/server/dist/routes/reports.d.ts.map +1 -0
- package/dist/server/dist/routes/reports.js +44 -0
- package/dist/server/dist/routes/reports.js.map +1 -0
- package/dist/server/dist/routes/tasks.d.ts.map +1 -1
- package/dist/server/dist/routes/tasks.js +0 -4
- package/dist/server/dist/routes/tasks.js.map +1 -1
- package/dist/server/node_modules/@grpc/proto-loader/build/bin/proto-loader-gen-types.js +0 -0
- package/dist/server/node_modules/@omega/agent/dist/executor.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/executor.js +10 -83
- 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 -1
- 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 -1
- package/dist/server/node_modules/@omega/agent/dist/index.js.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/planner.d.ts +0 -5
- package/dist/server/node_modules/@omega/agent/dist/planner.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/planner.js +5 -31
- 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.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/prompt-versioning.js +2 -2
- package/dist/server/node_modules/@omega/agent/dist/prompt-versioning.js.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/prompts.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/agent/dist/prompts.js +16 -9
- package/dist/server/node_modules/@omega/agent/dist/prompts.js.map +1 -1
- package/dist/server/node_modules/@omega/agent/package.json +0 -4
- package/dist/server/node_modules/@omega/core/dist/provider.d.ts +2 -12
- package/dist/server/node_modules/@omega/core/dist/provider.d.ts.map +1 -1
- 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 +3 -6
- package/dist/server/node_modules/@omega/db/generated/client/index-browser.js +0 -3
- package/dist/server/node_modules/@omega/db/generated/client/index.d.ts +0 -223
- package/dist/server/node_modules/@omega/db/generated/client/index.js +3 -6
- 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 +0 -3
- package/dist/server/node_modules/@omega/db/generated/client/wasm.js +3 -6
- 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 +1 -16
- 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 +1 -14
- package/dist/server/node_modules/@omega/providers/dist/gemini.js.map +1 -1
- package/dist/server/node_modules/@omega/providers/dist/kimi.d.ts +4 -3
- package/dist/server/node_modules/@omega/providers/dist/kimi.d.ts.map +1 -1
- package/dist/server/node_modules/@omega/providers/dist/kimi.js +4 -6
- 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 +1 -13
- 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 +5 -28
- package/dist/server/node_modules/@omega/providers/dist/openai.js.map +1 -1
- package/dist/server/node_modules/@omega/providers/dist/providers.test.js +9 -13
- package/dist/server/node_modules/@omega/providers/dist/providers.test.js.map +1 -1
- package/dist/server/node_modules/mime/cli.js +0 -0
- package/dist/server/node_modules/mime/src/build.js +0 -0
- package/dist/server/node_modules/wrap-ansi/index.js +0 -0
- package/dist/server/pglite-data/base/5/1247 +0 -0
- package/dist/server/pglite-data/base/5/1249 +0 -0
- package/dist/server/pglite-data/base/5/1259 +0 -0
- package/dist/server/pglite-data/base/5/16384 +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/16510 +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/2610 +0 -0
- package/dist/server/pglite-data/base/5/2620 +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/pg_internal.init +0 -0
- package/dist/server/pglite-data/global/pg_control +0 -0
- package/dist/server/pglite-data/global/pg_internal.init +0 -0
- package/dist/server/pglite-data/pg_wal/000000010000000000000001 +0 -0
- package/dist/server/pglite-data/pg_xact/0000 +0 -0
- package/dist/server/pglite-data/postmaster.pid +1 -1
- package/dist/server/src/app.ts +2 -0
- package/dist/server/src/lib/run-task.ts +2 -3
- package/dist/server/src/routes/metrics.ts +2 -81
- package/dist/server/src/routes/reports.ts +54 -0
- package/dist/server/src/routes/tasks.ts +2 -6
- package/dist/web/dist/assets/index-Ce9D9lg5.css +1 -0
- package/dist/web/dist/assets/index-D-wO1mM7.js +40 -0
- package/dist/web/dist/index.html +2 -2
- package/package.json +13 -14
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 task of data) {
|
|
70
|
+
current[task.id] = task;
|
|
71
|
+
const prev = previousTasks.current[task.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: "${task.title}"`, "info");
|
|
74
|
+
} else if (prev.status !== task.status) {
|
|
75
|
+
if (task.status === "in_progress") {
|
|
76
|
+
const provider = task.provider ?? "unknown";
|
|
77
|
+
const model = task.model ?? "unknown";
|
|
78
|
+
addLog(`LLM picked up "${task.title}" \u2192 ${provider}/${model}`, "info");
|
|
79
|
+
} else if (task.status === "done") {
|
|
80
|
+
addLog(`Completed: "${task.title}"`, "success");
|
|
81
|
+
} else if (task.status === "failed") {
|
|
82
|
+
addLog(`Failed: "${task.title}"${task.error ? ` \u2014 ${task.error}` : ""}`, "error");
|
|
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((task) => {
|
|
235
|
+
const providerText = task.provider ? `${task.provider}/${task.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(task.status), children: statusSymbol(task.status) }),
|
|
239
|
+
/* @__PURE__ */ jsx(Box, { width: 20, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: formatTime(new Date(task.updatedAt)) }) }),
|
|
240
|
+
/* @__PURE__ */ jsx(Box, { width: 12, children: /* @__PURE__ */ jsx(Text, { color: statusColor(task.status), bold: true, children: task.status.toUpperCase() }) }),
|
|
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(task.title, titleMax) }) })
|
|
243
|
+
] }, task.id);
|
|
244
244
|
})
|
|
245
245
|
]
|
|
246
246
|
}
|
|
@@ -318,76 +318,6 @@ 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
|
-
|
|
391
321
|
// ../../apps/cli/src/index.ts
|
|
392
322
|
import { program as program2 } from "commander";
|
|
393
323
|
|
|
@@ -398,10 +328,10 @@ import { Command } from "commander";
|
|
|
398
328
|
import { program } from "commander";
|
|
399
329
|
function getApiUrl() {
|
|
400
330
|
const opts = program.opts();
|
|
401
|
-
return opts.api ??
|
|
331
|
+
return opts.api ?? "http://localhost:4000";
|
|
402
332
|
}
|
|
403
|
-
async function apiFetch(
|
|
404
|
-
const url = `${getApiUrl()}${
|
|
333
|
+
async function apiFetch(path11, init) {
|
|
334
|
+
const url = `${getApiUrl()}${path11}`;
|
|
405
335
|
const res = await fetch(url, init);
|
|
406
336
|
const data = await res.json().catch(() => ({}));
|
|
407
337
|
if (!res.ok) {
|
|
@@ -516,7 +446,7 @@ var taskFeedCmd = new Command2("feed").description("Feed a task into the harness
|
|
|
516
446
|
// ../../apps/cli/src/commands/task.ts
|
|
517
447
|
var taskCmd = new Command3("task").description("Manage tasks");
|
|
518
448
|
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) => {
|
|
519
|
-
const
|
|
449
|
+
const task = await apiFetch("/tasks", {
|
|
520
450
|
method: "POST",
|
|
521
451
|
headers: { "Content-Type": "application/json" },
|
|
522
452
|
body: JSON.stringify({
|
|
@@ -527,7 +457,7 @@ taskCmd.command("create").description("Create a task").requiredOption("--project
|
|
|
527
457
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : []
|
|
528
458
|
})
|
|
529
459
|
});
|
|
530
|
-
console.log(JSON.stringify(
|
|
460
|
+
console.log(JSON.stringify(task, null, 2));
|
|
531
461
|
});
|
|
532
462
|
taskCmd.command("list").description("List tasks").option("--project <id>", "filter by project id").action(async (opts) => {
|
|
533
463
|
const query = opts.project ? `?projectId=${opts.project}` : "";
|
|
@@ -542,17 +472,14 @@ taskCmd.addCommand(taskFeedCmd);
|
|
|
542
472
|
|
|
543
473
|
// ../../apps/cli/src/commands/ui.ts
|
|
544
474
|
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
|
|
551
475
|
import { spawn } from "child_process";
|
|
552
476
|
import { existsSync as existsSync2 } from "fs";
|
|
553
477
|
import net from "net";
|
|
554
478
|
import path2 from "path";
|
|
555
479
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
480
|
+
import open from "open";
|
|
481
|
+
import { render } from "ink";
|
|
482
|
+
import React2 from "react";
|
|
556
483
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
557
484
|
var __dirname2 = path2.dirname(__filename2);
|
|
558
485
|
var DEFAULT_PORT = 4e3;
|
|
@@ -562,7 +489,7 @@ var MAX_GRPC_PORT = 50061;
|
|
|
562
489
|
function findAvailablePort(preferred = DEFAULT_PORT, max = MAX_PORT) {
|
|
563
490
|
return new Promise((resolve, reject) => {
|
|
564
491
|
if (preferred > max) {
|
|
565
|
-
reject(new Error(`No available ports between ${
|
|
492
|
+
reject(new Error(`No available ports between ${DEFAULT_PORT.toString()} and ${max.toString()}`));
|
|
566
493
|
return;
|
|
567
494
|
}
|
|
568
495
|
const tester = net.createServer();
|
|
@@ -584,11 +511,11 @@ function findAvailablePort(preferred = DEFAULT_PORT, max = MAX_PORT) {
|
|
|
584
511
|
});
|
|
585
512
|
}
|
|
586
513
|
function startBundledServer(env) {
|
|
587
|
-
const realServerPath = path2.resolve(__dirname2, "
|
|
514
|
+
const realServerPath = path2.resolve(__dirname2, "server/dist/index.js");
|
|
588
515
|
if (existsSync2(realServerPath)) {
|
|
589
516
|
return spawn(process.execPath, [realServerPath], { stdio: "inherit", env });
|
|
590
517
|
}
|
|
591
|
-
const legacyServerPath = path2.resolve(__dirname2, "
|
|
518
|
+
const legacyServerPath = path2.resolve(__dirname2, "server.js");
|
|
592
519
|
if (existsSync2(legacyServerPath)) {
|
|
593
520
|
return spawn(process.execPath, [legacyServerPath], { stdio: "inherit", env });
|
|
594
521
|
}
|
|
@@ -610,29 +537,9 @@ async function waitForApi(apiUrl, maxMs = 3e4) {
|
|
|
610
537
|
}
|
|
611
538
|
throw new Error("Server did not become ready in time");
|
|
612
539
|
}
|
|
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
|
|
633
540
|
async function ensureProject(apiUrl) {
|
|
634
541
|
const cwd = process.cwd();
|
|
635
|
-
const name =
|
|
542
|
+
const name = path2.basename(cwd);
|
|
636
543
|
try {
|
|
637
544
|
const listRes = await fetch(`${apiUrl}/projects`);
|
|
638
545
|
const projects = await listRes.json();
|
|
@@ -658,16 +565,25 @@ async function ensureProject(apiUrl) {
|
|
|
658
565
|
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) => {
|
|
659
566
|
const defaultApiUrl = process.env.HARNESS_API_URL ?? `http://localhost:${DEFAULT_PORT.toString()}`;
|
|
660
567
|
let server;
|
|
661
|
-
let serverStarted = false;
|
|
662
568
|
const alreadyRunning = await isApiReady(defaultApiUrl);
|
|
663
569
|
const useTui = options.tui;
|
|
664
570
|
let apiUrl = defaultApiUrl;
|
|
665
571
|
if (!alreadyRunning) {
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
apiUrl =
|
|
669
|
-
serverStarted = true;
|
|
572
|
+
const port = await findAvailablePort();
|
|
573
|
+
const grpcPort = await findAvailablePort(DEFAULT_GRPC_PORT, MAX_GRPC_PORT);
|
|
574
|
+
apiUrl = `http://localhost:${port.toString()}`;
|
|
670
575
|
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
|
+
});
|
|
671
587
|
server.on("exit", (code) => {
|
|
672
588
|
process.exit(code ?? 0);
|
|
673
589
|
});
|
|
@@ -691,7 +607,7 @@ var uiCmd = new Command4("ui").description("Open the harness web UI").option("--
|
|
|
691
607
|
const { TuiApp: TuiApp2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
|
|
692
608
|
const { waitUntilExit } = render(React2.createElement(TuiApp2));
|
|
693
609
|
await waitUntilExit();
|
|
694
|
-
if (
|
|
610
|
+
if (server && !alreadyRunning) {
|
|
695
611
|
server.kill();
|
|
696
612
|
}
|
|
697
613
|
}
|
|
@@ -712,7 +628,7 @@ var consoleCmd = new Command5("console").description("Open the harness TUI conso
|
|
|
712
628
|
|
|
713
629
|
// ../../apps/cli/src/commands/skill.ts
|
|
714
630
|
import { Command as Command6 } from "commander";
|
|
715
|
-
import
|
|
631
|
+
import path3 from "path";
|
|
716
632
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
717
633
|
|
|
718
634
|
// ../skills/src/parse.ts
|
|
@@ -779,8 +695,7 @@ export class ${className} {
|
|
|
779
695
|
|
|
780
696
|
async execute(args: Record<string, unknown>): Promise<string> {
|
|
781
697
|
const prompt = ${instructionsLiteral} + '\\n\\nArgs:\\n' + JSON.stringify(args, null, 2);
|
|
782
|
-
|
|
783
|
-
return response.content;
|
|
698
|
+
return this.provider.send(prompt, { system: ${descriptionLiteral} });
|
|
784
699
|
}
|
|
785
700
|
}
|
|
786
701
|
`;
|
|
@@ -815,22 +730,22 @@ async function registerSkill(sourcePath, outputDir) {
|
|
|
815
730
|
// ../../apps/cli/src/commands/skill.ts
|
|
816
731
|
function getOutputDir() {
|
|
817
732
|
if (process.env.OMEGA_HARNESS_ROOT) {
|
|
818
|
-
return
|
|
733
|
+
return path3.join(path3.resolve(process.env.OMEGA_HARNESS_ROOT), "packages/skills/src");
|
|
819
734
|
}
|
|
820
735
|
try {
|
|
821
736
|
const __filename3 = fileURLToPath3(import.meta.url);
|
|
822
|
-
const __dirname4 =
|
|
823
|
-
return
|
|
737
|
+
const __dirname4 = path3.dirname(__filename3);
|
|
738
|
+
return path3.join(path3.resolve(__dirname4, "../../../.."), "packages/skills/src");
|
|
824
739
|
} catch {
|
|
825
|
-
return
|
|
740
|
+
return path3.join(process.cwd(), "harness-skills");
|
|
826
741
|
}
|
|
827
742
|
}
|
|
828
743
|
var skillCmd = new Command6("skill").description("Manage skill artifacts");
|
|
829
744
|
skillCmd.command("generate").description("Generate a harness adapter from a SKILL.md file").argument("<source>", "path to SKILL.md").action(async (source) => {
|
|
830
745
|
const outputDir = getOutputDir();
|
|
831
|
-
const manifest = await registerSkill(
|
|
746
|
+
const manifest = await registerSkill(path3.resolve(source), outputDir);
|
|
832
747
|
console.log(`Generated adapter for skill: ${manifest.name}`);
|
|
833
|
-
console.log(`Output directory: ${
|
|
748
|
+
console.log(`Output directory: ${path3.join(outputDir, "generated")}`);
|
|
834
749
|
});
|
|
835
750
|
|
|
836
751
|
// ../../apps/cli/src/commands/agent.ts
|
|
@@ -840,7 +755,7 @@ agentCmd.command("run").description("Run a single agent task").requiredOption("-
|
|
|
840
755
|
async (opts) => {
|
|
841
756
|
const tags = ["agent"];
|
|
842
757
|
if (opts.autoPublish) tags.push("publish");
|
|
843
|
-
const
|
|
758
|
+
const task = await apiFetch("/tasks", {
|
|
844
759
|
method: "POST",
|
|
845
760
|
headers: { "Content-Type": "application/json" },
|
|
846
761
|
body: JSON.stringify({
|
|
@@ -851,8 +766,8 @@ agentCmd.command("run").description("Run a single agent task").requiredOption("-
|
|
|
851
766
|
tags
|
|
852
767
|
})
|
|
853
768
|
});
|
|
854
|
-
console.log(JSON.stringify(
|
|
855
|
-
const result = await apiFetch(`/tasks/${
|
|
769
|
+
console.log(JSON.stringify(task, null, 2));
|
|
770
|
+
const result = await apiFetch(`/tasks/${task.id}/run`, { method: "POST" });
|
|
856
771
|
console.log(JSON.stringify(result, null, 2));
|
|
857
772
|
}
|
|
858
773
|
);
|
|
@@ -864,7 +779,7 @@ agentCmd.command("loop").description("Continuously run agent tasks").requiredOpt
|
|
|
864
779
|
const title = opts.prompt ?? "Improve the Omega harness: fix a TODO, add a test, refactor a function, or improve documentation";
|
|
865
780
|
const tags = ["agent", "self-improve"];
|
|
866
781
|
if (opts.autoPublish) tags.push("publish");
|
|
867
|
-
const
|
|
782
|
+
const task = await apiFetch("/tasks", {
|
|
868
783
|
method: "POST",
|
|
869
784
|
headers: { "Content-Type": "application/json" },
|
|
870
785
|
body: JSON.stringify({
|
|
@@ -875,8 +790,8 @@ agentCmd.command("loop").description("Continuously run agent tasks").requiredOpt
|
|
|
875
790
|
tags
|
|
876
791
|
})
|
|
877
792
|
});
|
|
878
|
-
console.log(`Created task ${
|
|
879
|
-
const result = await apiFetch(`/tasks/${
|
|
793
|
+
console.log(`Created task ${task.id}`);
|
|
794
|
+
const result = await apiFetch(`/tasks/${task.id}/run`, { method: "POST" });
|
|
880
795
|
console.log(JSON.stringify(result, null, 2));
|
|
881
796
|
return result;
|
|
882
797
|
};
|
|
@@ -905,8 +820,7 @@ agentCmd.command("diffs").description("Show diffs for a task").argument("<id>",
|
|
|
905
820
|
// ../../apps/cli/src/commands/bench.ts
|
|
906
821
|
import { Command as Command8 } from "commander";
|
|
907
822
|
import fs8 from "node:fs/promises";
|
|
908
|
-
import
|
|
909
|
-
import os from "node:os";
|
|
823
|
+
import path10 from "node:path";
|
|
910
824
|
|
|
911
825
|
// ../bench/dist/api-client.js
|
|
912
826
|
var ApiError = class extends Error {
|
|
@@ -923,15 +837,15 @@ async function apiFetch2(url, init) {
|
|
|
923
837
|
}
|
|
924
838
|
return res.json();
|
|
925
839
|
}
|
|
926
|
-
async function ensureProject2(apiUrl, name,
|
|
840
|
+
async function ensureProject2(apiUrl, name, path11) {
|
|
927
841
|
const res = await fetch(`${apiUrl}/projects`, {
|
|
928
842
|
method: "POST",
|
|
929
843
|
headers: { "Content-Type": "application/json" },
|
|
930
|
-
body: JSON.stringify({ name, path:
|
|
844
|
+
body: JSON.stringify({ name, path: path11 })
|
|
931
845
|
});
|
|
932
846
|
if (res.status === 409) {
|
|
933
|
-
const projects = await fetch(`${apiUrl}/projects?path=${encodeURIComponent(
|
|
934
|
-
const existing = projects.find((p) => p.path ===
|
|
847
|
+
const projects = await fetch(`${apiUrl}/projects?path=${encodeURIComponent(path11)}`).then((r) => r.json());
|
|
848
|
+
const existing = projects.find((p) => p.path === path11);
|
|
935
849
|
if (existing)
|
|
936
850
|
return existing;
|
|
937
851
|
}
|
|
@@ -948,9 +862,7 @@ async function createTask(apiUrl, projectId, title, options = {}) {
|
|
|
948
862
|
title,
|
|
949
863
|
description: options.description,
|
|
950
864
|
complexity: options.complexity ?? "simple",
|
|
951
|
-
tags: options.tags ?? ["benchmark"]
|
|
952
|
-
provider: options.provider,
|
|
953
|
-
model: options.model
|
|
865
|
+
tags: options.tags ?? ["benchmark"]
|
|
954
866
|
})
|
|
955
867
|
});
|
|
956
868
|
}
|
|
@@ -963,9 +875,9 @@ async function getTask(apiUrl, taskId) {
|
|
|
963
875
|
async function waitForTask(apiUrl, taskId, timeoutMs = 12e4) {
|
|
964
876
|
const deadline = Date.now() + timeoutMs;
|
|
965
877
|
while (Date.now() < deadline) {
|
|
966
|
-
const
|
|
967
|
-
if (
|
|
968
|
-
return
|
|
878
|
+
const task = await getTask(apiUrl, taskId);
|
|
879
|
+
if (task.status === "done" || task.status === "failed")
|
|
880
|
+
return task;
|
|
969
881
|
await new Promise((r) => setTimeout(r, 300));
|
|
970
882
|
}
|
|
971
883
|
return { status: "timeout", error: "Task did not finish in time" };
|
|
@@ -1003,169 +915,8 @@ function countSpans(node) {
|
|
|
1003
915
|
|
|
1004
916
|
// ../bench/dist/runner.js
|
|
1005
917
|
import fs from "node:fs/promises";
|
|
1006
|
-
import
|
|
918
|
+
import path4 from "node:path";
|
|
1007
919
|
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
|
|
1169
920
|
function countAllSpans(traceFlow) {
|
|
1170
921
|
if (!traceFlow)
|
|
1171
922
|
return 0;
|
|
@@ -1183,15 +934,8 @@ function ensureGitRepo(repoPath) {
|
|
|
1183
934
|
execSync("git add .", { cwd: repoPath, stdio: "ignore" });
|
|
1184
935
|
execSync('git commit -m "bench init"', { cwd: repoPath, stdio: "ignore" });
|
|
1185
936
|
}
|
|
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
|
-
}
|
|
1193
937
|
async function runBenchmark(tasks, options) {
|
|
1194
|
-
const { apiUrl, suiteName, timeoutMs = 12e4, projectPrefix = "bench" } = options;
|
|
938
|
+
const { apiUrl, suiteName, timeoutMs = 12e4, projectPrefix = "bench", provider, model } = options;
|
|
1195
939
|
const report = {
|
|
1196
940
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1197
941
|
suite: suiteName,
|
|
@@ -1200,12 +944,11 @@ async function runBenchmark(tasks, options) {
|
|
|
1200
944
|
failed: 0,
|
|
1201
945
|
timeouts: 0,
|
|
1202
946
|
totalDurationMs: 0,
|
|
1203
|
-
totalUsage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
1204
947
|
results: []
|
|
1205
948
|
};
|
|
1206
|
-
const baseDir =
|
|
949
|
+
const baseDir = path4.join("/tmp", `omega-bench-${String(Date.now())}`);
|
|
1207
950
|
await fs.mkdir(baseDir, { recursive: true });
|
|
1208
|
-
for (const
|
|
951
|
+
for (const task of tasks) {
|
|
1209
952
|
const start = Date.now();
|
|
1210
953
|
let harnessTaskId = "";
|
|
1211
954
|
let status = "failed";
|
|
@@ -1216,20 +959,27 @@ async function runBenchmark(tasks, options) {
|
|
|
1216
959
|
let projectId = "";
|
|
1217
960
|
let projectPath = "";
|
|
1218
961
|
try {
|
|
1219
|
-
projectPath =
|
|
962
|
+
projectPath = path4.join(baseDir, task.id);
|
|
1220
963
|
await fs.mkdir(projectPath, { recursive: true });
|
|
1221
|
-
if (
|
|
1222
|
-
await
|
|
964
|
+
if (task.setup) {
|
|
965
|
+
await task.setup(projectPath);
|
|
1223
966
|
}
|
|
1224
967
|
ensureGitRepo(projectPath);
|
|
1225
|
-
const project = await ensureProject2(apiUrl, `${projectPrefix}-${
|
|
968
|
+
const project = await ensureProject2(apiUrl, `${projectPrefix}-${task.id}`, projectPath);
|
|
1226
969
|
projectId = project.id;
|
|
1227
|
-
const harnessTask = await createTask(apiUrl, project.id,
|
|
1228
|
-
description:
|
|
1229
|
-
complexity:
|
|
970
|
+
const harnessTask = await createTask(apiUrl, project.id, task.title, {
|
|
971
|
+
description: task.description,
|
|
972
|
+
complexity: task.complexity ?? "simple",
|
|
1230
973
|
tags: ["benchmark", "agent"]
|
|
1231
974
|
});
|
|
1232
975
|
harnessTaskId = harnessTask.id;
|
|
976
|
+
if (provider || model) {
|
|
977
|
+
await fetch(`${apiUrl}/tasks/${harnessTaskId}`, {
|
|
978
|
+
method: "PATCH",
|
|
979
|
+
headers: { "Content-Type": "application/json" },
|
|
980
|
+
body: JSON.stringify({ provider, model })
|
|
981
|
+
});
|
|
982
|
+
}
|
|
1233
983
|
await runTask(apiUrl, harnessTask.id);
|
|
1234
984
|
const finished = await waitForTask(apiUrl, harnessTask.id, timeoutMs);
|
|
1235
985
|
status = finished.status === "timeout" ? "timeout" : finished.status;
|
|
@@ -1238,7 +988,7 @@ async function runBenchmark(tasks, options) {
|
|
|
1238
988
|
getDiffs(apiUrl, harnessTask.id),
|
|
1239
989
|
getTraceFlow(apiUrl, harnessTask.id)
|
|
1240
990
|
]);
|
|
1241
|
-
evaluation = await
|
|
991
|
+
evaluation = await task.evaluate({
|
|
1242
992
|
apiUrl,
|
|
1243
993
|
taskId: harnessTask.id,
|
|
1244
994
|
projectPath,
|
|
@@ -1254,21 +1004,14 @@ async function runBenchmark(tasks, options) {
|
|
|
1254
1004
|
};
|
|
1255
1005
|
}
|
|
1256
1006
|
const durationMs = Date.now() - start;
|
|
1257
|
-
const usage = agentRun ? {
|
|
1258
|
-
promptTokens: agentRun.promptTokens,
|
|
1259
|
-
completionTokens: agentRun.completionTokens,
|
|
1260
|
-
totalTokens: agentRun.totalTokens
|
|
1261
|
-
} : void 0;
|
|
1262
1007
|
const result = {
|
|
1263
|
-
task
|
|
1008
|
+
task,
|
|
1264
1009
|
harnessTaskId,
|
|
1265
1010
|
durationMs,
|
|
1266
1011
|
status,
|
|
1267
1012
|
evaluation,
|
|
1268
1013
|
agentRun,
|
|
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)
|
|
1014
|
+
spanCount: countAllSpans(traceFlow)
|
|
1272
1015
|
};
|
|
1273
1016
|
if (status === "timeout")
|
|
1274
1017
|
report.timeouts++;
|
|
@@ -1277,7 +1020,6 @@ async function runBenchmark(tasks, options) {
|
|
|
1277
1020
|
else
|
|
1278
1021
|
report.failed++;
|
|
1279
1022
|
report.totalDurationMs += durationMs;
|
|
1280
|
-
report.totalUsage = sumUsage(report.totalUsage, usage);
|
|
1281
1023
|
report.results.push(result);
|
|
1282
1024
|
options.onProgress?.(result);
|
|
1283
1025
|
}
|
|
@@ -1286,7 +1028,7 @@ async function runBenchmark(tasks, options) {
|
|
|
1286
1028
|
|
|
1287
1029
|
// ../bench/dist/report.js
|
|
1288
1030
|
import fs2 from "node:fs/promises";
|
|
1289
|
-
import
|
|
1031
|
+
import path5 from "node:path";
|
|
1290
1032
|
function nowIso() {
|
|
1291
1033
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1292
1034
|
}
|
|
@@ -1299,20 +1041,15 @@ function resultLine(r, idx) {
|
|
|
1299
1041
|
const symbol = r.evaluation.passed ? "\u2713" : "\u2717";
|
|
1300
1042
|
const status = r.status === "timeout" ? "timeout" : r.status;
|
|
1301
1043
|
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}]` : "";
|
|
1304
1044
|
const msg = r.evaluation.message ? ` \u2014 ${r.evaluation.message}` : "";
|
|
1305
|
-
return `${String(idx + 1)}. ${symbol} ${r.task.name} [${status}]
|
|
1306
|
-
}
|
|
1307
|
-
function usageLine(usage) {
|
|
1308
|
-
return `Tokens: ${String(usage.totalTokens ?? 0)} total (${String(usage.promptTokens ?? 0)} prompt, ${String(usage.completionTokens ?? 0)} completion)`;
|
|
1045
|
+
return `${String(idx + 1)}. ${symbol} ${r.task.name} [${status}] ${formatDuration(r.durationMs)}${score}${msg}`;
|
|
1309
1046
|
}
|
|
1310
1047
|
async function writeReport(report, outputDir = ".omega/reports") {
|
|
1311
1048
|
await fs2.mkdir(outputDir, { recursive: true });
|
|
1312
1049
|
const ts = nowIso();
|
|
1313
|
-
const
|
|
1314
|
-
const mdFile =
|
|
1315
|
-
await fs2.writeFile(
|
|
1050
|
+
const jsonFile = path5.join(outputDir, `benchmark-${ts}.json`);
|
|
1051
|
+
const mdFile = path5.join(outputDir, `benchmark-${ts}.md`);
|
|
1052
|
+
await fs2.writeFile(jsonFile, JSON.stringify(report, null, 2), "utf-8");
|
|
1316
1053
|
const passRate2 = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
|
|
1317
1054
|
const md = [
|
|
1318
1055
|
"# Omega Benchmark Report",
|
|
@@ -1325,7 +1062,6 @@ async function writeReport(report, outputDir = ".omega/reports") {
|
|
|
1325
1062
|
`- Timeouts: ${String(report.timeouts)}`,
|
|
1326
1063
|
`- Pass rate: ${String(passRate2)}%`,
|
|
1327
1064
|
`- Total duration: ${formatDuration(report.totalDurationMs)}`,
|
|
1328
|
-
`- ${usageLine(report.totalUsage)}`,
|
|
1329
1065
|
"",
|
|
1330
1066
|
"## Results",
|
|
1331
1067
|
"",
|
|
@@ -1339,7 +1075,7 @@ async function writeReport(report, outputDir = ".omega/reports") {
|
|
|
1339
1075
|
""
|
|
1340
1076
|
].join("\n");
|
|
1341
1077
|
await fs2.writeFile(mdFile, md, "utf-8");
|
|
1342
|
-
return
|
|
1078
|
+
return jsonFile;
|
|
1343
1079
|
}
|
|
1344
1080
|
function printSummary(report) {
|
|
1345
1081
|
const passRate2 = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
|
|
@@ -1348,60 +1084,22 @@ Benchmark: ${report.suite}`);
|
|
|
1348
1084
|
console.log(`Total: ${String(report.total)} | Passed: ${String(report.passed)} | Failed: ${String(report.failed)} | Timeouts: ${String(report.timeouts)}`);
|
|
1349
1085
|
console.log(`Pass rate: ${String(passRate2)}%`);
|
|
1350
1086
|
console.log(`Duration: ${formatDuration(report.totalDurationMs)}`);
|
|
1351
|
-
console.log(usageLine(report.totalUsage));
|
|
1352
1087
|
for (const r of report.results) {
|
|
1353
1088
|
console.log(resultLine(r, report.results.indexOf(r)));
|
|
1354
1089
|
}
|
|
1355
1090
|
}
|
|
1356
1091
|
|
|
1357
1092
|
// ../bench/dist/suites/synthetic.js
|
|
1358
|
-
import fs4 from "node:fs/promises";
|
|
1359
|
-
|
|
1360
|
-
// ../bench/dist/suites/builder.js
|
|
1361
1093
|
import fs3 from "node:fs/promises";
|
|
1362
|
-
import
|
|
1363
|
-
function
|
|
1364
|
-
|
|
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
|
-
};
|
|
1373
|
-
}
|
|
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");
|
|
1094
|
+
import path6 from "node:path";
|
|
1095
|
+
async function writeJson(projectPath, file, data) {
|
|
1096
|
+
await fs3.writeFile(path6.join(projectPath, file), JSON.stringify(data, null, 2), "utf-8");
|
|
1394
1097
|
}
|
|
1395
|
-
async function
|
|
1396
|
-
await
|
|
1397
|
-
await
|
|
1398
|
-
`);
|
|
1399
|
-
await codeFile(projectPath, "test.js", `console.log('test ok');
|
|
1400
|
-
`);
|
|
1401
|
-
await codeFile(projectPath, "build.js", `console.log('build ok');
|
|
1402
|
-
`);
|
|
1098
|
+
async function writeFile2(projectPath, file, content) {
|
|
1099
|
+
await fs3.mkdir(path6.dirname(path6.join(projectPath, file)), { recursive: true });
|
|
1100
|
+
await fs3.writeFile(path6.join(projectPath, file), content, "utf-8");
|
|
1403
1101
|
}
|
|
1404
|
-
function
|
|
1102
|
+
function parseValidation(ctx) {
|
|
1405
1103
|
if (!ctx.agentRun?.validationSummary)
|
|
1406
1104
|
return { allPassed: false };
|
|
1407
1105
|
try {
|
|
@@ -1411,598 +1109,142 @@ function readValidation(ctx) {
|
|
|
1411
1109
|
return { allPassed: false };
|
|
1412
1110
|
}
|
|
1413
1111
|
}
|
|
1414
|
-
function
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
message: v.allPassed ? "Validation passed" : "Validation failed"
|
|
1420
|
-
};
|
|
1421
|
-
};
|
|
1422
|
-
}
|
|
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
|
-
};
|
|
1112
|
+
function validate(ctx) {
|
|
1113
|
+
const v = parseValidation(ctx);
|
|
1114
|
+
return {
|
|
1115
|
+
passed: v.allPassed,
|
|
1116
|
+
message: v.allPassed ? "Validation passed" : "Validation failed"
|
|
1434
1117
|
};
|
|
1435
1118
|
}
|
|
1436
|
-
function
|
|
1437
|
-
return
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1119
|
+
function basePackage() {
|
|
1120
|
+
return {
|
|
1121
|
+
name: "synthetic-bench-project",
|
|
1122
|
+
version: "1.0.0",
|
|
1123
|
+
type: "module",
|
|
1124
|
+
scripts: {
|
|
1125
|
+
lint: "node lint.js",
|
|
1126
|
+
test: "node test.js",
|
|
1127
|
+
build: "node build.js"
|
|
1442
1128
|
}
|
|
1443
|
-
return { passed: true, message: "All checks passed" };
|
|
1444
1129
|
};
|
|
1445
1130
|
}
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
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');
|
|
1486
|
-
`);
|
|
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');
|
|
1501
|
-
`);
|
|
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');
|
|
1516
|
-
`);
|
|
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');
|
|
1131
|
+
var TASKS = [
|
|
1132
|
+
{
|
|
1133
|
+
id: "greet-function",
|
|
1134
|
+
name: "Add a greet function",
|
|
1135
|
+
title: "Add greet.js with a greet function",
|
|
1136
|
+
description: "Create a `greet.js` file that exports a function `greet(name)` returning `Hello, <name>!`. The existing test expects this function.",
|
|
1137
|
+
complexity: "simple",
|
|
1138
|
+
setup: async (projectPath) => {
|
|
1139
|
+
await writeJson(projectPath, "package.json", basePackage());
|
|
1140
|
+
await writeFile2(projectPath, "test.js", `import { greet } from './greet.js';
|
|
1141
|
+
import assert from 'node:assert';
|
|
1142
|
+
assert.strictEqual(greet('World'), 'Hello, World!');
|
|
1143
|
+
console.log('greet test passed');
|
|
1549
1144
|
`);
|
|
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; }
|
|
1145
|
+
await writeFile2(projectPath, "lint.js", `console.log('lint ok');
|
|
1579
1146
|
`);
|
|
1580
|
-
|
|
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; }
|
|
1147
|
+
await writeFile2(projectPath, "build.js", `console.log('build ok');
|
|
1598
1148
|
`);
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
setup: async (projectPath) => {
|
|
1613
|
-
await nodeProject(projectPath);
|
|
1614
|
-
await codeFile(projectPath, "src/validate.js", `export function validateUser(email) { return email.includes('@'); }
|
|
1149
|
+
},
|
|
1150
|
+
evaluate: (ctx) => Promise.resolve(validate(ctx))
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
id: "fix-lint-warning",
|
|
1154
|
+
name: "Fix a lint warning",
|
|
1155
|
+
title: "Remove unused variable flagged by linter",
|
|
1156
|
+
description: "The custom linter flags any use of `var`. Update `src/index.js` so the lint script passes while keeping the greeting behavior.",
|
|
1157
|
+
complexity: "simple",
|
|
1158
|
+
setup: async (projectPath) => {
|
|
1159
|
+
await writeJson(projectPath, "package.json", basePackage());
|
|
1160
|
+
await writeFile2(projectPath, "src/index.js", `var greeting = 'Hello';
|
|
1161
|
+
export function hi(name) { return greeting + ', ' + name + '!'; }
|
|
1615
1162
|
`);
|
|
1616
|
-
|
|
1617
|
-
import
|
|
1618
|
-
|
|
1619
|
-
|
|
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; }
|
|
1163
|
+
await writeFile2(projectPath, "test.js", `import { hi } from './src/index.js';
|
|
1164
|
+
import assert from 'node:assert';
|
|
1165
|
+
assert.strictEqual(hi('Omega'), 'Hello, Omega!');
|
|
1166
|
+
console.log('hi test passed');
|
|
1637
1167
|
`);
|
|
1638
|
-
|
|
1168
|
+
await writeFile2(projectPath, "lint.js", `import fs from 'node:fs/promises';
|
|
1639
1169
|
const code = await fs.readFile('./src/index.js', 'utf-8');
|
|
1640
|
-
if (code.includes('var ')) { console.error('var keyword found'); process.exit(1); }
|
|
1170
|
+
if (code.includes('var ')) { console.error('lint failed: var keyword found'); process.exit(1); }
|
|
1641
1171
|
console.log('lint ok');
|
|
1642
1172
|
`);
|
|
1643
|
-
|
|
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; }
|
|
1657
|
-
`);
|
|
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; }
|
|
1673
|
-
`);
|
|
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()); }
|
|
1687
|
-
`);
|
|
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; }
|
|
1173
|
+
await writeFile2(projectPath, "build.js", `console.log('build ok');
|
|
1707
1174
|
`);
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1175
|
+
},
|
|
1176
|
+
evaluate: (ctx) => Promise.resolve(validate(ctx))
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
id: "update-config",
|
|
1180
|
+
name: "Update a config value",
|
|
1181
|
+
title: "Update timeout value in config.json",
|
|
1182
|
+
description: "Change `config.json` so that `timeout` is `5000` instead of `1000`. The test will read the file and assert the value.",
|
|
1183
|
+
complexity: "simple",
|
|
1184
|
+
setup: async (projectPath) => {
|
|
1185
|
+
await writeJson(projectPath, "package.json", basePackage());
|
|
1186
|
+
await writeJson(projectPath, "config.json", { timeout: 1e3, retries: 3 });
|
|
1187
|
+
await writeFile2(projectPath, "test.js", `import { readFile } from 'node:fs/promises';
|
|
1188
|
+
const cfg = JSON.parse(await readFile('./config.json', 'utf-8'));
|
|
1189
|
+
if (cfg.timeout !== 5000) { console.error('timeout is ' + cfg.timeout); process.exit(1); }
|
|
1190
|
+
console.log('config test passed');
|
|
1721
1191
|
`);
|
|
1722
|
-
|
|
1723
|
-
export { greet };
|
|
1192
|
+
await writeFile2(projectPath, "lint.js", `console.log('lint ok');
|
|
1724
1193
|
`);
|
|
1725
|
-
|
|
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' }) }; }
|
|
1194
|
+
await writeFile2(projectPath, "build.js", `console.log('build ok');
|
|
1765
1195
|
`);
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
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; }
|
|
1196
|
+
},
|
|
1197
|
+
evaluate: (ctx) => Promise.resolve(validate(ctx))
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
id: "extract-constant",
|
|
1201
|
+
name: "Extract a magic constant",
|
|
1202
|
+
title: "Extract MAX_RETRIES constant",
|
|
1203
|
+
description: "Refactor `src/retry.js` so the magic number `3` is replaced by a top-level constant named `MAX_RETRIES`. The test checks for the constant.",
|
|
1204
|
+
complexity: "medium",
|
|
1205
|
+
setup: async (projectPath) => {
|
|
1206
|
+
await writeJson(projectPath, "package.json", basePackage());
|
|
1207
|
+
await writeFile2(projectPath, "src/retry.js", `export function attempts() { return 3; }
|
|
1810
1208
|
`);
|
|
1811
|
-
|
|
1812
|
-
import {
|
|
1813
|
-
|
|
1814
|
-
console.
|
|
1209
|
+
await writeFile2(projectPath, "test.js", `import fs from 'node:fs/promises';
|
|
1210
|
+
import { attempts } from './src/retry.js';
|
|
1211
|
+
const code = await fs.readFile('./src/retry.js', 'utf-8');
|
|
1212
|
+
if (!code.includes('const MAX_RETRIES')) { console.error('MAX_RETRIES constant not found'); process.exit(1); }
|
|
1213
|
+
if (attempts() !== 3) { console.error('attempts() changed'); process.exit(1); }
|
|
1214
|
+
console.log('constant test passed');
|
|
1815
1215
|
`);
|
|
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; }
|
|
1216
|
+
await writeFile2(projectPath, "lint.js", `console.log('lint ok');
|
|
1827
1217
|
`);
|
|
1828
|
-
|
|
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');
|
|
1218
|
+
await writeFile2(projectPath, "build.js", `console.log('build ok');
|
|
1848
1219
|
`);
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
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');
|
|
1220
|
+
},
|
|
1221
|
+
evaluate: (ctx) => Promise.resolve(validate(ctx))
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
id: "noop-validation",
|
|
1225
|
+
name: "Run validation on a clean project",
|
|
1226
|
+
title: "Confirm the project already passes validation",
|
|
1227
|
+
description: "This project already passes lint, test and build. Use the publish tool to confirm validation passes and finish.",
|
|
1228
|
+
complexity: "simple",
|
|
1229
|
+
setup: async (projectPath) => {
|
|
1230
|
+
await writeJson(projectPath, "package.json", basePackage());
|
|
1231
|
+
await writeFile2(projectPath, "test.js", `console.log('test ok');
|
|
1877
1232
|
`);
|
|
1878
|
-
|
|
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); }
|
|
1233
|
+
await writeFile2(projectPath, "lint.js", `console.log('lint ok');
|
|
1892
1234
|
`);
|
|
1893
|
-
|
|
1894
|
-
export function runB(d) { return formatDate(d); }
|
|
1235
|
+
await writeFile2(projectPath, "build.js", `console.log('build ok');
|
|
1895
1236
|
`);
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
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
|
-
];
|
|
1237
|
+
},
|
|
1238
|
+
evaluate: (ctx) => Promise.resolve(validate(ctx))
|
|
1239
|
+
}
|
|
1240
|
+
];
|
|
1241
|
+
function syntheticSuite() {
|
|
1242
|
+
return TASKS;
|
|
2001
1243
|
}
|
|
2002
1244
|
|
|
2003
1245
|
// ../bench/dist/adapters/deepswe.js
|
|
2004
|
-
import
|
|
2005
|
-
import
|
|
1246
|
+
import fs4 from "node:fs/promises";
|
|
1247
|
+
import path7 from "node:path";
|
|
2006
1248
|
import { execFile } from "node:child_process";
|
|
2007
1249
|
import { promisify } from "node:util";
|
|
2008
1250
|
var execFileAsync = promisify(execFile);
|
|
@@ -2044,18 +1286,18 @@ function mulberry32(seed) {
|
|
|
2044
1286
|
};
|
|
2045
1287
|
}
|
|
2046
1288
|
async function readTask(taskDir) {
|
|
2047
|
-
const tomlRaw = await
|
|
2048
|
-
const instructionRaw = await
|
|
1289
|
+
const tomlRaw = await fs4.readFile(path7.join(taskDir, "task.toml"), "utf-8");
|
|
1290
|
+
const instructionRaw = await fs4.readFile(path7.join(taskDir, "instruction.md"), "utf-8");
|
|
2049
1291
|
return { toml: parseToml(tomlRaw), instruction: instructionRaw };
|
|
2050
1292
|
}
|
|
2051
|
-
async function cloneRepo(repoUrl,
|
|
2052
|
-
await
|
|
1293
|
+
async function cloneRepo(repoUrl, commit2, targetPath) {
|
|
1294
|
+
await fs4.mkdir(path7.dirname(targetPath), { recursive: true });
|
|
2053
1295
|
await execFileAsync("git", ["clone", repoUrl, targetPath], { timeout: 12e4 });
|
|
2054
|
-
await execFileAsync("git", ["-C", targetPath, "checkout",
|
|
1296
|
+
await execFileAsync("git", ["-C", targetPath, "checkout", commit2], { timeout: 6e4 });
|
|
2055
1297
|
}
|
|
2056
1298
|
async function loadDeepSWESuite(options) {
|
|
2057
|
-
const entries = await
|
|
2058
|
-
const taskDirs = entries.filter((e) => e.isDirectory()).map((e) =>
|
|
1299
|
+
const entries = await fs4.readdir(options.tasksDir, { withFileTypes: true });
|
|
1300
|
+
const taskDirs = entries.filter((e) => e.isDirectory()).map((e) => path7.join(options.tasksDir, e.name));
|
|
2059
1301
|
let loaded = [];
|
|
2060
1302
|
for (const dir of taskDirs) {
|
|
2061
1303
|
try {
|
|
@@ -2066,7 +1308,7 @@ async function loadDeepSWESuite(options) {
|
|
|
2066
1308
|
}
|
|
2067
1309
|
if (options.taskIds && options.taskIds.length > 0) {
|
|
2068
1310
|
loaded = loaded.filter((t) => {
|
|
2069
|
-
const id = t.toml.metadata?.task_id ??
|
|
1311
|
+
const id = t.toml.metadata?.task_id ?? path7.basename(t.dir);
|
|
2070
1312
|
return options.taskIds?.includes(id);
|
|
2071
1313
|
});
|
|
2072
1314
|
}
|
|
@@ -2077,10 +1319,10 @@ async function loadDeepSWESuite(options) {
|
|
|
2077
1319
|
}
|
|
2078
1320
|
const tasks = [];
|
|
2079
1321
|
for (const { dir, toml, instruction } of loaded) {
|
|
2080
|
-
const id = toml.metadata?.task_id ??
|
|
1322
|
+
const id = toml.metadata?.task_id ?? path7.basename(dir);
|
|
2081
1323
|
const title = toml.metadata?.display_title ?? toml.metadata?.original_title ?? id;
|
|
2082
1324
|
const repo = toml.metadata?.repository_url;
|
|
2083
|
-
const
|
|
1325
|
+
const commit2 = toml.metadata?.base_commit_hash;
|
|
2084
1326
|
tasks.push({
|
|
2085
1327
|
id: `deepswe-${id}`,
|
|
2086
1328
|
name: id,
|
|
@@ -2088,10 +1330,10 @@ async function loadDeepSWESuite(options) {
|
|
|
2088
1330
|
description: instruction,
|
|
2089
1331
|
complexity: "complex",
|
|
2090
1332
|
setup: async (projectPath) => {
|
|
2091
|
-
if (!repo || !
|
|
1333
|
+
if (!repo || !commit2) {
|
|
2092
1334
|
throw new Error(`DeepSWE task ${id} is missing repository_url or base_commit_hash`);
|
|
2093
1335
|
}
|
|
2094
|
-
await cloneRepo(repo,
|
|
1336
|
+
await cloneRepo(repo, commit2, projectPath);
|
|
2095
1337
|
},
|
|
2096
1338
|
evaluate: (ctx) => {
|
|
2097
1339
|
const hasDiff = ctx.diffs.length > 0 && ctx.diffs.some((d) => d.patch.trim().length > 0);
|
|
@@ -2108,110 +1350,65 @@ async function loadDeepSWESuite(options) {
|
|
|
2108
1350
|
}
|
|
2109
1351
|
|
|
2110
1352
|
// ../bench/dist/optimise.js
|
|
2111
|
-
import
|
|
2112
|
-
import
|
|
1353
|
+
import fs5 from "node:fs/promises";
|
|
1354
|
+
import path8 from "node:path";
|
|
2113
1355
|
async function findLatestReport(outputDir) {
|
|
2114
|
-
const entries = await
|
|
1356
|
+
const entries = await fs5.readdir(outputDir).catch(() => []);
|
|
2115
1357
|
const files = entries.filter((f) => f.startsWith("benchmark-") && f.endsWith(".json")).sort().reverse();
|
|
2116
1358
|
if (files.length === 0)
|
|
2117
1359
|
return void 0;
|
|
2118
|
-
const raw = await
|
|
1360
|
+
const raw = await fs5.readFile(path8.join(outputDir, files[0]), "utf-8");
|
|
2119
1361
|
return JSON.parse(raw);
|
|
2120
1362
|
}
|
|
2121
|
-
function
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
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);
|
|
1363
|
+
function summariseTraceFlow(report, maxLines = 60) {
|
|
1364
|
+
const lines = report.split("\n");
|
|
1365
|
+
if (lines.length <= maxLines)
|
|
1366
|
+
return report;
|
|
1367
|
+
return lines.slice(0, maxLines).join("\n") + "\n... (truncated)";
|
|
2139
1368
|
}
|
|
2140
1369
|
function buildOptimisePrompt(report, failedResult, traceFlowText) {
|
|
2141
1370
|
const passRate2 = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
|
|
1371
|
+
const failed = report.results.filter((r) => !r.evaluation.passed);
|
|
2142
1372
|
const lines = [];
|
|
2143
|
-
lines.push("
|
|
1373
|
+
lines.push("# Prompt optimisation task");
|
|
2144
1374
|
lines.push("");
|
|
2145
1375
|
lines.push(`The latest benchmark run achieved ${String(passRate2)}% pass rate (${String(report.passed)}/${String(report.total)}).`);
|
|
2146
1376
|
lines.push("");
|
|
2147
|
-
lines.push("##
|
|
2148
|
-
const
|
|
2149
|
-
|
|
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
|
-
}
|
|
1377
|
+
lines.push("## Failed tasks");
|
|
1378
|
+
for (const r of failed.slice(0, 5)) {
|
|
1379
|
+
lines.push(`- ${r.task.name}: ${r.evaluation.message ?? "no message"}`);
|
|
2155
1380
|
}
|
|
2156
1381
|
lines.push("");
|
|
2157
1382
|
if (failedResult) {
|
|
2158
|
-
|
|
2159
|
-
lines.push(
|
|
2160
|
-
lines.push(`
|
|
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
|
-
}
|
|
1383
|
+
lines.push(`## Focus task: ${failedResult.task.name}`);
|
|
1384
|
+
lines.push(`Status: ${failedResult.status}`);
|
|
1385
|
+
lines.push(`Evaluation: ${failedResult.evaluation.message ?? "none"}`);
|
|
2168
1386
|
lines.push("");
|
|
2169
1387
|
}
|
|
2170
1388
|
if (traceFlowText) {
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
lines.push(err);
|
|
2177
|
-
}
|
|
2178
|
-
lines.push("");
|
|
2179
|
-
}
|
|
1389
|
+
lines.push("## Trace flow snapshot");
|
|
1390
|
+
lines.push("```json");
|
|
1391
|
+
lines.push(summariseTraceFlow(traceFlowText));
|
|
1392
|
+
lines.push("```");
|
|
1393
|
+
lines.push("");
|
|
2180
1394
|
}
|
|
2181
1395
|
lines.push("## Instructions");
|
|
2182
|
-
lines.push("
|
|
2183
|
-
lines.push("
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
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.");
|
|
1396
|
+
lines.push("Analyse the failures above and edit `packages/agent/src/prompts.ts` (and optionally `packages/agent/src/planner.ts`) to improve the Omega agent.");
|
|
1397
|
+
lines.push("Rules:");
|
|
1398
|
+
lines.push("- Keep changes minimal and targeted.");
|
|
1399
|
+
lines.push("- Do not break existing tool signatures or system behaviour.");
|
|
1400
|
+
lines.push("- Run `pnpm lint` and `pnpm test` after edits.");
|
|
1401
|
+
lines.push("- Finish with a summary of what changed and why it should help.");
|
|
2198
1402
|
return lines.join("\n");
|
|
2199
1403
|
}
|
|
2200
1404
|
async function loadOptimisationContext(apiUrl, outputDir) {
|
|
2201
1405
|
const report = await findLatestReport(outputDir);
|
|
2202
1406
|
if (!report)
|
|
2203
1407
|
return void 0;
|
|
2204
|
-
const
|
|
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);
|
|
1408
|
+
const failedResult = report.results.find((r) => !r.evaluation.passed);
|
|
2212
1409
|
let traceFlowText;
|
|
2213
1410
|
if (failedResult?.harnessTaskId) {
|
|
2214
|
-
const traceFlow =
|
|
1411
|
+
const traceFlow = await getTraceFlow(apiUrl, failedResult.harnessTaskId);
|
|
2215
1412
|
if (traceFlow) {
|
|
2216
1413
|
traceFlowText = JSON.stringify(traceFlow, null, 2);
|
|
2217
1414
|
}
|
|
@@ -2273,13 +1470,25 @@ function buildAbReport(baseline, candidate, baselineReport, candidateReport) {
|
|
|
2273
1470
|
passRate: candidateRate - baselineRate,
|
|
2274
1471
|
passedDelta: candidateReport.passed - baselineReport.passed,
|
|
2275
1472
|
durationMsDelta: candidateReport.totalDurationMs - baselineReport.totalDurationMs,
|
|
2276
|
-
promptTokensDelta: (candidateReport.totalUsage
|
|
2277
|
-
completionTokensDelta: (candidateReport.totalUsage
|
|
2278
|
-
totalTokensDelta: (candidateReport.totalUsage
|
|
1473
|
+
promptTokensDelta: (candidateReport.totalUsage?.promptTokens ?? 0) - (baselineReport.totalUsage?.promptTokens ?? 0),
|
|
1474
|
+
completionTokensDelta: (candidateReport.totalUsage?.completionTokens ?? 0) - (baselineReport.totalUsage?.completionTokens ?? 0),
|
|
1475
|
+
totalTokensDelta: (candidateReport.totalUsage?.totalTokens ?? 0) - (baselineReport.totalUsage?.totalTokens ?? 0)
|
|
2279
1476
|
},
|
|
2280
1477
|
perTask
|
|
2281
1478
|
};
|
|
2282
1479
|
}
|
|
1480
|
+
async function runAbBenchmark(tasks, baseline, candidate, options) {
|
|
1481
|
+
const suiteName = options.suiteName ?? "ab";
|
|
1482
|
+
const baselineReport = await runBenchmark(tasks, {
|
|
1483
|
+
...options,
|
|
1484
|
+
suiteName: `${suiteName}-baseline`
|
|
1485
|
+
});
|
|
1486
|
+
const candidateReport = await runBenchmark(tasks, {
|
|
1487
|
+
...options,
|
|
1488
|
+
suiteName: `${suiteName}-candidate`
|
|
1489
|
+
});
|
|
1490
|
+
return buildAbReport(baseline, candidate, baselineReport, candidateReport);
|
|
1491
|
+
}
|
|
2283
1492
|
function deltaSymbol(value) {
|
|
2284
1493
|
if (value > 0)
|
|
2285
1494
|
return "+";
|
|
@@ -2304,7 +1513,7 @@ function formatAbReport(report) {
|
|
|
2304
1513
|
lines.push(`| Pass rate | ${String(passRate(report.baselineReport))}% | ${String(passRate(report.candidateReport))}% | ${formatDelta(report.delta.passRate)}% |`);
|
|
2305
1514
|
lines.push(`| Passed | ${String(report.baselineReport.passed)}/${String(report.baselineReport.total)} | ${String(report.candidateReport.passed)}/${String(report.candidateReport.total)} | ${formatDelta(report.delta.passedDelta)} |`);
|
|
2306
1515
|
lines.push(`| Duration | ${formatDuration2(report.baselineReport.totalDurationMs)} | ${formatDuration2(report.candidateReport.totalDurationMs)} | ${formatDuration2(report.delta.durationMsDelta)} |`);
|
|
2307
|
-
lines.push(`| Tokens | ${String(report.baselineReport.totalUsage
|
|
1516
|
+
lines.push(`| Tokens | ${String(report.baselineReport.totalUsage?.totalTokens ?? 0)} | ${String(report.candidateReport.totalUsage?.totalTokens ?? 0)} | ${formatDelta(report.delta.totalTokensDelta)} |`);
|
|
2308
1517
|
lines.push("");
|
|
2309
1518
|
lines.push("## Per-task comparison");
|
|
2310
1519
|
lines.push(`| Task | Baseline | Candidate | Token delta |`);
|
|
@@ -2322,6 +1531,237 @@ function formatDuration2(ms) {
|
|
|
2322
1531
|
return `${(ms / 1e3).toFixed(1)}s`;
|
|
2323
1532
|
}
|
|
2324
1533
|
|
|
1534
|
+
// ../agent/dist/tool-definitions.js
|
|
1535
|
+
var AGENT_TOOLS = [
|
|
1536
|
+
{
|
|
1537
|
+
name: "read_file",
|
|
1538
|
+
description: "Read a file relative to project root.",
|
|
1539
|
+
parameters: {
|
|
1540
|
+
type: "object",
|
|
1541
|
+
properties: { path: { type: "string" } },
|
|
1542
|
+
required: ["path"]
|
|
1543
|
+
}
|
|
1544
|
+
},
|
|
1545
|
+
{
|
|
1546
|
+
name: "write_file",
|
|
1547
|
+
description: "Write content to a file relative to project root.",
|
|
1548
|
+
parameters: {
|
|
1549
|
+
type: "object",
|
|
1550
|
+
properties: { path: { type: "string" }, content: { type: "string" } },
|
|
1551
|
+
required: ["path", "content"]
|
|
1552
|
+
}
|
|
1553
|
+
},
|
|
1554
|
+
{
|
|
1555
|
+
name: "edit_file",
|
|
1556
|
+
description: "Apply a targeted edit to an existing file relative to project root. Replaces one occurrence of old_string with new_string.",
|
|
1557
|
+
parameters: {
|
|
1558
|
+
type: "object",
|
|
1559
|
+
properties: {
|
|
1560
|
+
path: { type: "string" },
|
|
1561
|
+
old_string: { type: "string" },
|
|
1562
|
+
new_string: { type: "string" }
|
|
1563
|
+
},
|
|
1564
|
+
required: ["path", "old_string", "new_string"]
|
|
1565
|
+
}
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
name: "run_command",
|
|
1569
|
+
description: "Run a shell command in the project root.",
|
|
1570
|
+
parameters: {
|
|
1571
|
+
type: "object",
|
|
1572
|
+
properties: { command: { type: "string" } },
|
|
1573
|
+
required: ["command"]
|
|
1574
|
+
}
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
name: "think",
|
|
1578
|
+
description: "Record reasoning.",
|
|
1579
|
+
parameters: {
|
|
1580
|
+
type: "object",
|
|
1581
|
+
properties: { thought: { type: "string" } },
|
|
1582
|
+
required: ["thought"]
|
|
1583
|
+
}
|
|
1584
|
+
},
|
|
1585
|
+
{
|
|
1586
|
+
name: "finish",
|
|
1587
|
+
description: "Mark the task complete.",
|
|
1588
|
+
parameters: {
|
|
1589
|
+
type: "object",
|
|
1590
|
+
properties: { summary: { type: "string" }, success: { type: "boolean" } },
|
|
1591
|
+
required: ["summary", "success"]
|
|
1592
|
+
}
|
|
1593
|
+
},
|
|
1594
|
+
{
|
|
1595
|
+
name: "publish",
|
|
1596
|
+
description: "Build, validate, and publish the project.",
|
|
1597
|
+
parameters: {
|
|
1598
|
+
type: "object",
|
|
1599
|
+
properties: { version: { type: "string" } },
|
|
1600
|
+
required: []
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
];
|
|
1604
|
+
|
|
1605
|
+
// ../agent/dist/planner.js
|
|
1606
|
+
var toolDescriptions = AGENT_TOOLS.map((t) => `- ${t.name}: ${t.description}`).join("\n");
|
|
1607
|
+
var PLAN_PROMPT = `You are a planning assistant. Given a task, produce a concise step-by-step plan.
|
|
1608
|
+
|
|
1609
|
+
Respond with strict JSON in this exact shape (no markdown):
|
|
1610
|
+
{
|
|
1611
|
+
"reasoning": "brief reasoning",
|
|
1612
|
+
"plan": [
|
|
1613
|
+
{ "name": "step name", "tool": "optional tool name", "input": { optional tool args } }
|
|
1614
|
+
]
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
Available tools:
|
|
1618
|
+
${toolDescriptions}
|
|
1619
|
+
|
|
1620
|
+
If a step does not need a tool, omit tool/input. Use edit_file for small file changes.`;
|
|
1621
|
+
|
|
1622
|
+
// ../agent/dist/tools.js
|
|
1623
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
1624
|
+
import { promisify as promisify2 } from "node:util";
|
|
1625
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
1626
|
+
|
|
1627
|
+
// ../agent/dist/validator.js
|
|
1628
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
1629
|
+
import { promisify as promisify3 } from "node:util";
|
|
1630
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
1631
|
+
|
|
1632
|
+
// ../agent/dist/publisher.js
|
|
1633
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
1634
|
+
import { promisify as promisify5 } from "node:util";
|
|
1635
|
+
|
|
1636
|
+
// ../agent/dist/git.js
|
|
1637
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
1638
|
+
import { promisify as promisify4 } from "node:util";
|
|
1639
|
+
var execFileAsync4 = promisify4(execFile4);
|
|
1640
|
+
|
|
1641
|
+
// ../agent/dist/publisher.js
|
|
1642
|
+
var execFileAsync5 = promisify5(execFile5);
|
|
1643
|
+
|
|
1644
|
+
// ../agent/dist/prompts.js
|
|
1645
|
+
import fs6 from "node:fs";
|
|
1646
|
+
function loadPromptFromEnv(name) {
|
|
1647
|
+
const direct = process.env[name];
|
|
1648
|
+
if (direct)
|
|
1649
|
+
return direct;
|
|
1650
|
+
const fileKey = `${name}_FILE`;
|
|
1651
|
+
const filePath = process.env[fileKey];
|
|
1652
|
+
if (filePath) {
|
|
1653
|
+
try {
|
|
1654
|
+
return fs6.readFileSync(filePath, "utf-8");
|
|
1655
|
+
} catch {
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
return void 0;
|
|
1659
|
+
}
|
|
1660
|
+
var AGENT_SYSTEM_PROMPT = loadPromptFromEnv("OMEGA_SYSTEM_PROMPT") ?? `You are Omega, an autonomous software engineering agent running inside a project repository.
|
|
1661
|
+
|
|
1662
|
+
Your job is to complete the user's task by calling tools. Do not write prose or explanations outside tool calls. Prefer targeted edits over full rewrites.
|
|
1663
|
+
|
|
1664
|
+
Available tools:
|
|
1665
|
+
|
|
1666
|
+
- read_file: Read a file relative to project root. Arguments: { "path": "relative/path" }
|
|
1667
|
+
- write_file: Overwrite or create a file. Arguments: { "path": "relative/path", "content": "full file content" }
|
|
1668
|
+
- edit_file: Replace one exact occurrence of old_string with new_string in an existing file. Use this for small changes. Arguments: { "path": "relative/path", "old_string": "...", "new_string": "..." }
|
|
1669
|
+
- run_command: Run a single simple command. No pipes (|), &&, ;, redirects, or globs. Prefer pnpm/npm/node. Arguments: { "command": "pnpm lint" }
|
|
1670
|
+
- think: Record a reasoning step. Arguments: { "thought": "..." }
|
|
1671
|
+
- finish: Mark the task complete. Arguments: { "summary": "what was done", "success": true }. Use summary, not message.
|
|
1672
|
+
- publish: Run build/test/publish validation. Only after your edits pass validation. Arguments: { "version": "optional" }
|
|
1673
|
+
|
|
1674
|
+
Rules:
|
|
1675
|
+
1. Read the task, then use think to plan.
|
|
1676
|
+
2. Use edit_file for small changes; write_file only when creating a file or rewriting most of it.
|
|
1677
|
+
3. ALWAYS read a file with read_file before using edit_file on it. Do NOT read a file before write_file when creating a new file.
|
|
1678
|
+
4. Before creating JavaScript files, read package.json to check whether "type" is "module" and use ESM (export) or CommonJS (module.exports) accordingly.
|
|
1679
|
+
5. You MUST run validation (pnpm lint, pnpm test, or the project's validation command) after every edit using publish or run_command.
|
|
1680
|
+
6. Do NOT call finish until validation passes. If validation fails, fix the issue and re-run validation.
|
|
1681
|
+
7. Do not expose secrets or run destructive commands.
|
|
1682
|
+
8. Finish only when the task is done. Always include summary and success.
|
|
1683
|
+
9. Do not repeat the same tool call with the same arguments. If something fails, change your approach.
|
|
1684
|
+
10. Do not ask the user for clarification. Do not say "No task was provided". Use the Task and Description above to proceed.
|
|
1685
|
+
|
|
1686
|
+
Example tool call sequence for "Add a greet function":
|
|
1687
|
+
1. think: { "thought": "I need to create greet.js and export a greet function." }
|
|
1688
|
+
2. write_file: { "path": "greet.js", "content": "export function greet(name) { return 'Hello, ' + name + '!'; }
|
|
1689
|
+
" }
|
|
1690
|
+
3. publish: { }
|
|
1691
|
+
4. finish: { "summary": "Created greet.js with greet(name) function and verified it passes tests.", "success": true }`;
|
|
1692
|
+
var TEXT_TOOLS_SYSTEM_PROMPT = loadPromptFromEnv("OMEGA_TEXT_TOOLS_PROMPT") ?? `You are Omega, an autonomous software engineering agent running inside a project repository.
|
|
1693
|
+
|
|
1694
|
+
You MUST respond with a single JSON object containing a "tool_calls" array. Do not output markdown, explanations, or reasoning outside the JSON.
|
|
1695
|
+
|
|
1696
|
+
Available tools (use ONLY these exact names):
|
|
1697
|
+
|
|
1698
|
+
- read_file: { "path": "relative/path" }
|
|
1699
|
+
- write_file: { "path": "relative/path", "content": "full file content" }
|
|
1700
|
+
- edit_file: { "path": "relative/path", "old_string": "...", "new_string": "..." }
|
|
1701
|
+
- run_command: { "command": "simple command, no pipes/&&/;/redirects" }
|
|
1702
|
+
- think: { "thought": "reasoning text" }
|
|
1703
|
+
- finish: { "summary": "what was done", "success": true | false }
|
|
1704
|
+
- publish: { "version": "optional" }
|
|
1705
|
+
|
|
1706
|
+
Rules:
|
|
1707
|
+
- Plan with think, then act.
|
|
1708
|
+
- Use edit_file for small changes; write_file only for new files or large rewrites.
|
|
1709
|
+
- ALWAYS read a file with read_file before using edit_file on it. Do NOT read a file before write_file when creating a new file.
|
|
1710
|
+
- Before creating JavaScript files, read package.json to check whether "type" is "module" and use ESM (export) or CommonJS (module.exports) accordingly.
|
|
1711
|
+
- You MUST run validation (pnpm lint, pnpm test, or the project's validation command) after every edit using publish or run_command.
|
|
1712
|
+
- Do NOT call finish until validation passes. If validation fails, fix the issue and re-run validation.
|
|
1713
|
+
- Do not expose secrets or run destructive commands.
|
|
1714
|
+
- Finish only when done. Use summary, not message.
|
|
1715
|
+
- Do not repeat the same tool call with the same arguments.
|
|
1716
|
+
- Do not ask the user for clarification. Do not say "No task was provided". Use the Task and Description above to proceed.
|
|
1717
|
+
|
|
1718
|
+
Example response for "Add a greet function":
|
|
1719
|
+
{ "tool_calls": [
|
|
1720
|
+
{ "id": "1", "name": "think", "arguments": { "thought": "Create greet.js with greet(name)." } },
|
|
1721
|
+
{ "id": "2", "name": "write_file", "arguments": { "path": "greet.js", "content": "export function greet(name) { return 'Hello, ' + name + '!'; }
|
|
1722
|
+
" } },
|
|
1723
|
+
{ "id": "3", "name": "publish", "arguments": {} },
|
|
1724
|
+
{ "id": "4", "name": "finish", "arguments": { "summary": "Created greet.js and verified tests pass.", "success": true } }
|
|
1725
|
+
] }`;
|
|
1726
|
+
|
|
1727
|
+
// ../agent/dist/prompt-versioning.js
|
|
1728
|
+
import fs7 from "node:fs/promises";
|
|
1729
|
+
import path9 from "node:path";
|
|
1730
|
+
import crypto from "node:crypto";
|
|
1731
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
1732
|
+
var __dirname3 = path9.dirname(fileURLToPath4(import.meta.url));
|
|
1733
|
+
var PROMPTS_PATH = path9.resolve(__dirname3, "..", "src", "prompts.ts");
|
|
1734
|
+
var PLANNER_PATH = path9.resolve(__dirname3, "..", "src", "planner.ts");
|
|
1735
|
+
function hashString(value) {
|
|
1736
|
+
return crypto.createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
1737
|
+
}
|
|
1738
|
+
function hashPrompts(input) {
|
|
1739
|
+
return hashString([input.systemPrompt, input.textToolsPrompt, input.planningPrompt ?? ""].join("\n---\n"));
|
|
1740
|
+
}
|
|
1741
|
+
async function readPromptsSource() {
|
|
1742
|
+
const source = await fs7.readFile(PROMPTS_PATH, "utf-8");
|
|
1743
|
+
const systemMatch = /export const AGENT_SYSTEM_PROMPT =\s*(?:loadPromptFromEnv\('OMEGA_SYSTEM_PROMPT'\) \?\?\s*)?`([\s\S]*?)`;/m.exec(source);
|
|
1744
|
+
const textToolsMatch = /export const TEXT_TOOLS_SYSTEM_PROMPT =\s*(?:loadPromptFromEnv\('OMEGA_TEXT_TOOLS_PROMPT'\) \?\?\s*)?`([\s\S]*?)`;/m.exec(source);
|
|
1745
|
+
return {
|
|
1746
|
+
systemPrompt: systemMatch?.[1] ?? "",
|
|
1747
|
+
textToolsPrompt: textToolsMatch?.[1] ?? ""
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
async function loadCurrentPrompts() {
|
|
1751
|
+
const { systemPrompt, textToolsPrompt } = await readPromptsSource();
|
|
1752
|
+
const plannerSource = await fs7.readFile(PLANNER_PATH, "utf-8");
|
|
1753
|
+
const planningMatch = /const PLAN_PROMPT = `([\s\S]*?)`;/m.exec(plannerSource);
|
|
1754
|
+
const planningPrompt = planningMatch?.[1];
|
|
1755
|
+
return {
|
|
1756
|
+
name: `auto-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`,
|
|
1757
|
+
sourcePath: PROMPTS_PATH,
|
|
1758
|
+
systemPrompt,
|
|
1759
|
+
textToolsPrompt,
|
|
1760
|
+
planningPrompt,
|
|
1761
|
+
hash: hashPrompts({ systemPrompt, textToolsPrompt, planningPrompt })
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
|
|
2325
1765
|
// ../../apps/cli/src/commands/bench.ts
|
|
2326
1766
|
async function waitForApi2(apiUrl, maxMs = 1e4) {
|
|
2327
1767
|
const deadline = Date.now() + maxMs;
|
|
@@ -2337,43 +1777,82 @@ async function waitForApi2(apiUrl, maxMs = 1e4) {
|
|
|
2337
1777
|
}
|
|
2338
1778
|
function currentProject(apiUrl) {
|
|
2339
1779
|
const cwd = process.cwd();
|
|
2340
|
-
const name =
|
|
1780
|
+
const name = path10.basename(cwd);
|
|
2341
1781
|
return ensureProject2(apiUrl, `bench-${name}`, cwd);
|
|
2342
1782
|
}
|
|
2343
|
-
|
|
2344
|
-
const
|
|
2345
|
-
await
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
1783
|
+
async function savePromptVersion(apiUrl, name) {
|
|
1784
|
+
const current = await loadCurrentPrompts();
|
|
1785
|
+
const versionRes = await fetch(`${apiUrl}/prompt-versions`, {
|
|
1786
|
+
method: "POST",
|
|
1787
|
+
headers: { "Content-Type": "application/json" },
|
|
1788
|
+
body: JSON.stringify({
|
|
1789
|
+
name: name ?? current.name,
|
|
1790
|
+
sourcePath: current.sourcePath,
|
|
1791
|
+
systemPrompt: current.systemPrompt,
|
|
1792
|
+
textToolsPrompt: current.textToolsPrompt,
|
|
1793
|
+
hash: current.hash,
|
|
1794
|
+
metadata: { planningPrompt: current.planningPrompt }
|
|
1795
|
+
})
|
|
1796
|
+
});
|
|
1797
|
+
if (!versionRes.ok) {
|
|
1798
|
+
console.warn("Could not save prompt version:", await versionRes.text());
|
|
1799
|
+
return void 0;
|
|
1800
|
+
}
|
|
1801
|
+
return versionRes.json();
|
|
1802
|
+
}
|
|
1803
|
+
async function loadPromptSpec(source) {
|
|
1804
|
+
if (source === "current") {
|
|
1805
|
+
const current = await loadCurrentPrompts();
|
|
1806
|
+
return {
|
|
1807
|
+
name: "current",
|
|
1808
|
+
systemPrompt: current.systemPrompt,
|
|
1809
|
+
textToolsPrompt: current.textToolsPrompt
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
const raw = await fs8.readFile(path10.resolve(source), "utf-8");
|
|
1813
|
+
const parsed = JSON.parse(raw);
|
|
1814
|
+
if (!parsed.systemPrompt || !parsed.textToolsPrompt) {
|
|
1815
|
+
throw new Error("Prompt spec must include systemPrompt and textToolsPrompt");
|
|
1816
|
+
}
|
|
1817
|
+
return parsed;
|
|
1818
|
+
}
|
|
1819
|
+
async function resolveTasks(opts) {
|
|
2349
1820
|
if (opts.suite === "deep-swe") {
|
|
2350
1821
|
if (!opts.path) {
|
|
2351
1822
|
throw new Error("--path is required for the deep-swe suite");
|
|
2352
1823
|
}
|
|
2353
|
-
tasks = await loadDeepSWESuite({
|
|
1824
|
+
const tasks = await loadDeepSWESuite({
|
|
2354
1825
|
tasksDir: opts.path,
|
|
2355
1826
|
nTasks: opts.nTasks,
|
|
2356
1827
|
sampleSeed: opts.sampleSeed
|
|
2357
1828
|
});
|
|
2358
|
-
suiteName
|
|
2359
|
-
}
|
|
2360
|
-
|
|
1829
|
+
return { tasks, suiteName: "deep-swe" };
|
|
1830
|
+
}
|
|
1831
|
+
if (opts.suite === "synthetic") {
|
|
1832
|
+
let tasks = syntheticSuite();
|
|
2361
1833
|
if (opts.nTasks !== void 0 && opts.nTasks > 0) {
|
|
2362
1834
|
tasks = tasks.slice(0, opts.nTasks);
|
|
2363
1835
|
}
|
|
2364
|
-
suiteName
|
|
2365
|
-
} else {
|
|
2366
|
-
throw new Error(`Unknown suite: ${opts.suite}`);
|
|
1836
|
+
return { tasks, suiteName: "synthetic" };
|
|
2367
1837
|
}
|
|
1838
|
+
throw new Error(`Unknown suite: ${opts.suite}`);
|
|
1839
|
+
}
|
|
1840
|
+
var runCmd = new Command8("run").description("Run a benchmark suite").option("--suite <name>", "suite name: synthetic | deep-swe", "synthetic").option("--path <dir>", "path to DeepSWE tasks directory (for deep-swe suite)").option("--n-tasks <n>", "limit number of tasks", parseInt).option("--sample-seed <n>", "seed for deterministic sampling (for deep-swe)", parseInt).option("--timeout <ms>", "per-task timeout in ms", "120000").option("--output-dir <dir>", "report output directory", ".omega/reports").option("--provider <name>", "provider to use for benchmark tasks").option("--model <model>", "model to use for benchmark tasks").action(async (opts) => {
|
|
1841
|
+
const apiUrl = getApiUrl();
|
|
1842
|
+
await waitForApi2(apiUrl);
|
|
1843
|
+
const { tasks, suiteName } = await resolveTasks(opts);
|
|
2368
1844
|
if (tasks.length === 0) {
|
|
2369
1845
|
console.log("No benchmark tasks to run.");
|
|
2370
1846
|
return;
|
|
2371
1847
|
}
|
|
1848
|
+
const timeoutMs = Number(opts.timeout);
|
|
2372
1849
|
console.log(`Running ${String(tasks.length)} benchmark tasks against ${apiUrl}`);
|
|
2373
1850
|
const report = await runBenchmark(tasks, {
|
|
2374
1851
|
apiUrl,
|
|
2375
1852
|
suiteName,
|
|
2376
1853
|
timeoutMs,
|
|
1854
|
+
provider: opts.provider,
|
|
1855
|
+
model: opts.model,
|
|
2377
1856
|
onProgress: (result) => {
|
|
2378
1857
|
const symbol = result.evaluation.passed ? "\u2713" : "\u2717";
|
|
2379
1858
|
console.log(`${symbol} ${result.task.name} [${result.status}] ${String(result.durationMs)}ms`);
|
|
@@ -2392,251 +1871,120 @@ var optimiseCmd = new Command8("optimise").description("Create a self-improve ta
|
|
|
2392
1871
|
console.log("No benchmark report found. Run `omega bench run` first.");
|
|
2393
1872
|
return;
|
|
2394
1873
|
}
|
|
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
|
-
}
|
|
2415
1874
|
const project = await currentProject(apiUrl);
|
|
2416
1875
|
const prompt = buildOptimisePrompt(
|
|
2417
1876
|
context.report,
|
|
2418
1877
|
context.failedResult,
|
|
2419
1878
|
context.traceFlowText
|
|
2420
1879
|
);
|
|
2421
|
-
const
|
|
2422
|
-
console.log(`Created self-improve task ${
|
|
2423
|
-
console.log(`Run \`omega task run ${
|
|
1880
|
+
const task = await submitOptimiseTask(apiUrl, project.id, prompt);
|
|
1881
|
+
console.log(`Created self-improve task ${task.id}`);
|
|
1882
|
+
console.log(`Run \`omega task run ${task.id}\` to execute it.`);
|
|
2424
1883
|
});
|
|
2425
|
-
|
|
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) => {
|
|
1884
|
+
var abCmd = new Command8("ab").description("Run an A/B benchmark comparing two prompt versions").option("--baseline <source>", 'baseline prompts: "current" or path to JSON file', "current").option("--candidate <source>", "candidate prompts: path to JSON file").option("--suite <name>", "suite name: synthetic | deep-swe", "synthetic").option("--path <dir>", "path to DeepSWE tasks directory (for deep-swe suite)").option("--n-tasks <n>", "limit number of tasks", parseInt).option("--sample-seed <n>", "seed for deterministic sampling (for deep-swe)", parseInt).option("--timeout <ms>", "per-task timeout in ms", "120000").option("--output-dir <dir>", "report output directory", ".omega/reports").option("--provider <name>", "provider to use for benchmark tasks").option("--model <model>", "model to use for benchmark tasks").action(async (opts) => {
|
|
2477
1885
|
if (!opts.candidate) {
|
|
2478
1886
|
throw new Error("--candidate is required");
|
|
2479
1887
|
}
|
|
2480
|
-
const timeoutMs = Number(opts.timeout);
|
|
2481
|
-
const existingApiUrl = getApiUrl();
|
|
2482
|
-
let tasks;
|
|
2483
|
-
let suiteName;
|
|
2484
1888
|
if (opts.suite === "deep-swe") {
|
|
2485
1889
|
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
1890
|
}
|
|
1891
|
+
const apiUrl = getApiUrl();
|
|
1892
|
+
await waitForApi2(apiUrl);
|
|
1893
|
+
const { tasks, suiteName } = await resolveTasks(opts);
|
|
1894
|
+
if (tasks.length === 0) {
|
|
1895
|
+
console.log("No benchmark tasks to run.");
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
const timeoutMs = Number(opts.timeout);
|
|
2495
1899
|
const baselineSpec = await loadPromptSpec(opts.baseline);
|
|
2496
1900
|
const candidateSpec = await loadPromptSpec(opts.candidate);
|
|
2497
1901
|
console.log(`Baseline: ${baselineSpec.name}`);
|
|
2498
1902
|
console.log(`Candidate: ${candidateSpec.name}`);
|
|
2499
|
-
|
|
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, {
|
|
1903
|
+
const report = await runAbBenchmark(tasks, baselineSpec, candidateSpec, {
|
|
2553
1904
|
apiUrl,
|
|
2554
1905
|
suiteName,
|
|
2555
1906
|
timeoutMs,
|
|
1907
|
+
provider: opts.provider,
|
|
1908
|
+
model: opts.model,
|
|
2556
1909
|
onProgress: (result) => {
|
|
2557
1910
|
const symbol = result.evaluation.passed ? "\u2713" : "\u2717";
|
|
2558
1911
|
console.log(`${symbol} ${result.task.name} [${result.status}] ${String(result.durationMs)}ms`);
|
|
2559
1912
|
}
|
|
2560
1913
|
});
|
|
2561
|
-
await
|
|
2562
|
-
|
|
2563
|
-
const
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
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
|
-
}
|
|
1914
|
+
await fs8.mkdir(opts.outputDir, { recursive: true });
|
|
1915
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1916
|
+
const jsonFile = path10.join(opts.outputDir, `ab-${ts}.json`);
|
|
1917
|
+
const mdFile = path10.join(opts.outputDir, `ab-${ts}.md`);
|
|
1918
|
+
await fs8.writeFile(jsonFile, JSON.stringify(report, null, 2), "utf-8");
|
|
1919
|
+
await fs8.writeFile(mdFile, formatAbReport(report), "utf-8");
|
|
1920
|
+
console.log("\n" + formatAbReport(report));
|
|
2581
1921
|
console.log(`
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
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) => {
|
|
1922
|
+
A/B report written to ${jsonFile}`);
|
|
1923
|
+
});
|
|
1924
|
+
var loopCmd = new Command8("loop").description("Run a self-improve benchmark loop").option("--suite <name>", "suite name: synthetic | deep-swe", "synthetic").option("--path <dir>", "path to DeepSWE tasks directory (for deep-swe suite)").option("--n-tasks <n>", "limit number of tasks", parseInt).option("--sample-seed <n>", "seed for deterministic sampling (for deep-swe)", parseInt).option("--iterations <n>", "max iterations", "1").option("--timeout <ms>", "per-task timeout in ms", "120000").option("--output-dir <dir>", "report output directory", ".omega/reports").option("--provider <name>", "provider to use for self-improve tasks").option("--model <model>", "model to use for self-improve tasks").action(async (opts) => {
|
|
2598
1925
|
const apiUrl = getApiUrl();
|
|
2599
1926
|
await waitForApi2(apiUrl);
|
|
2600
|
-
const
|
|
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
|
-
}
|
|
1927
|
+
const { tasks, suiteName } = await resolveTasks(opts);
|
|
2621
1928
|
if (tasks.length === 0) {
|
|
2622
1929
|
console.log("No benchmark tasks to run.");
|
|
2623
1930
|
return;
|
|
2624
1931
|
}
|
|
1932
|
+
const timeoutMs = Number(opts.timeout);
|
|
1933
|
+
const maxIterations = Number(opts.iterations);
|
|
1934
|
+
const project = await currentProject(apiUrl);
|
|
2625
1935
|
for (let i = 1; i <= maxIterations; i++) {
|
|
2626
1936
|
console.log(`
|
|
2627
1937
|
=== Iteration ${String(i)}/${String(maxIterations)} ===`);
|
|
2628
|
-
const {
|
|
1938
|
+
const beforeVersion = await savePromptVersion(apiUrl, `loop-before-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`);
|
|
1939
|
+
if (beforeVersion) {
|
|
1940
|
+
console.log(`Saved before-loop prompt version: ${beforeVersion.name} (${beforeVersion.id})`);
|
|
1941
|
+
}
|
|
1942
|
+
console.log(`
|
|
1943
|
+
Running ${String(tasks.length)} benchmark tasks...`);
|
|
1944
|
+
const report = await runBenchmark(tasks, {
|
|
2629
1945
|
apiUrl,
|
|
2630
|
-
project.id,
|
|
2631
|
-
tasks,
|
|
2632
1946
|
suiteName,
|
|
2633
1947
|
timeoutMs,
|
|
2634
|
-
opts.
|
|
2635
|
-
opts.
|
|
2636
|
-
|
|
2637
|
-
|
|
1948
|
+
provider: opts.provider,
|
|
1949
|
+
model: opts.model,
|
|
1950
|
+
onProgress: (result) => {
|
|
1951
|
+
const symbol = result.evaluation.passed ? "\u2713" : "\u2717";
|
|
1952
|
+
console.log(`${symbol} ${result.task.name} [${result.status}] ${String(result.durationMs)}ms`);
|
|
1953
|
+
}
|
|
1954
|
+
});
|
|
1955
|
+
await writeReport(report, opts.outputDir);
|
|
1956
|
+
printSummary(report);
|
|
2638
1957
|
const passRate2 = report.total > 0 ? Math.round(report.passed / report.total * 100) : 0;
|
|
2639
|
-
if (passRate2 === 100)
|
|
1958
|
+
if (passRate2 === 100) {
|
|
1959
|
+
console.log("\nAll tasks passed. Loop complete.");
|
|
1960
|
+
break;
|
|
1961
|
+
}
|
|
1962
|
+
const context = await loadOptimisationContext(apiUrl, opts.outputDir);
|
|
1963
|
+
if (!context) {
|
|
1964
|
+
throw new Error("Benchmark report could not be loaded for optimisation");
|
|
1965
|
+
}
|
|
1966
|
+
const prompt = buildOptimisePrompt(context.report, context.failedResult, context.traceFlowText);
|
|
1967
|
+
const task = await submitOptimiseTask(apiUrl, project.id, prompt);
|
|
1968
|
+
if (opts.provider || opts.model) {
|
|
1969
|
+
await fetch(`${apiUrl}/tasks/${task.id}`, {
|
|
1970
|
+
method: "PATCH",
|
|
1971
|
+
headers: { "Content-Type": "application/json" },
|
|
1972
|
+
body: JSON.stringify({ provider: opts.provider, model: opts.model })
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
console.log(`
|
|
1976
|
+
Created self-improve task ${task.id}${opts.provider ? ` (provider: ${opts.provider})` : ""}${opts.model ? ` (model: ${opts.model})` : ""}`);
|
|
1977
|
+
console.log("Running agent...");
|
|
1978
|
+
await runTask(apiUrl, task.id);
|
|
1979
|
+
const finished = await waitForTask(apiUrl, task.id, timeoutMs * 5);
|
|
1980
|
+
console.log(`Agent finished: ${finished.status}`);
|
|
1981
|
+
if (finished.error) {
|
|
1982
|
+
console.error(`Agent error: ${finished.error}`);
|
|
1983
|
+
}
|
|
1984
|
+
const afterVersion = await savePromptVersion(apiUrl, `loop-after-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`);
|
|
1985
|
+
if (afterVersion) {
|
|
1986
|
+
console.log(`Saved after-loop prompt version: ${afterVersion.name} (${afterVersion.id})`);
|
|
1987
|
+
}
|
|
2640
1988
|
}
|
|
2641
1989
|
});
|
|
2642
1990
|
var benchCmd = new Command8("bench").description("Run benchmarks and optimise prompts").addCommand(runCmd).addCommand(abCmd).addCommand(optimiseCmd).addCommand(loopCmd);
|