Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

All notable changes to **Catalyst Code** (formerly Umans Harness), day by day from first commit.

## 2026-08-06

- Wired web model-list refresh (`refresh_models` / `models_refreshed`) with spinner + ModelPicker control; fixed CLI-only install summary for embedded core. [16ec65a]
- Hardened multi-provider wire paths end-to-end (max_tokens floors, empty auth omit, Gemini tool results, SSE/usage coercion, discovery caps). [0a95f73]

## 2026-08-05

- Fixed add-custom-provider: non-blocking model discovery, no Umans fallback on dead endpoints, cancel/error UX, and paste into TUI form fields. [5f852cb]
Expand Down
7 changes: 5 additions & 2 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ The web service requires Node.js 22.13+.
curl -fsSL https://raw.githubusercontent.com/catalystctl/catcode/refs/heads/master/install.sh | bash
```

Installs the `catcode` TUI binary to `/usr/local/bin` (system-wide).
Installs the `catcode` TUI binary to `/usr/local/bin` (system-wide). The
prebuilt TUI embeds the Rust core and extracts it to
`~/.cache/catalyst-code/` on first run — a separate `catcode-core` on PATH is
**not** installed for terminal-only installs.
Comment on lines +31 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 5 'os\.UserCacheDir|cacheDir := filepath.Join' tui/embed_core.go
rg -n -C 3 '~/.cache/catalyst-code' docs/installation.md

if command -v go >/dev/null 2>&1; then
  go doc os.UserCacheDir
fi

Repository: catalystctl/catcode

Length of output: 2424


Document the platform user cache directory.

embeddedCorePath() uses os.UserCacheDir() before appending catalyst-code, so the documented fixed path can be wrong when XDG_CACHE_HOME, macOS ~/Library/Caches, or unavailable user cache paths are in effect. Use “the platform user cache directory” or list platform-specific paths.

Proposed fix
 The
 prebuilt TUI embeds the Rust core and extracts it to
-`~/.cache/catalyst-code/` on first run — a separate `catcode-core` on PATH is
+the platform user cache directory on first run — a separate `catcode-core` on PATH is
 **not** installed for terminal-only installs.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Installs the `catcode` TUI binary to `/usr/local/bin` (system-wide). The
prebuilt TUI embeds the Rust core and extracts it to
`~/.cache/catalyst-code/` on first run — a separate `catcode-core` on PATH is
**not** installed for terminal-only installs.
Installs the `catcode` TUI binary to `/usr/local/bin` (system-wide). The
prebuilt TUI embeds the Rust core and extracts it to
the platform user cache directory on first run — a separate `catcode-core` on PATH is
**not** installed for terminal-only installs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/installation.md` around lines 31 - 34, Update the installation
documentation around the prebuilt TUI’s extracted core location to refer to the
platform user cache directory followed by catalyst-code, rather than hard-coding
~/.cache/catalyst-code. Preserve the existing note that terminal-only installs
do not place catcode-core on PATH.


### Terminal + Web service (Linux & macOS)

Expand Down Expand Up @@ -342,7 +345,7 @@ Removes (from code in `install.sh` `do_uninstall()`):

- Systemd unit (stop → disable → remove file) or launchd plist (unload → delete)
- `/usr/local/bin/catcode`
- `/usr/local/bin/catcode-core`
- `/usr/local/bin/catcode-core` (when present — only installed with `--with-web` / source builds)
- Web bundle directory (`/opt/catalyst-code/web` or `~/Library/Application Support/catalyst-code/web`)
- Installer state file (`/etc/catalyst-code/installer.state`)
- The git repo clone (if built from source) is left untouched
Expand Down
59 changes: 42 additions & 17 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,8 @@ print_banner() {
local mode="download (prebuilt)"
$BUILD_FROM_SOURCE && mode="build-from-source"
print_box "Catalyst Code — installer v${VERSION_DETECTED}" \
"TUI (catcode) + core (catcode-core) -> PATH" \
"optional 24/7 web service (Next.js, prebuilt)" \
"TUI (catcode, core embedded) -> PATH" \
"optional web service (+ catcode-core) via --with-web" \
Comment on lines +406 to +407

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the banner conditional for source builds.

When $BUILD_FROM_SOURCE is true, Lines 1566-1570 report $PREFIX/catcode-core as a separate binary. Lines 406-407 still state that the TUI embeds the core and that catcode-core is added only by --with-web. Source users receive conflicting installation information.

Build these lines from $BUILD_FROM_SOURCE.

Proposed fix
   local mode="download (prebuilt)"
   $BUILD_FROM_SOURCE && mode="build-from-source"
+  local tui_line="TUI (catcode, core embedded) -> PATH"
+  local web_line="optional web service (+ catcode-core) via --with-web"
+  if $BUILD_FROM_SOURCE; then
+    tui_line="TUI (catcode, separate core) -> PATH"
+    web_line="optional web service; catcode-core is installed for source builds"
+  fi
   print_box "Catalyst Code  —  installer v${VERSION_DETECTED}" \
-    "TUI (catcode, core embedded) -> PATH" \
-    "optional web service (+ catcode-core) via --with-web" \
+    "$tui_line" \
+    "$web_line" \
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install.sh` around lines 406 - 407, Make the banner entries near the
TUI/web-service descriptions conditional on BUILD_FROM_SOURCE: for source
builds, report catcode-core as a separate binary consistent with the later
installation summary; otherwise retain the existing embedded-core and --with-web
wording. Update only the banner construction in install.sh.

"scope: system-wide | platform: ${PLATFORM} (${SVC_MGR})"
printf " ${C_DIM}mode: %s | dry-run: %s${C_RST}\n\n" "$mode" "$DRY_RUN"
}
Expand Down Expand Up @@ -1557,6 +1557,17 @@ do_status() {
summary_install() {
local web_line="(not installed — run with --with-web)"
local svc_line=""
local core_line
# Download CLI-only ships an embed_core TUI (extracts core to ~/.cache on
# first run). A separate catcode-core on PATH is only installed with --with-web
# (web service needs CATCODE_CORE) or --build-from-source. Do not probe
# $PREFIX for an existing binary — a leftover from a prior --with-web
# install would make a CLI-only summary lie about what this run installed.
if $WITH_WEB || $BUILD_FROM_SOURCE; then
core_line="core: $PREFIX/catcode-core"
else
core_line="core: embedded in TUI (extracted on first run)"
fi
if $WITH_WEB; then
local svc_id="$UNIT_NAME"
[[ "$PLATFORM" == "Darwin" ]] && svc_id="$LAUNCHD_LABEL (launchd)"
Expand All @@ -1570,16 +1581,22 @@ summary_install() {
fi
local expose_line=""
$WITH_WEB && expose_line="expose: ${EXPOSE_MODE} origin: ${ORIGIN:-<auto>}"
print_box "✓ Installed ${APP_NAME} v${VERSION_DETECTED}" \
"tui: $PREFIX/catcode" \
"core: $PREFIX/catcode-core" \
"web: $web_line" \
"$expose_line" \
"$svc_line" \

"update: catcode --update (or bash install.sh --update)" \
"uninstall: bash install.sh --uninstall" \
# Build the box line list without empty entries or a blank line after `\`
# (a bare newline mid-continuation makes bash try to execute the next
# string as a command — set -e then aborts the installer after success).
local box_lines=(
"tui: $PREFIX/catcode"
"$core_line"
"web: $web_line"
)
[[ -n "$expose_line" ]] && box_lines+=("$expose_line")
[[ -n "$svc_line" ]] && box_lines+=("$svc_line")
box_lines+=(
"update: catcode --update (or bash install.sh --update)"
"uninstall: bash install.sh --uninstall"
"log: ${LOG_FILE:-<disabled>}"
)
print_box "✓ Installed ${APP_NAME} v${VERSION_DETECTED}" "${box_lines[@]}"
log_info "Run the TUI with: catcode"
if $WITH_WEB && ! $SKIP_SERVICE; then
if [[ "$PLATFORM" == "Darwin" ]]; then
Expand All @@ -1601,12 +1618,20 @@ summary_update() {
[[ "${WEB_INSTALLED:-no}" == yes ]] && web_line="http://${HOST}:${PORT} (restarted)"
local expose_line=""
[[ "${WEB_INSTALLED:-no}" == yes ]] && expose_line="expose: ${EXPOSE_MODE} origin: ${ORIGIN:-<auto>}"
print_box "✓ Updated ${APP_NAME} v${VERSION_DETECTED}" \
"tui: $PREFIX/catcode" \
"core: $PREFIX/catcode-core" \
"web: $web_line" \
"$expose_line" \
"source: ${METHOD:-download} @ ${BASE_URL:-${REPO_DIR:-<unknown>}}"
local core_line
if [[ "${WEB_INSTALLED:-no}" == yes ]] || $BUILD_FROM_SOURCE || $WITH_WEB; then
core_line="core: $PREFIX/catcode-core"
else
core_line="core: embedded in TUI (extracted on first run)"
fi
local box_lines=(
"tui: $PREFIX/catcode"
"$core_line"
"web: $web_line"
)
[[ -n "$expose_line" ]] && box_lines+=("$expose_line")
box_lines+=("source: ${METHOD:-download} @ ${BASE_URL:-${REPO_DIR:-<unknown>}}")
print_box "✓ Updated ${APP_NAME} v${VERSION_DETECTED}" "${box_lines[@]}"
Comment on lines +1621 to +1634

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 8 \
  'summary_update|WEB_INSTALLED|BUILD_FROM_SOURCE|WITH_WEB|METHOD|load.*state|state.*load' \
  install.sh

Repository: catalystctl/catcode

Length of output: 26579


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

sed -n '1007,1345p' install.sh
printf '\n--- action dispatch ---\n'
sed -n '1348,1560p' install.sh

Repository: catalystctl/catcode

Length of output: 18035


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Read-only behavioral probe: derive summary_update core_line for representative
# source-install CLI-only state and an update run without explicit --build-from-source.
python3 - <<'PY'
base = {"METHOD": "source", "REPO_DIR": "/tmp/catalyst-code", "VERSION": "1.2.3"}
for cli_with_web, cli_build_from_source in [(False, False), (False, True), (True, False), (True, True)]:
    state = dict(base)
    state["WEB_INSTALLED"] = "yes" if cli_with_web else "no"
    # simulate the update dispatch flags before do_update_source (from do_update)
    if state["WEB_INSTALLED"] == "yes":
        with_web = True
    else:
        with_web = cli_with_web
    build_from_source = state.get("METHOD") == "source" and cli_build_from_source
    core_line = "$PREFIX/catcode-core" if state["WEB_INSTALLED"] == "yes" or build_from_source or with_web else "embedded in TUI (extracted on first run)"
    lines = [f"cli_with_web={cli_with_web}, cli_build_from_source={cli_build_from_source} ->"]
    lines.append(f"  state.METHOD={state['METHOD']}, state.WEB_INSTALLED={state['WEB_INSTALLED']}")
    lines.append(f"  effective with_web={with_web}, build_from_source={build_from_source}")
    lines.append(f"  core_line={core_line}")
    print("\n".join(lines))
PY

Repository: catalystctl/catcode

Length of output: 899


Restore METHOD while loading installer state.

For a source-installed CLI-only install, load_state() reads METHOD=source, but do_update() only forces WITH_WEB=true; $BUILD_FROM_SOURCE stays false. That makes both summary_update() and summary_reinstall() call do_update_source() while the final summary still reports embedded in TUI, even though save_state() rewrites METHOD=source. Load/set the source install flag from the state before dispatching source updates/reinstalls.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install.sh` around lines 1621 - 1634, Restore the source-install state in
load_state() by setting BUILD_FROM_SOURCE when the persisted METHOD is source,
before do_update() or do_update_source() dispatches. Ensure source CLI-only
updates and reinstalls follow the source path and
summary_update()/summary_reinstall() report the external catcode-core location
consistently with save_state().

log_info "Run the TUI with: catcode"
}

Expand Down
2 changes: 2 additions & 0 deletions web/src/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,7 @@ export function ChatInner({ agent, docked }: { agent: AgentApi; docked?: boolean
provider={state.provider}
models={state.models}
selectedModel={state.selectedModel}
modelsRefreshing={state.modelsRefreshing}
thinkingLevel={state.thinkingLevel}
approvalMode={state.approvalMode}
metrics={state.metrics}
Expand All @@ -736,6 +737,7 @@ export function ChatInner({ agent, docked }: { agent: AgentApi; docked?: boolean
theme={theme}
onMenuClick={() => setSidebarOpen(true)}
onSelectModel={agent.setModel}
onRefreshModels={() => void agent.refreshModels()}
onSelectThinking={agent.setThinking}
onSetApproval={agent.setApproval}
onReconnect={agent.reconnect}
Expand Down
6 changes: 6 additions & 0 deletions web/src/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ interface Props {
provider: string;
models: ModelInfo[];
selectedModel: string | null;
/** True while an on-demand model-list refresh is in flight. */
modelsRefreshing?: boolean;
thinkingLevel: string;
approvalMode: string;
metrics: Metrics | null;
Expand All @@ -45,6 +47,8 @@ interface Props {
theme?: string;
onMenuClick?: () => void;
onSelectModel: (id: string) => void;
/** Force-refresh multi-provider model cache. */
onRefreshModels?: () => void;
onSelectThinking: (level: string) => void;
onSetApproval: (mode: "never" | "destructive" | "always") => void;
onReconnect?: () => void;
Expand Down Expand Up @@ -151,6 +155,8 @@ export function Header(props: Props) {
onSelect={props.onSelectModel}
variant="popover"
onClose={() => setModelOpen(false)}
onRefresh={props.onRefreshModels}
refreshing={props.modelsRefreshing}
/>
</div>
)}
Expand Down
2 changes: 2 additions & 0 deletions web/src/components/ide/shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -556,11 +556,13 @@ export function IdeShell() {
ready={agent.state.ready}
models={agent.state.models}
selectedModel={agent.state.selectedModel}
modelsRefreshing={agent.state.modelsRefreshing}
thinkingLevel={agent.state.thinkingLevel}
approvalMode={agent.state.approvalMode}
autoCompact={agent.state.ready?.auto_compact ?? true}
sandbox={agent.state.ready?.sandbox ?? "none"}
onSelectModel={agent.setModel}
onRefreshModels={() => void agent.refreshModels()}
onSelectThinking={agent.setThinking}
onSetApproval={agent.setApproval}
onSetBashTimeout={(secs) => void agent.setConfig("bash_timeout_secs", secs)}
Expand Down
39 changes: 33 additions & 6 deletions web/src/components/model-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import { useMemo, useState, type ReactNode } from "react";
import type { ModelInfo } from "@/lib/types";
import { formatTokens } from "@/lib/format";
import { CheckIcon, ModelIcon, SearchIcon, BrainIcon, XIcon } from "./icons";
import { CheckIcon, ModelIcon, SearchIcon, BrainIcon, XIcon, RefreshIcon } from "./icons";

interface Props {
models: ModelInfo[];
Expand All @@ -23,6 +23,10 @@ interface Props {
variant?: "popover" | "inline";
/** Called after a selection is made (e.g. to close the popover). */
onClose?: () => void;
/** Force-refresh the multi-provider model cache (`refresh_models`). */
onRefresh?: () => void;
/** True while an on-demand refresh is in flight. */
refreshing?: boolean;
}

const PROVIDER_LABELS: Record<string, string> = {
Expand All @@ -49,6 +53,8 @@ export function ModelPicker({
onSelect,
variant = "inline",
onClose,
onRefresh,
refreshing = false,
}: Props) {
const [query, setQuery] = useState("");
const [provider, setProvider] = useState<string | null>(null);
Expand Down Expand Up @@ -180,11 +186,32 @@ export function ModelPicker({
})
)}
</div>
{/* Result count footer */}
{models.length > 0 && (
<div className="border-t border-ink-800/60 px-3 py-1.5 text-[10px] text-ink-600">
{filtered.length} of {models.length} models
{provider && ` · ${prettyProvider(provider)}`}
{/* Result count + optional refresh footer */}
{(models.length > 0 || onRefresh) && (
<div className="flex items-center justify-between gap-2 border-t border-ink-800/60 px-3 py-1.5 text-[10px] text-ink-600">
<span>
{models.length > 0
? `${filtered.length} of ${models.length} models`
: "No models yet"}
{provider && ` · ${prettyProvider(provider)}`}
</span>
{onRefresh && (
<button
type="button"
onClick={onRefresh}
disabled={refreshing}
className="inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-ink-400 transition-colors hover:bg-ink-850 hover:text-ink-200 disabled:cursor-wait disabled:opacity-60"
title="Refresh model list from providers"
aria-label="Refresh model list"
>
<RefreshIcon
width={11}
height={11}
className={refreshing ? "animate-spin" : ""}
/>
{refreshing ? "Refreshing…" : "Refresh"}
</button>
)}
</div>
)}
</div>
Expand Down
6 changes: 6 additions & 0 deletions web/src/components/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,15 @@ interface Props {
ready: ReadyPayload | null;
models: ModelInfo[];
selectedModel: string | null;
/** True while an on-demand model-list refresh is in flight. */
modelsRefreshing?: boolean;
thinkingLevel: string;
approvalMode: string;
autoCompact: boolean;
sandbox: string;
onSelectModel: (id: string) => void;
/** Force-refresh multi-provider model cache. */
onRefreshModels?: () => void;
onSelectThinking: (level: string) => void;
onSetApproval: (mode: "never" | "destructive" | "always") => void;
onSetBashTimeout: (secs: number) => void;
Expand Down Expand Up @@ -784,6 +788,8 @@ export function SettingsModal(props: Props) {
selectedModel={props.selectedModel}
onSelect={props.onSelectModel}
variant="inline"
onRefresh={props.onRefreshModels}
refreshing={props.modelsRefreshing}
/>
</div>
</div>
Expand Down
17 changes: 17 additions & 0 deletions web/src/lib/reducer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,23 @@ describe("models rebinds selectedModel", () => {
});
});

describe("models_refreshed", () => {
test("clears modelsRefreshing and toasts the count", () => {
let s = reduce(initialState, {
type: "_set_models_refreshing",
refreshing: true,
});
expect(s.modelsRefreshing).toBe(true);
s = reduce(s, {
type: "models_refreshed",
count: 3,
providers: { umans: ["a", "b", "c"] },
});
expect(s.modelsRefreshing).toBe(false);
expect(s.toasts.some((t) => t.message.includes("3 models"))).toBe(true);
});
});

describe("history tokens_in", () => {
test("history with tokens_in seeds stats", () => {
const s = reduce(initialState, {
Expand Down
21 changes: 21 additions & 0 deletions web/src/lib/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const initialState: AgentState = {
providerModelsPreview: null,
providerModelsPreviewError: null,
selectedModel: null,
modelsRefreshing: false,
thinkingLevel: "medium",
messages: [],
currentAssistantId: null,
Expand Down Expand Up @@ -658,6 +659,8 @@ export function reduce(state: AgentState, ev: AgentEvent): AgentState {
return { ...state, providerModelsPreview: null, providerModelsPreviewError: null };
case "_set_provider_models_preview_error":
return { ...state, providerModelsPreviewError: ev.error };
case "_set_models_refreshing":
return { ...state, modelsRefreshing: ev.refreshing };
case "_add_notifications": {
// Client-only: append feed items emitted by useAgent's liveSessions diff.
// Dedup per session+kind: refresh (bump ts) an existing UNREAD item for
Expand Down Expand Up @@ -797,6 +800,23 @@ export function reduce(state: AgentState, ev: AgentEvent): AgentState {
selectedModel: stillValid ? state.selectedModel : models[0]?.id ?? null,
};
}
case "models_refreshed": {
// Terminal event for `refresh_models` (core already re-emitted `models`).
// Clear any optimistic spinner and confirm the count — parity with TUI.
const count =
typeof (ev as { count?: unknown }).count === "number"
? (ev as { count: number }).count
: state.models.length;
return {
...state,
modelsRefreshing: false,
toasts: pushToast(
state.toasts,
"info",
`Model list refreshed (${count} model${count === 1 ? "" : "s"})`,
),
};
}
case "provider_presets":
return { ...state, providerPresets: ev.presets ?? [] };
case "provider_models_preview":
Expand Down Expand Up @@ -1439,6 +1459,7 @@ export function reduce(state: AgentState, ev: AgentEvent): AgentState {
goalIterations: [],
subagentRuns: {},
metrics: null,
modelsRefreshing: false,
};
case "session_renamed": {
const sessions = state.sessions.map((s) =>
Expand Down
11 changes: 11 additions & 0 deletions web/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,12 @@ export interface GoalPlan {
export type CoreEvent =
| ReadyPayload
| { type: "models"; models: ModelInfo[] }
/** Terminal event for `refresh_models` (after the `models` re-emit). */
| {
type: "models_refreshed";
count?: number;
providers?: Record<string, string[]>;
}
| { type: "provider_presets"; presets: ProviderPreset[] }
| { type: "provider_models_preview"; models: ModelInfo[]; base_url: string; error?: string }
| { type: "authed"; ok: boolean; provider: string }
Expand Down Expand Up @@ -862,6 +868,7 @@ export type CoreCommand =
| { type: "set_search_key"; provider: string; api_key: string }
| { type: "set_provider"; name: string }
| { type: "list_provider_presets" }
| { type: "refresh_models" }
| { type: "login"; preset: string; api_key?: string }
| {
type: "add_custom_provider";
Expand Down Expand Up @@ -978,6 +985,8 @@ export type SyntheticEvent =
| { type: "_goal_approve_optimistic" }
| { type: "_clear_provider_models_preview" }
| { type: "_set_provider_models_preview_error"; error: string | null }
/** Optimistic: an on-demand `refresh_models` is in flight. */
| { type: "_set_models_refreshing"; refreshing: boolean }
// Cross-session notification feed (client-only; derived in useAgent from
// LiveSessionStatus transitions, never dispatched server-side).
| { type: "_add_notifications"; items: NotificationItem[] }
Expand Down Expand Up @@ -1127,6 +1136,8 @@ export interface AgentState {
* hard failure). Cleared on a successful non-empty preview or on modal close. */
providerModelsPreviewError: string | null;
selectedModel: string | null;
/** True while an on-demand `refresh_models` is in flight (optimistic UI). */
modelsRefreshing: boolean;
thinkingLevel: string;
messages: UIMessage[];
currentAssistantId: string | null;
Expand Down
Loading
Loading