Extract adapter registry across CLI, server, and UI

Refactor monolithic heartbeat service, AgentConfigForm, and CLI
heartbeat-run into a proper adapter registry pattern. Each adapter
type (process, claude-local, codex-local, http) gets its own module
with server-side execution logic, CLI invocation, and UI config form.
Significantly reduces file sizes and enables adding new adapters
without touching core code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Forgotten 2026-02-18 13:53:03 -06:00
parent 3a91ecbae3
commit 47ccd946b6
52 changed files with 1961 additions and 1361 deletions

View file

@ -0,0 +1,73 @@
import type { TranscriptEntry } from "../types";
function safeJsonParse(text: string): unknown {
try {
return JSON.parse(text);
} catch {
return null;
}
}
function asRecord(value: unknown): Record<string, unknown> | null {
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
return value as Record<string, unknown>;
}
export function parseCodexStdoutLine(line: string, ts: string): TranscriptEntry[] {
const parsed = asRecord(safeJsonParse(line));
if (!parsed) {
return [{ kind: "stdout", ts, text: line }];
}
const type = typeof parsed.type === "string" ? parsed.type : "";
if (type === "thread.started") {
const threadId = typeof parsed.thread_id === "string" ? parsed.thread_id : "";
return [{
kind: "init",
ts,
model: "codex",
sessionId: threadId,
}];
}
if (type === "item.completed") {
const item = asRecord(parsed.item);
if (item) {
const itemType = typeof item.type === "string" ? item.type : "";
if (itemType === "agent_message") {
const text = typeof item.text === "string" ? item.text : "";
if (text) return [{ kind: "assistant", ts, text }];
}
if (itemType === "tool_use") {
return [{
kind: "tool_call",
ts,
name: typeof item.name === "string" ? item.name : "unknown",
input: item.input ?? {},
}];
}
}
}
if (type === "turn.completed") {
const usage = asRecord(parsed.usage) ?? {};
const inputTokens = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
const outputTokens = typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
const cachedTokens = typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : 0;
return [{
kind: "result",
ts,
text: "",
inputTokens,
outputTokens,
cachedTokens,
costUsd: 0,
subtype: "",
isError: false,
errors: [],
}];
}
return [{ kind: "stdout", ts, text: line }];
}