// The agent module's page, on the shell's F10 contract (docs/reference/app-kit.md): a loader the web shell // runs on the server under `////agent`, and the view of what it returned. // // /agent the run list, the approval queue and the start form // /agent/ one run: the plan with per-step progress, the budget, the recovery controls and // a completed run's AI-generated answer, live through the run's event stream // // Every read is a procedure through the same caller the API uses, and every label is translated in the // loader. The run page's first render is `agent.run.status`, the seed of a client island that follows the // run's AG-UI events (live-run.tsx); both come from the durable record, so a reload after a restart tells // the truth. The progress bar is a real element, keyboard-operable controls only. import { type Container, notFound, type TransportCaller } from "@conusai-kit/core" import type { I18n } from "@conusai-kit/i18n" import { type ProjectPage, type ProjectPageViewProps, projectPage } from "@conusai-kit/ui" import { DetailLayout, FactList, Page, PageHeader } from "@conusai-kit/ui/patterns" import type { ReactElement } from "react" import { type ApprovalView, approvalList, type RunView, runList, runStatus } from "../contract/index.js" import { ApprovalQueue, type ApprovalQueueLabels } from "./approval-queue.js" import { LiveRun, type LiveRunLabels } from "./live-run.js" import { RunList, type RunListLabels } from "./live-runs.js" import { StartForm, type StartFormLabels } from "./start-form.js" /** The run list, the approval queue and the start form. */ export interface AgentListData { readonly kind: "list" readonly title: string readonly intro: string readonly approvals: readonly ApprovalView[] readonly approvalLabels: ApprovalQueueLabels readonly start: StartFormLabels readonly runLabels: RunListLabels /** The first page of runs: the live list's seed. */ readonly runs: readonly RunView[] } /** One run: the seed of the live island, and the facts that do not change while it runs. */ export interface AgentRunData { readonly kind: "run" readonly back: string readonly startedBy: string readonly usageLabel: string readonly usage: string readonly run: RunView readonly labels: LiveRunLabels } export type AgentPageData = AgentListData | AgentRunData export type AgentPage = ProjectPage export const runStatusKey: Record = { queued: "runs.statusQueued", running: "runs.statusRunning", awaiting_approval: "runs.statusAwaitingApproval", completed: "runs.statusCompleted", failed: "runs.statusFailed", cancelled: "runs.statusCancelled", uncertain: "runs.statusUncertain", } const stepStatusKey: Record = { pending: "runs.stepPending", committed: "runs.stepCommitted", failed: "runs.stepFailed", cancelled: "runs.stepCancelled", uncertain: "runs.stepUncertain", } const mapValues = (keys: Record, t: (key: string) => string): Record => Object.fromEntries(Object.entries(keys).map(([status, key]) => [status, t(key)])) as Record export const agentPage: AgentPage = projectPage({ async load({ slugs, segments, caller, i18n }): Promise { const t = (key: string, values?: Record) => i18n.message(`agent.${key}`, values) const failed = i18n.message("app.failed") if (segments.length === 0) { const [runs, approvals] = await Promise.all([caller.call(runList, slugs), caller.call(approvalList, slugs)]) return { kind: "list", title: t("title"), intro: t("intro"), approvals: approvals.approvals as ApprovalView[], approvalLabels: { heading: t("approvals.heading"), empty: t("approvals.empty"), proposed: t("approvals.proposed"), review: t("approvals.review"), approve: t("approvals.approve"), reject: t("approvals.reject"), decided: t("approvals.decided"), disclosure: t("approvals.disclosure"), failed, }, start: { heading: t("start.heading"), goalLabel: t("start.goalLabel"), goalHint: t("start.goalHint"), planLabel: t("start.planLabel"), planHint: t("start.planHint"), submit: t("start.submit"), starting: t("start.starting"), failed, }, runLabels: { heading: t("runs.heading"), empty: t("runs.empty"), goal: t("runs.goal"), status: t("runs.status"), progress: t("runs.progress"), startedBy: t("runs.startedBy"), statuses: mapValues(runStatusKey, t), }, runs: runs.runs, } } // A path that is not one run id is a page that does not exist. const [runId, ...rest] = segments if (runId === undefined || rest.length > 0) throw notFound("no such page") const run = await caller.call(runStatus, { ...slugs, runId }) return { kind: "run", back: t("runs.back"), startedBy: t("runs.startedBy"), usageLabel: t("runs.usageLabel"), usage: t("runs.usage", { steps: run.usageSteps, cost: run.usageCostMicrocents }), run, labels: { status: t("runs.status"), progress: t("runs.progress"), plan: t("runs.plan"), step: t("runs.step"), tool: t("runs.tool"), needsApproval: t("runs.needsApproval"), answer: t("runs.answer"), answerDisclosure: t("runs.answerDisclosure"), streamLost: t("runs.streamLost"), statuses: mapValues(runStatusKey, t), stepStatuses: mapValues(stepStatusKey, t), controls: { cancel: t("runs.cancel"), cancelling: t("runs.cancelling"), failedHint: t("recovery.failedHint"), cancelledHint: t("recovery.cancelledHint"), uncertainHint: t("recovery.uncertainHint"), budgetExhausted: t("recovery.budgetExhausted"), approvalUnavailable: t("recovery.approvalUnavailable"), toolError: t("recovery.toolError"), planRefused: t("recovery.planRefused"), internal: t("recovery.internal"), }, }, } }, View({ slugs, data }: ProjectPageViewProps): ReactElement { if (data.kind === "list") { return ( }> ) } const { run } = data return ( {data.back}} /> ) }, })