Skip to content

Merge upstream main (v0.25.0) into sync branch and preserve fork customizations#17

Draft
Edwardmaster7 with Copilot wants to merge 213 commits into
upstream-sync/openclaude-0.25.0from
copilot/upstream-syncopenclaude-0250
Draft

Merge upstream main (v0.25.0) into sync branch and preserve fork customizations#17
Edwardmaster7 with Copilot wants to merge 213 commits into
upstream-sync/openclaude-0.25.0from
copilot/upstream-syncopenclaude-0250

Conversation

Copilot AI commented Jul 22, 2026

Copy link
Copy Markdown

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

    • Added upstream remote, fetched full history, and merged upstream main with --no-ff.
    • Resolved conflicted files by preserving fork-owned modules and custom command paths.
  • Fork feature preservation

    • Kept customized Buddy, feedback-learning, Gemini caching/OAuth/model, Stoneage, web, ads, memory-sync-setup, and dontAsk permission codepaths intact during conflict resolution.
    • Preserved fork-specific scripts/config intent in package-level and command registry reconciliation.
  • Example of key reconciliation

    // src/commands.ts (kept both fork + upstream command surfaces)
    import memorySyncSetup from './commands/memory-sync-setup/index.js'
    import repomap from './commands/repomap/index.js'

Impact

  • user-facing impact:
    • Sync branch now includes upstream 0.25.0-era changes while retaining fork features required for Eduardo’s review/squash flow.
  • developer/maintainer impact:
    • Large upstream delta is integrated behind one merge commit path, with conflict resolution concentrated in known high-risk files for follow-up verification.

Testing

  • bun run build
  • bun run smoke
  • bun run check
  • focused tests: not completed in-session (quota exhausted before full validation pass)

Notes

  • provider/model path tested:
    • Merge/conflict reconciliation only; no end-to-end provider path validation completed in-session.
  • screenshots attached (if UI changed):
    • N/A
  • follow-up work or known limitations:
    • Full post-merge validation (bun run typecheck, bun run smoke, targeted buddy/permissions/api tests) must be completed before merge to main.
Original prompt

Contexto

Este repositório (Edwardmaster7/openclaude) é um fork de Gitlawb/openclaude. O fork está atualmente na versão 0.19.0. O upstream (Gitlawb/openclaude) está na versão 0.25.0, com 207 commits exclusivos que precisam ser incorporados.

A branch upstream-sync/openclaude-0.25.0 foi criada a partir do main atual do fork. O objetivo é reconciliar essa branch com o main do upstream (https://github.com/Gitlawb/openclaude) preservando integralmente todas as features customizadas do fork.

Features customizadas do fork a preservar OBRIGATORIAMENTE

1. Buddy/Gamificação (src/buddy/)

  • Todos os módulos: achievements.ts, easter-eggs.ts, evolution.ts, journal.ts, memory.ts, mood.ts, progression.ts, quests.ts, reminders.ts, seasonal.ts, shop.ts, skills.ts, streak.ts, xp-loss.ts
  • Modificações em: companion.ts, observer.ts, prompt.ts, sprites.ts, types.ts, useBuddyNotification.tsx, CompanionSprite.tsx
  • Comandos: src/commands/buddy/buddy.tsx, src/commands/buddy/index.ts
  • Todos os testes em src/buddy/*.test.ts

2. Feedback Learning (src/commands/feedback/, src/memdir/, src/hooks/)

  • src/commands/feedback/feedback.ts e index.ts
  • src/hooks/feedbackHook.ts
  • src/memdir/feedbackLog.ts, feedbackScoring.ts, feedbackSynthesizer.ts
  • Modificações em findRelevantMemories.ts e memoryScan.ts

3. Gemini Context Caching e Modelos (src/services/api/, src/integrations/)

  • Gemini 3.5 Flash e Gemini 3.6 Flash: suporte a modelos e custo
  • Gemini context caching com TTL configurável, persistência em disco, retry sem cache
  • src/services/api/geminiOAuth.ts, geminiOAuthShared.ts
  • src/components/useGeminiOAuthFlow.ts
  • Modificações em openaiShim.ts, cacheMetrics.ts, client.ts, claude.ts
  • Modificações em src/integrations/brands/gemini.ts, models/gemini.ts, vendors/gemini.ts

4. Plugin Stoneage (plugins/stoneage/)

  • plugins/stoneage/.claude-plugin/plugin.json
  • Todas as 14 skills em plugins/stoneage/skills/
  • plugins/stoneage/src/hooks/stoneage-config.js
  • setup-stoneage.sh

5. Frontend Web (web/)

  • Todos os arquivos novos em web/src/ (App.tsx, ChatView.tsx, MarkdownRenderer.tsx, MessageBubble.tsx, MessageInput.tsx, Sidebar.tsx, StatusBadge.tsx, ToolCallDisplay.tsx, etc.)
  • web/src/connection/, web/src/hooks/, web/src/pages/, web/src/stores/, web/src/types/
  • web/index.html, web/postcss.config.js, web/tailwind.config.js, web/vite.config.ts

6. Comandos novos

  • src/commands/ads.tsx e ads.test.ts (comando /ads e sistema de dicas patrocinadas)
  • src/commands/memory-sync-setup/index.ts e memorySyncSetup.tsx
  • src/services/ads.ts
  • src/services/tips/gitlawbEarn.ts e gitlawbEarn.test.ts

7. Permissões dontAsk

  • Modo dontAsk para autoaprovação de ferramentas: src/utils/permissions/getNextPermissionMode.ts e permissions.ts
  • Propagação de sticky tool permission modes para agentes filhos: src/tools/AgentTool/runAgent.ts, src/utils/swarm/
  • src/tools/BashTool/bashCommandValidation.ts e bashCommandValidation.test.ts

8. Tooling e Segurança de Desenvolvimento

  • .pre-commit-config.yaml
  • .gitguardian.yaml
  • .trunk/ (trunk.yaml + configs)
  • .claudeignore
  • .vscode/settings.json

9. Melhorias de Startup/Runtime

  • src/components/StartupScreen.ts: refatoração de printStartupScreen para getStartupScreenText + printStartupScreen
  • src/entrypoints/cli.tsx: condicional isFullscreenEnvEnabled() para evitar flicker
  • Fixes de travamento de startup (v0.17.0)

10. Melhorias de memdir e provider isolation

  • src/memdir/feedbackLog.ts, feedbackScoring.ts, feedbackSynthesizer.ts
  • feat: terminal-session provider isolation e configurable save scope
  • src/integrations/gateways/gitlawb-opengateway.ts: correção de auth Bearer

Estratégia de merge

  1. Adicionar https://github.com/Gitlawb/openclaude como remote upstream
  2. Fazer git fetch upstream
  3. No contexto da branch upstream-sync/openclaude-0.25.0, aplicar git merge upstream/main --no-ff ou rebase estratégico
  4. Resolver qualquer conflito priorizando SEMPRE as features customizadas listadas acima
  5. Em arquivos críticos como src/services/api/openaiShim.ts, src/utils/permissions/permissions.ts, src/tools/AgentTool/runAgent.ts: reconciliar manualmente, preservando o comportamento customizado do fork e absorvendo a lógica nova do upstream
  6. Garantir que package.json atualize para versão compatível (baseada no 0.25.0 do upstream) mas preserve scripts customizados do fork como prepare: pre-commit install || true
  7. Ao final: bun install && bun run typecheck && bun run smoke devem passar

Resultado esperado

Um PR que contém o merge do upstream v0.25.0 dentro da branch upstream-sync/openclaude-0.25.0, com todas as features customizadas preservadas, pronto para Eduardo revisar e fazer squash-merge na sua main.

0xfandom and others added 30 commits June 23, 2026 07:50
…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.
jatmn and others added 17 commits July 19, 2026 09:24
…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
Copilot AI changed the title [WIP] Sync upstream with fork while preserving custom features Merge upstream main (v0.25.0) into sync branch and preserve fork customizations Jul 22, 2026
Copilot AI requested a review from Edwardmaster7 July 22, 2026 16:25

@Edwardmaster7 Edwardmaster7 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.