mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-11-06 12:50:54 -04:00
5bc160fb8bd89d18a7f75da0eef3eae5a72fbb73
1233
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5bc160fb8b |
feat: add AnySearch as an explicit web search backend (#2261)
* feat: add AnySearch web search backend * fix(anysearch): preserve non-json error details --------- Co-authored-by: liuyang <liuy.web@lingyu.work> |
||
|
|
5ff58c4ff9 |
perf(read): dedup touched-but-identical files and covered sub-ranges (#2259)
The Read dedup stub only fired for the exact same range with an unchanged mtime. Two common cases re-sent content the model already had: - mtime moved but bytes did not (git checkout/stash pop, formatters, touch) - a sub-range of a file that was already read in full Extract the eligibility check into readDedup.ts. On an mtime mismatch, re-read the stored range and compare it byte-for-byte before deduping, refreshing the cached timestamp on a match. Treat an in-bounds range of a prior full Read as already seen. Edit/Write entries, partial views, out-of-range offsets, and any stat/read failure still fall through to a normal read. |
||
|
|
53c7ed9c51 |
fix(hooks): match the .sh suffix case-insensitively on Windows (#2254)
getWindowsBashHookCommand prepends `bash` so a directly invoked script executes instead of opening in the default file handler. The suffix test was case-sensitive, but the behaviour it works around is not: Windows picks a handler by extension without regard to case, so `./hook.SH` opened in an editor exactly as `./hook.sh` would. A hook that never ran is the quiet kind of failure - on UserPromptSubmit it blocks the prompt, and the only trace is a DEBUG_SDK session log. Three cases added to the prefix list (`./hook.SH`, `./hook.Sh argument`, a quoted uppercase path) and two to the preserve list, so an uppercase suffix that is not the command word still changes nothing. Without the flag those three are the only failures in the file. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
10254f4140 |
fix(openai-shim): keep terminal usage from a repeated finish_reason (#2252)
Some OpenAI-compatible providers end a stream twice: one chunk carrying the finish_reason, then a second, identical but for the usage, with choices still populated. The final-usage fallback required an EMPTY choices array, so that second chunk was skipped and its usage dropped. The streaming loop ignores a repeated finish_reason by design, so nothing else picked it up: every assistant message from such a provider stored usage of all zeros. Anything reading the size or the cost of a conversation reads it from there - tokenCountWithEstimation walks back to the newest message carrying usage and trusts it - so automatic compaction on size could not fire, the context warning never warned, and cost tracking recorded nothing. A measured session reached 1763 messages with preTokens: 183 recorded at its compaction boundary. Dropping the empty-choices requirement leaves hasEmittedFinalUsage as the single guard against a double emit, which the existing empty-choices and both-terminal-chunks tests already cover. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3ed1c5a191 |
docs: clarify sharp opt-in for local image decode vs passthrough (#2226)
Maintainer confirmed sharp stays intentionally opt-in on published installs. Document that clipboard paste / local FileRead decode need `npm i -g sharp`, while pre-encoded URL/base64 passthrough does not. |
||
|
|
790c009ae9 |
feat(search): make Exa the default web search backend and add /search (#2257)
* feat(search): make Exa the default web search backend and add /search Exa now leads the auto chain: keyed Exa (EXA_API_KEY) runs first, other keyed backends next, then a new keyless Exa free-tier adapter (Exa's hosted MCP endpoint), with DuckDuckGo as the last-resort fallback. Built-in search for Anthropic/Vertex/Foundry/Codex is unchanged. WEB_SEARCH_PROVIDER=exa uses the keyed API when a key is set, otherwise the free tier. New Exa options: EXA_SEARCH_TYPE, EXA_NUM_RESULTS, EXA_FREE_TIER (opt-out). The free-tier adapter handles all three rate-limit reply shapes, spaces calls to Exa's default QPS, backs off for five minutes after a limit, and parses Exa's plain-text result blocks defensively. New /search command: pick a backend, add or remove an API key via a masked input, show status, run a test search. Choices are saved to the env block of ~/.openclaude.json (0600) and applied immediately; settings.json env entries that override them are reported instead of silently losing. Security: isSensitive arg redaction now also covers local-jsx commands (/ads, /search), which previously recorded raw inline args in the transcript and model context. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(search): fall through when domain filters empty the Exa free-tier hits The keyless Exa endpoint has no server-side domain filters, so when the client-side filter removed every hit, runSearch returned a successful empty result and never tried DuckDuckGo, which can honor the filter. Providers can now set `fallbackInAuto` on their output; auto mode skips to the next backend on that signal, and returns the filtered-empty result only if no later backend does better. Genuine no-results replies and explicit `exa` mode are unchanged. Also adds doc comments to the touched functions that lacked them. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com> |
||
|
|
88a2286baf |
Honor session context windows and cancel stalled MCP calls (#2250)
* Honor session context windows and cancel stalled MCP calls. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com> * fix: address context and MCP cancellation review feedback 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com> * test: wait for MCP cancellation to reach server 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com> --------- Co-authored-by: root <root@nihalluffy.qnc1y4wdjnjulnzfea2dkakqzf.milx.internal.cloudapp.net> Co-authored-by: Codebuff <noreply@codebuff.com> |
||
|
|
0bef4d2230 | fix(openai-shim): strip store for Anthropic models on OpenAI-compatible routes (#2242) (#2247) | ||
|
|
87d4e3a6e8 |
Fix/2238 color prompt layout (#2239)
* fix(prompt): prevent color-only banner layout regression
* test: fix standalone banner regression cleanup
* fix: preserve team border identity and validate color-only prompts
* fix: suppress standalone banners while team identity is active
* fix: resolve prompt color from AppState team members
Resolve explicit self color, matching self/leader member color, then dynamic teammate color. Preserve mode priority and suppress saved standalone colors for active teams.
Validation (Windows, Node 24.21.0, Bun 1.4.2):
- PromptInput tests: 21 passed, 0 failed, 42 assertions.
- Frozen install, typecheck, type tests (10 files), build/smoke/deadcode, both CLI version checks, diff check and PR intent scan passed.
- Full check incomplete: hung at repo-map timeout cancellation; isolated PR test passed, isolated unchanged base
|
||
|
|
9a2910da23 |
feat(gateway): add API Route hybrid gateway (#2225)
* feat(gateway): add API Route hybrid gateway Add API Route as a descriptor-first OpenAI-compatible aggregating gateway with hybrid catalog discovery, dedicated API_ROUTE_API_KEY credentials, and route boundary protections. * fix(gateway): address review feedback for API Route gateway * test(api-route): verify auth header withheld on noncanonical URL * fix(api-route): honor generic saved profile credentials * test(api-route): cover generic profile credential fallback * fix(api-route): protect route vars from settings overrides * fix(api-route): allow dedicated vars in provider env files * test(api-route): isolate provider flag environment * fix(api-route): preserve env-only intent across startup profiles * test(api-route): cover provider env file loading * test(api-route): cover host-managed settings boundary * test(api-route): cover saved profile ownership and switch cleanup * test(api-route): preserve env-only startup selection * test(api-route): verify saved profile credential boundary * test(api-route): accept canonical saved profile credentials * test(api-route): cover saved profile relaunch * sync: apply upstream xAI OAuth state validation * sync: apply upstream xAI OAuth state validation * sync: apply upstream xAI OAuth state validation * test(api-route): preserve env-file precedence and atomicity * test(api-route): cover saved-only profile contract * fix(gateway): register api-route for proxy credential withholding and sanitize model flag - Add api-route to isNoncanonicalDedicatedOpenAILaunch to strip ambient credentials - Register persistedApiRouteProxy in applyStartupEnvFromProfile - Delete OPENAI_API_KEYS on noncanonical base URLs in applyApiRouteEnvOnlyDefaults - Sanitize API_ROUTE_MODEL with usableProviderModelEnvValue in applyProviderFlag - Add regression tests covering credential withholding and model placeholder handling * fix(gateway): clear OPENAI_API_KEYS in canonical API Route selection - Delete OPENAI_API_KEYS when dedicated API_ROUTE_API_KEY is mirrored in applyApiRouteEnvOnlyDefaults - Delete OPENAI_API_KEYS in applyProviderFlag for canonical api-route selection - Add request-level regression test verifying API_ROUTE_API_KEY precedence over OPENAI_API_KEYS - Add providerFlag test confirming credential pool cleanup * fix(api-route): complete saved profile credential parity |
||
|
|
5cd11336ca |
chore(main): release 0.31.0 (#2190)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>v0.31.0 |
||
|
|
bfb44ffa81 |
fix(chrome): launch the packaged CLI entrypoint in npm installs (#2146)
* fix(chrome): launch the packaged CLI entrypoint * fix(chrome): harden install verification * fix(chrome): verify installed setup targets * fix(chrome): harden setup receipt verification * test(chrome): require the packaged heap-limit helper A packed tarball that has every other required entry but not package/bin/heap-limit.mjs must fail clean-install verification. |
||
|
|
55e9b24d91 |
perf(bg): avoid full scans for exact session IDs (#2166)
* perf(bg): avoid full registry scans for exact session IDs * fix(bg): keep resolver test seam internal |
||
|
|
70fe2de2f8 |
fix(bg): clean up old completed session artifacts (#2167)
* fix(bg): clean up old completed session artifacts * fix(bg): reclaim orphaned terminal facts * test(bg): stabilize reservation cleanup handoff * test(bg): clarify cleanup race invariants * fix(bg): validate cleanup completion timestamps * fix(bg): isolate reused session generations * fix(bg): recover terminal session handoffs * fix(bg): retain zero-day cleanup ownership * fix(bg): preserve handled exception outcomes * fix(bg): unify retention lifecycle cleanup * fix(bg): harden retention coordination * fix(bg): preserve retention pass outcomes * test(bg): harden cleanup fixtures and launcher checks |
||
|
|
5fb5abb6f8 | fix(ci): restore main validation (#2236) | ||
|
|
a07b06cee6 |
fix(agent): forward subagent permission prompts to parent session (#2235)
* fix(agent): forward interactive permission prompts * fix(agent): preserve nested permission prompts * fix(agent): isolate permission prompt state * fix(agent): preserve root state for forked commands * fix(agent): close permission handoff races * fix(agent): settle forwarded permission cancellations * fix(agent): keep permission prompts session-owned * fix(agent): preserve origin session permission policy * fix(agent): retain permission owner across delayed starts * fix(agent): isolate concurrent permission prompts * fix(agent): retain inactive owner permission prompts * fix(agent): preserve owner decisions across switches * fix(agent): scope channel permission requests * fix(agent): validate permission mode updates against root context Subagent persist paths were validating setMode against the agent-local context while applying to the root session, which could enable fullAccess or bypassPermissions when only the child marked bypass available. |
||
|
|
70d97fd137 |
feat(web-search): add Ollama backend (#2234)
* feat(web-search): add Ollama backend * fix(web-search): harden Ollama routing * fix(web-search): preserve Ollama profile identity * fix(web-search): align Ollama diagnostics * fix(web-search): align Ollama endpoint diagnostics * fix(web-search): honor Ollama endpoint aliases * fix(web-search): reject Ollama key placeholders * fix(web-search): complete Ollama configuration guidance |
||
|
|
876fb7924a |
feat(query): configure idle timeout (#2233)
* feat(query): configure idle timeout * docs(query): clarify idle timeout limits * feat(settings): configure query idle timeout * test(query): exercise idle timeout integration * fix(query): apply live idle timeout overrides |
||
|
|
0c2043f05e |
fix(hooks): preserve compound Bash commands on Windows (#2217)
* fix(hooks): preserve compound Bash commands on Windows * fix(hooks): preserve environment assignments before scripts --------- Co-authored-by: fancivez <fancive@gmail.com> |
||
|
|
6d09260291 |
chore(types): reduce any usage with safety tooling + 7-batch phase 1 (#2208)
* chore(types): add any-usage baseline + lint budget gate (Phase 0)
Phase 0 of a multi-phase plan to reduce explicit `any` usage safely.
No source files modified in this PR.
- Add eslint@9 + @typescript-eslint deps (lint only, no fix rules).
- New minimal flat eslint.config.js enabling only
`@typescript-eslint/no-explicit-any` as warn. Stub plugins registered
for legacy `custom-rules`, `eslint-plugin-n`, and `react-hooks`
references in existing `// eslint-disable` comments so the comments
stay valid (no rule name resolution errors).
- New `scripts/any-usage-report.ts`: walks src/, tests/, scripts/ and
buckets `any` usage into `: any` annotations, `as any` casts,
`[key: ...]: any` index signatures, `// @ts-ignore` directives, and
generic `any` args. Emits `reports/any-usage.{json,md}`. `--check`
mode exits non-zero if the count grows vs the baseline.
- Lock the baseline at 1,031 occurrences across 198 files.
- Add `lint`, `lint:any-budget`, `check:strict` scripts.
- Plan: docs/plans/imperative-hugging-sphinx.md
Verification:
- `bun run typecheck` clean
- `bun run lint` exit 0 (845 warnings, 0 errors)
- `bun run lint:any-budget` ok (current=1031 baseline=1031)
* chore(types): narrow any in 6 small src files (Phase 1, batch 1)
Phase 1 of the plan to reduce `any` usage. Targets only files with
1–2 lint hits where the concrete type is reachable in scope.
- src/services/api/withRetry.ts: isQuotaExhausted signature `any` → `unknown`,
add local structural narrowing for `.message`/`.status` access.
- src/utils/validation.ts: assertFunction predicate signature `any[]`/`any` →
`unknown[]`/`unknown`.
- src/utils/conversationArc.ts: extractTextFromContent — replace `(block: any)`
in filter/map with explicit type predicate narrowing.
- src/utils/optionalRuntimeModule.ts: dynamic-import wrapper returns
`Promise<unknown>` instead of `Promise<any>`.
- src/entrypoints/mcp.ts: tighten block mapping with the existing structural
type already in scope; replace `(finalResult as any).isError` with a
narrow cast.
- src/components/EffortPicker.tsx: drop redundant `: any` on useAppState
selector — the function's generic infers `AppState` for free.
Verification:
- bun run typecheck clean
- bun run lint: 835 warnings (was 845)
- bun run lint:any-budget: 1022 / 1031 baseline (delta -9)
- bun test src/utils/optionalRuntimeModule.test.ts + conversationArc.test.ts:
36 pass / 0 fail
- bun test src/services/api/withRetry.test.ts: 44 pass / 0 fail
* chore(types): narrow any in WebSearchTool + adapters (Phase 1, batch 2)
Phase 1 continues. These are all boundary code (HTTP responses from
external search APIs), so every `any` is at a module boundary per the
plan. Each adapter now types its response with `unknown` + structural
narrowing instead of `any`.
- providers/types.ts: firstMatch/normalizeHit accept `unknown` and narrow
to `Record<string, unknown>` before index access.
- providers/timeout.ts: fetchJsonWithWebSearchTimeout returns
Promise<unknown>.
- providers/{bing,brave,jina,linkup,mojeek,tavily,you,exa,custom}.ts: each
adapter's response data is cast to a minimal shape (`{ results?: unknown }`
etc.), then array elements are narrowed to `Record<string, unknown>` with
`typeof` checks per field.
- WebSearchTool.ts: getCodexSources and extractCodexWebSearchFailure now
take `Record<string, unknown>`; nested `action`/`error`/`result` are
cast locally to `Record<string, unknown>` so `?.field` chains type-check.
Verification:
- bun run typecheck clean
- bun run lint: 805 warnings (was 835, -30)
- bun run lint:any-budget: 992 / 1022 baseline
- bun test src/tools/WebSearchTool: 138 pass / 0 fail across 9 files
* chore(types): narrow any in 6 mid-size src files (Phase 1, batch 3)
Phase 1 continues. Each replacement uses a precise structural cast
(`as { uuid?: string }` etc.) or `unknown` with typeof/Array.isArray
narrows at use sites.
- src/utils/settings/types.ts: zod preprocess callback `any` → `unknown`,
copy via `Record<string, unknown>`.
- src/entrypoints/sdk/v2.ts: initialMessages param/var `any[]` → `Message[]`;
local cast at the boundary with stripChainFields.
- src/entrypoints/sdk/query.ts: msg `.uuid` access uses
`as { uuid?: string }`; `fileHistoryCanRestore` etc. accept `UUID` (from
`crypto`); `mcpServerStatus` accesses `client.error` / `client.config`
directly since the union members already have them.
- src/commands/cache-probe/cache-probe.ts: getField reduce `any` → `unknown`
with typeof guard; catch (err: any) → `err: unknown` with
`err instanceof Error`; mainMakeUsage/mainConvertChunkUsage take `unknown`
and read fields via typeof checks.
- src/services/compact/snipProjection.ts: snipMetadata / uuid accessed via
narrow structural casts, no longer `as any`.
- src/utils/messages.ts: tool_use block destructuring for extra_content uses
`as { extra_content?: unknown; [k: string]: unknown }` instead of
`as any`.
Public SDK surface (entrypoints/sdk.d.ts, entrypoints/sdk/index.ts
SdkMcpToolDefinition<Schema = any>) is intentionally permissive for
consumer ergonomics and is left untouched per the plan's do-not-touch
list.
Verification:
- bun run typecheck clean
- bun run lint: 782 warnings (was 805, -23)
- bun run lint:any-budget: 969 / 992 baseline
- bun test (sdk v2/factories + compact + messages): 91 pass / 0 fail
* fix(types): correct BetaContentBlock cast in messages.ts (Phase 1 fix)
Verifier caught that the `as { [k: string]: unknown; extra_content?: unknown }`
cast on a `BetaToolUseBlock` did not sufficiently overlap (BetaToolUseBlock
lacks an index signature), so the destructured spread of `restBlock`
yielded a malformed union member that failed to assign to BetaContentBlock[].
Fix: cast through `unknown` first, then assert the final return value as
`BetaContentBlock` (the declared element type of the outer `.map`). This
narrows from the prior `as any` while keeping the previous structural
intent (preserve all block fields, optionally inject `extra_content`).
Verification:
- bun run typecheck clean
- bun test src/utils/messages/: 79 pass / 0 fail
* chore(types): narrow any in 5 dense src + 1 script (Phase 1, batch 4)
Each replacement preserves runtime behavior while using concrete types
where reachable and `unknown` with structural narrowing where not.
- src/components/ClaudeMdExternalIncludesDialog.tsx: 4 helper updaters
(acceptProject/User, declineProject/User) take `ProjectConfig` from
`src/utils/config.ts` instead of `any` (matches the
`saveCurrentProjectConfig` updater signature).
- src/components/ProviderManager.tsx: env-clearing objects in
`activateGithubProvider` and the GitHub disable path used
`undefined as any`. The intent is "delete this key on merge" but the
zod schema coerces to string. Replaced with
`undefined as unknown as string` (honest about the lie) and added a
comment explaining intent.
- src/services/compact/snipCompact.ts: introduced a structural
`SnipMessage` type for the public function params. Tests construct
fake messages with `uuid: 'u4'` (not branded UUID), so a strict
`Message[]` would reject them; the structural type accepts both
production messages and test fixtures. `snipCompactIfNeeded` is
generic `<T extends SnipMessage>` so callers get the same shape back.
Internal content blocks use `unknown` + type-predicate narrowing.
- src/query.ts: yield site for the boundary message casts to `Message`
since the value is constructed as a real `Message` in production.
- scripts/grpc-cli.ts: proto descriptor, stream, and message callback
typed as `unknown` / `ClientDuplexStream<unknown, unknown>`. The
dynamic `.proto` payload is cast once at the boundary into a
structural shape so per-field access (`text_chunk.text` etc.) is
tracked explicitly.
- scripts/generate-sdk-types.ts: the JSON-Schema-walking converter
takes `unknown` everywhere and narrows via typeof / Array.isArray at
each access. `Map<unknown, string>` for placeholder identity
comparison, `Set<string | undefined>` no longer used here.
Verification:
- bun run typecheck clean
- bun run lint: 728 warnings (was 782, -54)
- bun run lint:any-budget: 916 / 969 baseline
- bun test src/services/compact: 91 pass / 0 fail
* chore(types): remove 3 eslint-disable no-explicit-any suppressions
Tighten three small call sites where the `any` was used as a lazy
escape hatch and a real type was reachable in scope.
- src/main.tsx: `(global as any).require('inspector')` becomes
`(global as unknown as { require: (s: string) => { url: () => string } })`.
The dynamic require is being phased out, but while it remains the
narrow structural cast documents the shape we actually consume.
- src/ink/reconciler.ts: catch (error: any) → catch (error: unknown);
`error.code === 'ERR_MODULE_NOT_FOUND'` accesses the code via
`(error as { code?: unknown }).code`.
- src/components/wizard/WizardProvider.tsx:
`createContext<WizardContextValue<any> | null>(null)` →
`createContext<WizardContextValue | null>(null)`. The
`WizardContextValue` generic already defaults to
`Record<string, unknown>`, so the `<any>` was redundant; consumers
that need a tighter type can supply it via
`useContext<WizardContextValue<T>>`.
Note: most remaining lint warnings come from file-level
`/* eslint-disable @typescript-eslint/no-explicit-any */` blocks at the
top of do-not-touch files (src/types/{message,utils,tools}.ts,
src/constants/querySource.ts, src/entrypoints/sdk/controlTypes.ts) and
from public SDK surface (src/entrypoints/sdk{,.d.ts,v2.ts}). Both are
intentional per the plan.
Verification:
- bun run typecheck clean
* chore(types): refresh any-usage baseline
* fix(types): address PR review feedback on any-reduction tooling
- Pin eslint to resolved version (9.39.5) so lint warning totals stay
stable across lockfile updates.
- Wire lint:any-budget into the regular `check` gate (was only in
check:strict), so any-usage regressions block PRs.
- any-usage-report: use lstat and skip symlinks so a self-referencing
link under src/tests/scripts can't stall the budget scan.
- any-usage-report --check: exit nonzero when the baseline file is
missing or invalid so the gate can't be silently disabled.
- Remove dangling references to docs/plans/imperative-hugging-sphinx.md
from eslint.config.js and scripts/any-usage-report.ts headers.
- mcp.ts: defensively skip non-object tool-output entries (primitives
/ null) instead of crashing on boundary data.
- messages.ts: realign the mis-indented tool_search-off branch so the
blockExtra + return block reads at the right level.
No budget delta (913 -> 913). Typecheck, lint, budget, and the affected
unit tests all green.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(types): narrow any in src/grpc/server.ts (5 warnings)
- protoDescriptor cast: replace `as any` with a structural type that only
declares the AgentService shape we actually use.
- handleChat: ServerDuplexStream<any, any> -> <unknown, unknown>; the
clientMessage is narrowed locally where it's read.
- previousMessages: any[] -> Message[] (already imported, already used
as the type of this.sessions).
- catch (err: any) -> catch (err); err.message is now gated behind an
instanceof Error check.
Typecheck, lint, budget, and the 2 grpc unit tests all green.
Lint 728 -> 723 (-5). Budget 913 -> 909 (-4).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(types): narrow any in src/utils/knowledgeGraph.ts (18 warnings)
Add explicit LegacyEntity / LegacyRelation / LegacySummary / LegacyData
types so the migration path stops round-tripping through `any`.
- LegacySource.data, mergeLegacySources, normalizeLegacyData,
readLegacySqliteStore, SqliteReadResult.data, and doMigration all take
the new LegacyData shape.
- LegacyData.entities is typed `Record<string, LegacyEntity> | LegacyEntity[]`
because older JSON dumps stored entities as an array. normalizeLegacyData
converts the array form to the record form so downstream code can keep
using Object.entries. (Old loop relied on the same `id ?? entryKey`
fallback, so behavior is preserved; verified by all 20 unit tests.)
- SQLite row accessor now returns `Record<string, unknown>` (was `any[]`);
per-row readers narrow `row.id`, `row.attributes`, `row.keywords`,
`row.content` from `unknown` and the existing typeof guards preserve
the previous fail-soft behavior on malformed rows.
Lint 723 -> 705 (-18). Budget 909 -> 894 (-15). All 20 knowledgeGraph
unit tests pass; typecheck clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(types): narrow SDK tool() generic and handler signature (12 warnings)
The public SDK MCP tool() helper previously defaulted its Schema generic
to `any` and typed handler args as `any`. Switch both to `unknown`:
- SdkMcpToolDefinition<Schema = unknown>
- tool<Schema = unknown> with handler (args: Schema, extra: unknown)
- annotations?: any -> ToolAnnotations (imported from MCP SDK types)
This is a strictly tighter API: callers who pass a concrete schema get
the same behavior; callers who relied on implicit any now have to
validate handler args (which they should have been doing anyway).
All 4 SDK MCP tool wiring tests pass; typecheck clean.
Lint 705 -> 695 (-10). Budget 894 -> 884 (-10).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(types): address PR feedback on SDK + grpc type hardening
Apply 4 of 5 CodeRabbit review items (skipped item 5: SDK resume flow
JSONL validation, deferred as larger refactor).
- sdk.d.ts: switch MCP type imports to the package's public
`@modelcontextprotocol/sdk/types.js` subpath (was the internal
dist/esm/types.js path).
- sdk.d.ts: handler return Promise<any> -> Promise<CallToolResult> in
both SdkMcpToolDefinition and the tool() helper, matching the
runtime contract and the sibling declarations in sdk/index.ts and
sdk/v2.ts.
- grpc/server.ts: introduce a local ClientMessage interface (matching
the openclaude.proto oneof payload) and type the duplex-stream
request as ServerDuplexStream<ClientMessage, unknown>. The handler
body was already reading these fields by name; the type now
documents them.
- grpc/server.ts: ServiceDefinition generic was being passed a
ServerDuplexStream, which isn't a valid T (T must be a record
keyed by method name). Switch to ServiceDefinition<{ Chat: never }>
so the call to addService accepts a real implementation-map key.
Typecheck, lint, budget, and the 6 affected unit tests all green.
Lint 695 -> 693 (-2). Budget 884 -> 882 (-2).
* fix(types): address 6 reviewer findings on PR #2208
Six findings (four P2, two P3) from the second review pass.
1. knowledgeGraph legacy array normalization: previously dropped entries
whose `id` was neither string nor number, breaking recovery for
idless array entries. Use the array index as the fallback key so
mergeLegacySources' existing `id ?? entryKey` logic continues to
recover the entry. All 20 unit tests still pass.
2. SDK tool() generic conflated schema descriptor with invocation
value type, breaking the test fixture (and any external consumer)
that declares the handler with a concrete value type. Restore
`args: any` for the handler across the factory, SdkMcpToolDefinition,
and the .d.ts declaration; keep `Schema` as the generic for
inputSchema typing.
3. sdk.d.ts now declares ToolAnnotations and CallToolResult locally
instead of importing from @modelcontextprotocol/sdk, preserving
the optional peer contract. A consumer without MCP installed no
longer gets TS2307 on a no-op SDKSession type import.
4. any-usage-report now strips comments, string literals, and
template-literal contents before applying the annotation regex,
so `// example: any`, `': any'`, and similar non-type occurrences
no longer count. @ts-ignore / @ts-expect-error / @ts-nocheck
directives are preserved for the ignore category. Budget went
from 882 to 776.
5. generate-sdk-types: schemas module cast as Record<string, () => unknown>
was wrong because HOOK_EVENTS is a non-callable; cast as
Record<string, unknown> and narrow per-access. def.getter() and
Reflect.get(schema, 'description') both narrowed to a callable /
object shape before use. Generated output unchanged (diff empty).
6. any-usage-report walk(): readdir failures now propagate instead
of being silently swallowed, so a partial scan cannot publish a
too-low replacement baseline or pass --check.
Typecheck, lint, budget, and 26 affected unit tests all green.
* chore(types): refresh any-usage baseline after upstream rebase
Baseline 776 -> 779. The +3 comes from upstream #2196 (Command Code
gateway) test mocks merged to main after the baseline was recorded:
2x (payload: any) in ProviderManager.test.tsx, 1x 'as any' in
providerProfiles.test.ts. Symptom-level narrowing of another merged
PR's test fixtures is out of scope; re-baseline to the rebased tree.
---------
Co-authored-by: Gravirei <gravirei@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
|
||
|
|
35d7edc2f6 |
fix(api): validate API_TIMEOUT_MS in the native Anthropic clients (#2169)
* fix(api): validate API_TIMEOUT_MS in the native Anthropic clients
The native Anthropic client path parsed API_TIMEOUT_MS with a raw
parseInt: `parseInt(process.env.API_TIMEOUT_MS || String(600 * 1000), 10)`
in client.ts and `parseInt(process.env.API_TIMEOUT_MS || '', 10)` in the
non-streaming fallback. Because `||` only substitutes the default for an
empty/unset value, any non-empty malformed value ("abc", "25ms") is truthy
and parses to NaN, and a negative value passes straight through. A NaN or
negative timeout on the SDK client is undefined behavior, and an oversized
value overflows the 32-bit setTimeout ceiling into a tiny/immediate delay.
The OpenAI-compatible shim already routes API_TIMEOUT_MS through
getApiTimeoutMs, which rejects non-digit/negative input, falls back to a
default, and clamps to the 32-bit ceiling. Reuse that single parser in the
native clients too. getApiTimeoutMs gains an optional fallbackMs argument so
the non-streaming path keeps its context-specific default (120s on remote,
300s otherwise) while still validating and clamping the override; existing
shim callers are unaffected (the default remains 600s).
Tests cover the fallback argument in the parser and assert the constructed
native client's timeout for malformed, negative, zero, unset, valid, and
oversized values.
* test(api): cover the non-streaming fallback timeout and pin route env
Add direct coverage for getNonstreamingFallbackTimeoutMs, which the previous
tests only exercised indirectly through the shared parser. It now asserts the
context-specific defaults survive the change (300s local, 120s remote) for
missing and invalid API_TIMEOUT_MS, that a valid override is honored in both
contexts, and that an oversized one is clamped. The function is exported for
this; test files are knip entry points, so it does not read as dead code.
Also clear every provider route-selection variable explicitly in the native
client timeout test. The suite's afterEach already restores them, but pinning
them up front keeps an inherited CLAUDE_CODE_USE_OPENAI (or another route
flag) from diverting getAnthropicClient into the shim and measuring the wrong
client's timeout.
* test(api): pin the native client path and document the timeout contract
The native timeout test cleared many provider variables but left
NEARAI_API_KEY untouched, and the suite never snapshotted it at all.
NEARAI_API_KEY alone selects the env-only Near AI route, and clearing the
competing vendor keys removed the very credentials whose presence suppresses
it, so an inherited value routed getAnthropicClient to the OpenAI shim. The
shim is returned as a cast object with no SDK timeout, so the assertions read
undefined and the regression silently stopped being covered. CLINE_API_KEY had
the same gap.
Snapshot, clear, and restore both alongside the other route-selection inputs,
and assert the constructed object is an Anthropic instance before reading its
timeout, so a stray variable or a future env-only route fails loudly instead
of weakening the test. Verified by injecting NEARAI_API_KEY: the guard now
fails on the shim object rather than passing on undefined.
Also refresh the API_TIMEOUT_MS reference, which still said the Anthropic SDK
kept its own timeout handling. It now describes both consumers: the
headers-deadline semantics for OpenAI-compatible paths, and the native
Anthropic SDK request timeout plus the non-streaming fallback's own defaults
(120s remote, 300s otherwise), under the same validation and clamping.
|
||
|
|
decad0e172 |
fix(openai-shim): request streaming usage from compatible local providers (#2144)
* fix(openai-shim): request streaming usage from local providers * test(openai-shim): verify cached streaming usage accounting * fix(openai-shim): retry strict streams without usage options |
||
|
|
cbbe10ff8a |
fix(frontmatter): anchor the closing --- to the start of a line (#2027)
* fix(frontmatter): anchor the closing --- to the start of a line The opening delimiter was anchored but the closing one was not, and [\s\S]*? is lazy, so parsing stopped at the first `---` appearing anywhere -- including inside a value. A description reading "Reviews code --- thoroughly" ended the block early: the value was truncated and the remaining frontmatter lines plus the real delimiter leaked into the body that is sent to the model. A `---` line inside a block scalar did the same. Requiring the captured block to be empty or to end at a line break pins the close to a line start. The m flag would also work for the close but would un-anchor the open, letting a horizontal rule partway down a body be read as frontmatter. Tightened the trailing run to spaces and tabs as well, since \s* matched a newline and accepted a bare `---` with no line terminator after it. Affects every .md frontmatter consumer: agents, skills, slash commands, output styles and memory files. * test(frontmatter): fold the delimiter cases into the existing suite A separate test file adds a slot to bun's sequential file ordering, which shifted which suite runs before which and surfaced an unrelated mock leak in CI (taskReport git metadata and the /ads command failed on both Node versions, neither touching frontmatter). The cases belong next to the existing parser coverage anyway. * test(frontmatter): cover tabs after both delimiters * fix(frontmatter): accept CRLF delimiter lines Windows-authored Markdown terminates the `---` delimiters with `\r\n`. The `[ \t]*\n` matcher rejected the carriage return, so such files were treated as having no frontmatter. Accept an optional `\r` on the open, captured body, and close line endings. * fix(frontmatter): keep consuming blank separator lines after the close Anchoring the closing delimiter narrowed its trailing match from `\s*` to just the delimiter's own line ending, so a conventional blank line between the frontmatter and body survived as a leading newline in the parsed content. Skill and plugin-command bodies are fed into prompts untrimmed, so that became a stray newline in the model instructions. Consume trailing whitespace-only lines again after the (still strictly validated, line-terminated) close, which cannot revive the mid-document false close since it only runs once a valid terminator matched. * fix(frontmatter): close empty frontmatter at the first delimiter line The anchored close made the captured block's outer quantifier greedy, so empty frontmatter followed by a body horizontal rule (`---\n---\n---`) read the second delimiter as YAML and closed on the third, swallowing the rule. Make the outer capture lazy so an empty block closes at the first `---` line as it did before anchoring; real frontmatter still matches on backtrack. Fixes the same shape hard-failing `claude plugin validate`. |
||
|
|
c3930ca5ad |
fix(mcp): resolve server names by own-property, not the prototype chain (#1983)
* fix(mcp): resolve server names by own-property, not the prototype chain
getMcpConfigByName and the mcp remove handler look names up in plain
object maps built from JSON config with a bare servers[name] truthiness
check. Reserved names ('constructor', '__proto__', 'toString', …) resolve
to inherited Object.prototype members, which are truthy, so:
- `mcp get constructor` skips the not-found guard, prints a fabricated
record, and hands the Object constructor to the health check;
- `mcp remove constructor` reports the reserved name as present in
multiple scopes and prompts to pick one instead of "No MCP server found".
The same leak reaches runAgent and the print handlers, which all route
through getMcpConfigByName. Gate every lookup on Object.hasOwn so only
real, own-property server names resolve.
* test(mcp): restore project mcpServers in afterEach for isolation
Save and restore getCurrentProjectConfig().mcpServers the same way the
global config is handled, rather than hardcoding undefined, so the test
never leaks state into suites that share the in-memory test config.
* fix(mcp): cover the remaining prototype-name mutation and doctor paths
The own-property gate only covered unscoped removal and getMcpConfigByName.
The scoped paths still used bare lookups:
- removeMcpConfig's project/user/local existence checks accepted inherited
members, so 'mcp remove constructor -s user' reported a successful removal
while leaving the configuration untouched;
- addMcpConfig's already-exists checks rejected valid 'mcp add constructor';
- doctor's servers[name] and activeServers[name] fabricated definitions for
prototype names.
Gate all of them on Object.hasOwn. Also reject the name '__proto__' at add
time: it passes the character check but assigning it on a plain object hits
the prototype setter instead of creating an own property, so the server
would be reported as added and silently vanish.
Restore NODE_ENV in the test teardown alongside the config state.
* fix(mcp): report a reserved __proto__ entry in file config
A hand-authored .mcp.json can contain a server named "__proto__": JSON.parse
gives it a real own key, but it cannot be copied onto a plain object, so the
schema's rebuild dropped it before validation ever ran. The entry simply did
not exist and nothing said why, and with own-property lookups the scoped
removal path now correctly reports it as absent -- so there was no way to
learn the name was the problem.
Detect it on the raw parsed config and surface a fatal validation error
naming the entry, matching the rejection addMcpConfig already performs for
the same name. The rest of the file still parses.
* test(mcp): serialize the proto-name suite on the shared mutation lock
The suite swaps NODE_ENV and the process-wide global and project MCP
configurations without holding sharedMutationLock, so bun can run it
alongside another state-mutating file: that file observes the injected
realserver/locallyreal fixtures, or its own updates are overwritten when
this teardown restores its snapshots.
Acquire the lock in setup and release it from a finally in teardown, the
same shape officialRegistry.test.ts uses.
Also cover the reserved __proto__ entry arriving from parsed file config.
* fix(mcp): reserve constructor alongside __proto__
With own-property lookups in place, `mcp add constructor` persists an entry
the config schema then rejects -- and it rejects the whole mcpServers
object, so the newly added server is unusable and every other server in that
scope stops loading with it.
Refuse the name at add, and name it at file ingress: the only diagnostic
before was a generic "does not adhere to schema" against `mcpServers`, which
does not say which entry is at fault. The scan runs ahead of the schema
parse because that path returns early. `__proto__` and `constructor` are the
only two names that fail a write/read round trip -- toString,
hasOwnProperty, valueOf and the rest all persist and read back correctly.
* fix(mcp): reject a config with a reserved server name outright
parseMcpConfig flagged a fatal reserved name but, for __proto__, still returned
a usable partial config because zod's rebuild silently drops the key and the
schema parse succeeds. Callers that branch on the config -- the --mcp-config
ingress in main.tsx -- then took the partial config and discarded the error,
starting with the bad entry quietly gone. Return config: null whenever a fatal
reserved-name error is present, matching the constructor path where the schema
already fails, so the invalid input is rejected at every consumer.
* fix(mcp): keep reserved-name findings visible in all-server doctor output
doctorAllServers builds server reports only for names that survived parsing or
are active, and attaches server-keyed findings to those. A fatal reserved-name
error is keyed by a name that never survives parsing, so its finding was built
into no report and dropped -- mcp doctor --config-only read clean while the
invalid config was present. Surface any finding whose server has no report as a
global finding.
* fix(mcp): do not mutate a fatally poisoned project .mcp.json
A reserved server name (`__proto__`/`constructor`) now fails the whole
parse and returns config: null, so getProjectMcpConfigsFromCwd reports an
empty map even though valid siblings remain on disk. Project-scope add
rebuilt the file from that empty snapshot and wrote only the new server,
silently dropping every sibling; project-scope remove reported a valid
sibling as "not found". Refuse to mutate the file when it fails to parse,
naming the offending entry, instead of treating the empty map as truth.
* fix(mcp): surface reserved-name errors in single-server doctor output
doctorServer set report.findings to only the global findings and never
applied the orphan fold doctorAllServers uses, so a fatal reserved-name
error in a poisoned scope was dropped when the requested target lived in
another scope -- exit 0 with the real fault hidden. Promote findings for
names other than the reported one, and skip the state.not_found branch
when a validation finding already explains the requested name so the
report no longer carries two blocking findings with contradictory text.
* fix(mcp): honor the fatal-parse contract across every scope
The config:null contract was only enforced on project-scope .mcp.json.
Extend it so a fatally poisoned scope is never silently mutated:
- Re-check the project snapshot immediately before the write rebuild, not
only at the existence check -- the file can go fatal in between, and
rebuilding from the resulting empty map would drop valid siblings.
- Refuse user/local add and remove when that scope parses to config:null,
so the CLI no longer reports success for a scope that then loads nothing
(or mutates a sibling in a raw map that stays unloadable).
- Gate enterprise exclusive control on the managed file's presence, not on
a successful parse, so a poisoned managed-mcp.json can't silently drop
the policy lock and let user/project servers take over.
Generalize the guard to assertMcpScopeWritable(errors, scopeLabel).
* test(mcp): pin allowedSettingSources so the local-scope guard is hermetic
getMcpConfigsByScope() returns an empty writability-error list when a scope's
setting source is disabled, and allowedSettingSources is a process-wide global
other suites mutate. In bun's file order a suite that leaves localSettings
disabled (e.g. one exercising --setting-sources) would make the 'fatally
poisoned local scope' case skip its guard, so addMcpConfig(...'local') resolved
instead of rejecting -- the intermittent CI failure at config.protoName.test.ts.
Pin the full source set in beforeEach and restore it in afterEach, matching the
NODE_ENV and MCP-config snapshotting this suite already does.
* fix(mcp): guard scope mutations on raw parse errors, not the source filter
assertMcpScopeWritable read errors from getMcpConfigsByScope()/
getProjectMcpConfigsFromCwd(), which suppress errors to an empty list when a
scope's setting source is disabled. addMcpConfig/removeMcpConfig still mutate
the raw mcpServers maps, so under a narrowed --setting-sources set a fatally
poisoned scope could be written or deleted (clobbering valid siblings) while the
CLI reported success. Add getScopeMutationErrors(), which parses each scope's
raw source directly, and route every write guard through it.
* fix(mcp): detect unscoped-remove membership from parsed scope views
Unscoped `mcp remove` read local/user membership from the raw
getCurrentProjectConfig()/getGlobalConfig() maps while project used the parsed
view. A fatally poisoned local/user scope has no loadable servers yet still
holds the raw entry, so the handler listed the server as living there and then
removeMcpConfig refused with 'Cannot modify … config'. Read all three scopes
through getMcpConfigsByScope() so detection matches what is actually removable.
* fix(mcp): read the raw project scope for mutations, not the source view
getScopeMutationErrors only covered the write guard's errors; the project
existence check and .mcp.json rebuild still read servers from the
source-gated getProjectMcpConfigsFromCwd(). With projectSettings excluded from
--setting-sources that returned an empty map, so an add rebuilt the file
without the valid siblings and a remove mis-reported the server as absent.
Drop the source gate from getProjectMcpConfigsFromCwd() (a mutation-only read)
so it always sees the real file, and fold the project write guards back onto
its returned errors. Regression covers add/remove with projectSettings off.
* test(mcp): re-pin the poisoned local fixture atomically per mutation
The local-scope poison regressions flaked under the full parallel suite:
sharedMutationLock serializes other lock holders but not every writer of the
process-wide test project config or NODE_ENV, so a stray async task from another
suite could clobber the fixture during an await gap between the setup and the
assertion. addMcpConfig/removeMcpConfig reach their scope guard with no awaited
work in between, so re-establishing NODE_ENV, the enabled sources, and the
poisoned config synchronously immediately before each mutation (pinPoisonedLocalScope)
keeps the guard's read atomic regardless of what ran during earlier awaits.
* fix(mcp): surface managed-mcp.json parse errors in enterprise mode
Enterprise exclusive mode engages on the managed file's presence, so a
malformed or reserved-name managed-mcp.json fail-closes every other MCP source.
getClaudeCodeMcpConfigs returned errors: [] for that branch, and
getSettingsWithAllErrors aggregated only user/project/local, so startup and the
MCP UI saw an empty server list with no diagnosable reason. Propagate the
enterprise scope's parse errors as generic-error PluginErrors from the exclusive
branch and include the enterprise scope in the settings error aggregation.
* fix(mcp): detect unscoped-remove membership source-filter-independent
Reading membership from getMcpConfigsByScope applied the --setting-sources load
filter, so an unscoped `mcp remove foo` reported "No MCP server found" for a
clean user/local/project entry that removeMcpConfig(foo, scope) still mutates
when that source is narrowed out. Read each scope the same way removeMcpConfig
resolves existence -- the raw getCurrentProjectConfig()/getGlobalConfig() maps
and getProjectMcpConfigsFromCwd() -- still own-property gated so inherited
members are not treated as present.
* fix(mcp): surface managed-mcp.json errors in headless startup
The headless (-p) MCP consumer destructured only servers from the
config promise and discarded errors. A fatal managed-mcp.json
fail-closes every file-based MCP source, so scripted users saw an empty
server list with no diagnostic — indistinguishable from an intentionally
empty configuration. Emit the config errors on stderr in non-interactive
sessions (interactive already surfaces them via the MCP error UI).
* fix(mcp): enforce enterprise-exclusive boundary in named lookups
getMcpConfigByName() gated plugin-only mode but not the enterprise-
exclusive policy. A present managed-mcp.json takes exclusive, fail-closed
control of MCP (its mere presence engages the policy, even when the file
is malformed), and getClaudeCodeMcpConfigs() drops every non-enterprise
source accordingly — but the named lookup fell through to the user/
project/local maps. A user with a locally configured server plus an
invalid managed file could therefore still resolve that server by name
via 'mcp get <server>' (checkMcpServerHealth) or an agent definition's
named-server reference (runAgent), bypassing the intended policy.
Return only an enterprise-owned entry (or null) whenever
doesEnterpriseMcpConfigExist() is true, before consulting other scopes,
mirroring the boundary getClaudeCodeMcpConfigs() already enforces.
|
||
|
|
d16318a47f | fix(xai): validate OAuth state before settling callbacks (#2228) | ||
|
|
e2b021d8bb |
fix(plugins): keep Windows marketplace cache on copy ENOENT (#2220)
* fix(plugins): keep marketplace cache when Windows copy hits ENOENT (#2183) ChromeDevTools marketplace add cloned successfully then failed finalize because recursive fs.cp throws on unreadable nested files. Keep the validated clone and join tilde plugin-cache paths so Windows does not glue the home directory onto .openclaude. * fix(plugins): keep marketplace cache if dest cleanup throws (#2183) Copy-fallback recovery still failed add when removing a partial dest threw, and getMarketplace refetch did not persist a keep-temp cachePath. * fix(plugins): use persisted installLocation after marketplace refetch (#2183) Keep-temp recovery can change cachePath on disk while same-call getPluginById and install-by-name still returned the pre-refetch path. * fix(plugins): persist keep-temp installLocation only for remote sources getMarketplace refetch was rewriting file/directory installLocation to the marketplace root, which can make later remove delete a user directory. |
||
|
|
055e5d551a |
feat(launcher): support --max-old-space-size-percentage (#2219)
* feat(launcher): support --max-old-space-size-percentage heap sizing Convert Node's percentage heap flag into --max-old-space-size from constrained or total RAM so small containers and large workstations can avoid the fixed 8192 MB cap on every supported Node version. Fixes #2213 * fix(launcher): reject unconstrained memory sentinels in heap percentage Ignore libuv UINT64_MAX constrainedMemory values, clamp sub-megabyte percentage results to 1 MB, and keep spaced heap-flag values on background Node launches. * fix(launcher): report unresolved heap percentage instead of silent MB fallback A valid percentage with unknown available memory no longer looks like an unrelated megabyte env or default success; the launcher warns once, docs state full precedence, and child NODE_OPTIONS tests call the real helper. * fix(launcher): strip launcher heap flags when Node already has a cap Commander rejected --max-old-space-size-percentage on native heap+gc and disable-relaunch paths. Strip those argv tokens before import, and do not treat a following non-percentage word as the flag value. * fix(launcher): propagate execArgv heap caps to subprocess NODE_OPTIONS Child tools inherit NODE_OPTIONS, not the parent process.execArgv. When the CLI already started with a numeric --max-old-space-size cap there, copy that value into NODE_OPTIONS instead of defaulting to 8192 MB. Also strip launcher-only heap flags on the OPENCLAUDE_HEAP_RELAUNCHED early-return path and harden launcher integration tests with Node 22-safe execArgv fixtures. |
||
|
|
2e687df4ec |
fix(image): allow large/metadata-less screenshots through on resize failure (#1964) (#1970)
* fix(image): allow large/metadata-less screenshots through on resize failure (#1964) Stop Alt+V paste from falling through to "No image found in clipboard" when the native image connector returns undefined metadata or a screenshot exceeds 1568px. - Reorder the metadata guard so an undefined metadata object is handled before reading metadata.format (previously threw TypeError and was swallowed into a "no image" result). Fall back to magic-byte format detection. - Drop the `!overDim` gate from the catch-block allow-through so a dimensionally-large but base64-small image passes through. The API resizes images >1568px server-side; only the base64 size is a hard limit. - Keep IMAGE_MAX_WIDTH/HEIGHT at 1568; only the crash and gate logic changed. - Add imageResizer.test.ts covering the undefined-metadata path, the overDim<=5MB allow-through regression, the >5MB throw, and the happy path. * fix(image): enforce many-image dimension limit in resize-failure fallback (#1970) When the native image processor crashes, the fallback previously returned the original buffer unchanged whenever its base64 size was <= 5MB, with no dimension check. That let oversized images (e.g. 3840x2160 PNG/JPEG/WebP) stay in conversation history and later break many-image requests with a 400 "image dimensions exceed ... many-image" error. - Add IMAGE_MANY_IMAGE_MAX_WIDTH/HEIGHT (2000px) to apiLimits. - In the resize-failure fallback, detect images exceeding the 2000px many-image limit via magic-byte dimension parsing (PNG IHDR, WebP VP8/ VP8L, JPEG SOF0/2/3, GIF screen descriptor) and, when possible, downsample with platform Canvas APIs (awaiting a fully-decoded source). If downsample is unavailable or the result still exceeds the 5MB payload budget, reject with ImageResizeError instead of returning the oversized original. - Unknown/unparseable dimensions fail safe (treated as exceeding the limit). - Correct two pre-existing WebP dimension-offset bugs surfaced during review. Adds regression tests for PNG/JPEG/WEBP/GIF over the many-image limit. * fix(image): address review - media-type subtype, WebP offsets, Canvas DOM resolve, metadata-less budget - ResizeResult.mediaType is the subtype ('png'/'jpeg'), not a full MIME type. The Canvas downsample branch wrongly returned 'image/png'/'image/jpeg'; corrected to 'png'/'jpeg' to match every other return in the file. - WebP dimension parser corrected against real sharp-encoded frames: VP8L transform header reads from byte 21 (bits[0..13] width, [14..27] height); VP8 lossy width/height read from bytes 26/28. Previous offsets were off and produced wrong dimensions. - Canvas fallback now resolves createElement/Image from document (browser/ Electron) with a globalThis fallback, so it actually engages where Canvas is available. - metadata-less + oversized branch now tries progressive JPEG quality [80,60,40,20] and only returns an in-budget result; otherwise fails via the user-facing limit error instead of returning an oversized buffer that validateImagesForAPI would reject (the regression the PR recovers from). - Add WebP/VP8L fixtures and tests asserting exact parsed dimensions and in-limit acceptance. * fix(image): address 4 P1 review findings in resize-failure fallback - Metadata-less branch now enforces the many-image 2000px limit via a shared enforceManyImageDimensionLimit helper (extracted from the catch-path guard), instead of letting compact high-resolution screenshots pass through unchecked. - VP8 lossy WebP dimensions are stored directly (no -1 bias per RFC 6386); only VP8L uses minus-one encoding. Fixed parser and test fixture. - Add VP8X (extended WebP) dimension parsing, needed for alpha/animation. - DOM Image fallback now constructs with no args and assigns src after installing onload/onerror, since the Image constructor takes width/height, not a URL. * fix(image): require raw dimensions before canvas downsample in many-image guard readImageDimensions can return null for unparseable buffers; falling back to 0,0 made tryDownsampleToManyImageLimit treat the image as 1x1 and silently downsample to a single pixel instead of throwing ImageResizeError. * fix(image): keep in-budget 4K pastes when Canvas resize fallback is unavailable The many-image 2000px guard rejected Windows clipboard screenshots whenever image_processor_napi failed and Canvas was absent, recreating #1964. Pass those images through, invoke DOM canvas methods with their owners, and skip JPEG fill/TEM bytes before reading segment lengths. * fix(image): reject API-hard 8000px edges and surface paste resize errors Passthrough after a processor crash was sending images the API hard-rejects and still mapping ImageResizeError to "No image found". Keep 4K single-image paste, fall through from createImageBitmap to Image, and show the real resize error on Alt+V. * fix(image): document paste resize errors and test native rethrow Clipboard ImageResizeError was still easy to treat as a missing reader on the native path, and the Alt+V vs generic notification contract had no focused tests. Share the paste error helpers, document the 5MB/8000px prompt, and cover native resize rejection without enabling NATIVE_CLIPBOARD_IMAGE. * test(image): stabilize clipboard mock module identity * fix(image): close resize fallback review gaps * test(image): cover JPEG canvas fallback subtype * fix(image): downsample before enforcing API edge * fix(paste): settle rejected image path reads * test(paste): isolate rejection regressions * fix(image): report payload limit after fallback * test(image): restore mocked modules after suites Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(images): enforce many-image limits at Anthropic request boundary Count retained history and tool-result images, verify resized payload dimensions, and report actionable failures before dispatch. Exercise mock restoration deterministically. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(images): preserve resize failure diagnostics at request boundary The many-image validation catch replaced the original image-processing error with a generic RequestImageDimensionsError without logging it or keeping a cause chain, so logAPIError and the assistant error path only ever saw the wrapper message. - Log the underlying processing failure via logError before wrapping. - Rethrow an existing RequestImageDimensionsError unchanged instead of reconstructing it. - Attach the original error as the wrapper's cause; ImageResizeError and RequestImageDimensionsError now forward ErrorOptions to super. - Expand usesAnthropicImageLimits coverage to the reachable isFirstPartyBaseUrl:false input for bedrock/vertex/foundry. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix: count partner documents in request image limits --------- Co-authored-by: jatmn <the@jat.mn> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> |
||
|
|
eb3c5902eb |
feat(skills): add verify for revocations and eyebrow drift checks (#2215)
* feat(skills): add verify for revocations and eyebrow drift checks * apply code reviews fixes * fix(skills): read execute bits through the fs seam, skip symlink tests where unsupported * fix(skills): check X_OK through the fs seam, report symlink tests as skipped |
||
|
|
0b68e579b9 |
fix(profiles): apply context limit to all profile models (#2201)
* fix(profiles): apply context limit to all models * fix(profiles): include saved model in context limits * test(profiles): cover saved model context lifecycle * fix(profiles): align context limit keys with runtime models --------- Co-authored-by: fancivez <fancive@gmail.com> |
||
|
|
1afeb4b1e7 |
docs: add a skills guide (#2211)
* docs: add a skills guide * docs: imprvements on skills doc * docs(skills): address review on the skills guide * docs(skills): correct verify exit code descriptions * docs(skills): state what each check covers * docs(skills): align validate wording and name each install path condition |
||
|
|
ec850d2d1a |
docs(web): add llms.txt for LLM discovery (#2205) (#2209)
* docs(web): add llms.txt for LLM discovery (#2205) Hosts an llmstxt.org-spec summary of OpenClaude at the website root so LLMs (ChatGPT et al.) have an authoritative source and stop conflating the project with unrelated tools named "Openclaw". * docs(web): address review feedback on llms.txt - Clarify the License entry: MIT applies only to OpenClaude contributors' modifications, not the underlying Claude Code (Anthropic proprietary). Addresses Greptile's misstatement flag. - Convert the bare npm one-liner in the Install section into a Markdown linked list entry per the llmstxt.org spec. Addresses CodeRabbit's "link-only sections" note. * fix(web): make AUR entry in llms.txt hyperlink-first The 'AUR package: [openclaude](...)' entry put text between the list marker and the hyperlink, which makes the reference llms.txt parser fail with AttributeError ('NoneType' has no attribute 'groupdict') and aborts parsing of the whole document. Use link-first syntax preserving destination and meaning. Verified with the reference parser (parse_llms_file): 4 sections, 21 links, all URLs correct. web:build passes and web/dist/llms.txt matches source byte-for-byte; web:typecheck clean. --------- Co-authored-by: Gravirei <gravirei@users.noreply.github.com> |
||
|
|
0abfca30e5 |
fix(sdk): preserve async generator session context (#2204)
* fix(sdk): preserve async generator session context * fix(sdk): complete async context boundaries * test(sdk): assert queryAsync init isolation and close V2 sessions. Replace the source-text queryAsync check with a behavioral observation of init, and release concurrent V2 sessions so the async-context coverage matches the runtime contract. |
||
|
|
3609d751c0 |
fix: identify OpenCode Go requests (#2203)
* fix: identify OpenCode Go requests * fix: harden OpenCode Go identity headers * fix: pin OpenCode session across retries |
||
|
|
5b9daef605 |
feat(providers): add Command Code hybrid gateway (#2196)
* feat(providers): add focused Command Code hybrid gateway Register Command Code as a descriptor-first OpenAI-compatible aggregating gateway with a hybrid catalog, dedicated CMD_API_KEY credentials, and the same surgical sibling wiring used by LLMTR. * fix(providers): reject Command Code placeholder keys and restore saved secrets on relaunch Treat CMD_API_KEY placeholders as unusable at request time, and migrate a profile-owned persisted OPENAI_API_KEY into CMD_API_KEY on canonical Command Code restart when dedicated keys are missing. * fix(providers): re-apply Command Code when the dedicated key drifts Profile apply already writes CMD_API_KEY, but alignment only compared OPENAI_API_KEY, so a stale dedicated secret could keep winning request auth. * fix(providers): isolate Command Code from leftover OpenAI keys Selecting --provider commandcode left a distinct generic OPENAI_API_KEY in-process because Command Code shared LLMTR's fallback path. Mirror the dedicated credential or clear the generic key on the canonical endpoint. * fix: enforce Command Code transport contract * fix: apply model override before validation * fix: support inline model overrides * fix: align early model parsing with CLI * fix: address Command Code review feedback * fix: validate Command Code profile models * fix: keep startup model display in sync * fix: avoid echoing rejected Command Code models * fix: share effective model flag parsing * fix: preserve teammate provider overrides * fix: isolate command code startup state * fix: align command code startup state * fix: close command code route gaps * fix: preserve teammate route precedence * fix: align launcher and root option parsing * fix: preserve model and credential semantics * fix: reject Claude aliases on Command Code * fix: preserve root --model after a consumed delimiter Required-value options such as --system-prompt consume a following -- token, so early Command Code model recovery must scan the full argv instead of truncating at the first delimiter. * fix: reject Command Code Claude models in /model and lists The shared Chat Completions predicate only inspected whole strings, and /model still applied OpenClaude aliases. Reject list members and block those selections on the Command Code route. * fix: ignore nested --model when applying --provider Command Code preflight was last-wins on full argv, so a nested aimlapi/auto-mode --model could abort provider selection even when the root session model was valid. * fix: unmount Ink if /model wait times out The Command Code /model tests left the Ink instance registered when waitForCondition rejected, which can leak into later tests. * fix: enforce Command Code contracts at profile write time Reject incompatible models and capture dedicated CMD credentials in toProfile so Console OAuth env adoption and other add/update callers cannot persist Chat Completions-invalid Command Code state. Align background session metadata with the same root --model argv slice used at CLI startup. * fix: persist Command Code dedicated keys over leftover OpenAI env Env adoption that copies OPENAI_API_KEY must not store a generic secret on a canonical Command Code profile when CMD_API_KEY is present. Document that the route still requires explicit selection. * fix: test background session root --model from launch argv Replace the source-text assertion with the shared metadata helper used by handleBgFlag so nested aimlapi/auto-mode --model flags cannot silently become the recorded session model. * fix(commandcode): unify route validation contracts * fix(commandcode): preserve explicit profile credentials * fix(cli): preserve explicit provider routing * fix(cli): align provider option consumers * fix(commandcode): align credential and discovery contracts * docs(commandcode): clarify Claude model support * fix(bg): align persisted provider metadata * fix(commandcode): preserve model and base defaults |
||
|
|
0ea8eefbd0 |
feat(skills): enforce registry revocations.json on install (#2187)
* feat(skills): enforce registry revocations.json on install The registry ships a revocations.json kill switch that nothing reads. Installs now fetch it from beside the registry (or OPENCLAUDE_SKILLS_REVOCATIONS_URL) and refuse a skill whose id, version, or sha256 matches an entry. A missing list revokes nothing; a malformed list fails the install. * fix(skills): fail closed and validate revocation entries Review follow-up: only a confirmed-absent revocation list (ENOENT or HTTP 404) is treated as empty; other read failures block the install. The list must be a JSON array and every entry is schema-checked (non-empty id/version strings, 64-hex sha256, at least an id or a digest). Adds tests for the OPENCLAUDE_SKILLS_REVOCATIONS_URL override, invalid entries, non-array lists, and unreadable lists. * docs(skills): add docstrings to revocation helpers * test(skills): isolate revocation override and cover remote failure paths |
||
|
|
3451187aed |
fix(ctx): let explicit overrides beat discovered context windows (#2082)
* fix(ctx): prefer known model limits over generic 128k discovery Proxies such as OmniRoute often report context_length 128000 when per-model metadata is missing. That discovery value was beating known descriptors and settings.modelLimits, so 1M models auto-compacted against a 128k budget. Prefer larger known descriptors over the generic 128k discovery default, and let settings/env overrides beat discovery. Document the override path in /set-context-window help. Refs #2081 * test(ctx): cover OmniRoute + gpt-5.6-sol (user-reported gpt sol) The Discord report used GPT-5.6 Sol, not gpt-5.4. Regression now pins the catalog-less custom-route rescue to the 272k descriptor for gpt-5.6-sol. Refs #2081 * fix(ctx): let explicit overrides beat discovered context windows Addresses both review findings on #2082. P1 — drop the generic-128k rescue from the previous commit. The custom gateway discovery mapper copies an endpoint's own context_length / context_window into ModelCatalogEntry.contextWindow, and nothing synthesizes 128k on our side, so the numeric value carries no provenance: a flat 128000 is indistinguishable from a real per-deployment or tenant cap. Budgeting a known-larger descriptor over that cap traded an early auto-compact for a late context-window API failure. Discovery is authoritative over the model descriptor again. What remains is the safe half of the fix: an env prefix override and settings.json modelLimits now sit above discovery, so a user on a gateway that advertises the wrong window can pin the real one. Docs and /set-context-window help point at that path. Tests cover a gateway-advertised 128k and 200k window staying put for a model the catalog knows is larger, plus env and modelLimits overrides beating it. P3 — isolate the discovery-cache fixtures. getClaudeConfigHomeDir() ignores CLAUDE_CONFIG_DIR by design, so both the new modelLimits test and the pre-existing withTempConfigDir helper were writing fixtures into the caller's real config dir. Both now use setClaudeConfigHomeDirForTesting with save and restore, and clear the discovery cache on teardown so the in-memory sync snapshot cannot leak into a later suite. The modelLimits test now asserts the isolated cache resolves on its own before asserting the settings pin wins. Refs #2081 * test(ctx): cover env prefix overrides over discovery cache CodeRabbit on #2082 asked for a regression where CLAUDE_CODE_OPENAI_* prefix keys beat a seeded discovery cache for both contextWindow and maxOutputTokens, not just exact keys and settings.modelLimits. Refs #2081 * docs(ctx): Clarify permanent overrides for catalogued models Keep the documented workaround aligned with resolver precedence so users do not expect modelLimits to override built-in catalog entries. Refs #2081 |
||
|
|
aceacf0e59 |
feat(zai): add GLM-5.3-Flash Coding Plan support (#2185)
* feat(zai): add GLM-5.3-Flash Coding Plan support * test(effort): preserve scoped xhigh context * fix(zai): scope Coding Plan route metadata * fix(integrations): prefer explicit runtime endpoints * fix(integrations): preserve override runtime limits |
||
|
|
6c7efdeaf5 |
chore(main): release 0.30.0 (#2165)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>v0.30.0 |
||
|
|
b4af6f1884 |
test(settings): unit-test the multi-source merge customizer (#2176)
* test(settings): unit-test the multi-source merge customizer settingsMergeCustomizer is exported for testing but had no direct coverage, despite being the seam that combines policy, user, and project settings. Cover the documented behaviour: permission arrays concatenate with deduplication under target priority, modelPricing entries replace atomically per model id on a null-prototype map (including Object.prototype-named ids), and all other keys defer to lodash's default deep merge. * test(settings): assert the merged modelPricing map is null-prototype The previous assertion checked a fresh unrelated object, which would pass even if the customizer returned a plain object carrying the keys. Assert Object.getPrototypeOf on the merged map itself. |
||
|
|
aaccb57721 |
fix(integrations): keep managed AIMLAPI attribution over caller headers (#2179)
* fix(integrations): keep managed AIMLAPI attribution over caller headers getRouteDiscoveryHeaders spread options.headers after resolveAimlapiAttributionHeaders had already sanitized the descriptor headers, so caller-supplied values won every collision and undid the sanitizer: a partner id from ANTHROPIC_CUSTOM_HEADERS overrode the one resolvePartnerId had just re-stamped, and attribution headers were re-added after the non-canonical filter stripped them. Fold caller headers into the sanitizer's input so both branches see them, and keep the previous descriptor-then-caller precedence for every non-aimlapi route so ordinary custom headers are unaffected. Refs #2178 * fix(integrations): Strip AIMLAPI attribution headers case-insensitively. Fetch combines duplicate header names, so a caller `x-aimlapi-source` could ride alongside the managed `X-AIMLAPI-Source`. The shared resolver now removes every managed name case-insensitively before restamping a single canonical value, and the proxy path still drops the whole set. Refs #2178 --------- Co-authored-by: euxaristia <euxaristia@users.noreply.github.com> |
||
|
|
5f1ab9b80c |
fix(plugins): anchor marketplace hostPattern against lookalike hosts (#2177)
strictKnownMarketplaces hostPattern entries were compiled with new RegExp(pattern) and applied with regex.test(host). RegExp.test is a substring search, so an admin pattern that is not fully anchored matched any host merely containing it. Host authority reads right-to-left, so this is not just a missing leading anchor: a policy of `github\.mycompany\.com` is satisfied by an attacker-controlled `github.mycompany.com.evil.example`, which a leading `^` alone would still admit. It is also satisfied by `evil-github.mycompany.com`. isSourceAllowedByPolicy gates whether a marketplace may be installed at all, and installation leads to plugin code execution, so a bypass defeats the enterprise lockdown before anything is fetched. Anchor the pattern as `^(?:<pattern>)$` so it must match the entire host. The non-capturing group preserves a top-level alternation (`a\.com|b\.com` must not become `^a\.com|b\.com$`), and a pattern that is already fully anchored — the form the schema documents — behaves exactly as before. This tightens matching, so a deliberately loose pattern that relied on substring behavior now needs an explicit wildcard (`.*\.mycompany\.com`). That is the intended contract, and it can only ever narrow the allowlist, never widen it. The schema description now states the whole-host requirement. pathPattern is deliberately left alone: paths nest left-to-right, so its documented prefix form (`^/opt/approved/`) is correct and anchoring the end would break it. |
||
|
|
8db8830666 |
fix(settings): stop proto-named permission rules from aborting validation (#2170)
getCustomValidation looks a tool name up in the plain-object TOOL_VALIDATION_CONFIG.customValidation with a bare bracket access, so the prototype chain is live. validatePermissionRule gates tool names on an uppercase first character, which rejects the lowercase prototype members (constructor, toString, ...), but the double-underscore members slip through because '_'.toUpperCase() === '_'. A rule like "__proto__(x)" therefore reaches the lookup, which returns the inherited Object.prototype value (or, for "__defineGetter__(x)", a real inherited function). The caller treats that truthy value as a configured validator and invokes it, throwing an uncaught TypeError. filterInvalidPermissionRules runs that validation over every permissions allow/deny/ask entry when a settings.json is loaded. Its whole purpose is to skip one bad rule without rejecting the file, but the throw escapes: for a project .claude/settings.json the caller's catch discards the entire file's settings, and parseCommandOutputAsSettings (MDM) has no catch at all. A single hostile or fat-fingered rule in an untrusted cloned repo's settings thus silently wipes that file's configuration. Guard the lookup with Object.hasOwn so an unrecognized tool name resolves to undefined and the rule validates as an ordinary (inert) rule instead of crashing. Mirrors normalizeLegacyToolName, which already hardened the sibling LEGACY_TOOL_NAME_ALIASES lookup against the same class. |
||
|
|
30090c70e1 |
fix(api): scope Anthropic attribution to compatible request paths (#2147)
* fix(api): scope Anthropic attribution to compatible request paths * test(api): expand Anthropic attribution route coverage * fix(api): align managed OAuth attribution credentials * fix(api): preserve bare attribution auth * fix(api): align SSH proxy attribution auth |
||
|
|
ca7c3efb6e | fix(bg): identify sessions with persisted process markers (#2163) | ||
|
|
e8026263ca |
fix(tui): proper Unicode/IME input handling for composed sequences (#2018) (#2154)
* fix(tui): proper Unicode/IME input handling for composed sequences (#2018) * test(tui): address CodeRabbit review - hook-level IME coverage, full Unicode marks, astral code points, timeout regression tests |
||
|
|
54f963d006 | fix(openai-shim): drop synthetic tool-results marker and guard echoes (#2039) (#2153) | ||
|
|
39c3850c2e |
docs: tighten PR review expectations in CONTRIBUTING and AGENTS guides (#2151)
* docs: tighten PR review expectations in CONTRIBUTING and AGENTS guides - Add drift caveat to CodeRabbit findings: verify suggestions against PR intent before applying; decline out-of-scope with justification or ask a maintainer; never silently ignore findings - Add Keep Your Branch Current subsection (rebase duty, fix-churn warning) - Require full local CI-equivalent suite green before every push, with cross-platform exception - Add ignored/filler PR template submissions to close-without-review list - Expand follow-up guidance: multi-round review is normal, repeated fix requests signal root-cause investigation and better agent prompting - Mirror all of the above in AGENTS.md for coding agents * docs: address CodeRabbit findings on review-expectations guides - Make CONTRIBUTING.md Validation the single authoritative pre-push validation contract mirroring .github/workflows/pr-checks.yml exactly: --frozen-lockfile install, launcher compatibility checks, provider recommendation via npm as CI does, web job carve-out - Remove conflicting 'relevant subset' wording; cross-platform exception is the only carve-out from the full suite - AGENTS.md now defers to the CONTRIBUTING contract instead of defining a divergent core-checks list - Reword ambiguous 'submit the PR template ignored' bullet to 'submit a PR with the PR template ignored' * docs: align pre-push validation suite with CI semantics - Drop standalone 'bun run test:full'; bun run check already includes it - Document web workspace install (bun install --cwd web --frozen-lockfile) before web checks, matching the web CI job - Pass explicit --base/--head to security:pr-scan so local scans target the PR merge-base like CI does instead of script defaults * docs: use PR base commit ref for local security scan parity Replace git merge-base computation with origin/main and document the required invariant (fetch + keep branch rebased onto current origin/main) so the local scan matches CI's PR base.sha instead of diverging. * docs: use exact PR base for security scan * docs: make local validation contract portable * docs: scope local checks and baseline waivers * docs: harden contributor workflow guidance * docs: pin contributor safety contracts |
||
|
|
69aca780ea |
fix(effort): preserve known model exclusions when force-enabled (#2148)
* fix(effort): preserve known model exclusions when force-enabled * fix(effort): address review feedback * test(effort): cover provider-scoped capability overrides * fix(effort): preserve route-specific reasoning controls * fix(effort): honor scoped routing environment |
||
|
|
34536c6220 |
fix(settings): preserve concurrent updates (#2137)
* fix(settings): preserve concurrent updates Serialize the complete settings read-merge-write transaction under a physical-target lock with a bounded synchronous contention wait. Read the merge base fresh after ownership, preserve logical symlinks during publication, and route direct settings-sync replacements through the same lock. * fix(settings): address transaction review feedback * fix(settings): reject invalid merge bases * fix(settings): preserve lock ownership and apply outcomes * fix(settings): address transaction follow-up * fix(settings): avoid unsafe cleanup control flow * fix(settings): publish complete lock claims * test(settings): document pending lock claim * fix(settings): track lock owner process identity |