Merge upstream main (v0.25.0) into sync branch and preserve fork customizations#17
Merge upstream main (v0.25.0) into sync branch and preserve fork customizations#17Edwardmaster7 with Copilot wants to merge 213 commits into
main (v0.25.0) into sync branch and preserve fork customizations#17Conversation
…suites (Gitlawb#1708) The cleanupNpmInstallations test replaces execFileNoThrowWithCwd with a stub that simulates a failed npm uninstall (E404). bun's mock.module is process-wide, and re-mocking the module back to the real implementation in afterEach does not reliably undo it, so the stub leaked into later test files that shell out for real (e.g. git worktree add), making them fail with a bogus "npm ERR! code E404". Install the override once at module load and gate it on a module-level flag that is cleared in afterEach, so the persisted mock transparently falls through to the real implementation whenever the flag is off. The real module is snapshotted into a plain object before the mock is installed, because bun live-updates the imported namespace to the mock — delegating through the namespace inside the override would otherwise recurse infinitely.
…itlawb#1750) Reorders classifyOpenAIHttpFailure so the status >= 500 branch runs before the isMalformedProviderResponse check. Gateway 502/504 overload pages have HTML bodies (matching "<!doctype html" / "<html") that previously tripped the malformed-provider-response path, marking the error non-retryable and surfacing "Provider returned a malformed response" even though the failure was transient — users had to retry manually. Now any 5xx is classified as provider_unavailable (retryable) regardless of body shape, matching the actual semantics. 4xx HTML responses still classify as malformed_provider_response since those are genuine protocol failures. Adds three regression tests covering 502 HTML, 504 HTML, and the unchanged 400 HTML path.
* Add Xiaomi MiMo token plan provider Add a descriptor-backed Xiaomi MiMo Token Plan gateway with the token-plan SGP/CN routing hosts, raw api-key OpenAI-compatible shim settings, and MiMo catalog defaults. Wire the new preset through generated integration artifacts, provider flag handling, active route/provider resolution, provider profile env mirroring, and ProviderManager preset ordering. Cover the new path with gateway metadata, compatibility, provider flag, provider profile, and focused ProviderManager tests. Validated with bun run build, bun run smoke, bun run check, typechecks, provider suites, integrations:check, security:pr-scan -- --base upstream/main, and doctor:runtime. * Address Xiaomi MiMo token plan review feedback Reset stale known OpenAI-compatible base URLs when xiaomi-mimo-token is explicitly selected so prior provider routing cannot survive the provider flag. Use hostname-based Xiaomi MiMo base URL detection when mirroring MIMO_API_KEY from provider profiles, avoiding substring matches in unrelated URL paths or query strings. Add focused regression coverage for stale base URL replacement and token-plan CN profile env mirroring.
* fix(opencode-go): sync model catalog with opencode.ai/go The OpenCode Go subscription page (https://opencode.ai/go) lists 13 models, but the catalog had 20. Remove the 7 models no longer offered: glm-5, kimi-k2.5, minimax-m2.5, qwen3.5-plus, mimo-v2-pro, mimo-v2-omni, hy3-preview. Catalog now matches the page exactly: - OpenAI-compatible: GLM 5.2, GLM 5.1, Kimi K2.7 Code, Kimi K2.6, DeepSeek V4 Pro, DeepSeek V4 Flash, MiMo V2.5 Pro, MiMo V2.5 - Anthropic messages: MiniMax M3, MiniMax M2.7, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus Updates gateway catalog, model descriptors, generated artifacts, and tests. * fix(opencode-go): reorder model catalog to match opencode.ai/go listing Reorder both the gateway catalog and model descriptor lists to match the order models appear on https://opencode.ai/go, so the in-app model picker mirrors the subscription page. No model added or removed — purely a reorder. GLM-5.2 → Qwen3.7 Max → Kimi K2.7 Code → MiMo V2.5 Pro → DeepSeek V4 Pro → Qwen3.7 Plus → MiniMax M3 → MiMo V2.5 → DeepSeek V4 Flash → GLM 5.1 → Kimi K2.6 → Qwen3.6 Plus → MiniMax M2.7 * test(opencode-go): assert exact model set matches opencode.ai/go catalog Address CodeRabbit review on Gitlawb#1745 — the count-only test wouldn't catch catalog drift. Add a strict set assertion verifying the 13 expected IDs are present and no removed/unexpected IDs remain. * test(opencode-go): update Anthropic Messages route test for refreshed catalog The direct-env-routing test listed minimax-m2.5 and qwen3.5-plus, which were removed from the opencode-go catalog. Replace with the five /messages-endpoint models that remain: minimax-m3, minimax-m2.7, qwen3.7-max, qwen3.7-plus, qwen3.6-plus. Unblocks the smoke-and-tests CI check on Gitlawb#1745. * fix: update OpenCode Go model references to 13 models and add assertions
Adds zai-org/glm-5.2 with the same 202,752 context window as GLM 5.1 so the model is selectable through the Atlas Cloud provider. Co-authored-by: OpenClaude <openclaude@gitlawb.com>
* feat(api): rotate OpenAI credential pools * fix(api): align pooled credential discovery * fix(cache-probe): preserve GitHub credential precedence * fix(provider): honor pooled OpenAI fallbacks * fix(provider): validate pooled profile credential labels * fix(api): harden OpenAI credential pool handling Reject placeholder values in pooled OpenAI credentials before requests, discovery, diagnostics, and profile generation can use them. Normalize pooled credentials to a single usable key for model discovery, runtime cache partitions, cache probing, and NVIDIA NIM cache lookups. Preserve documented profile precedence by letting live shell credentials override saved pools, carrying OpenCode fallback pools through launch, and redacting individual pool members in profile display. Add regression coverage for pooled credential validation, profile launch/rebuild behavior, discovery/cache callers, diagnostics, provider autodetect, and shim failover semantics. * fix(provider): cover pooled key recommendation path Import the pooled OpenAI credential validator in provider-recommend and split invalid credentials from unset credentials in user guidance. Add a script-level regression that runs the OpenAI recommendation path with OPENAI_API_KEYS so the ts-nocheck script cannot regress with runtime ReferenceErrors. Scrub pooled OpenAI keys before xAI OAuth profile env construction and loosen the invalid-pool discovery test to assert auth header absence instead of exact header shape. * fix(tests): stabilize rebased provider checks * fix(provider): address pooled credential review findings * test(api): cover opencode go credential failover * fix(provider): share OpenAI credential usability checks * fix(provider): respect pooled credential precedence * fix(model): preserve pooled discovery credential precedence * fix(model): fall back from unusable pooled discovery keys
…lawb#1754) - Enrich every entry with maxOutputTokens (from max_output_length) and capabilities (function calling, json mode, vision, reasoning) pulled from the live catalog. Addresses discoveryService metadata gap. - Add transportOverrides.openaiShim.removeBodyFields for xai/grok-build-0.1 to drop reasoning_effort (fixes 400 on Atlas Cloud). - Curate current model set: - Add: Kimi K2.7 Code, GLM 5.2, Qwen3.7 Max/Plus, Doubao Seed 2.0 variants, Claude Sonnet 4.6 / Haiku 4.5 (base + coding), latest GPT/Gemini/Grok. - Drop: K2 Thinking/Instruct 0905, older MiniMax M2.1/M2, duplicate Qwen. - Keep source: 'static' only. Entries sorted in descending version order within each vendor family. - Preserve notes: 'Free' on the owl model. .gitignore: ignore .tmp-* directories (test artifacts such as replay-index tests). Follows static-over-hybrid, catalog-model-ordering, and grok-build-0.1 notes.
* feat: auto-detect and persist project conventions to wiki Adds a convention scanner that reads project config files (package.json, tsconfig.json, eslint, prettier, Dockerfile, CI workflows, lockfiles) on startup and saves extracted conventions to .openclaude/wiki/pages/conventions.md. Includes a fingerprint cache to avoid redundant writes and a /wiki scan command for manual re-scans. New modules: - src/services/wiki/conventions.ts — scanner + cache + save - src/services/wiki/identity.ts — project identity (name, languages, monorepo) - src/services/wiki/conventions.test.ts — 7 tests Modified: - paths/types/init/status — extended wiki infrastructure for conventions - wiki.tsx/index — added /wiki scan command - main.tsx — fires scan via startDeferredPrefetches - init.test.ts/status.test.ts — updated for new conventions page/fields Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(wiki): address PR Gitlawb#1010 review — trust gate, cache, pre-init, indexing Resolves the three blockers raised by @jatmn and @Vasanthdev2004, plus a post-rebase integration gap. 1. Trust gate (blocker): the startup convention scan ran git (via project identity) before workspace trust was established. Now gated behind the same check as prefetchSystemContextIfSafe — runs only when non-interactive (implicit trust) or the trust dialog has been accepted. 2. Cache fingerprint (blocker): computeFingerprint hashed only the detected config sections, so identity-only changes (project name, language counts, monorepo, default branch) left conventions.md stale. Identity inputs are now folded into the fingerprint. (Hashing the rendered markdown isn't viable — it embeds a "Last scanned" timestamp.) 3. /wiki scan pre-init crash (blocker): forceScanConventions caught the page write failure but still wrote the cache, throwing ENOENT before /wiki init. It now only writes the cache on a successful page write and reports saved=false; /wiki scan surfaces "run /wiki init first". 4. Index integration: the conventions page is written to pages/, but the wiki index (rebuildWikiIndex, added after this PR was opened) was never refreshed, so the new page wasn't listed. Both save paths now reindex on success. Testing: 3 new regression tests (identity-only re-save, pre-init no-crash/no-cache, reindex); wiki suite 14 pass; tsc clean; smoke + knip green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(wiki): address CodeRabbit review on PR Gitlawb#1010 - main.tsx: add a terminal .catch(logError) to the deferred conventions-scan promise chain so a failed dynamic import or scan can't become an unhandled rejection on the startup path. (Major) - conventions.ts: narrow both page-write catch blocks to ENOENT (= wiki not initialized → skip); rethrow other failures (EACCES, EROFS, …) instead of masking them as "not initialized". (Major) - identity.ts: replace blocking execFileSync('git', …) with async execa (the service-layer subprocess convention); getProjectIdentity is now async, awaited in scanProjectConventions. Keeps the startup scan off the event loop. - commands/wiki/index.ts: restore `ingest` in the argumentHint — `[init|status|scan|ingest <path>]` — it's still an implemented subcommand. Testing: tsc clean; wiki suite 14 pass; smoke + knip green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com>
…1759) `node --version` and `python --version` are auto-approved as read-only Bash, but `npm`, `bun`, and `tsc` version queries were missing from the allowlist, so they fell through to a permission prompt. Add them in the same exact-anchored form (no trailing args) so a version flag can't smuggle a script-running suffix past the check (the `node -v --run <task>` class of bypass). Closes the only read-only gap that the now-superseded Gitlawb#787 classifier covered, without a parallel classification surface. Testing: new readOnlyValidation.test.ts (18 cases — allows -v/--version, rejects install/suffixed forms); tsc clean; BashTool suite 117 pass. Co-authored-by: OpenClaude <openclaude@gitlawb.com>
…itlawb#1758) Long-running turns looked frozen ("are you still working?") and a dropped network stream could hang for the full 5-minute QueryGuard idle timeout. Three focused changes: - Spinner: show the elapsed-time counter at 5s instead of 30s (split the timer gate from the token-count gate). The timer is wall-clock derived, so it keeps ticking during long tool calls — proof of life even when no tokens stream. - Spinner: surface the currently-executing tool name in the status line (reusing the spinnerSuffix channel; stop-hook progress still wins). A long subagent/typecheck now reads "(↓ Bash · 1m 20s)" instead of a bare spinner. - claude.ts: enable the stream idle-timeout watchdog by default, matching the always-on read-timeout already used by the OpenAI/Codex shims. A silently dropped Anthropic-family stream now aborts and falls back to a non-streaming retry within STREAM_IDLE_TIMEOUT_MS (90s) instead of hanging to 5 minutes. Opt out with CLAUDE_DISABLE_STREAM_WATCHDOG=1. Testing: tsc --noEmit clean; bugfixes.test.ts (31) and src/services/api (835) pass; bun run smoke builds + runs. Co-authored-by: OpenClaude <openclaude@gitlawb.com>
Co-authored-by: Gravirei <gravirei@users.noreply.github.com>
…k with local client (Gitlawb#1771) * fix(deps): remove deprecated uuid install path * fix(api): address PR Gitlawb#1232 review — type the local Vertex client surface Resolves the blocker raised by @Vasanthdev2004, @gnanam1990, and @jatmn: the in-repo AnthropicVertex replacement compiled under bun (no type-check) but added 4 `tsc --noEmit` errors that the upstream typed SDK did not. - Declare `messages`/`beta` as typed class fields (BaseAnthropic doesn't, but the upstream @anthropic-ai/vertex-sdk client did), so typed consumers — client.ts `new AnthropicVertex(...)` and the SDK calling `.messages` — keep the resource surface. (vertexClient.ts:145/146, test:53) - Widen the header-merge helpers to accept the base client's request header type (HeadersLike), and handle the NullableHeaders shape it actually passes so the merge stays correct, not just type-clean. (vertexClient.ts:182) Also drops the now-stale `@anthropic-ai/vertex-sdk` entries left behind by the dependency removal: - scripts/externals.ts INTENTIONALLY_BUNDLED (P3) - knip.json ignoreDependencies Testing: `tsc --noEmit` clean; vertex/client/gemini tests 51 pass; smoke green (INTENTIONALLY_BUNDLED back in sync, 57 entries); knip clean. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(api): address CodeRabbit review on PR Gitlawb#1232 — auth precedence + coverage - Security (vertexClient.ts): merge resolved Google auth headers LAST so a caller-supplied Authorization / x-goog-user-project can't override the Vertex credential and send the wrong token upstream. Other request headers still pass through unchanged. - Tests (vertexClient.test.ts): add focused regression coverage for the previously-unguarded routing/auth branches — * streaming → :streamRawPredict path (+ model stripped, stream preserved) * count_tokens → count-tokens:rawPredict path rewrite * auth-header precedence: caller Authorization does NOT override the Vertex token (guards the fix above + exercises the NullableHeaders merge branch). Testing: tsc clean; vertexClient tests 5 pass; full src/services/api 840 pass; smoke + knip green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(api): validate and encode Vertex model before building the URL CodeRabbit follow-up on PR Gitlawb#1232: `model` was interpolated straight into the Vertex endpoint path, so a missing/non-string model would silently route to `.../models/undefined:rawPredict` instead of failing fast. Now throw a clear error on a missing/empty model and encodeURIComponent the value before building the path. Adds a focused test for the missing-model case. Testing: tsc clean; vertexClient tests 6 pass; smoke + knip green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(api): address remaining review items from PR Gitlawb#1232 - [P2] Fix count_tokens method guard: apply method==='post' to both paths - [P3] Remove unused accessToken option from AnthropicVertex - [P3] Narrow batches type on messages/beta resources with Omit * test: add count_tokens?beta=true routing regression test --------- Co-authored-by: Kevin Codex <kevin@gitlawb.com> Co-authored-by: OpenClaude <openclaude@gitlawb.com> Co-authored-by: Gravirei <gravirei@users.noreply.github.com>
* initial commit * feat(ads): passive per-turn earning tips + mask earn code - Gitlawb earning tips now appear on a per-turn cadence (default every 2nd tip slot, OPENCLAUDE_ADS_TIP_EVERY-tunable) for opt-in users, bypassing the per-startup sponsored gate that only ever showed one ad per session. - Each shown earning tip fetches a real impression and confirms after the dwell, crediting opengateway credits; fails silent so ads never break the CLI. - Mark /ads isSensitive so the earn code is redacted from history. * feat(ads): mask earn code via paste dialog; never accept it inline /ads on always opens a masked TextInput dialog (mask="*") — the code is never typed inline, because the terminal echoes inline args as you type (isSensitive only redacts history after submission, not the live echo). An inline /ads on <code> now also opens the dialog and warns that the typed code is exposed and should be rotated. Converts /ads to a local-jsx command. * fix(ads): address CodeRabbit review on PR Gitlawb#1674 - ads.ts: hard 5s timeout (AbortController) on both fetchNextTip and confirmTip so a stalled connection can never hang the spinner-tip path; the abort timer is unref'd. (fetchWithProxyRetry forwards init.signal and treats AbortError as non-retryable.) - gitlawbEarn.ts: unref the best-effort confirm timer so it can't keep a short-lived CLI run alive for the dwell window. - ads.tsx: clear the stored earnCode on `/ads off` (it's a credential, no reason to keep it at rest after opt-out); fix the `/ads on` doc comment to match the always-masked-dialog behavior. - ads.test.ts: restore ADS_BASE_URL in afterEach; add submit/cancel coverage for the masked dialog (enables + persists code / cancellation message) and assert `/ads off` clears the code. - gitlawbEarn.test.ts: restore ADS_BASE_URL + OPENCLAUDE_ADS_TIP_EVERY in afterEach to stop env leaking across suites. - tipScheduler.test.ts: cover the earning-tip branch precedence in getTipToShowOnSpinner (driven via the existing config mock + env, no module mock — avoids the bun mock.module cross-file leak). Testing: tsc clean; tips+ads suite 36 pass; smoke green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * feat(ads): contextual sponsored tips — share the latest prompt for ad matching Switches the earning tips from generic to contextually-matched ads. When the viewer enables sponsored tips (the /ads on dialog now discloses this), the client sends their most recent prompt to the ads service, which matches a relevant ad via Gravity's contextual endpoint. - types.ts: TipContext gains `latestUserMessage` (opt-in earning tip only). - REPL.tsx: extract the latest user message at the spinner-tip pick site and thread the full TipContext into tip.content() (previously only {theme}). - gitlawbEarn.ts: pass ctx.latestUserMessage to fetchNextTip. - ads.ts: new sanitizeForAds() redacts secrets/JWTs/emails/long hex and truncates to 500 chars; fetchNextTip POSTs { context:{messages:[user]} } when a prompt is present (else GET, identity-only). - ads.tsx: /ads on dialog + enable message disclose that the recent prompt (secrets redacted) is shared with the ad partner — consent folded into enabling. Privacy: explicit disclosed consent, minimal context (last prompt only), sanitized client-side (the ads service re-bounds size server-side too). Testing: tsc clean; new ads.test.ts (7 sanitize cases); ads+tips suite 43 pass; smoke green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(ads): show the real advertiser + ad click URL in earning tips renderEarningTip hardcoded the Gitlawb name + gitlawb.com URL, so every served ad rendered as "Sponsored · Gitlawb — … gitlawb.com" and discarded the actual ad's `name` and `link`. Pass the served ad's advertiser name and click URL (Gravity's tracker — required for click attribution/payout) into the renderer; fall back to Gitlawb only for the static no-ad line. Testing: tsc clean; tips suite 16 pass; smoke green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * feat(ads): hyperlink the advertiser name instead of printing the tracker URL Sponsored/earning tips render the advertiser as a terminal hyperlink to the ad's click URL (Gravity's tracker) via a shared renderSponsorLink helper, rather than printing the long tracker URL inline. Clicks still route through the tracker, so attribution/payout are unchanged. Testing: tsc clean; tips/ads suite 46 pass (incl. tipLink); smoke green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(ads): address CodeRabbit review on the contextual ads changes Security: - tipLink.ts: sponsor name/url are advertiser-controlled (untrusted). Strip C0/C1 control chars from the name and accept only http(s) URLs (parsed via URL()), preventing terminal escape-sequence injection and javascript:/file: click targets. - ads.tsx: soften the consent copy to "best-effort secret redaction" (both the dialog and the enable message) — sanitizeForAds is heuristic, not absolute. Correctness: - ads.ts: clamp dwell_ms to a finite, non-negative integer (malformed values no longer yield NaN/Infinity in the confirm-delay math). - gitlawbEarn.ts: never point a third-party advertiser name at the Gitlawb URL — a real ad uses only its own click URL; the Gitlawb fallback is reserved for the static no-ad line. Tests + isolation: - ads.test.ts: cover fetchNextTip/confirmTip (POST-with-context vs GET, !ok→null, ad:null→null, dwell clamp, confirm normalization) via a stubbed fetch. - tipLink: renderSponsorLink takes an injectable `hyperlinks` flag so both branches are deterministically tested; added control-char/unsafe-URL cases. - gitlawbEarn.test.ts: restore global `ads` config in afterEach. - tipScheduler.test.ts: preserve OPENCLAUDE_ADS_TIP_EVERY in cleanup. Note: CodeRabbit's packages/memory/* findings are moot — that stray package was removed from the branch. Testing: tsc clean; ads+tips suite 56 pass; smoke green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(ads): address CodeRabbit follow-up review (round 2) - ads.tsx: remove warmOneEarn — it fetched AND confirmed a tip during /ads on that the user never saw, crediting an unshown impression (and could confirm after a quick opt-out). Earning now happens only on the per-turn rendered-tip path. Also drop the unsupported "Run /ads for your balance" instruction. - ads.ts: normalize confirm amounts (earned_micro/balance_micro) to finite integers via toFiniteInt, mirroring the dwell_ms clamp. - gitlawbEarn.ts: degrade to the static fallback when tip_text is blank, so a malformed ad never renders an empty line and credits a blank impression. - ads.test.ts (commands): restore global ads config in afterEach; assert the inline-code exposure warning (warnExposed) is shown. - ads.test.ts (services): build the AWS-shaped fixture at runtime so it isn't flagged by security:pr-scan; add a success-path test for buildEarningTip() rendering a fetched ad + a blank-ad fallback test. Testing: tsc clean; ads+tips suite 58 pass; smoke green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(ads): address CodeRabbit full review (round 3) + comments Blocking: - ads.ts: gate the serve response on a string `token` (the signed impression), not on an `ad` field. A served tip carries NO `ad` key, so CodeRabbit's suggested `data.ad == null` would have suppressed every real tip; the empty slot (`{ ad: null }`) and malformed responses both lack a token, so the token check covers them correctly. Documented the contract. - gitlawbEarn.ts: drop the dead try/catch around fetchNextTip — it is contractually non-throwing (catches everything, returns null), so the wrapper was unreachable/misleading. - ads.ts: make the base64-blob redaction boundary reliable — \b is meaningless around + and / (both \W), so bound the run with explicit look-around instead. Comments / clarity (per review): - ads.ts: note withAbortTimeout is a per-CALL deadline (shared across retries), not per-attempt. - tipLink.ts: clarify the CONTROL_CHARS_RE comment — the ESC/BEL literals above are intentional terminal-sequence constants, not part of the matcher. - config.ts: document that GlobalConfig.ads is managed via /ads and intentionally excluded from GLOBAL_CONFIG_KEYS (earnCode is a credential). (resetEarningCadenceForTesting export is an accepted in-repo pattern — no change.) Testing: tsc clean; ads+tips suite 58 pass; smoke green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com>
…wb#1772) * feat(integrations): add GLM 5.2 as an Opengateway-routed model Add an `opengateway-glm-5.2` catalog entry (apiName `z-ai/glm-5.2`) to the gitlawb-opengateway gateway, reusing the existing `glm-5.2` model descriptor. Routes GLM 5.2 through the credit-billed Opengateway (OpenRouter upstream) alongside the existing direct Z.AI vendor path. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * test(model): add z-ai/glm-5.2 to the opengateway picker fixture The model-picker option-order assertion enumerates the gitlawb-opengateway catalog; add the new z-ai/glm-5.2 entry (after qwen/qwen3.7-max) so it matches the catalog. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Gitlawb#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.
…in/main (Gitlawb#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 Gitlawb#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.
Gitlawb#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.
* 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 Gitlawb#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 Gitlawb#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 Gitlawb#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 Gitlawb#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.
…tlawb#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 Gitlawb#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.
…interrupt' reason (Gitlawb#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.
* fix(memory): bound memory-directory scanning work * fix(memory): harden bounded memory scanning follow-up
…h NODE_ENV=development (Gitlawb#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>
* 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
…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>
* fix(resume): tolerate malformed legacy attachment records * test(resume): allow resume hook messages in recovery test * fix(resume): use validated attachment for skill listing
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* 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.
…itlawb#1999) * fix(compaction): honor disabled auto-compact under memory pressure * fix(compaction): preserve overflow recovery * test(compaction): cover disabled config setting
* fix(openai): compress tool history on Responses requests * fix(openai): tighten transport preprocessing * fix(openai): preserve structured compressed tool results * fix(openai): tier parallel tool results independently * fix(openai): preserve Ollama image endpoint fallback * fix(openai): omit old inline image payloads * fix(openai): bound inline image history * fix(openai): preserve local image fallback * fix(openai): bound data URL tool images * fix(openai): match parameterized image data URLs * fix(openai): bound adjacent tool images * fix(openai): bound leading tool images * fix(openai): size structured history per transport * fix(openai): scope attached image compression * fix(codex): match tool text serialization budget * fix(openai): recompress GitHub Responses fallback * fix(openai): preserve user images in history compression * fix(openai): scope history image omission to result ownership * fix(messages): preserve image owners after media retry * fix(messages): retain image ownership through normalization --------- Co-authored-by: jatmn <the@jat.mn>
…ey gate (Gitlawb#1995) * feat(aimlapi): provider foundation — config, catalog, ambient-key gate First layer of the AI/ML API onboarding split. Self-contained: config endpoints + partner-header resolution, gateway catalog entry, runtime metadata and artifact-generator wiring, and the regenerated integration manifest. Also lands the P1 security fix in providerProfiles: an ambient AIMLAPI key is only forwarded when the profile targets the canonical inference endpoint, so a proxy/staging profile can never leak the key elsewhere. No client/checkout/UI changes here — those stack in later PRs. * fix(aimlapi): harden credential gating and restore renamed-preset test path * fix(aimlapi): harden credential gating and finish renamed-preset path * fix(aimlapi): finish aimlapi.com rename in provider UI; gate generic proxy credential * fix(aimlapi): close remaining proxy credential and attribution leaks * fix(aimlapi): withhold ambient credentials and attribution from proxies * fix(aimlapi): withhold ambient credentials and attribution from proxies * fix(aimlapi): stop forcing profile credentials onto retargeted endpoints * fix(aimlapi): withhold ambient custom headers from proxy launches --------- Co-authored-by: Lookoff123 <bataryshkinairina@gmail.com>
Gitlawb#2019) * feat(install): enforce and guard the zero-warning npm install contract `npm install -g @gitlawb/openclaude` is verified zero-warning today, but nothing kept it that way: the runtime deps were caret ranges resolved fresh on every user install (the published tarball ships no lockfile), no CI step ever installed the package, and registry-side drift (a transitive dep deprecated after we ship) is invisible to file-based CI. Static contract (fast, offline, every PR via `bun run build`): - Pin the 3 runtime deps to exact versions so the verified resolution IS the shipped resolution. - New validators in scripts/externalsValidation.ts (unit-tested): dependencies must equal RUNTIME_DEPENDENCY_CONTRACT exactly (no ranges, no unreviewed additions), no consumer-run install hooks or funding field, engines.node pinned. Wired into validate-externals.ts. Runtime verification (scripts/verify-clean-install.ts, `install:verify`): - Tarball mode (release gate) and published mode (registry watch), each running cold-install and upgrade-over-previous scenarios in throwaway prefixes with a cold cache and normalized env/flags. - Strict output whitelist (summary lines only) with network failures retried and reported as infra (exit 2), never as a hygiene verdict. - Structural authority over the installed tree: any package declaring install scripts fails, the installed manifest must match the static contract, tarball payload/size asserted. - Boot must be silent: --version prints the exact packed version; --help (which, unlike the --version zero-import fast path, loads the real bundle) must exit 0 with empty stderr. CI: release publishes only after the verify passes on Node 22 (npm 10, the supported floor — warning phrasing and EBADENGINE behavior differ from npm 11) and Node 24, plus a final gate on the publishing machine replacing `npm pack --dry-run`. A daily install-hygiene workflow re-verifies the published @latest on {ubuntu, macos, windows} x {Node 22, 24} — the only defense against post-release registry drift, and the OS matrix covers the per-platform @vscode/ripgrep packages. Found-by-the-guard fix: a fresh machine printed "Warning: ignoring saved provider profile. OPENGATEWAY_API_KEY is required..." on every command (even --help) because the injected fresh-install Opengateway default fails validation without a key (Gitlawb#1651 chose ignore+warn). The default env is still ignored, but the warning now only fires for genuinely persisted profiles; published 0.24.0 carries the old noise, so the verify script exempts exactly that version until the next release. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(install): address CodeRabbit review on the install-hygiene guard - release.yml: pin install-verify to least-privilege `contents: read` and disable credential persistence on its checkout; same persist-credentials hardening on the install-hygiene cron checkout. - verify-clean-install: previousPublishedVersion now follows the same retry/infra discipline as installWithRetry — transient registry failures retry and then exit 2 (infra) instead of silently skipping the upgrade-scenario coverage; a clean not-published answer still skips. - providerProfile: the fresh-install warning suppression now keys on explicit provenance (persisted profile resolved once in applyStartupEnvFromProfile) instead of sniffing the DEFAULT_STARTUP_PROVIDER_ENV_VAR marker, which a persisted profile's env can inherit from a parent CLI process; regression test covers the marker-collision case. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * test(install): cover previousPublishedVersion retry/skip/infra branches CodeRabbit follow-up: the branches deciding whether the upgrade-install scenario runs, skips, or aborts as infra were untested. Extract the loop as resolvePreviousPublishedVersion with injected effects (runView, onRetry, onInfraFailure) per the repo's dependency-injection testing convention, guard main() behind import.meta.main so the test import does not launch a real verification, and add regression tests: first-try success, transient-infra retry then success, clean E404 → null skip without retries, persistent infra → onInfraFailure (exit 2 in the real wiring), and unparseable version output → null. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com>
Gitlawb#2014) * feat(codex): add GPT-5.6 family models and fix saved-model rehydration Add the GPT-5.6 family (gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna) to the Codex OAuth provider: alias map with default reasoning efforts, /model picker options, display names, and Codex-route context metadata. Bare `gpt-5.6` resolves to the flagship Sol tier at parse time (Codex CLI convention) — matched on the base name so a ?reasoning=/?thinking= query suffix cannot defeat the rewrite — keeping context sizing and display on real descriptor metadata. Context windows reconcile with the Gitlawb#1961 direct-OpenAI routing: the gpt.ts descriptors pin the ~272k effective Codex input cap (issue Gitlawb#1118 precedent; the Codex base URL resolves to a catalog-less route that reads descriptors), while the openai vendor catalog keeps the true 1.05M window for direct api.openai.com /v1/responses traffic. The gpt-5.6 alias-default reasoning effort is likewise Codex-transport-only: OPENAI_API_BASE gateways do not inherit first-party effort metadata (explicit /effort and ?reasoning= picks still flow everywhere). Fix startup rehydration for Codex profiles: profileSupportsModel is now authoritative for Codex-backend profiles — it accepts every Codex alias and Codex-eligible gpt-5.x free-text pick (shared isCodexEligibleGpt5Model predicate), so a /model choice (e.g. gpt-5.6-terra) survives restart instead of silently reverting to codexplan/gpt-5.5. A trailing [1m] tag is normalized off before matching, so tagged picks stick too. Foreign leftovers (kimi-k2.6) and API-only tiers the backend does not serve (gpt-5-mini/-nano) still fall back to the profile default instead of 400ing. Also: generalize the picker's custom-model recovery to keep curated labels for all Codex models across provider switches ([1m]-tolerant, single lookup), and add GPT-5.6 cases to the display-name maps. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(codex): address CodeRabbit review — [1m] tag parsing + coverage - parseModelDescriptor now strips a trailing [1m] context tag (whole-string or base-id position) before parsing: the tag is a client-side 1M opt-in, never a wire model id, so tagged aliases keep their mapping and effort defaults and resolvedModel never leaks the bracket suffix to the backend (pre-existing gap for e.g. gpt-5.5[1m], now fixed at the parse layer). - The bare-gpt-5.6 rewrite keeps a [1m] tag TRAILING after a preserved query (gpt-5.6?reasoning=medium[1m] → gpt-5.6-sol?reasoning=medium[1m]); the previously emitted tag-before-query form broke the request-time base-model split. End-to-end regression tests cover parse + request. - New coverage per review: alias effort defaults are asserted suppressed on a custom OpenAI-compatible gateway (non-Codex transport) while explicit ?reasoning= overrides flow; picker-recovery tests assert a persisted gpt-5.6-terra[1m] under a non-Codex provider keeps its exact tagged value with the curated label/description instead of a "Custom model" entry. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com>
…om loop detection, configurable compaction (Gitlawb#1869) * feat(perf): tier1 token optimization — universal tool compression, doom loop detection, configurable compaction - Extend compressToolHistory to Anthropic-native transports (firstParty/ bedrock/vertex/native-GitHub), gated to runs where prompt caching is inactive: rewriting old messages as they cross tier boundaries diverges the request prefix every call, so cached native sessions keep relying on the cache-aware microCompact instead. Shim-routed traffic (OpenAI-compatible env providers, per-agent providerOverride, Codex) still compresses at its own layer, where the local fast-path opt-out applies. compressToolHistory is now idempotent (skips its own stub/truncation markers) so layered call sites can never re-mangle output. - Add doom loop detection: blocks after 3 consecutive identical tool calls (same name + input signature). State is keyed per agent (main thread and each subagent separately) so concurrent subagents neither trip nor reset each other's counters. Resets at the start of each agent's query turn. - Add configurable compactTailTurns in GlobalConfig, wired into autoCompact's relevance pruning (default: 3, clamped to positive) and exposed in the /config UI next to the other compaction settings. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(perf): address review — tier-aware idempotency, full-input signatures Post-rebase reconciliation with Gitlawb#1958's compressToolHistory rework (the guard's extractText call no longer even typechecked against the new signature) plus the CodeRabbit findings: - Tier-aware idempotency replaces the blanket already-compressed skip, which permanently blocked mid→old upgrades: stubs stay terminal, and a truncated block left alone while mid-tier still upgrades to a stub when later exchanges age it into the old tier. The upgraded stub's omitted-chars count is recovered from the truncation marker (visible length + marker's omitted count = exact pre-truncation length), so it reports the tool's real output size — asserted equal to what a fresh single-pass stub would have produced. Regression tests cover the aging upgrade, recovered length, and same-input no-op. - computeSignature hashes the FULL serialized input (sha-256, fixed-size stored signature) instead of comparing a 2KB prefix, which treated distinct calls sharing a long prefix (e.g. Write calls differing only in trailing content) as identical — a false-positive block on legitimate work. Regression test included. - DEFAULT_COMPACT_TAIL_TURNS shared constant replaces the `3` duplicated across autoCompact, pruneByRelevance's default, and the /config UI. - Doom-loop block path: added a tengu_doom_loop_blocked analytics event (false-positive rates become observable for threshold tuning) and the nudge now tells the model a deliberate repeat is fine once something observable has changed. The blocked yield's message shape mirrors the sibling pre-execution error paths, preserving tool_use/tool_result pairing. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(perf): normalize compactTailTurns everywhere + native-routing coverage Second CodeRabbit round: - normalizeCompactTailTurns (relevancePruning.ts, next to the shared default) is now the single rule for the hand-editable config: finite values >= 1 floor to integers, everything else falls back to the default. The /config UI displays AND persists through it, so the picker shows exactly what autoCompact preserves (a hand-edited 2.5 no longer displays as 2.5 while running as 2; 0/negatives no longer display as selected while running as 3). Also fixes a real edge in the previous inline clamp: 0.5 passed the `> 0` check and floored to a ZERO-message tail, pruning everything. Unit tests cover the boundary matrix. - shouldCompressNativeToolHistory extracted from queryModel and exported: the request-mutating routing decision is now parameterized-tested across all four native transports (first-party, Bedrock, Vertex, GitHub-native-Anthropic) x caching on/off, the providerOverride exclusion, and non-native providers — queryModel itself needs a live client, so the predicate is the honest testable seam. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(perf): guard custom first-party endpoints + strict config coercion Third CodeRabbit round: - shouldCompressNativeToolHistory now requires an Anthropic first-party base URL before accepting the firstParty provider as native, mirroring the exact guard getPromptCachingEnabled uses. Without it, a custom ANTHROPIC_BASE_URL (proxy / compatible endpoint) reported firstParty with caching disabled and had every request's messages compressed — an assumption we cannot make about arbitrary endpoints. Test added for the custom-base-URL exclusion. - normalizeCompactTailTurns only coerces numbers (persisted config) and strings (the /config picker channel); other hand-edited shapes no longer smuggle a tiny tail through Number() coercion (true → 1, [2] → 2) and fall back to the default instead. Boundary tests added. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com>
…lawb#1864) * feat(onboarding): first-run experience for third-party providers Two gates in showSetupScreens were keyed on usesAnthropicAccountFlow(), so users of any non-Anthropic provider skipped onboarding entirely: - Onboarding (theme + security notes) now runs for all providers. The component already drops its preflight/OAuth steps when Anthropic auth is not enabled, so third-party users get theme -> security notes -> terminal setup with no login screens. - The trust dialog now runs for all providers. Workspace trust is orthogonal to the API provider — an untrusted repo is exactly as dangerous over a local model as over Anthropic. (The block comment even said "always show"; the inner gate contradicted it.) Also: the login-method screen now detects OPENAI_BASE_URL+OPENAI_MODEL in the environment and offers "Use current environment configuration" as the first (default) option. Selecting it saves and activates a provider profile via addProviderProfile — env vars alone do NOT activate the OpenAI route (resolveActiveRouteIdFromEnv requires CLAUDE_CODE_USE_OPENAI or a saved profile), a gap previously masked in manual testing by a stray legacy .openclaude-profile.json in the cwd. Verified live (tmux, scratch config dir, mock OpenAI server): fresh 3P first run walks theme -> security -> trust -> REPL; env option saves "Local OpenAI-compatible", the session completes a real turn against the env endpoint, and the profile persists across relaunch. Second launch shows no onboarding. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(onboarding): address review — testable gating seam + env-profile dedup - The first-run screen decisions move into src/utils/setupScreenGates.ts, a provider-free importable seam (showSetupScreens' import chain cannot be loaded under bun test — the same constraint and pattern as the dev-channels registration seam). Behavioral tests cover the gate matrix (fresh install, completed install, theme-missing re-show, trust independence, claubbit skip); the bugfixes.test.ts checks now assert the wiring (both dialogs consult the seam, no provider gate at the call sites) instead of only regexing for the removed string. - The "use current environment configuration" onboarding option dedupes: an existing profile matching the env base URL + model is re-activated via setActiveProviderProfile (which also re-applies profile env and syncs the startup profile file) instead of appending a near-identical profile on every pass through the flow. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(onboarding): refresh reused profile credentials + accurate env var label - The reuse branch now refreshes the stored credential from the environment before activating: a rotated OPENAI_API_KEY would otherwise leave the flow running on the profile's stale key. Falls back to the existing key when the env no longer carries one, so a working credential is never blanked. Status text says "Activated" for reuse and keeps "Saved" for a newly created profile. - The environment option's label names the variable the value actually came from (OPENAI_BASE_URL vs OPENAI_API_BASE) instead of hardcoding the former, so troubleshooting points at a variable that is really set. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(onboarding): preserve profile fields and verify the credential refresh Follow-up on the reuse path added last round: - updateProviderProfile REPLACES the profile (toProfile builds a fresh object rather than merging), so passing only name/baseUrl/model/apiKey silently dropped any configured apiFormat, azureStyle, authHeader, authScheme, authHeaderValue, customHeaders, or maxContextLength. Spread the existing profile and override only the refreshed credential. - A null return from updateProviderProfile (env values failing profile validation) no longer falls through to activation: reporting "Activated" while still running on the stale key is worse than routing the user to guided setup, which is what the create path already does. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(onboarding): redact credential-bearing endpoints before display OPENAI_BASE_URL / OPENAI_API_BASE are credential-bearing in the wild (userinfo like https://user:pass@host/v1, or ?token=/?api_key= query params), and both the option label and the completion status message rendered the raw value straight into terminal scrollback. The derivation moves into src/utils/envProviderOption.ts, which owns the disclosure boundary explicitly: `displayBaseUrl` is passed through the codebase's existing redactUrlForDisplay and is the only form the UI may render, while the raw `baseUrl` is retained for profile creation and activation so the saved profile still authenticates. Both rendered sites now use the redacted value. Regression coverage: envProviderOption.test.ts asserts userinfo and sensitive query params never reach displayBaseUrl (including via the non-URL fallback path) while baseUrl stays intact, plus var-name and availability cases; a wiring guard in bugfixes.test.ts fails if either rendered site is ever pointed back at the raw endpoint. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com>
…1986) * feat: add LongCat as first-class OpenAI-compatible provider Register LongCat-2.0 in the integration catalog with LONGCAT_API_KEY auth, /provider preset support, and zai-compatible thinking controls that emit thinking:{type} while stripping unverified reasoning_effort fields. * fix: complete LongCat provider integration * fix: complete LongCat provider integration * test: isolate LongCat provider environment * test: isolate LongCat environment in provider tests * test: isolate LongCat environment in route tests * test: isolate LongCat environment in utility tests * fix: harden LongCat provider integration * fix: complete LongCat transport support * fix: keep LongCat requests text-only * fix: normalize LongCat endpoint URLs * fix: reject malformed LongCat base URLs * fix: harden LongCat text-only transport * fix: scope LongCat transport hardening * fix: scope generic OpenAI credentials by route * fix: preserve required provider API formats * fix: align LongCat with documented tool support * fix: harden LongCat environment routing * fix: enable LongCat tool calling * Revert "fix: enable LongCat tool calling" This reverts commit d8b1802.
* feat(kimi): add K3 context variants * fix(moonshot): default K3 to 1M * fix(kimi): restore K3 model name * fix(kimi): preserve K3 route metadata * fix(kimi): recommend the selected context variant * fix(kimi): preserve max shim reasoning effort * fix(kimi): validate K3 reasoning levels * fix(kimi): reconcile K3 route metadata * test(integrations): accept catalog default ids * fix(kimi): preserve K3 runtime limits and effort aliases * fix(kimi): preserve override context limits * fix(kimi): canonicalize K3 reasoning to max * fix(moonshot): preserve K3 query reasoning * fix(effort): scope K3 xhigh normalization * fix(kimi): scope max-only override effort * docs(kimi): qualify HighSpeed plan claim * fix(kimi): preserve documented K3 effort levels * fix(kimi): preserve disabled thinking for K3 * fix(kimi): avoid disabled-thinking fallback * fix(compact): honor route runtime output limits * fix(rebase): preserve transport compression routing * fix(kimi): preserve route defaults and runtime controls * fix(kimi): retain selected runtime limits for overrides
settings.outputStyle is a free-form z.string() with no enum, and the style maps are plain object literals, so the name reached a bare index and resolved inherited Object.prototype members. The trailing '?? null' does not neutralize that — the Object constructor is not nullish — so the documented 'unknown style falls back to the default' contract was skipped and a function was handed on as if it were a config. With outputStyle set to 'constructor' the model's system prompt gained # Output Style: Object undefined and the output_style attachment announced 'Object output style is active'. getSimpleIntroSection also branches on the config being non-null, so it told the model to follow an output style that does not exist. Route both lookups through a shared resolveOutputStyle helper gated on Object.hasOwn.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…itlawb#1982) * fix(bridge): truncate derived session titles on grapheme boundaries deriveTitle cut the title with flat.slice(0, TITLE_MAX_LEN - 1), a UTF-16 code-unit slice. When an emoji or astral-plane character in the user's first message straddles the cut, the slice keeps its high surrogate and drops the low one, leaving a lone surrogate. The title is PATCHed to the claude.ai backend and UTF-8-serialized, so that lone surrogate is transmitted as the U+FFFD replacement character and the remote/mobile session list shows mojibake. Route through truncateToWidth, the grapheme-safe helper deriveSessionTitle in bridgeMain.ts already uses for the identical purpose. * test(bridge): drop lookbehind from the lone-surrogate check The source regex in initReplBridge.ts avoids lookbehinds to stay within YARR/JSC (the engine Bun uses); mirror that in the test by matching an unpaired low surrogate with a leading non-high-surrogate alternation instead of a negative lookbehind. * fix(bridge): bound the derived title in characters, not display width TITLE_MAX_LEN caps the session-title API field in characters, but truncateToWidth measures terminal columns. That charged 2 columns per wide glyph, so 30 CJK characters — well inside the 50-char field — were cut to 24 plus an ellipsis, while zero-width graphemes cost 0 columns and removed the cap entirely (100,000 code units passed through as a title). Walk graphemes and accumulate against the code-unit length instead. That keeps the surrogate pair and any combining marks intact, which is what the original raw slice broke, while still enforcing the documented character bound.
* test(openai-shim): mark extraction ownership seams * test(openai-shim): stabilize independent extraction seams * test(openai-shim): anchor façade extraction boundaries * refactor(openai-shim): anchor shared tool-call sequence * test(openai-shim): anchor JSON fallback ownership * test(openai-shim): anchor provider and message seams * test(openai-shim): add stream extraction seams * test(openai-shim): isolate stream normalization ownership * test(openai-shim): isolate schema ownership seam * test(openai-shim): stabilize executor extraction seams * test(openai-shim): isolate split executor seams * refactor(openai-shim): share executor planner retry state * refactor(openai-shim): extract stream control * fix(openai-shim): preserve final SSE frame at EOF * test(openai-shim): cover extraction seams * fix(openai-shim): cancel SSE source after done
On Windows, writing a file directly at a drive root (e.g. writing
content to D:\foo via the Write tool) fails with EPERM: dirname('D:\foo')
is 'D:\' itself, and mkdir('D:\', { recursive: true }) always fails —
the kernel cannot create a root that already exists, and libuv maps
that to EPERM rather than EEXIST. The error propagated to the Write
tool as a spurious 'permission denied' on mkdir D:\.
Treat EPERM like EACCES in NodeFsOperations.mkdir/mkdirSync: swallow it
only when the directory already exists. Genuine permission failures
still propagate — the existsSync guard limits the no-op to cases where
there was nothing to create, and a present-but-unwritable directory
still fails at the subsequent file write.
The regression tests use spyOn + mock.restore() rather than
mock.module(): module mocks are process-global and leak across test
files in the same bun process (neither mock.restore() nor
re-registering the real module clears them in bun 1.3.x).
* test(openai-shim): mark extraction ownership seams * refactor(openai-shim): share executor planner retry state * test(openai-shim): add stable extraction seams * refactor(openai-shim): extract provider compatibility * fix(openai-shim): honor disabled NIM thinking
main (v0.25.0) into sync branch and preserve fork customizations
Edwardmaster7
left a comment
There was a problem hiding this comment.
o build falhou. Investigue e mitigue esse erro.
eduardo.batista@MacBook-Air-de-Eduardo-Batista openclaude % bun run build
$ bun run scripts/build.ts
🔇 no-telemetry: stubbed 10 modules
🔄 feature-flags: transformed 195 files during bundling
33 | checkPlanModePermissions,
^
error: No matching export in "src/utils/permissions/permissions.ts" for import "checkPlanModePermissions"
at /Users/eduardo.batista/Code/openclaude/src/services/tools/toolHooks.ts:33:3
36 | samePermissionAskConstraint,
^
error: No matching export in "src/utils/permissions/permissions.ts" for import "samePermissionAskConstraint"
at /Users/eduardo.batista/Code/openclaude/src/services/tools/toolHooks.ts:36:3
Bun v1.3.12 (macOS arm64)
error: script "build" exited with code 1
…ding, depth limiting, and prioritized header retention
… to global settings
Summary
Upstream sync branch was reconciled with
Gitlawb/openclaude@main(targeting 0.25.0) and merge conflicts were resolved with priority on fork-specific surfaces (Buddy/gamification, feedback-learning, Gemini custom paths, Stoneage plugin, web frontend, ads/memory-sync commands, dontAsk permission flow, startup/runtime customizations).Conflict hotspots (
package.json,src/commands.ts, API/model/permission/runtime touchpoints) were manually reconciled to keep fork behavior while absorbing upstream structure and dependency updates.Merge integration
upstreamremote, fetched full history, and merged upstreammainwith--no-ff.Fork feature preservation
Example of key reconciliation
Impact
Testing
bun run buildbun run smokebun run checkNotes
bun run typecheck,bun run smoke, targeted buddy/permissions/api tests) must be completed before merge tomain.Original prompt