* fix(ai): update spoofed Gemini CLI User-Agent to v0.35.3 format
Gemini CLI v0.35+ changed its User-Agent format:
- Dropped 'google-api-nodejs-client/10.5.0' suffix
- Added surface field (third param in parens)
- New format: GeminiCLI/VERSION/MODEL (PLATFORM; ARCH; SURFACE)
The stale v0.34.0 UA was contributing to google-gemini-cli/gemini-3.1-pro-preview
being rejected, alongside Google's new abuse detection for third-party CCA usage
(google-gemini/gemini-cli#22970).
Verified: model responds successfully with updated UA.
* feat(scripts): add spoofed version drift checker
Fetches latest stable release from google-gemini/gemini-cli GitHub
releases and compares against the hardcoded version in the provider.
bun check-spoofed-versions # report drift, exit 1 on mismatch
bun check-spoofed-versions --update # apply version bump in-place
Extensible for additional spoofed tools (Antigravity, Copilot headers).
- Replaced truncation with line wrapping for diff output in the edit tool renderer.
- Added `wrapEditRendererLine` to preserve gutter markers and line numbers on continuation lines.
Fixes#578
- Applied `disabledProviders` setting to `getAvailable()` and `getDiscoverableProviders()` so `--list-models` and `/model` hide models from disabled providers.
- Initialized settings before model refresh in the `--list-models` code path.
- Added tests verifying disabled providers are excluded from both methods.
Fixes#588
- Added `thinkingSignature` field to thinking content blocks to preserve the original reasoning field name (e.g., `reasoning_text`, `reasoning_content`) for accurate follow-up requests.
- Enhanced thinking block handling to track and distinguish between different reasoning field types, enabling proper field name preservation across multiple conversation turns.
- Updated `appendThinking` function to accept optional signature parameter and conditionally create new thinking blocks when signature changes.
- Added test case validating that streamed reasoning field names are preserved for follow-up requests.
- Added first-event timeout detection for streaming responses to prevent stuck requests and enable faster recovery from stream stalls.
- Added PI_STREAM_FIRST_EVENT_TIMEOUT_MS environment variable to configure first-event timeout behavior independently from idle timeouts.
- Fixed Anthropic stream timeout errors to be properly retried with first-event recognition, distinguishing first-event from idle timeouts.
- Enhanced idle-iterator utility with FirstEventWatchdog mechanism and markFirstStreamEvent wrapper for unified timeout handling across OpenAI, Azure OpenAI, and Anthropic providers.
- Removed code_search tool and all related Exa API integration from web search module.
- Deleted code-search.ts implementation including CodeSearchResponse types and executeCodeSearch() function.
- Updated getSearchTools() to return only webSearchCustomTool, removing dual-tool support.
- Removed code_search tool documentation from README and prompt files.
* fix(ai): Added developer role support for reasoning-enabled OpenAI models
- Added supportsDeveloperRole function to check developer role support based on provider and base URL.
- Updated system prompt role logic to use developer role only when supported.
- Improved reasoning content handling with provider-specific role enforcement.
* feat(ai): Added developer role support for reasoning models with provider fallback
- Exported supportsDeveloperRole function to enable cross-provider usage.
- Updated Azure provider to check developer role support before assignment.
- Implemented system role fallback when developer role is unsupported.
- Fixed role assignment to use developer role when supported.
* feat(ai): added support for Azure OpenAI inference endpoint
- Allow Azure OpenAI Inference endpoint (azure.com/openai) via `includes()` matching on baseUrl, enabling broader Azure-compatibility.
* test: add test file for developer role handler in OpenAI responses provider
* fix(ai): fixed developer role validation to reject custom base URLs
- Changed supportsDeveloperRole to use base URL pattern matching instead of provider short-circuit.
- Added tests to verify developer role support returns false for custom proxy base URLs.
* fix(ai): Updated GitHub Copilot endpoint detection
- Expands GitHub Copilot endpoint detection to support multiple GitHub Copilot enterprise and copilot-api domains.
- Adds 3 new test scenarios wrapping unsupported/correct patterns for github-copilot provider endpoints.
* chore(ci): Add *.tmp pattern in OS section
- Adds *.tmp to ignore all temporary files with .tmp extension
- Groups with other OS temp files (.DS_Store)
* docs(ai): Consolidate changelog entry for developer role fix
* feat(ai/providers): Added baseUrl resolution support across OpenAI providers
- Resolve baseUrl before buildParams calls in azure-openai-responses.ts for correct system role logic.
- Add baseUrl resolution to openai-responses.ts and openai-completions.ts.
- Add optional resolvedBaseUrl to getCompat function for better compatibility detection.
- Update supportsDeveloperRole to accept both Model objects and baseUrl strings.
listInstalledPlugins() was marking user-scope entries as shadowedBy:
"project" based on plugin ID presence in the project registry alone.
A disabled project copy (enabled: false) is not active in discovery, so
the user copy is the effective install and must not appear shadowed.
activeProjectIds now only includes project plugins where the first entry
does not have enabled: false.
omp plugin ... --scope project was rejected by the strict CLI parser
before reaching any handler, making the feature inaccessible via the
omp plugin entrypoint. Declaring scope in static flags and forwarding
it to PluginCommandArgs.flags lets the CLI framework accept and route it.
When a plugin is installed in both user and project scope, the selector
now shows separate scoped entries (e.g. 'plugin-name@1.0 [project]' and
'plugin-name@1.0 [user]') and forwards the selected scope to
uninstallPlugin(), preventing the disambiguation error introduced when
dual-scope support was added.
Plugins can now be installed at user scope (global) or project scope
(per-project, higher capability priority). Scope is encoded in registry
file location, not a metadata field:
user: ~/.omp/plugins/installed_plugins.json
project: <nearest-project>/.omp/plugins/installed_plugins.json
cache: ~/.omp/plugins/cache/plugins/ (shared, path-referenced)
Project root discovery: resolveActiveProjectRegistryPath(cwd) walks up
from cwd looking for the nearest .omp/ directory, falling back to the
nearest .git root. This is the single resolver used by install, uninstall,
list, upgrade, discovery, and doctor.
Discovery: listClaudePluginRoots(home, cwd?) reads both registries when
cwd is provided. Project entries shadow user entries for the same plugin
ID. Cache key is canonical ("${home}:${resolvedProjectPath}") so nested
cwds within the same project share a cache entry.
Manager changes:
- installPlugin({ scope? }): routes registry reads/writes by scope;
checks collectReferencedPaths() across both registries before deleting
any cached plugin dir to prevent cross-scope data loss
- uninstallPlugin(id, scope?), setPluginEnabled(id, enabled, scope?),
upgradePlugin(id, scope?): throw a disambiguation error when the plugin
exists in both scopes and no scope is specified
- upgradePluginAcrossScopes(id): new; upgrades all scopes the plugin is
installed in; returns InstalledPluginEntry[]
- upgradeAllPlugins(): uses upgradePluginAcrossScopes; result includes scope
- listInstalledPlugins(): returns InstalledPluginSummary[] merged from both
registries; user entries marked shadowedBy: "project" when overridden
CLI: omp plugin install|uninstall|upgrade|enable|disable --scope user|project
Slash: /marketplace install [--scope user|project] name@marketplace
MarketplaceManager constructed with projectInstalledRegistryPath in all
CLI handlers and builtin-registry.ts via resolveActiveProjectRegistryPath.
.gitignore: .omp/plugins/ added (local runtime state, not committed).
preloadPluginRoots/clearClaudePluginRootsCache carry cwd through for LSP.
main.ts passes getProjectDir() at startup.
Tests: 226 pass across 13 files. New: project-scope.test.ts (resolver
walk-up, .git fallback, null return, canonical path, shadow precedence);
manager scope tests (registry isolation, disambiguation errors, cross-scope
cache-ref protection, upgradePluginAcrossScopes, shadowedBy marking).
fixes#581
- Added error handling for marketplace plugin selector failures with user-facing error messages.
- Fixed marketplace clone promotion timing by extracting logic into separate function and applying consistently across addMarketplace and refreshMarketplace methods.
- Added cleanup of temporary clone directories on source verification failures to prevent orphaned files.
- Improved plugin selector UI rendering by expanding onCancel callback to trigger requestRender after completion.
- Fixed LSP config path handling by replacing hardcoded filename array with variable reference.
- Enhanced error handling for unknown plugin source variants in marketplace validation.
- addMarketplace reads registry before fetching; if the fetched catalog
name collides with an existing entry, cleans up the clone and throws
before any cache corruption occurs
- installPlugin rejects relative string sources when the marketplace was
added via URL (only marketplace.json is cached, not the plugin tree)
- /reload-plugins now calls refreshSlashCommandState() so newly
installed/removed plugin commands take effect without restart
- parseMarketplaceCatalog rejects string sources that don't start
with './' at parse time instead of deferring to install-time crash
- classifyInstallTarget skips known npm dist-tags (latest, next, beta,
etc.) and semver-like strings before checking marketplace names
- updateMarketplace throws if fetched catalog name differs from the
registered name, preventing stale data on upstream renames
- parseMarketplaceCatalog validates variant-specific required fields
(github.repo, url.url, git-subdir.url+path, npm.package) at parse
time instead of deferring to install-time crashes
- parseMarketplaceCatalog rejects plugin names that fail isValidNameSegment
instead of deferring the error to install time
- cachePlugin copies to staging dir first, then renames into place;
a failed copy no longer destroys the active install
- Fix ReturnType<> usage in cloneAndReadCatalog
- Carry over enabled:false from existing entries during force reinstall
and upgrade; disabled plugins stay disabled without user intent change
- After clearing plugin roots cache, fire-and-forget preloadPluginRoots()
so sync LSP config reads stay valid for the rest of the session
- Store injected --plugin-dir roots in module-level variable; survive
cache clears by re-merging on next listClaudePluginRoots() call
- Defer old plugin deletion in force-reinstall until new cache succeeds;
transient failures no longer leave the plugin uninstalled
- Classify HTTP URLs by pathname instead of full string; query params
no longer cause .json URLs to be misclassified as git
Check installed_plugins.json directly instead of relying on the
marketplace being registered. Handles the case where a marketplace
entry is removed but its plugins are still installed.
- handleUninstall now classifies targets and routes marketplace
specs to MarketplaceManager.uninstallPlugin()
- handleList shows both npm and marketplace plugins grouped by type
Route marketplace install targets (name@marketplace) to
MarketplaceManager.installPlugin() instead of npm install.
classifyInstallTarget() was implemented but never called from
the install handler.
- Honor enabled:false flag in listClaudePluginRoots (skip disabled plugins)
- Normalize local source URIs to absolute paths in addMarketplace
- Resolver returns explicit tempCloneRoot instead of path-based inference
refreshMCPTools preserves the prior MCP tool selection by only keeping
tools that were already active. Brand-new servers added via /mcp add
had their tools registered in the registry but never activated on the
agent, making them invisible until restart.
After the connection is confirmed in #handleWizardComplete, explicitly
add the new server's tools to the active set via setActiveToolsByName.
Covers both the normal connection path and the fallback
Co-authored-by: Miroslav Drbal <miroslav.drbal@gendigital.com>
The default was set to glm-4.6 on 2026-02-18 during a descriptor
refactor, carried forward from older config. At that point glm-4.7 had
already been GA for two months.
GLM-5.1 (GA 2026-03-27) scores 45.3 on coding benchmarks vs GLM-5's
35.4 (28% improvement). Same endpoint, auth, and API type as all other
zai models. Already in the catalog with full specs.