Commit Graph
582 Commits
Author SHA1 Message Date
OpenClaude Worker 3 371641e40e fix: keep claude-cli identity for GitHub Copilot
GitHub Copilot whitelists 'claude-cli' but may not whitelist
'openclaude-cli'. Keep using the upstream-compatible identity
for GitHub Copilot until confirmed safe to switch.

Refs: PR #976
2026-05-17 10:47:19 +05:30
JATMN c2fecc6728 Narrow Kimi Code user agent fallback 2026-05-08 16:46:50 -07:00
JATMN 580174e19d Preserve Kimi Code compatibility user agent 2026-05-08 16:39:21 -07:00
JATMN 589eee4a43 Merge remote-tracking branch 'upstream/main' into api-client-version
# Conflicts:
#	src/services/api/client.ts
2026-05-08 16:33:39 -07:00
TechBrewBossandGitHub 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
jackil182andGitHub 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
TechBrewBossandGitHub 16726399d8 fix plan mode branding and plan path (#1062) 2026-05-08 15:30:41 +08:00
0xfandomandGitHub 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
ArkhAngelLifeJiggyandGitHub 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 XavierandGitHub 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
0xfandomandGitHub 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 NakhlaandGitHub 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
0xfandomandGitHub 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
0xfandomandGitHub 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 PrefectandGitHub 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]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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łandGitHub 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]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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
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 CodexandGitHub d19f4d335d fix flaky test (#1021) 2026-05-05 18:33:55 +08:00
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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
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 CodexandGitHub 1f66d322ad fix flaky tests in full test run (#1020) 2026-05-05 17:57:24 +08:00
0xfandomandGitHub 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
0xfandomandGitHub 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
0xfandomandGitHub 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
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
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
TechBrewBossandGitHub 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
TechBrewBossandGitHub 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
ArkhAngelLifeJiggyandGitHub 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
TechBrewBossandGitHub 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
JATMNandGitHub 6d0953a79c fix(groq): strip unsupported store field (#983) 2026-05-04 16:21:50 +08:00
TechBrewBossandGitHub 884746dbe9 Provider: Add Hicap gateway provider (#979)
* Add Hicap provider and gateway auth presets

* Fix Hicap compatibility preset coverage

* Authenticate ripgrep download in PR checks

* Use Opus 4.7 as Hicap default

* Address Hicap review feedback

* Address provider review blockers

* Clarify gateway header UI docs

* Remove Hicap endpoint from README
2026-05-04 16:21:11 +08:00
JATMNandGitHub 3d791bf07f Disable feedback/mobile commands and refresh OpenClaude branding (#980)
- disable /feedback and /mobile from command availability while keeping implementation code in place
- remove or rewrite lingering user guidance that pointed to /feedback or /mobile
- switch HelpV2 to a public build version helper and fix the help dialog wrapper regression
- update OpenClaude-facing links and prompt copy for issue reporting and branding consistency
2026-05-04 16:18:17 +08:00
chioarubandGitHub 11f265c094 test(api): cover first-party fetch wrapper runtime path (#990) 2026-05-04 15:36:01 +08:00
7bb4c2e10d Merge configured and discovered provider profile models (#991)
Co-authored-by: Murali D <mdudaka@cisco.com>
2026-05-04 15:35:11 +08:00
990a5a2afb fix(tests): resolve flakiness due to module leak and env state leakage (#988)
- StartupScreen.test.ts: Scope settings module mock to prevent process-global pollution.
- providerConfig.github.test.ts: Explicitly scrub environment variables before each test.
- providerProfiles.test.ts: Add beforeEach to clear RESTORED_KEYS and ensure fresh registry state.
- providerValidation.test.ts: Ensure integrations registry is loaded and scrub environment to prevent leakage from host shell.

This consolidates fixes from PR 944 and addresses additional failures found after rebasing onto main (v0.8.0).

Co-authored-by: Kevin Codex <kevin@gitlawb.com>
2026-05-03 07:19:16 +08:00
JATMNandGitHub d948769dd5 feat: rework release notes around GitHub releases (#981)
* feat: rework release notes around GitHub releases

- switch /release-notes from upstream changelog parsing to OpenClaude GitHub release data
- add a public build version helper and use it for release URL/seen-version tracking
- render release notes in-app with section headers like Features and Bug Fixes
- cache serialized GitHub release notes locally for startup and command fallback paths
- preserve snake_case identifiers while sanitizing markdown content
- keep LogoV2 whats-new output within its display budget
- add focused tests for parsing, formatting, version tags, and display slicing

* Normalize release-please changelog versions

Use normalizePublicVersion when parsing cached changelog headings so release-please markdown headings like ## [0.8.0](...) (2026-05-02) map to the expected version key.

Also normalize direct version lookups consistently and add a regression test covering getReleaseNotesForVersion and getRecentReleaseNotes against the new CHANGELOG.md format.
2026-05-02 20:26:58 +08:00
35f86a9580 fix(startup): make CLAUDE logo D distinct (#986)
* fix(startup): make CLAUDE logo D distinct

Adjust the startup ASCII logo so the D in CLAUDE no longer reads as an O, and add a focused regression test for the rendered shape.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(startup): clear CI flag for logo rendering assertion

Ensure the startup logo regression test exercises the interactive render path under GitHub Actions, where CI is set by default.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-02 20:20:20 +08:00
KRATOSandGitHub dc3c065c4a fix(mcp): allow third-party providers to approve project-scope .mcp.json servers (#696) (#937)
When a user runs openclaude with a third-party provider, project-scope
MCP servers added via `openclaude mcp add -s project ...` were silently
dropped from `/mcp` and `openclaude mcp list`. Re-adding the same
server printed "MCP server already exists in .mcp.json" but the server
never actually loaded. Reporter @gbmerrall pinpointed the cause to the
`if (usesAnthropicSetup)` gate around `handleMcpjsonServerApprovals`
in src/interactiveHelpers.tsx.

The MCP approval dialog and the CLAUDE.md external-includes warning
are about workspace trust, not about Anthropic auth. Gating them on
`usesAnthropicSetup` meant 3P-provider users never saw the dialog
that writes `enableAllProjectMcpServers: true` and
`enabledMcpjsonServers: [...]` to settings.local.json — without
those settings, the MCP server isn't loaded for use.

Drop the `usesAnthropicSetup` gate around the approval flow. The
inner logic is unchanged and `handleMcpjsonServerApprovals` already
early-returns when no servers are pending, so users without project
`.mcp.json` see no new behavior.

- src/interactiveHelpers.tsx: drop the gate, add a comment explaining
  why (and cite #696 so future readers can find context).
- src/__tests__/bugfixes.test.ts: +2 regression tests asserting the
  gate is gone and the issue is referenced.

Verified locally on Linux: build passes (v0.7.0), 1634 tests pass,
the 4 remaining failures (StartupScreen.test.ts, thinking.test.ts)
reproduce on main and are unrelated. The bundled dist/cli.mjs shows
`handleMcpjsonServerApprovals(root2)` running directly after
`setSessionTrustAccepted(true)` with no auth gate.
2026-05-02 11:04:59 +08:00
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
da375271b4 chore(main): release 0.8.0 (#927)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
v0.8.0
2026-05-02 10:28:39 +08:00
95a817fdb0 fix(provider): apply Codex OAuth session switch correctly (#974)
* fix(provider): apply Codex OAuth session switch correctly

Ensure Codex OAuth activation in an existing session does not briefly apply an empty OpenAI API key, preventing missing Authorization headers until restart.

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

* fix(provider): preserve explicit env for Codex API profiles

Limit the Codex session-switch override to OAuth profiles so explicit OpenAI environment settings keep taking precedence for regular Codex profiles.

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

* fix(provider): isolate Codex OAuth env from ambient credentials

Keep Codex OAuth profile activation from inheriting ambient Codex API credentials so CI and user shells cannot poison the in-session OAuth regression path.

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

---------

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
2026-05-02 10:24:13 +08:00
KRATOSandGitHub cc0dab60a3 fix(openai-shim): don't label transport failures as HTTP 503 (#971) (#975)
Network/transport failures from custom OpenAI providers (e.g. ENETDOWN,
EAI_AGAIN, generic fetch failed) were thrown via APIError.generate(503,
...). The OpenAI SDK formats the error message as "${status} ${message}",
so users saw output like:

    503 OpenAI API transport error: fetch failed (code=ENETDOWN)

This is misleading: a 503 implies the upstream server returned Service
Unavailable, but no HTTP response was received at all -- the failure
happened at the transport layer.

Use APIError.generate(0, ...) instead. The SDK's own factory returns
APIConnectionError for status 0, which is the semantically correct class
for "no response received" and produces a message without the spurious
"503" prefix. APIConnectionError extends APIError, so existing
`instanceof APIError` branches in errors.ts and elsewhere keep working.

Add a regression test that asserts the constructed error is an
APIConnectionError, has no status, and the message does not start with
"503".
2026-05-02 10:02:50 +08:00
jatmn 9f86de01ec Route API user agents by actual provider target
Split Anthropic-owned endpoint user agents from provider-routed API traffic.

Keep compatibility-sensitive Anthropic requests on claude-cli/99.0.0 while using openclaude-cli with the current public build version for non-first-party/OpenAI-compatible connections.

Wire getAnthropicClient() to pass its providerOverride-aware first-party decision into the user-agent helper so per-request routing stays correct.

Expand focused tests to cover first-party compatibility traffic, Anthropic-owned endpoints under third-party env, non-first-party provider traffic, and explicit first-party override behavior.
2026-05-01 18:53:09 -07:00
jatmn 2d12fd46bc Merge remote-tracking branch 'upstream/main' into api-client-version 2026-05-01 18:20:30 -07:00
jatmn 0a1c56cc6c Split public build version from compatibility headers
Keep compatibility-sensitive claude-cli/claude-code user agents on MACRO.VERSION while adding a shared public build version helper for MCP-facing metadata.

Update MCP client metadata and MCP user-agent strings to report DISPLAY_VERSION where appropriate without reintroducing first-party version-gate regressions.

Add focused regression tests covering:
- public build version helper behavior
- API client user-agent compatibility behavior
2026-05-01 18:18:39 -07:00
7711ddae48 fix(worktree): surface git stderr in rev-parse failure message (#690) (#954)
When /statusline (or any AgentTool path that creates a worktree) hits
`git rev-parse HEAD` failure during base-branch resolution, the
previous error swallowed git's stderr and reported only:

  Failed to resolve base branch "HEAD": git rev-parse failed

That message gives users no way to distinguish empty repos
('unknown revision or path'), detached HEADs pointing at missing
objects, or a missing git binary on PATH — all surfaced identically.

Extract the message construction into buildRevParseFailureMessage()
and include git's stderr in the thrown Error. When the failing ref is
literally 'HEAD' (the fallback path when fetching origin/<branch>
fails), append a short hint about the most common cause (no commits)
and PATH check.

Adds focused tests in worktree.test.ts covering the empty-repo case,
the empty-stderr fallback (exit code), the branch-ref path (no HEAD
hint), and stderr whitespace trimming.

Fixes #690

Co-authored-by: 0xfandom <nikhoriariteish@gmail.com>
2026-05-02 08:54:47 +08:00
KRATOSandGitHub 5c4fdca217 fix(plugins): sanitize env before spawning git so /plugin marketplace add works (#751) (#934)
Git 2.30+ refuses to start when any environment value contains a NUL,
CR, or LF character ("Unsafe environment: control characters are not
allowed in values"). User shells frequently leak such values — a
copy-pasted API key with a trailing newline, a terminal-set variable
with embedded escape sequences — which made every /plugin marketplace
add and /plugin install fail with that error before git even ran.

Add a small shared helper that builds the env passed to git child
processes and drops keys whose name or value contains a control
character. The legacy GIT_NO_PROMPT_ENV overrides (terminal prompt
disabled, askpass cleared) move into the same helper. Apply it to
every git invocation in marketplaceManager.ts (5 sites: gitPull,
gitClone, sparse-checkout, post-sparse checkout, reconcileSparseCheckout)
and pluginLoader.ts (8 sites: clone, fetch, checkout in both gitClone
and installFromGitSubdir).

A debug-level warning is logged once per process listing the dropped
key NAMES (not values) so the user can clean them up in their shell.

- src/utils/plugins/gitEnv.ts (new): sanitizeEnvForGit + buildGitChildEnv
- src/utils/plugins/gitEnv.test.ts (new): 10 unit tests covering CR/LF/NUL
  in values, control char in key name, undefined values, defaults,
  extras override
- src/utils/plugins/marketplaceManager.ts: replace 5 inline env spreads
  with buildGitChildEnv()
- src/utils/plugins/pluginLoader.ts: pass env: buildGitChildEnv() to 8
  git exec sites that previously inherited process.env unfiltered

Verified locally on Linux: before fix, git --version with a leaked
control-char env value fails with "Unsafe environment"; after fix it
runs cleanly. Live marketplaceManager.gitClone against a real GitHub
repo with the same leaked env succeeds and the repo is materialized
on disk.
2026-05-02 08:36:03 +08:00