import type { V2ItemSupport } from "@t3tools/client-runtime/state/item-support"; import type { ThreadId } from "@t3tools/contracts"; import { formatDuration } from "@t3tools/shared/orchestrationTiming"; import * as DateTime from "effect/DateTime"; import type { ThreadFeedActivity } from "./threadActivity"; export interface ThreadActivityInspectorField { readonly label: string; readonly value: string; } export interface ThreadActivityInspectorBlock { readonly label: string; readonly value: string; readonly monospaced: boolean; } export interface ThreadActivityFileLink { readonly label: string; readonly path: string; readonly line?: number; } export interface ThreadActivityWebLink { readonly label: string; readonly url: string; } export interface ThreadActivityInspectorModel { readonly fields: ReadonlyArray; readonly blocks: ReadonlyArray; readonly fileLinks: ReadonlyArray; readonly webLinks: ReadonlyArray; readonly canRollback: boolean; readonly rollbackTarget: { readonly threadId: ThreadId; readonly checkpointId: string; readonly scopeId: string; } | null; readonly structuredDetails: string; } function formatStructured(value: unknown): string { if (typeof value === "string") return value; try { return JSON.stringify(value, null, 2) ?? String(value); } catch { return String(value); } } function durationLabel( startedAt: DateTime.Utc | null, completedAt: DateTime.Utc | null, ): string | null { if (startedAt === null) return null; const start = DateTime.toEpochMillis(startedAt); const end = completedAt === null ? Date.now() : DateTime.toEpochMillis(completedAt); return formatDuration(Math.max(0, end - start)); } function addBlock( blocks: ThreadActivityInspectorBlock[], label: string, value: unknown, monospaced = true, ): void { if (value === undefined || value === null || value === "") return; blocks.push({ label, value: formatStructured(value), monospaced }); } export function buildThreadActivityInspector( activity: ThreadFeedActivity, support: V2ItemSupport, currentThreadId: ThreadId, ): ThreadActivityInspectorModel { const row = activity.projectedItem; const item = row.item; const fields: ThreadActivityInspectorField[] = [ { label: "Item", value: item.type.replaceAll("_", " ") }, { label: "Status", value: item.status.replaceAll("_", " ") }, ]; const duration = durationLabel(item.startedAt, item.completedAt); if (duration) fields.push({ label: "Duration", value: duration }); if (row.visibility !== "local") fields.push({ label: "Visibility", value: row.visibility }); if (support.run) fields.push({ label: "Run", value: support.run.status }); const latestAttempt = support.attempts.at(-1); if (latestAttempt) { fields.push({ label: "Attempt", value: `${latestAttempt.attemptOrdinal} · ${latestAttempt.status} · ${latestAttempt.reason.replaceAll("_", " ")}`, }); } if (support.node) { fields.push({ label: "Node", value: `${support.node.kind.replaceAll("_", " ")} · ${support.node.status}`, }); } if (support.providerThread) { fields.push({ label: "Provider thread", value: `${support.providerThread.providerInstanceId} · ${support.providerThread.status}`, }); } if (support.providerTurn) { fields.push({ label: "Provider turn", value: support.providerTurn.status }); } if (support.providerSession) { fields.push({ label: "Session", value: `${support.providerSession.status} · ${support.providerSession.model ?? "default model"}`, }); fields.push({ label: "Working directory", value: support.providerSession.cwd }); } if (support.runtimeRequest) { fields.push({ label: "Request", value: `${support.runtimeRequest.status} · ${support.runtimeRequest.responseCapability.type.replaceAll("_", " ")}`, }); } const blocks: ThreadActivityInspectorBlock[] = []; const fileLinks: ThreadActivityFileLink[] = []; const webLinks: ThreadActivityWebLink[] = []; if (support.attempts.length > 1) { addBlock( blocks, "Attempt history", support.attempts .map( (attempt) => `Attempt ${attempt.attemptOrdinal} · ${attempt.status} · ${attempt.reason.replaceAll("_", " ")}`, ) .join("\n"), ); } switch (item.type) { case "reasoning": addBlock(blocks, "Reasoning", item.text, false); break; case "command_execution": addBlock(blocks, "Command", item.input); addBlock(blocks, "Output", item.output); if (item.exitCode !== undefined) { addBlock(blocks, "Exit", `Process exited with code ${item.exitCode}`); } break; case "file_change": fileLinks.push({ label: item.fileName, path: item.fileName }); if (item.additions !== undefined || item.deletions !== undefined) { fields.push({ label: "Changes", value: `+${item.additions ?? 0} −${item.deletions ?? 0}`, }); } addBlock(blocks, "Patch", item.diffStr); addBlock(blocks, "Before", item.oldStr); addBlock(blocks, "After", item.newStr); break; case "file_search": addBlock(blocks, "Query", item.pattern); for (const result of item.results ?? []) { fileLinks.push({ label: result.preview ? `${result.fileName} — ${result.preview}` : result.fileName, path: result.fileName, ...(result.line === undefined ? {} : { line: result.line }), }); } break; case "web_search": addBlock(blocks, "Queries", item.patterns?.join("\n")); for (const result of item.results ?? []) { if (result.url) { webLinks.push({ label: result.title ?? result.url, url: result.url, }); } if (result.snippet) addBlock(blocks, result.title ?? "Search result", result.snippet, false); } break; case "dynamic_tool": addBlock(blocks, "Input", item.input); addBlock(blocks, "Output", item.output); break; case "approval_request": addBlock(blocks, "Prompt", item.prompt, false); break; case "user_input_request": addBlock( blocks, "Questions", item.questions.map((question) => question.question).join("\n"), false, ); break; case "checkpoint": addBlock( blocks, "Files", item.files .map((file) => `${file.path} +${file.additions} −${file.deletions} ${file.kind}`) .join("\n"), ); break; case "subagent": addBlock(blocks, "Prompt", item.prompt, false); addBlock(blocks, "Progress", support.subagent?.progress ?? item.progress, false); addBlock(blocks, "Result", support.subagent?.result ?? item.result, false); if (support.subagent) { fields.push({ label: "Delegated task", value: `${support.subagent.origin.replaceAll("_", " ")} · ${support.subagent.status}`, }); } break; case "handoff": addBlock(blocks, "Summary", item.summary, false); fields.push({ label: "Handoff", value: `${item.strategy.replaceAll("_", " ")} · ${support.contextHandoff?.status ?? item.status}`, }); if (support.contextTransfer) { fields.push({ label: "Transfer", value: `${support.contextTransfer.type.replaceAll("_", " ")} · ${support.contextTransfer.status}`, }); if (support.contextTransfer.resolution) { fields.push({ label: "Context", value: support.contextTransfer.resolution.strategy.replaceAll("_", " "), }); } } break; case "error": addBlock(blocks, "Error", item.failure.message, false); if (item.failure.code) fields.push({ label: "Code", value: item.failure.code }); if (item.failure.retryable !== null) { fields.push({ label: "Retryable", value: item.failure.retryable ? "yes" : "no" }); } break; case "proposed_plan": addBlock(blocks, "Plan", item.markdown, false); break; case "todo_list": addBlock( blocks, "Tasks", item.steps .map((step) => `${step.status === "completed" ? "✓" : "○"} ${step.text}`) .join("\n"), false, ); addBlock(blocks, "Explanation", item.explanation, false); break; case "compaction": addBlock(blocks, "Summary", item.summary, false); if (item.beforeTokenCount !== undefined || item.afterTokenCount !== undefined) { fields.push({ label: "Context tokens", value: `${item.beforeTokenCount ?? "?"} → ${item.afterTokenCount ?? "?"}`, }); } break; case "run_interrupt_request": case "run_interrupt_result": addBlock(blocks, "Message", item.message, false); break; case "fork": case "thread_created": case "user_message": case "assistant_message": break; } const checkpoint = item.type === "checkpoint" && row.sourceThreadId === currentThreadId ? support.checkpoint : null; return { fields, blocks, fileLinks, webLinks, canRollback: checkpoint?.status === "ready", rollbackTarget: checkpoint?.status === "ready" ? { threadId: row.sourceThreadId, checkpointId: checkpoint.id, scopeId: checkpoint.scopeId, } : null, structuredDetails: formatStructured({ visibility: row.visibility, sourceThreadId: row.sourceThreadId, sourceItemId: row.sourceItemId, item, }), }; }