| 1 | import { createRoot } from "react-dom/client"; |
| 2 | import { flushSync } from "react-dom"; |
| 3 | import { Composer } from "../src/components/Composer"; |
| 4 | import { Transcript } from "../src/components/Transcript"; |
| 5 | import { LocaleProvider } from "../src/lib/i18n"; |
| 6 | import { ToastProvider } from "../src/lib/toast"; |
| 7 | import { initialState, reducer, type State } from "../src/lib/useController"; |
| 8 | import { runtimeStateStore, type RuntimeState } from "../src/lib/runtimeStateStore"; |
| 9 | import { orderedLocalSubmissions } from "../src/lib/localSubmissionState"; |
| 10 | import { sessionOperationHistoryMessage } from "../src/lib/sessionMaintenanceOperation"; |
| 11 | import { submitTurn } from "../src/lib/turnSubmit"; |
| 12 | import type { AppBindings } from "../src/lib/bridge"; |
| 13 | import { installDesktopHostStub } from "../src/__tests__/desktopHostStub"; |
| 14 | import "../src/styles.css"; |
| 15 | |
| 16 | const root = createRoot(document.getElementById("root")!); |
| 17 | const operation = { operationId: "compact-A", kind: "compact", activity: "running", status: "running", operationRevision: 1, runtimeEpoch: "epoch-A" }; |
| 18 | const terminal = { ...operation, status: "completed", activity: "finalizing", operationRevision: 3, summary: "The durable compaction summary survived switching sessions.", applied: true }; |
| 19 | const runtime = (revision: number, maintenance?: RuntimeState["maintenance"]): RuntimeState => ({ |
| 20 | schemaVersion: 1, projectionEpoch: "projection-A", runtimeEpoch: "epoch-A", activityRevision: revision, revision, |
| 21 | phase: maintenance ? "executing" : "idle", running: Boolean(maintenance), turnId: "", turnStatus: "", turnEventSeq: 0, |
| 22 | pendingPrompt: false, cancelRequested: false, cancellable: Boolean(maintenance), backgroundJobs: 0, activity: maintenance ? "maintenance" : "", maintenance, |
| 23 | }); |
| 24 | const states = new Map<string, State>(["A", "B"].map(id => [id, reducer(initialState, { type: "history", messages: [{ role: "user", messageId: id, content: `Session ${id}` }] })])); |
| 25 | let active = "A"; |
| 26 | let submissions = 0; |
| 27 | let starts = 0; |
| 28 | let lastInput = ""; |
| 29 | let revision = 0; |
| 30 | let admission: Promise<void> | undefined; |
| 31 | let releaseAdmission: (() => void) | undefined; |
| 32 | let rejectAdmission: ((reason: Error) => void) | undefined; |
| 33 | const noop = () => {}; |
| 34 | // The Composer captures the real submission target before asynchronous preparation. |
| 35 | installDesktopHostStub({ |
| 36 | CaptureInboxTarget: (tabId: string, sessionPath: string) => ({ tabId, sessionPath, generation: 1, selection: 0, remote: false }), |
| 37 | Commands: () => [], Models: () => [], ModelsForTab: () => [], |
| 38 | TranscriptOutlineForTab: () => ({ turns: [], hasMore: false }), |
| 39 | }); |
| 40 | const bridge = { StartTurnForTab: async (tab: string, input: string) => { |
| 41 | lastInput = input; |
| 42 | await admission; |
| 43 | if (states.get(tab)!.runtimeStateSnapshot?.maintenance) return { disposition: "management_handled", operationId: operation.operationId, managementErrorCode: "maintenance_busy" }; |
| 44 | starts++; |
| 45 | states.set(tab, reducer(states.get(tab)!, { type: "runtime_snapshot", snapshot: runtime(1, operation) })); |
| 46 | paint(); |
| 47 | return { disposition: "management_handled", operationId: operation.operationId }; |
| 48 | } } as unknown as AppBindings; |
| 49 | async function send(display: string, input = display, tab = active) { |
| 50 | const submissionId = `submit-${++submissions}`; |
| 51 | states.set(tab, reducer(states.get(tab)!, { type: "management_requested" })); |
| 52 | const [outcome, receipt] = await submitTurn(bridge, tab, submissionId, display, input, ""); |
| 53 | if (outcome !== 2) throw new Error("expected management receipt"); |
| 54 | states.set(tab, reducer(states.get(tab)!, { type: "management_confirmed", submissionId, receipt })); |
| 55 | paint(); |
| 56 | } |
| 57 | function paint() { |
| 58 | runtimeStateStore.commit({ epoch: "fixture", revision: ++revision, topics: [], sessions: [...states.entries()].map(([tabId, state]) => ({ |
| 59 | tabId, scope: "global", workspaceRoot: "", topicId: "", sessionPath: `fixture-${tabId}`, sessionGeneration: 1, |
| 60 | open: true, remote: false, freshness: "synced", state: state.runtimeStateSnapshot ?? runtime(0), |
| 61 | })) }); |
| 62 | const state = states.get(active)!; |
| 63 | flushSync(() => root.render(<LocaleProvider><ToastProvider> |
| 64 | <div style={{ height: "100vh", display: "flex", flexDirection: "column" }}> |
| 65 | <nav><button onClick={() => { active = active === "A" ? "B" : "A"; paint(); }}>Switch session</button><span data-active-session>{active}</span></nav> |
| 66 | <div style={{ flex: 1, minHeight: 0, position: "relative" }}><Transcript items={state.items} tabId={active} geometrySessionKey={active} |
| 67 | localSubmissions={orderedLocalSubmissions(state)} running={state.running} onPrompt={noop} /></div> |
| 68 | <div style={{ width: "100%", padding: "16px 24px", boxSizing: "border-box" }}><Composer tabId={active} sessionKey={active} inboxSessionPath={`fixture-${active}`} running={state.running} commandCatalog={[]} |
| 69 | ready collaborationMode="normal" toolApprovalMode="ask" modelLabel="Fixture" onSend={send} |
| 70 | onCancel={async () => ({ discardedItemIds: [] })} onCycleMode={noop} onSetMode={noop} onSetCollaborationMode={noop} |
| 71 | onSetToolApprovalMode={noop} onClearGoal={noop} onEditGoal={noop} onPauseGoal={noop} onResumeGoal={noop} |
| 72 | onSwitchModel={() => true} onSetEffort={noop} /></div> |
| 73 | </div> |
| 74 | </ToastProvider></LocaleProvider>)); |
| 75 | } |
| 76 | Object.assign(window, { maintenanceProbe: { |
| 77 | holdAdmission: () => { admission = new Promise<void>((resolve, reject) => { releaseAdmission = resolve; rejectAdmission = reject; }); }, |
| 78 | releaseAdmission: () => { releaseAdmission?.(); admission = undefined; }, |
| 79 | rejectAdmission: () => { rejectAdmission?.(new Error("fixture admission failure")); admission = undefined; }, |
| 80 | stats: () => ({ starts, submissions }), |
| 81 | lifecycle: () => ({ running: states.get("A")!.running, cancellable: states.get("A")!.cancellable, echoes: states.get("A")!.localSubmissionOrder.length }), |
| 82 | lastInput: () => lastInput, |
| 83 | complete: () => { |
| 84 | let state = reducer(states.get("A")!, { type: "event", e: { kind: "session_operation", sessionOperation: terminal } }); |
| 85 | state = reducer(state, { type: "runtime_snapshot", snapshot: runtime(3) }); |
| 86 | states.set("A", state); paint(); |
| 87 | }, |
| 88 | coldRestore: () => { |
| 89 | const history = sessionOperationHistoryMessage(`maintenance:${operation.operationId}`, terminal); |
| 90 | states.set("A", reducer(reducer(initialState, { type: "history", messages: [history] }), { type: "runtime_snapshot", snapshot: runtime(3) })); |
| 91 | paint(); |
| 92 | }, |
| 93 | } }); |
| 94 | paint(); |
| 95 |