{"version":3,"file":"background.d.ts","sourceRoot":"","sources":["../../src/evolve/background.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,OAAO,KAAK,EAAE,YAAY,EAA4B,MAAM,aAAa,CAAC;AAkF1E,wBAAsB,wBAAwB,CAAC,OAAO,EAAE;IACvD,KAAK,EAAE,QAAQ,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,YAAY,CAAC,CAaxB;AAED,wFAAwF;AACxF,wBAAsB,6BAA6B,CAAC,OAAO,EAAE;IAC5D,KAAK,EAAE,QAAQ,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,YAAY,CAAC,CAIxB;AAmFD,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhH;AAED,yEAAyE;AACzE,wBAAsB,2BAA2B,CAAC,OAAO,EAAE;IAC1D,KAAK,EAAE,QAAQ,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhB;AAED;;;GAGG;AACH,wBAAsB,iCAAiC,CAAC,OAAO,EAAE;IAChE,KAAK,EAAE,QAAQ,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,YAAY,CAAC,CA4BxB;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAYpG;AAED,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAarG;AAED,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7F;AAED,wBAAsB,2BAA2B,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAsB1F;AAuDD,wBAAsB,4BAA4B,CACjD,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE,SAAS,YAAY,EAAE,EAC7B,UAAU,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAiDhD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,YAAY,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAiB9F","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { createWriteStream, type WriteStream } from \"node:fs\";\nimport { access, appendFile, open, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { EvoPaths } from \"../paths.ts\";\nimport { loadProposal } from \"../proposal.ts\";\nimport { createPiModelRunner } from \"../reflect/model-runner.ts\";\nimport type { EvolutionRun, EvolutionRunActiveStatus } from \"../types.ts\";\nimport { runEvolutionCycle } from \"./cycle.ts\";\nimport { prepareBuildingResume, resumeEvolutionEvidence } from \"./retry.ts\";\nimport {\n\tcreateEvolutionRun,\n\tdeleteEvolutionRun,\n\tevolutionRunDirectory,\n\tlistEvolutionRuns,\n\treadEvolutionRun,\n\tupdateEvolutionRun,\n} from \"./run.ts\";\n\nconst ACTIVE_STATUSES = new Set<EvolutionRun[\"status\"]>([\n\t\"queued\",\n\t\"researching\",\n\t\"planned\",\n\t\"building\",\n\t\"validating\",\n\t\"replaying\",\n\t\"evaluating\",\n]);\nconst TERMINAL_STATUSES = new Set<EvolutionRun[\"status\"]>([\"completed\", \"failed\", \"cancelled\"]);\n\nfunction workerEntrypoint(): string {\n\treturn fileURLToPath(new URL(\"../../dist/bin.js\", import.meta.url));\n}\n\nasync function processMatchesRun(pid: number, runId: string): Promise<boolean> {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\ttry {\n\t\t\tconst command = (await readFile(`/proc/${pid}/cmdline`, \"utf8\")).replaceAll(\"\\0\", \" \");\n\t\t\treturn command.includes(\"__worker\") && command.includes(runId);\n\t\t} catch {\n\t\t\treturn true;\n\t\t}\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function assertNoActiveEvolution(paths: EvoPaths): Promise<void> {\n\t// Single-writer semantics: a second trigger fails fast instead of queueing\n\t// behind the evolution-cycle lock for up to a day.\n\tconst existing = await inspectBackgroundEvolutions(paths);\n\tconst active = existing.find((run) => ACTIVE_STATUSES.has(run.status) || run.status === \"paused\");\n\tif (active) {\n\t\tthrow new Error(\n\t\t\t`Evolution run ${active.id} is already ${active.status}; wait for it or remove it before starting another`,\n\t\t);\n\t}\n}\n\nasync function spawnEvolutionWorker(paths: EvoPaths, run: EvolutionRun, cwd: string): Promise<EvolutionRun> {\n\tconst entrypoint = workerEntrypoint();\n\tconst logFile = join(evolutionRunDirectory(paths, run.id), \"worker.log\");\n\ttry {\n\t\tawait access(entrypoint);\n\t\tconst log = await open(logFile, \"a\", 0o600);\n\t\ttry {\n\t\t\tconst child = spawn(process.execPath, [entrypoint, \"__worker\", paths.root, run.id, cwd], {\n\t\t\t\tcwd,\n\t\t\t\tdetached: true,\n\t\t\t\tstdio: [\"ignore\", log.fd, log.fd],\n\t\t\t});\n\t\t\tif (!child.pid) throw new Error(\"Background worker did not start\");\n\t\t\tchild.unref();\n\t\t\treturn await updateEvolutionRun(paths, run.id, {\n\t\t\t\tworkerPid: child.pid,\n\t\t\t\tworkerStartedAt: new Date().toISOString(),\n\t\t\t\tlogFile,\n\t\t\t});\n\t\t} finally {\n\t\t\tawait log.close();\n\t\t}\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tawait updateEvolutionRun(paths, run.id, { status: \"failed\", error: message });\n\t\tthrow error;\n\t}\n}\n\nexport async function startBackgroundEvolution(options: {\n\tpaths: EvoPaths;\n\tcwd: string;\n\trequest?: string;\n\tretryOfRunId?: string;\n}): Promise<EvolutionRun> {\n\tawait assertNoActiveEvolution(options.paths);\n\tconst created = await createEvolutionRun({\n\t\tpaths: options.paths,\n\t\ttrigger: options.request ? \"request\" : \"scheduled\",\n\t\t...(options.request ? { request: options.request } : {}),\n\t\tevidenceDigest: \"pending\",\n\t\tstatus: \"queued\",\n\t});\n\tconst run = options.retryOfRunId\n\t\t? await updateEvolutionRun(options.paths, created.id, { retryOfRunId: options.retryOfRunId })\n\t\t: created;\n\treturn spawnEvolutionWorker(options.paths, run, options.cwd);\n}\n\n/** Spawn a detached worker that resumes a terminal run's frozen plan at the builder. */\nexport async function startBackgroundBuildingResume(options: {\n\tpaths: EvoPaths;\n\tsourceRunId: string;\n\tcwd: string;\n}): Promise<EvolutionRun> {\n\tawait assertNoActiveEvolution(options.paths);\n\tconst run = await prepareBuildingResume(options);\n\treturn spawnEvolutionWorker(options.paths, run, options.cwd);\n}\n\nasync function appendWorkerProgress(paths: EvoPaths, runId: string, event: Record<string, unknown>): Promise<void> {\n\tawait appendFile(\n\t\tjoin(evolutionRunDirectory(paths, runId), \"progress.jsonl\"),\n\t\t`${JSON.stringify({ timestamp: new Date().toISOString(), ...event })}\\n`,\n\t\t\"utf8\",\n\t);\n}\n\ntype WorkerRunner = { run: ReturnType<typeof createPiModelRunner>[\"run\"] };\n\nasync function runTrackedWorker(\n\toptions: { paths: EvoPaths; runId: string },\n\twork: (runner: WorkerRunner, run: EvolutionRun, signal: AbortSignal) => Promise<void>,\n): Promise<void> {\n\tconst controller = new AbortController();\n\tlet transcript: WriteStream | undefined;\n\tconst abort = () => controller.abort(new Error(\"Evolution worker terminated\"));\n\tprocess.once(\"SIGTERM\", abort);\n\tprocess.once(\"SIGINT\", abort);\n\ttry {\n\t\tconst run = await readEvolutionRun(options.paths, options.runId);\n\t\tawait updateEvolutionRun(options.paths, run.id, {\n\t\t\tworkerPid: process.pid,\n\t\t\tworkerStartedAt: run.workerStartedAt ?? new Date().toISOString(),\n\t\t});\n\t\ttranscript = createWriteStream(join(evolutionRunDirectory(options.paths, run.id), \"transcript.jsonl\"), {\n\t\t\tflags: \"a\",\n\t\t\tmode: 0o600,\n\t\t});\n\t\tconst baseRunner = createPiModelRunner();\n\t\tconst runner: WorkerRunner = {\n\t\t\trun: async (request: Parameters<typeof baseRunner.run>[0]) => {\n\t\t\t\tconst phase = (await readEvolutionRun(options.paths, options.runId)).status;\n\t\t\t\tawait appendWorkerProgress(options.paths, options.runId, {\n\t\t\t\t\ttype: \"model-start\",\n\t\t\t\t\tphase,\n\t\t\t\t\tmodel: request.model,\n\t\t\t\t\tthinkingLevel: request.thinkingLevel,\n\t\t\t\t});\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await baseRunner.run({\n\t\t\t\t\t\t...request,\n\t\t\t\t\t\tonStreamEvent: (event) => {\n\t\t\t\t\t\t\trequest.onStreamEvent?.(event);\n\t\t\t\t\t\t\ttranscript?.write(`${JSON.stringify({ timestamp: new Date().toISOString(), phase, ...event })}\\n`);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\tawait appendWorkerProgress(options.paths, options.runId, {\n\t\t\t\t\t\ttype: \"model-complete\",\n\t\t\t\t\t\tphase,\n\t\t\t\t\t\tmodel: `${result.model.provider}/${result.model.id}`,\n\t\t\t\t\t\ttokens: result.stats.tokens,\n\t\t\t\t\t\toutputPreview: result.text.slice(0, 1_000),\n\t\t\t\t\t});\n\t\t\t\t\treturn result;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tawait appendWorkerProgress(options.paths, options.runId, {\n\t\t\t\t\t\ttype: \"model-error\",\n\t\t\t\t\t\tphase,\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t});\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t\tawait work(runner, run, controller.signal);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tawait updateEvolutionRun(options.paths, options.runId, {\n\t\t\tstatus: controller.signal.aborted ? \"cancelled\" : \"failed\",\n\t\t\terror: message,\n\t\t}).catch(() => undefined);\n\t\tthrow error;\n\t} finally {\n\t\tprocess.off(\"SIGTERM\", abort);\n\t\tprocess.off(\"SIGINT\", abort);\n\t\tif (transcript) await new Promise<void>((resolve) => transcript?.end(resolve));\n\t\tawait updateEvolutionRun(options.paths, options.runId, { workerPid: undefined }).catch(() => undefined);\n\t}\n}\n\nexport async function runEvolutionWorker(options: { paths: EvoPaths; runId: string; cwd: string }): Promise<void> {\n\tawait runTrackedWorker(options, (runner, run, signal) =>\n\t\trunEvolutionCycle({\n\t\t\tpaths: options.paths,\n\t\t\trunner,\n\t\t\tcwd: options.cwd,\n\t\t\t...(run.request ? { request: run.request } : {}),\n\t\t\ttrigger: run.trigger,\n\t\t\trunId: run.id,\n\t\t\tsignal,\n\t\t}).then(() => undefined),\n\t);\n}\n\n/** Detached worker that executes a user-approved evidence resumption. */\nexport async function runEvidenceResumptionWorker(options: {\n\tpaths: EvoPaths;\n\trunId: string;\n\tcwd: string;\n}): Promise<void> {\n\tawait runTrackedWorker(options, (runner, run, signal) =>\n\t\tresumeEvolutionEvidence({\n\t\t\tpaths: options.paths,\n\t\t\trunner,\n\t\t\trunId: run.id,\n\t\t\tcwd: options.cwd,\n\t\t\tsignal,\n\t\t}).then(() => undefined),\n\t);\n}\n\n/**\n * Spawn a detached worker that executes the recommended verification for a run\n * parked at awaiting-evidence, then re-evaluates and applies the release policy.\n */\nexport async function startBackgroundEvidenceResumption(options: {\n\tpaths: EvoPaths;\n\tcwd: string;\n\trunId: string;\n}): Promise<EvolutionRun> {\n\tconst run = await readEvolutionRun(options.paths, options.runId);\n\tif (run.status !== \"awaiting-evidence\") {\n\t\tthrow new Error(`Evolution run ${run.id} is ${run.status}, not awaiting-evidence`);\n\t}\n\tif (run.workerPid && (await processMatchesRun(run.workerPid, run.id))) {\n\t\tthrow new Error(`Evolution run ${run.id} already has an active worker`);\n\t}\n\tconst entrypoint = workerEntrypoint();\n\tawait access(entrypoint);\n\tconst logFile = join(evolutionRunDirectory(options.paths, run.id), \"worker.log\");\n\tconst log = await open(logFile, \"a\", 0o600);\n\ttry {\n\t\tconst child = spawn(process.execPath, [entrypoint, \"__worker-resume\", options.paths.root, run.id, options.cwd], {\n\t\t\tcwd: options.cwd,\n\t\t\tdetached: true,\n\t\t\tstdio: [\"ignore\", log.fd, log.fd],\n\t\t});\n\t\tif (!child.pid) throw new Error(\"Background worker did not start\");\n\t\tchild.unref();\n\t\treturn await updateEvolutionRun(options.paths, run.id, {\n\t\t\tworkerPid: child.pid,\n\t\t\tworkerStartedAt: new Date().toISOString(),\n\t\t\tlogFile,\n\t\t});\n\t} finally {\n\t\tawait log.close();\n\t}\n}\n\nexport async function pauseBackgroundEvolution(paths: EvoPaths, runId: string): Promise<EvolutionRun> {\n\tconst run = await readEvolutionRun(paths, runId);\n\tif (!ACTIVE_STATUSES.has(run.status)) throw new Error(`Evolution run ${runId} is not active`);\n\tif (!run.workerPid || !(await processMatchesRun(run.workerPid, run.id))) {\n\t\treturn updateEvolutionRun(paths, run.id, { status: \"failed\", error: \"Background worker is not running\" });\n\t}\n\tprocess.kill(run.workerPid, \"SIGSTOP\");\n\treturn updateEvolutionRun(paths, run.id, {\n\t\tstatus: \"paused\",\n\t\tpausedAt: new Date().toISOString(),\n\t\tpausedFrom: run.status as EvolutionRunActiveStatus,\n\t});\n}\n\nexport async function resumeBackgroundEvolution(paths: EvoPaths, runId: string): Promise<EvolutionRun> {\n\tconst run = await readEvolutionRun(paths, runId);\n\tif (run.status !== \"paused\") throw new Error(`Evolution run ${runId} is not paused`);\n\tif (!run.workerPid || !(await processMatchesRun(run.workerPid, run.id))) {\n\t\treturn updateEvolutionRun(paths, run.id, { status: \"failed\", error: \"Paused worker is not running\" });\n\t}\n\tconst resumed = await updateEvolutionRun(paths, run.id, {\n\t\tstatus: run.pausedFrom ?? \"researching\",\n\t\tpausedAt: undefined,\n\t\tpausedFrom: undefined,\n\t});\n\tprocess.kill(run.workerPid, \"SIGCONT\");\n\treturn resumed;\n}\n\nexport async function removeBackgroundEvolution(paths: EvoPaths, runId: string): Promise<void> {\n\tconst run = await readEvolutionRun(paths, runId);\n\tif (run.workerPid && (await processMatchesRun(run.workerPid, run.id))) process.kill(run.workerPid, \"SIGKILL\");\n\tawait deleteEvolutionRun(paths, run.id);\n}\n\nexport async function inspectBackgroundEvolutions(paths: EvoPaths): Promise<EvolutionRun[]> {\n\tconst runs = await listEvolutionRuns(paths);\n\tfor (const run of runs) {\n\t\tif ((ACTIVE_STATUSES.has(run.status) || run.status === \"paused\") && run.workerPid) {\n\t\t\tif (!(await processMatchesRun(run.workerPid, run.id))) {\n\t\t\t\t// A release intent means the crash happened after the release policy may\n\t\t\t\t// already have taken effect; reconcile from the proposal instead of\n\t\t\t\t// declaring a possibly-successful run failed.\n\t\t\t\tconst reconciled = await reconcileRunFromReleaseIntent(paths, run);\n\t\t\t\tif (reconciled) {\n\t\t\t\t\tawait updateEvolutionRun(paths, run.id, { workerPid: undefined });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tawait updateEvolutionRun(paths, run.id, {\n\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\terror: \"Background worker exited without completing the run\",\n\t\t\t\t\tworkerPid: undefined,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\treturn listEvolutionRuns(paths);\n}\n\nasync function reconcileRunFromReleaseIntent(paths: EvoPaths, run: EvolutionRun): Promise<boolean> {\n\tif (!run.proposalId) return false;\n\ttry {\n\t\tawait access(join(evolutionRunDirectory(paths, run.id), \"release-intent.json\"));\n\t} catch {\n\t\treturn false;\n\t}\n\tconst proposal = await loadProposal(paths, run.proposalId).catch(() => undefined);\n\tif (!proposal) return false;\n\tconst status =\n\t\tproposal.status === \"trialing\"\n\t\t\t? (\"trialing\" as const)\n\t\t\t: proposal.status === \"deferred\"\n\t\t\t\t? (\"awaiting-decision\" as const)\n\t\t\t\t: proposal.status === \"approved\" ||\n\t\t\t\t\t\tproposal.status === \"rejected\" ||\n\t\t\t\t\t\tproposal.status === \"kept\" ||\n\t\t\t\t\t\tproposal.status === \"rolled-back\"\n\t\t\t\t\t? (\"completed\" as const)\n\t\t\t\t\t: proposal.status === \"pending\"\n\t\t\t\t\t\t? (\"awaiting-decision\" as const)\n\t\t\t\t\t\t: undefined;\n\tif (!status || status === run.status) return status !== undefined;\n\tawait updateEvolutionRun(paths, run.id, { status });\n\treturn true;\n}\n\nasync function readRunText(path: string, maxCharacters = 20_000): Promise<string | undefined> {\n\ttry {\n\t\tconst text = await readFile(path, \"utf8\");\n\t\treturn text.length > maxCharacters ? `${text.slice(-maxCharacters)}\\n\\n[earlier content omitted]` : text;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction progressMarkdown(text: string | undefined): string[] {\n\tif (!text?.trim()) return [\"No completed progress event yet. A model request may currently be in flight.\"];\n\treturn text\n\t\t.trim()\n\t\t.split(\"\\n\")\n\t\t.slice(-20)\n\t\t.map((line) => JSON.parse(line) as Record<string, unknown>)\n\t\t.map((event) => {\n\t\t\tconst tokens = event.tokens as { input?: number; output?: number; cacheRead?: number } | undefined;\n\t\t\tconst usage = tokens\n\t\t\t\t? ` — tokens in=${tokens.input ?? 0} out=${tokens.output ?? 0} cache=${tokens.cacheRead ?? 0}`\n\t\t\t\t: \"\";\n\t\t\tconst error = event.error ? ` — ${String(event.error)}` : \"\";\n\t\t\treturn `- ${event.timestamp} **${event.type}** (${event.phase ?? \"unknown\"}) ${event.model ?? \"\"}${usage}${error}`;\n\t\t});\n}\n\nexport async function renderEvolutionRunInspection(\n\tpaths: EvoPaths,\n\truns: readonly EvolutionRun[],\n\tselectedId?: string,\n): Promise<{ summary: string; markdown: string }> {\n\tconst selected = selectedId\n\t\t? runs.filter((run) => run.id === selectedId)\n\t\t: runs.filter((run) => !TERMINAL_STATUSES.has(run.status));\n\tconst activeCount = runs.filter((run) => !TERMINAL_STATUSES.has(run.status)).length;\n\tconst summary = selectedId\n\t\t? selected[0]\n\t\t\t? `${selected[0].id}  ${selected[0].status}`\n\t\t\t: `Evolution run not found: ${selectedId}`\n\t\t: `Evo tasks: ${activeCount} active, ${runs.length} total`;\n\tconst sections = [\n\t\t\"# Evolution tasks\",\n\t\t\"\",\n\t\t...runs.map((run) => `- \\`${run.id}\\` — **${run.status}** — ${run.request ?? \"scheduled evolution\"}`),\n\t];\n\tfor (const run of selected) {\n\t\tconst directory = evolutionRunDirectory(paths, run.id);\n\t\tconst [progress, workerLog, plan, experiment, evaluation] = await Promise.all([\n\t\t\treadRunText(join(directory, \"progress.jsonl\")),\n\t\t\treadRunText(join(directory, \"worker.log\"), 8_000),\n\t\t\treadRunText(join(directory, \"plan.md\")),\n\t\t\treadRunText(join(directory, \"experiment.json\")),\n\t\t\treadRunText(join(directory, \"evaluation.md\")),\n\t\t]);\n\t\tsections.push(\n\t\t\t\"\",\n\t\t\t`## ${run.id}`,\n\t\t\t\"\",\n\t\t\t`- **status:** ${run.status}`,\n\t\t\t`- **started:** ${run.startedAt}`,\n\t\t\t`- **updated:** ${run.updatedAt}`,\n\t\t\t...(run.request ? [`- **request:** ${run.request}`] : []),\n\t\t\t...(run.workerPid ? [`- **worker PID:** ${run.workerPid}`] : []),\n\t\t\t...(run.proposalId ? [`- **proposal:** ${run.proposalId}`] : []),\n\t\t\t...(run.error ? [`- **error:** ${run.error}`] : []),\n\t\t\t\"\",\n\t\t\t\"### Live progress\",\n\t\t\t\"\",\n\t\t\t...progressMarkdown(progress),\n\t\t\t...(plan ? [\"\", \"### Research plan\", \"\", plan] : []),\n\t\t\t...(experiment ? [\"\", \"### Frozen experiment\", \"\", \"```json\", experiment, \"```\"] : []),\n\t\t\t...(evaluation ? [\"\", \"### Evaluation\", \"\", evaluation] : []),\n\t\t\t...(workerLog?.trim() ? [\"\", \"### Worker log\", \"\", \"```text\", workerLog, \"```\"] : []),\n\t\t);\n\t}\n\tif (!selectedId && selected.length === 0) {\n\t\tsections.push(\"\", \"No task is currently active. Pass a run ID to inspect its artifacts.\");\n\t}\n\treturn { summary, markdown: sections.join(\"\\n\") };\n}\n\nexport function formatEvolutionRuns(runs: readonly EvolutionRun[], selectedId?: string): string {\n\tconst selected = selectedId ? runs.filter((run) => run.id === selectedId) : runs;\n\tif (selected.length === 0) return selectedId ? `Evolution run not found: ${selectedId}` : \"No evolution runs\";\n\treturn selected\n\t\t.map((run) => {\n\t\t\tconst details = [\n\t\t\t\t`${run.id}  ${run.status}`,\n\t\t\t\t`started: ${run.startedAt}`,\n\t\t\t\t...(run.request ? [`request: ${run.request}`] : []),\n\t\t\t\t...(run.workerPid ? [`pid: ${run.workerPid}`] : []),\n\t\t\t\t...(run.logFile ? [`log: ${run.logFile}`] : []),\n\t\t\t\t...(run.proposalId ? [`proposal: ${run.proposalId}`] : []),\n\t\t\t\t...(run.error ? [`error: ${run.error}`] : []),\n\t\t\t];\n\t\t\treturn details.join(\"\\n\");\n\t\t})\n\t\t.join(\"\\n\\n\");\n}\n"]}