mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 02:34:15 -05:00
main
18
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bb6d66faa3 |
feat(providers): live model lists for OpenRouter and OpenGateway (#2084)
* feat(providers): fetch live model lists for OpenRouter and OpenGateway Enable hybrid discovery so OpenGateway and OpenRouter load public GET /v1/models catalogs (with coding filters on OpenRouter), matching cairn-code and the Zero live-list fix. Refs #2083 * Address live model discovery review feedback. Remove hardcoded model allowlisting, deduplicate live MiMo routes, avoid duplicate startup probes, share mapping helpers, and strengthen provider documentation and tests.\n\nRefs #2083 * test(providers): Isolate OpenGateway picker discovery state. Prevent persisted live discovery cache entries from making the static catalog assertion nondeterministic. Refs #2083 * fix(test): restore OPENGATEWAY_API_KEY after discovery test The no-auth OpenGateway discovery test deletes OPENGATEWAY_API_KEY but originalEnv never snapshotted it and afterEach never restored it, so a worker starting with the credential set would run every later test in that worker without it. Snapshot and restore it like the other provider env vars. Refs #2084 * fix(integrations): preserve route shim maxTokensField for live-only discovered models * fix(test): drop unrelated permissions.test.ts optional-chaining tweak Not part of the OpenGateway/OpenRouter live discovery change; jatmn's review on #2084 flagged it as unrelated drift that should be dropped or split into its own PR. Refs #2084 * docs(integrations): Add JSDoc comments to model mapping helpers Add detailed JSDoc documentation for gateway model normalization, tooling and reasoning support detection, and core model mapping type guards and helpers across OpenGateway, OpenRouter, and modelMapping. Refs #2083 * fix(integrations): address review feedback on live discovery and proxy credentials Preserve caller credentials and custom headers for private route overrides, remove deep-research exclusion for text models, isolate test config directories, and align model picker assertions with upstream curated models. Refs #2083 Refs #2084 * test(integrations): test explicit openaiShim precedence and removeBodyFields merge Add unit test assertions verifying that explicit descriptor and catalog openaiShim configurations take precedence over inferred model settings and that removeBodyFields arrays merge correctly across layers. Refs #2083 Refs #2084 * Filter expired catalog entries at discovery boundary and revert permissions test hunk. Wrap static and merged route catalog model lists in filterAvailableCatalogEntries across all discoverModelsForRoute and refreshStartupDiscoveryForRoute return paths, preventing expired time-boxed catalog entries and live duplicates from resurfacing in model picker refresh, summary, or bootstrap additional options. Also restore permissions.test.ts to upstream/main without optional-chaining. Refs #2084 * Fix ModelCatalogEntry type import in model test suite. Import ModelCatalogEntry from descriptors.js rather than index.js to satisfy typecheck. Refs #2084 --------- Co-authored-by: euxaristia <euxaristia@users.noreply.github.com> |
||
|
|
a5b277971d |
feat(settings): add settings-based subscription override and agy terminal support (#1731)
* feat(settings): add settings-based subscription override and agy terminal support * feat(provider): rebrand Gemini wizard setup steps to Google AI / Gemini * fix(settings): restrict subscription override to trusted sources Addresses jatmn's CodeRabbit review on #1731. P1 — untrusted settings sources could spoof subscriber state: Reads subscriptionType only from policy/flag/user/local settings, excluding project and repository settings which can be checked into shared repos. Adds getTrustedSubscriptionType() helper used by both isClaudeAISubscriber() and getSubscriptionType(). P2 — subscriptionType: "free" did not short-circuit OAuth-detected subscriber state. With a valid Claude AI OAuth token and subscriptionType: "free" in settings, isClaudeAISubscriber() still returned true. Now "free" is authoritative and returns false. P3 — test isolation: added afterEach(mock.restore()) to auth.test.ts so Bun's module mocks cannot leak into subsequent tests. Also splits the antigravity askpass test into focused cases for "agy" and "antigravity" substrings. * fix(settings): restrict subscriptionType override to user settings and return false early for free * fix(auth): clean up unused import and return type matching in tests * fix(provider): align Gemini chooser copy with the wizard's actual auth methods Addresses jatmn's review on #1731. The /provider chooser advertised "Google AI / Gemini Subscription" and "Use your Google AI Premium plan, …", but the Gemini setup wizard only offers three auth methods — API key, access token, and local ADC — with no subscription/Premium sign-in flow. Drop the "Subscription" label and "Google AI Premium plan" framing so the chooser matches the wizard (and the "Google AI / Gemini" wording used elsewhere in the file). No OAuth/subscription sign-in is planned: using Google AI/Gemini subscriptions via third-party tools violates Google's Terms of Service. * fix(pr1731): resolve review follow-ups * fix(pr1731): close review follow-ups * fix(pr1731): close antigravity and gemini follow-ups --------- Co-authored-by: jatmn <the@jat.mn> |
||
|
|
b8c8b2417b |
fix(opencode-go): surface clear error on subscription quota exhaustion (#1749)
* fix(opencode-go): surface clear error on subscription quota exhaustion The opencode.ai/zen/go gateway returns 429 with FreeUsageLimitError or GoUsageLimitError in the body when a user's Go subscription quota runs out. Previously these fell through to the generic "Request rejected (429)" path, causing a mysterious stop with no actionable hint. Detect the opencode-go-specific error markers, surface a clear message with the upgrade URL (free tier) or reset duration + workspace + limit name (paid tier), and skip retry — the quota is terminal until reset. Mirrors the canonical implementation in anomalyco/opencode packages/opencode/src/session/retry.ts. * fix(api): abort retry and avoid thinking hang on quota/allotment exhaustion * fix: implement request URL precedence and add test coverage * fix(api): make 'x-opencode-request-url' header authoritative for OpenCode Go errors * fix(api): preserve OpenCode Go quota message and reuse non-stream converter in JSON fallback Addresses the two remaining P2 review items on #1749. withRetry: the early isQuotaExhausted guard wrapped OpenCode Go FreeUsageLimitError/GoUsageLimitError 429s in the generic "API quota exhausted or not enabled" message, clobbering the actionable subscribe/ reset guidance. Skip the generic guard for OpenCode Go quota errors so they fall through to the standard shouldRetry=false terminal path, which rethrows the original APIError and lets getAssistantMessageFromError surface the specific message. Consolidate detection in a shared isOpenCodeGoQuotaError predicate (errors.ts) and drop the duplicated inline header check in shouldRetry. openaiShim: the application/json fallback in openaiStreamToAnthropic hand-rolled a thin converter that dropped tool_calls, forwarded raw OpenAI finish_reason values as Anthropic stop reasons, skipped array-content normalization, bypassed <think> stripping, and lost raw text tool-call recovery. Extract the established non-streaming conversion into a shared convertNonStreamingResponseToAnthropicMessage and route the fallback through it, re-emitting the result as stream events. _convertNonStreamingResponse now delegates to the same function. Adds regression coverage: a withRetry test proving the OpenCode Go message survives the retry loop, and JSON-fallback tests for tool_calls, stop-reason mapping, <think> stripping, array content, and raw text tool-call recovery. * fix(api): terminate OpenCode Go quota in fast mode; treat empty tool_calls as absent Addresses CodeRabbit's review on #1749. - [Major] withRetry.ts: throw CannotRetryError for isOpenCodeGoQuotaError BEFORE the fast-mode 429 fallback. Previously the guard only *skipped* the generic quota throw, so an OpenCode Go 429 while fast mode was active hit the fast-mode retry/cooldown path instead of surfacing the quota message immediately. Wrapping the original APIError still preserves the OpenCode Go assistant message via getAssistantMessageFromError. Adds a fast-mode regression test (mutation-checked) and a forceFastMode option on the test helper. - [Minor] openaiShim.ts: an empty tool_calls array is truthy, which skipped the raw "Tool calls requested" recovery in convertNonStreamingResponseToAnthropicMessage. Gate on a single hasStructuredToolCalls (length > 0) check across both the string- and array-content raw-recovery paths and the structured loop. Adds a JSON-fallback regression test for tool_calls: [] (mutation-checked). - [Minor] openaiShim.test.ts: collectFallbackEvents now saves and restores globalThis.fetch in a finally block so the stub can't leak past the helper. * test(openai-shim): cover empty tool_calls raw-recovery on array content too Addresses CodeRabbit's follow-up on #1749: the empty-tool_calls regression only exercised the string-content path, but the hasStructuredToolCalls fix also gates the array-content branch. Add a companion JSON-fallback test with array-form message.content and tool_calls: [] so the array branch can't regress silently. Mutation-checked: reverting hasStructuredToolCalls to a truthiness check fails it. * fix(api): preserve OpenCode Go quota errors --------- Co-authored-by: jatmn <the@jat.mn> |
||
|
|
4be017bd4d |
fix: resolve zai-compatible config for all GLM remote models (#1752)
* fix: resolve zai-compatible config for all GLM remote models
* fix: address PR reviews for GLM thinking continuation, custom aliases, Fireworks catalog entries, and reasoning effort preservation
* fix(integrations): gate GLM Z.AI shim to non-catalog routes
The name-based matcher applied the full Z.AI reasoning contract to any
model path containing `glm-<digit>`, overriding catalog-backed non-Z.AI
routes (NEAR AI `zai-org/GLM-5.1-FP8`, Fireworks `glm-5p2`). Now the GLM
branch fires only when there is no catalog entry; Z.AI-contract GLM
routes (opencode-go, atlas-cloud) declare the shim explicitly via
transportOverrides, and a shared ZAI_GLM_OPENAI_SHIM constant is the
single source of truth. Adds negative (NEAR AI) and positive
(opencode-go, atlas) regressions.
* test(integrations): cover the direct Z.AI vendor catalog route in the gating block
Addresses CodeRabbit's nitpick on #1752: the GLM catalog-aware gating describe
block had NEAR AI (negative), opencode-go, and atlas-cloud (positive) cases but
no direct `zai` vendor catalog positive. Add one asserting the full GLM contract
(routeId 'zai', preserveReasoningContent, thinkingRequestFormat 'zai-compatible',
requireReasoningContentOnAssistantMessages) alongside the override-based routes.
* fix(effort): extend supportsZaiReasoningEffort for provider-prefixed GLM-5.2 models
The previous implementation only matched bare 'glm-5.2' and 'zai-org/glm-5.2'. When accessed via an aggregator alias like 'openrouter/zhipu/glm-5.2', the model name doesn't start with 'glm-' or match the zai-org prefix, so supportsZaiReasoningEffort returned false and reasoning_effort was omitted from the request body. Add an endsWith('/glm-5.2') fallback to match any provider-scoped path ending in the base model name.
* fix(gateways): wire Z.AI GLM shim to atlas-cloud GLM entries and opengateway GLM 5.2
- Add enableToolStreaming to ZAI_GLM_OPENAI_SHIM shared constant
- Apply transportOverrides.openaiShim to all 7 atlas-cloud zai-org/glm-*
entries so catalog-backed routes get the full Z.AI shim contract
- Layer Z.AI-specific overrides (thinkingRequestFormat,
preserveReasoningContent, enableToolStreaming) on the opengateway-glm-5.2
entry without conflicting with the gateway-level max_completion_tokens
and removeBodyFields
Refs #1752
* refactor(gateways): derive opengateway GLM shim from shared ZAI_GLM_OPENAI_SHIM
* fix(gateways): align Atlas + OpenCode Zen GLM entries with Z.AI wire format
- Atlas Cloud GLM entries: wireFormat 'reasoning_effort' → 'zai_compatible'
- OpenCode Go catalogEntry: add reasoning + capabilities for zaiGlm models
- OpenCode Go model descriptors: add reasoning: true for GLM entries
* fix(integrations): rebase onto upstream/main, fix Atlas/OpenCode Zen GLM wiring per review
Rebased onto upstream/main (
|
||
|
|
885bd81045 |
fix(hicap): add missing hicap-claude-opus-4.7 catalog entry (#1797)
* fix(hicap): add missing hicap-claude-opus-4.7 catalog entry * test(integrations): cover hicap-claude-opus-4.7 runtime limits and catalog entry * fix(hicap): add claude-opus-4-7 alias and update provider request test * test(hicap): cover opus 4.7 catalog aliases |
||
|
|
78a4b99f68 |
[codex] Enable Windows long paths for worktrees (#1729)
* fix(worktree): enable Windows long paths * fix(worktree): gate core.longpaths behind worktree.enableGitLongPaths setting + add regression tests * fix(worktree): rename enableGitLongPaths to autoConfigureLongPaths, prevent global mock module leakage in tests * fix(worktree): isolate platform and mock dependencies in tests to prevent global mock leak * fix(worktree): map legacy worktree settings, route sparse-checkout errors through formatter, and add strict types |
||
|
|
4a60c0f30f |
fix: guard convertToLogOption against empty transcript (#1723)
* fix: guard convertToLogOption against empty transcript * test: add regression test for convertToLogOption empty transcript guard |
||
|
|
e9a3c308fc |
feat(skills): add PDF generation skill with native TypeScript implementation (#1718)
* feat(skills): add PDF generation skill with native TypeScript implementation
- Adds /pdf bundled skill for creating PDF documents from structured content
- Pure TypeScript PDF generator (~350 lines) with zero external dependencies
- Supports headings, paragraphs, bullet/numbered lists, code blocks, tables, images, HRs, spacers
- PDF spec 1.4 compliant with WinAnsiEncoding for common special characters
- Text wrapping, font sizing, and page layout handled automatically
- Uses embedded files pattern: pdfgen.ts extracted to CLAUDE_SKILL_DIR at runtime
- Model writes a TS script using the library, executes via bun run
- No binary dependencies, no pdfgen Rust tool, no system packages needed
* fix(pdf): address review feedback — escape template interpolations, fix object numbering, remove stub merge/split
* fix(pdf): address remaining review feedback — CLAUDE_SKILL_DIR substitution, image support removal, auto-pagination
- Remove all ${CLAUDE_SKILL_DIR} references from prompt and getPromptForCommand;
bundled skills prepend 'Base directory for this skill: <dir>' instead of
substituting this variable. Import examples now use relative './pdfgen'.
- Remove unimplemented image support:
- Remove { type: 'image' } from PDFElement in both prompt and pdfgen.ts
- Remove ImageData interface and all image XObject handling in PDFWriter
- Remove basename import (only used by image case)
- Remove 'Use relative paths for images' rule from prompt
- Add automatic multi-page continuation for overflowing content:
- Rename buildPageStream → buildPageStreams (returns PageStreamResult[])
- When y < maxY, flush current page and continue on a new page
- Code blocks that don't fit start on a new page
- PDFWriter.build creates a separate page object per content stream
- Replace el.rows.indexOf(row) with index variable for O(n) table rendering
Addresses review feedback from jatmn (round 2): R2-P2 x3
* fix(pdf): address CodeRabbit review — fonts, header/footer, CLI safety
- Create 8 distinct font objects (Helvetica/Bold/Oblique/BoldOblique +
Courier/Bold/Oblique/BoldOblique) instead of one shared Helvetica.
F1..F8 now reference separate objects (3..10) so bold/italic/courier
variants actually render correctly.
- Remove unused header/footer fields from PDFPage interface in both the
prompt and pdfgen.ts. These were silently dropped since buildPageStreams
never received them.
- Fix CLI --spec mode: outFile is now the last non-flag argument excluding
the spec file path, preventing data loss from overwriting the input JSON.
Previously args.find() could pick spec.json as outFile.
* fix(pdf): address R3 review — absolute import path, table cell wrapping
- P2: Replace relative './pdfgen' import with '<skill-base-dir>/pdfgen'
placeholder in prompt example and task instructions, instructing the
model to save and run scripts from the extracted skill directory
- P2: Replace silent .substring(0, 50) truncation with proper text
wrapping via wrapText() for table cells, with dynamic row heights
based on the tallest cell in each row
* fix(pdf): anchor multi-line table cells from row top to prevent downward overflow
- Compute cellStartY from row top (y + rowH - 4) instead of row bottom
so wrapped text flows downward within the cell boundary
* fix(pdf): split table rows that are taller than one page
R5-P2: Jatmn review — rows with very long wrapped cells could overflow
past the page bottom because pagination only checked once per row.
- Pre-compute wrapped lines for all cells in a row up front
- Render row in page-sized chunks, tracking linesRendered offset
- When a chunk fills the page, flushPage() and continue remaining
lines on the next page, drawing per-chunk backgrounds and borders
- Cells with fewer lines than the tallest cell simply have no text
drawn for the excess lines (no blank-line artefacts)
Fixes: Jatmn R5 finding (review 4450066815)
* fix(pdf): wrap overlong tokens and preserve WinAnsi characters
R6-P2 findings from Jatmn review (4451901963):
1. wrapText() now hard-splits tokens exceeding charsPerLine into
chunks, preventing long URLs/IDs/hashes from rendering off-page
or off-cell boundary as invisible text.
2. escapePdf() no longer calls toWinAnsi() again. The caller already
passes WinAnsi-encoded text; the double-pass was dropping mapped
characters (e.g. em-dashes, bullets) because the second pass treated
WinAnsi byte values as unsupported Unicode and silently dropped them.
* fix(pdf): encode table headers through WinAnsi and wrap long code lines
R7-P2 fixes:
- Table headers now pass through toWinAnsi() before escapePdf(), matching
the body text path. Headers containing em dashes, bullets, euro signs, and
other WinAnsi characters now render correctly instead of emitting raw
Unicode codepoints into the content stream.
- Code block lines are now wrapped to the available page width using the
existing wrapText() helper with Courier metrics. Long URLs, hashes,
minified lines, and other overlong tokens no longer extend past the page
boundary.
* fix(pdf): write PDF streams as latin1 bytes
* fix(pdf): address remaining review feedback - empty page, table validation, header overflow
* fix: resolve PDFElement type, support A3 page size, and enable Windows longpaths for worktrees
* fix: address CodeRabbit feedback on PDF skill allowedTools, worktree error propagation, and unit tests
* fix: restrict allowedTools for pdf skill and restore worktree files to main
* fix(skills): normalize base-dir to forward slashes for Windows import safety
Bundled skills receive a prompt prefix "Base directory for this skill:
<baseDir>". On Windows, <baseDir> is a backslash path like
C:\Users\...\pdf, and the skill prompt instructs the model to do
import { createPDF } from '<skill-base-dir>/pdfgen'
Embedding a backslash path into a single-quoted JS string breaks Bun
resolution because backslashes are treated as escape characters.
Normalize baseDir to forward slashes in prependBaseDir() before
building the prefix. Forward-slash paths work cross-platform in
TypeScript/Bun import statements, so the model can safely interpolate
the path verbatim.
Addresses jatmn's review on #1718 (Windows import path).
---------
Co-authored-by: SuperDuperZed <superduperzed@users.noreply.github.com>
|
||
|
|
9c298112dc |
feat(claude): add Opus 4.8 model support (#1769)
* feat(claude): add Opus 4.8 model support Adds Claude Opus 4.8 alongside 4.7 in the model registry, picker, pricing, integrations, and 1M-context support. Mirrors the established 4.7 pattern so longer suffixes resolve first in canonical-name matching. - configs: CLAUDE_OPUS_4_8_CONFIG + opus48 registry entry - model.ts: canonical resolver, default-model dispatch (1P -> 4.8, 3P bumped to 4.7), display & marketing names - modelOptions: getOpus48Option in PAYG 1P/3P, opusplan description - modelCost: COST_TIER_5_25 pricing - context: 1M-capable assertion - prompts: FRONTIER_MODEL_NAME -> Opus 4.8 - integrations: hicap gateway, nearai brand/vendor/model entries - claude brand catalog: new defineModel block Tests: extends modelSupports1M coverage for 4.8. * fix(models): gate Opus 4.8 out of PAYG 3P picker until rollout Opus 4.8 was being added to the third-party (3P) model picker while getDefaultOpusModel() keeps non-first-party usage on Opus 4.7. Remove the 3P option until 3P rollout is active; first-party picker is unaffected. Addresses CodeRabbit review on #1769. * test(integrations): cover NearAI anthropic/claude-opus-4-8 route Adds focused regression coverage for the new Opus 4.8 provider/model path: asserts the NearAI vendor catalog exposes the anthropic/claude-opus-4-8 entry and that it resolves through its modelDescriptorId to a registered NearAI model descriptor (vendor/brand nearai, correct default model + label), plus the NearAI route base URL. Addresses CodeRabbit's [Minor] request to test the exact route. * fix(models): wire Opus 4.8 into adaptive thinking, 3P fallback, and knowledge cutoff Addresses jatmn's review findings on #1769. - [High] thinking.ts: add opus-4-8 to the adaptive-thinking allowlist. Without it, claude-opus-4-8 hit the generic opus exclusion and returned false, dropping first-party Opus 4.8 into budget-based thinking instead of thinking: { type: 'adaptive' }. Adds a regression test (provider mocked to a non-1P value so the allowlist is the only reason 4.8 returns true). - [Medium] validateModel.ts: add an opus-4-8 -> opus47 entry to get3PFallbackSuggestion so an unavailable Opus 4.8 selection suggests 4.7. - [Medium] prompts.ts: getKnowledgeCutoff now returns "January 2026" for claude-opus-4-8 and claude-opus-4-7 instead of falling through to the stale generic "January 2025". - [Low] modelOptions.ts: update the PAYG 1P picker comment to include Opus 4.8. The betas.ts structured-outputs / auto-mode allowlists are intentionally left unchanged for this PR's scope (4.7 is also absent; auto mode is gated on PI safety probes) — to be revisited with safety-research before enabling. * fix(models): update remaining model-launch markers for Opus 4.8 default Addresses jatmn's follow-up review on #1769 — markers missed when Opus 4.8 became the default. - [P2] commitAttribution.ts: add explicit `opus-4-8` and `opus-4-7` branches to sanitizeModelName before the broad `opus-4` fallback, so commit/PR attribution shows the real model instead of `claude-opus-4`. Adds a focused regression test (commitAttribution.modelName.test.ts; mutation-checked). - [P2] attribution.ts: update the unknown-first-party-model co-author fallback from 'Claude Opus 4.6' to 'Claude Opus 4.8', and the matching test expectation. Also fixed a sibling de-dup test that was passing only by coincidence (it hit the 4.6 fallback): point it at a model the public-name map actually recognizes (dot form) so it exercises the real prefix-dedup path. - [P3] fastMode.ts: FAST_MODE_MODEL_DISPLAY 'Opus 4.6' -> 'Opus 4.8'. - [P3] context.test.ts: update the stale modelSupports1M test title/comment from Opus 4.7 to 4.8 (the current first-party default). * test(models): pin the claude-opus-4-7[1m] sanitizeModelName mapping too CodeRabbit follow-up on #1769: the test covered the suffixed 4.8 path but not the 4.7 branch with the same [1m] session suffix. Add the claude-opus-4-7[1m] case so both newly added mappings are pinned. * fix(models): extend fast-mode + default-effort gates to the current default Opus Addresses jatmn's review on #1769 — two predicates still gated to opus-4-6 only while the default Opus is now 4.8. - [P1] fastMode.ts: isFastModeSupportedByModel returned true only for opus-4-6, so for Max/Team Premium users on claude-opus-4-8 fast mode wouldn't actually enable even though FAST_MODE_MODEL_DISPLAY/the /fast command now say "Opus 4.8 only". Extend the predicate to the fast-mode-capable Opus models (4.8/4.7/4.6). - [P2] effort.ts: getDefaultEffortForModel applied the Pro/Max/Team `medium` default only for opus-4-6, so Pro/Max/Team sessions on the new default claude-opus-4-8 fell through to the generic effort path. Extend the branch to 4.8/4.7/4.6 (per the @[MODEL LAUNCH] marker). Adds regression tests for both (mutation-checked: reverting either predicate to opus-4-6 only fails them). * fix(models): wire Opus 4.8 into advisor, teammate fallback, skill vars, comments Addresses jatmn's follow-up model-launch markers on #1769. - [High] advisor.ts: modelSupportsAdvisor / isValidAdvisorModel only whitelisted opus-4-6 / sonnet-4-6, so first-party sessions on the new default claude-opus-4-8 reported the advisor tool unsupported. Add opus-4-8 and opus-4-7 to both (commands/advisor.ts and claude.ts use these centralized predicates, so they're covered). Adds a regression test (mutation-checked). - [Medium] swarm/teammateModel.ts: getHardcodedTeammateModelFallback hardcoded CLAUDE_OPUS_4_6_CONFIG -> CLAUDE_OPUS_4_8_CONFIG, so new teammates spawn on the current default. Adds a first-party test case (mutation-checked). - [Medium] skills/bundled/claudeApiContent.ts: SKILL_MODEL_VARS OPUS_ID/OPUS_NAME 4.6 -> 4.8 (the bundled claude-api skill docs don't hardcode 4.6 elsewhere). - [Low] effort.ts + figures.ts: refresh stale "max is Opus 4.6 only" comments to reflect the 4.8/4.7/4.6 runtime behavior. * test(swarm): assert provider-aware teammate fallback for Bedrock too CodeRabbit follow-up on #1769: add a non-first-party case so the provider-aware fallback is covered. Bedrock resolves to the Opus 4.8 Bedrock model id. * fix(models): give Opus 4.8/4.7 the elevated output-token limits and 3P fallback chain Addresses jatmn's review on #1769. - context.ts: getModelMaxOutputTokens only gave opus-4-6 the 64k/128k branch, so opus-4-7/4-8 fell through to the generic opus-4 branch and capped at 32k — including the new first-party default Opus 4.8. Extend the elevated branch to 4.8/4.7/4.6. Adds a regression test (mutation-checked). - errors.ts: get3PModelFallbackSuggestion had chains for opus-4-6/sonnet but not opus-4-8/4-7, so the error path suggested no fallback for the new default while validateModel.ts already does. Add opus-4-8 -> opus47 and opus-4-7 -> opus46 to mirror validateModel.ts. * fix(models): allow structured outputs on Opus 4.8/4.7 Addresses jatmn's finding #2 on #1769. modelSupportsStructuredOutputs whitelisted opus-4-1/4-5/4-6 but not 4-7/4-8, so first-party/Foundry requests on the new default Opus 4.8 lost the structured-output support that 4.6 had. Add claude-opus-4-7 and claude-opus-4-8 to the allowlist (4.6 supports it, so the newer Opus models do too). Adds a first-party regression test (mutation-checked). Auto-mode (modelSupportsExternalAutoMode) is intentionally left unchanged — it is gated on separate safety review and was not part of this finding. * fix(models): extend file-read mitigation exemption and effort callout to Opus 4.8/4.7 Addresses jatmn's remaining findings on #1769. - [P2] FileReadTool.ts: MITIGATION_EXEMPT_MODELS only held claude-opus-4-6, so the new default claude-opus-4-8 got the cyber-risk reminder appended to every file read that 4.6 did not — a behavioral regression. Add claude-opus-4-8 and claude-opus-4-7 so the recent Opus models inherit 4.6's exemption. - [P3] EffortCallout.tsx: shouldShowEffortCallout gated the medium-effort-default notification to opus-4-6 only; the same default now applies to opus-4-8, so users on the new default never saw it. Extend the gate to 4.8/4.7/4.6. Adds a regression test (mutation-checked). * fix(models): resolve Opus 4.6→4.8 drift in cost tracking, notifications, and picker strings Addresses jatmn's review on #1769 — remaining model-launch drift now that the first-party default is Opus 4.8. - [P1] modelCost.ts: getModelCosts only applied the elevated fast-mode tier to opus-4-6, so fast-mode Opus 4.8 was billed at the normal COST_TIER_5_25 rate while the picker advertised the fast-mode $30/$150 price. Extend the fast-mode cost check to the fast-mode-capable Opus models (4.8/4.7/4.6). Non-fast usage is unchanged (all three already map to COST_TIER_5_25). Adds a regression test (mutation-checked). - [P2] useModelMigrationNotifications.tsx: "Model updated to Opus 4.6" -> 4.8 (the migration lands users on the opus alias = 4.8 for first party). - [P2] commands/model/model.tsx: the 1M-unavailable error said "Opus 4.6"; made it generic ("Opus with 1M context...") since the gate matches any opus[1m]. - [P2] modelOptions.ts: getOpus46_1MOption is now provider-aware (3P → Opus 4.6, first-party → Opus 4.8); getMaxOpus46_1MOption (always first-party) → Opus 4.8. - [P3] migrateLegacyOpusToCurrent.ts: corrected the stale comment (opus alias resolves to 4.8, not 4.6). * docs(notifs): correct Opus default comment to 4.8 for 1P Comment said 4.6 but the migration notification text and the opus alias both resolve to Opus 4.8 for first-party users. Addresses jatmn P3 review note. * fix(integrations): remove duplicate claude-opus-4-8 descriptor A second claude-opus-4-8 entry (vendorId anthropic) with downgraded 200k/8192 specs duplicated the canonical 1M/128k descriptor. The artifact generator rejects duplicate (id, vendorId) pairs, so integrations:generate failed and smoke-and-tests could not pass. Removed the duplicate; the canonical entry and checked-in generated artifacts are unchanged. Addresses jatmn P1. * fix(models): address Opus 4.8 review — extra-usage label, callout test, stale copy - isBilledAsExtraUsage: recognize opus-4-7/4-8 1M variants, not just 4.6, so the "Billed as extra usage" label shows for the new default and 3P default - EffortCallout modelGate test: drop the unreliable `?ts=` cache-busting import and rely on mock.module live bindings, so the gate runs against the mocked deps on Linux CI (where the query-tagged specifier was not re-evaluated) - refresh stale "Opus 4.6+" effort help text and callout comments to reflect the recent Opus models (4.8/4.7/4.6) the gate now covers * test(effort): make Opus 4.8 callout regression deterministic via pure predicate The behavioral test mocked auth/config/effort and relied on the already-evaluated EffortCallout picking up those mocks, which is order-dependent and failed only in the full Linux CI suite (both the `?ts=` dynamic-import and the static-import variants regressed there). Extract the model check as a pure exported `effortCalloutCoversModel` and assert it directly with no module mocking, so the #1769 regression is covered deterministically on every platform. * test(effort): drop config-dependent 'opus' alias from callout regression test The bare 'opus' alias routes through getDefaultOpusModel(), whose result is environment/config-dependent, so `effortCalloutCoversModel('opus')` was false in a clean Linux CI environment even though the gate logic is correct — that single assertion was the only failure in smoke-and-tests (the explicit-id assertions passed). Assert the gate's opus-4-8/4-7/4-6 coverage with explicit canonical model ids (incl. a [1m] variant) instead, which is deterministic on every platform. |
||
|
|
701b68c215 |
fix(query): prevent spurious Windows interruption prompt by passing 'interrupt' reason (#1733)
* fix(query): prevent spurious Windows interruption prompt by passing 'interrupt' reason
* fix(cli): pass 'interrupt' abort reason and add stop-hook regression tests
* test(query): exercise handleStopHooks abort-reason branch directly
Adds focused coverage that imports and runs handleStopHooks() while a
Stop-hook generator is being consumed, asserting abort('interrupt')
suppresses the synthetic '[Request interrupted by user]' message and a
default abort still yields it. Closes jatmn's P2 regression request.
|
||
|
|
669ecdfa8b |
fix: prevent recursive debounce infinite loop in team memory sync (#1726)
* fix: prevent recursive debounce infinite loop in team memory sync * fix: chain cap-reschedule push after in-flight promise instead of running concurrently * fix: preserve currentPushPromise when replaced during yield point + tests * fix: clear pending debounce timer in _resetWatcherStateForTesting * fix(teamMemorySync): prevent concurrent pushes, make clearing identity-safe, and avoid duplicate follow-ups * fix(teamMemorySync): guard capped follow-up push against suppression; fix test gaps Addresses jatmn's three P3 findings on #1726. - watcher.ts: the capped reschedule path queues a serialized follow-up executePush() without consulting pushSuppressedReason, whereas schedulePush() short-circuits on suppression. If a permanent failure set suppression while the in-flight push was running, the queued follow-up fired one redundant, identically-failing call. Skip executePush() when pushSuppressedReason is set, mirroring schedulePush(). Adds a regression test (mutation-checked: removing the guard makes it fail). - watcher.test.ts: the "resets to 0 when executePush completes" test was vacuous — rescheduleCount started at 0 and executePush() never owns that reset (onDebounceFire and _resetWatcherStateForTesting do). Renamed to "clears pushInProgress when executePush completes" and dropped the misleading rescheduleCount assertion; the reset path stays covered by the onDebounceFire cap test. - watcher.test.ts: the top-level mock.module('./index.js') is process-global and mock.restore() does not undo it. Follow the spawnCtxAgent.test.ts pattern — (re)register the mock in beforeEach and restore the real module in afterEach — so it can't silently bleed into a future test that imports teamMemorySync/index. |
||
|
|
02d43b6942 |
fix: surface swallowed error in plan file write (#1725)
* fix: surface swallowed error in plan file write * test: add regression test for plan file write failure path * test(ExitPlanModeV2Tool): scope fs mock to single test and pass full call signature Two CodeRabbit concerns: 1. The global fs/promises mock in beforeAll was leaking into unrelated test suites (e.g. loadAgentsDir.test.ts) because mock.module() cannot be cleanly undone mid-suite. Move the mock into the single test that needs it and call mock.restore() in a finally block so it is torn down as soon as the assertion completes. 2. ExitPlanModeV2Tool.call() was invoked with 2 args but the Tool base class signature requires 4 (input, context, canUseTool, parentMessage). Pass the two extra args so the test typechecks against the real signature. * fix(test): prevent fs/promises mock leak from ExitPlanModeV2Tool test Addresses jatmn's P2 on #1725. The previous test used mock.module('fs/promises', ...) in a try/finally with mock.restore(). On Linux CI the mock was still active when loadAgentsDir.test.ts ran, causing all five agent-fixture tests to fail with "write failed" traces pointing back to this file. Fix: add afterEach(mock.restore()) to guarantee the mock is torn down after each test, regardless of pass/fail path. Verified locally by running both test files in the same process — 6/6 pass. * fix(test): avoid fs/promises mock entirely to prevent CI leak The afterEach(mock.restore()) approach still leaked the fs/promises mock into loadAgentsDir.test.ts on Linux CI (CodeRabbit comment at 12:12 EST). Bun's mock.module on Linux replaces the module cache in a way that persists across test files in the same process. Fix: don't mock fs/promises at all. Instead, re-mock plans.js to point getPlanFilePath at a nonexistent directory, so the REAL fs/promises.writeFile rejects with ENOENT. Nothing to leak. Verified: both test files pass in both orderings with --max-concurrency=1. * Fix race on exiting plan mode by saving plan file before permission updates and add regression tests asserting no side effects on error * fix(test): completely isolate mock module by using teammate context APIs instead of module mock override * test(plan-mode): cover the React write-before-permission guard; drop global fs/promises mock Addresses jatmn's two P2 findings on #1725. - Extract the plan-file write guard from ExitPlanModePermissionRequest's handleResponse into an exported persistPlanFileBeforeExit() helper (jatmn suggested extraction for testability). The component path is unchanged: on write failure it stays in plan mode (returns early) and queues a 'plan-save-error' notification. Adds focused tests for both success and write-failure (mutation-checked: a helper that swallows the failure fails the test). Tests use real filesystem paths — a temp file for success and a path with a missing parent dir for failure — so they need no fs/promises mock. - ExitPlanModeV2Tool.test.ts: replace the process-global mock.module('fs/promises') in both write-failure tests with the same real-failing-path mechanism (a plan path whose parent dir does not exist → genuine ENOENT). This removes the fragile core-module mock jatmn flagged, which mock.restore() cannot undo and which could leak into other test files. * test(plan-mode): assert the specific ENOENT write failure in ExitPlanModeV2Tool tests Addresses CodeRabbit's review on #1725: the two write-failure tests asserted a generic rejects.toThrow(), which would pass on any error. Tighten both to rejects.toThrow(/ENOENT/) so they lock in the intended missing-parent-dir write failure rather than masking an unrelated error. The post-write side-effect assertions (persistFileSnapshotIfRemote / writeToMailbox / setAppState not called) are unchanged. * test(plan-mode): add rendered guard test for handleResponse write failure Addresses jatmn's P3 on #1725: the only coverage for the write-before-permission guard was at the persistPlanFileBeforeExit helper level; there was no test that handleResponse itself returns early on write failure. Renders ExitPlanModePermissionRequest (following the MonitorPermissionRequest harness), points the V2 plan file at a path whose parent dir is missing (real ENOENT), confirms the first accept option, and asserts the plan-save-error notification is queued while toolUseConfirm.onAllow / onReject and onDone are NOT called. Mutation-checked: removing the `if (!saved) return` guard makes it fail, so a future refactor cannot silently drop the guard. * fix(test): type addNotification mock so render test typechecks CodeRabbit (correctly) flagged that `mock(() => {})` infers a zero-arg tuple, so `call[0]` was TS2493 and `bun run typecheck` (CI) failed. Type the mock param with the real `Notification` type, which also lets the assertion drop its cast. |
||
|
|
adcf5e5839 |
fix(permissions): bound the speculativeChecks cache with FIFO eviction (#1724)
Internal/upstream defensive maintenance: cap the speculativeChecks Map at MAX_SPECULATIVE_CHECKS_SIZE (1000) and FIFO-evict the oldest entries after each insert, so the bash-classifier speculative cache can't grow unbounded when the classifier path is active (it is a stub in the open-source build; this guards the upstream build where it is reachable). Pure cache-bounding — no runtime or permission behavior change, and no new env vars. Adds focused FIFO-eviction regression tests via a small `_test` surface (mutation-checked: neutering eviction fails them). Rebased onto current main. |
||
|
|
38b0e27333 |
fix(opencode-go): sync model catalog with opencode.ai/go (#1745)
* fix(opencode-go): sync model catalog with opencode.ai/go The OpenCode Go subscription page (https://opencode.ai/go) lists 13 models, but the catalog had 20. Remove the 7 models no longer offered: glm-5, kimi-k2.5, minimax-m2.5, qwen3.5-plus, mimo-v2-pro, mimo-v2-omni, hy3-preview. Catalog now matches the page exactly: - OpenAI-compatible: GLM 5.2, GLM 5.1, Kimi K2.7 Code, Kimi K2.6, DeepSeek V4 Pro, DeepSeek V4 Flash, MiMo V2.5 Pro, MiMo V2.5 - Anthropic messages: MiniMax M3, MiniMax M2.7, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus Updates gateway catalog, model descriptors, generated artifacts, and tests. * fix(opencode-go): reorder model catalog to match opencode.ai/go listing Reorder both the gateway catalog and model descriptor lists to match the order models appear on https://opencode.ai/go, so the in-app model picker mirrors the subscription page. No model added or removed — purely a reorder. GLM-5.2 → Qwen3.7 Max → Kimi K2.7 Code → MiMo V2.5 Pro → DeepSeek V4 Pro → Qwen3.7 Plus → MiniMax M3 → MiMo V2.5 → DeepSeek V4 Flash → GLM 5.1 → Kimi K2.6 → Qwen3.6 Plus → MiniMax M2.7 * test(opencode-go): assert exact model set matches opencode.ai/go catalog Address CodeRabbit review on #1745 — the count-only test wouldn't catch catalog drift. Add a strict set assertion verifying the 13 expected IDs are present and no removed/unexpected IDs remain. * test(opencode-go): update Anthropic Messages route test for refreshed catalog The direct-env-routing test listed minimax-m2.5 and qwen3.5-plus, which were removed from the opencode-go catalog. Replace with the five /messages-endpoint models that remain: minimax-m3, minimax-m2.7, qwen3.7-max, qwen3.7-plus, qwen3.6-plus. Unblocks the smoke-and-tests CI check on #1745. * fix: update OpenCode Go model references to 13 models and add assertions |
||
|
|
aed42df19b |
fix: treat 5xx HTML overload pages as retryable provider_unavailable (#1750)
Reorders classifyOpenAIHttpFailure so the status >= 500 branch runs before the isMalformedProviderResponse check. Gateway 502/504 overload pages have HTML bodies (matching "<!doctype html" / "<html") that previously tripped the malformed-provider-response path, marking the error non-retryable and surfacing "Provider returned a malformed response" even though the failure was transient — users had to retry manually. Now any 5xx is classified as provider_unavailable (retryable) regardless of body shape, matching the actual semantics. 4xx HTML responses still classify as malformed_provider_response since those are genuine protocol failures. Adds three regression tests covering 502 HTML, 504 HTML, and the unchanged 400 HTML path. |
||
|
|
a02c44143b |
fix(web-search): close SSRF bypasses in custom provider hostname guard (#610)
The previous `isPrivateHostname` used a list of regexes against `URL.hostname`. Several literal-address forms slipped past it: - IPv4-mapped IPv6 `[::ffff:127.0.0.1]` (WHATWG URL normalizes to `[::ffff:7f00:1]`, which no regex matched) — lets callers reach loopback and other private v4 via an IPv6 literal. - ULA `fc00::/7` (e.g. `[fc00::1]`) — not covered. - Link-local `fe80::/10` (e.g. `[fe80::1]`) — not covered. - IPv4 `169.254.0.0/16` (cloud metadata, including 169.254.169.254), `100.64.0.0/10` (CGNAT), and the full `0.0.0.0/8` — not covered. - The IPv6 regex `/^\[::1?\]$/` also required brackets, but `URL.hostname` returns bracketed form anyway, so this part happened to work. WHATWG `new URL(...)` already normalizes short-form / numeric / hex / octal IPv4 to dotted-quad before we see it, so those cases were in fact handled — the remaining gaps were IPv6 and a few missing v4 ranges. Replace the regex list with: - a dotted-quad IPv4 parser + int range check covering 0/8, 10/8, 100.64/10, 127/8, 169.254/16, 172.16/12, 192.168/16; - a small IPv6 parser (handles `::` compression and embedded v4 suffix) + a byte-range check covering `::`, `::1`, IPv4-mapped (recursing into the v4 classifier), IPv4-compatible, `fc00::/7`, `fe80::/10`, and `fec0::/10`. Export `isPrivateHostname` and add unit tests covering every bypass listed above plus public-address negatives. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
7817fe88bd |
fix(web-search): stop leaking abort listeners in custom provider retry (#611)
`fetchWithRetry` created a fresh `AbortController` per attempt and did:
signal?.addEventListener('abort', () => controller.abort(), { once: true })
The listener was never removed. Consequences:
- On retry, a second listener was attached to the caller's signal,
each closing over a different controller.
- After a successful fetch, the listener remained on the caller's
signal indefinitely, referencing a controller whose work was done.
For a long-lived caller signal this is a slow leak.
- The `{ once: true }` only helps if the signal actually fires — on
non-aborted signals the listener stays attached forever.
Replace the manual controller + timer + listener dance with
`AbortSignal.any([signal, AbortSignal.timeout(ms)])`, which the
codebase already uses elsewhere (see src/services/mcp/xaa.ts). This:
- has no user-code listener to leak,
- gives each attempt a fresh independent timeout,
- cleanly distinguishes caller-initiated abort from timeout via
`signal.aborted` vs `timeoutSignal.aborted` before rewriting the
error as "Custom search timed out after Ns".
Also resets `lastStatus` per attempt so a 5xx on attempt 0 can't leak
into attempt 1's retry decision, and collapses the two redundant
retry branches (`lastStatus >= 500` and `lastStatus === undefined`)
into one.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
|
||
|
|
b126e38b1a | fix: display selected model in startup screen instead of hardcoded sonnet 4.6 (#587) |