@open330/oac 2026.4.3 → 2026.220.1
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/CHANGELOG.md +115 -0
- package/README.md +2 -2
- package/dist/{chunk-YWIB3TRI.js → chunk-6A37SKAJ.js} +15 -2
- package/dist/chunk-6A37SKAJ.js.map +1 -0
- package/dist/{chunk-ZRYAHZQJ.js → chunk-7C7SC4TZ.js} +1 -1
- package/dist/{chunk-XUW3XWTX.js → chunk-7Y4LZUDP.js} +89 -121
- package/dist/chunk-7Y4LZUDP.js.map +1 -0
- package/dist/{chunk-CJAJ4MBO.js → chunk-OS3XDHOJ.js} +57 -18
- package/dist/chunk-OS3XDHOJ.js.map +1 -0
- package/dist/{chunk-HDMLNOND.js → chunk-OTPXGXO7.js} +44 -18
- package/dist/chunk-OTPXGXO7.js.map +1 -0
- package/dist/{chunk-7FWC3Z4W.js → chunk-QPVNC7S4.js} +479 -196
- package/dist/chunk-QPVNC7S4.js.map +1 -0
- package/dist/cli/cli.js +6 -6
- package/dist/cli/index.js +6 -6
- package/dist/completion/index.js +4 -8
- package/dist/completion/index.js.map +1 -1
- package/dist/core/index.d.ts +16 -1
- package/dist/core/index.js +8 -4
- package/dist/dashboard/index.js +33 -24
- package/dist/dashboard/index.js.map +1 -1
- package/dist/discovery/index.d.ts +18 -2
- package/dist/discovery/index.js +4 -2
- package/dist/execution/index.d.ts +45 -1
- package/dist/execution/index.js +7 -3
- package/dist/repo/index.d.ts +2 -2
- package/dist/repo/index.js +1 -1
- package/dist/{types-CYCwgojB.d.ts → types-3_IAAxh5.d.ts} +1 -0
- package/docs/config-reference.md +271 -0
- package/docs/multi-agent-support-technical-spec.md +312 -0
- package/package.json +23 -18
- package/dist/chunk-7FWC3Z4W.js.map +0 -1
- package/dist/chunk-CJAJ4MBO.js.map +0 -1
- package/dist/chunk-HDMLNOND.js.map +0 -1
- package/dist/chunk-XUW3XWTX.js.map +0 -1
- package/dist/chunk-YWIB3TRI.js.map +0 -1
- /package/dist/{chunk-ZRYAHZQJ.js.map → chunk-7C7SC4TZ.js.map} +0 -0
package/dist/dashboard/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cloneRepo,
|
|
3
3
|
resolveRepo
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-OS3XDHOJ.js";
|
|
5
5
|
import {
|
|
6
6
|
CompositeScanner,
|
|
7
7
|
GitHubIssuesScanner,
|
|
8
8
|
LintScanner,
|
|
9
9
|
TodoScanner,
|
|
10
10
|
rankTasks
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-OTPXGXO7.js";
|
|
12
12
|
import {
|
|
13
13
|
buildExecutionPlan,
|
|
14
14
|
estimateTokens
|
|
@@ -17,11 +17,11 @@ import {
|
|
|
17
17
|
CodexAdapter,
|
|
18
18
|
createSandbox,
|
|
19
19
|
executeTask
|
|
20
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-QPVNC7S4.js";
|
|
21
21
|
import {
|
|
22
22
|
createEventBus
|
|
23
|
-
} from "../chunk-
|
|
24
|
-
import "../chunk-
|
|
23
|
+
} from "../chunk-7C7SC4TZ.js";
|
|
24
|
+
import "../chunk-6A37SKAJ.js";
|
|
25
25
|
import {
|
|
26
26
|
buildLeaderboard,
|
|
27
27
|
contributionLogSchema,
|
|
@@ -969,21 +969,21 @@ function renderDashboardHtml(port) {
|
|
|
969
969
|
const data = JSON.parse(e.data);
|
|
970
970
|
updateStage(data.stage);
|
|
971
971
|
addEventLine(log, "stage", data.message || data.stage);
|
|
972
|
-
} catch {}
|
|
972
|
+
} catch {} // best-effort: SSE event data may be malformed
|
|
973
973
|
});
|
|
974
974
|
|
|
975
975
|
es.addEventListener("run:progress", (e) => {
|
|
976
976
|
try {
|
|
977
977
|
const data = JSON.parse(e.data);
|
|
978
978
|
updateProgress(data.progress);
|
|
979
|
-
} catch {}
|
|
979
|
+
} catch {} // best-effort: SSE event data may be malformed
|
|
980
980
|
});
|
|
981
981
|
|
|
982
982
|
es.addEventListener("run:task-start", (e) => {
|
|
983
983
|
try {
|
|
984
984
|
const data = JSON.parse(e.data);
|
|
985
985
|
addEventLine(log, "task", "Starting: " + data.title);
|
|
986
|
-
} catch {}
|
|
986
|
+
} catch {} // best-effort: SSE event data may be malformed
|
|
987
987
|
});
|
|
988
988
|
|
|
989
989
|
es.addEventListener("run:task-done", (e) => {
|
|
@@ -994,7 +994,7 @@ function renderDashboardHtml(port) {
|
|
|
994
994
|
if (data.prUrl) msg += " - PR: " + data.prUrl;
|
|
995
995
|
addEventLine(log, "task", msg);
|
|
996
996
|
addTaskResult(data);
|
|
997
|
-
} catch {}
|
|
997
|
+
} catch {} // best-effort: SSE event data may be malformed
|
|
998
998
|
});
|
|
999
999
|
|
|
1000
1000
|
es.addEventListener("run:completed", (e) => {
|
|
@@ -1002,7 +1002,7 @@ function renderDashboardHtml(port) {
|
|
|
1002
1002
|
addEventLine(log, "completed", "Run finished successfully");
|
|
1003
1003
|
updateStage("completed");
|
|
1004
1004
|
onRunCompleted(false);
|
|
1005
|
-
} catch {}
|
|
1005
|
+
} catch {} // best-effort: SSE event data may be malformed
|
|
1006
1006
|
});
|
|
1007
1007
|
|
|
1008
1008
|
es.addEventListener("run:error", (e) => {
|
|
@@ -1010,7 +1010,7 @@ function renderDashboardHtml(port) {
|
|
|
1010
1010
|
const data = JSON.parse(e.data);
|
|
1011
1011
|
addEventLine(log, "error", data.error);
|
|
1012
1012
|
onRunError(data.error);
|
|
1013
|
-
} catch {}
|
|
1013
|
+
} catch {} // best-effort: SSE event data may be malformed
|
|
1014
1014
|
});
|
|
1015
1015
|
|
|
1016
1016
|
es.onerror = () => {
|
|
@@ -1055,6 +1055,7 @@ var DEFAULT_OPTIONS = {
|
|
|
1055
1055
|
openBrowser: false,
|
|
1056
1056
|
oacDir: process.cwd()
|
|
1057
1057
|
};
|
|
1058
|
+
var MAX_SSE_CLIENTS = 50;
|
|
1058
1059
|
var currentRun = null;
|
|
1059
1060
|
var sseClients = /* @__PURE__ */ new Set();
|
|
1060
1061
|
function broadcastEvent(event) {
|
|
@@ -1074,18 +1075,18 @@ async function readContributionLogs(oacDir) {
|
|
|
1074
1075
|
return [];
|
|
1075
1076
|
}
|
|
1076
1077
|
const files = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name).sort();
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1078
|
+
const results = await Promise.all(
|
|
1079
|
+
files.map(async (fileName) => {
|
|
1080
|
+
try {
|
|
1081
|
+
const content = await readFile(resolve(contributionsPath, fileName), "utf8");
|
|
1082
|
+
const parsed = contributionLogSchema.safeParse(JSON.parse(content));
|
|
1083
|
+
return parsed.success ? parsed.data : null;
|
|
1084
|
+
} catch {
|
|
1085
|
+
return null;
|
|
1084
1086
|
}
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
return logs;
|
|
1087
|
+
})
|
|
1088
|
+
);
|
|
1089
|
+
return results.filter((log) => log !== null);
|
|
1089
1090
|
}
|
|
1090
1091
|
async function readRunStatus(oacDir) {
|
|
1091
1092
|
if (currentRun) {
|
|
@@ -1202,6 +1203,10 @@ data: ${JSON.stringify(event)}
|
|
|
1202
1203
|
|
|
1203
1204
|
`);
|
|
1204
1205
|
};
|
|
1206
|
+
if (sseClients.size >= MAX_SSE_CLIENTS) {
|
|
1207
|
+
reply.status(503).send({ error: "Too many SSE connections" });
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1205
1210
|
sseClients.add(sendEvent);
|
|
1206
1211
|
const interval = setInterval(() => {
|
|
1207
1212
|
reply.raw.write(
|
|
@@ -1231,9 +1236,13 @@ async function startDashboard(options = {}) {
|
|
|
1231
1236
|
console.log(` SSE: ${url}/api/v1/events
|
|
1232
1237
|
`);
|
|
1233
1238
|
if (opts.openBrowser) {
|
|
1234
|
-
const {
|
|
1239
|
+
const { execFile } = await import("child_process");
|
|
1235
1240
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1236
|
-
|
|
1241
|
+
execFile(command, [url], (err) => {
|
|
1242
|
+
if (err) {
|
|
1243
|
+
console.warn(`Could not open browser: ${err.message}`);
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
1237
1246
|
}
|
|
1238
1247
|
}
|
|
1239
1248
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/dashboard/server.ts","../../src/dashboard/pipeline.ts","../../src/dashboard/ui.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { Dirent } from \"node:fs\";\nimport { readFile, readdir } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport cors from \"@fastify/cors\";\nimport Fastify from \"fastify\";\nimport { contributionLogSchema } from \"../tracking/index.js\";\nimport type { ContributionLog } from \"../tracking/index.js\";\nimport { buildLeaderboard } from \"../tracking/index.js\";\nimport {\n type DashboardRunEvent,\n type RunConfig,\n type RunState,\n executePipeline,\n} from \"./pipeline.js\";\nimport { renderDashboardHtml } from \"./ui.js\";\n\nexport interface DashboardOptions {\n port: number;\n host: string;\n openBrowser: boolean;\n oacDir: string;\n}\n\nconst DEFAULT_OPTIONS: DashboardOptions = {\n port: 3141,\n host: \"0.0.0.0\",\n openBrowser: false,\n oacDir: process.cwd(),\n};\n\n// ---------------------------------------------------------------------------\n// Run state management (single-run mode)\n// ---------------------------------------------------------------------------\n\nlet currentRun: RunState | null = null;\nconst sseClients = new Set<(event: DashboardRunEvent) => void>();\n\nfunction broadcastEvent(event: DashboardRunEvent): void {\n for (const send of sseClients) {\n try {\n send(event);\n } catch {\n // Client disconnected — will be cleaned up on close\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// File readers\n// ---------------------------------------------------------------------------\n\nasync function readContributionLogs(oacDir: string): Promise<ContributionLog[]> {\n const contributionsPath = resolve(oacDir, \".oac\", \"contributions\");\n\n let entries: Dirent[];\n try {\n entries = await readdir(contributionsPath, { withFileTypes: true, encoding: \"utf8\" });\n } catch {\n return [];\n }\n\n const files = entries\n .filter((entry) => entry.isFile() && entry.name.endsWith(\".json\"))\n .map((entry) => entry.name)\n .sort();\n\n const logs: ContributionLog[] = [];\n for (const fileName of files) {\n try {\n const content = await readFile(resolve(contributionsPath, fileName), \"utf8\");\n const parsed = contributionLogSchema.safeParse(JSON.parse(content));\n if (parsed.success) {\n logs.push(parsed.data);\n }\n } catch {\n // skip invalid files\n }\n }\n\n return logs;\n}\n\nasync function readRunStatus(oacDir: string): Promise<unknown> {\n // Return live run state if a run is active\n if (currentRun) {\n return currentRun;\n }\n\n try {\n const content = await readFile(resolve(oacDir, \".oac\", \"status.json\"), \"utf8\");\n return JSON.parse(content);\n } catch {\n return { status: \"idle\", message: \"No active runs\" };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Server\n// ---------------------------------------------------------------------------\n\nexport async function createDashboardServer(\n options: Partial<DashboardOptions> = {},\n): Promise<ReturnType<typeof Fastify>> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const app = Fastify({ logger: false });\n\n await app.register(cors, { origin: true });\n\n // --- HTML Dashboard ---\n app.get(\"/\", async (_request, reply) => {\n reply.type(\"text/html\").send(renderDashboardHtml(opts.port));\n });\n\n // --- API Routes ---\n app.get(\"/api/v1/status\", async () => {\n return readRunStatus(opts.oacDir);\n });\n\n app.get(\"/api/v1/logs\", async () => {\n const logs = await readContributionLogs(opts.oacDir);\n return { count: logs.length, logs };\n });\n\n app.get(\"/api/v1/leaderboard\", async () => {\n const leaderboard = await buildLeaderboard(opts.oacDir);\n return leaderboard;\n });\n\n app.get(\"/api/v1/config\", async () => {\n return {\n oacDir: opts.oacDir,\n port: opts.port,\n host: opts.host,\n };\n });\n\n // --- Start Run ---\n app.post(\"/api/v1/runs\", async (request, reply) => {\n // Only one run at a time\n if (currentRun && currentRun.status === \"running\") {\n reply.code(409).send({ error: \"A run is already in progress\", runId: currentRun.runId });\n return;\n }\n\n const body = request.body as Partial<RunConfig> | null;\n if (!body?.repo || !body.provider || !body.tokens) {\n reply.code(400).send({ error: \"Missing required fields: repo, provider, tokens\" });\n return;\n }\n\n const config: RunConfig = {\n repo: body.repo,\n provider: body.provider,\n tokens: body.tokens,\n concurrency: typeof body.concurrency === \"number\" ? body.concurrency : undefined,\n maxTasks: body.maxTasks,\n source: body.source,\n };\n\n // Initialize run state\n const runId = randomUUID();\n currentRun = {\n runId,\n status: \"running\",\n stage: \"resolving\",\n config,\n startedAt: new Date().toISOString(),\n progress: {\n tasksDiscovered: 0,\n tasksSelected: 0,\n tasksCompleted: 0,\n tasksFailed: 0,\n prsCreated: 0,\n tokensUsed: 0,\n prUrls: [],\n },\n };\n\n // Fire-and-forget: pipeline runs in background\n executePipeline(config, (event) => {\n // Update currentRun from events\n if (event.type === \"run:stage\" && currentRun) {\n currentRun.stage = event.stage;\n }\n if (event.type === \"run:progress\" && currentRun) {\n currentRun.progress = event.progress;\n }\n if (event.type === \"run:completed\" && currentRun) {\n currentRun.status = \"completed\";\n currentRun.completedAt = new Date().toISOString();\n }\n if (event.type === \"run:error\" && currentRun) {\n currentRun.status = \"failed\";\n currentRun.error = event.error;\n currentRun.completedAt = new Date().toISOString();\n }\n\n // Broadcast to all SSE clients\n broadcastEvent(event);\n }).catch((err) => {\n if (currentRun) {\n currentRun.status = \"failed\";\n currentRun.error = err instanceof Error ? err.message : String(err);\n currentRun.completedAt = new Date().toISOString();\n }\n });\n\n reply.code(202).send({ runId, status: \"started\" });\n });\n\n // --- SSE Event Stream ---\n app.get(\"/api/v1/events\", async (_request, reply) => {\n reply.raw.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n\n reply.raw.write(\n `event: connected\\ndata: ${JSON.stringify({ time: new Date().toISOString() })}\\n\\n`,\n );\n\n // Register for run event broadcasts\n const sendEvent = (event: DashboardRunEvent) => {\n reply.raw.write(`event: ${event.type}\\ndata: ${JSON.stringify(event)}\\n\\n`);\n };\n sseClients.add(sendEvent);\n\n const interval = setInterval(() => {\n reply.raw.write(\n `event: heartbeat\\ndata: ${JSON.stringify({ time: new Date().toISOString() })}\\n\\n`,\n );\n }, 10_000);\n\n _request.raw.on(\"close\", () => {\n clearInterval(interval);\n sseClients.delete(sendEvent);\n });\n });\n\n return app;\n}\n\nexport async function startDashboard(options: Partial<DashboardOptions> = {}): Promise<void> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const app = await createDashboardServer(opts);\n\n await app.listen({ port: opts.port, host: opts.host });\n const url =\n opts.host === \"0.0.0.0\" ? `http://localhost:${opts.port}` : `http://${opts.host}:${opts.port}`;\n console.log(`\\n 🚀 OAC Dashboard running at ${url}`);\n console.log(` Network: http://0.0.0.0:${opts.port}`);\n console.log(`\\n API: ${url}/api/v1/status`);\n console.log(` SSE: ${url}/api/v1/events\\n`);\n\n if (opts.openBrowser) {\n const { exec } = await import(\"node:child_process\");\n const command =\n process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"start\" : \"xdg-open\";\n exec(`${command} ${url}`);\n }\n}\n","import { randomUUID } from \"node:crypto\";\n\nimport { execa } from \"execa\";\nimport PQueue from \"p-queue\";\nimport { buildExecutionPlan, estimateTokens } from \"../budget/index.js\";\nimport { type Task, type TokenEstimate, createEventBus } from \"../core/index.js\";\nimport {\n CompositeScanner,\n GitHubIssuesScanner,\n LintScanner,\n type Scanner,\n TodoScanner,\n rankTasks,\n} from \"../discovery/index.js\";\nimport {\n CodexAdapter,\n createSandbox,\n executeTask as workerExecuteTask,\n} from \"../execution/index.js\";\nimport { cloneRepo, resolveRepo } from \"../repo/index.js\";\nimport { type ContributionLog, writeContributionLog } from \"../tracking/index.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface RunConfig {\n repo: string;\n provider: string;\n tokens: number;\n concurrency?: number;\n maxTasks?: number;\n source?: string;\n}\n\nexport type RunStage =\n | \"resolving\"\n | \"cloning\"\n | \"scanning\"\n | \"estimating\"\n | \"planning\"\n | \"executing\"\n | \"creating-pr\"\n | \"tracking\"\n | \"completed\"\n | \"failed\";\n\nexport interface RunProgress {\n tasksDiscovered: number;\n tasksSelected: number;\n tasksCompleted: number;\n tasksFailed: number;\n prsCreated: number;\n tokensUsed: number;\n currentTask?: string;\n prUrls: string[];\n}\n\nexport interface RunState {\n runId: string;\n status: \"running\" | \"completed\" | \"failed\";\n stage: RunStage;\n config: RunConfig;\n startedAt: string;\n completedAt?: string;\n error?: string;\n progress: RunProgress;\n}\n\nexport type DashboardRunEvent =\n | { type: \"run:stage\"; stage: RunStage; message: string }\n | { type: \"run:progress\"; progress: RunProgress }\n | { type: \"run:task-start\"; taskId: string; title: string }\n | {\n type: \"run:task-done\";\n taskId: string;\n title: string;\n success: boolean;\n prUrl?: string;\n filesChanged: number;\n }\n | { type: \"run:completed\"; summary: RunState }\n | { type: \"run:error\"; error: string };\n\nexport type RunEventCallback = (event: DashboardRunEvent) => void;\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\ninterface ExecutionOutcome {\n success: boolean;\n exitCode: number;\n totalTokensUsed: number;\n filesChanged: string[];\n duration: number;\n error?: string;\n}\n\ninterface SandboxInfo {\n branchName: string;\n sandboxPath: string;\n cleanup: () => Promise<void>;\n}\n\ninterface TaskResult {\n task: Task;\n execution: ExecutionOutcome;\n sandbox?: SandboxInfo;\n pr?: { number: number; url: string; status: \"open\" | \"merged\" | \"closed\" };\n}\n\nfunction buildScanners(): { names: string[]; scanner: CompositeScanner } {\n const scanners: Scanner[] = [new LintScanner(), new TodoScanner()];\n const names = [\"lint\", \"todo\"];\n\n if (process.env.GITHUB_TOKEN) {\n scanners.push(new GitHubIssuesScanner());\n names.push(\"github-issues\");\n }\n\n return { names, scanner: new CompositeScanner(scanners) };\n}\n\nasync function executeWithCodex(input: {\n task: Task;\n estimate: TokenEstimate;\n codexAdapter: CodexAdapter;\n repoPath: string;\n baseBranch: string;\n timeoutSeconds: number;\n}): Promise<{ execution: ExecutionOutcome; sandbox: SandboxInfo }> {\n const startedAt = Date.now();\n const taskSlug = input.task.id\n .replace(/[^a-zA-Z0-9-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .slice(0, 30);\n const branchName = `oac/${Date.now()}-${taskSlug}`;\n\n const sandbox = await createSandbox(input.repoPath, branchName, input.baseBranch);\n const eventBus = createEventBus();\n const sandboxInfo: SandboxInfo = {\n branchName,\n sandboxPath: sandbox.path,\n cleanup: sandbox.cleanup,\n };\n\n try {\n const result = await workerExecuteTask(input.codexAdapter, input.task, sandbox, eventBus, {\n tokenBudget: input.estimate.totalEstimatedTokens,\n timeoutMs: input.timeoutSeconds * 1_000,\n });\n\n const commitResult = await commitSandboxChanges(sandbox.path, input.task);\n const filesChanged =\n commitResult.filesChanged.length > 0\n ? commitResult.filesChanged\n : result.filesChanged.length > 0\n ? result.filesChanged\n : [];\n\n return {\n execution: {\n success: result.success || commitResult.hasChanges,\n exitCode: result.exitCode,\n totalTokensUsed: result.totalTokensUsed,\n filesChanged,\n duration: result.duration > 0 ? result.duration / 1_000 : (Date.now() - startedAt) / 1_000,\n error: result.error,\n },\n sandbox: sandboxInfo,\n };\n } catch (error) {\n const commitResult = await commitSandboxChanges(sandbox.path, input.task);\n if (commitResult.hasChanges) {\n return {\n execution: {\n success: true,\n exitCode: 0,\n totalTokensUsed: 0,\n filesChanged: commitResult.filesChanged,\n duration: (Date.now() - startedAt) / 1_000,\n },\n sandbox: sandboxInfo,\n };\n }\n\n const message = error instanceof Error ? error.message : String(error);\n return {\n execution: {\n success: false,\n exitCode: 1,\n totalTokensUsed: 0,\n filesChanged: [],\n duration: (Date.now() - startedAt) / 1_000,\n error: message,\n },\n sandbox: sandboxInfo,\n };\n }\n}\n\nasync function commitSandboxChanges(\n sandboxPath: string,\n task: Task,\n): Promise<{ hasChanges: boolean; filesChanged: string[] }> {\n try {\n const statusResult = await execa(\"git\", [\"status\", \"--porcelain\"], { cwd: sandboxPath });\n if (!statusResult.stdout.trim()) {\n return { hasChanges: false, filesChanged: [] };\n }\n\n await execa(\"git\", [\"add\", \"-A\"], { cwd: sandboxPath });\n await execa(\"git\", [\"commit\", \"-m\", `[OAC] ${task.title}\\n\\nAutomated contribution by OAC.`], {\n cwd: sandboxPath,\n });\n\n const diffResult = await execa(\"git\", [\"diff\", \"--name-only\", \"HEAD~1\", \"HEAD\"], {\n cwd: sandboxPath,\n });\n const changedFiles = diffResult.stdout.trim().split(\"\\n\").filter(Boolean);\n return { hasChanges: true, filesChanged: changedFiles };\n } catch {\n return { hasChanges: false, filesChanged: [] };\n }\n}\n\nasync function resolveGitHubToken(): Promise<string | undefined> {\n if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;\n if (process.env.GH_TOKEN) return process.env.GH_TOKEN;\n try {\n const result = await execa(\"gh\", [\"auth\", \"token\"], { reject: false, timeout: 5_000 });\n if (result.exitCode === 0 && result.stdout.trim().length > 0) return result.stdout.trim();\n } catch {\n // gh not available\n }\n return undefined;\n}\n\nasync function createPullRequest(input: {\n task: Task;\n execution: ExecutionOutcome;\n sandbox: SandboxInfo;\n repoFullName: string;\n baseBranch: string;\n}): Promise<{ number: number; url: string; status: \"open\" | \"merged\" | \"closed\" } | undefined> {\n const { branchName, sandboxPath } = input.sandbox;\n\n try {\n // Resolve token upfront to avoid interactive device flow\n const ghToken = await resolveGitHubToken();\n const ghEnv: Record<string, string> = { ...process.env } as Record<string, string>;\n if (ghToken) {\n ghEnv.GH_TOKEN = ghToken;\n ghEnv.GITHUB_TOKEN = ghToken;\n }\n\n await execa(\"git\", [\"push\", \"--set-upstream\", \"origin\", branchName], {\n cwd: sandboxPath,\n env: ghEnv,\n });\n\n const prTitle = `[OAC] ${input.task.title}`;\n const prBody = [\n \"## Summary\",\n \"\",\n input.task.description || `Automated contribution for task \"${input.task.title}\".`,\n \"\",\n \"## Context\",\n \"\",\n `- **Task source:** ${input.task.source}`,\n `- **Complexity:** ${input.task.complexity}`,\n `- **Tokens used:** ${input.execution.totalTokensUsed}`,\n `- **Files changed:** ${input.execution.filesChanged.length}`,\n \"\",\n \"---\",\n \"*This PR was automatically generated by [OAC](https://github.com/Open330/open-agent-contribution).*\",\n ].join(\"\\n\");\n\n const ghResult = await execa(\n \"gh\",\n [\n \"pr\",\n \"create\",\n \"--repo\",\n input.repoFullName,\n \"--title\",\n prTitle,\n \"--body\",\n prBody,\n \"--head\",\n branchName,\n \"--base\",\n input.baseBranch,\n ],\n { cwd: sandboxPath, env: ghEnv },\n );\n\n const prUrl = ghResult.stdout.trim();\n const prNumberMatch = prUrl.match(/\\/pull\\/(\\d+)/);\n const prNumber = prNumberMatch ? Number.parseInt(prNumberMatch[1], 10) : 0;\n\n return { number: prNumber, url: prUrl, status: \"open\" };\n } catch {\n return undefined;\n }\n}\n\nfunction resolveGithubUsername(): string {\n const candidates = [\n process.env.GITHUB_USER,\n process.env.GITHUB_USERNAME,\n process.env.USER,\n process.env.LOGNAME,\n ];\n\n for (const c of candidates) {\n const cleaned = c\n ?.trim()\n .replace(/[^A-Za-z0-9-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n if (cleaned && cleaned.length <= 39 && /^(?!-)[A-Za-z0-9-]+(?<!-)$/.test(cleaned)) {\n return cleaned;\n }\n }\n\n return \"oac-user\";\n}\n\n// ---------------------------------------------------------------------------\n// Main Pipeline\n// ---------------------------------------------------------------------------\n\nexport async function executePipeline(\n config: RunConfig,\n onEvent: RunEventCallback,\n): Promise<RunState> {\n const runId = randomUUID();\n const startedAt = new Date().toISOString();\n const progress: RunProgress = {\n tasksDiscovered: 0,\n tasksSelected: 0,\n tasksCompleted: 0,\n tasksFailed: 0,\n prsCreated: 0,\n tokensUsed: 0,\n prUrls: [],\n };\n\n const state: RunState = {\n runId,\n status: \"running\",\n stage: \"resolving\",\n config,\n startedAt,\n progress,\n };\n\n const emit = (event: DashboardRunEvent) => {\n if (event.type === \"run:stage\") state.stage = event.stage;\n if (event.type === \"run:progress\") state.progress = event.progress;\n onEvent(event);\n };\n\n try {\n // 0. Ensure GitHub auth before any API calls\n const preAuthToken = await resolveGitHubToken();\n if (preAuthToken && !process.env.GITHUB_TOKEN && !process.env.GH_TOKEN) {\n process.env.GITHUB_TOKEN = preAuthToken;\n }\n\n // 1. Resolve repo\n emit({ type: \"run:stage\", stage: \"resolving\", message: `Resolving ${config.repo}...` });\n const resolvedRepo = await resolveRepo(config.repo);\n\n // 2. Clone if not local\n emit({ type: \"run:stage\", stage: \"cloning\", message: `Cloning ${resolvedRepo.fullName}...` });\n await cloneRepo(resolvedRepo);\n\n // 3. Scan\n const { names, scanner } = buildScanners();\n emit({\n type: \"run:stage\",\n stage: \"scanning\",\n message: `Scanning with ${names.join(\", \")}...`,\n });\n const scannedTasks = await scanner.scan(resolvedRepo.localPath, {\n repo: resolvedRepo,\n });\n\n // 4. Rank & filter\n let candidateTasks = rankTasks(scannedTasks).filter((t) => t.priority >= 20);\n if (config.source) {\n candidateTasks = candidateTasks.filter((t) => t.source === config.source);\n }\n if (config.maxTasks) {\n candidateTasks = candidateTasks.slice(0, config.maxTasks);\n }\n progress.tasksDiscovered = candidateTasks.length;\n emit({ type: \"run:progress\", progress: { ...progress } });\n\n if (candidateTasks.length === 0) {\n emit({ type: \"run:stage\", stage: \"completed\", message: \"No tasks discovered.\" });\n state.status = \"completed\";\n state.completedAt = new Date().toISOString();\n emit({ type: \"run:completed\", summary: { ...state } });\n return state;\n }\n\n // 5. Estimate tokens\n emit({\n type: \"run:stage\",\n stage: \"estimating\",\n message: `Estimating tokens for ${candidateTasks.length} task(s)...`,\n });\n const estimates = new Map<string, TokenEstimate>();\n for (const task of candidateTasks) {\n const est = await estimateTokens(task, config.provider);\n estimates.set(task.id, est);\n }\n\n // 6. Build execution plan\n emit({ type: \"run:stage\", stage: \"planning\", message: \"Building execution plan...\" });\n const plan = buildExecutionPlan(candidateTasks, estimates, config.tokens);\n progress.tasksSelected = plan.selectedTasks.length;\n emit({ type: \"run:progress\", progress: { ...progress } });\n\n if (plan.selectedTasks.length === 0) {\n emit({ type: \"run:stage\", stage: \"completed\", message: \"No tasks fit within budget.\" });\n state.status = \"completed\";\n state.completedAt = new Date().toISOString();\n emit({ type: \"run:completed\", summary: { ...state } });\n return state;\n }\n\n // 7. Execute tasks (with concurrency)\n const concurrency = Math.max(1, config.concurrency ?? 1);\n emit({\n type: \"run:stage\",\n stage: \"executing\",\n message: `Executing ${plan.selectedTasks.length} task(s) (concurrency: ${concurrency})...`,\n });\n\n const codexAdapter = new CodexAdapter();\n const codexAvailability = await codexAdapter.checkAvailability();\n const useRealExecution = config.provider.includes(\"codex\") && codexAvailability.available;\n\n const taskQueue = new PQueue({ concurrency });\n const taskResults = await Promise.all(\n plan.selectedTasks.map((entry) =>\n taskQueue.add(async (): Promise<TaskResult> => {\n emit({ type: \"run:task-start\", taskId: entry.task.id, title: entry.task.title });\n progress.currentTask = entry.task.title;\n emit({ type: \"run:progress\", progress: { ...progress } });\n\n let execution: ExecutionOutcome;\n let sandbox: SandboxInfo | undefined;\n\n if (useRealExecution) {\n const result = await executeWithCodex({\n task: entry.task,\n estimate: entry.estimate,\n codexAdapter,\n repoPath: resolvedRepo.localPath,\n baseBranch: resolvedRepo.meta.defaultBranch,\n timeoutSeconds: 300,\n });\n execution = result.execution;\n sandbox = result.sandbox;\n } else {\n await new Promise((r) => setTimeout(r, 500));\n execution = {\n success: true,\n exitCode: 0,\n totalTokensUsed: Math.round(entry.estimate.totalEstimatedTokens * 0.9),\n filesChanged: entry.task.targetFiles.slice(0, 4),\n duration: 0.5,\n };\n }\n\n // Create PR if execution produced changes\n let pr: TaskResult[\"pr\"];\n if (execution.success && sandbox && execution.filesChanged.length > 0) {\n emit({\n type: \"run:stage\",\n stage: \"creating-pr\",\n message: `Creating PR for \"${entry.task.title}\"...`,\n });\n pr = await createPullRequest({\n task: entry.task,\n execution,\n sandbox,\n repoFullName: resolvedRepo.fullName,\n baseBranch: resolvedRepo.meta.defaultBranch,\n });\n if (pr) {\n progress.prsCreated += 1;\n progress.prUrls.push(pr.url);\n }\n }\n\n if (execution.success) {\n progress.tasksCompleted += 1;\n } else {\n progress.tasksFailed += 1;\n }\n progress.tokensUsed += execution.totalTokensUsed;\n progress.currentTask = undefined;\n\n const result: TaskResult = { task: entry.task, execution, sandbox, pr };\n\n emit({\n type: \"run:task-done\",\n taskId: entry.task.id,\n title: entry.task.title,\n success: execution.success,\n prUrl: pr?.url,\n filesChanged: execution.filesChanged.length,\n });\n emit({ type: \"run:progress\", progress: { ...progress } });\n\n return result;\n }) as Promise<TaskResult>,\n ),\n );\n\n // 8. Write contribution log\n emit({ type: \"run:stage\", stage: \"tracking\", message: \"Writing contribution log...\" });\n\n const contributionLog: ContributionLog = {\n version: \"1.0\",\n runId,\n timestamp: new Date().toISOString(),\n contributor: { githubUsername: resolveGithubUsername() },\n repo: {\n fullName: resolvedRepo.fullName,\n headSha: resolvedRepo.git.headSha,\n defaultBranch: resolvedRepo.meta.defaultBranch,\n },\n budget: {\n provider: config.provider,\n totalTokensBudgeted: config.tokens,\n totalTokensUsed: progress.tokensUsed,\n },\n tasks: taskResults.map((r) => ({\n taskId: r.task.id,\n title: r.task.title,\n source: r.task.source,\n complexity: r.task.complexity,\n status: r.execution.success ? (\"success\" as const) : (\"failed\" as const),\n tokensUsed: r.execution.totalTokensUsed,\n duration: r.execution.duration,\n filesChanged: r.execution.filesChanged,\n pr: r.pr,\n error: r.execution.error,\n })),\n metrics: {\n tasksDiscovered: progress.tasksDiscovered,\n tasksAttempted: taskResults.length,\n tasksSucceeded: progress.tasksCompleted,\n tasksFailed: progress.tasksFailed,\n totalDuration: (Date.now() - new Date(startedAt).getTime()) / 1_000,\n totalFilesChanged: taskResults.reduce((sum, r) => sum + r.execution.filesChanged.length, 0),\n totalLinesAdded: 0,\n totalLinesRemoved: 0,\n },\n };\n\n try {\n await writeContributionLog(contributionLog, resolvedRepo.localPath);\n } catch {\n // Non-fatal: log write failure shouldn't fail the run\n }\n\n // 9. Complete\n emit({ type: \"run:stage\", stage: \"completed\", message: \"Run completed successfully.\" });\n state.status = \"completed\";\n state.completedAt = new Date().toISOString();\n emit({ type: \"run:completed\", summary: { ...state } });\n\n return state;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n state.status = \"failed\";\n state.stage = \"failed\";\n state.error = message;\n state.completedAt = new Date().toISOString();\n emit({ type: \"run:error\", error: message });\n return state;\n }\n}\n\n\n","export function renderDashboardHtml(port: number): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>OAC Dashboard</title>\n <style>\n :root {\n --bg: #0a0a0a; --card: #111; --border: #222; --text: #e5e5e5;\n --muted: #888; --accent: #3b82f6; --green: #22c55e; --red: #ef4444;\n --yellow: #eab308; --purple: #a855f7;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", monospace; background: var(--bg); color: var(--text); }\n .container { max-width: 1200px; margin: 0 auto; padding: 24px; }\n header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 32px; border-bottom: 1px solid var(--border); padding-bottom: 16px; }\n header h1 { font-size: 24px; font-weight: 700; }\n header h1 span { color: var(--accent); }\n .badge { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; }\n .badge-idle { background: #1a1a2e; color: var(--muted); }\n .badge-running { background: #0a2a1a; color: var(--green); animation: pulse 2s infinite; }\n .badge-completed { background: #0a2a1a; color: var(--green); }\n .badge-failed { background: #2a0a0a; color: var(--red); }\n @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.6; } }\n .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px; }\n @media (max-width: 768px) { .grid { grid-template-columns: 1fr; } }\n .card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 20px; }\n .card h2 { font-size: 14px; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 16px; }\n .stat-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid var(--border); }\n .stat-row:last-child { border-bottom: none; }\n .stat-label { color: var(--muted); font-size: 13px; }\n .stat-value { font-weight: 600; font-size: 14px; }\n .full-width { grid-column: 1 / -1; }\n table { width: 100%; border-collapse: collapse; font-size: 13px; }\n th { text-align: left; color: var(--muted); font-weight: 500; padding: 8px 12px; border-bottom: 1px solid var(--border); }\n td { padding: 8px 12px; border-bottom: 1px solid var(--border); }\n tr:hover td { background: #1a1a1a; }\n .rank { color: var(--yellow); font-weight: 700; }\n .empty { color: var(--muted); text-align: center; padding: 40px 0; font-size: 14px; }\n .event-log { font-family: \"SF Mono\", \"Fira Code\", monospace; font-size: 12px; max-height: 200px; overflow-y: auto; padding: 12px; background: #050505; border-radius: 8px; }\n .event-line { padding: 2px 0; color: var(--muted); }\n .event-line .time { color: var(--accent); margin-right: 8px; }\n .connected { color: var(--green); }\n .toolbar { display: flex; gap: 8px; margin-bottom: 20px; }\n .btn { padding: 8px 16px; border-radius: 8px; border: 1px solid var(--border); background: var(--card); color: var(--text); cursor: pointer; font-size: 13px; transition: all 0.15s; }\n .btn:hover { background: #1a1a1a; border-color: var(--accent); }\n .btn:disabled { opacity: 0.4; cursor: not-allowed; }\n .btn-primary { background: var(--accent); border-color: var(--accent); color: white; }\n .btn-primary:hover:not(:disabled) { opacity: 0.9; }\n footer { text-align: center; padding: 32px 0 16px; color: var(--muted); font-size: 12px; }\n\n /* Start Run Form */\n .run-form { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }\n .run-form .form-group { display: flex; flex-direction: column; gap: 4px; }\n .run-form .form-group.full { grid-column: 1 / -1; }\n .run-form label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }\n .run-form input, .run-form select {\n padding: 8px 12px; border-radius: 8px; border: 1px solid var(--border);\n background: #050505; color: var(--text); font-size: 13px; font-family: inherit;\n }\n .run-form input:focus, .run-form select:focus { outline: none; border-color: var(--accent); }\n .run-form .form-actions { grid-column: 1 / -1; display: flex; gap: 8px; margin-top: 4px; }\n\n /* Stage Progress */\n .stage-pipeline { display: flex; align-items: center; gap: 0; margin: 16px 0; overflow-x: auto; padding-bottom: 4px; }\n .stage-dot { display: flex; align-items: center; gap: 0; white-space: nowrap; }\n .stage-dot .dot {\n width: 10px; height: 10px; border-radius: 50%; background: var(--border);\n flex-shrink: 0; transition: background 0.3s;\n }\n .stage-dot .dot.active { background: var(--accent); animation: pulse 1.5s infinite; }\n .stage-dot .dot.done { background: var(--green); }\n .stage-dot .dot.error { background: var(--red); }\n .stage-dot .label { font-size: 11px; color: var(--muted); margin-left: 4px; margin-right: 4px; }\n .stage-dot .label.active { color: var(--accent); font-weight: 600; }\n .stage-dot .label.done { color: var(--green); }\n .stage-connector { width: 16px; height: 1px; background: var(--border); flex-shrink: 0; }\n .stage-connector.done { background: var(--green); }\n\n /* Task Results */\n .task-results { margin-top: 12px; }\n .task-result { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--border); font-size: 13px; }\n .task-result:last-child { border-bottom: none; }\n .task-result .icon { font-size: 14px; }\n .task-result a { color: var(--accent); text-decoration: none; }\n .task-result a:hover { text-decoration: underline; }\n .task-result .meta { color: var(--muted); font-size: 12px; margin-left: auto; }\n\n .hidden { display: none !important; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <header>\n <h1><span>OAC</span> Dashboard</h1>\n <div>\n <span id=\"status-badge\" class=\"badge badge-idle\">idle</span>\n <span id=\"sse-indicator\" style=\"margin-left: 8px; font-size: 11px; color: var(--muted);\">connecting...</span>\n </div>\n </header>\n\n <!-- Start Run Form -->\n <div id=\"run-form-card\" class=\"card\" style=\"margin-bottom: 20px;\">\n <h2>Start Run</h2>\n <div class=\"run-form\">\n <div class=\"form-group full\">\n <label for=\"run-repo\">Repository (owner/repo or GitHub URL)</label>\n <input type=\"text\" id=\"run-repo\" placeholder=\"e.g. Open330/open-agent-contribution\" />\n </div>\n <div class=\"form-group\">\n <label for=\"run-provider\">Agent Provider</label>\n <select id=\"run-provider\">\n <option value=\"claude-code\">Claude Code</option>\n <option value=\"codex\">Codex CLI</option>\n </select>\n </div>\n <div class=\"form-group\">\n <label for=\"run-tokens-mode\">Token Budget</label>\n <select id=\"run-tokens-mode\" onchange=\"toggleTokenInput()\">\n <option value=\"fixed\">Fixed</option>\n <option value=\"unlimited\">Unlimited (until rate-limited)</option>\n </select>\n </div>\n <div class=\"form-group\" id=\"run-tokens-value-group\">\n <label for=\"run-tokens\">Token Amount</label>\n <input type=\"number\" id=\"run-tokens\" value=\"100000\" min=\"1000\" step=\"1000\" />\n </div>\n <div class=\"form-group\">\n <label for=\"run-max-tasks\">Max Tasks</label>\n <input type=\"number\" id=\"run-max-tasks\" value=\"5\" min=\"1\" max=\"50\" />\n </div>\n <div class=\"form-group\">\n <label for=\"run-concurrency\">Concurrency</label>\n <input type=\"number\" id=\"run-concurrency\" value=\"2\" min=\"1\" max=\"10\" />\n </div>\n <div class=\"form-group\">\n <label for=\"run-source\">Source Filter</label>\n <select id=\"run-source\">\n <option value=\"\">All sources</option>\n <option value=\"github-issue\">GitHub Issues</option>\n <option value=\"lint\">Lint warnings</option>\n <option value=\"todo\">To-do comments</option>\n <option value=\"test-gap\">Test gaps</option>\n </select>\n </div>\n <div class=\"form-actions\">\n <button id=\"run-start-btn\" class=\"btn btn-primary\" onclick=\"startRun()\">Start Run</button>\n <span id=\"run-error\" style=\"color: var(--red); font-size: 13px; align-self: center;\"></span>\n </div>\n </div>\n </div>\n\n <!-- Run Progress (shown during active run) -->\n <div id=\"run-progress-card\" class=\"card hidden\" style=\"margin-bottom: 20px;\">\n <h2>Run Progress</h2>\n <div id=\"stage-pipeline\" class=\"stage-pipeline\"></div>\n <div id=\"run-progress-stats\" style=\"margin-top: 8px;\">\n <div class=\"stat-row\"><span class=\"stat-label\">Tasks discovered</span><span class=\"stat-value\" id=\"prog-discovered\">0</span></div>\n <div class=\"stat-row\"><span class=\"stat-label\">Tasks selected</span><span class=\"stat-value\" id=\"prog-selected\">0</span></div>\n <div class=\"stat-row\"><span class=\"stat-label\">Completed</span><span class=\"stat-value\" id=\"prog-completed\">0</span></div>\n <div class=\"stat-row\"><span class=\"stat-label\">Failed</span><span class=\"stat-value\" id=\"prog-failed\">0</span></div>\n <div class=\"stat-row\"><span class=\"stat-label\">PRs created</span><span class=\"stat-value\" id=\"prog-prs\">0</span></div>\n <div class=\"stat-row\"><span class=\"stat-label\">Tokens used</span><span class=\"stat-value\" id=\"prog-tokens\">0</span></div>\n </div>\n <div id=\"task-results\" class=\"task-results\"></div>\n </div>\n\n <div class=\"toolbar\">\n <button class=\"btn\" onclick=\"fetchStatus()\">Refresh Status</button>\n <button class=\"btn\" onclick=\"fetchLogs()\">Refresh Logs</button>\n <button class=\"btn\" onclick=\"fetchLeaderboard()\">Refresh Leaderboard</button>\n </div>\n\n <div class=\"grid\">\n <div class=\"card\">\n <h2>Run Status</h2>\n <div id=\"status-content\">\n <div class=\"empty\">Loading...</div>\n </div>\n </div>\n\n <div class=\"card\">\n <h2>Quick Stats</h2>\n <div id=\"stats-content\">\n <div class=\"empty\">Loading...</div>\n </div>\n </div>\n\n <div class=\"card full-width\">\n <h2>Contribution Log</h2>\n <div id=\"logs-content\">\n <div class=\"empty\">Loading...</div>\n </div>\n </div>\n\n <div class=\"card full-width\">\n <h2>Leaderboard</h2>\n <div id=\"leaderboard-content\">\n <div class=\"empty\">Loading...</div>\n </div>\n </div>\n\n <div class=\"card full-width\">\n <h2>Event Stream</h2>\n <div id=\"event-log\" class=\"event-log\">\n <div class=\"event-line\" style=\"color: var(--muted);\">Connecting to SSE...</div>\n </div>\n </div>\n </div>\n\n <footer>OAC v0.1.0 — Open Agent Contribution</footer>\n </div>\n\n <script>\n const API = \"\";\n const STAGES = [\"resolving\",\"cloning\",\"scanning\",\"estimating\",\"planning\",\"executing\",\"creating-pr\",\"tracking\",\"completed\"];\n let activeRunId = null;\n\n function formatDate(iso) {\n if (!iso) return \"-\";\n try { return new Date(iso).toLocaleString(); } catch { return iso; }\n }\n\n // ---- Start Run ----\n\n function toggleTokenInput() {\n const mode = document.getElementById(\"run-tokens-mode\").value;\n const group = document.getElementById(\"run-tokens-value-group\");\n if (mode === \"unlimited\") {\n group.classList.add(\"hidden\");\n } else {\n group.classList.remove(\"hidden\");\n }\n }\n\n async function startRun() {\n const repo = document.getElementById(\"run-repo\").value.trim();\n const provider = document.getElementById(\"run-provider\").value;\n const tokensMode = document.getElementById(\"run-tokens-mode\").value;\n const tokens = tokensMode === \"unlimited\"\n ? 9007199254740991\n : parseInt(document.getElementById(\"run-tokens\").value, 10);\n const maxTasks = parseInt(document.getElementById(\"run-max-tasks\").value, 10);\n const concurrency = parseInt(document.getElementById(\"run-concurrency\").value, 10) || 2;\n const source = document.getElementById(\"run-source\").value || undefined;\n const errEl = document.getElementById(\"run-error\");\n const btn = document.getElementById(\"run-start-btn\");\n\n errEl.textContent = \"\";\n if (!repo) { errEl.textContent = \"Repository is required\"; return; }\n if (tokensMode !== \"unlimited\" && (!tokens || tokens < 1000)) { errEl.textContent = \"Token budget must be >= 1000\"; return; }\n\n btn.disabled = true;\n btn.textContent = \"Starting...\";\n\n try {\n const res = await fetch(API + \"/api/v1/runs\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ repo, provider, tokens, maxTasks, concurrency, source }),\n });\n const data = await res.json();\n\n if (!res.ok) {\n errEl.textContent = data.error || \"Failed to start run\";\n btn.disabled = false;\n btn.textContent = \"Start Run\";\n return;\n }\n\n activeRunId = data.runId;\n showProgressCard();\n } catch (e) {\n errEl.textContent = \"Network error: \" + e.message;\n btn.disabled = false;\n btn.textContent = \"Start Run\";\n }\n }\n\n function showProgressCard() {\n document.getElementById(\"run-progress-card\").classList.remove(\"hidden\");\n initStageList();\n document.getElementById(\"task-results\").innerHTML = \"\";\n document.getElementById(\"prog-discovered\").textContent = \"0\";\n document.getElementById(\"prog-selected\").textContent = \"0\";\n document.getElementById(\"prog-completed\").textContent = \"0\";\n document.getElementById(\"prog-failed\").textContent = \"0\";\n document.getElementById(\"prog-prs\").textContent = \"0\";\n document.getElementById(\"prog-tokens\").textContent = \"0\";\n\n const badge = document.getElementById(\"status-badge\");\n badge.className = \"badge badge-running\";\n badge.textContent = \"running\";\n }\n\n function initStageList() {\n const pipeline = document.getElementById(\"stage-pipeline\");\n pipeline.innerHTML = \"\";\n STAGES.forEach((stage, i) => {\n const dot = document.createElement(\"div\");\n dot.className = \"stage-dot\";\n dot.innerHTML = '<div class=\"dot\" id=\"dot-' + stage + '\"></div><span class=\"label\" id=\"label-' + stage + '\">' + stage + '</span>';\n pipeline.appendChild(dot);\n if (i < STAGES.length - 1) {\n const conn = document.createElement(\"div\");\n conn.className = \"stage-connector\";\n conn.id = \"conn-\" + stage;\n pipeline.appendChild(conn);\n }\n });\n }\n\n function updateStage(currentStage) {\n let reached = false;\n STAGES.forEach((stage, i) => {\n const dot = document.getElementById(\"dot-\" + stage);\n const label = document.getElementById(\"label-\" + stage);\n const conn = i < STAGES.length - 1 ? document.getElementById(\"conn-\" + stage) : null;\n\n if (!dot || !label) return;\n\n if (stage === currentStage) {\n reached = true;\n dot.className = currentStage === \"completed\" ? \"dot done\" : \"dot active\";\n label.className = currentStage === \"completed\" ? \"label done\" : \"label active\";\n if (conn && currentStage === \"completed\") conn.className = \"stage-connector done\";\n } else if (!reached) {\n dot.className = \"dot done\";\n label.className = \"label done\";\n if (conn) conn.className = \"stage-connector done\";\n } else {\n dot.className = \"dot\";\n label.className = \"label\";\n if (conn) conn.className = \"stage-connector\";\n }\n });\n }\n\n function updateProgress(progress) {\n document.getElementById(\"prog-discovered\").textContent = progress.tasksDiscovered || 0;\n document.getElementById(\"prog-selected\").textContent = progress.tasksSelected || 0;\n document.getElementById(\"prog-completed\").textContent = progress.tasksCompleted || 0;\n document.getElementById(\"prog-failed\").textContent = progress.tasksFailed || 0;\n document.getElementById(\"prog-prs\").textContent = progress.prsCreated || 0;\n document.getElementById(\"prog-tokens\").textContent = (progress.tokensUsed || 0).toLocaleString();\n }\n\n function addTaskResult(data) {\n const container = document.getElementById(\"task-results\");\n const div = document.createElement(\"div\");\n div.className = \"task-result\";\n const icon = data.success ? \"\\\\u2705\" : \"\\\\u274c\";\n let html = '<span class=\"icon\">' + icon + '</span><span>' + escapeHtml(data.title) + '</span>';\n if (data.prUrl) {\n html += ' <a href=\"' + escapeHtml(data.prUrl) + '\" target=\"_blank\">PR \\\\u2197</a>';\n }\n html += '<span class=\"meta\">' + (data.filesChanged || 0) + ' files</span>';\n div.innerHTML = html;\n container.appendChild(div);\n }\n\n function onRunCompleted(isError) {\n const btn = document.getElementById(\"run-start-btn\");\n btn.disabled = false;\n btn.textContent = \"Start Run\";\n activeRunId = null;\n\n const badge = document.getElementById(\"status-badge\");\n if (isError) {\n badge.className = \"badge badge-failed\";\n badge.textContent = \"failed\";\n } else {\n badge.className = \"badge badge-completed\";\n badge.textContent = \"completed\";\n }\n\n // Refresh data\n fetchStatus();\n fetchLogs();\n fetchLeaderboard();\n }\n\n function onRunError(errorMsg) {\n const container = document.getElementById(\"task-results\");\n const div = document.createElement(\"div\");\n div.className = \"task-result\";\n div.innerHTML = '<span class=\"icon\">\\\\u274c</span><span style=\"color: var(--red);\">Error: ' + escapeHtml(errorMsg) + '</span>';\n container.appendChild(div);\n\n // Mark failed stage\n STAGES.forEach((stage) => {\n const dot = document.getElementById(\"dot-\" + stage);\n if (dot && dot.className === \"dot active\") {\n dot.className = \"dot error\";\n }\n });\n\n onRunCompleted(true);\n }\n\n function escapeHtml(str) {\n const div = document.createElement(\"div\");\n div.textContent = str || \"\";\n return div.innerHTML;\n }\n\n // ---- Fetch functions ----\n\n async function fetchStatus() {\n try {\n const res = await fetch(API + \"/api/v1/status\");\n const data = await res.json();\n const badge = document.getElementById(\"status-badge\");\n\n if (data.status === \"running\") {\n badge.className = \"badge badge-running\";\n badge.textContent = \"running\";\n // Restore progress card if page was refreshed mid-run\n if (!activeRunId) {\n activeRunId = data.runId;\n showProgressCard();\n if (data.stage) updateStage(data.stage);\n if (data.progress) updateProgress(data.progress);\n }\n } else if (data.status === \"idle\" && !activeRunId) {\n badge.className = \"badge badge-idle\";\n badge.textContent = \"idle\";\n }\n\n let html = \"\";\n const displayKeys = [\"status\", \"stage\", \"runId\", \"startedAt\", \"completedAt\", \"error\"];\n for (const key of displayKeys) {\n if (data[key] !== undefined) {\n html += '<div class=\"stat-row\"><span class=\"stat-label\">' + key + '</span><span class=\"stat-value\">' + escapeHtml(String(data[key])) + '</span></div>';\n }\n }\n if (!html) {\n for (const [key, value] of Object.entries(data)) {\n html += '<div class=\"stat-row\"><span class=\"stat-label\">' + key + '</span><span class=\"stat-value\">' + escapeHtml(String(value)) + '</span></div>';\n }\n }\n document.getElementById(\"status-content\").innerHTML = html || '<div class=\"empty\">No status data</div>';\n } catch (e) {\n document.getElementById(\"status-content\").innerHTML = '<div class=\"empty\">Failed to load status</div>';\n }\n }\n\n async function fetchLogs() {\n try {\n const res = await fetch(API + \"/api/v1/logs\");\n const data = await res.json();\n\n if (!data.logs || data.logs.length === 0) {\n document.getElementById(\"logs-content\").innerHTML = '<div class=\"empty\">No contributions yet. Run <code>oac run</code> to start contributing!</div>';\n document.getElementById(\"stats-content\").innerHTML = [\n '<div class=\"stat-row\"><span class=\"stat-label\">Total Runs</span><span class=\"stat-value\">0</span></div>',\n '<div class=\"stat-row\"><span class=\"stat-label\">Total Tasks</span><span class=\"stat-value\">0</span></div>',\n '<div class=\"stat-row\"><span class=\"stat-label\">Total Tokens</span><span class=\"stat-value\">0</span></div>',\n '<div class=\"stat-row\"><span class=\"stat-label\">PRs Created</span><span class=\"stat-value\">0</span></div>',\n ].join(\"\");\n return;\n }\n\n const totalRuns = data.logs.length;\n const totalTasks = data.logs.reduce((s, l) => s + (l.tasks?.length || 0), 0);\n const totalTokens = data.logs.reduce((s, l) => s + (l.budget?.totalTokensUsed || 0), 0);\n const totalPRs = data.logs.reduce((s, l) => s + (l.tasks?.filter(t => t.pr).length || 0), 0);\n\n document.getElementById(\"stats-content\").innerHTML = [\n '<div class=\"stat-row\"><span class=\"stat-label\">Total Runs</span><span class=\"stat-value\">' + totalRuns + '</span></div>',\n '<div class=\"stat-row\"><span class=\"stat-label\">Total Tasks</span><span class=\"stat-value\">' + totalTasks + '</span></div>',\n '<div class=\"stat-row\"><span class=\"stat-label\">Total Tokens</span><span class=\"stat-value\">' + totalTokens.toLocaleString() + '</span></div>',\n '<div class=\"stat-row\"><span class=\"stat-label\">PRs Created</span><span class=\"stat-value\">' + totalPRs + '</span></div>',\n ].join(\"\");\n\n let html = '<table><thead><tr><th>Date</th><th>Repo</th><th>Tasks</th><th>Tokens</th><th>Agent</th></tr></thead><tbody>';\n for (const log of data.logs.slice(0, 20)) {\n html += '<tr>';\n html += '<td>' + formatDate(log.timestamp) + '</td>';\n html += '<td>' + escapeHtml(log.repo?.fullName || log.repoFullName || \"-\") + '</td>';\n html += '<td>' + (log.tasks?.length || 0) + '</td>';\n html += '<td>' + (log.budget?.totalTokensUsed || 0).toLocaleString() + '</td>';\n html += '<td>' + escapeHtml(log.budget?.provider || log.agentProvider || \"-\") + '</td>';\n html += '</tr>';\n }\n html += '</tbody></table>';\n document.getElementById(\"logs-content\").innerHTML = html;\n } catch (e) {\n document.getElementById(\"logs-content\").innerHTML = '<div class=\"empty\">Failed to load logs</div>';\n }\n }\n\n async function fetchLeaderboard() {\n try {\n const res = await fetch(API + \"/api/v1/leaderboard\");\n const data = await res.json();\n\n if (!data.entries || data.entries.length === 0) {\n document.getElementById(\"leaderboard-content\").innerHTML = '<div class=\"empty\">No contributors yet</div>';\n return;\n }\n\n let html = '<table><thead><tr><th>#</th><th>User</th><th>Tasks</th><th>Tokens</th><th>PRs</th><th>Last Active</th></tr></thead><tbody>';\n data.entries.forEach((entry, i) => {\n html += '<tr>';\n html += '<td class=\"rank\">' + (i + 1) + '</td>';\n html += '<td>' + escapeHtml(entry.githubUsername || \"anonymous\") + '</td>';\n html += '<td>' + (entry.totalTasksCompleted || 0) + '</td>';\n html += '<td>' + (entry.totalTokensDonated || 0).toLocaleString() + '</td>';\n html += '<td>' + (entry.totalPRsCreated || 0) + '</td>';\n html += '<td>' + formatDate(entry.lastContribution) + '</td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n document.getElementById(\"leaderboard-content\").innerHTML = html;\n } catch (e) {\n document.getElementById(\"leaderboard-content\").innerHTML = '<div class=\"empty\">Failed to load leaderboard</div>';\n }\n }\n\n // ---- SSE Connection ----\n\n function connectSSE() {\n const indicator = document.getElementById(\"sse-indicator\");\n const log = document.getElementById(\"event-log\");\n\n const es = new EventSource(API + \"/api/v1/events\");\n\n es.addEventListener(\"connected\", (e) => {\n indicator.innerHTML = '<span class=\"connected\">\\\\u25cf connected</span>';\n addEventLine(log, \"connected\", \"SSE stream connected\");\n });\n\n es.addEventListener(\"heartbeat\", (e) => {\n // Silent heartbeat — no log spam\n });\n\n // Run events\n es.addEventListener(\"run:stage\", (e) => {\n try {\n const data = JSON.parse(e.data);\n updateStage(data.stage);\n addEventLine(log, \"stage\", data.message || data.stage);\n } catch {}\n });\n\n es.addEventListener(\"run:progress\", (e) => {\n try {\n const data = JSON.parse(e.data);\n updateProgress(data.progress);\n } catch {}\n });\n\n es.addEventListener(\"run:task-start\", (e) => {\n try {\n const data = JSON.parse(e.data);\n addEventLine(log, \"task\", \"Starting: \" + data.title);\n } catch {}\n });\n\n es.addEventListener(\"run:task-done\", (e) => {\n try {\n const data = JSON.parse(e.data);\n const status = data.success ? \"OK\" : \"FAILED\";\n let msg = \"[\" + status + \"] \" + data.title;\n if (data.prUrl) msg += \" - PR: \" + data.prUrl;\n addEventLine(log, \"task\", msg);\n addTaskResult(data);\n } catch {}\n });\n\n es.addEventListener(\"run:completed\", (e) => {\n try {\n addEventLine(log, \"completed\", \"Run finished successfully\");\n updateStage(\"completed\");\n onRunCompleted(false);\n } catch {}\n });\n\n es.addEventListener(\"run:error\", (e) => {\n try {\n const data = JSON.parse(e.data);\n addEventLine(log, \"error\", data.error);\n onRunError(data.error);\n } catch {}\n });\n\n es.onerror = () => {\n indicator.innerHTML = '<span style=\"color: var(--red);\">\\\\u25cf disconnected</span>';\n addEventLine(log, \"error\", \"SSE disconnected, reconnecting...\");\n };\n\n es.onmessage = (e) => {\n addEventLine(log, \"message\", e.data);\n };\n }\n\n function addEventLine(container, type, data) {\n const now = new Date().toLocaleTimeString();\n const line = document.createElement(\"div\");\n line.className = \"event-line\";\n line.innerHTML = '<span class=\"time\">' + now + '</span> <strong>' + type + '</strong> ' + escapeHtml(String(data));\n container.appendChild(line);\n container.scrollTop = container.scrollHeight;\n\n while (container.children.length > 100) {\n container.removeChild(container.firstChild);\n }\n }\n\n // ---- Init ----\n fetchStatus();\n fetchLogs();\n fetchLeaderboard();\n connectSSE();\n\n setInterval(() => { fetchStatus(); fetchLogs(); }, 30000);\n </script>\n</body>\n</html>`;\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["randomUUID","result","randomUUID"]}
|
|
1
|
+
{"version":3,"sources":["../../src/dashboard/server.ts","../../src/dashboard/pipeline.ts","../../src/dashboard/ui.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { Dirent } from \"node:fs\";\nimport { readFile, readdir } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport cors from \"@fastify/cors\";\nimport Fastify from \"fastify\";\nimport { contributionLogSchema } from \"../tracking/index.js\";\nimport type { ContributionLog } from \"../tracking/index.js\";\nimport { buildLeaderboard } from \"../tracking/index.js\";\nimport {\n type DashboardRunEvent,\n type RunConfig,\n type RunState,\n executePipeline,\n} from \"./pipeline.js\";\nimport { renderDashboardHtml } from \"./ui.js\";\n\nexport interface DashboardOptions {\n port: number;\n host: string;\n openBrowser: boolean;\n oacDir: string;\n}\n\nconst DEFAULT_OPTIONS: DashboardOptions = {\n port: 3141,\n host: \"0.0.0.0\",\n openBrowser: false,\n oacDir: process.cwd(),\n};\n\n// ---------------------------------------------------------------------------\n// Run state management (single-run mode)\n// ---------------------------------------------------------------------------\n\nconst MAX_SSE_CLIENTS = 50;\n\nlet currentRun: RunState | null = null;\nconst sseClients = new Set<(event: DashboardRunEvent) => void>();\n\nfunction broadcastEvent(event: DashboardRunEvent): void {\n for (const send of sseClients) {\n try {\n send(event);\n } catch {\n // Client disconnected — will be cleaned up on close\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// File readers\n// ---------------------------------------------------------------------------\n\nasync function readContributionLogs(oacDir: string): Promise<ContributionLog[]> {\n const contributionsPath = resolve(oacDir, \".oac\", \"contributions\");\n\n let entries: Dirent[];\n try {\n entries = await readdir(contributionsPath, { withFileTypes: true, encoding: \"utf8\" });\n } catch {\n return [];\n }\n\n const files = entries\n .filter((entry) => entry.isFile() && entry.name.endsWith(\".json\"))\n .map((entry) => entry.name)\n .sort();\n\n const results = await Promise.all(\n files.map(async (fileName) => {\n try {\n const content = await readFile(resolve(contributionsPath, fileName), \"utf8\");\n const parsed = contributionLogSchema.safeParse(JSON.parse(content));\n return parsed.success ? parsed.data : null;\n } catch {\n return null;\n }\n }),\n );\n\n return results.filter((log): log is ContributionLog => log !== null);\n}\n\nasync function readRunStatus(oacDir: string): Promise<unknown> {\n // Return live run state if a run is active\n if (currentRun) {\n return currentRun;\n }\n\n try {\n const content = await readFile(resolve(oacDir, \".oac\", \"status.json\"), \"utf8\");\n return JSON.parse(content);\n } catch {\n return { status: \"idle\", message: \"No active runs\" };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Server\n// ---------------------------------------------------------------------------\n\nexport async function createDashboardServer(\n options: Partial<DashboardOptions> = {},\n): Promise<ReturnType<typeof Fastify>> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const app = Fastify({ logger: false });\n\n await app.register(cors, { origin: true });\n\n // --- HTML Dashboard ---\n app.get(\"/\", async (_request, reply) => {\n reply.type(\"text/html\").send(renderDashboardHtml(opts.port));\n });\n\n // --- API Routes ---\n app.get(\"/api/v1/status\", async () => {\n return readRunStatus(opts.oacDir);\n });\n\n app.get(\"/api/v1/logs\", async () => {\n const logs = await readContributionLogs(opts.oacDir);\n return { count: logs.length, logs };\n });\n\n app.get(\"/api/v1/leaderboard\", async () => {\n const leaderboard = await buildLeaderboard(opts.oacDir);\n return leaderboard;\n });\n\n app.get(\"/api/v1/config\", async () => {\n return {\n oacDir: opts.oacDir,\n port: opts.port,\n host: opts.host,\n };\n });\n\n // --- Start Run ---\n app.post(\"/api/v1/runs\", async (request, reply) => {\n // Only one run at a time\n if (currentRun && currentRun.status === \"running\") {\n reply.code(409).send({ error: \"A run is already in progress\", runId: currentRun.runId });\n return;\n }\n\n const body = request.body as Partial<RunConfig> | null;\n if (!body?.repo || !body.provider || !body.tokens) {\n reply.code(400).send({ error: \"Missing required fields: repo, provider, tokens\" });\n return;\n }\n\n const config: RunConfig = {\n repo: body.repo,\n provider: body.provider,\n tokens: body.tokens,\n concurrency: typeof body.concurrency === \"number\" ? body.concurrency : undefined,\n maxTasks: body.maxTasks,\n source: body.source,\n };\n\n // Initialize run state\n const runId = randomUUID();\n currentRun = {\n runId,\n status: \"running\",\n stage: \"resolving\",\n config,\n startedAt: new Date().toISOString(),\n progress: {\n tasksDiscovered: 0,\n tasksSelected: 0,\n tasksCompleted: 0,\n tasksFailed: 0,\n prsCreated: 0,\n tokensUsed: 0,\n prUrls: [],\n },\n };\n\n // Fire-and-forget: pipeline runs in background\n executePipeline(config, (event) => {\n // Update currentRun from events\n if (event.type === \"run:stage\" && currentRun) {\n currentRun.stage = event.stage;\n }\n if (event.type === \"run:progress\" && currentRun) {\n currentRun.progress = event.progress;\n }\n if (event.type === \"run:completed\" && currentRun) {\n currentRun.status = \"completed\";\n currentRun.completedAt = new Date().toISOString();\n }\n if (event.type === \"run:error\" && currentRun) {\n currentRun.status = \"failed\";\n currentRun.error = event.error;\n currentRun.completedAt = new Date().toISOString();\n }\n\n // Broadcast to all SSE clients\n broadcastEvent(event);\n }).catch((err) => {\n if (currentRun) {\n currentRun.status = \"failed\";\n currentRun.error = err instanceof Error ? err.message : String(err);\n currentRun.completedAt = new Date().toISOString();\n }\n });\n\n reply.code(202).send({ runId, status: \"started\" });\n });\n\n // --- SSE Event Stream ---\n app.get(\"/api/v1/events\", async (_request, reply) => {\n reply.raw.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n\n reply.raw.write(\n `event: connected\\ndata: ${JSON.stringify({ time: new Date().toISOString() })}\\n\\n`,\n );\n\n // Register for run event broadcasts\n const sendEvent = (event: DashboardRunEvent) => {\n reply.raw.write(`event: ${event.type}\\ndata: ${JSON.stringify(event)}\\n\\n`);\n };\n if (sseClients.size >= MAX_SSE_CLIENTS) {\n reply.status(503).send({ error: \"Too many SSE connections\" });\n return;\n }\n sseClients.add(sendEvent);\n\n const interval = setInterval(() => {\n reply.raw.write(\n `event: heartbeat\\ndata: ${JSON.stringify({ time: new Date().toISOString() })}\\n\\n`,\n );\n }, 10_000);\n\n _request.raw.on(\"close\", () => {\n clearInterval(interval);\n sseClients.delete(sendEvent);\n });\n });\n\n return app;\n}\n\nexport async function startDashboard(options: Partial<DashboardOptions> = {}): Promise<void> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const app = await createDashboardServer(opts);\n\n await app.listen({ port: opts.port, host: opts.host });\n const url =\n opts.host === \"0.0.0.0\" ? `http://localhost:${opts.port}` : `http://${opts.host}:${opts.port}`;\n console.log(`\\n 🚀 OAC Dashboard running at ${url}`);\n console.log(` Network: http://0.0.0.0:${opts.port}`);\n console.log(`\\n API: ${url}/api/v1/status`);\n console.log(` SSE: ${url}/api/v1/events\\n`);\n\n if (opts.openBrowser) {\n const { execFile } = await import(\"node:child_process\");\n const command =\n process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"start\" : \"xdg-open\";\n execFile(command, [url], (err) => {\n if (err) {\n // Non-critical — browser opening is best-effort.\n console.warn(`Could not open browser: ${err.message}`);\n }\n });\n }\n}\n","import { randomUUID } from \"node:crypto\";\n\nimport { execa } from \"execa\";\nimport PQueue from \"p-queue\";\nimport { buildExecutionPlan, estimateTokens } from \"../budget/index.js\";\nimport { type Task, type TokenEstimate, createEventBus } from \"../core/index.js\";\nimport {\n CompositeScanner,\n GitHubIssuesScanner,\n LintScanner,\n type Scanner,\n TodoScanner,\n rankTasks,\n} from \"../discovery/index.js\";\nimport {\n CodexAdapter,\n createSandbox,\n executeTask as workerExecuteTask,\n} from \"../execution/index.js\";\nimport { cloneRepo, resolveRepo } from \"../repo/index.js\";\nimport { type ContributionLog, writeContributionLog } from \"../tracking/index.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface RunConfig {\n repo: string;\n provider: string;\n tokens: number;\n concurrency?: number;\n maxTasks?: number;\n source?: string;\n}\n\nexport type RunStage =\n | \"resolving\"\n | \"cloning\"\n | \"scanning\"\n | \"estimating\"\n | \"planning\"\n | \"executing\"\n | \"creating-pr\"\n | \"tracking\"\n | \"completed\"\n | \"failed\";\n\nexport interface RunProgress {\n tasksDiscovered: number;\n tasksSelected: number;\n tasksCompleted: number;\n tasksFailed: number;\n prsCreated: number;\n tokensUsed: number;\n currentTask?: string;\n prUrls: string[];\n}\n\nexport interface RunState {\n runId: string;\n status: \"running\" | \"completed\" | \"failed\";\n stage: RunStage;\n config: RunConfig;\n startedAt: string;\n completedAt?: string;\n error?: string;\n progress: RunProgress;\n}\n\nexport type DashboardRunEvent =\n | { type: \"run:stage\"; stage: RunStage; message: string }\n | { type: \"run:progress\"; progress: RunProgress }\n | { type: \"run:task-start\"; taskId: string; title: string }\n | {\n type: \"run:task-done\";\n taskId: string;\n title: string;\n success: boolean;\n prUrl?: string;\n filesChanged: number;\n }\n | { type: \"run:completed\"; summary: RunState }\n | { type: \"run:error\"; error: string };\n\nexport type RunEventCallback = (event: DashboardRunEvent) => void;\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\ninterface ExecutionOutcome {\n success: boolean;\n exitCode: number;\n totalTokensUsed: number;\n filesChanged: string[];\n duration: number;\n error?: string;\n}\n\ninterface SandboxInfo {\n branchName: string;\n sandboxPath: string;\n cleanup: () => Promise<void>;\n}\n\ninterface TaskResult {\n task: Task;\n execution: ExecutionOutcome;\n sandbox?: SandboxInfo;\n pr?: { number: number; url: string; status: \"open\" | \"merged\" | \"closed\" };\n}\n\nfunction buildScanners(): { names: string[]; scanner: CompositeScanner } {\n const scanners: Scanner[] = [new LintScanner(), new TodoScanner()];\n const names = [\"lint\", \"todo\"];\n\n if (process.env.GITHUB_TOKEN) {\n scanners.push(new GitHubIssuesScanner());\n names.push(\"github-issues\");\n }\n\n return { names, scanner: new CompositeScanner(scanners) };\n}\n\nasync function executeWithCodex(input: {\n task: Task;\n estimate: TokenEstimate;\n codexAdapter: CodexAdapter;\n repoPath: string;\n baseBranch: string;\n timeoutSeconds: number;\n}): Promise<{ execution: ExecutionOutcome; sandbox: SandboxInfo }> {\n const startedAt = Date.now();\n const taskSlug = input.task.id\n .replace(/[^a-zA-Z0-9-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .slice(0, 30);\n const branchName = `oac/${Date.now()}-${taskSlug}`;\n\n const sandbox = await createSandbox(input.repoPath, branchName, input.baseBranch);\n const eventBus = createEventBus();\n const sandboxInfo: SandboxInfo = {\n branchName,\n sandboxPath: sandbox.path,\n cleanup: sandbox.cleanup,\n };\n\n try {\n const result = await workerExecuteTask(input.codexAdapter, input.task, sandbox, eventBus, {\n tokenBudget: input.estimate.totalEstimatedTokens,\n timeoutMs: input.timeoutSeconds * 1_000,\n });\n\n const commitResult = await commitSandboxChanges(sandbox.path, input.task);\n const filesChanged =\n commitResult.filesChanged.length > 0\n ? commitResult.filesChanged\n : result.filesChanged.length > 0\n ? result.filesChanged\n : [];\n\n return {\n execution: {\n success: result.success || commitResult.hasChanges,\n exitCode: result.exitCode,\n totalTokensUsed: result.totalTokensUsed,\n filesChanged,\n duration: result.duration > 0 ? result.duration / 1_000 : (Date.now() - startedAt) / 1_000,\n error: result.error,\n },\n sandbox: sandboxInfo,\n };\n } catch (error) {\n const commitResult = await commitSandboxChanges(sandbox.path, input.task);\n if (commitResult.hasChanges) {\n return {\n execution: {\n success: true,\n exitCode: 0,\n totalTokensUsed: 0,\n filesChanged: commitResult.filesChanged,\n duration: (Date.now() - startedAt) / 1_000,\n },\n sandbox: sandboxInfo,\n };\n }\n\n const message = error instanceof Error ? error.message : String(error);\n return {\n execution: {\n success: false,\n exitCode: 1,\n totalTokensUsed: 0,\n filesChanged: [],\n duration: (Date.now() - startedAt) / 1_000,\n error: message,\n },\n sandbox: sandboxInfo,\n };\n }\n}\n\nasync function commitSandboxChanges(\n sandboxPath: string,\n task: Task,\n): Promise<{ hasChanges: boolean; filesChanged: string[] }> {\n try {\n const statusResult = await execa(\"git\", [\"status\", \"--porcelain\"], { cwd: sandboxPath });\n if (!statusResult.stdout.trim()) {\n return { hasChanges: false, filesChanged: [] };\n }\n\n await execa(\"git\", [\"add\", \"-A\"], { cwd: sandboxPath });\n await execa(\"git\", [\"commit\", \"-m\", `[OAC] ${task.title}\\n\\nAutomated contribution by OAC.`], {\n cwd: sandboxPath,\n });\n\n const diffResult = await execa(\"git\", [\"diff\", \"--name-only\", \"HEAD~1\", \"HEAD\"], {\n cwd: sandboxPath,\n });\n const changedFiles = diffResult.stdout.trim().split(\"\\n\").filter(Boolean);\n return { hasChanges: true, filesChanged: changedFiles };\n } catch {\n return { hasChanges: false, filesChanged: [] };\n }\n}\n\nasync function resolveGitHubToken(): Promise<string | undefined> {\n if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;\n if (process.env.GH_TOKEN) return process.env.GH_TOKEN;\n try {\n const result = await execa(\"gh\", [\"auth\", \"token\"], { reject: false, timeout: 5_000 });\n if (result.exitCode === 0 && result.stdout.trim().length > 0) return result.stdout.trim();\n } catch {\n // gh not available\n }\n return undefined;\n}\n\nasync function createPullRequest(input: {\n task: Task;\n execution: ExecutionOutcome;\n sandbox: SandboxInfo;\n repoFullName: string;\n baseBranch: string;\n}): Promise<{ number: number; url: string; status: \"open\" | \"merged\" | \"closed\" } | undefined> {\n const { branchName, sandboxPath } = input.sandbox;\n\n try {\n // Resolve token upfront to avoid interactive device flow\n const ghToken = await resolveGitHubToken();\n const ghEnv: Record<string, string> = { ...process.env } as Record<string, string>;\n if (ghToken) {\n ghEnv.GH_TOKEN = ghToken;\n ghEnv.GITHUB_TOKEN = ghToken;\n }\n\n await execa(\"git\", [\"push\", \"--set-upstream\", \"origin\", branchName], {\n cwd: sandboxPath,\n env: ghEnv,\n });\n\n const prTitle = `[OAC] ${input.task.title}`;\n const prBody = [\n \"## Summary\",\n \"\",\n input.task.description || `Automated contribution for task \"${input.task.title}\".`,\n \"\",\n \"## Context\",\n \"\",\n `- **Task source:** ${input.task.source}`,\n `- **Complexity:** ${input.task.complexity}`,\n `- **Tokens used:** ${input.execution.totalTokensUsed}`,\n `- **Files changed:** ${input.execution.filesChanged.length}`,\n \"\",\n \"---\",\n \"*This PR was automatically generated by [OAC](https://github.com/Open330/open-agent-contribution).*\",\n ].join(\"\\n\");\n\n const ghResult = await execa(\n \"gh\",\n [\n \"pr\",\n \"create\",\n \"--repo\",\n input.repoFullName,\n \"--title\",\n prTitle,\n \"--body\",\n prBody,\n \"--head\",\n branchName,\n \"--base\",\n input.baseBranch,\n ],\n { cwd: sandboxPath, env: ghEnv },\n );\n\n const prUrl = ghResult.stdout.trim();\n const prNumberMatch = prUrl.match(/\\/pull\\/(\\d+)/);\n const prNumber = prNumberMatch ? Number.parseInt(prNumberMatch[1], 10) : 0;\n\n return { number: prNumber, url: prUrl, status: \"open\" };\n } catch {\n return undefined;\n }\n}\n\nfunction resolveGithubUsername(): string {\n const candidates = [\n process.env.GITHUB_USER,\n process.env.GITHUB_USERNAME,\n process.env.USER,\n process.env.LOGNAME,\n ];\n\n for (const c of candidates) {\n const cleaned = c\n ?.trim()\n .replace(/[^A-Za-z0-9-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n if (cleaned && cleaned.length <= 39 && /^(?!-)[A-Za-z0-9-]+(?<!-)$/.test(cleaned)) {\n return cleaned;\n }\n }\n\n return \"oac-user\";\n}\n\n// ---------------------------------------------------------------------------\n// Main Pipeline\n// ---------------------------------------------------------------------------\n\nexport async function executePipeline(\n config: RunConfig,\n onEvent: RunEventCallback,\n): Promise<RunState> {\n const runId = randomUUID();\n const startedAt = new Date().toISOString();\n const progress: RunProgress = {\n tasksDiscovered: 0,\n tasksSelected: 0,\n tasksCompleted: 0,\n tasksFailed: 0,\n prsCreated: 0,\n tokensUsed: 0,\n prUrls: [],\n };\n\n const state: RunState = {\n runId,\n status: \"running\",\n stage: \"resolving\",\n config,\n startedAt,\n progress,\n };\n\n const emit = (event: DashboardRunEvent) => {\n if (event.type === \"run:stage\") state.stage = event.stage;\n if (event.type === \"run:progress\") state.progress = event.progress;\n onEvent(event);\n };\n\n try {\n // 0. Ensure GitHub auth before any API calls\n const preAuthToken = await resolveGitHubToken();\n if (preAuthToken && !process.env.GITHUB_TOKEN && !process.env.GH_TOKEN) {\n process.env.GITHUB_TOKEN = preAuthToken;\n }\n\n // 1. Resolve repo\n emit({ type: \"run:stage\", stage: \"resolving\", message: `Resolving ${config.repo}...` });\n const resolvedRepo = await resolveRepo(config.repo);\n\n // 2. Clone if not local\n emit({ type: \"run:stage\", stage: \"cloning\", message: `Cloning ${resolvedRepo.fullName}...` });\n await cloneRepo(resolvedRepo);\n\n // 3. Scan\n const { names, scanner } = buildScanners();\n emit({\n type: \"run:stage\",\n stage: \"scanning\",\n message: `Scanning with ${names.join(\", \")}...`,\n });\n const scannedTasks = await scanner.scan(resolvedRepo.localPath, {\n repo: resolvedRepo,\n });\n\n // 4. Rank & filter\n let candidateTasks = rankTasks(scannedTasks).filter((t) => t.priority >= 20);\n if (config.source) {\n candidateTasks = candidateTasks.filter((t) => t.source === config.source);\n }\n if (config.maxTasks) {\n candidateTasks = candidateTasks.slice(0, config.maxTasks);\n }\n progress.tasksDiscovered = candidateTasks.length;\n emit({ type: \"run:progress\", progress: { ...progress } });\n\n if (candidateTasks.length === 0) {\n emit({ type: \"run:stage\", stage: \"completed\", message: \"No tasks discovered.\" });\n state.status = \"completed\";\n state.completedAt = new Date().toISOString();\n emit({ type: \"run:completed\", summary: { ...state } });\n return state;\n }\n\n // 5. Estimate tokens\n emit({\n type: \"run:stage\",\n stage: \"estimating\",\n message: `Estimating tokens for ${candidateTasks.length} task(s)...`,\n });\n const estimates = new Map<string, TokenEstimate>();\n for (const task of candidateTasks) {\n const est = await estimateTokens(task, config.provider);\n estimates.set(task.id, est);\n }\n\n // 6. Build execution plan\n emit({ type: \"run:stage\", stage: \"planning\", message: \"Building execution plan...\" });\n const plan = buildExecutionPlan(candidateTasks, estimates, config.tokens);\n progress.tasksSelected = plan.selectedTasks.length;\n emit({ type: \"run:progress\", progress: { ...progress } });\n\n if (plan.selectedTasks.length === 0) {\n emit({ type: \"run:stage\", stage: \"completed\", message: \"No tasks fit within budget.\" });\n state.status = \"completed\";\n state.completedAt = new Date().toISOString();\n emit({ type: \"run:completed\", summary: { ...state } });\n return state;\n }\n\n // 7. Execute tasks (with concurrency)\n const concurrency = Math.max(1, config.concurrency ?? 1);\n emit({\n type: \"run:stage\",\n stage: \"executing\",\n message: `Executing ${plan.selectedTasks.length} task(s) (concurrency: ${concurrency})...`,\n });\n\n const codexAdapter = new CodexAdapter();\n const codexAvailability = await codexAdapter.checkAvailability();\n const useRealExecution = config.provider.includes(\"codex\") && codexAvailability.available;\n\n const taskQueue = new PQueue({ concurrency });\n const taskResults = await Promise.all(\n plan.selectedTasks.map((entry) =>\n taskQueue.add(async (): Promise<TaskResult> => {\n emit({ type: \"run:task-start\", taskId: entry.task.id, title: entry.task.title });\n progress.currentTask = entry.task.title;\n emit({ type: \"run:progress\", progress: { ...progress } });\n\n let execution: ExecutionOutcome;\n let sandbox: SandboxInfo | undefined;\n\n if (useRealExecution) {\n const result = await executeWithCodex({\n task: entry.task,\n estimate: entry.estimate,\n codexAdapter,\n repoPath: resolvedRepo.localPath,\n baseBranch: resolvedRepo.meta.defaultBranch,\n timeoutSeconds: 300,\n });\n execution = result.execution;\n sandbox = result.sandbox;\n } else {\n await new Promise((r) => setTimeout(r, 500));\n execution = {\n success: true,\n exitCode: 0,\n totalTokensUsed: Math.round(entry.estimate.totalEstimatedTokens * 0.9),\n filesChanged: entry.task.targetFiles.slice(0, 4),\n duration: 0.5,\n };\n }\n\n // Create PR if execution produced changes\n let pr: TaskResult[\"pr\"];\n if (execution.success && sandbox && execution.filesChanged.length > 0) {\n emit({\n type: \"run:stage\",\n stage: \"creating-pr\",\n message: `Creating PR for \"${entry.task.title}\"...`,\n });\n pr = await createPullRequest({\n task: entry.task,\n execution,\n sandbox,\n repoFullName: resolvedRepo.fullName,\n baseBranch: resolvedRepo.meta.defaultBranch,\n });\n if (pr) {\n progress.prsCreated += 1;\n progress.prUrls.push(pr.url);\n }\n }\n\n if (execution.success) {\n progress.tasksCompleted += 1;\n } else {\n progress.tasksFailed += 1;\n }\n progress.tokensUsed += execution.totalTokensUsed;\n progress.currentTask = undefined;\n\n const result: TaskResult = { task: entry.task, execution, sandbox, pr };\n\n emit({\n type: \"run:task-done\",\n taskId: entry.task.id,\n title: entry.task.title,\n success: execution.success,\n prUrl: pr?.url,\n filesChanged: execution.filesChanged.length,\n });\n emit({ type: \"run:progress\", progress: { ...progress } });\n\n return result;\n }) as Promise<TaskResult>,\n ),\n );\n\n // 8. Write contribution log\n emit({ type: \"run:stage\", stage: \"tracking\", message: \"Writing contribution log...\" });\n\n const contributionLog: ContributionLog = {\n version: \"1.0\",\n runId,\n timestamp: new Date().toISOString(),\n contributor: { githubUsername: resolveGithubUsername() },\n repo: {\n fullName: resolvedRepo.fullName,\n headSha: resolvedRepo.git.headSha,\n defaultBranch: resolvedRepo.meta.defaultBranch,\n },\n budget: {\n provider: config.provider,\n totalTokensBudgeted: config.tokens,\n totalTokensUsed: progress.tokensUsed,\n },\n tasks: taskResults.map((r) => ({\n taskId: r.task.id,\n title: r.task.title,\n source: r.task.source,\n complexity: r.task.complexity,\n status: r.execution.success ? (\"success\" as const) : (\"failed\" as const),\n tokensUsed: r.execution.totalTokensUsed,\n duration: r.execution.duration,\n filesChanged: r.execution.filesChanged,\n pr: r.pr,\n error: r.execution.error,\n })),\n metrics: {\n tasksDiscovered: progress.tasksDiscovered,\n tasksAttempted: taskResults.length,\n tasksSucceeded: progress.tasksCompleted,\n tasksFailed: progress.tasksFailed,\n totalDuration: (Date.now() - new Date(startedAt).getTime()) / 1_000,\n totalFilesChanged: taskResults.reduce((sum, r) => sum + r.execution.filesChanged.length, 0),\n totalLinesAdded: 0,\n totalLinesRemoved: 0,\n },\n };\n\n try {\n await writeContributionLog(contributionLog, resolvedRepo.localPath);\n } catch {\n // Non-fatal: log write failure shouldn't fail the run\n }\n\n // 9. Complete\n emit({ type: \"run:stage\", stage: \"completed\", message: \"Run completed successfully.\" });\n state.status = \"completed\";\n state.completedAt = new Date().toISOString();\n emit({ type: \"run:completed\", summary: { ...state } });\n\n return state;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n state.status = \"failed\";\n state.stage = \"failed\";\n state.error = message;\n state.completedAt = new Date().toISOString();\n emit({ type: \"run:error\", error: message });\n return state;\n }\n}\n\n\n","export function renderDashboardHtml(port: number): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>OAC Dashboard</title>\n <style>\n :root {\n --bg: #0a0a0a; --card: #111; --border: #222; --text: #e5e5e5;\n --muted: #888; --accent: #3b82f6; --green: #22c55e; --red: #ef4444;\n --yellow: #eab308; --purple: #a855f7;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", monospace; background: var(--bg); color: var(--text); }\n .container { max-width: 1200px; margin: 0 auto; padding: 24px; }\n header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 32px; border-bottom: 1px solid var(--border); padding-bottom: 16px; }\n header h1 { font-size: 24px; font-weight: 700; }\n header h1 span { color: var(--accent); }\n .badge { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; }\n .badge-idle { background: #1a1a2e; color: var(--muted); }\n .badge-running { background: #0a2a1a; color: var(--green); animation: pulse 2s infinite; }\n .badge-completed { background: #0a2a1a; color: var(--green); }\n .badge-failed { background: #2a0a0a; color: var(--red); }\n @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.6; } }\n .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px; }\n @media (max-width: 768px) { .grid { grid-template-columns: 1fr; } }\n .card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 20px; }\n .card h2 { font-size: 14px; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 16px; }\n .stat-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid var(--border); }\n .stat-row:last-child { border-bottom: none; }\n .stat-label { color: var(--muted); font-size: 13px; }\n .stat-value { font-weight: 600; font-size: 14px; }\n .full-width { grid-column: 1 / -1; }\n table { width: 100%; border-collapse: collapse; font-size: 13px; }\n th { text-align: left; color: var(--muted); font-weight: 500; padding: 8px 12px; border-bottom: 1px solid var(--border); }\n td { padding: 8px 12px; border-bottom: 1px solid var(--border); }\n tr:hover td { background: #1a1a1a; }\n .rank { color: var(--yellow); font-weight: 700; }\n .empty { color: var(--muted); text-align: center; padding: 40px 0; font-size: 14px; }\n .event-log { font-family: \"SF Mono\", \"Fira Code\", monospace; font-size: 12px; max-height: 200px; overflow-y: auto; padding: 12px; background: #050505; border-radius: 8px; }\n .event-line { padding: 2px 0; color: var(--muted); }\n .event-line .time { color: var(--accent); margin-right: 8px; }\n .connected { color: var(--green); }\n .toolbar { display: flex; gap: 8px; margin-bottom: 20px; }\n .btn { padding: 8px 16px; border-radius: 8px; border: 1px solid var(--border); background: var(--card); color: var(--text); cursor: pointer; font-size: 13px; transition: all 0.15s; }\n .btn:hover { background: #1a1a1a; border-color: var(--accent); }\n .btn:disabled { opacity: 0.4; cursor: not-allowed; }\n .btn-primary { background: var(--accent); border-color: var(--accent); color: white; }\n .btn-primary:hover:not(:disabled) { opacity: 0.9; }\n footer { text-align: center; padding: 32px 0 16px; color: var(--muted); font-size: 12px; }\n\n /* Start Run Form */\n .run-form { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }\n .run-form .form-group { display: flex; flex-direction: column; gap: 4px; }\n .run-form .form-group.full { grid-column: 1 / -1; }\n .run-form label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }\n .run-form input, .run-form select {\n padding: 8px 12px; border-radius: 8px; border: 1px solid var(--border);\n background: #050505; color: var(--text); font-size: 13px; font-family: inherit;\n }\n .run-form input:focus, .run-form select:focus { outline: none; border-color: var(--accent); }\n .run-form .form-actions { grid-column: 1 / -1; display: flex; gap: 8px; margin-top: 4px; }\n\n /* Stage Progress */\n .stage-pipeline { display: flex; align-items: center; gap: 0; margin: 16px 0; overflow-x: auto; padding-bottom: 4px; }\n .stage-dot { display: flex; align-items: center; gap: 0; white-space: nowrap; }\n .stage-dot .dot {\n width: 10px; height: 10px; border-radius: 50%; background: var(--border);\n flex-shrink: 0; transition: background 0.3s;\n }\n .stage-dot .dot.active { background: var(--accent); animation: pulse 1.5s infinite; }\n .stage-dot .dot.done { background: var(--green); }\n .stage-dot .dot.error { background: var(--red); }\n .stage-dot .label { font-size: 11px; color: var(--muted); margin-left: 4px; margin-right: 4px; }\n .stage-dot .label.active { color: var(--accent); font-weight: 600; }\n .stage-dot .label.done { color: var(--green); }\n .stage-connector { width: 16px; height: 1px; background: var(--border); flex-shrink: 0; }\n .stage-connector.done { background: var(--green); }\n\n /* Task Results */\n .task-results { margin-top: 12px; }\n .task-result { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--border); font-size: 13px; }\n .task-result:last-child { border-bottom: none; }\n .task-result .icon { font-size: 14px; }\n .task-result a { color: var(--accent); text-decoration: none; }\n .task-result a:hover { text-decoration: underline; }\n .task-result .meta { color: var(--muted); font-size: 12px; margin-left: auto; }\n\n .hidden { display: none !important; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <header>\n <h1><span>OAC</span> Dashboard</h1>\n <div>\n <span id=\"status-badge\" class=\"badge badge-idle\">idle</span>\n <span id=\"sse-indicator\" style=\"margin-left: 8px; font-size: 11px; color: var(--muted);\">connecting...</span>\n </div>\n </header>\n\n <!-- Start Run Form -->\n <div id=\"run-form-card\" class=\"card\" style=\"margin-bottom: 20px;\">\n <h2>Start Run</h2>\n <div class=\"run-form\">\n <div class=\"form-group full\">\n <label for=\"run-repo\">Repository (owner/repo or GitHub URL)</label>\n <input type=\"text\" id=\"run-repo\" placeholder=\"e.g. Open330/open-agent-contribution\" />\n </div>\n <div class=\"form-group\">\n <label for=\"run-provider\">Agent Provider</label>\n <select id=\"run-provider\">\n <option value=\"claude-code\">Claude Code</option>\n <option value=\"codex\">Codex CLI</option>\n </select>\n </div>\n <div class=\"form-group\">\n <label for=\"run-tokens-mode\">Token Budget</label>\n <select id=\"run-tokens-mode\" onchange=\"toggleTokenInput()\">\n <option value=\"fixed\">Fixed</option>\n <option value=\"unlimited\">Unlimited (until rate-limited)</option>\n </select>\n </div>\n <div class=\"form-group\" id=\"run-tokens-value-group\">\n <label for=\"run-tokens\">Token Amount</label>\n <input type=\"number\" id=\"run-tokens\" value=\"100000\" min=\"1000\" step=\"1000\" />\n </div>\n <div class=\"form-group\">\n <label for=\"run-max-tasks\">Max Tasks</label>\n <input type=\"number\" id=\"run-max-tasks\" value=\"5\" min=\"1\" max=\"50\" />\n </div>\n <div class=\"form-group\">\n <label for=\"run-concurrency\">Concurrency</label>\n <input type=\"number\" id=\"run-concurrency\" value=\"2\" min=\"1\" max=\"10\" />\n </div>\n <div class=\"form-group\">\n <label for=\"run-source\">Source Filter</label>\n <select id=\"run-source\">\n <option value=\"\">All sources</option>\n <option value=\"github-issue\">GitHub Issues</option>\n <option value=\"lint\">Lint warnings</option>\n <option value=\"todo\">To-do comments</option>\n <option value=\"test-gap\">Test gaps</option>\n </select>\n </div>\n <div class=\"form-actions\">\n <button id=\"run-start-btn\" class=\"btn btn-primary\" onclick=\"startRun()\">Start Run</button>\n <span id=\"run-error\" style=\"color: var(--red); font-size: 13px; align-self: center;\"></span>\n </div>\n </div>\n </div>\n\n <!-- Run Progress (shown during active run) -->\n <div id=\"run-progress-card\" class=\"card hidden\" style=\"margin-bottom: 20px;\">\n <h2>Run Progress</h2>\n <div id=\"stage-pipeline\" class=\"stage-pipeline\"></div>\n <div id=\"run-progress-stats\" style=\"margin-top: 8px;\">\n <div class=\"stat-row\"><span class=\"stat-label\">Tasks discovered</span><span class=\"stat-value\" id=\"prog-discovered\">0</span></div>\n <div class=\"stat-row\"><span class=\"stat-label\">Tasks selected</span><span class=\"stat-value\" id=\"prog-selected\">0</span></div>\n <div class=\"stat-row\"><span class=\"stat-label\">Completed</span><span class=\"stat-value\" id=\"prog-completed\">0</span></div>\n <div class=\"stat-row\"><span class=\"stat-label\">Failed</span><span class=\"stat-value\" id=\"prog-failed\">0</span></div>\n <div class=\"stat-row\"><span class=\"stat-label\">PRs created</span><span class=\"stat-value\" id=\"prog-prs\">0</span></div>\n <div class=\"stat-row\"><span class=\"stat-label\">Tokens used</span><span class=\"stat-value\" id=\"prog-tokens\">0</span></div>\n </div>\n <div id=\"task-results\" class=\"task-results\"></div>\n </div>\n\n <div class=\"toolbar\">\n <button class=\"btn\" onclick=\"fetchStatus()\">Refresh Status</button>\n <button class=\"btn\" onclick=\"fetchLogs()\">Refresh Logs</button>\n <button class=\"btn\" onclick=\"fetchLeaderboard()\">Refresh Leaderboard</button>\n </div>\n\n <div class=\"grid\">\n <div class=\"card\">\n <h2>Run Status</h2>\n <div id=\"status-content\">\n <div class=\"empty\">Loading...</div>\n </div>\n </div>\n\n <div class=\"card\">\n <h2>Quick Stats</h2>\n <div id=\"stats-content\">\n <div class=\"empty\">Loading...</div>\n </div>\n </div>\n\n <div class=\"card full-width\">\n <h2>Contribution Log</h2>\n <div id=\"logs-content\">\n <div class=\"empty\">Loading...</div>\n </div>\n </div>\n\n <div class=\"card full-width\">\n <h2>Leaderboard</h2>\n <div id=\"leaderboard-content\">\n <div class=\"empty\">Loading...</div>\n </div>\n </div>\n\n <div class=\"card full-width\">\n <h2>Event Stream</h2>\n <div id=\"event-log\" class=\"event-log\">\n <div class=\"event-line\" style=\"color: var(--muted);\">Connecting to SSE...</div>\n </div>\n </div>\n </div>\n\n <footer>OAC v0.1.0 — Open Agent Contribution</footer>\n </div>\n\n <script>\n const API = \"\";\n const STAGES = [\"resolving\",\"cloning\",\"scanning\",\"estimating\",\"planning\",\"executing\",\"creating-pr\",\"tracking\",\"completed\"];\n let activeRunId = null;\n\n function formatDate(iso) {\n if (!iso) return \"-\";\n try { return new Date(iso).toLocaleString(); } catch { return iso; }\n }\n\n // ---- Start Run ----\n\n function toggleTokenInput() {\n const mode = document.getElementById(\"run-tokens-mode\").value;\n const group = document.getElementById(\"run-tokens-value-group\");\n if (mode === \"unlimited\") {\n group.classList.add(\"hidden\");\n } else {\n group.classList.remove(\"hidden\");\n }\n }\n\n async function startRun() {\n const repo = document.getElementById(\"run-repo\").value.trim();\n const provider = document.getElementById(\"run-provider\").value;\n const tokensMode = document.getElementById(\"run-tokens-mode\").value;\n const tokens = tokensMode === \"unlimited\"\n ? 9007199254740991\n : parseInt(document.getElementById(\"run-tokens\").value, 10);\n const maxTasks = parseInt(document.getElementById(\"run-max-tasks\").value, 10);\n const concurrency = parseInt(document.getElementById(\"run-concurrency\").value, 10) || 2;\n const source = document.getElementById(\"run-source\").value || undefined;\n const errEl = document.getElementById(\"run-error\");\n const btn = document.getElementById(\"run-start-btn\");\n\n errEl.textContent = \"\";\n if (!repo) { errEl.textContent = \"Repository is required\"; return; }\n if (tokensMode !== \"unlimited\" && (!tokens || tokens < 1000)) { errEl.textContent = \"Token budget must be >= 1000\"; return; }\n\n btn.disabled = true;\n btn.textContent = \"Starting...\";\n\n try {\n const res = await fetch(API + \"/api/v1/runs\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ repo, provider, tokens, maxTasks, concurrency, source }),\n });\n const data = await res.json();\n\n if (!res.ok) {\n errEl.textContent = data.error || \"Failed to start run\";\n btn.disabled = false;\n btn.textContent = \"Start Run\";\n return;\n }\n\n activeRunId = data.runId;\n showProgressCard();\n } catch (e) {\n errEl.textContent = \"Network error: \" + e.message;\n btn.disabled = false;\n btn.textContent = \"Start Run\";\n }\n }\n\n function showProgressCard() {\n document.getElementById(\"run-progress-card\").classList.remove(\"hidden\");\n initStageList();\n document.getElementById(\"task-results\").innerHTML = \"\";\n document.getElementById(\"prog-discovered\").textContent = \"0\";\n document.getElementById(\"prog-selected\").textContent = \"0\";\n document.getElementById(\"prog-completed\").textContent = \"0\";\n document.getElementById(\"prog-failed\").textContent = \"0\";\n document.getElementById(\"prog-prs\").textContent = \"0\";\n document.getElementById(\"prog-tokens\").textContent = \"0\";\n\n const badge = document.getElementById(\"status-badge\");\n badge.className = \"badge badge-running\";\n badge.textContent = \"running\";\n }\n\n function initStageList() {\n const pipeline = document.getElementById(\"stage-pipeline\");\n pipeline.innerHTML = \"\";\n STAGES.forEach((stage, i) => {\n const dot = document.createElement(\"div\");\n dot.className = \"stage-dot\";\n dot.innerHTML = '<div class=\"dot\" id=\"dot-' + stage + '\"></div><span class=\"label\" id=\"label-' + stage + '\">' + stage + '</span>';\n pipeline.appendChild(dot);\n if (i < STAGES.length - 1) {\n const conn = document.createElement(\"div\");\n conn.className = \"stage-connector\";\n conn.id = \"conn-\" + stage;\n pipeline.appendChild(conn);\n }\n });\n }\n\n function updateStage(currentStage) {\n let reached = false;\n STAGES.forEach((stage, i) => {\n const dot = document.getElementById(\"dot-\" + stage);\n const label = document.getElementById(\"label-\" + stage);\n const conn = i < STAGES.length - 1 ? document.getElementById(\"conn-\" + stage) : null;\n\n if (!dot || !label) return;\n\n if (stage === currentStage) {\n reached = true;\n dot.className = currentStage === \"completed\" ? \"dot done\" : \"dot active\";\n label.className = currentStage === \"completed\" ? \"label done\" : \"label active\";\n if (conn && currentStage === \"completed\") conn.className = \"stage-connector done\";\n } else if (!reached) {\n dot.className = \"dot done\";\n label.className = \"label done\";\n if (conn) conn.className = \"stage-connector done\";\n } else {\n dot.className = \"dot\";\n label.className = \"label\";\n if (conn) conn.className = \"stage-connector\";\n }\n });\n }\n\n function updateProgress(progress) {\n document.getElementById(\"prog-discovered\").textContent = progress.tasksDiscovered || 0;\n document.getElementById(\"prog-selected\").textContent = progress.tasksSelected || 0;\n document.getElementById(\"prog-completed\").textContent = progress.tasksCompleted || 0;\n document.getElementById(\"prog-failed\").textContent = progress.tasksFailed || 0;\n document.getElementById(\"prog-prs\").textContent = progress.prsCreated || 0;\n document.getElementById(\"prog-tokens\").textContent = (progress.tokensUsed || 0).toLocaleString();\n }\n\n function addTaskResult(data) {\n const container = document.getElementById(\"task-results\");\n const div = document.createElement(\"div\");\n div.className = \"task-result\";\n const icon = data.success ? \"\\\\u2705\" : \"\\\\u274c\";\n let html = '<span class=\"icon\">' + icon + '</span><span>' + escapeHtml(data.title) + '</span>';\n if (data.prUrl) {\n html += ' <a href=\"' + escapeHtml(data.prUrl) + '\" target=\"_blank\">PR \\\\u2197</a>';\n }\n html += '<span class=\"meta\">' + (data.filesChanged || 0) + ' files</span>';\n div.innerHTML = html;\n container.appendChild(div);\n }\n\n function onRunCompleted(isError) {\n const btn = document.getElementById(\"run-start-btn\");\n btn.disabled = false;\n btn.textContent = \"Start Run\";\n activeRunId = null;\n\n const badge = document.getElementById(\"status-badge\");\n if (isError) {\n badge.className = \"badge badge-failed\";\n badge.textContent = \"failed\";\n } else {\n badge.className = \"badge badge-completed\";\n badge.textContent = \"completed\";\n }\n\n // Refresh data\n fetchStatus();\n fetchLogs();\n fetchLeaderboard();\n }\n\n function onRunError(errorMsg) {\n const container = document.getElementById(\"task-results\");\n const div = document.createElement(\"div\");\n div.className = \"task-result\";\n div.innerHTML = '<span class=\"icon\">\\\\u274c</span><span style=\"color: var(--red);\">Error: ' + escapeHtml(errorMsg) + '</span>';\n container.appendChild(div);\n\n // Mark failed stage\n STAGES.forEach((stage) => {\n const dot = document.getElementById(\"dot-\" + stage);\n if (dot && dot.className === \"dot active\") {\n dot.className = \"dot error\";\n }\n });\n\n onRunCompleted(true);\n }\n\n function escapeHtml(str) {\n const div = document.createElement(\"div\");\n div.textContent = str || \"\";\n return div.innerHTML;\n }\n\n // ---- Fetch functions ----\n\n async function fetchStatus() {\n try {\n const res = await fetch(API + \"/api/v1/status\");\n const data = await res.json();\n const badge = document.getElementById(\"status-badge\");\n\n if (data.status === \"running\") {\n badge.className = \"badge badge-running\";\n badge.textContent = \"running\";\n // Restore progress card if page was refreshed mid-run\n if (!activeRunId) {\n activeRunId = data.runId;\n showProgressCard();\n if (data.stage) updateStage(data.stage);\n if (data.progress) updateProgress(data.progress);\n }\n } else if (data.status === \"idle\" && !activeRunId) {\n badge.className = \"badge badge-idle\";\n badge.textContent = \"idle\";\n }\n\n let html = \"\";\n const displayKeys = [\"status\", \"stage\", \"runId\", \"startedAt\", \"completedAt\", \"error\"];\n for (const key of displayKeys) {\n if (data[key] !== undefined) {\n html += '<div class=\"stat-row\"><span class=\"stat-label\">' + key + '</span><span class=\"stat-value\">' + escapeHtml(String(data[key])) + '</span></div>';\n }\n }\n if (!html) {\n for (const [key, value] of Object.entries(data)) {\n html += '<div class=\"stat-row\"><span class=\"stat-label\">' + key + '</span><span class=\"stat-value\">' + escapeHtml(String(value)) + '</span></div>';\n }\n }\n document.getElementById(\"status-content\").innerHTML = html || '<div class=\"empty\">No status data</div>';\n } catch (e) {\n document.getElementById(\"status-content\").innerHTML = '<div class=\"empty\">Failed to load status</div>';\n }\n }\n\n async function fetchLogs() {\n try {\n const res = await fetch(API + \"/api/v1/logs\");\n const data = await res.json();\n\n if (!data.logs || data.logs.length === 0) {\n document.getElementById(\"logs-content\").innerHTML = '<div class=\"empty\">No contributions yet. Run <code>oac run</code> to start contributing!</div>';\n document.getElementById(\"stats-content\").innerHTML = [\n '<div class=\"stat-row\"><span class=\"stat-label\">Total Runs</span><span class=\"stat-value\">0</span></div>',\n '<div class=\"stat-row\"><span class=\"stat-label\">Total Tasks</span><span class=\"stat-value\">0</span></div>',\n '<div class=\"stat-row\"><span class=\"stat-label\">Total Tokens</span><span class=\"stat-value\">0</span></div>',\n '<div class=\"stat-row\"><span class=\"stat-label\">PRs Created</span><span class=\"stat-value\">0</span></div>',\n ].join(\"\");\n return;\n }\n\n const totalRuns = data.logs.length;\n const totalTasks = data.logs.reduce((s, l) => s + (l.tasks?.length || 0), 0);\n const totalTokens = data.logs.reduce((s, l) => s + (l.budget?.totalTokensUsed || 0), 0);\n const totalPRs = data.logs.reduce((s, l) => s + (l.tasks?.filter(t => t.pr).length || 0), 0);\n\n document.getElementById(\"stats-content\").innerHTML = [\n '<div class=\"stat-row\"><span class=\"stat-label\">Total Runs</span><span class=\"stat-value\">' + totalRuns + '</span></div>',\n '<div class=\"stat-row\"><span class=\"stat-label\">Total Tasks</span><span class=\"stat-value\">' + totalTasks + '</span></div>',\n '<div class=\"stat-row\"><span class=\"stat-label\">Total Tokens</span><span class=\"stat-value\">' + totalTokens.toLocaleString() + '</span></div>',\n '<div class=\"stat-row\"><span class=\"stat-label\">PRs Created</span><span class=\"stat-value\">' + totalPRs + '</span></div>',\n ].join(\"\");\n\n let html = '<table><thead><tr><th>Date</th><th>Repo</th><th>Tasks</th><th>Tokens</th><th>Agent</th></tr></thead><tbody>';\n for (const log of data.logs.slice(0, 20)) {\n html += '<tr>';\n html += '<td>' + formatDate(log.timestamp) + '</td>';\n html += '<td>' + escapeHtml(log.repo?.fullName || log.repoFullName || \"-\") + '</td>';\n html += '<td>' + (log.tasks?.length || 0) + '</td>';\n html += '<td>' + (log.budget?.totalTokensUsed || 0).toLocaleString() + '</td>';\n html += '<td>' + escapeHtml(log.budget?.provider || log.agentProvider || \"-\") + '</td>';\n html += '</tr>';\n }\n html += '</tbody></table>';\n document.getElementById(\"logs-content\").innerHTML = html;\n } catch (e) {\n document.getElementById(\"logs-content\").innerHTML = '<div class=\"empty\">Failed to load logs</div>';\n }\n }\n\n async function fetchLeaderboard() {\n try {\n const res = await fetch(API + \"/api/v1/leaderboard\");\n const data = await res.json();\n\n if (!data.entries || data.entries.length === 0) {\n document.getElementById(\"leaderboard-content\").innerHTML = '<div class=\"empty\">No contributors yet</div>';\n return;\n }\n\n let html = '<table><thead><tr><th>#</th><th>User</th><th>Tasks</th><th>Tokens</th><th>PRs</th><th>Last Active</th></tr></thead><tbody>';\n data.entries.forEach((entry, i) => {\n html += '<tr>';\n html += '<td class=\"rank\">' + (i + 1) + '</td>';\n html += '<td>' + escapeHtml(entry.githubUsername || \"anonymous\") + '</td>';\n html += '<td>' + (entry.totalTasksCompleted || 0) + '</td>';\n html += '<td>' + (entry.totalTokensDonated || 0).toLocaleString() + '</td>';\n html += '<td>' + (entry.totalPRsCreated || 0) + '</td>';\n html += '<td>' + formatDate(entry.lastContribution) + '</td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n document.getElementById(\"leaderboard-content\").innerHTML = html;\n } catch (e) {\n document.getElementById(\"leaderboard-content\").innerHTML = '<div class=\"empty\">Failed to load leaderboard</div>';\n }\n }\n\n // ---- SSE Connection ----\n\n function connectSSE() {\n const indicator = document.getElementById(\"sse-indicator\");\n const log = document.getElementById(\"event-log\");\n\n const es = new EventSource(API + \"/api/v1/events\");\n\n es.addEventListener(\"connected\", (e) => {\n indicator.innerHTML = '<span class=\"connected\">\\\\u25cf connected</span>';\n addEventLine(log, \"connected\", \"SSE stream connected\");\n });\n\n es.addEventListener(\"heartbeat\", (e) => {\n // Silent heartbeat — no log spam\n });\n\n // Run events\n es.addEventListener(\"run:stage\", (e) => {\n try {\n const data = JSON.parse(e.data);\n updateStage(data.stage);\n addEventLine(log, \"stage\", data.message || data.stage);\n } catch {} // best-effort: SSE event data may be malformed\n });\n\n es.addEventListener(\"run:progress\", (e) => {\n try {\n const data = JSON.parse(e.data);\n updateProgress(data.progress);\n } catch {} // best-effort: SSE event data may be malformed\n });\n\n es.addEventListener(\"run:task-start\", (e) => {\n try {\n const data = JSON.parse(e.data);\n addEventLine(log, \"task\", \"Starting: \" + data.title);\n } catch {} // best-effort: SSE event data may be malformed\n });\n\n es.addEventListener(\"run:task-done\", (e) => {\n try {\n const data = JSON.parse(e.data);\n const status = data.success ? \"OK\" : \"FAILED\";\n let msg = \"[\" + status + \"] \" + data.title;\n if (data.prUrl) msg += \" - PR: \" + data.prUrl;\n addEventLine(log, \"task\", msg);\n addTaskResult(data);\n } catch {} // best-effort: SSE event data may be malformed\n });\n\n es.addEventListener(\"run:completed\", (e) => {\n try {\n addEventLine(log, \"completed\", \"Run finished successfully\");\n updateStage(\"completed\");\n onRunCompleted(false);\n } catch {} // best-effort: SSE event data may be malformed\n });\n\n es.addEventListener(\"run:error\", (e) => {\n try {\n const data = JSON.parse(e.data);\n addEventLine(log, \"error\", data.error);\n onRunError(data.error);\n } catch {} // best-effort: SSE event data may be malformed\n });\n\n es.onerror = () => {\n indicator.innerHTML = '<span style=\"color: var(--red);\">\\\\u25cf disconnected</span>';\n addEventLine(log, \"error\", \"SSE disconnected, reconnecting...\");\n };\n\n es.onmessage = (e) => {\n addEventLine(log, \"message\", e.data);\n };\n }\n\n function addEventLine(container, type, data) {\n const now = new Date().toLocaleTimeString();\n const line = document.createElement(\"div\");\n line.className = \"event-line\";\n line.innerHTML = '<span class=\"time\">' + now + '</span> <strong>' + type + '</strong> ' + escapeHtml(String(data));\n container.appendChild(line);\n container.scrollTop = container.scrollHeight;\n\n while (container.children.length > 100) {\n container.removeChild(container.firstChild);\n }\n }\n\n // ---- Init ----\n fetchStatus();\n fetchLogs();\n fetchLeaderboard();\n connectSSE();\n\n setInterval(() => { fetchStatus(); fetchLogs(); }, 30000);\n </script>\n</body>\n</html>`;\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["randomUUID","result","randomUUID"]}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { h as TaskSource, T as Task, g as TaskComplexity, E as Epic } from '../types-Ck7IucqK.js';
|
|
2
|
-
import { R as ResolvedRepo } from '../types-
|
|
2
|
+
import { R as ResolvedRepo } from '../types-3_IAAxh5.js';
|
|
3
|
+
import { O as OacConfig } from '../config-DequKoFA.js';
|
|
4
|
+
import 'zod';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Shared options passed to each scanner invocation.
|
|
@@ -174,6 +176,20 @@ declare class CompositeScanner implements Scanner {
|
|
|
174
176
|
}
|
|
175
177
|
declare function createDefaultCompositeScanner(): CompositeScanner;
|
|
176
178
|
|
|
179
|
+
type ScannerName = "lint" | "todo" | "test-gap" | "github-issues";
|
|
180
|
+
/**
|
|
181
|
+
* Builds a list of scanner instances based on the user's config and
|
|
182
|
+
* whether a GitHub token is available.
|
|
183
|
+
*
|
|
184
|
+
* This is the single source of truth for scanner construction, used by
|
|
185
|
+
* `oac run` (both task and epic modes) and `oac analyze`.
|
|
186
|
+
*/
|
|
187
|
+
declare function buildScanners(config: OacConfig | null, hasGitHubAuth: boolean): {
|
|
188
|
+
names: ScannerName[];
|
|
189
|
+
instances: Scanner[];
|
|
190
|
+
composite: CompositeScanner;
|
|
191
|
+
};
|
|
192
|
+
|
|
177
193
|
/**
|
|
178
194
|
* Rank tasks by computed priority (0-100) and return descending order.
|
|
179
195
|
*/
|
|
@@ -266,4 +282,4 @@ declare function updateBacklog(existing: Backlog, newEpics: Epic[], completedEpi
|
|
|
266
282
|
*/
|
|
267
283
|
declare function getPendingEpics(backlog: Backlog): Epic[];
|
|
268
284
|
|
|
269
|
-
export { type AnalyzeOptions, type Backlog, type CodebaseMap, CompositeScanner, type FileInfo, GitHubIssuesScanner, type GrouperOptions, LintScanner, type ModuleInfo, type PriorityWeights, type QualityReport, type RawFinding, type ScanOptions, type Scanner, type ScannerTaskContext, TestGapScanner, TodoScanner, analyzeCodebase, computeEpicComplexity, computeEpicPriority, createBacklog, createDefaultCompositeScanner, deriveModuleFromPath, findingToTask, getPendingEpics, groupFindingsIntoEpics, isContextStale, loadBacklog, loadContext, persistBacklog, persistContext, rankTasks, updateBacklog };
|
|
285
|
+
export { type AnalyzeOptions, type Backlog, type CodebaseMap, CompositeScanner, type FileInfo, GitHubIssuesScanner, type GrouperOptions, LintScanner, type ModuleInfo, type PriorityWeights, type QualityReport, type RawFinding, type ScanOptions, type Scanner, type ScannerName, type ScannerTaskContext, TestGapScanner, TodoScanner, analyzeCodebase, buildScanners, computeEpicComplexity, computeEpicPriority, createBacklog, createDefaultCompositeScanner, deriveModuleFromPath, findingToTask, getPendingEpics, groupFindingsIntoEpics, isContextStale, loadBacklog, loadContext, persistBacklog, persistContext, rankTasks, updateBacklog };
|
package/dist/discovery/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
TestGapScanner,
|
|
6
6
|
TodoScanner,
|
|
7
7
|
analyzeCodebase,
|
|
8
|
+
buildScanners,
|
|
8
9
|
computeEpicComplexity,
|
|
9
10
|
computeEpicPriority,
|
|
10
11
|
createBacklog,
|
|
@@ -20,8 +21,8 @@ import {
|
|
|
20
21
|
persistContext,
|
|
21
22
|
rankTasks,
|
|
22
23
|
updateBacklog
|
|
23
|
-
} from "../chunk-
|
|
24
|
-
import "../chunk-
|
|
24
|
+
} from "../chunk-OTPXGXO7.js";
|
|
25
|
+
import "../chunk-6A37SKAJ.js";
|
|
25
26
|
export {
|
|
26
27
|
CompositeScanner,
|
|
27
28
|
GitHubIssuesScanner,
|
|
@@ -29,6 +30,7 @@ export {
|
|
|
29
30
|
TestGapScanner,
|
|
30
31
|
TodoScanner,
|
|
31
32
|
analyzeCodebase,
|
|
33
|
+
buildScanners,
|
|
32
34
|
computeEpicComplexity,
|
|
33
35
|
computeEpicPriority,
|
|
34
36
|
createBacklog,
|