reloadServers() now reads ctx.settings and session.setMCPPromptCommands, which
the shared /mcp reauth|unauth harness did not provide, so those tests threw
inside reload and failed their showError assertions. Add both members to the
fake context.
Fixes#7189
MCPManager.disconnectAll clears connections without notifying the session's
prompt-command consumer, so removed or disabled prompt servers left stale
/server:prompt commands after /reload-plugins and /mcp reload.
Clear the session MCP prompt-command registry immediately after disconnect and
before asynchronous rediscovery. Newly loaded prompts repopulate it through
the existing manager callback. Extend the reload regression coverage.
Fixes#7189
GMI's launch post documents only $0.14/$0.28 per 1M for DeepSeek-V4-Flash;
the $0.028 cache-read figure was back-derived from OpenRouter's discounted
route and has no direct api.gmi-serving.com source. Keep cacheRead at 0
until GMI confirms cached-token billing, so usage cost reporting is not
overstated.
- Set deepseek-ai/DeepSeek-V4-Flash cost to GMI's direct api.gmi-serving.com
tariff ($0.14/$0.28 per 1M, cache read $0.028) per GMI's DeepSeek V4
launch post, instead of values inherited from other providers.
- Documented that mapWithBundledReference keeps the seed's cost/reasoning/
thinking at runtime: /v1/models discovery only overrides the model ID set
and context/max-token limits, so the seed must carry real GMI values.
- Regenerated models.json.
reloadServers() rediscovered MCP with no options, so loadAllMCPConfigs
defaulted enableProjectConfig to true — /reload-plugins (and /mcp reload)
could start project .mcp.json servers a user opted out of.
Derive discovery filters (enableProjectConfig, filterExa, filterBrowser)
from ctx.settings before reconnecting, matching startup discovery. Added a
regression test asserting the opt-out is forwarded.
Fixes#7189
- Added GMI_CLOUD_STATIC_MODELS seed wired into gen:models so a fresh
install resolves deepseek-ai/DeepSeek-V4-Flash synchronously at boot,
before async /v1/models discovery fires; live discovery stays
authoritative and replaces the seed.
- Regenerated models.json with the seeded gmi-cloud slice.
- Dropped the GeneratedProvider cast now that gmi-cloud is bundled.
- Moved gmi-cloud to the end of the /login provider order.
- Moved changelog entries from released 17.0.3 sections to Unreleased.
- Added a regression test asserting the seed covers the descriptor's
defaultModel.
/reload-plugins documents MCP in its reload scope but the TUI handler only
reset skill/command/capability caches and never reconnected MCP servers or
refreshed the session MCP tool registry, so .mcp.json edits stayed inactive
until process restart.
Route the TUI reload pipeline through a shared reloadTuiPluginState() helper
that also runs the disconnect/rediscover/refreshMCPTools path used by
/mcp reload, exposed as MCPCommandController.reloadServers().
Fixes#7189
Google AI Studio's OpenAI-compatible endpoint
(generativelanguage.googleapis.com/v1beta/openai/chat/completions)
implements a subset of the chat-completions schema and rejects the
`store` field with HTTP 400:
Invalid JSON payload received. Unknown name "store": Cannot find field.
OMP unconditionally emits `store: false` for any openai-completions
model whose resolved compat has supportsStore: true, so every request
to a custom provider pointed at this host (a common wiring for the
`vision` role, e.g. gemini-2.5-flash) failed before the first token.
Add a googleAistudio host class (URL-marker only, like chutes, since
users wire this host under arbitrary provider ids in models.yml) and
include it in the isNonStandard set so supportsStore resolves false.
Same failure shape as openclaw/openclaw#22704.
Detection is URL-based: verified against a captured 400 request dump
from generativelanguage.googleapis.com/v1beta/openai/chat/completions.
Add the gmi-cloud chat-model provider, an OpenAI-compatible inference
gateway at https://api.gmi-serving.com/v1. Wired per the adding-a-provider
contract: a createSimpleOpenAICompletionsOptions helper and catalog entry
in pi-catalog, plus a registry definition with an API-key paste login in
pi-ai. Dynamic model discovery via /v1/models; auth falls back to
GMI_API_KEY. defaultModel is deepseek-ai/DeepSeek-V4-Flash.
The providerId is cast to GeneratedProvider until the next gen:models run
seeds gmi-cloud into models.json (same pattern as xai-oauth/vllm).
- Applied the same same-model K3 replay guard to ConversationState.turns thinking steps so foreign or non-K3 reasoning never leaks into Cursor turn history.
Fixes#7184
refreshStoredOAuthCredential's observed-credential mismatch guard
returned the stored row without a freshness check. When a concurrent
usage-fetch cycle rotated the in-memory selected credential out from
under a request and the stored row was itself expired, the guard handed
back the dead token, which getOAuthApiKey then refused — surfacing as a
misleading "No API key found for <provider>" during plan finalization.
Gate both the pre-lease and post-lease mismatch guards on the stored
credential still being fresh; expired stored copies now fall through to
a normal refresh.
Fixes#7179
ExtensionRunner cached cwd from its constructor argument, which is set
once at session start. /move (SessionManager.moveTo) relocates the
active session's directory, but ExtensionRunner never re-read it, so
every ExtensionContext built afterwards (tool calls, hooks, slash
commands) kept reporting the pre-move directory for the rest of the
session -- observed while building an extension that tracks the
session's git worktree via ctx.cwd.
Turn cwd into a getter over this.sessionManager.getCwd() instead of a
constructor-time snapshot. Session-scoped, not the process-global
project directory: the interactive /move handler happens to also
chdir the process (command-controller.ts -> applyCwdChange ->
setProjectDir), but moveTo() itself never touches that global, so a
programmatic AgentSession.moveSession()/SessionManager.moveTo() call,
a collab guest adopting a host's session cwd without chdir'ing, or an
SDK/ACP session opened via createAgentSession({ cwd }) with a cwd that
differs from the process's own would all still observe a stale
ctx.cwd under a getProjectDir()-based getter. Reading the runner's own
sessionManager -- already held for other purposes -- covers every one
of these instead of just the single-session interactive case.
The constructor parameter is kept (renamed _initialCwd, documented as
ignored) so the two existing call sites don't need touching.
Added a regression test constructing a real ExtensionRunner over an
in-memory SessionManager, relocating it via SessionManager.moveTo(),
and asserting both runner.cwd and createContext().cwd observe the new
directory.