// Top-level orchestrator for `opencode mini`. // // Wires the boot sequence, lifecycle (renderer + footer), stream transport, // and prompt queue together into a single session loop. The frontend paints // before resolving its Session, then: // 1. resolves TUI config, model info, and session history, // 2. creates the split-footer lifecycle (renderer + RunFooter), // 3. starts the stream transport (SDK event subscription), lazily for fresh // local sessions, // 4. runs the prompt queue until the footer closes. import { SessionMessage } from "@opencode-ai/schema/session-message" import type { LocationRef } from "@opencode-ai/client/promise" import type { Config } from "../config" import { loadRunAgents, loadRunCommands, loadRunReferences } from "./catalog.shared" import { resolveMiniSettings, resolveModelInfo, resolveModelInfoStrict, resolveRunTuiConfig, resolveSessionInfo, } from "./runtime.boot" import { createRuntimeLifecycle } from "./runtime.lifecycle" import { cycleVariant, formatModelLabel, resolveVariant } from "./variant.shared" import type { LocalReplayRow, MiniHost, MiniSettings, RunInput, RunPrompt, RunProvider, RunTuiConfig, StreamCommit, } from "./types" type BootContext = Pick & { location: LocationRef } type CreateSessionInput = { location: LocationRef agent: string | undefined model: RunInput["model"] variant: string | undefined } type CreateSession = (sdk: RunInput["sdk"], input: CreateSessionInput, signal?: AbortSignal) => Promise type Reconnect = (signal: AbortSignal) => Promise type RunRuntimeInput = { host: MiniHost boot: () => Promise resolveSession: (sdk: RunInput["sdk"], signal: AbortSignal) => Promise createSession?: CreateSession reconnect?: Reconnect files: RunInput["files"] initialInput?: string thinking?: boolean replay?: boolean replayLimit?: number demo?: RunInput["demo"] tuiConfig?: RunTuiConfig | Promise config?: Pick } export type RunDeferredInput = { host: MiniHost sdk: RunInput["sdk"] reconnect?: Reconnect directory: string target: (sdk: RunInput["sdk"], signal: AbortSignal) => Promise createSession?: CreateSession agent: RunInput["agent"] model: RunInput["model"] variant: RunInput["variant"] files: RunInput["files"] initialInput?: string thinking?: boolean replay?: boolean replayLimit?: number demo?: RunInput["demo"] tuiConfig?: RunTuiConfig | Promise config?: Pick } type StreamTransportModule = Pick< Awaited, "createSessionTransport" | "formatUnknownError" > export type RunRuntimeDeps = { createRuntimeLifecycle?: typeof createRuntimeLifecycle streamTransport?: Promise } type StreamState = { mod: StreamTransportModule handle: Awaited> } type RunDemo = ReturnType<(typeof import("./demo"))["createRunDemo"]> type ResolvedSession = { sdk?: RunInput["sdk"] sessionID: string sessionTitle?: string location: RunInput["location"] model: RunInput["model"] variant: string | undefined agent?: string | undefined resume?: boolean } type RuntimeState = { sdk: RunInput["sdk"] shown: boolean aborting: boolean model: RunInput["model"] defaultModel: RunInput["model"] providers: RunProvider[] variants: string[] activeVariant: string | undefined sessionID: string history: RunPrompt[] localRows: LocalReplayRow[] sessionTitle?: string agent: string | undefined location: LocationRef switching?: Promise demo?: RunDemo selectSubagent?: (sessionID: string | undefined) => void session?: Promise stream?: Promise } type ClientAttempt = { sdk: RunInput["sdk"] generation: number signal: AbortSignal } function variantsFor(providers: RunProvider[], model: RunInput["model"]) { if (!model) { return [] } return Object.keys(providers.find((item) => item.id === model.providerID)?.models?.[model.modelID]?.variants ?? {}) } function formRequestOptions(location: LocationRef | undefined) { if (!location) return return { headers: { "x-opencode-directory": encodeURIComponent(location.directory), ...(location.workspaceID ? { "x-opencode-workspace": location.workspaceID } : {}), }, } } function formAlreadySettled(error: unknown) { return !!error && typeof error === "object" && Reflect.get(error, "_tag") === "FormAlreadySettledError" } const RESIZE_DELAY = 250 const LOCAL_REPLAY_ROW_LIMIT = 100 function abortable(task: Promise, signal: AbortSignal): Promise { if (signal.aborted) return Promise.resolve(undefined) return new Promise((resolve) => { const abort = () => { signal.removeEventListener("abort", abort) resolve(undefined) } signal.addEventListener("abort", abort, { once: true }) void task.then( (value) => { signal.removeEventListener("abort", abort) resolve(value) }, () => { signal.removeEventListener("abort", abort) resolve(undefined) }, ) }) } // Core runtime loop. Boot resolves the SDK context, then we set up the // lifecycle (renderer + footer), wire the stream transport for SDK events, // and feed prompts through the queue until the user exits. // // Files only attach on the first prompt turn -- after that, includeFiles // flips to false so subsequent turns don't re-send attachments. async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDeps = {}): Promise { const start = input.host.startup.now() const log = input.host.diagnostics.trace const config = input.config const configState: { current: MiniSettings } = { current: resolveMiniSettings() } const tuiConfigTask = resolveRunTuiConfig(input.tuiConfig, input.host.platform).then((tuiConfig) => { configState.current = resolveMiniSettings(tuiConfig) return tuiConfig }) const ctx = await input.boot() const runtimeController = new AbortController() const session = { first: true, history: [] as RunPrompt[], model: undefined as RunInput["model"], variant: undefined as string | undefined, } const savedVariant = await input.host.preferences.resolveVariant(ctx.model) const state: RuntimeState = { sdk: ctx.sdk, shown: !session.first, aborting: false, model: ctx.model ?? session.model, defaultModel: undefined, providers: [], variants: [], activeVariant: resolveVariant(ctx.variant, session.variant, savedVariant, []), sessionID: "", history: [...session.history], localRows: [], agent: ctx.agent, location: ctx.location, } const settleForm = async (sessionID: string, formID: string) => { if (!state.stream) return const stream = await state.stream stream.handle.settleForm?.(sessionID, formID) } const shell = await (deps.createRuntimeLifecycle ?? createRuntimeLifecycle)({ host: input.host, getDirectory: () => state.location.directory, findFiles: (query) => state.sdk.file .find({ query, type: "file", location: { directory: state.location.directory, workspace: state.location.workspaceID }, }) .then((result) => result.data.map((file) => file.path)) .catch(() => []), agents: [], references: [], sessionID: state.sessionID, sessionTitle: state.sessionTitle, getSessionID: () => state.sessionID, first: session.first, history: state.history, agent: state.agent, model: state.model, variant: state.activeVariant, tuiConfig: tuiConfigTask, onMiniSettingChange: config ? async (change) => { const info = await config.update((draft) => { if (!draft.mini || typeof draft.mini !== "object") draft.mini = {} draft.mini[change.key] = change.value }) configState.current = resolveMiniSettings(info) return configState.current } : undefined, onPermissionReply: async (next) => { if (state.demo?.permission(next)) { return } log?.write("send.permission.reply", next) await state.sdk.permission.reply(next) }, onFormReply: async (next) => { if (state.demo?.formReply(next)) return try { await state.sdk.form.reply(next, formRequestOptions(next.sessionID === "global" ? next.location : undefined)) } catch (error) { if (!formAlreadySettled(error)) throw error } await settleForm(next.sessionID, next.formID) }, onFormCancel: async (next) => { if (state.demo?.formCancel(next)) return try { await state.sdk.form.cancel(next, formRequestOptions(next.sessionID === "global" ? next.location : undefined)) } catch (error) { if (!formAlreadySettled(error)) throw error } await settleForm(next.sessionID, next.formID) }, onCycleVariant: () => { const model = state.model ?? state.defaultModel if (!model || state.variants.length === 0) { return { status: "no variants available", } } if (!state.model) state.model = model state.activeVariant = cycleVariant(state.activeVariant, state.variants) void input.host.preferences.saveVariant(model, state.activeVariant) return { status: state.activeVariant ? `variant ${state.activeVariant}` : "variant default", modelLabel: formatModelLabel(model, state.activeVariant, state.providers), variant: state.activeVariant, } }, onAgentSelect: (agent) => { state.agent = agent }, onModelSelect: async (model) => { if (state.model?.providerID === model.providerID && state.model.modelID === model.modelID) { return } state.model = model state.activeVariant = undefined state.variants = variantsFor(state.providers, model) const switching = input.host.preferences.resolveVariant(model).then((saved) => { const current = state.model if (!current || current.providerID !== model.providerID || current.modelID !== model.modelID) { return } state.activeVariant = resolveVariant(undefined, undefined, saved, state.variants) }) state.switching = switching await switching if (state.switching === switching) { state.switching = undefined } const current = state.model if (!current || current.providerID !== model.providerID || current.modelID !== model.modelID) { return } return { modelLabel: formatModelLabel(model, state.activeVariant, state.providers), status: `model ${model.modelID}`, variant: state.activeVariant, variants: state.variants, } }, onVariantSelect: async (variant) => { const model = state.model ?? state.defaultModel if (!model || state.variants.length === 0) { return { status: "no variants available", } } if (variant && !state.variants.includes(variant)) { return { status: `variant ${variant} unavailable`, } } if (!state.model) state.model = model state.activeVariant = variant void input.host.preferences.saveVariant(model, state.activeVariant) return { status: state.activeVariant ? `variant ${state.activeVariant}` : "variant default", modelLabel: formatModelLabel(model, state.activeVariant, state.providers), variant: state.activeVariant, variants: state.variants, } }, onInterrupt: () => { if (!state.sessionID || state.aborting) { return false } state.aborting = true void ( state.stream ? state.stream.then((item) => item.handle.interruptActiveTurn()) : state.sdk.session.interrupt({ sessionID: state.sessionID }) ) .catch(() => {}) .finally(() => { state.aborting = false }) return true }, onBackground: () => { if (!state.sessionID) { return } log?.write("send.background", { sessionID: state.sessionID }) void state.sdk.session.background({ sessionID: state.sessionID }).catch(() => {}) }, onQueuedPromptAction: async (action, inputID) => { if (!state.sessionID) return log?.write(`send.pending.${action}`, { sessionID: state.sessionID, inputID }) if (action === "steer") { await state.sdk.session.pending.steer({ sessionID: state.sessionID, inputID }) return } await state.sdk.session.pending.cancel({ sessionID: state.sessionID, inputID }) }, onSubagentInterrupt: (sessionID) => { log?.write("send.subagent.interrupt", { sessionID }) void state.sdk.session.interrupt({ sessionID }).catch(() => {}) }, onSubagentSelect: (sessionID) => { state.selectSubagent?.(sessionID) log?.write("subagent.select", { sessionID, }) }, }) await tuiConfigTask const thinking = () => input.thinking ?? configState.current.thinking === "show" const footer = shell.footer const firstPaint = footer.idle().catch(() => {}) const offRuntimeClose = footer.onClose(() => runtimeController.abort()) let clientGeneration = 0 let clientController = new AbortController() let modelAttempt: AbortController | undefined let modelLoad: Promise | undefined let modelLoadQueued = false let modelLoadStarted = false const updateClient = (sdk: RunInput["sdk"]) => { if (state.sdk === sdk) return state.sdk = sdk clientGeneration++ clientController.abort() clientController = new AbortController() modelAttempt?.abort() if (modelLoadStarted && !state.model) void requestModelLoad() } const clientAttempt = (signal?: AbortSignal): ClientAttempt => ({ sdk: state.sdk, generation: clientGeneration, signal: AbortSignal.any( signal ? [runtimeController.signal, clientController.signal, signal] : [runtimeController.signal, clientController.signal], ), }) const currentClient = (attempt: ClientAttempt) => !footer.isClosed && !attempt.signal.aborted && attempt.generation === clientGeneration && attempt.sdk === state.sdk const ensureSession = () => { if (state.sessionID) { return Promise.resolve() } if (state.session) { return state.session } const task = input .resolveSession(state.sdk, runtimeController.signal) .then(async (next) => { if (next.sdk) updateClient(next.sdk) if (footer.isClosed || runtimeController.signal.aborted) return state.sessionID = next.sessionID state.sessionTitle = next.sessionTitle ?? state.sessionTitle shell.setTitle(state.sessionTitle) state.agent = next.agent state.location = next.location state.model = next.model state.activeVariant = next.variant footer.event({ type: "agent", agent: state.agent }) if (!next.resume) return const resumed = await resolveSessionInfo(state.sdk, next.sessionID, next.model, runtimeController.signal) if (footer.isClosed || runtimeController.signal.aborted) return session.first = resumed.first session.history = resumed.history session.model = resumed.model session.variant = resumed.variant state.shown = !resumed.first state.history = [...resumed.history] state.model = next.model ?? resumed.model const resumedSavedVariant = state.model ? await input.host.preferences.resolveVariant(state.model) : undefined state.activeVariant = resolveVariant(next.variant, resumed.variant, resumedSavedVariant, []) session.variant = state.activeVariant footer.event({ type: "history", history: resumed.history }) footer.event({ type: "first", first: resumed.first }) if (footer.isClosed || runtimeController.signal.aborted) return await shell.resetForReplay({ sessionTitle: state.sessionTitle, sessionID: state.sessionID, history: state.history, }) }) .catch((error) => { if (footer.isClosed || runtimeController.signal.aborted) return throw error }) state.session = task void task.catch(() => { if (state.session === task) state.session = undefined }) return task } const currentModelLoad = (generation: number, sdk: RunInput["sdk"]) => !footer.isClosed && !runtimeController.signal.aborted && generation === clientGeneration && sdk === state.sdk const loadCurrentModel = async () => { const generation = clientGeneration const sdk = state.sdk const selected = state.model const controller = new AbortController() const signal = AbortSignal.any([runtimeController.signal, controller.signal]) modelAttempt = controller try { const info = await abortable(resolveModelInfo(sdk, state.location, signal), signal) if ( !info || !currentModelLoad(generation, sdk) || (selected && (state.model?.providerID !== selected.providerID || state.model.modelID !== selected.modelID)) ) return applyModelInfo( info, selected ? session.variant : state.activeVariant, { sdk, generation, signal }, !!selected, savedVariant, ) } finally { if (modelAttempt === controller) modelAttempt = undefined } } function requestModelLoad(): Promise { modelLoadQueued = true if (modelLoad || footer.isClosed) return modelLoad ?? Promise.resolve() const task = (async () => { while (modelLoadQueued && !footer.isClosed) { modelLoadQueued = false await loadCurrentModel() } })() modelLoad = task const cleanup = () => { if (modelLoad === task) modelLoad = undefined if (modelLoadQueued && !footer.isClosed) void requestModelLoad() } void task.then(cleanup, cleanup) return task } const modelTask = firstPaint.then(async () => { if (footer.isClosed) return await ensureSession() if (footer.isClosed) return modelLoadStarted = true return requestModelLoad() }) const rememberLocal = (commit: StreamCommit) => { const last = state.localRows.at(-1) if ( last && (commit.kind === "assistant" || commit.kind === "reasoning") && last.commit.kind === commit.kind && last.commit.source === commit.source && last.commit.messageID === commit.messageID && last.commit.partID === commit.partID && last.commit.tool === commit.tool ) { state.localRows = [...state.localRows.slice(0, -1), { commit }] return } state.localRows = [...state.localRows, { commit }].slice(-LOCAL_REPLAY_ROW_LIMIT) } const applyCatalog = ( catalog: { agents: Awaited> references: Awaited> commands: Awaited> }, attempt: ClientAttempt, ) => { if (!currentClient(attempt)) return footer.event({ type: "catalog", agents: catalog.agents, references: catalog.references, commands: catalog.commands, }) } const fetchCatalog = async (attempt: ClientAttempt) => { const [agents, references, commands] = await Promise.all([ loadRunAgents(attempt.sdk, state.location, attempt.signal), loadRunReferences(attempt.sdk, state.location, attempt.signal), loadRunCommands(attempt.sdk, state.location, attempt.signal), ]) return { agents, references, commands } } const loadCatalog = async (attempt: ClientAttempt) => { const catalog = await abortable( Promise.all([ loadRunAgents(attempt.sdk, state.location, attempt.signal).catch(() => []), loadRunReferences(attempt.sdk, state.location, attempt.signal).catch(() => []), loadRunCommands(attempt.sdk, state.location, attempt.signal).catch(() => []), ]).then(([agents, references, commands]) => ({ agents, references, commands })), attempt.signal, ) if (catalog) applyCatalog(catalog, attempt) } function applyModelInfo( info: Awaited>, current: string | undefined, attempt: ClientAttempt, boot = false, saved = savedVariant, ) { if (!currentClient(attempt)) return state.providers = info.providers const model = state.model ?? state.defaultModel state.variants = variantsFor(state.providers, model) state.activeVariant = boot ? resolveVariant(ctx.variant, current, saved, state.variants) : current && !state.variants.includes(current) ? undefined : current if (footer.isClosed) return footer.event({ type: "models", providers: info.providers }) footer.event({ type: "variants", variants: state.variants, current: state.activeVariant }) if (model) footer.event({ type: "model", model: formatModelLabel(model, state.activeVariant, state.providers), selection: model, }) } let catalogRefresh: | { attempt: ClientAttempt source: AbortSignal | undefined queued: boolean task: Promise } | undefined let defaultModelLoad: Promise | undefined let defaultModelQueued = false const loadDefaultModel = (attempt: ClientAttempt) => { if (state.model || !currentClient(attempt)) return if (defaultModelLoad) { defaultModelQueued = true return } defaultModelQueued = false defaultModelLoad = attempt.sdk.model .default( { location: { directory: state.location.directory, workspace: state.location.workspaceID, }, }, { signal: attempt.signal }, ) .then(async (result) => { if (!result.data || state.model || !currentClient(attempt)) return const model = { providerID: result.data.providerID, modelID: result.data.id } const changed = state.defaultModel?.providerID !== model.providerID || state.defaultModel.modelID !== model.modelID const saved = changed ? await input.host.preferences.resolveVariant(model) : undefined if (state.model || !currentClient(attempt)) return state.defaultModel = model state.variants = variantsFor(state.providers, model) if (changed) state.activeVariant = resolveVariant(ctx.variant, state.activeVariant, saved, state.variants) if (state.activeVariant) state.model = model footer.event({ type: "variants", variants: state.variants, current: state.activeVariant }) footer.event({ type: "model", model: formatModelLabel(model, state.activeVariant, state.providers), selection: model, }) }) .catch(() => {}) .finally(() => { defaultModelLoad = undefined if (defaultModelQueued) loadDefaultModel(clientAttempt()) }) } const requestCatalogRefresh = (signal?: AbortSignal): Promise => { const attempt = clientAttempt(signal) if (!currentClient(attempt)) return Promise.resolve() const running = catalogRefresh if ( running && !running.attempt.signal.aborted && running.attempt.generation === attempt.generation && running.attempt.sdk === attempt.sdk ) { running.queued = true return running.task } const refresh = { attempt, source: signal, queued: true, task: Promise.resolve(), } const task = (async () => { await Promise.all([abortable(modelTask, attempt.signal), abortable(initialCatalog, attempt.signal)]) while (refresh.queued && currentClient(attempt)) { refresh.queued = false const [catalog, info] = await Promise.all([ abortable(fetchCatalog(attempt), attempt.signal), abortable(resolveModelInfoStrict(attempt.sdk, state.location, attempt.signal), attempt.signal), ]) if (!currentClient(attempt)) return if (catalog) applyCatalog(catalog, attempt) if (info) applyModelInfo(info, state.activeVariant, attempt) loadDefaultModel(attempt) } })() refresh.task = task catalogRefresh = refresh const cleanup = () => { if (catalogRefresh !== refresh) return catalogRefresh = undefined if (refresh.queued && currentClient(attempt)) void requestCatalogRefresh(refresh.source) } void task.then(cleanup, cleanup) return task } const initialCatalog = firstPaint .then(() => (footer.isClosed ? undefined : ensureSession())) .then(() => (footer.isClosed ? undefined : loadCatalog(clientAttempt()))) .catch(() => {}) void initialCatalog if (input.host.startup.showTiming) { void firstPaint.then(() => { if (footer.isClosed) return footer.append({ kind: "system", text: `startup ${Math.max(0, Math.round(input.host.startup.now() - start))}ms`, phase: "final", source: "system", }) }) } const createDemo = async () => { const { createRunDemo } = await import("./demo") return createRunDemo({ footer, sessionID: state.sessionID, thinking: thinking(), }) } if (input.demo) { await firstPaint if (!footer.isClosed) { await ensureSession() state.demo = await createDemo() } } let streamTask = deps.streamTransport const loadStreamTransport = () => { if (streamTask) return streamTask streamTask = import("./stream-v2.transport") return streamTask } const ensureStream = () => { if (state.stream) { return state.stream } // Share eager prewarm and first-turn boot through one in-flight promise, // but clear it if transport creation fails so a later prompt can retry. const next = (async () => { await ensureSession() if (footer.isClosed) { throw new Error("runtime closed") } const mod = await loadStreamTransport() if (footer.isClosed) { throw new Error("runtime closed") } const handle = await mod.createSessionTransport({ sdk: state.sdk, reconnect: input.reconnect, onClient: updateClient, readTextFile: input.host.files.readText, location: state.location, sessionID: state.sessionID, thinking: thinking(), replay: input.replay, replayLimit: input.replayLimit, footer, onCommit: rememberLocal, onSessionTitle: (title) => { state.sessionTitle = title shell.setTitle(title) }, trace: log, onCatalogRefresh: requestCatalogRefresh, contextLimit: (model) => state.providers.find((provider) => provider.id === model.providerID)?.models[model.modelID]?.limit?.context, }) if (footer.isClosed) { await handle.close() throw new Error("runtime closed") } state.selectSubagent = (sessionID) => handle.selectSubagent(sessionID) return { mod, handle } })() state.stream = next void next.catch(() => { if (state.stream === next) { state.stream = undefined } }) return next } let resizeTimer: ReturnType | undefined const offResize = shell.onResize(() => { if (resizeTimer) { clearTimeout(resizeTimer) } resizeTimer = setTimeout(() => { resizeTimer = undefined if (footer.isClosed) { return } shell.refreshTheme() if (!input.replay || !state.stream) { return } void state.stream .then((item) => item.handle.replayOnResize({ localRows: () => state.localRows, reset: () => shell.resetForReplay({ sessionTitle: state.sessionTitle, sessionID: state.sessionID, history: state.history, }), }), ) .catch(() => {}) }, RESIZE_DELAY) }) const renderPromptError = async (prompt: RunPrompt, error: unknown, signal?: AbortSignal) => { if (signal?.aborted || footer.isClosed) return const text = (await state.stream?.then((item) => item.mod).catch(() => undefined))?.formatUnknownError(error) ?? (error instanceof Error ? error.message : String(error)) const commit = { kind: "error", text, phase: "start", source: "system", messageID: prompt.messageID, } as const rememberLocal(commit) footer.append(commit) } const runQueue = async () => { await firstPaint if (footer.isClosed) return await ensureSession() if (footer.isClosed) return await modelTask if (footer.isClosed) return let includeFiles = true if (state.demo) { await state.demo.start() } const mod = await import("./runtime.queue") const createSession = input.createSession await mod.runPromptQueue({ footer, initialInput: input.initialInput, trace: log, onSend: (prompt, delivery) => { state.shown = true state.history.push({ ...prompt, delivery: undefined }) if (prompt.mode !== "shell" && delivery === "steer") { rememberLocal({ kind: "user", text: prompt.text, phase: "start", source: "system", messageID: prompt.messageID, }) } }, admit: async (prompt, delivery, signal) => { await state.switching?.catch(() => {}) const next = await ensureStream() await next.handle.admitPromptTurn( { agent: state.agent, model: state.model, variant: state.activeVariant, prompt, files: input.files, includeFiles: false, signal, }, delivery, ) }, onAdmissionError: renderPromptError, onCompact: async () => { await state.sdk.session.compact({ sessionID: state.sessionID }, formRequestOptions(state.location)) }, settle: async () => { const next = await ensureStream() await next.handle.waitForIdle() }, onNewSession: createSession ? async () => { try { await state.switching?.catch(() => {}) const created = await createSession( state.sdk, { location: state.location, agent: state.agent, model: state.model, variant: state.activeVariant, }, runtimeController.signal, ) if (!created.sessionID) throw new Error("Failed to create session") await footer.idle().catch(() => {}) await state.stream?.then((item) => item.handle.close()).catch(() => {}) state.stream = undefined state.session = undefined state.selectSubagent = undefined state.shown = false state.sessionID = created.sessionID state.sessionTitle = created.sessionTitle shell.setTitle(state.sessionTitle) state.agent = created.agent ?? state.agent state.location = created.location state.model = created.model state.activeVariant = created.variant footer.event({ type: "agent", agent: state.agent }) state.history = [] state.localRows = [] includeFiles = true state.demo = input.demo ? await createDemo() : undefined log?.write("session.new", { sessionID: state.sessionID, }) footer.event({ type: "stream.subagent", state: { tabs: [], details: {}, permissions: [], forms: [], }, }) footer.event({ type: "stream.view", view: { type: "prompt" } }) footer.event({ type: "queued.prompts", prompts: [] }) footer.event({ type: "stream.patch", patch: { phase: "idle", usage: "", first: true, }, }) footer.append({ kind: "system", text: `new session ${state.sessionID}`, phase: "final", source: "system", }) await state.demo?.start() } catch (error) { footer.event({ type: "stream.patch", patch: { phase: "idle", status: "failed to start new session", }, }) const commit = { kind: "error", text: error instanceof Error ? error.message : String(error), phase: "start", source: "system", messageID: SessionMessage.ID.create(), } as const rememberLocal(commit) footer.append(commit) } } : undefined, run: async (prompt, signal, admitted) => { if (state.demo && (await state.demo.prompt(prompt, signal))) { return } await state.switching?.catch(() => {}) try { const next = await ensureStream() await next.handle.runPromptTurn( { agent: state.agent, model: state.model, variant: state.activeVariant, prompt, files: input.files, includeFiles, signal, }, admitted, ) if (prompt.messageID) { state.localRows = state.localRows.filter( (row) => row.commit.kind !== "user" || row.commit.messageID !== prompt.messageID, ) } // Shell and skill turns never send CLI file attachments; keep them // pending for the next prompt-shaped turn. if (prompt.mode !== "shell" && prompt.command?.source !== "skill") includeFiles = false } catch (error) { await renderPromptError(prompt, error, signal) } }, }) } try { if (input.demo) { await firstPaint if (footer.isClosed) return await ensureStream() } else { void firstPaint .then(() => { if (footer.isClosed) { return } return ensureStream() }) .catch(() => {}) } try { await runQueue() } finally { if (resizeTimer) { clearTimeout(resizeTimer) } offResize() await state.stream?.then((item) => item.handle.close()).catch(() => {}) } } finally { runtimeController.abort() offRuntimeClose() await shell.close({ showExit: state.shown && !!state.sessionID, sessionTitle: state.sessionTitle, sessionID: state.sessionID, history: state.history, }) } } // Deferred mode paints before session resolution. The caller may back the // generated client with a transport that is still acquiring a daemon. export async function runInteractiveDeferredMode(input: RunDeferredInput, deps?: RunRuntimeDeps): Promise { const sdk = input.sdk return runInteractiveRuntime( { host: input.host, files: input.files, initialInput: input.initialInput, thinking: input.thinking, replay: input.replay, replayLimit: input.replayLimit, demo: input.demo, tuiConfig: input.tuiConfig, config: input.config, reconnect: input.reconnect, resolveSession: input.target, createSession: input.createSession, boot: async () => { return { sdk, location: { directory: input.directory }, agent: input.agent, model: input.model, variant: input.variant, } }, }, deps, ) }