A failed live catalog fetch serves the persisted last-known-good catalog
with a warning, and every TTL-expired automatic load re-attempted live —
so a backend whose discovery keeps failing re-armed the renderer's
「暂时无法更新实时模型列表」 banner forever, even though the persisted
models worked and selecting one never touched the banner state.
Serve the persisted last-known-good catalog without a live attempt on
every non-force load (the LKG-first policy, decided at the single
fetchSdkModelCatalog choke point): automatic loads now return a clean,
warning-less serve, so the next automatic load after the renderer's TTL
clears a stale banner. Warnings are reserved for an explicit force
refresh that fails, and for a first fetch with neither cache nor
persisted entry. The persisted current-id remap moves into a helper
shared by both serve paths.
- Last-known-good serving keeps a decorated current id only when the base row
advertises that level (same rule as the renderer's modelPresetsContainId).
- ANTHROPIC_BASE_URL joins the main-process and renderer catalog cache keys so
a different Claude endpoint never serves another endpoint's persisted list.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
- appendRetainedStoredModelPreset appends the exact suffixed id when a
base-only row exists that does not cover the suffix.
- createSdkRuntimeModelCache carries a degraded refresh's warning onto the
retained cached catalog so the banner and Retry stay visible.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
- an empty catalog now yields no runtime presets even when the user has custom
composer models, so the built-in fallback list stays visible
- add GROK_CONFIG_DIR to the renderer cache env hints (the sync test caught
the drift after the main-process key was added)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
- generate the Copilot headless wrapper in the managed temp directory, create it
exclusively and only reuse an existing file whose content matches; a tampered
file is not handed to the SDK
- use the headless wrapper on Windows only; POSIX passes the resolved CLI straight
to the SDK so forceStop() reaches the real process
- keep the renderer model-cache env hints in sync with the main-process keys
(CODEBUDDY_* and XAI_API_KEY) and guard it with a test
- forward source/fetchedAt from list-models so a last-known-good catalog is not
labelled live
- do not retain a saved Claude alias on top of an empty catalog, which would turn
a one-row list into the authoritative one and hide the built-in presets
- ASCII-only comments in the two new modules
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
- keep an already-qualified {provider, model} default in getOpenCodeDefaultModelIds
instead of prefixing the provider twice
- resolve npm's copilot.ps1 wrapper to the JS entry on Windows like .cmd/.bat
- when serving a last-known-good catalog, remap a persisted current id that has
no row to the first visible row for every backend (null still means default),
so the picker and the send preflight agree
- reconcile the catalog warning banner from the result of every refresh, so a
panel that joined another panel's in-flight load also shows it
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
Implements the unified model catalog UX on top of the last-known-good
persistence layer (#3624):
- R1 source provenance end-to-end: list-models now returns
source: 'live' | 'last-known-good' plus fetchedAt (ISO) on live
successes and last-known-good serve-outs; the pre-flight cache replays
the original provenance on hits. The renderer cache persists
source/fetchedAt alongside the catalog, and the build-time preset
fallback derives 'presets'.
- R2 loading state for all backends: drop the opencode-only gate on
modelCatalogLoading so every SDK backend shows the composer loading
state while its live fetch is in flight (opencode behavior unchanged).
- R3 unified status chip: ComposerModelPicker renders a muted footer chip
for every backend - live / cached with relative last-success time /
built-in default list - replaced by the spinner row while loading.
Keys added to en/es/zh-CN/zh-TW (ru keeps its pre-existing ai.opencode
gap).
- R4 copilot headless wrapper: generated per-resolve wrapper (baked with
the platform exe the npm loader would spawn) is handed to
RuntimeConnection.forStdio, spawning with windowsHide:true so no
console window flashes in Electron. Falls back to the loader when the
exe cannot be resolved (window may flash - documented degradation).
- last-known-good store: a current-only catalog (zero visible models,
valid currentModelId) now refreshes the persisted current id via
refreshLastKnownCatalogCurrentId instead of being skipped, so the
user's selection survives a later live failure without clobbering
the persisted models list
- SDK_MODEL_CACHE_ENV_KEYS: add CODEBUDDY_API_KEY, CODEBUDDY_AUTH_TOKEN
and CODEBUDDY_INTERNET_ENVIRONMENT so two codebuddy profiles sharing a
binary but not credentials no longer share one catalog cache key
- isCopilotAutoOnlyFallback: detect the copilot auto row among the
renderer-appended rows (composer custom ids, retained selections)
instead of requiring exact single-entry shape, keeping saved explicit
selections valid when the picker list mixes extra rows
appendRetainedStoredModelPreset reduced a saved selection to its base id,
so a suffixed saved copilot model (e.g. claude-sonnet-4.5/high) produced
a row that no longer matched the stored id: the picker fell back to auto
while the send preflight passed the saved id. Append the exact stored
spelling when no build-time preset defines the base id.
A stored selection preserved through the claude-alias or copilot
auto-only degraded fallback had no preset row, so the picker could show
no selected row while the send preflight passed the hidden value; append
a preset row (reusing the build-time preset when it defines the id).
Last-known-good responses were cached without their warning, so another
panel or a remount within the renderer TTL presented degraded models as
live and hid the Retry action. The warning now travels with the cached
catalog and re-syncs the banner on cached hits.
The auto-only COPILOT_MODEL_PRESETS fallback is nonempty, so a live
listModels failure invalidated a saved explicit Copilot model selection:
shouldUseStoredAgentModel only preserved unknown stored ids when the
preset list was empty, and picker plus send path silently switched to
auto. A saved id keeps resolving while the presets are exactly the
auto-only fallback.
Preset lists before the 2026-10 claude catalog refresh persisted
sonnet/haiku alias ids. A live catalog of full ids made
shouldAdoptSdkCurrentModel treat such a stored selection as stale and
silently overwrite it, even though the claude CLI still resolves the
alias. Stored aliases now keep resolving (picker and send path) and are
never replaced by the catalog's current model; other backends and
non-alias ids are unchanged.
* feat(ai): add MiMo Code (mimo) agent support scaffolding
Registry/wiring only: discovery entry, managed-agent metadata, model presets, icon, i18n and Settings UI for Xiaomi's MiMo Code CLI (an OpenCode fork). The SDK driver is intentionally not wired yet - it depends on the pending @mimo-ai/sdk parity probe, so DRIVER_REGISTRY keeps a TODO and run-turn/list-models reject the mimo backend until then.
* feat(ai): add MiMo Code (mimo) agent support
Adds Xiaomi's MiMo Code CLI as a managed agent backend, mirroring the existing
OpenCode integration (discovery, managed-agent metadata, model presets, icon,
i18n, Settings UI) and adding the SDK driver that was left as a TODO in the
scaffolding commit.
Driver notes:
- New electron/bridges/aiBridge/sdk/mimoDriver.cjs reuses the pure translation
helpers exported by opencodeDriver.cjs (config/prompt building, event
translation, model mapping) because MiMo Code is an OpenCode fork and speaks
the same wire protocol.
- It does NOT use @mimo-ai/sdk's createOpencode(). On 0.1.15 that helper spawns
a binary named "opencode", waits for an "opencode server listening" banner,
and passes config via OPENCODE_CONFIG_CONTENT; the mimo CLI prints
"mimocode server listening on <url>", reads MIMOCODE_CONFIG_CONTENT, and ships
as "mimo"/"mimo.exe". So createOpencode() can only ever time out. The driver
runs "mimo serve" itself, parses the readiness URL, and attaches the SDK's
createOpencodeClient({ baseUrl }).
- Binary resolution honours binPath, MIMOCODE_BIN and MIMOCODE_BIN_PATH, then
falls back to PATH via resolveCliFromPath (the npm shim is mimo.cmd on
Windows, which Node cannot spawn directly).
- @mimo-ai/sdk ^0.1.15 added as a dependency.
Verified on Windows against mimo 0.1.15 + @mimo-ai/sdk 0.1.15:
- "mimo serve --port" boots and reports its URL; driver parses it.
- SDK symbols used all exist: createOpencodeClient, client.config.providers(),
client.global.event() (SSE stream), client.session.create/abort/promptAsync.
- Live provider catalog reads 2 providers (xiaomi, mimo); MIMO_MODEL_PRESETS
updated to the real lower-case provider-prefixed ids and its TODO removed.
- Server process is killed on close (taskkill tree kill), no orphan processes.
Not yet verified at runtime:
- A full end-to-end turn (no prompt was sent, so no tokens were spent): text /
reasoning / tool-part streaming and abort handling are untested against a
live model.
- MIMOCODE_CONFIG_CONTENT accepting the OpenCode config schema (permission,
mcp, model, share, autoupdate) end-to-end; only that the env var exists in
the binary has been confirmed.
- Whether the loopback server needs MIMOCODE_SERVER_PASSWORD when the API
client attaches.
- The placeholder public/ai/agents/mimo.svg still needs an official asset.
- Multi-turn resume (resumeSessionId) and MCP injection against the live CLI.
* feat(ai): inject MIMOCODE_BIN for discovered MiMo Code agents
Mirror the OpenCode OPENCODE_BIN handling so a discovered or re-detected
mimo binary path reaches the driver, and cover it with the same assertion
shape as the opencode managed-agent env test.
* fix(ai): use MiMo branding in the mimo driver
* test(ai): cover the mimo driver
Add a unit test file for the MiMo Code driver, matching the existing
opencode driver test in shape, runner (node:test via node --test) and
`.test.cjs` extension. It is the last driver in the registry without
one.
Coverage:
- spawn/launch: `mimo serve --hostname/--port` argv, binary resolution
(explicit binPath / MIMOCODE_BIN / MIMOCODE_BIN_PATH), pass-through
env and MIMOCODE_CONFIG_CONTENT; `child_process.spawn` is stubbed, so
no real `mimo` process is ever started.
- readiness: parsing of `mimocode server listening on <url>` from
stdout (including split chunks) and stderr, the OpenCode spelling,
and the MIMO_SERVE_TIMEOUT_MS timeout path (asserts the rejection and
that the child is killed).
- abort/cleanup: stopMimoProcess kill/idempotency/swallow, the Windows
taskkill tree kill, server.close() teardown, and abort mid-turn with
a throwing close().
- branding: OpenCode -> MiMo Code for the busy/idle/status, failed and
tool-failure fallback strings, with tool output and non-brand
technical errors left verbatim.
- happy path: session create -> streamed event translation -> done,
plus listMimoModels catalog mapping and the empty/aborted paths.
* fix(ai): filter MiMo events by session and stop the server behind the npm shim
Address review on #3500:
- The global SSE stream carries every session on the server, and
runMimoTurn translated all of them, so another session's text could leak
into the reply and its session.idle could end the turn early. Skip events
whose session id is not ours, reusing the OpenCode driver's
getOpenCodeSessionIdFromEvent (now exported). Covered by a test with
interleaved sessions.
- On macOS/Linux the official @mimo-ai/cli `mimo` is a Node shim that runs
the native binary through spawnSync, so killing the shim left the server
listening. `mimo serve` is now spawned detached on POSIX and
stopMimoProcess signals its whole process group (SIGTERM, then SIGKILL
after a grace period). Windows keeps taskkill /T.
Verified against the real @mimo-ai/cli@0.1.15 npm shim: on Linux (WSL
Ubuntu 22.04) the listener is the native .mimocode child; before this change
it was still listening 3s after close(), now the port is released within 1s
and no mimo process remains. On Windows the .cmd shim path also releases the
port within 1s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: harden MiMo CLI integration before merge
* fix: wait for MiMo events and handle setup cancellation
* fix: finish MiMo stream handling and scope skill access
* fix: preflight MiMo skill paths before one server startup
* fix: scope MiMo skill reads to trusted roots
* fix: allow MiMo skills from ancestor project roots
* fix: honor MiMo observer and confirm command permissions
* fix: clear MiMo approval when its service exits
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix(#3496): automated AI fix
* fix: address Codex review on PR #3499
* fix: address Codex review on PR #3499
* fix: keep live model catalogs scoped and surface discovery failures
* fix: isolate Claude catalog profiles and support xhigh effort
* fix: handle missing external model catalog state
* fix: isolate Claude model catalogs by credentials
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Avoid rewriting thinking/provider maps when values are unchanged so
mounted AI panels do not re-render. Put permission title and short
description on one line and drop the Cursor CLI parenthetical.
Do not rewrite bare CodeBuddy model ids to /medium, fail closed when
Google has no model id, and send Gemini 2.5 thinkingBudget instead of
Gemini 3 thinkingLevel.
Map Cursor effort variants and CodeBuddy SDK effort onto the shared
sidebar thinking chip, and send Gemini thinkingConfig for Catty
models that look like they support it.
Expose a Settings toggle on the Grok Build card (like Codex App Server)
to choose ACP agent stdio or the original headless streaming-json path.
Persist grokRuntime on ExternalAgentConfig and pass it into the driver
via NETCATTY_GROK_RUNTIME on the agent env.
Expose Grok as a managed Settings agent card, icon, coding-CLI provider,
and runtime model discovery target. Add en/zh/ru strings for in-app Grok
Build (distinct from External MCP client install).
* feat(ai): support Cursor CLI login for subscription quota
Allow the managed Cursor agent to authenticate via local `agent` CLI
login as an alternative to CURSOR_API_KEY, so Catty can use subscription
Auto quota. Keep API key and CLI login mutually exclusive, isolate
resume sessions by auth mode, and expose selectable CLI models with
auto as the default.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ai): harden Cursor CLI login path after review
Address multi-agent review findings: soft-cancel abort without late
stream errors, prefer cursor-agent over ambiguous agent binaries,
mode-aware availability without wiping API keys or sticky-disabling,
and concurrent MCP merge refcount restore. Add unit coverage for the
new contracts.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix(ai): stop OpenCode process spawn storms when unused (#2184)
OpenCode model catalog loads were spawning full servers for discovered
agents, skipping main-process cache, and not aborting on timeout—so
idle Netcatty could accumulate many opencode processes.
Only load catalogs for the selected agent, cache/coalesce list-models,
abort and tear down on timeout, and share a short-lived list-models
server pool per binary.
* fix(ai): address OpenCode list-models review findings
Restore the main-process env immediately after spawning OpenCode so an
idle list-models server cannot leave PATH/OPENCODE_BIN on a temp shim.
Key model caches and the list-models pool by catalog-affecting env so
profiles that share a binary do not receive each other's catalogs.
* fix(ai): tighten OpenCode model-cache invalidation
Include profile-affecting env in the renderer runtime model cache key so
OpenCode config switches do not reuse a stale catalog. Skip caching empty
list-models results in the main process so transient OpenCode failures can
recover before TTL expiry.
Route Netcatty context through body.system, filter echoed user parts,
keep OpenCode env/shim alive for server lifetime, and cap model preset dropdown height.
Co-authored-by: Cursor <cursoragent@cursor.com>