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,99 @@
import pc from "picocolors";
function asErrorText(value: unknown): string {
if (typeof value === "string") return value;
if (typeof value !== "object" || value === null || Array.isArray(value)) return "";
const obj = value as Record<string, unknown>;
const message =
(typeof obj.message === "string" && obj.message) ||
(typeof obj.error === "string" && obj.error) ||
(typeof obj.code === "string" && obj.code) ||
"";
if (message) return message;
try {
return JSON.stringify(obj);
} catch {
return "";
}
}
export function printClaudeStreamEvent(raw: string, debug: boolean): void {
const line = raw.trim();
if (!line) return;
let parsed: Record<string, unknown> | null = null;
try {
parsed = JSON.parse(line) as Record<string, unknown>;
} catch {
console.log(line);
return;
}
const type = typeof parsed.type === "string" ? parsed.type : "";
if (type === "system" && parsed.subtype === "init") {
const model = typeof parsed.model === "string" ? parsed.model : "unknown";
const sessionId = typeof parsed.session_id === "string" ? parsed.session_id : "";
console.log(pc.blue(`Claude initialized (model: ${model}${sessionId ? `, session: ${sessionId}` : ""})`));
return;
}
if (type === "assistant") {
const message =
typeof parsed.message === "object" && parsed.message !== null && !Array.isArray(parsed.message)
? (parsed.message as Record<string, unknown>)
: {};
const content = Array.isArray(message.content) ? message.content : [];
for (const blockRaw of content) {
if (typeof blockRaw !== "object" || blockRaw === null || Array.isArray(blockRaw)) continue;
const block = blockRaw as Record<string, unknown>;
const blockType = typeof block.type === "string" ? block.type : "";
if (blockType === "text") {
const text = typeof block.text === "string" ? block.text : "";
if (text) console.log(pc.green(`assistant: ${text}`));
} else if (blockType === "tool_use") {
const name = typeof block.name === "string" ? block.name : "unknown";
console.log(pc.yellow(`tool_call: ${name}`));
if (block.input !== undefined) {
console.log(pc.gray(JSON.stringify(block.input, null, 2)));
}
}
}
return;
}
if (type === "result") {
const usage =
typeof parsed.usage === "object" && parsed.usage !== null && !Array.isArray(parsed.usage)
? (parsed.usage as Record<string, unknown>)
: {};
const input = Number(usage.input_tokens ?? 0);
const output = Number(usage.output_tokens ?? 0);
const cached = Number(usage.cache_read_input_tokens ?? 0);
const cost = Number(parsed.total_cost_usd ?? 0);
const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
const isError = parsed.is_error === true;
const resultText = typeof parsed.result === "string" ? parsed.result : "";
if (resultText) {
console.log(pc.green("result:"));
console.log(resultText);
}
const errors = Array.isArray(parsed.errors) ? parsed.errors.map(asErrorText).filter(Boolean) : [];
if (subtype.startsWith("error") || isError || errors.length > 0) {
console.log(pc.red(`claude_result: subtype=${subtype || "unknown"} is_error=${isError ? "true" : "false"}`));
if (errors.length > 0) {
console.log(pc.red(`claude_errors: ${errors.join(" | ")}`));
}
}
console.log(
pc.blue(
`tokens: in=${Number.isFinite(input) ? input : 0} out=${Number.isFinite(output) ? output : 0} cached=${Number.isFinite(cached) ? cached : 0} cost=$${Number.isFinite(cost) ? cost.toFixed(6) : "0.000000"}`,
),
);
return;
}
if (debug) {
console.log(pc.gray(line));
}
}

View File

@@ -0,0 +1,7 @@
import type { CLIAdapterModule } from "../types.js";
import { printClaudeStreamEvent } from "./format-event.js";
export const claudeLocalCLIAdapter: CLIAdapterModule = {
type: "claude_local",
formatStdoutEvent: printClaudeStreamEvent,
};

View File

@@ -0,0 +1,56 @@
import pc from "picocolors";
export function printCodexStreamEvent(raw: string, _debug: boolean): void {
const line = raw.trim();
if (!line) return;
let parsed: Record<string, unknown> | null = null;
try {
parsed = JSON.parse(line) as Record<string, unknown>;
} catch {
console.log(line);
return;
}
const type = typeof parsed.type === "string" ? parsed.type : "";
if (type === "thread.started") {
const threadId = typeof parsed.thread_id === "string" ? parsed.thread_id : "";
console.log(pc.blue(`Codex thread started${threadId ? ` (session: ${threadId})` : ""}`));
return;
}
if (type === "item.completed") {
const item =
typeof parsed.item === "object" && parsed.item !== null && !Array.isArray(parsed.item)
? (parsed.item as Record<string, unknown>)
: null;
if (item) {
const itemType = typeof item.type === "string" ? item.type : "";
if (itemType === "agent_message") {
const text = typeof item.text === "string" ? item.text : "";
if (text) console.log(pc.green(`assistant: ${text}`));
} else if (itemType === "tool_use") {
const name = typeof item.name === "string" ? item.name : "unknown";
console.log(pc.yellow(`tool_call: ${name}`));
}
}
return;
}
if (type === "turn.completed") {
const usage =
typeof parsed.usage === "object" && parsed.usage !== null && !Array.isArray(parsed.usage)
? (parsed.usage as Record<string, unknown>)
: {};
const input = Number(usage.input_tokens ?? 0);
const output = Number(usage.output_tokens ?? 0);
const cached = Number(usage.cached_input_tokens ?? 0);
console.log(
pc.blue(`tokens: in=${input} out=${output} cached=${cached}`),
);
return;
}
console.log(line);
}

View File

@@ -0,0 +1,7 @@
import type { CLIAdapterModule } from "../types.js";
import { printCodexStreamEvent } from "./format-event.js";
export const codexLocalCLIAdapter: CLIAdapterModule = {
type: "codex_local",
formatStdoutEvent: printCodexStreamEvent,
};

View File

@@ -0,0 +1,4 @@
export function printHttpStdoutEvent(raw: string, _debug: boolean): void {
const line = raw.trim();
if (line) console.log(line);
}

View File

@@ -0,0 +1,7 @@
import type { CLIAdapterModule } from "../types.js";
import { printHttpStdoutEvent } from "./format-event.js";
export const httpCLIAdapter: CLIAdapterModule = {
type: "http",
formatStdoutEvent: printHttpStdoutEvent,
};

View File

@@ -0,0 +1,2 @@
export { getCLIAdapter } from "./registry.js";
export type { CLIAdapterModule } from "./types.js";

View File

@@ -0,0 +1,4 @@
export function printProcessStdoutEvent(raw: string, _debug: boolean): void {
const line = raw.trim();
if (line) console.log(line);
}

View File

@@ -0,0 +1,7 @@
import type { CLIAdapterModule } from "../types.js";
import { printProcessStdoutEvent } from "./format-event.js";
export const processCLIAdapter: CLIAdapterModule = {
type: "process",
formatStdoutEvent: printProcessStdoutEvent,
};

View File

@@ -0,0 +1,13 @@
import type { CLIAdapterModule } from "./types.js";
import { claudeLocalCLIAdapter } from "./claude-local/index.js";
import { codexLocalCLIAdapter } from "./codex-local/index.js";
import { processCLIAdapter } from "./process/index.js";
import { httpCLIAdapter } from "./http/index.js";
const adaptersByType = new Map<string, CLIAdapterModule>(
[claudeLocalCLIAdapter, codexLocalCLIAdapter, processCLIAdapter, httpCLIAdapter].map((a) => [a.type, a]),
);
export function getCLIAdapter(type: string): CLIAdapterModule {
return adaptersByType.get(type) ?? processCLIAdapter;
}

View File

@@ -0,0 +1,4 @@
export interface CLIAdapterModule {
type: string;
formatStdoutEvent: (line: string, debug: boolean) => void;
}