feat(ui): add operator UI server, stores, and insights

This commit is contained in:
2026-02-23 18:49:53 -05:00
parent 8100f4d1c6
commit cf386e1aaa
18 changed files with 3252 additions and 17 deletions

595
src/ui/provider-executor.ts Normal file
View File

@@ -0,0 +1,595 @@
import { Codex } from "@openai/codex-sdk";
import { query, type Options, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
import {
buildClaudeAuthEnv,
resolveAnthropicToken,
resolveOpenAiApiKey,
type AppConfig,
} from "../config.js";
import { isDomainEventType, type DomainEventEmission } from "../agents/domain-events.js";
import type { ActorExecutionInput, ActorExecutionResult, ActorExecutor } from "../agents/pipeline.js";
import { isRecord, type JsonObject, type JsonValue } from "../agents/types.js";
import { createSessionContext, type SessionContext } from "../examples/session-context.js";
export type RunProvider = "codex" | "claude";
export type ProviderRunRuntime = {
provider: RunProvider;
config: Readonly<AppConfig>;
sessionContext: SessionContext;
close: () => Promise<void>;
};
type ProviderUsage = {
tokenInput?: number;
tokenOutput?: number;
tokenTotal?: number;
durationMs?: number;
costUsd?: number;
};
const ACTOR_RESPONSE_SCHEMA = {
type: "object",
additionalProperties: true,
properties: {
status: {
type: "string",
enum: ["success", "validation_fail", "failure"],
},
payload: {
type: "object",
},
stateFlags: {
type: "object",
additionalProperties: {
type: "boolean",
},
},
stateMetadata: {
type: "object",
},
events: {
type: "array",
items: {
type: "object",
additionalProperties: true,
},
},
failureKind: {
type: "string",
enum: ["soft", "hard"],
},
failureCode: {
type: "string",
},
},
required: ["status"],
};
const CLAUDE_OUTPUT_FORMAT = {
type: "json_schema",
name: "actor_execution_result",
schema: ACTOR_RESPONSE_SCHEMA,
} as const;
function toErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
return String(error);
}
function toJsonValue(value: unknown): JsonValue {
return JSON.parse(JSON.stringify(value)) as JsonValue;
}
function toJsonObject(value: unknown): JsonObject | undefined {
if (!isRecord(value)) {
return undefined;
}
try {
const cloned = toJsonValue(value);
if (!isRecord(cloned)) {
return undefined;
}
return cloned as JsonObject;
} catch {
return undefined;
}
}
function toBooleanRecord(value: unknown): Record<string, boolean> | undefined {
if (!isRecord(value)) {
return undefined;
}
const output: Record<string, boolean> = {};
for (const [key, candidate] of Object.entries(value)) {
if (typeof candidate === "boolean") {
output[key] = candidate;
}
}
return Object.keys(output).length > 0 ? output : undefined;
}
function toEventEmissions(value: unknown): DomainEventEmission[] | undefined {
if (!Array.isArray(value)) {
return undefined;
}
const output: DomainEventEmission[] = [];
for (const item of value) {
if (!isRecord(item)) {
continue;
}
const type = item.type;
if (typeof type !== "string" || !isDomainEventType(type)) {
continue;
}
const payload = toJsonObject(item.payload);
output.push({
type,
...(payload ? { payload } : {}),
});
}
return output.length > 0 ? output : undefined;
}
function extractJsonFromFencedBlock(text: string): unknown {
const matches = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
if (!matches || !matches[1]) {
return undefined;
}
try {
return JSON.parse(matches[1]);
} catch {
return undefined;
}
}
function extractFirstBalancedJsonObject(text: string): unknown {
const start = text.indexOf("{");
if (start < 0) {
return undefined;
}
let depth = 0;
let inString = false;
let escaped = false;
for (let index = start; index < text.length; index += 1) {
const character = text[index];
if (!character) {
continue;
}
if (inString) {
if (escaped) {
escaped = false;
} else if (character === "\\") {
escaped = true;
} else if (character === '"') {
inString = false;
}
continue;
}
if (character === '"') {
inString = true;
continue;
}
if (character === "{") {
depth += 1;
continue;
}
if (character === "}") {
depth -= 1;
if (depth === 0) {
const candidate = text.slice(start, index + 1);
try {
return JSON.parse(candidate);
} catch {
return undefined;
}
}
}
}
return undefined;
}
function tryParseResponseObject(rawText: string, structuredOutput?: unknown): unknown {
if (structuredOutput !== undefined) {
return structuredOutput;
}
const trimmed = rawText.trim();
if (!trimmed) {
return undefined;
}
try {
return JSON.parse(trimmed);
} catch {
// fall through
}
const fenced = extractJsonFromFencedBlock(trimmed);
if (fenced !== undefined) {
return fenced;
}
return extractFirstBalancedJsonObject(trimmed);
}
function ensureUsageMetadata(input: {
result: ActorExecutionResult;
providerUsage: ProviderUsage;
}): ActorExecutionResult {
const stateMetadata = toJsonObject(input.result.stateMetadata) ?? {};
const existingUsage = toJsonObject(stateMetadata.usage) ?? {};
const usage: JsonObject = {
...existingUsage,
...(typeof input.providerUsage.tokenInput === "number"
? { tokenInput: input.providerUsage.tokenInput }
: {}),
...(typeof input.providerUsage.tokenOutput === "number"
? { tokenOutput: input.providerUsage.tokenOutput }
: {}),
...(typeof input.providerUsage.tokenTotal === "number"
? { tokenTotal: input.providerUsage.tokenTotal }
: {}),
...(typeof input.providerUsage.durationMs === "number"
? { durationMs: input.providerUsage.durationMs }
: {}),
...(typeof input.providerUsage.costUsd === "number"
? { costUsd: input.providerUsage.costUsd }
: {}),
};
return {
...input.result,
stateMetadata: {
...stateMetadata,
usage,
},
};
}
export function parseActorExecutionResultFromModelOutput(input: {
rawText: string;
structuredOutput?: unknown;
}): ActorExecutionResult {
const parsed = tryParseResponseObject(input.rawText, input.structuredOutput);
if (!isRecord(parsed)) {
return {
status: "success",
payload: {
assistantResponse: input.rawText.trim(),
},
};
}
const status = parsed.status;
if (status !== "success" && status !== "validation_fail" && status !== "failure") {
return {
status: "success",
payload: {
assistantResponse: input.rawText.trim(),
},
};
}
const payload = toJsonObject(parsed.payload) ?? {
assistantResponse: input.rawText.trim(),
};
const stateMetadata = toJsonObject(parsed.stateMetadata);
const stateFlags = toBooleanRecord(parsed.stateFlags);
const events = toEventEmissions(parsed.events);
const failureKind = parsed.failureKind === "soft" || parsed.failureKind === "hard"
? parsed.failureKind
: undefined;
const failureCode = typeof parsed.failureCode === "string"
? parsed.failureCode
: undefined;
return {
status,
payload,
...(stateFlags ? { stateFlags } : {}),
...(stateMetadata ? { stateMetadata } : {}),
...(events ? { events } : {}),
...(failureKind ? { failureKind } : {}),
...(failureCode ? { failureCode } : {}),
};
}
function buildActorPrompt(input: ActorExecutionInput): string {
const recentHistory = input.context.state.history.slice(-15);
return [
"You are executing one orchestration node in a schema-driven DAG runtime.",
"Return ONLY JSON with this object shape:",
JSON.stringify(
{
status: "success | validation_fail | failure",
payload: {},
stateFlags: {
optional_boolean_flag: true,
},
stateMetadata: {
optional_metadata: "value",
},
events: [
{
type: "requirements_defined | tasks_planned | code_committed | task_blocked | validation_passed | validation_failed | branch_merged",
payload: {
summary: "optional",
details: {},
errorCode: "optional",
artifactPointer: "optional",
},
},
],
failureKind: "soft | hard",
failureCode: "optional",
},
null,
2,
),
"Do not include markdown or extra explanation outside JSON.",
`Node Prompt:\n${input.prompt}`,
`Execution Context:\n${JSON.stringify(input.executionContext, null, 2)}`,
`Current Handoff Payload:\n${JSON.stringify(input.context.handoff.payload, null, 2)}`,
`Session Flags:\n${JSON.stringify(input.context.state.flags, null, 2)}`,
`Recent Domain History:\n${JSON.stringify(recentHistory, null, 2)}`,
].join("\n\n");
}
async function runCodexActor(input: {
runtime: ProviderRunRuntime;
actorInput: ActorExecutionInput;
}): Promise<ActorExecutionResult> {
const { runtime, actorInput } = input;
const prompt = buildActorPrompt(actorInput);
const startedAt = Date.now();
const apiKey = resolveOpenAiApiKey(runtime.config.provider);
const codex = new Codex({
...(apiKey ? { apiKey } : {}),
...(runtime.config.provider.openAiBaseUrl
? { baseUrl: runtime.config.provider.openAiBaseUrl }
: {}),
...(actorInput.mcp.resolvedConfig.codexConfig
? { config: actorInput.mcp.resolvedConfig.codexConfig }
: {}),
env: runtime.sessionContext.runtimeInjection.env,
});
const thread = codex.startThread({
workingDirectory: runtime.sessionContext.runtimeInjection.workingDirectory,
skipGitRepoCheck: runtime.config.provider.codexSkipGitCheck,
});
const turn = await runtime.sessionContext.runInSession(() =>
thread.run(prompt, {
signal: actorInput.signal,
outputSchema: ACTOR_RESPONSE_SCHEMA,
}),
);
const usage: ProviderUsage = {
...(turn.usage
? {
tokenInput: turn.usage.input_tokens + turn.usage.cached_input_tokens,
tokenOutput: turn.usage.output_tokens,
tokenTotal: turn.usage.input_tokens + turn.usage.cached_input_tokens + turn.usage.output_tokens,
}
: {}),
durationMs: Date.now() - startedAt,
};
const parsed = parseActorExecutionResultFromModelOutput({
rawText: turn.finalResponse,
});
return ensureUsageMetadata({
result: parsed,
providerUsage: usage,
});
}
type ClaudeTurnResult = {
text: string;
structuredOutput?: unknown;
usage: ProviderUsage;
};
function buildClaudeOptions(input: {
runtime: ProviderRunRuntime;
actorInput: ActorExecutionInput;
}): Options {
const { runtime, actorInput } = input;
const authOptionOverrides = runtime.config.provider.anthropicOauthToken
? { authToken: runtime.config.provider.anthropicOauthToken }
: (() => {
const token = resolveAnthropicToken(runtime.config.provider);
return token ? { apiKey: token } : {};
})();
const runtimeEnv = {
...runtime.sessionContext.runtimeInjection.env,
...buildClaudeAuthEnv(runtime.config.provider),
};
return {
maxTurns: 1,
...(runtime.config.provider.claudeModel
? { model: runtime.config.provider.claudeModel }
: {}),
...(runtime.config.provider.claudeCodePath
? { pathToClaudeCodeExecutable: runtime.config.provider.claudeCodePath }
: {}),
...authOptionOverrides,
...(actorInput.mcp.resolvedConfig.claudeMcpServers
? { mcpServers: actorInput.mcp.resolvedConfig.claudeMcpServers as Options["mcpServers"] }
: {}),
canUseTool: actorInput.mcp.createClaudeCanUseTool(),
cwd: runtime.sessionContext.runtimeInjection.workingDirectory,
env: runtimeEnv,
outputFormat: CLAUDE_OUTPUT_FORMAT,
};
}
async function runClaudeTurn(input: {
runtime: ProviderRunRuntime;
actorInput: ActorExecutionInput;
prompt: string;
}): Promise<ClaudeTurnResult> {
const options = buildClaudeOptions({
runtime: input.runtime,
actorInput: input.actorInput,
});
const startedAt = Date.now();
const stream = query({
prompt: input.prompt,
options,
});
let resultText = "";
let structuredOutput: unknown;
let usage: ProviderUsage = {};
const onAbort = (): void => {
stream.close();
};
input.actorInput.signal.addEventListener("abort", onAbort, { once: true });
try {
for await (const message of stream as AsyncIterable<SDKMessage>) {
if (message.type !== "result") {
continue;
}
if (message.subtype !== "success") {
const detail = message.errors.join("; ");
throw new Error(
`Claude query failed (${message.subtype})${detail ? `: ${detail}` : ""}`,
);
}
resultText = message.result.trim();
structuredOutput = message.structured_output;
usage = {
tokenInput: message.usage.input_tokens,
tokenOutput: message.usage.output_tokens,
tokenTotal: message.usage.input_tokens + message.usage.output_tokens,
durationMs: message.duration_ms,
costUsd: message.total_cost_usd,
};
}
} finally {
input.actorInput.signal.removeEventListener("abort", onAbort);
stream.close();
}
if (!resultText && structuredOutput !== undefined) {
resultText = JSON.stringify(structuredOutput);
}
if (!resultText) {
throw new Error("Claude run completed without a final result.");
}
return {
text: resultText,
structuredOutput,
usage: {
...usage,
durationMs: usage.durationMs ?? Date.now() - startedAt,
},
};
}
async function runClaudeActor(input: {
runtime: ProviderRunRuntime;
actorInput: ActorExecutionInput;
}): Promise<ActorExecutionResult> {
const prompt = buildActorPrompt(input.actorInput);
const turn = await input.runtime.sessionContext.runInSession(() =>
runClaudeTurn({
runtime: input.runtime,
actorInput: input.actorInput,
prompt,
}),
);
const parsed = parseActorExecutionResultFromModelOutput({
rawText: turn.text,
structuredOutput: turn.structuredOutput,
});
return ensureUsageMetadata({
result: parsed,
providerUsage: turn.usage,
});
}
export async function createProviderRunRuntime(input: {
provider: RunProvider;
initialPrompt: string;
config: Readonly<AppConfig>;
}): Promise<ProviderRunRuntime> {
const sessionContext = await createSessionContext(input.provider, {
prompt: input.initialPrompt,
config: input.config,
});
return {
provider: input.provider,
config: input.config,
sessionContext,
close: async () => {
await sessionContext.close();
},
};
}
export function createProviderActorExecutor(runtime: ProviderRunRuntime): ActorExecutor {
return async (actorInput) => {
try {
if (runtime.provider === "codex") {
return await runCodexActor({
runtime,
actorInput,
});
}
return await runClaudeActor({
runtime,
actorInput,
});
} catch (error) {
return {
status: "failure",
payload: {
error: toErrorMessage(error),
},
failureKind: "hard",
failureCode: `provider_${runtime.provider}_execution_error`,
};
}
};
}