Commit Graph
596 Commits
Author SHA1 Message Date
Anandan 382b738537 Keep Codex OAuth provider active after onboarding (#1112)
Codex OAuth first-run setup saved the new provider profile without making it the active provider profile, while still switching the current process environment. That left the current session and next startup disagreeing about which provider should be used.

This makes the saved OAuth profile active unless it is already active, preserving the existing current-session activation and credential persistence flow.

Constraint: First-run setup intentionally creates the profile with makeActive: false before OAuth credentials are persisted.

Rejected: Only update the current session env | next startup would still use the previous active provider.

Confidence: high

Scope-risk: narrow

Directive: Codex OAuth onboarding must keep activeProviderProfileId, stored credential profileId, and current-session provider env aligned.

Tested: bun test src/components/ProviderManager.test.tsx

Tested: git diff --check

Not-tested: repo-wide bun run typecheck, existing unrelated missing-module/type errors block it
2026-05-13 10:58:09 +08:00
RodrigoandClaude Sonnet 4.6 4a98a4a227 fix(bashPermissions): block command substitution in array subscript position (#1111)
stripAllLeadingEnvVars used [^\]]* for the array subscript component of the
ENV_VAR_PATTERN, which accepted $(cmd), ${var}, and backtick expressions.
Bash evaluates subscripts during assignment parsing, so a command like
FOO[$(denied_cmd)]=val harmless executed the denied command as a side effect
while the deny-rule check only saw `harmless`.

Changed subscript character class to [^\]$`{(]* to exclude the characters
that introduce all bash expansion forms:
  - $ blocks $(cmd), ${var}, $((expr))
  - ` blocks backtick command substitution
  - { and ( are belt-and-suspenders guards

Safe numeric and identifier subscripts (FOO[0], FOO[idx]) continue to match.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:52:15 +08:00
JATMN cf33f03755 fix: hide missing-module slash command stubs (#1136)
Prevent generated missing-module noop functions from entering the built-in command registry.

Add a runtime isCommand guard in src/types/command.ts and apply it when building the COMMANDS() list so bare noopN tree-shaking stubs are excluded before they can appear in slash-command autocomplete.

Add focused tests covering rejection of noop-style stubs and acceptance of valid command objects.

Refs Gitlawb/openclaude#1132.
2026-05-13 08:53:01 +08:00
TechBrewBoss f34650a10c Migrate legacy config home to .openclaude (#1122) 2026-05-12 21:59:12 +08:00
7ea74f29f0 fix(codex): normalize empty MCP object schemas (#1121)
* fix(codex): default missing 'type' on MCP tool properties to avoid 400 (#1114)

MCP tools sometimes register properties without an explicit `type` (e.g. a
generic `value` field intended to accept any JSON). Codex Responses strict
mode then rejects the tool registration with
`schema must have a 'type' key`. Add `ensureSchemaType()` to infer a type
from sibling keys (`properties` -> object, `items` -> array, `enum`/`const`
-> value type) and fall back to `string` for fully empty nodes.
Combinator-only schemas (`anyOf`/`oneOf`/`allOf`) are left alone so their
branches keep their semantics.

Fixes #1114

* fix(codex): normalize empty MCP object schemas

Ensure Codex strict tool schemas include an explicit empty properties object when MCP tools provide required keys without properties.

Co-Authored-By: OpenClaude (gpt-5.5) <openclaude@gitlawb.com>

---------

Co-authored-by: gnanam1990 <gnanasekaran.sekareee@gmail.com>
Co-authored-by: OpenClaude (gpt-5.5) <openclaude@gitlawb.com>
2026-05-12 14:16:06 +08:00
3kin0x e12432eaf6 feat: implement high-performance SQLite storage layer with JSON audit log (Phase 2 Masterpiece) (#1106) 2026-05-12 01:56:19 +08:00
Kevin CodexandOpenClaude f9621ab575 feat(provider): add Venice official provider (#1109)
Adds Venice as a descriptor-backed OpenAI-compatible provider with VENICE_API_KEY support, provider UI metadata, startup persistence, and regression coverage.

Co-authored-by: OpenClaude (gpt-5.5) <openclaude@gitlawb.com>
2026-05-11 22:06:09 +08:00
Nik 0c88defbe0 fix(bashSecurity): tighten fc -e detection to avoid long-flag false positives (#1107)
Closes #1051 (BUG-01).

The regex `/\s-\S*e/` in `validateZshDangerousCommands` only required
`e` to appear *somewhere* after `-`, so any flag that happened to
contain `e` — `-reset`, `-reverse`, `-message`, etc. — tripped the
"dangerous fc" path and surfaced an interactive permission prompt to
the user even though those flags do not invoke an editor and have
nothing to do with the `fc -e <editor>` eval vector the check is
trying to catch.

Replace with `/\s-[a-zA-Z]{0,3}e(?:\s|$)/` so:

  * `e` must be the last letter in the flag bundle (followed by
    whitespace or end-of-string), not anywhere inside it
  * the bundle is capped at 4 chars total, matching the shape of
    real POSIX `fc` short-flag bundles (`-e`, `-le`, `-lne`)

The bundle cap is what distinguishes us from the issue's initial
suggested fix `/\s-[a-zA-Z]*e(?:\s|$)/`, which still false-positives
on `-reverse` and `-message` because both end in `e` and the unbounded
`*` swallows the entire word.

New regression tests in `bashSecurity.test.ts` cover the real-flag
positive cases (`-e`, `-le`, `-lne`) and the long-flag negative cases
called out in the bug report (`-reset`, `-reverse`, `-message`), plus
`-l` to confirm the safe list flag still passes through.
2026-05-11 19:54:36 +08:00
github-actions[bot] 7166400660 chore(main): release 0.10.0 (#1039)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
v0.10.0
2026-05-11 08:13:50 +08:00
RodrigoandClaude Sonnet 4.6 ebc9c70bb5 fix(bashSecurity): reject nested heredoc ranges in stripSafeHeredocSubstitutions (#1050)
* fix(bashSecurity): reject nested heredoc ranges in stripSafeHeredocSubstitutions

isSafeHeredoc already rejects nested $(cat <<'A'...A) matches to prevent
stale-index corruption when stripping in reverse order. Apply the same guard
to stripSafeHeredocSubstitutions, which was missing it.

Without the check, a nested inner range stripped first leaves outer.end stale.
result.slice(outer.end) then skips any trailing content (e.g., `; rm -rf /`),
silently hiding it from downstream validators.

Fix: return null on nested ranges so callers fall back to full command validation.

* test(bashSecurity): add regression tests for stripSafeHeredocSubstitutions

Covers: single heredoc strip, nested heredoc null-return (stale-index
regression), no heredoc present, and multiple non-nested heredocs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 08:07:40 +08:00
0xfandom 20bc6aec21 fix(openai-shim): redact ?auth=, ?passwd=, ?pwd= in diagnostic URLs (#1070)
* refactor(urlRedaction): export shouldRedactUrlQueryParam as the single source of truth

The credential-param-name list lives in `src/utils/urlRedaction.ts` and
is the canonical coverage used by `redactUrlForDisplay`. A second copy
in `src/services/api/openaiShim.ts` had drifted, dropping `passwd`,
`pwd`, `auth`, and `apikey` — see follow-up commit replacing the shim
copy with this export.

Tests pin the four regression cases (`?passwd=`, `?pwd=`, `?auth=`,
`?apikey=`) and assert the helper accepts the same set across both
underscore and dash separators, so any future fork trips the test
instead of silently re-introducing the leak.

Refs #1069

* fix(openai-shim): use shared shouldRedactUrlQueryParam to redact ?auth/?passwd/?pwd in diagnostics

The shim's local `SENSITIVE_URL_QUERY_PARAM_NAMES` (10 entries) had
drifted from the canonical 14-entry list in `urlRedaction.ts`. Three
of the missing names — `passwd`, `pwd`, `auth` — are not substrings
of any shim-list token, so `redactUrlForDiagnostics` was emitting
them verbatim into the self-heal retry log, transport-error log,
HTTP-error log, and toolless retry log.

Replace the local copy with an import from the canonical module so
both code paths stay in lock-step. The post-redaction
`redactSecretValueForDisplay` env-secret pass is preserved — it's
the second layer that catches credentials hardcoded directly into
the URL string when the param name itself is non-obvious.

Closes #1069
2026-05-11 08:01:47 +08:00
0xfandom 4fad5d25da perf(local): add OPENCLAUDE_LOCAL_FAST_PATH to skip cloud-only transforms (#1068)
* feat(providerConfig): add OPENCLAUDE_LOCAL_FAST_PATH opt-out for local backends

Local OpenAI-compatible endpoints (llama.cpp, vLLM, Ollama, LM Studio,
…) do not implement the cloud-side caching/strict-validation behaviours
that several pre-send transforms target — byte-stable serialization
hashes nothing, strict tool-schema rewrites are accepted as-is, and
tool-result tiering compresses against contexts that already live in
local RAM.

`getLocalFastPathConfig(baseUrl)` returns a flag bundle so callers can
skip those transforms uniformly. By default the helper auto-detects via
the existing `isLocalProviderUrl` (loopback / RFC1918 / .local / ULA).
`OPENCLAUDE_LOCAL_FAST_PATH=1|0|auto` overrides the detection — useful
when a remote LAN host needs the same treatment, or when a user wants
to keep the cloud transforms even on localhost while debugging.

Tests cover auto-detect on every host class, all truthy/falsy aliases,
the explicit `env` argument override, and `auto`/empty/garbage fall-
through to detection.

Refs #1016

* perf(openai-shim): wire local fast path through compress/strict/serialize

Apply the local fast-path opt-out at the three pre-send transforms that
showed up in the issue #1016 audit of v0.5+ regressions against ~45
tok/s local backends:

  - compressToolHistory: skip the tool_result tier walk; on a single-
    user local box the conversation lives in RAM and the tier-walk is
    pure CPU per request.
  - convertTools strict mode: drop the recursive
    `additionalProperties: false` rewrite; local llama.cpp / vLLM /
    Ollama accept the original Anthropic schema unchanged.
  - request body serialization: fall back to native `JSON.stringify`
    instead of `stableStringify`; local backends do not implement
    implicit prefix caching, so the deep key-sort hashes nothing.

Behaviour for cloud providers is unchanged — every call site still
runs the full pipeline when `getLocalFastPathConfig(baseUrl).enabled`
is false. The GitHub Copilot `/responses` retry path (which is GitHub-
specific and never reachable for a local baseUrl) keeps the byte-
stable serialization to preserve the existing prefix-caching contract.

Refs #1016
2026-05-11 08:01:04 +08:00
Kevin CodexandOpenClaude dea7ef998a Feat/xai grok 4 3 default (#1080)
* feat(xai): add Grok 4.3 as default model

Add Grok 4.3 to xAI model metadata and make it the provider default while preserving explicit Grok 4 and Grok 3 selections.

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

* fix(xai): correct Grok 4.3 context window to 1M tokens

Grok 4.3 docs list 1,000,000 token context window. The PR had
incorrectly registered 2,000,000, which would allow local budgeting
to retain ~2x the supported input size before compaction triggers,
leading to provider-side failures.

- src/integrations/models/xai.ts: set contextWindow to 1_000_000
- src/utils/context.test.ts: update expectation to match

🤖 Generated with [OpenClaude](https://github.com/Gitlawb/openclaude)

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

---------

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
2026-05-11 07:57:41 +08:00
JATMNandJATMN 6655d470b3 Centralize OpenClaude display name (#1084)
* Centralize OpenClaude display name

Add PRODUCT_DISPLAY_NAME as the shared product display label and use it across prompt identity, onboarding, permission dialogs, hook/agent UI, and tool permission messages.

Update onboarding security copy to a single security note, remove the prompt injection note, and replace remaining user-facing Claude possessives with the product display name where appropriate.

* Restore plan author display name

* Restore onboarding prompt injection warning

Reinstate the two-item security notes layout that was collapsed during the product display name cleanup.

Keep the OpenClaude PRODUCT_DISPLAY_NAME branding while restoring a concise prompt-injection/trusted-code warning that does not depend on the external Claude Code docs link.

Verification: parsed src/components/Onboarding.tsx with TypeScript createSourceFile.

---------

Co-authored-by: JATMN <12479882+jatmn@users.noreply.github.com>
2026-05-11 07:57:21 +08:00
KRATOS fc8976708b fix(agents): coerce non-string whenToUse to prevent crash on save (#1086) (#1087)
* fix(agents): coerce non-string whenToUse to prevent crash on save (#1086)

The new-agent confirm step crashed with `whenToUse.replace is not a
function` when an LLM (e.g., qwen3.5:9b) returned a non-string value
for the `whenToUse` field. The downstream YAML escape in
formatAgentAsMarkdown assumed a string and threw mid-write.

Tighten the parse boundary in generateAgent to require non-empty strings
for identifier/whenToUse/systemPrompt, and add a defensive coercion in
formatAgentAsMarkdown so any future caller producing a non-string value
fails closed (writes an empty description) rather than crashing the
agent creation flow.

* fix(agents): truly fail closed for non-string whenToUse

Address review feedback on #1087: the prior `String(value ?? '')` fallback
serialized non-string values as "42", "[object Object]", etc., which
contradicted the stated fail-closed intent and silently saved garbage as
the agent description. Switch to `typeof === 'string' ? : ''` so invalid
metadata writes an empty description instead.

Tighten the regression test to assert the exact serialized `description: ""`
output for undefined/null/number/array/object inputs, rather than only
asserting "does not throw".
2026-05-11 07:54:49 +08:00
3kin0x f4436697f0 feat: make Orama the default search engine with JSON-backed (#1094)
* feat: make Orama the default search engine with robust JSON-backed hybrid architecture

* fix: return empty string for no-hit knowledge searches and add regression test
2026-05-11 07:54:01 +08:00
MikeTomlin19 9fed6ae4a0 fix: validate plugin component paths (#1096)
* fix: validate plugin component paths

Fixes Gitlawb/openclaude#1058

* fix: reject symlink escapes in plugin paths

* fix: validate auto-detected plugin directories

* fix: allow plugin root component paths

* fix: validate discovered plugin skill files

* fix: validate plugin hook file paths

* test: guard hook symlink regression on Windows
2026-05-11 07:52:28 +08:00
Firebug24k e1e277a3af fix: replace unsupported Unicode glyphs with widely available alternatives (#1088)
* fix: replace unsupported Unicode glyphs with widely available alternatives

Replace TUI characters from obscure Unicode blocks (Miscellaneous Technical,
Dentistry Symbols) that render as tofu boxes on most Linux terminal fonts,
including all Nerd Font variants.

Replacements:
- ⏵⏵ (U+23F5) → ▶▶ (U+25B6) — permission mode indicator
- ⎿ (U+23BF) → └ (U+2514) — tool output connector bracket

U+23F5 and U+23BF are not present in any common monospace or Nerd Font.
U+25B6 and U+2514 (Box Drawing) are universally supported.

Fixes rendering on Linux terminals (kitty, xterm, Ptyxis), Windows terminal
emulators (MobaXterm, PuTTY), and mosh sessions.

Related upstream issues:
- anthropics/claude-code#24102
- anthropics/claude-code#39127
- anthropics/claude-code#53080

* fix: replace remaining escaped \u23BF glyphs with \u2514

The initial patch replaced literal ⎿ characters but missed 5
escaped \u23BF forms in AgentProgressLine, SystemTextMessage,
and UserLocalCommandOutputMessage. These render to the same
unsupported U+23BF codepoint at runtime, producing tofu boxes
on terminals without CJK serif fonts.

Addresses reviewer feedback from jatmn and techbrewboss.
2026-05-10 09:04:33 +08:00
Németh Áron e66d6e285a Prevent duplicate startup plugin hooks (#1073)
* Deduplicate duplicate plugin hooks on startup

* Kill running hook processes on interrupt

* Fix asyncRewake interrupt handling and hook dedupe tests

* Reuse stable stringify for hook dedupe keys
2026-05-09 23:54:15 +08:00
3kin0x 5873bc6714 feat(knowledge): introduce local Orama persistence (feature-flagged) (#1015)
* feat(knowledge): introduce local Orama persistence (clean phase 1)

- Added @orama/orama and persistence plugin.
- Implemented optional local-only Orama backend in knowledgeGraph.ts.
- Gated Orama logic behind OPENCLAUDE_KNOWLEDGE_ORAMA=1.
- Converted knowledge and conversation arc functions to async.
- Fixed circular dependency between knowledgeGraph and sessionStorage by moving getProjectsDir to envUtils.
- Updated all call sites and tests to handle async Knowledge API.
- Verified build and tests pass on latest main.

* fix: address PR review comments for knowledge feature (async finalizeArcTurn and Orama cleanup)

* test: add comprehensive stress and edge case testing for Orama Knowledge Graph

* fix: prevent test pollution by restoring Orama env flag in stress test

* refactor: harden Knowledge architecture with concurrency locks, optimized I/O, and consolidated state
2026-05-09 22:35:37 +08:00
JATMNandJATMN 23f6a956bd Fix theme picker OpenClaude branding (#1082)
Update the startup theme picker demo patch so the added console.log line says OpenClaude instead of Claude.

Validation: bun run build; bun run smoke.

Co-authored-by: JATMN <12479882+jatmn@users.noreply.github.com>
2026-05-09 22:25:52 +08:00
TechBrewBoss ed7b6972f9 Feat: Add startup logo palette picker (#1072)
* Add startup logo palette picker

* Address logo picker review feedback
2026-05-09 00:01:53 +08:00
jackil182 41b2496101 docs: update setup guides to clarify the only available auth method for Gemini (#1064)
* Update README.md

Access token and local ADC workflow authentication is NOT available for Gemini.

* Update advanced-setup.md

API key authentication is the only available method for Gemini
2026-05-08 15:33:07 +08:00
TechBrewBoss 16726399d8 fix plan mode branding and plan path (#1062) 2026-05-08 15:30:41 +08:00
0xfandom 4830d6f778 fix(openai-shim): strip store for local providers (vLLM, custom) (#1048)
Local OpenAI-compatible servers (vLLM, llama.cpp, custom self-hosted
gateways) often validate request bodies against a strict JSON schema
and reject unknown fields with `400 Bad Request`. The shim already
sends `store: false` (an OpenAI-only field for cloud conversation
persistence) and strips it for known cloud hosts that share the same
intolerance (Gemini, Cerebras). Local servers have no notion of
remote conversation storage and fall in the same bucket.

Add `isLocal` to `shouldStripResponsesStore` so any baseUrl resolved
by `isLocalProviderUrl` (localhost / 127.0.0.1 / ::1 / 0.0.0.0) gets
the field removed. Lenient locals (Ollama) already ignored it; this
unblocks strict ones (vLLM Qwen) without behavior change for the
former.

Closes #672 (the `store: false` symptom; the separate `max_tokens`
default vs. vLLM `max_model_len` collision is a different concern).
2026-05-08 08:56:30 +08:00
ArkhAngelLifeJiggy 4b1e516fc7 feat: incremental and cached token counting (#795)
* feat: incremental and cached token counting

- Add IncrementalTokenCounter for performance (avoids recounting entire context)
- Add getCacheTokens() to extract cache read/creation tokens
- Add getNewTokensOnly() to get new tokens excluding cache
- Add getTokenBreakdown() with cache efficiency percentage
- Add comprehensive tests (10 passing)

PR 1A: Token Counting Core (Features 1.6, 1.13)

* refactor: extract IncrementalTokenCounter and tokenCache to separate files

- Move IncrementalTokenCounter to incrementalTokenCounter.ts with stats tracking
- Move cache utilities to tokenCache.ts with cost estimation and analytics
- Remove duplicate implementations from tokens.ts
- Update tokens.test.ts to import from new files
- Add comprehensive tests for both new modules

* fix: content-aware cache invalidation + high-precision costs

Blocker fixes:
- IncrementalTokenCounter: hash last message content for cache key
- tokenCache: use 4-decimal precision for cost estimates (was 3, collapsed to $0)
- exceedsBudget: use raw high-precision cost for budget comparisons

Content hash prevents stale cache on same-length edits.

* fix: PR 795 - fix cache invalidation and hash collisions

Blockers:
- getMessageHash now hashes ALL messages with full content (not just last message)
- Prevents hash collisions when edits occur outside recent window
- Incremental branch now verifies prefix hash matches before using cached count
- If earlier messages mutated, full recalculation instead of stale increment

* fix: PR 795 - fix prefix mutation + append invalidation

Blocking:
- Store both lastFullHash and lastPrefixHash separately
- Compare actual prefix hash values, not just length (which always passes)
- If prefix mutated, do full recalculation instead of stale increment

Non-blocking:
- Add regression tests for prefix mutation + append case

* fix: PR 795 - fix isApproachingLimit to use tokenBudget not cache size

Blocking:
- Rename maxCacheSize to tokenBudget in IncrementalCounterConfig
- isApproachingLimit now correctly compares against token budget (context window size)
- Not cache entry size which was meaningless
- Update CounterFactory to use appropriate tokenBudget values

* fix: PR 795 - wire IncrementalTokenCounter into tokenCountWithEstimation

- Integrate incremental counter into production token counting path
- tokenCountWithEstimation now uses IncrementalTokenCounter.getCount() for rough estimation
- Preserves exact usage baseline from last API response
- getIncrementalTokenCounter() exported for external use
- Uses lazy init to avoid circular dependency issues

* fix: PR 795 - trim unused token cache surface

- Remove tokenCache.ts (300+ line unused utility with no production caller)
- Remove tokenCache.test.ts
- Remove unused getCacheInfoFromUsage() from tokens.ts
- Remove related tests in tokens.test.ts
- PR now focused on IncrementalTokenCounter wired into tokenCountWithEstimation
2026-05-07 23:52:57 +08:00
Fernando Xavier e438c89fbc fix: resolve two bugs making interactive mode unusable with plugin ecosystems (#825) (#830)
* fix(ink): import logForDebugging in App.tsx to prevent ReferenceError

App.tsx used `logForDebugging()` in four call sites (XTVERSION async
handlers, handleReadable/handleDataChunk stdin error-recovery branches)
without importing it. When esbuild bundled this, the unresolved symbol
collided with another identifier in scope; the bundler renamed most
references to `logForDebugging2` but left the four in App.tsx pointing
at the original name, which became undefined in the bundle. At runtime
any modern terminal replying to the XTVERSION probe triggers an
`unhandledRejection: logForDebugging is not defined`.

Adding the missing import resolves the symbol before bundling, so
the bundler emits a single consistent name for every call site.

Refs #825

* fix(hooks): always close stdin after initial hook payload

The conditional `if (!requestPrompt) child.stdin.end()` kept stdin
open in interactive mode (where requestPrompt is always truthy while
the REPL is mounted). Every plugin hook written against the Anthropic
hook input contract reads stdin until EOF, so hooks blocked on the
per-hook timeout (default 60s) on every user message — no HTTP request
to the provider was made until every UserPromptSubmit hook had timed
out. With ~10 plugins hooked to UserPromptSubmit (pipeline-orchestrator,
superpowers, skill-advisor, episodic-memory, reflexion, etc.), a single
prompt accumulated minutes of wait before any model call.

Always closing stdin after the initial JSON payload restores the
documented EOF-based contract. Verified locally: typical `oi`
turnaround drops from ~60s to ~1s.

Trade-off: the existing duplex-stdin path (hooks emitting prompt
requests on stdout and receiving responses written back to stdin at
hooks.ts:1237) is incompatible with an EOF contract by design and
stops working with this change. Restoring that feature requires a
separate IPC channel (named pipe, node IPC, or a second stream)
rather than reusing the initial stdin; that refactor is out of scope
for this fix. Given the blast radius of the current behaviour (every
user with a plugin ecosystem sees unusable interactive mode), trading
the rarely-used duplex path for the documented single-shot contract
is the right short-term move.

Closes #825
2026-05-07 23:49:31 +08:00
0xfandom 7cfc8d5dad feat(cli): honor --model alone without requiring --provider (#854)
Closes #808. Today `openclaude --model <name>` is parsed by Commander
inside main.tsx but the startup banner and any provider-env-reading
code run first, so the override is invisible until request time and
saved-profile users see their stale model in the banner.

Add applyModelFlagFromArgs that runs after saved-profile env
application and before the banner. It routes the value to the env var
matching the already-active provider (OPENAI_MODEL / GEMINI_MODEL /
MISTRAL_MODEL / ANTHROPIC_MODEL) so the banner, resolution, and
request payload all agree. Skipped when --provider is also present;
that path is still handled by applyProviderFlagFromArgs.

No writes to .openclaude-profile.json — override is process-scoped.
2026-05-07 23:48:19 +08:00
Dan Nakhla 402cd3dbe8 feat(websearch): add first-class Brave adapter; fix Google + Brave presets; restore Exa snippets (#1044)
This PR addresses three real bugs in WebSearch's provider layer plus adds Brave
as a first-class adapter so users with a BRAVE_API_KEY get auto-detection +
auto-chain inclusion (matching the ergonomics of TAVILY_API_KEY, EXA_API_KEY,
etc.).

## 1. New: Brave first-class adapter

`providers/brave.ts` — auto-detects `BRAVE_API_KEY`, slots into the auto chain
between Jina and Bing. Sends the bare token in `X-Subscription-Token` per
Brave's API contract. Mirrors the structure of `tavily.ts` / `bing.ts`.

Brave runs an independent web index (~30B pages), making it a useful
non-Google, non-Bing fallback. Bing's hosted API was sunsetted in Aug 2025
for new users, so Brave is a more practical default fallback in 2026.

## 2. Bug fix: Brave preset sent malformed auth header

The `WEB_PROVIDER=brave` preset in `custom.ts` declared
`authHeader: 'X-Subscription-Token'` but no `authScheme`, so the default
`'Bearer'` scheme prefix kicked in, producing:

    X-Subscription-Token: Bearer <token>      ← wrong, returns 401

Brave's API expects:

    X-Subscription-Token: <token>              ← bare token, no scheme

Fix: declare `authScheme: ''` on the preset; update
`buildAuthHeadersForPreset` to emit a bare token (no leading space) when the
scheme is empty.

## 3. Bug fix: Google preset never worked

`WEB_PROVIDER=google` was wired with `Authorization: Bearer <key>`, but the
Google Custom Search JSON API does not support Bearer auth. It requires:

  - `?key=<API_KEY>`  as a query param
  - `?cx=<ENGINE_ID>` as a query param (Programmable Search Engine ID)

The preset previously had no slot for the engine ID at all, so any user
trying `WEB_PROVIDER=google` hit a 400/401 immediately.

Fix: extend `ProviderPreset` with two minimal fields — `authQueryParam` (key
goes in URL, not header) and `envQueryParams` (additional URL params sourced
from env vars). Rewire the `google` preset to use them; reading
`GOOGLE_CSE_ID` for `cx`.

A clear error fires fast if either `WEB_KEY` or `GOOGLE_CSE_ID` is missing,
instead of silently producing a 400 from upstream.

> Note: Google has announced the Custom Search JSON API will be discontinued
> on 2027-01-01 and is closed to new customers. The fix unbreaks existing
> users for the remaining ~8 months; the README includes a sunset notice and
> recommends Brave/Tavily/Exa for new setups.

## 4. Bug fix: Exa results had empty descriptions

`providers/exa.ts` never passed `contents` in the request body. Per the Exa
docs (https://docs.exa.ai/reference/search-api-guide-for-coding-agents):

  > Use `highlights` for agent workflows. Highlights return 10x fewer
  > tokens with the most relevant excerpts.

Without `contents: { highlights: true }`, the Exa response includes only
`{title, url, id, ...}` — no `text`, no `highlights`, no `summary`. The
adapter was then mapping `r.snippet ?? r.text` (neither field exists in the
default response shape), so every Exa hit came back with
`description: undefined`. Tavily/Brave/DDG all return snippets — Exa was
silently degraded.

Fix: request `contents: { highlights: true }` in the body, and map
`results[].highlights[]` (an array of strings) into the description by
joining up to 3 excerpts with ` … `. Falls back to `text` when present, then
`undefined` if neither field is populated.

## Tests

105 / 105 pass in `src/tools/WebSearchTool/providers/`:

  - `providers/brave.test.ts`         (new) — 7 tests: auth header, mapping,
                                              domain filters, error paths
  - `providers/exa.test.ts`           (new) — 9 tests: contents request shape,
                                              highlights mapping, fallback
                                              chain, error paths
  - `providers/custom.test.ts`        (extended) — 6 new tests covering
                                              `authScheme: ''` (Brave preset),
                                              `authQueryParam` suppression
                                              (Google preset), GOOGLE_CSE_ID +
                                              WEB_KEY fail-fast errors, full
                                              request shape via mocked fetch

205 / 205 pass across `src/tools/`. tsc clean for changed files.

## Docs

  - `README_SEARCH_PROVIDERS.md` — promotes `BRAVE_API_KEY` to first-class,
    documents `GOOGLE_CSE_ID` + sunset notice, fixes the provider table,
    updates the auto-chain priority list and mode list
  - `.env.example` — adds `BRAVE_API_KEY` line, documents `GOOGLE_CSE_ID`
    requirement + sunset notice, updates auto-chain priority comment

## Migration / behavior changes

  - `WEB_PROVIDER=google` users must now set `GOOGLE_CSE_ID` (was previously
    unable to function at all, so this is a strict improvement).
  - `WEB_PROVIDER=brave` users with `WEB_AUTH_SCHEME=""` workarounds can
    drop the workaround — the preset now emits a bare token by default.
  - Brave joins the auto-chain priority order
    (firecrawl → tavily → exa → you → jina → **brave** → bing → mojeek →
    linkup → ddg).
2026-05-07 21:31:56 +08:00
0xfandom 0adf97dc14 fix(openai-shim): strip store when baseUrl points at Cerebras (#1040)
Cerebras Cloud's chat-completions endpoint rejects requests with a
`store` field — `400 store: property 'store' is unsupported`. The
shim already strips `store` for Gemini hosts via `hasGeminiApiHost`;
add the symmetric host check for Cerebras so users hitting
`api.cerebras.ai` (directly or via the Custom provider preset) don't
hit the same wall.

Closes #1023.
2026-05-07 15:01:46 +08:00
0xfandom feb5791320 fix(effort): persist xhigh and send reasoning_effort on chat_completions (#857)
* fix(effort): persist xhigh and send reasoning_effort on chat_completions

Fixes #853.

Persistence:
- EffortPicker.handleSelect normalizes the OpenAI-shaped `xhigh` to the
  standard `max` before writing AppState/settings. Previously `xhigh` fell
  through `toPersistableEffort` as undefined and the setting reverted on
  reload.
- `/effort xhigh` takes the same normalization path.
- `toPersistableEffort` maps `xhigh` -> `max` as a defensive backstop.
- EffortPicker initialFocus reflects the user's stored selection
  (`max` shown as `xhigh`) instead of always snapping to the alias default.

Payload:
- openaiShim chat_completions body now emits `reasoning_effort` from
  `request.reasoning.effort`. Previously only codex_responses transport
  forwarded it, so Custom API users on chat_completions got no effort.
- `getAnthropicClient` accepts `effortValue` and forwards it as
  `reasoningEffort` to `createOpenAIShimClient` (both providerOverride
  and direct-OpenAI paths) after converting `max` -> `xhigh` at the
  OpenAI boundary via `standardEffortToOpenAI`.
- `claude.ts` threads the resolved effort through main streaming and
  both non-streaming fallback call sites.

Tests:
- openaiShim.test.ts: reasoning_effort emitted when override is passed,
  omitted otherwise, falls back to codex alias default.
- effort.codex.test.ts: xhigh -> max normalization and
  standard<->OpenAI conversion.

* fix(effort): skip max→high clamp for OpenAI/Codex models

resolveAppliedEffort() unconditionally downgraded any non-Opus 'max'
to 'high'. OpenAI/Codex models use 'max' as the standard internal form
of 'xhigh' (the client shim converts on the wire), so the clamp was
silently turning xhigh selections into high.

The picker stored xhigh→max correctly and the shim emitted
reasoning_effort, but the resolver in between rewrote max→high before
the shim ever saw it. End result: UI showed xhigh, request sent high.

Skip the clamp when modelUsesOpenAIEffort(model). Anthropic non-Opus
clamp behavior unchanged.

Adds two e2e tests in effort.codex.test.ts walking the full chain
(persist → resolve → wire) so the regression can't recur silently.

* test(effort): keep mocked module surfaces compatible with downstream tests

bun:test's `mock.module()` is process-global and is not undone by
`mock.restore()` (see comment in user.test.ts). When this file mocked
`./auth.js`, `./thinking.js`, `../services/api/providerConfig.js`, etc.
with reduced surfaces, later test files that imported the missing
exports (refreshAndGetAwsCredentials, getRainbowColor, ...) crashed at
module load with `SyntaxError: Export named 'X' not found`.

Spread the real module exports into each mock factory so subsequent
imports keep the full surface, while the targeted overrides for this
file's tests still take effect.
2026-05-06 21:17:44 +08:00
Dolph Prefect 6af709e65e fix(agent): ensure main agent waits for subagent completion (#1032)
Updated the tool result instructions for async launched agents to explicitly
command the model to end its turn. This prevents the main agent from
prematurely continuing and duplicating work already delegated to a subagent,
reducing redundant costs and improving orchestration reliability.
2026-05-06 10:54:23 +08:00
github-actions[bot] 280a7c14be chore(main): release 0.9.2 (#1038)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
v0.9.2
2026-05-06 10:43:34 +08:00
Paweł c873725d90 fix(cli): replace createRequire with static import for teammate.js (#1026) (#1033)
In commit 1f66d32, require() was changed to createRequire(). While this
likely fixed an ESM warning in tests, createRequire bypasses the Bun bundler.
As a result, teammate.js was no longer inlined into dist/cli.mjs.

At runtime in the published npm package, this resolves to <root>/utils/teammate.js,
but utils/ is not included in the npm files array, causing a crash on startup.

Replaced with a static import. The original comment regarding a circular
dependency is stale, as teammate.ts only relies on an import type from
AppState.ts which gets erased at compile time.
2026-05-06 10:40:42 +08:00
github-actions[bot] 00263c5a0e chore(main): release 0.9.1 (#1022)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
v0.9.1
2026-05-05 18:41:53 +08:00
Vasanth Tandroot 094f04c803 fix(theme): remove stale memo wrappers from theme context hooks (#534)
* fix(theme): remove stale React Compiler memo wrappers from theme hooks

Rebase on current main (includes #589 reconciler fix).

The React Compiler memo caches (_c) in useTheme() and usePreviewTheme()
use referential equality checks on destructured context values. These
caches can return stale references when the ThemeProvider's useMemo
recreates the context value object but the individual property
references (setThemeSetting, setPreviewTheme, etc.) compare equal —
the memo short-circuits and returns a cached tuple/object that still
holds the old closure captures.

This is a distinct bug from #589 (which fixed the ink reconciler's
commitUpdate path for host prop updates). #589 ensures that when
React _does_ re-render a component with new props, those props actually
reach the DOM node. But the memo wrappers here prevent React from
_even seeing_ the new context value in the first place — the hook
returns the stale cached result.

Removing the memo wrappers ensures useTheme() and usePreviewTheme()
always read the current context value, eliminating the stale-reference
path entirely.

* test(theme): add regression tests for useTheme()/usePreviewTheme() stale-value bug

These tests verify that context hooks always return fresh values after
ThemeProvider re-renders, even when React Compiler memo caches are in play.

- useTheme() must reflect currentTheme changes immediately after
  setThemeSetting is called (not return a stale cached tuple).
- usePreviewTheme() must return functional actions after context
  re-renders (not stale closures from before the theme change).

On current main (with _c memo wrappers), these tests expose the bug:
the memo cache compares setThemeSetting by reference (stable across
renders via useMemo) and short-circuits, returning the old cached result
with stale currentTheme.

* fix(test): correct import paths for ThemeProvider.test.tsx

Fix relative paths for ink.js, KeybindingSetup, AppStateProvider,
useStdin mock, systemTheme mock, and config mock to account for
the test file being in src/components/design-system/ rather than
src/components/.

* fix(test): rewrite ThemeProvider tests using Ink renderer

Use Ink's createRoot instead of react-dom/client, matching the pattern
from ThemePicker.test.tsx. The tests now render through Ink's terminal
renderer and check frame output for theme values, which is the same
environment ThemeProvider actually runs in.

* fix(test): correct all relative import paths for design-system/ depth

- ink.js, KeybindingSetup, AppStateProvider: ../ → ../../
- StructuredDiff: same pattern as ThemePicker test adjusted for depth

---------

Co-authored-by: root <root@vm7508.lumadock.com>
2026-05-05 18:38:48 +08:00
Kevin Codex d19f4d335d fix flaky test (#1021) 2026-05-05 18:33:55 +08:00
github-actions[bot] 9994b50b0a chore(main): release 0.9.0 (#978)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
v0.9.0
2026-05-05 18:20:10 +08:00
Kevin CodexandOpenClaude 7b02695b15 Feat/codex default provider (#1014)
* chore: add .openclaude/ to gitignore

The .openclaude/ directory contains auto-generated project-local files
(wiki pages, convention cache, local settings) that should not be
committed to the repository.

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

* feat: make Codex + GPT 5.5 the default provider and model

Changes the default provider to Codex and default model to GPT 5.5:

- package.json: dev script now uses provider-launch.ts codex
- providerRecommendation.ts: getGoalDefaultOpenAIModel returns gpt-5.5
  for coding and balanced goals (was gpt-4o)
- providerConfig.ts: fallback model changed from gpt-4o to codexplan
  (resolves to gpt-5.5)
- ProviderManager.tsx: Codex OAuth option now shows green
  "★ Recommended" badge in the provider picker

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

* fix: replace Box with nested Text in Codex label

Ink's <Text> component cannot contain <Box>. The label is rendered
inside a <Text> parent, so use nested <Text> elements instead.

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

* fix: default to Codex when no provider profile is saved

When no persisted provider profile exists (fresh install / first run),
buildStartupEnvFromProfile now injects Codex + GPT 5.5 env vars instead
of returning process.env unchanged. Falls back gracefully — if Codex
credentials are available (OAuth or existing), uses those; otherwise
injects base URL and model defaults so the provider picker shows
GPT 5.5 as the default.

This closes the gap where node dist/cli.mjs (production start) would
default to firstParty (Anthropic) when no profile or env vars were set.

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

* chore: resolve stash conflict markers from accidental stash pop

Cleans up merge conflict artifacts left by a git stash pop from an
unrelated branch (chore/add-atomic-chat-partner). Kept upstream
(current branch) version in all cases.

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

* fix: restore memoize import and cleanup stash artifacts

Restores the memoize import dropped during conflict resolution in
modelSupportOverrides.ts. Removes duplicate originalEnv declaration
and redundant delete statements in providerValidation.test.ts.

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

* revert change in package.json

* fix broken test

* fix color

---------

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
2026-05-05 18:13:33 +08:00
Kevin Codex 1f66d322ad fix flaky tests in full test run (#1020) 2026-05-05 17:57:24 +08:00
0xfandom 40ae1e7200 fix(shims): strip x-anthropic-billing-header block before forwarding system prompt (#1019)
`getAttributionHeader()` (src/constants/system.ts) builds an
`x-anthropic-billing-header: cc_version=...; cc_entrypoint=...; ...` line
that gets prepended to the system-prompt block array in
src/services/api/claude.ts:1390-1401. The Anthropic API path needs it
(server _parse_cc_header consumes it), but the OpenAI / Codex shims
joined every text block straight into the outbound `system` /
`instructions` payload, so non-Anthropic providers received an
Anthropic-only billing string in their prompt — token waste, plus a
per-build fingerprint that churns local-model KV cache and any
upstream prompt cache (the slowdown unsloth flagged for Claude Code).

Fix the two `convertSystemPrompt` helpers (openaiShim.ts:241,
codexShim.ts:124) to drop blocks whose text starts with
`x-anthropic-billing-header`. Anthropic-bound traffic is unaffected —
the block is built into Anthropic-shaped requests directly and never
flows through these helpers.

Tests:
- openaiShim.test.ts: e2e capturedBody assertions on chat-completions
  + responses-API paths confirm the line is absent and the rest of the
  system prompt survives.
- codexShim.test.ts: convertSystemPrompt is now exported (pure helper)
  and unit-tested for array + only-attribution + plain-string cases.

Closes #607.
2026-05-05 16:41:22 +08:00
0xfandom 1020663990 chore(engines): require Node >=22 to match runtime deps (#1018)
@mendable/firecrawl-js@4.18.1 (lazy-loaded by WebSearch + WebFetch) requires
Node >=22, and CI runs Node 22 + 24, but package.json still advertised
>=20.0.0. Result: npm install on Node 20 surfaced an EBADENGINE warning that
users routinely ignored, then exploded with a cryptic syntax error the first
time a web tool actually pulled firecrawl in.

Bump engines.node to >=22.0.0 so npm refuses Node 20 up front, and refresh
the stale comment in withResolvers.ts that still pointed at the long-gone
>=18.0.0 baseline.

Closes #1009 (engine half — the EACCES half is standard global-install perms,
not openclaude's to fix).
2026-05-05 16:40:12 +08:00
0xfandom 1c746750f6 fix(web-search): surface diagnostic when adapter returns 0 hits and no native fallback (#1006)
For openai-shim providers (minimax, moonshot, nvidia-nim, github
copilot, etc.) hasNativeSearchFallback() is false, so when the
DuckDuckGo adapter returns 0 hits in auto mode the call() path
silently falls through to the native Anthropic web_search_20250305
tool. Those providers don't support that tool, producing a silent
"Did 0 searches" with no signal that the default DDG backend is
rate-limited or that no API-key backend is configured.

Convert the silent fallthrough into an actionable result that names
the active provider, the failing backend, and the env vars to set
(FIRECRAWL_API_KEY / TAVILY_API_KEY / EXA_API_KEY / JINA_API_KEY /
BING_API_KEY / MOJEEK_API_KEY / LINKUP_API_KEY / YOU_API_KEY) plus
the native-provider escape hatch (Anthropic / Vertex / Foundry).
Same root-cause family as the catch branch directly below — that
already throws an actionable error for the throw-on-failure case;
this matches it for the success-with-0-hits case.

Auto mode + 0 hits + native fallback available is unchanged: still
falls through so Anthropic/Vertex/Foundry/Codex can serve the
result. Explicit adapter mode is unchanged.

Adds bun:test coverage for the new helpers via the WebSearchTool
__test export.

Refs #614, #994
2026-05-04 20:57:06 +08:00
Ali AlakbarliandAli Alakbarli 60c76b6599 feat: SDK Runtime — Query Engine, Sessions, and Build Pipeline (#984)
* feat(sdk): add SDK foundation — type declarations, errors, and utilities

Adds standalone SDK building blocks with no SDK source dependencies:
- sdk.d.ts: ambient type declarations for SDK bundle
- coreSchemas.ts + coreTypes.generated.ts: Zod schemas and generated types
- errors.ts: SDK-specific error classes
- validation.ts: input validation utilities
- messageFilters.ts: extracted message filter logic
- handlePromptSubmit.ts: imports from messageFilters
- 16 generated-types tests

* fix(sdk): narrow assertFunction type from broad Function to callable signature

Code review finding: assertFunction used `asserts value is Function` which
accepts any function-like value without narrowing. Changed to
`(...args: any[]) => any` for better type safety.

* fix(sdk): update sdk.d.ts header — manually maintained, not generated

Reviewer noted the header said "Generated from index.ts" but no generator
produces this file. Updated to "Manually maintained — keep in sync with
index.ts". Drift detection added in validate-externals.ts (PR 3).

* fix(sdk): align sdk.d.ts types with canonical coreTypes.generated.ts

Tighten SDK public type contract to resolve reviewer blockers:

- PermissionResult: unknown[] → precise 6-shape discriminated union
  (addRules/replaceRules/removeRules/setMode/addDirectories/removeDirectories)
- SDKSessionInfo: snake_case → camelCase (sessionId, lastModified, etc.)
- ForkSessionResult: session_id → sessionId
- SDKPermissionRequestMessage: uuid + session_id now required
- SDKPermissionTimeoutMessage: added uuid + session_id
- SessionMessage: parent_uuid → parentUuid
- SDKMessage/SDKUserMessage/SDKResultMessage: replaced loose inline
  definitions with re-exports from coreTypes.generated.ts

* feat(sdk): wire existing code modules + SDK shared utilities

Modifies core modules for SDK integration:
- QueryEngine, tools, state, commands: SDK type hooks
- SDK shared utilities (shared.ts, permissions.ts)
- 21 SDK tests (shared-utils, permissions)

Stack: main ← pr1-foundation ← pr2-sdk-core

* feat(sdk): add snake_case ↔ camelCase key mapping utilities

casing.ts provides recursive key transformation for the SDK boundary
layer. Internal runtime uses snake_case; public API exposes camelCase.
Will be used by shared.ts, sessions.ts, query.ts at export boundaries.

* test(sdk): add tests for snake_case ↔ camelCase mapping utilities

Covers snakeToCamel, camelToSnake, mapKeysToCamel, mapKeysToSnake
including nested objects, arrays, null/undefined, and round-trips.

* feat(sdk): add SDK runtime — query engine, sessions, build pipeline

Completes the SDK implementation:
- SDK build target (dist/sdk.mjs) with TUI dependency stubbing
- External dependency lists (scripts/externals.ts)
- SDK type generation from Zod schemas (scripts/generate-sdk-types.ts)
- External validation (scripts/validate-externals.ts)
- SDK source: index, query, v2, sessions modules
- agentSdkTypes: re-exports SDK functions (query, createSession, etc.)
- 136 SDK tests + 7 build scanner tests

Stack: main ← pr1-foundation ← pr2-sdk-core ← pr3-sdk-runtime

* fix(sdk): align internal SDK types with camelCase public contract

shared.ts: SDKSessionInfo, ForkSessionResult, SessionMessage fields
now use camelCase matching sdk.d.ts. SDKPermissionRequestMessage and
SDKPermissionTimeoutMessage gain required uuid + session_id fields.

permissions.ts: onPermissionRequest/onTimeout callbacks now include
uuid and session_id in emitted messages.

* fix(sdk): update runtime modules to use camelCase field names

sessions.ts: toSDKSessionInfo outputs camelCase keys, entryToSessionMessage
uses parentUuid, forkSession returns sessionId.

query.ts: reads sessionId from listSessions/forkSession results
instead of snake_case session_id.

* fix(test): update session tests to use camelCase field names

session_id → sessionId in forkSession result assertions and
getSessionMessages calls.

* fix(sdk): prevent permission timeout race condition with once-only resolve wrapper

Add createOnceOnlyResolve utility to prevent double-resolution of promises
when timeout and host response happen simultaneously. This ensures
deterministic behavior in the permission handling flow.

* fix(sdk): improve race condition test robustness

* fix(sdk): handle consecutive underscores in snakeToCamel conversion

Changes:
- Use _+([a-z]) regex to match multiple consecutive underscores before letters
- Add lookahead (?=. ) to preserve underscore-letter pairs at string end
- Handle dunder names (__proto__, __typename) by stripping wrapper and capitalizing
- Add tests for consecutive underscores and trailing underscore preservation

* fix(sdk): include original error message in permission callback denial

When a canUseTool callback throws an error, the catch block now
includes the original error message in the denial message, making
debugging easier for SDK consumers.

* feat(sdk): add optional timeout to env mutex for deadlock prevention

Add timeout parameter to acquireEnvMutex() to prevent infinite waits
in deadlock scenarios. The timeout is optional and defaults to no timeout
(wait forever) for backward compatibility.

Returns a MutexAcquireResult object with acquired status and optional
timeout reason for failed acquisitions.

* fix(sdk): remove timed-out callback from mutex queue to prevent deadlock

* test(sdk): add missing error path and timeout scenario tests

Add tests for timeout scenarios when host doesn't respond to permission
requests, fallback behavior when no onPermissionRequest callback, and
MCP connection edge cases for undefined/empty config.

* fix(sdk): address code review issues - race conditions, validation, error handling

- Add createPermissionTarget() factory that applies onceOnlyResolve at
  registration time, fixing race condition where timeout and host response
  could both try to resolve the same promise
- Add try-catch to releaseEnvMutex() to prevent permanent lock if callback throws
- Extract DEFAULT_PERMISSION_TIMEOUT_MS constant (30 seconds)
- Add MCP config validation rejecting null, non-objects, and arrays
- Preserve error stack traces in MCP connection failures
- Add runtime validation to mapMessageToSDK for null/non-object/invalid type
- Update tests to use createPermissionTarget and add validation tests

* fix(sdk): syntax fixes and MCP connection error handling

- Remove extra closing parenthesis in permissions.ts
- Remove extra closing braces in shared.ts type definitions
- Wrap MCP connection in try/catch to continue without MCP tools on failure

* fix(sdk): syntax fixes, MCP error handling, and logic clarity

- Remove extra closing parenthesis in permissions.ts
- Remove extra closing braces in shared.ts type definitions
- Wrap MCP connection in try/catch to continue without MCP tools on failure
- Clarify thinkingConfig logic: use ?? true instead of !== false
- Add explanatory comment about thinkingEnabled default behavior
- Apply createOnceOnlyResolve wrapper in QueryImpl.registerPendingPermission

* fix(sdk): comprehensive error handling and resource cleanup

- Add try-catch around injectAgents() to gracefully handle plugin agent
  tool validation failures (prevents test crashes from unknown 'LS' tool)
- Add console.warn logging to agent loading/injection catch blocks for
  debugging visibility (matches v2.ts pattern)
- Add pendingPermissionPrompts.clear() to close() and interrupt() methods
  in both query.ts and v2.ts to prevent memory accumulation
- Add close() method to SDKSession interface and SDKSessionImpl
- Wrap MCP connection in query.ts with try-catch (matches v2.ts behavior)
- Add timeoutQueue cleanup in finally blocks (query.ts + v2.ts)
- Remove error.stack from MCP error messages to prevent internal path leak

All 208 SDK tests pass. TypeScript errors are pre-existing.

* fix(sdk): address code review non-blocking issues

- Add SDKAgentLoadFailureMessage type for agent load failure events
- Emit agent definition/injection failures to SDK message stream
- Add tool name to permission timeout denial message
- Replace 'as any' casts with proper typed state access
- Fix supportedCommands to use correct mcp.commands/plugins.commands paths
- Update test for correct AppState structure

* fix(sdk): address code review blocking and non-blocking issues

Blocking Issues Fixed:
- MCP cleanup missing on session/query close - now disconnects MCP clients
  to prevent resource leaks in long-running processes with multiple sessions
- Engine reference not cleared on close - now sets _engine = null to prevent
  memory leaks
- Added MCP cleanup tests (9 new tests covering cleanup scenarios)

Non-Blocking Issues Fixed:
- Removed redundant catch block that just rethrew errors (query.ts)
- Fixed inconsistent timeout denial message format (permissions.ts)
- Fixed hardcoded tool name 'Bash' in test (permissions.test.ts)
- Exported PermissionResolveDecision type for SDK consumers (index.ts)

All 217 SDK tests pass.

* fix(sdk): address code review type consistency issues

- Add close() method to SDKSession interface (documented but missing from type)
- Fix SDKSessionInfo, ForkSessionResult, SessionMessage field naming:
  snake_case → camelCase to match sdk.d.ts public contract and implementation
- Add uuid and session_id to SDKPermissionTimeoutMessage for correlation
- Fix JSDoc comment in forkSession to use sessionId (not session_id)

These changes align internal types (shared.ts) with the public SDK contract
(sdk.d.ts) and actual implementation output. The merge from origin/main
introduced snake_case types that mismatched camelCase implementation and tests.

* fix: restore openclaude.json comment in REPL.tsx

Merge 0f3aa7a incorrectly took main's side for this comment, reverting
PR2 fix c725c48. Project has migrated to ~/.openclaude.json, not ~/.claude.json.

This is the only PR2 fix lost during merge - all other PR2 fixes
(permissions.ts race conditions, state.ts parentSessionId, etc.)
are preserved in PR3 via subsequent fix commits.

* fix(sdk): add missing type declarations to sdk.d.ts

Add SDKAgentLoadFailureMessage and PermissionResolveDecision to sdk.d.ts
to resolve type declaration drift detected by build validation.

- SDKAgentLoadFailureMessage: Agent loading failure notification
  (stage: definitions/injection, error_message)
- PermissionResolveDecision: SDK-specific permission resolution result
  (allow with updatedInput, deny with message + decisionReason)

Build validation now passes: 56 exports match between index.ts and sdk.d.ts.

* fix(sdk): resource leak and null safety in close/interrupt paths

- unstable_v2_prompt: wrap session in try/finally to guarantee
  session.close() on both success and error paths, preventing
  MCP connection and engine resource leaks
- QueryImpl.interrupt(): add null guard on _engine so calling
  interrupt() after close() is a safe no-op instead of throwing
- SDKSessionImpl.interrupt(): add matching null guard for v2
  sessions, consistent with the Query fix
- QueryImpl.close(): call this.interrupt() before cleanup to
  properly stop in-flight engine operations, matching v2's close()
  pattern and ensuring engine.interrupt() runs before nulling

* fix(sdk): abort AbortController in SDKSessionImpl.close() to prevent resource leak

SDKSessionImpl.close() was not aborting the AbortController, unlike
QueryImpl.close() which does. This meant in-flight HTTP requests and
async operations could continue running after session closure.

- Store AbortController reference via _abortController field + late-bind setter
- Abort and null the controller in close(), mirroring QueryImpl pattern
- Also null _appStateStore in close() to release state snapshots
- Wire abortController through createEngineFromOptions return value

* fix(sdk): index ALL entries in byUuid for compact preserved segment

The byUuid map must index system compact_boundary entries, not just
user/assistant. When anchorUuid === boundary.uuid, the relink walk
needs to find the boundary in byUuid.

Changes:
- query.ts: Index ALL non-sidechain entries (user, assistant, system)
- v2.ts: Same fix — index ALL entries, leaf selection user/assistant only
- Add regression test: boundary.uuid as anchorUuid scenario

Test verifies preserved messages kept, stale pre-compact dropped,
post-boundary chain intact when anchorUuid points to boundary itself.

* fix(sdk): complete preserved segment handling for compact resumes

Multiple fixes for compact-aware transcript loading:

1. Index ALL entries in byUuid (including system compact_boundary)
   - Needed when anchorUuid === boundary.uuid

2. Keep anchorUuid when pruning preserved segment entries
   - The anchor is the parent of preserved head after relink
   - Deleting it breaks the conversation chain

3. Filter system entries from final messages
   - compact_boundary is metadata, shouldn't pass to engine

4. Fix test timestamp format (ISO 8601 requires 2-digit hours)
   - '2025-01-04T0:00:00Z' → '2025-01-04T00:00:00Z'

5. Update test expectations for anchor inclusion
   - When anchor is a stale entry, it appears in messages
   - preserved(4) + anchor(1) + post(4) = 9 max

All 224 SDK tests pass.

* fix(sdk): MCP type:sdk tools properly convert SdkMcpToolDefinition to Tool

- Import MCPTool base from tools/MCPTool/MCPTool.js
- Spread MCPTool properties for proper Tool interface compliance
- Add tools field to SdkMcpSdkConfig type declaration
- Add regression tests for type:sdk tools wiring

Fix ensures in-process SDK tools match Tool interface expected
by QueryEngine and permission handlers.

* test(sdk): strengthen preserved segment and MCP tools tests

Preserved segment test improvements:
- Fix content extraction (access message.content, not message)
- Add exact count assert: messages.length === 6
- Add exact content asserts: preserved turn 1/2, post-boundary present
- Assert no stale, no system entries in final messages

MCP tools test additions:
- Direct test of connectSdkMcpServers() function
- Assert clients.length === 0 (in-process, no MCP connections)
- Assert tools.length === 1 with proper name/description
- Verify handler works via direct call (not via Tool.call which needs context)

* fix(sdk): published types complete, init errors fatal, permission session IDs

Three fixes for SDK production readiness:

1. HIGH: Published SDK types incomplete
   - Add coreTypes.generated.d.ts to package.json "files" array
   - sdk.d.ts re-exports from ./sdk/coreTypes.generated.js which was missing
   - TypeScript consumers would get module resolution errors

2. MEDIUM: query() swallows real init() failures
   - Add _engineWasInjected field to track pre-injected vs fresh engine
   - Check _engineWasInjected, not _engine !== null (always true after setEngine)
   - Auth/config/init errors now properly fatal for normal query() calls

3. MEDIUM: SDK permission events lose real session id
   - Pass sessionId to createExternalCanUseTool() in both query.ts and v2.ts
   - Permission_request/timeout messages now have correct session_id
   - Hosts can correlate permission callbacks to sessions

Test result: 225 pass, 0 fail

* fix(sdk): complete package types + dynamic permission session_id

Two fixes for SDK production readiness:

1. Published SDK types now include actual definitions
   - Replace 215-byte wrapper with 63KB coreTypes.generated.ts
   - TypeScript consumers get full type definitions (SDKMessage, etc.)
   - npm pack now includes real generated types

2. Permission event session_id dynamic for all query() paths
   - createExternalCanUseTool accepts string | (() => string | undefined)
   - query.ts passes () => queryImpl.sessionId getter
   - Fresh/fork/continue queries emit correct session_id at event time
   - V2 passes static sessionId (stable at creation/resume)
   - Add 4 tests: static sessionId, getter resolution, undefined fallback, timeout

Test result: 229 pass, 0 fail

* fix(sdk): fix sdk.d.ts for real TypeScript consumer compilation

Two issues prevented external consumers from compiling against packed SDK types:

1. SDKRateLimitError used constructor parameter properties (readonly resetsAt,
   readonly rateLimitType) which are invalid in .d.ts declarations — moved to
   class properties with separate constructor signature.

2. Re-exported SDKMessage/SDKUserMessage/SDKResultMessage were not imported
   into local scope — added import type alongside export type so TypeScript
   can resolve them for use in other declarations within the same file.

Added package-consumer-types.test.ts that compiles a real temp project against
the SDK types with skipLibCheck:false, catching both regressions.

* fix(sdk): eliminate React/Ink imports from SDK bundle

SDK bundle leaked React/Ink imports via tool UI modules, keybindings,
react-compiler-runtime, and spawnMultiAgent's static React import.

Changes:
- Stub root ink.js barrel, tool UI.js, keybindings/, react-compiler-runtime,
  It2SetupPrompt, and React hook files in SDK build
- Add local no-op stub for react/jsx-dev-runtime (jsxDEV returns null)
- Convert spawnMultiAgent's static React/It2SetupPrompt imports to dynamic
  await import() — spawnTeammate logic stays fully intact
- Add post-build leakage validation (fails on from "react"/"ink"/jsx-dev-runtime)
- Remove react/jsx-dev-runtime from SDK externals (now handled by build plugin)

* fix(sdk): wire disallowedTools through permission context

QueryOptions.disallowedTools was declared but never used. buildPermissionContext()
now passes it to alwaysDenyRules.cliArg so getTools() filters denied tools from
the model-visible list. Also added to V2 SDKSessionOptions for API consistency.

* fix(sdk): defer permission warning to execution time

createDefaultCanUseTool() warned at construction time even when the caller
provided canUseTool/onPermissionRequest. Move warning to first actual default
denial so valid SDK consumers never see false warnings. Add tests for
disallowedTools filtering, tool exclusion, and warning timing.

* refactor(sdk): extract transcript helpers + fix permission typing

- Extract shared transcript utilities to transcript.ts
  (parseJsonlEntries, findLastCompactBoundary, applyPreservedSegmentRelinks,
  buildConversationChain, stripExtraFields) deduplicating query.ts and v2.ts

- Add PermissionTarget interface to hide internal pendingPermissionPrompts
  map from createExternalCanUseTool, with deletePendingPermission and
  denyPendingPermission methods on QueryImpl and SDKSessionImpl

- Fix sessionId stability: preserve constructor UUID for fresh queries
  when continue:true finds no existing sessions, and when explicit
  sessionId does not resolve to a valid transcript file

- Add getMcpClients/setMcpClients to QueryEngine for SDK cleanup access

* fix(sdk): resolve remaining TypeScript errors in SDK modules

- Fix PermissionDecision type compatibility: import from types/permissions
  and cast PermissionResolveDecision to PermissionDecision properly

- Fix AsyncIterator/AsyncGenerator: async generators must return
  AsyncGenerator (which implements AsyncIterable), not AsyncIterator

- Fix Map method callable errors: cast additionalWorkingDirectories
  to Map<string, unknown> before calling .set() and .keys()

- Fix ApiKeySource type: map internal ApiKeySource to SDK's narrower
  type using conversion function, spread info before apiKeySource
  to avoid override

- Fix MCP config scope type: cast 'session' scope to ScopedMcpServerConfig
  for connectToServer compatibility

- Add PermissionMode import and cast for decisionReason.mode

- Deny pending permissions in interrupt(): resolve all pending promises
  with deny before clearing the map (both query.ts and v2.ts)

* fix(sdk): correct init skip logic and test mocks

- query.ts: skip init() entirely for injected engines (mocks, SDK host
  overrides) instead of calling init() and swallowing errors. Pass
  { injected: false } from query() factory to distinguish real engine
  from test mocks.
- mock-engine.ts: add getMcpClients() and setMcpClients() methods to
  match QueryEngine API added in this PR.
- permissions.test.ts: use filterToolsByDenyRules instead of getTools
  for disallowedTools tests, with proper base tool fixtures.

* fix: address code review feedback for exports and build script

package.json exports (Breaking Change Mitigation):
- Add "./package.json": "./package.json" for tool compatibility
- Add "./dist/cli.mjs": "./dist/cli.mjs" for CLI bundle access
- Keep ./sdk as sole library entrypoint
- Root import intentionally blocked (CLI-first package, no main field)

build.ts (Bug Fix):
- Add | undefined to result/sdkResult type declarations
- Add optional chaining: result?.success, sdkResult?.success
- Prevents TypeError masking actual build errors when Bun.build throws

tests/sdk/package-consumer-types.test.ts:
- Update simulated exports to match real package.json
- Add tests verifying exports map structure and file existence

---------

Co-authored-by: Ali Alakbarli <ali.alakbarli@users.noreply.github.com>
2026-05-04 20:56:30 +08:00
TechBrewBossandCopilot Autofix powered by AI 6636bce74b Add opt-in Karpathy guidelines skill (#909)
* Add opt-in Karpathy guidelines skill

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-04 20:55:05 +08:00
TechBrewBoss de0e395467 Fix commit attribution configuration (#920)
* fix: configure commit attribution

* fix: avoid duplicate commit-message usage

* fix: clarify commit attribution command

* fix: clarify set usage and default email

* fix: address commit attribution review feedback

* fix: use openclaude email for all attribution

* fix: tighten attribution model formatting
2026-05-04 20:54:00 +08:00
TechBrewBoss f5ec185609 Store provider profiles in user config (#969) 2026-05-04 20:51:49 +08:00
a133e7631a feat: support self-hosted Firecrawl via FIRECRAWL_API_URL (#949)
* feat: support self-hosted Firecrawl via FIRECRAWL_API_URL

Adds FIRECRAWL_API_URL env var to enable self-hosted Firecrawl
instances. Both WebFetchTool and firecrawl search provider now check
for either FIRECRAWL_API_KEY (cloud) or FIRECRAWL_API_URL (self-hosted).
The FirecrawlClient accepts apiUrl for custom endpoints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove incorrect non-null assertion on FIRECRAWL_API_KEY

Passing undefined to FirecrawlClient.apiKey is correct when using
FIRECRAWL_API_URL without an API key. Also adds regression tests for
isConfigured() covering all four env combinations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: trigger CI

---------

Co-authored-by: Kunthawat Greethong <kunthawat@gmailcom>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-04 16:47:36 +08:00
ArkhAngelLifeJiggy ca676affc4 feat: context partitioning and relevance-based pruning (#849)
* feat: context partitioning and relevance-based pruning

PR 2B - Section 2.3, 2.4:
- Add contextPartitioning.ts with priority zones
- Add relevancePruning.ts with keyword overlap scoring
- Add comprehensive tests (13 passing)

* fix: resolve PR 849 blocking issues

- Fix older system messages now appended in partitionContext()
- Fix getAvailableSpace() accepts contextWindow param
- Fix hasToolCalls/hasErrors() handle structured content blocks
- Wire helpers into autoCompact.ts

* fix: resolve PR 849 remaining blockers

- Preserve system messages in pruning (not dropped)
- Group messages by id to preserve transcript pairs
- Add test for message ID group preservation

* fix: rewrite pruneByRelevance to use API-round grouping - preserves tool_use/tool_result pairs

- Add groupMessagesByApiRound() matching repo invariant from grouping.ts
- Groups both recent and older messages at assistant message.id boundaries
- Ensures tool_use + tool_result pairs stay together, not split across prune boundary
- Add test for API-round grouping with real tool_use + tool_result transcript shape
2026-05-04 16:28:42 +08:00
TechBrewBoss a8f71f3ac0 Fix user agent loading from OpenClaude config dirs (#972)
* Load OpenClaude agents from config dir

* Address agent path review feedback
2026-05-04 16:26:06 +08:00