mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 10:14:19 -05:00
v0.21.0
959
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
64955db0fb |
chore(main): release 0.21.0 (#1783)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>v0.21.0 |
||
|
|
c1a9dadea5 |
fix(claude): make stream watchdog deterministic (#1823)
* fix(claude): make stream watchdog deterministic * test(claude): harden watchdog env restore |
||
|
|
a7945e5a70 |
fix(plugins): keep marketplace reconciliation prototype-safe (#1821)
Marketplace names come from user settings (extraKnownMarketplaces), and diffMarketplaces looks each one up with materialized[name]. When the materialized map is a plain object, a name colliding with an Object.prototype member (constructor / toString / valueOf / hasOwnProperty) resolves to the inherited value instead of undefined, so the entry is misclassified as already-materialized (sourceChanged) rather than missing. reconcileMarketplaces produced exactly such a plain object in its error fallback. Two changes: - diffMarketplaces now does an own-property-exact lookup via Object.hasOwn, so the comparison is correct regardless of the map's prototype. - reconcileMarketplaces uses loadKnownMarketplacesConfigSafe, which already degrades a corrupted/unreadable config to a null-prototype empty map (and logs via logForDebugging rather than the error file). The snapshot is only used to diff — the install step re-reads and mutates the real config — so the graceful empty fallback cannot clobber the user's file. Same prototype-pollution class as the marketplace cache fix in #1787. Adds the first tests for diffMarketplaces, which was previously untested. |
||
|
|
1827d84709 |
feat(agents): add per-agent step limits (#1815)
* feat(agents): add per-agent step limits Add maxSteps agent configuration for markdown, JSON, plugin, and SDK agent definitions. Enforce the limit in subagent query execution by blocking over-limit tool calls, preserving a no-tool summary turn, and recording an agent_step_limit terminal reason. Add focused coverage for default behavior, invalid values, multi-turn accumulation, plugin parsing, failure-loop interaction, and summary-tool blocking. * test(agents): isolate agent loader fixtures * test(agents): stabilize agent loader config fixtures * fix(agents): harden step-limit summaries * fix(sdk): harden agent injection follow-up * fix(sdk): report invalid agent step limits |
||
|
|
985984b9ff |
feat(ClinePass): add gateway provider with usage support (#1818)
* feat(integrations): add ClinePass gateway provider with usage support Adds ClinePass (https://cline.bot) as an OpenAI-compatible gateway provider. Gateway - New descriptor at src/integrations/gateways/clinepass.ts with 10 static models. - Uses wireFormat: 'reasoning_effort' and full granular levels (low/medium/high/xhigh) so each model can expose reasoning controls consistent with Atlas Cloud. - Dedicated credentials only: requires CLINE_API_KEY and ignores stale OPENAI_API_KEY. - Generated integration artifacts updated via bun run integrations:generate. /usage support - New service module under src/services/api/clinepassUsage/ for types, fetching, and normalizing the ClinePass usage-limits response. - New UI component src/components/Settings/ClinePassUsage.tsx rendered by Usage.tsx. - Displays 5-hour, weekly, and monthly usage progress bars with longer progress bars. Provider profile fixes - routeMetadata.ts now resolves the active provider from the saved profile even when CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED is not yet set, so /usage works immediately after switching providers with /provider. - providerProfile.ts / providerProfiles.ts learn CLINE_API_KEY so saved profiles apply the ClinePass credential alongside the OpenAI-compatible env vars. Tests & docs - Updated ProviderManager.test.tsx and compatibility.test.ts for the new preset. - Added routeMetadata.test.ts cases for ClinePass and generic profile fallback. - Added clinepassUsage.test.ts for payload normalization and row building. - Updated .env.example and README.md with CLINE_API_KEY instructions. Validation - bun run typecheck - bun run build - bun run test:provider (964 pass) - bun run check (5331 pass; 1 unrelated Windows file-mode failure in branch.test.ts) * fixup: wire CLINE_API_KEY/CLINE_API_MODEL into runtime routing and profile persistence - Add 'clinepass' to the ProviderProfile union so saved profiles can use it. - Add CLINE_API_KEY to PROFILE_ENV_KEYS so profile switching clears stale keys. - routeMetadata.ts: env-only CLINE_API_KEY now selects the clinepass route; other env-only intents treat CLINE_API_KEY as a competing credential. Added isClinePassBaseUrl/getClinePassBaseUrlOverride and activeProfileBaseUrl option to resolveActiveRouteIdFromEnv so custom/unknown profiles targeting api.cline.bot resolve correctly. - providerConfig.ts: resolveProviderRequest now reads CLINE_API_MODEL when CLINE_API_KEY is present and defaults the base URL to https://api.cline.bot/api/v1. - providerProfiles.ts: CLINE_API_KEY is mirrored into startup/profile env for clinepass and custom profiles at api.cline.bot, and is checked in isProcessEnvAlignedWithProfile. - parse.ts: toIsoDate drops invalid resetsAt values instead of echoing them. - Add fetchClinePassUsage tests covering auth, headers, non-OK responses, and errors. - Add routeMetadata and providerConfig regression tests for ClinePass env/model wiring. - Add providerProfiles tests for CLINE_API_KEY propagation in apply/env and startup persistence. - Remove extra blank line in .env.example. Validation: - bun run typecheck - bun run build - bun run test:provider (972 pass) - bun run check (5346 pass; 1 unrelated Windows file-mode failure in branch.test.ts) * fixup: address PR review findings for ClinePass env routing and profile persistence - routeMetadata.ts: Let the concrete OPENAI_BASE_URL match win before falling back to activeProfileProvider / activeProfileBaseUrl. Prevents a saved ClinePass profile from overriding an explicit CLAUDE_CODE_USE_OPENAI env pointing at another gateway. - providerConfig.ts: Gate the ClinePass model branch behind !isGithubMode so a stale CLINE_API_KEY/CLINE_API_MODEL does not override GitHub Copilot model selection. - providerProfiles.ts: Introduce isClinePassProfile() predicate that uses route resolution (routeId === 'clinepass' || baseUrl includes api.cline.bot) and share it across live application, alignment, and startup persistence paths so saved ClinePass profiles keep the dedicated credential consistently even with non-default base URLs. - Add regression tests covering all three findings. Validation: - bun run typecheck - bun run build - bun run test:provider (973 pass) * fixup: use hostname-based ClinePass detection in providerProfiles Replace includes('api.cline.bot') substring matching with isClinePassBaseUrl which validates the exact hostname via URL parsing, preventing spoofed hosts like api.cline.bot.evil.example from triggering CLINE_API_KEY mirroring. Validation: - bun run typecheck - bun run build - bun run test:provider (973 pass) * fixup: gate ClinePass model selection on resolved base URL Move base URL resolution before model selection in resolveProviderRequest so effectiveClinePassMode is only active when no explicit non-ClinePass base URL is set via options.baseUrl, OPENAI_BASE_URL, or OPENAI_API_BASE. Previously CLINE_API_KEY=cp-key + CLINE_API_MODEL=cline-pass/qwen3.7-max + OPENAI_BASE_URL=https://api.openai.com/v1 would return requestedModel=cline-pass/qwen3.7-max with baseUrl=https://api.openai.com/v1, sending a ClinePass model ID to a non-Cline provider. Now the resolver returns the correct OPENAI_MODEL and OpenAI base URL in that scenario, and only uses ClinePass model/default-base when the resolved base URL is absent or actually api.cline.bot. Added regression tests for: - CLINE_API_KEY + CLINE_API_MODEL + explicit OPENAI_BASE_URL - CLINE_API_KEY + CLINE_API_MODEL + explicit baseUrl option - CLINE_API_KEY + CLINE_API_MODEL with no base URL (ClinePass default) Validation: - bun run typecheck - bun run build - bun run test:provider (976 pass) * fixup: default ClinePass model for blank env Treat whitespace-only CLINE_API_MODEL as unset so OPENAI_MODEL can still provide the ClinePass model override. When CLINE_API_KEY selects ClinePass without any model env, fall back to the ClinePass route default instead of the generic codexplan alias. Validation: - timeout 600 bun test src/services/api/providerConfig.test.ts - timeout 600 bun test src/services/api/clinepassUsage.test.ts src/integrations/routeMetadata.test.ts src/services/api/providerConfig.test.ts src/utils/providerProfiles.test.ts src/integrations/compatibility.test.ts src/components/ProviderManager.test.tsx - timeout 600 bun test src/services/api/providerConfig.test.ts src/services/api/client.test.ts src/integrations/routeMetadata.test.ts src/integrations/runtimeMetadata.test.ts src/services/api/clinepassUsage.test.ts src/services/api/minimaxUsage.test.ts src/utils/providerProfiles.test.ts - timeout 600 bun run integrations:check - timeout 600 bun run typecheck - timeout 600 bun run build - timeout 600 bun run security:pr-scan - git diff --check origin/main...HEAD |
||
|
|
d0843bed0b |
fix(compaction): make snip nudges model-aware (#1816)
Scale HISTORY_SNIP context-efficiency nudges with the active model window so large-context sessions do not get prompted after fixed low token growth. Keep existing reset behavior and add focused regression coverage for custom intervals and model-aware attachment gating. |
||
|
|
185ffea893 |
fix(ollama): cap qwen3-coder-next:cloud output at 32768 (#1814)
* fix(ollama): cap qwen3-coder-next:cloud output at 32768 Ollama Cloud rejects qwen3-coder-next:cloud requests with max_tokens above 32768 (400). The shared qwen3-coder-next descriptor stays at its 65536 default; add a gateway catalog override so only the :cloud variant is capped to 32768, with the 262144 context window inherited from the descriptor. A local qwen3-coder-next run keeps the descriptor default. Re-submission of the abandoned #1133, with the deepseek-v4-pro:cloud trailing comma kept and one added after the new entry's notes. * test(discovery): include qwen3-coder-next:cloud in the ollama stale-cache fixture The new gateway catalog entry adds qwen3-coder-next:cloud to the ollama route, so the stale-cache discovery fixture must list it alongside deepseek-v4-pro:cloud and the discovered model. |
||
|
|
5a7969785a |
feat: add /set-context-window and /clear-context-window commands (#1810)
* feat: add /set-context-window and /clear-context-window commands Add session-scoped context window overrides for OpenAI-compatible models that fall back to the default 128k context window. - /set-context-window [model] <tokens>: set override for current model - /clear-context-window [model]: clear override(s) - Overrides are in-memory, per-session, and die with the process - Auto-compact and all context display commands respect the override - Minimum 32k tokens to avoid auto-compact floor paradox * fix: tighten parsing, fix minimum floor, add tests - Reject extra arguments in /set-context-window (require exactly 1 or 2 tokens) - Reject non-integer tokens like '64000foo' with strict regex check - Fix MIN_CONTEXT_WINDOW_OVERRIDE from 32k to 33k to match documented floor - Add 8 tests for session override set/get/clear/normalization/precedence * fix: test isolation, integer validation, env override precedence test - Add clearSessionContextWindowOverride to beforeEach/afterEach for test isolation - Add Number.isInteger check to setSessionContextWindowOverride - Split regression test into env override and unknown model fallback cases - Add test for fractional values (64000.5) * fix: provider-qualified model isolation, session cleanup, model resolution - Store full normalized model name (lowercase only, no prefix stripping) - Lookup tries full name first, then stripped prefix fallback - Provider-qualified names (zai-org/glm-5.2) no longer collide with unqualified (glm-5.2) - Clear overrides in clearSessionCaches() so /clear resets state - Resolve active model via getMainLoopModel() instead of raw context.options.mainLoopModel - Add tests for known model precedence, provider isolation, session isolation * fix: use session-active model, clear both exact and stripped keys - Revert to context.options.mainLoopModel for session-active model (not global getMainLoopModel) - clearSessionContextWindowOverride now deletes both exact and stripped-prefix keys - Add regression test: clearing openai/gpt-4o also clears gpt-4o fallback * fix: store both exact and stripped keys on write for symmetric lookup - setSessionContextWindowOverride now stores both normalized and stripped-prefix keys - Reading openai/gpt-4o via gpt-4o now works on first query (no fallback needed) - Update test: provider-qualified writes now create both keys (symmetric with clear) * test: canonicalize session context overrides and fix mixed-order alias tests * test: verify CLAUDE_CODE_MAX_CONTEXT_TOKENS precedence over session overrides |
||
|
|
1cdca1cc7b |
fix(remote-session): match ingress host by hostname, not raw substring (#1792)
* fix(remote-session): match ingress host by hostname, not raw substring isRemoteSessionLocal and isRemoteSessionStaging decided which Claude AI base URL receives remote-session traffic (production, staging, or http://localhost:4000) by testing whether the ingress URL contained the substring 'localhost' / 'staging' anywhere in the string. A production ingress URL that merely carried those words in its path or query — e.g. https://claude.ai/code/x?ref=staging — was misrouted to the staging or local-dev endpoint. Parse the ingress hostname and match it precisely instead: localhost or 127.0.0.1 for local (mirroring isLocalhostBaseUrl), and a 'staging' hostname label for staging. A malformed URL yields no match. The session-id (_local_ / _staging_) signals are unchanged, and the real local/staging hosts still resolve correctly. Add product.ts tests. * fix(remote-session): allowlist real staging ingress hosts The dot-label match (`hostname.split('.').includes('staging')`) both missed the real default staging ingress host `api-staging.anthropic.com` (no bare `staging` label, so staging remote-control links fell back to production claude.ai) and over-matched unrelated hosts such as `foo.staging.example.com`. Replace it with an explicit allowlist: exact `api-staging.anthropic.com` or a subdomain of the `.staging.ant.dev` zone. Add regression coverage for both directions. |
||
|
|
8859c5d6e2 |
fix(plugins): treat prototype-shadowing marketplace names as not found (#1787)
* fix(plugins): treat prototype-shadowing marketplace names as not found Marketplace names are user-supplied and used directly as object keys for membership and lookup (config[name]) throughout marketplaceManager. The config came back as a normal object, so a name shadowing an Object.prototype member — constructor, __proto__, toString, hasOwnProperty, valueOf — resolved up the prototype chain to a truthy inherited value. 'claude plugin marketplace remove constructor' therefore skipped the "not found" guard and acted on a bogus inherited entry: it crashes on entry.installLocation when a plugin seed dir is configured, or silently no-ops the removal otherwise, instead of reporting that no such marketplace exists. Return a null-prototype object from loadKnownMarketplacesConfig (and its safe variant) so every config[name] lookup across this module is own-property exact. Add a regression test covering removeMarketplaceSource for the shadowing names. * fix(plugins): harden cache-only marketplace lookups against prototype names getMarketplaceCacheOnly and getPluginByIdCacheOnly parsed known_marketplaces.json directly with jsonParse, leaving the result on the normal Object prototype. A marketplace name shadowing a prototype member (constructor, __proto__, ...) resolved to the inherited value, so the not-found guard was skipped and the readers took a bogus path. Route both through toNullProtoConfig so the lookup matches the rest of the module. Add cache-only regression coverage and assert the removal path never rms a directory derived from an inherited entry. * docs(plugins): reunite loadKnownMarketplacesConfig JSDoc with its function The toNullProtoConfig helper and its comment were inserted between the loader's JSDoc block and the loader, orphaning the documentation. Move toNullProtoConfig (and its explanation) above the loader's JSDoc so the doc sits directly on loadKnownMarketplacesConfig again, and cross-link the null-prototype rationale from the loader doc. * test(plugins): assert getMarketplaceCacheOnly never reads a bogus cache path The direct getMarketplaceCacheOnly proto-name test only asserted a null result, which the unhardened path also yields (it reaches readCachedMarketplace on an inherited entry, which then fails and returns null). Add a deterministic regression signal: seed a sentinel installLocation on Object.prototype so a removed null-prototype guard would drive the reader into a cache path derived from the bogus inherited entry, and assert no such path is ever read. Fails if the toNullProtoConfig wrapper is dropped from getMarketplaceCacheOnly alone. |
||
|
|
9bf6aa2308 |
feat(session): add branch command for conversation forks (#1808)
* feat(session): add branch command for conversation forks * test(session): harden branch test cache cleanup * test(session): isolate branch loader checks * test(session): guard branch project cache cleanup |
||
|
|
259c7ec27a |
fix(ollama): preserve chat history with native context (#1805)
* fix(ollama): preserve chat history with native context Route Ollama chat requests through the native /api/chat endpoint so OpenClaude can send request-level options.num_ctx instead of relying on Ollama's OpenAI-compatible shim. Default the Ollama request context to 32768 tokens, support OPENCLAUDE_OLLAMA_NUM_CTX and OLLAMA_CONTEXT_LENGTH overrides, and map max tokens/temperature/top_p into native Ollama options. Adapt native Ollama streaming and non-streaming responses back into the existing OpenAI-shaped conversion pipeline, including usage, text, structured tool calls, and tool_use stop reasons. Normalize native Ollama request messages for images and historical tool calls, avoiding OpenAI-only image_url/id/type payload fields in /api/chat requests. Add Ollama context diagnostics, loopback-only ollama ps status checks, regression coverage, and documentation for verifying active context length. * fix(ollama): address native routing review feedback * fix(ollama): restrict loopback host matching * fix(ollama): exclude wildcard bind address * fix(ollama): keep https localhost proxies on chat completions --------- Co-authored-by: jatmn <12479882+jatmn@users.noreply.github.com> |
||
|
|
b15660e388 |
chore: remove unused atomic chat python helper (#1804)
Remove the unused top-level Python Atomic Chat helper and its dedicated pytest coverage. The active Atomic Chat provider path is implemented in TypeScript through the OpenAI-compatible shim, so keeping this standalone Python module only adds dead code. Leave the remaining Python helper modules, pytest suite, and CI workflow intact. Co-authored-by: jatmn <12479882+jatmn@users.noreply.github.com> |
||
|
|
ff8d47d6c5 |
fix(status-line): estimate usage for unsupported providers (#1803)
Treat all-zero assistant usage from providers that do not report token counts as unsupported usage instead of real zero usage. Estimate current input and output tokens from the transcript, mark the values as estimated in the status-line payload, and use estimated totals whenever the active provider lacks usage data so mixed-provider sessions do not show stale cumulative totals. Preserve tiny nonzero context percentages in the status JSON and render them as ctx <1% in the built-in status line instead of rounding back to 0%. Update the /statusline setup schema and add focused regression coverage for normal reported usage, unsupported all-zero usage, stale-provider fallback avoidance, tiny percentage display, and context percentage rounding. Validation run locally with Bun 1.3.13: bun run build, bun run smoke, bun run typecheck, bun run typecheck:type-tests, focused tests for tokens/context/BuiltinStatusLine, bun run check, bun run test:provider, bun run test:provider-recommendation, python -m pytest -q python/tests, and bun run security:pr-scan -- --base upstream/main. Co-authored-by: jatmn <12479882+jatmn@users.noreply.github.com> |
||
|
|
a47493342f |
feat(report): generate deterministic session task reports (#1802)
* feat(report): generate deterministic session task reports * fix(report): address task report review findings * fix(report): stabilize task report paths on Windows * test(report): expect redacted git metadata cwd * test(report): assert literal redacted git cwd * fix(report): capture PowerShell and backgrounded validations * fix(report): detect quoted validation commands * fix(report): reconcile background validation notifications * fix(report): keep foreground command statuses authoritative * test(report): assert command status precedence |
||
|
|
8023356841 |
feat(session): harden fork-session branching (#1801)
* feat(session): harden fork-session branching Add explicit fork-session branching metadata, preserve fork-owned transcript state, and seed retained content replacement records for forked resumes. Document --fork-session behavior and cover forked resume transcript/materialization behavior with focused tests. * fix(session): respect print persistence for fork seeding |
||
|
|
320d63c812 |
fix(compaction): skip microcompact when compaction is off (#1800)
* fix(compaction): respect disabled microcompact setting Skip automatic query-loop microcompact when the message-count compaction threshold is explicitly set to off, while preserving default, numeric, and explicit compact behavior. * test(query): isolate auto-compact config regression * test(query): type auto-compact deps fixture |
||
|
|
ab8645da9f |
fix: OpenClaude native launcher after Linux install (#1798)
* fix linux openclaude native install launcher Create the package-aware OpenClaude launcher for native installs while keeping the downloaded native payload name unchanged. Repair the native launcher after npm cleanup with a relink-only helper so npm uninstall cannot remove ~/.local/bin/openclaude. Update protocol registration and cleanup fallbacks to use the OpenClaude command name, with regression coverage for install surfaces. * test linux install repair flow |
||
|
|
13cf30afa4 |
fix(moonshot): Add verified Kimi effort metadata (#1796)
* Add verified Kimi effort metadata Add kimi-k2.7-code to the Kimi Code gateway catalog and the direct Moonshot catalog, ordered newest/highest first. Annotate Moonshot and Kimi Code Kimi models with context/output limits, reasoning capabilities, and reasoning_effort metadata limited to the verified low/medium/high levels. Add runtime metadata, effort resolution, and provider override tests covering catalog order, qualified aliases, stale xhigh clamping, and Kimi Code request serialization. Validation run locally: bun run build; bun run smoke; bun run check; bun run test:provider; bun run typecheck; bun run integrations:check; py -3.13 -m pytest -q -p no:cacheprovider python/tests; bun run typecheck:type-tests; bun run test:provider-recommendation; bun run security:pr-scan; focused effort/runtime/client/providerConfig tests; git diff --check. * Fix Kimi K2.7 PR review findings Clamp Kimi K2.7 max output metadata to 32,768 for Kimi Code, direct Moonshot, and Atlas Cloud so runtime limits do not advertise the context window as a generation cap. Update the Moonshot runtime metadata assertion to the corrected cap and keep Hicap's separate K2.7 cap unchanged. Restore globalThis.fetch in the Kimi Code providerOverride test with try/finally to avoid cross-test leakage. Validation: bun test --feature=UNATTENDED_RETRY src/integrations/runtimeMetadata.test.ts src/services/api/client.test.ts src/utils/effort.codex.test.ts; bun run integrations:check; bun run build; bun run test:provider; bun run typecheck; git diff --check; bun run check. * Fix Moonshot K2.6 and K2.5 output caps Raise direct Moonshot Kimi K2.6 and K2.5 max output metadata from the default token count to the provider context-budget ceiling. Add runtime metadata assertions so direct Moonshot K2.6 and K2.5 keep the 262,144 output cap while K2.7 Code remains capped at 32,768. Validation: bun test --feature=UNATTENDED_RETRY src/integrations/runtimeMetadata.test.ts; bun run integrations:check; bun run typecheck; git diff --check; bun run build. * Clamp Atlas Kimi K2.7 effort levels Remove xhigh from the Atlas Cloud moonshotai/kimi-k2.7-code metadata so the gateway no longer advertises an unverified effort level for that model. Update providerOverride and effort metadata tests to cover the exact Atlas K2.7 path and assert stale xhigh settings clamp to high. Validation: bun test --feature=UNATTENDED_RETRY src/services/api/client.test.ts src/utils/effort.codex.test.ts src/integrations/runtimeMetadata.test.ts; git diff --check; bun run integrations:check; bun run typecheck; bun run build. * Address Kimi metadata review findings |
||
|
|
6f794f4185 |
fix(xAI): Update xAI model metadata and effort handling (#1795)
* Update xAI model metadata and effort handling Refresh xAI direct provider catalog with live Grok 4.3, Grok Build, and Grok 4.20 model metadata, aliases, context windows, and output caps. Align Atlas Cloud xAI entries with the refreshed model aliases and verified effort semantics. Treat Grok Build as a coding model without reasoning effort support while keeping its xAI Responses endpoint override. Update xAI defaults, display names, alias descriptor lookup, and regression coverage for runtime metadata, effort selection, context limits, model options, and vision capability lookup. Validated with bun run integrations:check, focused provider/model tests, bun run typecheck, bun run build, and git diff --check. * Address xAI PR review findings Add the Grok 4.20 max output cap to both xAI descriptor paths and assert the runtime metadata. Keep route-less vision catalog lookup from resolving aliases globally while preserving route-specific alias resolution. |
||
|
|
eea0a1a740 |
feat(cli): add headless heartbeat for print mode (#1789)
* feat(cli): add headless heartbeat for print mode * fix(cli): harden heartbeat validation and predicates * fix(cli): align print heartbeat phases * fix(cli): keep heartbeat payloads schema-valid * fix(cli): delay stream-json heartbeat until drain * test(sdk): cover heartbeat placeholder identifiers * fix(cli): clamp heartbeat durations * fix(cli): ignore file persistence final events * test(cli): cover post-turn final filtering * fix(cli): harden headless heartbeat follow-up Export the heartbeat SDK message type from generated core types. Keep heartbeat cleanup paired with setup and streaming failures, and cover timing/count edge cases with focused regression tests. * test(sdk): exercise generated heartbeat types Expose the SDK type generator as a pure helper so tests compare fresh output with the checked-in generated artifact. * fix(scripts): canonicalize sdk type generator entrypoint Compare real paths for direct script execution so symlinked invocations still run the generator. * test(sdk): harden generator import coverage Normalize generated type freshness checks across line endings and keep the SDK type generator import-safe for non-file entrypoints. * test(sdk): assert generator import has no write side effects Snapshot the generated SDK type artifact around the non-file import regression so importing the generator cannot silently rewrite the committed output. |
||
|
|
0b4000042e |
fix(bash): correct off-by-one in truncated-line count (#1786)
* fix(bash): correct off-by-one in truncated-line count formatOutput reported one more truncated line than were actually omitted. The line straddling the cut has its head shown in the kept output, but counting newlines from maxOutputLength and adding one counted that partially-shown line as truncated. Drop the +1 so the "[N lines truncated]" marker counts only fully-omitted lines, and add a regression test for the previously untested truncation branch. * fix(bash): count the line omitted at a truncation boundary When the cut lands exactly on a line boundary (the char before maxOutputLength is a newline), the line before it is shown in full and the next line is fully omitted but has no preceding newline in the omitted suffix, so the newline count missed it. Add that line back when cutAtLineBoundary, leaving the mid-line (straddle) case unchanged. Adds a boundary regression test alongside the straddle one. |
||
|
|
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. |
||
|
|
4704cbc474 |
fix(core): join multi-block message text with a real newline (#1793)
extractTextFromContent joined text blocks with a literal "\\n" (backslash-n) instead of a newline. Assistant messages commonly arrive as multi-block content arrays, and the joined text feeds conversation-arc fact extraction whose regexes deliberately treat newlines as boundaries (e.g. env-var and URL values use [^\\s\\n"']+). With the literal separator a value at the end of one block absorbed the next block — e.g. API_KEY=secret123 across two blocks recorded the knowledge-graph value as "secret123\\nand..." instead of "secret123". Use a real newline so blocks stay separated. Adds a regression test asserting the extracted env-var value stops at the block boundary. |
||
|
|
2083d1cdff |
fix(openai-shim): recover GLM/Qwen XML tool calls emitted as text (#1791)
* fix(openai-shim): recover GLM/Qwen XML tool calls emitted as text GLM/Qwen-family models routed through OpenAI-compatible gateways emit tool calls as XML text (`<tool_call><function=…>`) instead of structured `tool_calls`. The shim had no parser, so these leaked into visible prose and never executed — the turn ended with no tool_use block and the agent appeared to "forget" and stop mid-task. Add `parseXmlToolCalls` covering the three dialects seen in the wild (function/parameter, GLM-native arg_key/arg_value, Hermes JSON), wired into both the streaming and non-streaming paths. The streaming path holds back text from `<tool_call>` onward (incl. an opener split across SSE deltas), converts it to tool_use blocks at finalize, and flips the finish reason to tool_calls — mirroring the existing Ollama fallback. Structured tool_calls and false-positive prose are handled losslessly. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * Update src/services/api/openaiShim.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> |
||
|
|
618e901dd8 |
fix(hicap): improve model catalog and effort routing (#1790)
* Improve Hicap model catalog and effort routing Update the Hicap gateway default to claude-opus-4.8 and add verified static catalog entries for DeepSeek V4 Pro, GLM 5.2, GPT-5.4, GPT-5.5, Grok 4.3, Kimi K2.7 Code, and MiniMax M3 with context, output, reasoning, and transport metadata. Force Hicap GPT-5.4/GPT-5.5 through the Responses transport when chat completions would reject reasoning_effort, and shape GLM 5.2 requests with the Z.AI-compatible thinking/tool-streaming behavior. Canonicalize discovered route aliases against static catalog entries so GLM and other aliased models do not duplicate or restart with the wrong API model, while preserving the session-level /model contract without mutating provider profile model lists. Merge static route catalog options with scoped/profile OpenAI-compatible discovery caches and ignore stale legacy OpenAI model caches for env-only Hicap routes. Add focused Atlas Cloud and OpenRouter regressions so these catalog changes do not break other gateways. Validation run locally: bun test --timeout 10000 --feature=UNATTENDED_RETRY src/utils/providerProfiles.test.ts src/utils/model/modelOptions.gateways.test.ts src/utils/model/modelOptions.hicap.test.ts src/utils/model/modelOptions.xiaomi-mimo.test.ts src/utils/model/modelOptions.github.test.ts; bun test --timeout 15000 --feature=UNATTENDED_RETRY src/commands/model/model.test.tsx src/commands/provider/provider.test.tsx src/services/api/client.test.ts; bun test --timeout 10000 --feature=UNATTENDED_RETRY src/services/api/providerConfig.test.ts src/services/api/providerConfig.local.test.ts src/integrations/runtimeMetadata.test.ts src/integrations/compatibility.test.ts src/integrations/routeMetadata.test.ts src/utils/providerFlag.test.ts src/utils/providerValidation.test.ts src/utils/effort.codex.test.ts; bun run typecheck; git diff --check. * Address Hicap PR review findings Rename the discovered model helper to reflect production use, seed ModelPicker focus from the canonical option value, normalize focused/selected picker aliases through the option list, and simplify requested API format support logic. Validation: bun test src/components/ModelPicker.test.tsx src/services/api/bootstrap.test.ts src/services/api/providerConfig.test.ts src/services/api/providerConfig.local.test.ts; bun run typecheck * Fix Hicap catalog id responses routing Resolve route catalog aliases before OpenAI shim runtime/support checks so Hicap catalog ids such as hicap-gpt-5.5 are probed as gpt-5.5 and keep their required Responses API transport. Validation: bun test src/services/api/providerConfig.local.test.ts src/services/api/providerConfig.test.ts src/services/api/openaiShim.test.ts; bun run typecheck * Add bootstrap regression for Hicap errored discovery Exercise fetchLocalOpenAIModelOptions with an errored Hicap discovery result so canonical catalog option mapping and duplicate removal are covered at the bootstrap payload level. Validation: bun test src/services/api/bootstrap.test.ts; bun run typecheck |
||
|
|
a5a8ccc378 |
fix: auto-continuation overly biased toward Claude-style output — breaks with non-Claude models (#1713)
* fix: auto-continuation overly biased toward Claude-style output When using non-Claude models (OpenAI, Gemini, local models, etc.), the continuation detection frequently fails, causing the agent to stop after each turn and wait for manual user input. Changes: - Expand CONTINUATION_SIGNALS verb list to include common verbs used by non-Claude models: process, download, upload, compile, train, evaluate, test, continue, generate, extract, merge, deploy, install, configure, refactor, optimize (plus their -ing forms) - Fix COMPLETION_MARKERS check to only block continuation when no continuation signal is present nearby (prevents false positives when words like 'complete' or 'done' appear mid-sentence) - Add soft fallback nudging when text has no terminal punctuation and no explicit completion signal (assumes model intends to continue by default) - Increase MAX_CONTINUATION_NUDGES from 3 to 20 for longer multi-step tasks - Pass 'interrupt' reason to abortController.abort() in the TUI control interrupt handler for consistent signal propagation Fixes #1707 * fix: add 429 retry, disable microcompact, disable command-queue abort - Add 429 rate limit retry with backoff for non-Claude providers (src/services/api/withRetry.ts) - Disable microcompact to prevent aggressive conversation trimming (src/query.ts) - Disable command-queue abort to prevent interrupting running tools (src/cli/print.ts) * fix: address PR review feedback - 429 retry: use separate counter instead of manipulating attempt - Soft fallback: remove aggressive fallback nudging all unpunctuated text - Microcompact: honor maxMessagesCompactionThreshold 'off' setting - Fix typecheck issues (pendingCacheEdits, imports) * fix: address second round of PR review - Remove 429 retry policy (out of scope for continuation fix) - Add presentProgressive check for 'complete. Now processing...' case - Restore command queue preemption (split to separate PR) - Fix PendingCacheEdits type for typecheck * fix: address third round of PR review - Fix completion guard condition (inverted logic — was blocking with continuation signals present) - Narrow presentProgressive to only 'Now [verb]ing' (avoid gerund false positives) - Restore original microcompact call (remove config gate) - Add 'interrupt' reason to permission deny abort calls * fix: address fourth round of PR review - [P1] Add 'continuing with' and 'proceeding to' to strongIntent regex so punctuated transition phrases survive terminal-punctuation gate - [P2] Pass 'interrupt' reason to abortController.abort() in PermissionContext.runHooks() for interactive permission hooks * fix: address fifth round of PR review - [P1] Add missing -ing gerund forms to line 15 (creating, writing, editing, running, checking, building, etc.) so the late gerund transition fix covers the original action verbs too - [P2] Add imperative/declarative signals for bare patterns: 'need to <verb>' ('Need to process files'), 'now <verb>' ('Now process files', with negative lookahead to avoid 'Now you' false positives), 'next (i|we) <verb>' ('Next I process files') * fix: address sixth round of PR review Addresses all 5 findings from jatmn's CHANGES_REQUESTED review: 1. (code-quality) Extract verb list to shared ACTION_VERBS array; build all continuation regexes from it via buildContinuationSignals() 2. (minor) Restrict presentProgressive to gerund forms of the same verb list instead of broad \w+ing 3. (nit) Remove accidental 'nul' entry from .gitignore 4. (medium) Add focused tests for new verbs, imperative patterns, present-progressive fallback, completion-marker guard, and verb-list deduplication 5. (minor) Add tests verifying MAX_CONTINUATION_NUDGES = 20 and the guard comparison * fix: address seventh round of PR review Addresses all 4 findings from jatmn's CHANGES_REQUESTED review: 1. [P1] Remove dead 'take' branch from VERB_ING gerund map 2. [P2] Pass 'interrupt' reason to abort() in print.ts SIGINT handler and PermissionContext.ts cancelAndAbort path 3. [P3] Fix tab indentation in query.ts, PermissionPromptToolResultSchema.ts, and permissions.ts 4. [P3] Update PR description to match actual code changes * fix: address PR review feedback (indentation, filter-based verb exclusion) - Fix indentation (2-space style) in PermissionPromptToolResultSchema.ts else-if block (jatmn review finding 1) - Use ACTION_VERBS.filter() instead of fragile v.replace(/^do\|/, '') for 'time to' regex (finding 2) - Clarify default fallback behavior in PR description (finding 4) - Note: queryLifecycle cleanup (finding 3) not applicable — field does not exist in this codebase * fix: punctuated imperative/declarative patterns now signal continuation intent The new imperative patterns (need to <verb>, now <verb>, next i/we <verb>) matched in the late-window signal check but were silently dropped when the text had terminal punctuation, because the punctuated branch only checked strongIntent / presentProgressive / endsWithColon. This left examples like 'Need to process files.' and 'Now create the component.' returning shouldNudge: false, even though the bare variants correctly returned true. Fix: add hasImperativeSignal to the punctuated gate, re-testing lowerText against the imperative patterns so punctuated action-intent signals are recognized. Also narrows the #1707 closure claim in the PR description: the default fallback remains shouldNudge: false (the broader inversion suggested in the issue introduced false positives in earlier rounds). Tests added for punctuated imperative variants. * fix: address ninth round of PR review - [Medium] Restore abortController.abort('interrupt') in onInterrupt() bridge/SDK callback to keep interrupt-reason fix consistent - [Minor] Normalize indent (tabs -> 2-space) in imperative/declarative test block in bugfixes.test.ts - [Nit] Use lateText (last 120 chars) for hasImperativeSignal check for consistency with surrounding late-window logic - queryLifecycle removal is intentional cleanup from earlier round; no longer referenced in callModel options * fix: tighten need-to pattern to exclude subject-led advice (You need to...) CodeRabbit review flagged that the `need to` continuation pattern was overmatching subject-led advice like "You need to update..." causing false-positive nudges. Added negative lookbehind `(?<!\b(?:you|i|we|...)\s+)` to both `CONTINUATION_SIGNALS` and `hasImperativeSignal` so only bare imperatives (no subject) trigger continuation. Added regression tests. Also confirmed three earlier findings were already fixed: - print.ts onInterrupt() already uses abort('interrupt') ✅ - Indentation in bugfixes.test.ts:172-200 already 2-space ✅ - hasImperativeSignal already uses lateText ✅ - queryLifecycle removal was intentional cleanup ✅ |
||
|
|
22fa5b4227 |
fix(copilot): auto-refresh Copilot token on 401 instead of only showing re-auth hint (#1766)
* fix(copilot): auto-refresh Copilot token on 401 instead of only showing re-auth hint When GitHub Copilot returns 401 'token expired', the existing flow only showed a hint to run /onboard-github (the #1042 fix) but never actually refreshed the token mid-session. Now _doRequest detects the 401 + 'token expired' body in GitHub mode, calls refreshCopilotTokenOn401() to exchange the stored OAuth token for a fresh Copilot token, updates process.env, and retries the request. Fixes #1746 * fix: promote refreshedCopilotToken in auth precedence and add 401 retry integration test - Fix auth value precedence in buildHeadersForAttempt so refreshedCopilotToken takes effect even when a CredentialPool is active (single key still creates a pool, shadowing the refreshed token on retry) - Add integration test verifying GitHub Copilot 401 'token expired' triggers refreshCopilotTokenOn401() and retries chat_completions with the new token (P2 for PR #1766 review) * test: add codex_responses 401 retry integration test (CodeRabbit P2) Adds a new test 'GitHub Copilot 401 codex_responses retries with refreshed token' that verifies the 401 retry path for the codex_responses transport (lines 2349-2390). Mocks performCodexRequest to throw APIError(401, 'token expired') on first call and return a valid SSE response on second, verifies refreshCopilotTokenOn401 is invoked and the retry uses the refreshed token. * review: shared token-expired helper, credential-pool test, edge case tests Finding 1: Promote token-expired detection into isCopilotTokenExpiredError() helper used by both chat_completions and codex_responses paths. Finding 2: Add credential-pool integration test verifying that refreshedCopilotToken takes precedence over credentialPool.next() when OPENAI_API_KEYS is active. Finding 3: Add edge case tests - 'token has expired' variant, 401 without expired substring (no refresh), and same-token refresh (retries with original token, exhausted, then fails). * review: credential source check in codex retry path, fix mock leak - Add credential source gate (apiKey === process.env.OPENAI_API_KEY) to codex_responses 401 retry path, matching the chat_completions path guard - Add same-token guard (newApiKey !== apiKey) after refresh in codex path - Remove leaking mock.module('./codexShim.js') from providerOverride test, use fetch mock instead so performCodexRequest converts 401 to APIError - Remove duplicate old test that was left behind by previous edit - Add providerOverride test verifying the credential source gate blocks refresh * review: P2 refreshedCopilotCodexToken, P3 guard continue on last attempt P2: Add refreshedCopilotCodexToken variable that takes precedence over providerOverride.apiKey on retry, mirroring the chat path's refreshedCopilotToken in buildHeadersForAttempt. Prevents stale override key from being reused after a successful refresh. P3: Guard the chat path's continue with attempt < maxAttempts - 1 so the original 401 is surfaced when the 401 lands on the final retry slot, instead of falling through to the generic 500 exit error. * review: P1 chat path credential gate, P3 log refresh failures P1: Add oldToken === (process.env.OPENAI_API_KEY ?? '') check in chat path before calling refreshCopilotTokenOn401(), matching the codex path guard. Prevents credential substitution when providerOverride.apiKey, route credential, or custom auth header was the failing credential. Add regression test for chat path with mismatched providerOverride. P3: Log caught error with logForDebugging before returning false in refreshCopilotTokenOn401() catch block, so secure storage read failures, token exchange rejections, and persistence failures are visible in debug logs instead of silently swallowed. * fix: restrict codex refresh path to Copilot/GHE endpoints only isGithubWithCodexTransport was using isGithubMode which is true for all GitHub modes including GitHub Models API and custom routes. Changed to isGithubCopilotEndpoint that checks githubEndpointType === 'copilot' || githubEndpointType === 'ghe', matching the chat path's isGithubCopilot guard. Prevents COPILOT_HEADERS and refreshCopilotTokenOn401 from being triggered for non-Copilot endpoints. * fix: move didRefreshCopilotToken assignment after credential source check The refresh flag was set before confirming oldToken matches process.env.OPENAI_API_KEY, burning the single refresh chance when the credential pool rotates to a non-matching key first. Moved inside the credential source check so it's only consumed for eligible credentials. * fix: add oldToken guard to chat-path credential source check Prevents '' === '' match when both Authorization and OPENAI_API_KEY are empty from burning the refresh flag on an unauthenticated request. --------- Co-authored-by: Gravirei <gravirei@users.noreply.github.com> |
||
|
|
0071454479 |
Update Xiaomi MiMo v2.5 catalog reasoning (#1788)
Limit the direct Xiaomi MiMo and token-plan catalogs to the supported v2.5 chat models, removing the deprecated v2/flash entries. Add OpenAI-compatible reasoning_effort metadata for low/medium/high on Xiaomi MiMo v2.5 models while keeping the provider on the existing OpenAI-compatible path. Add tests covering the token-plan catalog, raw api-key auth header, and reasoning_effort request serialization. Validation: bun run build; bun run smoke; bun run check; bun run test:full; bun run test:provider; bun run test:provider-recommendation; bun run typecheck; bun run typecheck:type-tests; python -m pytest -q python/tests; bun run integrations:check; focused Xiaomi/OpenAI shim tests; git diff --check; bun run scripts/pr-intent-scan.ts --base upstream/main. |
||
|
|
40faf256db |
fix(atlas-cloud): vendor to gateway catalog correction and added reasoning support (#1785)
* Fix Atlas Cloud gateway catalog Move Atlas Cloud from a direct vendor descriptor to a hosted OpenAI-compatible gateway while preserving the atlas-cloud preset, dedicated ATLAS_CLOUD_API_KEY handling, model env defaults, and generated integration metadata. Add shared upstream model descriptors needed by the Atlas gateway catalog so Atlas entries do not own model metadata or resolve through NearAI-scoped descriptors. Add conservative Atlas reasoning metadata for verified Kimi effort levels and Grok Build always-on/no-wire reasoning, plus resolver and OpenAI shim tests for top-level reasoning_effort serialization and no-wire behavior. * Add verified Atlas reasoning effort metadata Update the Atlas Cloud gateway catalog with route-specific reasoning controls verified through live Atlas probes. Enable top-level reasoning_effort metadata for models that accepted low/medium/high/xhigh, limit Doubao routes to low/medium/high, keep Grok Build as always-on no-wire reasoning, and leave OWL without effort controls after probes showed no reasoning output. Expand effort resolver coverage and OpenAI shim serialization tests so Atlas metadata exposes the expected effort levels and strips reasoning_effort for no-wire models. Validation: bun install; bun run build; bun run smoke; bun run check; bun run test:provider; bun run test:provider-recommendation; python -m pytest -q python/tests; bun run typecheck; bun run typecheck:type-tests; bun run doctor:runtime; bun run security:pr-scan -- --base HEAD --head 1878a1f05f7fcce7ab7098c890ff3091dafcaf66. * Add explicit Atlas catalog aliases Introduce opt-in catalog aliases for model entries and resolve those aliases through the active route catalog before sending OpenAI-compatible requests. Add Atlas aliases where descriptor IDs differ from Atlas API model IDs, including GLM, Claude coding variants, GPT, Kimi, MiniMax, Qwen, and Grok entries. Teach effort metadata lookup to use the same explicit aliases so /effort support matches routed model IDs. Cover Atlas alias routing plus OpenRouter and Hicap no-op routing in providerConfig tests, and add Atlas GLM alias effort coverage. * Strengthen Atlas routing regression tests Assert the Atlas gateway catalog resolves at least one shared model before checking for NearAI descriptor leakage. Add an Atlas full API-name fallback assertion so catalog alias routing cannot over-match provider-qualified model IDs. |
||
|
|
82fd23798e |
fix(env-file): collapse escaped backslashes in quoted values (#1773)
* fix(env-file): collapse escaped backslashes in quoted values unescapeQuotedValue only collapsed an escaped quote (\" -> "), but the closing-quote scanner (findClosingQuote/isEscapedQuote) already treats a quoted value as backslash-escaped: an odd-length backslash run escapes the following quote, so a \\ pair is consumed as a single escaped backslash when locating the closing quote. Because the unescaper left \\ untouched, the two stages disagreed and a value such as "a\\b" round-tripped to the doubled "a\\b" instead of "a\b". Collapse \\ to \ in unescapeQuotedValue so both stages share one escaping rule. Lone backslashes before ordinary characters are still preserved. * test(env-file): cover escaped backslash adjacent to closing quote Locks down the terminator interaction between findClosingQuote/isEscapedQuote and unescapeQuotedValue: a value written as "a\\" (two backslashes before the closing quote) parses to a single trailing backslash. |
||
|
|
13f7401541 |
perf(repl): batch streaming text, cache normalize, coalesce config writes (#1744)
Three independent runtime hot-path wins for long sessions and fast streams:
- Streaming text (REPL.tsx): the Ink root is a LegacyRoot, so every
per-delta setStreamingText committed a synchronous REPL render. The
preview hides the in-progress trailing line, so deltas between newlines
changed nothing on screen yet still re-rendered. Hold the full text in a
ref and publish to state only when the newline-truncated preview changes
(or on clear), dropping those no-op renders under 100-300 delta/sec
streams. Displayed text is byte-identical; the Esc-interrupt path reads
the ref so no trailing partial line is lost.
- Incremental normalize (messages.ts + Messages.tsx): normalizeMessages
flat-maps the whole transcript on every append (O(n) over 2,800+
messages). normalizeMessagesCached memoizes per-message output keyed on
(message identity, isNewChain entry flag) in a WeakMap, so unchanged
messages are reused with stable object identity (reviving downstream
memo/WeakMap bailouts and cutting GC pressure). It is an allocation/
identity optimization — the call still scans the list, it is not an O(1)
append. Proven equivalent to normalizeMessages in tests.
- Coalesced config writes (config.ts): saveGlobalConfig does a sync
lock+reread+backup+fsync per call. saveGlobalConfigDeferred queues the
updater, write-throughs the cache for read coherence, and folds the
batch into one locked write on a 500ms debounce (flushed on cleanup and
process exit). Auth-loss guard, lock and backup stay on the disk path.
Also fixes handleMessageFromStream's input_json_delta to update tool input
in place instead of reordering the updated tool to the array tail.
Review fixes (CodeRabbit + jatmn):
- REPL onStreamingText updates streamingTextRef BEFORE the showStreamingText
guard so reduced-motion / cursor-up-yank-bug terminals preserve partial
assistant output on Esc. The publish decision is extracted to a pure
helper (streamingTextPublish.ts) with streamingTextPublish.test.ts, and
replStreamingTextClear.test.ts source-scans REPL.tsx to assert both
turn-boundary paths fully clear the streaming refs (no stale re-append).
- saveGlobalConfigDeferred primes the cache (getGlobalConfig) before
enqueueing, and a direct saveGlobalConfig flushes pending deferred writes
first. Together these keep same-process reads coherent: the first deferred
counter update is visible immediately even on a cold cache, and a direct
save can no longer clobber a queued delta with a disk snapshot. Regressions
in deferredConfigWrites.test.ts.
- Extracted the deferred-write queue/debounce/write-through/drain into a
generic, disk-free engine (deferredConfigWrites.ts) with injected
storage/scheduler; deferredConfigWrites.test.ts exercises the real branch.
- config.deferredWrite.test.ts loads config via a query-suffixed specifier
so a leaked mock.module('./config.js') can no longer silently turn its
assertions into no-ops; it always exercises the real path.
- messages.streamingToolUses.test.ts covers interleaved input_json_delta
order preservation; messages.normalizeCached.test.ts now covers the
reused-message entryFlag true<->false cache transition.
- Corrected the normalizeMessagesCached comment (O(n) scan / allocation
optimization, not an O(1) append).
Co-authored-by: OpenClaude <openclaude@gitlawb.com>
|
||
|
|
a723540163 |
perf(build): minify the CLI bundle (whitespace + syntax, keep identifiers) (#1743)
dist/cli.mjs shipped unminified at 21.7MB; whitespace+syntax minification cuts it to ~16MB (-26%) and shaves V8 parse time on every invocation. Identifier mangling stays off because the codebase matches constructor.name (errors.ts, toolExecution.ts, useCanUseTool). The SDK bundle stays unminified — its React/Ink leak check greps import syntax that minification would rewrite. The bundle guard's missing-module tripwire relied on Bun's `// missing-module-stub:<path>` module-boundary comments, which minification strips. The stub loader now also emits the marker as a side-effecting string push (survives treeshaking and syntax-minify), and the guard parses both forms. Review fix (CodeRabbit + jatmn): the marker parser previously truncated paths at the first backslash or space, so a JSON-escaped Windows marker like "missing-module-stub:C:\\Users\\Jane Doe\\...\\src\\...\\foo.js" was captured as a useless `C:` (or `C:\\Users\\Jane`) fragment and canonicalized to the wrong key — letting a newly stubbed module slip past the tripwire on Windows/spaced build hosts. Parse each marker form to its correct terminator instead: the string literal runs to its matching (back-ref) closing quote consuming escaped pairs, and Bun's comment runs to end of line. Extract canonicalStub() + the parser into scripts/stubMarkerGuard.ts so the logic is unit-testable, and add regression tests for Windows, spaced, comment-form, and multi-marker-per-line cases. Verified: build green, bundle ~16MB minified, guard passes against the real bundle, stub-guard tests pass, --version works through the minified bundle. Co-authored-by: OpenClaude <openclaude@gitlawb.com> |
||
|
|
a1a3cfc31e |
perf(integrations): load descriptor catalog lazily on first registry read (#1742)
Split the generated output into integrationManifest.generated.ts (plain preset metadata, type-only deps) and integrationArtifacts.generated.ts (the ~10k-line descriptor graph). Add a setRegistryLazyLoader() hook in registry.ts so read getters evaluate the catalog on first access instead of eagerly at module load — cutting startup cost on paths that never read the registry (index.ts previously eval'd 60+ descriptor modules at import). Review fixes: - registry.ts ensureLoaded() keeps the lazy loader armed until it succeeds (re-entrancy guard + clear-on-success), so a throwing lazy import retries on the next read instead of leaving the registry empty. - providerSecrets.ts no longer statically imports the heavy descriptor artifacts. It sat on the bootstrap startup chain (bootstrap -> providerConfig -> providerProfile -> providerSecrets), so the static import undercut the lazy-loading goal. Import PROVIDER_PRESET_MANIFEST from the lightweight manifest module and lazily require() the descriptor arrays inside readDescriptorCredentialEnvKeys() (cached by getKnownProviderSecretEnvKeys(), so it runs at most once). - artifactGenerator.test.ts resolves artifacts by full src/integrations/generated/<name> path instead of tuple order; the last order-dependent test now uses splitGeneratedArtifacts(). - Added registry + providerSecrets regression tests (loader retry on failure; no static descriptor import on the bootstrap path). Co-authored-by: OpenClaude <openclaude@gitlawb.com> |
||
|
|
6fdb1d0c46 | fix(bg): preserve provider env-file values during prompt detection (#1767) | ||
|
|
3157ee715b | Fix slash command suggestion filtering (#1664) | ||
|
|
cb689cc33a |
feat(effort): Add model-level reasoning effort routing (#1780)
* Add model-level reasoning effort metadata Introduce per-model reasoning control metadata on catalog entries and model descriptors so /effort support can be expanded without provider-wide inference. Resolve /effort through explicit model metadata first, preserve legacy allowlist behavior, and treat supportsReasoning-only entries as capability metadata that does not mutate requests. Guard OpenAI shim effort serialization with the model-level wire support check and add focused tests for capability-only, explicit metadata, opt-out, and toggle-mode cases. Document the reasoning metadata contract and provider follow-up workflow. * Expand reasoning effort routing Centralize OpenAI shim reasoning request planning so DeepSeek-compatible and Z.AI-compatible controls flow through the effort resolver instead of provider-specific shim helpers. Add compatibility metadata handling for DeepSeek and Z.AI routes while keeping supportsReasoning-only catalog entries non-controllable until exact wire formats are verified. Respect route removeBodyFields after compatibility serialization and make provider override support checks use the resolved override route/base URL instead of ambient provider metadata. Document temporary compatibility rules and add regression coverage for Atlas DeepSeek, Z.AI levels, Groq stripping, providerOverride OpenAI effort, providerOverride Groq stripping, and non-generic metadata opt-out. Verified: bun test --feature=UNATTENDED_RETRY src/utils/effort.codex.test.ts; bun test --feature=UNATTENDED_RETRY src/services/api/client.test.ts; bun test --feature=UNATTENDED_RETRY src/services/api/openaiShim.test.ts; node .\\node_modules\\typescript\\bin\\tsc --noEmit; bun run build; git diff --check. * Fix Responses API reasoning effort shape Send reasoning effort on OpenAI-compatible Responses requests using the nested reasoning object expected by the endpoint instead of flat reasoning_effort/reasoning_summary fields. Keep chat_completions behavior unchanged so OpenAI-compatible chat endpoints still receive top-level reasoning_effort. Add a regression test covering the Responses request body shape and verifying the flat fields are omitted. Validation: bun test --feature=UNATTENDED_RETRY src/services/api/openaiShim.test.ts; node .\\node_modules\\typescript\\bin\\tsc --noEmit; git diff --check. * Document reasoning effort metadata rules Make the new reasoning-effort guide discoverable from the integrations overview and reading order. Update model, gateway, and vendor onboarding docs to explain that supportsReasoning is descriptive only and does not enable /effort request mutation without verified per-model reasoning metadata. Clarify that gateway and vendor catalogs must annotate reasoning controls per exact route/model rather than provider-wide. Validation: git diff --check. * Stabilize effort resolver tests Add an optional reasoning control context so effort tests can inject provider, catalog, model descriptor, and shim metadata without mocking process-global integration/provider modules. Update effort.codex tests to use the injected context and restore only the remaining local mocks, preventing mock leakage into later full-suite provider tests. Validation: bun run check; bun run test:provider; bun run test:provider-recommendation; bun run typecheck:type-tests; bun run integrations:check; python -m pytest -q python/tests; bun run security:pr-scan -- --base upstream/main --head HEAD. * Address effort PR review findings Load integration registry before catalog reasoning lookup, isolate provider override route resolution from ambient routes, and carry explicit compat reasoning metadata into the OpenAI shim request planner. Clarify reasoning metadata documentation and add focused regression coverage for compat metadata and provider override route preference. * Fix Z.AI metadata high effort serialization Include high in the Z.AI-compatible metadata gate so high-only reasoning metadata emits reasoning_effort instead of silently dropping the user-selected effort. Add regression coverage for a high-only zai_compatible catalog entry flowing through the OpenAI shim request planner. * Fix provider override effort fallback Allow unrecognized providerOverride OpenAI-compatible routes to fall back to legacy effort support instead of dropping user-selected effort. Constrain compat metadata levels to wire-faithful high/xhigh values and clarify reserved reasoning wire formats in docs and descriptors. * Clamp provider override effort by route metadata Resolve providerOverride effort against the override model and route context before converting it for the OpenAI shim, so stale persisted effort values respect per-model metadata levels. Add regression coverage for high-only providerOverride metadata and explicit max filtering in compat metadata levels. |
||
|
|
263f07e2ab |
chore(main): release 0.20.1 (#1774)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>v0.20.1 |
||
|
|
db66f41071 |
fix(resume): tolerate malformed legacy attachment records (#1768)
* fix(resume): tolerate malformed legacy attachment records * test(resume): allow resume hook messages in recovery test * fix(resume): use validated attachment for skill listing |
||
|
|
b8c34645c9 |
chore(deps): clean npm install — fix CVEs, silence warnings (#1782)
* chore(deps): clean npm install — fix CVEs, silence warnings - bump undici 7.24.6 → 7.28.0 (7 high CVEs: TLS bypass, header injection, DoS, cache poisoning, SameSite downgrade, cross-origin routing) - bump ws 8.20.0 → 8.21.0 (2 high CVEs: uninitialized memory disclosure, memory exhaustion DoS) - add allowScripts for sharp + protobufjs to silence install-script warnings - vendor node-domexception shim (re-exports native DOMException) and override the deprecated polyfill pulled transitively by google-auth-library → gaxios → node-fetch@3 → fetch-blob Result: `npm install` reports 0 vulnerabilities, 0 warnings. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * chore(deps): update bun.lock for undici/ws bumps and node-domexception override CI runs `bun install --frozen-lockfile`, which requires bun.lock to match package.json. The previous commit bumped undici/ws and added the node-domexception shim override but didn't include the regenerated lockfile, causing frozen-lockfile CI to fail. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(publish): include vendor/node-domexception-shim in npm tarball The file: override in package.json points at vendor/node-domexception-shim, but the files array didn't list vendor/, so npm pack excluded it. End-user npm installs would fail resolving the override. Add vendor/node-domexception-shim/ to the files array. Verified via npm pack --dry-run: tarball now contains both shim files (12 → 14 files). Addresses reviewer finding #1. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com> |
||
|
|
bd00b3b3c5 |
fix(bg): stream session logs with bounded memory (#1762)
* fix(bg): stream session logs with bounded memory * fix(bg): handle log follow cleanup edge cases * fix(bg): surface non-follow log read errors * test(bg): isolate log streaming temp dirs |
||
|
|
d32b6f0476 |
fix(update): stop false "development build" block on npm installs with NODE_ENV=development (#1781)
`/update` and `openclaude update` reported "Auto-update is unavailable for
a development build." even when OpenClaude was correctly installed from npm,
as long as the launching shell had `NODE_ENV=development` exported.
Root cause: `getCurrentInstallationType()` checked `NODE_ENV === 'development'`
as its first branch, before any path-based detection. A user's shell env var
then downgraded a real npm install to 'development', which routed
`resolveUpdateStrategy()` to `{ action: 'blocked', reason: 'development' }`.
Two-part fix:
1. doctorDiagnostic.ts — move the `NODE_ENV === 'development'` check to a
fallback position after all real-install path markers (bundled mode, local
npm, npm-global paths, /npm/, /nvm/, `npm config get prefix`). Path
detection runs first; NODE_ENV only classifies as 'development' when no
install path matches (i.e. an actual source-tree `bun run dev` run).
2. bin/openclaude — the heap-sizing relaunch previously used
`fileURLToPath(import.meta.url)`, which resolves symlinks. After relaunch,
`process.argv[1]` pointed at the real file target (repo path for
`npm install -g .`, package path inside node_modules for real installs),
defeating path-based detection. Preserve `process.argv[1]` (the original
invocation path, e.g. /usr/local/bin/openclaude or nvm bin symlink) so
npm-global path markers can match correctly.
Verified: `bun run typecheck` passes; `openclaude doctor` now reports
npm-global (not development) with NODE_ENV=development set on a real
npm global install.
Co-authored-by: OpenClaude <openclaude@gitlawb.com>
|
||
|
|
28bbec4948 |
fix(memory): bound memory-directory scanning work (#1757)
* fix(memory): bound memory-directory scanning work * fix(memory): harden bounded memory scanning follow-up |
||
|
|
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. |
||
|
|
3fb718f403 |
fix(worktree): base agent isolation worktree on parent HEAD, not origin/main (#1652)
* fix(worktree): base agent isolation worktree on parent HEAD, not origin/main createAgentWorktree delegated to getOrCreateWorktree with no base, so the non-PR path checked out origin/<defaultBranch> whenever that remote-tracking ref existed locally. An isolated agent (isolation: "worktree") is expected to see the same committed state as the parent session, but instead got an older tree and missed files that exist only on the active branch. Resolve the parent session's HEAD from the session cwd and thread it through getOrCreateWorktree as a new `baseRef` option (used verbatim, then rev-parsed to a SHA). Falls back to the prior origin-based behavior when HEAD can't be resolved (e.g. a repo with no commits). EnterWorktree/PR worktree paths are unchanged. Fixes #1586 * test(worktree): make agent-base regression test hermetic; add explicit cwd seam The integration test went through createAgentWorktree's ambient getCwd(), which reads process-global cwd state. bun runs test files concurrently in one process and a sibling test mutates that global cwd, so the agent-base test raced and failed in the full suite (worktree based on origin/main, missing the feature-only file). Add an optional `cwd` to createAgentWorktree, used for both the canonical git-root and the parent-HEAD lookups (defaults to getCwd()). The test pins it explicitly so it no longer depends on the raced global cwd. Verified it passes isolated and in the full src/utils suite, and still fails on the pre-fix code. * test(worktree): isolate agent-base regression from leaked module mocks Per review: the regression test imported createAgentWorktree (and thus worktree.ts's execFileNoThrow.js dependency) at module load, so it could bind process-global bun mock.module state left by a neighboring suite before establishing its own isolation. Hold the shared mutation lock for the whole test so it never runs interleaved with suites that mock execFileNoThrow.js, and import the worktree module only after the lock is held, via a cache-busted dynamic import, so the binding resolves against the real module. * test(worktree): use execa and a longer timeout in the agent-base regression Per AGENTS.md, src/utils tests shell out via execa rather than child_process, so swap the git() helper to execaSync. The test also performs real git work (init, commit, branch, worktree add) that can exceed Bun's default 5s timeout on slower/Windows runners, so give it an explicit 15s budget. * test: stop two suites leaking partial module mocks process-wide Bun's mock.module is process-global and is not reverted by mock.restore(), so a partial-surface mock left active by one suite breaks any later suite that imports the same module — it sees a module missing the un-mocked exports and fails to link with "named export not found". - setupGitHubActions.test.ts mocked config.js with only saveGlobalConfig. Spread the real surface (as it already does for execFileNoThrow/browser) and restore it in afterEach, so config.js keeps its full surface for later suites. - osc.test.ts mocked execFileNoThrow.js and tempfile.js with partial stubs and never restored them. Spread the real surfaces and gate each overridden export on an active-suite flag, so when osc's tests aren't running the exports delegate to the real implementation instead of leaking stubs. This unblocks worktree.agentBase.test.ts (which loads worktree.ts → config.js / execFileNoThrow.js) when it runs after either suite. * Revert mock-isolation changes that regressed the full test suite The previous commit reworked the leaking config.js / execFileNoThrow.js mocks in setupGitHubActions.test.ts and osc.test.ts (and swapped the worktree test helper to execa). While it fixed the two-file reproductions, it regressed the full smoke-and-tests run: createAgentWorktree began failing with "not in a git repository" in the combined suite. Revert to the last green state while a more robust isolation approach (process-level isolation for the real-git test) is worked out. * test(worktree): run the agent-base regression in an isolated process The previous in-suite isolation attempts couldn't survive the full test run: createAgentWorktree shells out to git via execFileNoThrow.js, which other suites mock with `mock.module` — a process-global override Bun cannot reliably revert, so any leaked stub made the test fail with "not in a git repository" depending on suite ordering. Move the actual createAgentWorktree call into a standalone child process (worktree.agentBase.fixture.ts) that loads only the real modules, and keep the git repo setup and assertions in the test using real git directly. Nothing the shared test process mocks can reach the child, so the test is now order- and leak-independent. Verified passing in isolation, alongside the two leaking suites that previously broke it, and in the full suite. * test(worktree): register agent-base fixture as a knip entrypoint The agent-base regression runs createAgentWorktree in a standalone child process (worktree.agentBase.fixture.ts) to escape leaked module mocks. knip sees no static importer for it — it is spawned via execFileSync — and the deadcode check fails it as an unused file. It is a genuine process entrypoint, so add the *.fixture.ts glob to knip's entry list. |
||
|
|
66ddbece19 |
fix(bridge): match loopback hostname exactly in HTTPS credential guard (#1760)
* fix(bridge): match loopback hostname exactly in HTTPS credential guard
The bridge requires HTTPS for non-localhost base URLs to protect OAuth
credentials in transit, but the guard decided "localhost" via substring
checks (`baseUrl.includes('localhost')` / `.includes('127.0.0.1')`). Any
remote URL that merely contains that text — `http://evil.example.com/localhost`,
`http://evil.localhost.com`, `http://127.0.0.1.evil.com` — slips past the
check and is allowed to carry credentials over plain HTTP.
Extract `isLocalhostBaseUrl`, which parses the URL and matches the hostname
component exactly (mirroring the logic already in `buildSdkUrl`), and route
both guard sites plus `buildSdkUrl` through it. A malformed URL is treated as
non-local so the HTTPS requirement still applies.
Add direct coverage for the helper, including the substring-bypass cases.
* fix(bridge): reject mixed-case HTTP scheme in credential guard
The HTTPS credential guard tested `baseUrl.startsWith('http://')`, which only
matches the exact lowercase scheme. `HTTP://example.com` / `Http://example.com`
slipped past — the URL parser normalizes the scheme to `http:`, so credentials
would still be sent over plaintext.
Fold the scheme test into the parse: add `isInsecureHttpBaseUrl`, which reads
`new URL(...).protocol === 'http:'` (case-normalized) and requires a non-local
host, and route both guard sites through it. Malformed URLs return false.
Extend coverage with mixed-case HTTP/HTTPS cases.
|
||
|
|
39604fe871 |
chore(main): release 0.20.0 (#1684)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>v0.20.0 |