mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 02:34:15 -05:00
db66f41071cce118eaffc301e392b4576d07ff5b
921
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
db66f41071 |
fix(resume): tolerate malformed legacy attachment records (#1768)
* fix(resume): tolerate malformed legacy attachment records * test(resume): allow resume hook messages in recovery test * fix(resume): use validated attachment for skill listing |
||
|
|
b8c34645c9 |
chore(deps): clean npm install — fix CVEs, silence warnings (#1782)
* chore(deps): clean npm install — fix CVEs, silence warnings - bump undici 7.24.6 → 7.28.0 (7 high CVEs: TLS bypass, header injection, DoS, cache poisoning, SameSite downgrade, cross-origin routing) - bump ws 8.20.0 → 8.21.0 (2 high CVEs: uninitialized memory disclosure, memory exhaustion DoS) - add allowScripts for sharp + protobufjs to silence install-script warnings - vendor node-domexception shim (re-exports native DOMException) and override the deprecated polyfill pulled transitively by google-auth-library → gaxios → node-fetch@3 → fetch-blob Result: `npm install` reports 0 vulnerabilities, 0 warnings. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * chore(deps): update bun.lock for undici/ws bumps and node-domexception override CI runs `bun install --frozen-lockfile`, which requires bun.lock to match package.json. The previous commit bumped undici/ws and added the node-domexception shim override but didn't include the regenerated lockfile, causing frozen-lockfile CI to fail. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(publish): include vendor/node-domexception-shim in npm tarball The file: override in package.json points at vendor/node-domexception-shim, but the files array didn't list vendor/, so npm pack excluded it. End-user npm installs would fail resolving the override. Add vendor/node-domexception-shim/ to the files array. Verified via npm pack --dry-run: tarball now contains both shim files (12 → 14 files). Addresses reviewer finding #1. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com> |
||
|
|
bd00b3b3c5 |
fix(bg): stream session logs with bounded memory (#1762)
* fix(bg): stream session logs with bounded memory * fix(bg): handle log follow cleanup edge cases * fix(bg): surface non-follow log read errors * test(bg): isolate log streaming temp dirs |
||
|
|
d32b6f0476 |
fix(update): stop false "development build" block on npm installs with NODE_ENV=development (#1781)
`/update` and `openclaude update` reported "Auto-update is unavailable for
a development build." even when OpenClaude was correctly installed from npm,
as long as the launching shell had `NODE_ENV=development` exported.
Root cause: `getCurrentInstallationType()` checked `NODE_ENV === 'development'`
as its first branch, before any path-based detection. A user's shell env var
then downgraded a real npm install to 'development', which routed
`resolveUpdateStrategy()` to `{ action: 'blocked', reason: 'development' }`.
Two-part fix:
1. doctorDiagnostic.ts — move the `NODE_ENV === 'development'` check to a
fallback position after all real-install path markers (bundled mode, local
npm, npm-global paths, /npm/, /nvm/, `npm config get prefix`). Path
detection runs first; NODE_ENV only classifies as 'development' when no
install path matches (i.e. an actual source-tree `bun run dev` run).
2. bin/openclaude — the heap-sizing relaunch previously used
`fileURLToPath(import.meta.url)`, which resolves symlinks. After relaunch,
`process.argv[1]` pointed at the real file target (repo path for
`npm install -g .`, package path inside node_modules for real installs),
defeating path-based detection. Preserve `process.argv[1]` (the original
invocation path, e.g. /usr/local/bin/openclaude or nvm bin symlink) so
npm-global path markers can match correctly.
Verified: `bun run typecheck` passes; `openclaude doctor` now reports
npm-global (not development) with NODE_ENV=development set on a real
npm global install.
Co-authored-by: OpenClaude <openclaude@gitlawb.com>
|
||
|
|
28bbec4948 |
fix(memory): bound memory-directory scanning work (#1757)
* fix(memory): bound memory-directory scanning work * fix(memory): harden bounded memory scanning follow-up |
||
|
|
701b68c215 |
fix(query): prevent spurious Windows interruption prompt by passing 'interrupt' reason (#1733)
* fix(query): prevent spurious Windows interruption prompt by passing 'interrupt' reason
* fix(cli): pass 'interrupt' abort reason and add stop-hook regression tests
* test(query): exercise handleStopHooks abort-reason branch directly
Adds focused coverage that imports and runs handleStopHooks() while a
Stop-hook generator is being consumed, asserting abort('interrupt')
suppresses the synthetic '[Request interrupted by user]' message and a
default abort still yields it. Closes jatmn's P2 regression request.
|
||
|
|
669ecdfa8b |
fix: prevent recursive debounce infinite loop in team memory sync (#1726)
* fix: prevent recursive debounce infinite loop in team memory sync * fix: chain cap-reschedule push after in-flight promise instead of running concurrently * fix: preserve currentPushPromise when replaced during yield point + tests * fix: clear pending debounce timer in _resetWatcherStateForTesting * fix(teamMemorySync): prevent concurrent pushes, make clearing identity-safe, and avoid duplicate follow-ups * fix(teamMemorySync): guard capped follow-up push against suppression; fix test gaps Addresses jatmn's three P3 findings on #1726. - watcher.ts: the capped reschedule path queues a serialized follow-up executePush() without consulting pushSuppressedReason, whereas schedulePush() short-circuits on suppression. If a permanent failure set suppression while the in-flight push was running, the queued follow-up fired one redundant, identically-failing call. Skip executePush() when pushSuppressedReason is set, mirroring schedulePush(). Adds a regression test (mutation-checked: removing the guard makes it fail). - watcher.test.ts: the "resets to 0 when executePush completes" test was vacuous — rescheduleCount started at 0 and executePush() never owns that reset (onDebounceFire and _resetWatcherStateForTesting do). Renamed to "clears pushInProgress when executePush completes" and dropped the misleading rescheduleCount assertion; the reset path stays covered by the onDebounceFire cap test. - watcher.test.ts: the top-level mock.module('./index.js') is process-global and mock.restore() does not undo it. Follow the spawnCtxAgent.test.ts pattern — (re)register the mock in beforeEach and restore the real module in afterEach — so it can't silently bleed into a future test that imports teamMemorySync/index. |
||
|
|
02d43b6942 |
fix: surface swallowed error in plan file write (#1725)
* fix: surface swallowed error in plan file write * test: add regression test for plan file write failure path * test(ExitPlanModeV2Tool): scope fs mock to single test and pass full call signature Two CodeRabbit concerns: 1. The global fs/promises mock in beforeAll was leaking into unrelated test suites (e.g. loadAgentsDir.test.ts) because mock.module() cannot be cleanly undone mid-suite. Move the mock into the single test that needs it and call mock.restore() in a finally block so it is torn down as soon as the assertion completes. 2. ExitPlanModeV2Tool.call() was invoked with 2 args but the Tool base class signature requires 4 (input, context, canUseTool, parentMessage). Pass the two extra args so the test typechecks against the real signature. * fix(test): prevent fs/promises mock leak from ExitPlanModeV2Tool test Addresses jatmn's P2 on #1725. The previous test used mock.module('fs/promises', ...) in a try/finally with mock.restore(). On Linux CI the mock was still active when loadAgentsDir.test.ts ran, causing all five agent-fixture tests to fail with "write failed" traces pointing back to this file. Fix: add afterEach(mock.restore()) to guarantee the mock is torn down after each test, regardless of pass/fail path. Verified locally by running both test files in the same process — 6/6 pass. * fix(test): avoid fs/promises mock entirely to prevent CI leak The afterEach(mock.restore()) approach still leaked the fs/promises mock into loadAgentsDir.test.ts on Linux CI (CodeRabbit comment at 12:12 EST). Bun's mock.module on Linux replaces the module cache in a way that persists across test files in the same process. Fix: don't mock fs/promises at all. Instead, re-mock plans.js to point getPlanFilePath at a nonexistent directory, so the REAL fs/promises.writeFile rejects with ENOENT. Nothing to leak. Verified: both test files pass in both orderings with --max-concurrency=1. * Fix race on exiting plan mode by saving plan file before permission updates and add regression tests asserting no side effects on error * fix(test): completely isolate mock module by using teammate context APIs instead of module mock override * test(plan-mode): cover the React write-before-permission guard; drop global fs/promises mock Addresses jatmn's two P2 findings on #1725. - Extract the plan-file write guard from ExitPlanModePermissionRequest's handleResponse into an exported persistPlanFileBeforeExit() helper (jatmn suggested extraction for testability). The component path is unchanged: on write failure it stays in plan mode (returns early) and queues a 'plan-save-error' notification. Adds focused tests for both success and write-failure (mutation-checked: a helper that swallows the failure fails the test). Tests use real filesystem paths — a temp file for success and a path with a missing parent dir for failure — so they need no fs/promises mock. - ExitPlanModeV2Tool.test.ts: replace the process-global mock.module('fs/promises') in both write-failure tests with the same real-failing-path mechanism (a plan path whose parent dir does not exist → genuine ENOENT). This removes the fragile core-module mock jatmn flagged, which mock.restore() cannot undo and which could leak into other test files. * test(plan-mode): assert the specific ENOENT write failure in ExitPlanModeV2Tool tests Addresses CodeRabbit's review on #1725: the two write-failure tests asserted a generic rejects.toThrow(), which would pass on any error. Tighten both to rejects.toThrow(/ENOENT/) so they lock in the intended missing-parent-dir write failure rather than masking an unrelated error. The post-write side-effect assertions (persistFileSnapshotIfRemote / writeToMailbox / setAppState not called) are unchanged. * test(plan-mode): add rendered guard test for handleResponse write failure Addresses jatmn's P3 on #1725: the only coverage for the write-before-permission guard was at the persistPlanFileBeforeExit helper level; there was no test that handleResponse itself returns early on write failure. Renders ExitPlanModePermissionRequest (following the MonitorPermissionRequest harness), points the V2 plan file at a path whose parent dir is missing (real ENOENT), confirms the first accept option, and asserts the plan-save-error notification is queued while toolUseConfirm.onAllow / onReject and onDone are NOT called. Mutation-checked: removing the `if (!saved) return` guard makes it fail, so a future refactor cannot silently drop the guard. * fix(test): type addNotification mock so render test typechecks CodeRabbit (correctly) flagged that `mock(() => {})` infers a zero-arg tuple, so `call[0]` was TS2493 and `bun run typecheck` (CI) failed. Type the mock param with the real `Notification` type, which also lets the assertion drop its cast. |
||
|
|
adcf5e5839 |
fix(permissions): bound the speculativeChecks cache with FIFO eviction (#1724)
Internal/upstream defensive maintenance: cap the speculativeChecks Map at MAX_SPECULATIVE_CHECKS_SIZE (1000) and FIFO-evict the oldest entries after each insert, so the bash-classifier speculative cache can't grow unbounded when the classifier path is active (it is a stub in the open-source build; this guards the upstream build where it is reachable). Pure cache-bounding — no runtime or permission behavior change, and no new env vars. Adds focused FIFO-eviction regression tests via a small `_test` surface (mutation-checked: neutering eviction fails them). Rebased onto current main. |
||
|
|
3fb718f403 |
fix(worktree): base agent isolation worktree on parent HEAD, not origin/main (#1652)
* fix(worktree): base agent isolation worktree on parent HEAD, not origin/main createAgentWorktree delegated to getOrCreateWorktree with no base, so the non-PR path checked out origin/<defaultBranch> whenever that remote-tracking ref existed locally. An isolated agent (isolation: "worktree") is expected to see the same committed state as the parent session, but instead got an older tree and missed files that exist only on the active branch. Resolve the parent session's HEAD from the session cwd and thread it through getOrCreateWorktree as a new `baseRef` option (used verbatim, then rev-parsed to a SHA). Falls back to the prior origin-based behavior when HEAD can't be resolved (e.g. a repo with no commits). EnterWorktree/PR worktree paths are unchanged. Fixes #1586 * test(worktree): make agent-base regression test hermetic; add explicit cwd seam The integration test went through createAgentWorktree's ambient getCwd(), which reads process-global cwd state. bun runs test files concurrently in one process and a sibling test mutates that global cwd, so the agent-base test raced and failed in the full suite (worktree based on origin/main, missing the feature-only file). Add an optional `cwd` to createAgentWorktree, used for both the canonical git-root and the parent-HEAD lookups (defaults to getCwd()). The test pins it explicitly so it no longer depends on the raced global cwd. Verified it passes isolated and in the full src/utils suite, and still fails on the pre-fix code. * test(worktree): isolate agent-base regression from leaked module mocks Per review: the regression test imported createAgentWorktree (and thus worktree.ts's execFileNoThrow.js dependency) at module load, so it could bind process-global bun mock.module state left by a neighboring suite before establishing its own isolation. Hold the shared mutation lock for the whole test so it never runs interleaved with suites that mock execFileNoThrow.js, and import the worktree module only after the lock is held, via a cache-busted dynamic import, so the binding resolves against the real module. * test(worktree): use execa and a longer timeout in the agent-base regression Per AGENTS.md, src/utils tests shell out via execa rather than child_process, so swap the git() helper to execaSync. The test also performs real git work (init, commit, branch, worktree add) that can exceed Bun's default 5s timeout on slower/Windows runners, so give it an explicit 15s budget. * test: stop two suites leaking partial module mocks process-wide Bun's mock.module is process-global and is not reverted by mock.restore(), so a partial-surface mock left active by one suite breaks any later suite that imports the same module — it sees a module missing the un-mocked exports and fails to link with "named export not found". - setupGitHubActions.test.ts mocked config.js with only saveGlobalConfig. Spread the real surface (as it already does for execFileNoThrow/browser) and restore it in afterEach, so config.js keeps its full surface for later suites. - osc.test.ts mocked execFileNoThrow.js and tempfile.js with partial stubs and never restored them. Spread the real surfaces and gate each overridden export on an active-suite flag, so when osc's tests aren't running the exports delegate to the real implementation instead of leaking stubs. This unblocks worktree.agentBase.test.ts (which loads worktree.ts → config.js / execFileNoThrow.js) when it runs after either suite. * Revert mock-isolation changes that regressed the full test suite The previous commit reworked the leaking config.js / execFileNoThrow.js mocks in setupGitHubActions.test.ts and osc.test.ts (and swapped the worktree test helper to execa). While it fixed the two-file reproductions, it regressed the full smoke-and-tests run: createAgentWorktree began failing with "not in a git repository" in the combined suite. Revert to the last green state while a more robust isolation approach (process-level isolation for the real-git test) is worked out. * test(worktree): run the agent-base regression in an isolated process The previous in-suite isolation attempts couldn't survive the full test run: createAgentWorktree shells out to git via execFileNoThrow.js, which other suites mock with `mock.module` — a process-global override Bun cannot reliably revert, so any leaked stub made the test fail with "not in a git repository" depending on suite ordering. Move the actual createAgentWorktree call into a standalone child process (worktree.agentBase.fixture.ts) that loads only the real modules, and keep the git repo setup and assertions in the test using real git directly. Nothing the shared test process mocks can reach the child, so the test is now order- and leak-independent. Verified passing in isolation, alongside the two leaking suites that previously broke it, and in the full suite. * test(worktree): register agent-base fixture as a knip entrypoint The agent-base regression runs createAgentWorktree in a standalone child process (worktree.agentBase.fixture.ts) to escape leaked module mocks. knip sees no static importer for it — it is spawned via execFileSync — and the deadcode check fails it as an unused file. It is a genuine process entrypoint, so add the *.fixture.ts glob to knip's entry list. |
||
|
|
66ddbece19 |
fix(bridge): match loopback hostname exactly in HTTPS credential guard (#1760)
* fix(bridge): match loopback hostname exactly in HTTPS credential guard
The bridge requires HTTPS for non-localhost base URLs to protect OAuth
credentials in transit, but the guard decided "localhost" via substring
checks (`baseUrl.includes('localhost')` / `.includes('127.0.0.1')`). Any
remote URL that merely contains that text — `http://evil.example.com/localhost`,
`http://evil.localhost.com`, `http://127.0.0.1.evil.com` — slips past the
check and is allowed to carry credentials over plain HTTP.
Extract `isLocalhostBaseUrl`, which parses the URL and matches the hostname
component exactly (mirroring the logic already in `buildSdkUrl`), and route
both guard sites plus `buildSdkUrl` through it. A malformed URL is treated as
non-local so the HTTPS requirement still applies.
Add direct coverage for the helper, including the substring-bypass cases.
* fix(bridge): reject mixed-case HTTP scheme in credential guard
The HTTPS credential guard tested `baseUrl.startsWith('http://')`, which only
matches the exact lowercase scheme. `HTTP://example.com` / `Http://example.com`
slipped past — the URL parser normalizes the scheme to `http:`, so credentials
would still be sent over plaintext.
Fold the scheme test into the parse: add `isInsecureHttpBaseUrl`, which reads
`new URL(...).protocol === 'http:'` (case-normalized) and requires a non-local
host, and route both guard sites through it. Malformed URLs return false.
Extend coverage with mixed-case HTTP/HTTPS cases.
|
||
|
|
39604fe871 |
chore(main): release 0.20.0 (#1684)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>v0.20.0 |
||
|
|
adafde30fa |
feat(integrations): add GLM 5.2 as an Opengateway-routed model (#1772)
* feat(integrations): add GLM 5.2 as an Opengateway-routed model Add an `opengateway-glm-5.2` catalog entry (apiName `z-ai/glm-5.2`) to the gitlawb-opengateway gateway, reusing the existing `glm-5.2` model descriptor. Routes GLM 5.2 through the credit-billed Opengateway (OpenRouter upstream) alongside the existing direct Z.AI vendor path. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * test(model): add z-ai/glm-5.2 to the opengateway picker fixture The model-picker option-order assertion enumerates the gitlawb-opengateway catalog; add the new z-ai/glm-5.2 entry (after qwen/qwen3.7-max) so it matches the catalog. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com> |
||
|
|
1b1dfcfdbc |
Feat/ads sponsored tips (#1674)
* initial commit * feat(ads): passive per-turn earning tips + mask earn code - Gitlawb earning tips now appear on a per-turn cadence (default every 2nd tip slot, OPENCLAUDE_ADS_TIP_EVERY-tunable) for opt-in users, bypassing the per-startup sponsored gate that only ever showed one ad per session. - Each shown earning tip fetches a real impression and confirms after the dwell, crediting opengateway credits; fails silent so ads never break the CLI. - Mark /ads isSensitive so the earn code is redacted from history. * feat(ads): mask earn code via paste dialog; never accept it inline /ads on always opens a masked TextInput dialog (mask="*") — the code is never typed inline, because the terminal echoes inline args as you type (isSensitive only redacts history after submission, not the live echo). An inline /ads on <code> now also opens the dialog and warns that the typed code is exposed and should be rotated. Converts /ads to a local-jsx command. * fix(ads): address CodeRabbit review on PR #1674 - ads.ts: hard 5s timeout (AbortController) on both fetchNextTip and confirmTip so a stalled connection can never hang the spinner-tip path; the abort timer is unref'd. (fetchWithProxyRetry forwards init.signal and treats AbortError as non-retryable.) - gitlawbEarn.ts: unref the best-effort confirm timer so it can't keep a short-lived CLI run alive for the dwell window. - ads.tsx: clear the stored earnCode on `/ads off` (it's a credential, no reason to keep it at rest after opt-out); fix the `/ads on` doc comment to match the always-masked-dialog behavior. - ads.test.ts: restore ADS_BASE_URL in afterEach; add submit/cancel coverage for the masked dialog (enables + persists code / cancellation message) and assert `/ads off` clears the code. - gitlawbEarn.test.ts: restore ADS_BASE_URL + OPENCLAUDE_ADS_TIP_EVERY in afterEach to stop env leaking across suites. - tipScheduler.test.ts: cover the earning-tip branch precedence in getTipToShowOnSpinner (driven via the existing config mock + env, no module mock — avoids the bun mock.module cross-file leak). Testing: tsc clean; tips+ads suite 36 pass; smoke green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * feat(ads): contextual sponsored tips — share the latest prompt for ad matching Switches the earning tips from generic to contextually-matched ads. When the viewer enables sponsored tips (the /ads on dialog now discloses this), the client sends their most recent prompt to the ads service, which matches a relevant ad via Gravity's contextual endpoint. - types.ts: TipContext gains `latestUserMessage` (opt-in earning tip only). - REPL.tsx: extract the latest user message at the spinner-tip pick site and thread the full TipContext into tip.content() (previously only {theme}). - gitlawbEarn.ts: pass ctx.latestUserMessage to fetchNextTip. - ads.ts: new sanitizeForAds() redacts secrets/JWTs/emails/long hex and truncates to 500 chars; fetchNextTip POSTs { context:{messages:[user]} } when a prompt is present (else GET, identity-only). - ads.tsx: /ads on dialog + enable message disclose that the recent prompt (secrets redacted) is shared with the ad partner — consent folded into enabling. Privacy: explicit disclosed consent, minimal context (last prompt only), sanitized client-side (the ads service re-bounds size server-side too). Testing: tsc clean; new ads.test.ts (7 sanitize cases); ads+tips suite 43 pass; smoke green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(ads): show the real advertiser + ad click URL in earning tips renderEarningTip hardcoded the Gitlawb name + gitlawb.com URL, so every served ad rendered as "Sponsored · Gitlawb — … gitlawb.com" and discarded the actual ad's `name` and `link`. Pass the served ad's advertiser name and click URL (Gravity's tracker — required for click attribution/payout) into the renderer; fall back to Gitlawb only for the static no-ad line. Testing: tsc clean; tips suite 16 pass; smoke green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * feat(ads): hyperlink the advertiser name instead of printing the tracker URL Sponsored/earning tips render the advertiser as a terminal hyperlink to the ad's click URL (Gravity's tracker) via a shared renderSponsorLink helper, rather than printing the long tracker URL inline. Clicks still route through the tracker, so attribution/payout are unchanged. Testing: tsc clean; tips/ads suite 46 pass (incl. tipLink); smoke green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(ads): address CodeRabbit review on the contextual ads changes Security: - tipLink.ts: sponsor name/url are advertiser-controlled (untrusted). Strip C0/C1 control chars from the name and accept only http(s) URLs (parsed via URL()), preventing terminal escape-sequence injection and javascript:/file: click targets. - ads.tsx: soften the consent copy to "best-effort secret redaction" (both the dialog and the enable message) — sanitizeForAds is heuristic, not absolute. Correctness: - ads.ts: clamp dwell_ms to a finite, non-negative integer (malformed values no longer yield NaN/Infinity in the confirm-delay math). - gitlawbEarn.ts: never point a third-party advertiser name at the Gitlawb URL — a real ad uses only its own click URL; the Gitlawb fallback is reserved for the static no-ad line. Tests + isolation: - ads.test.ts: cover fetchNextTip/confirmTip (POST-with-context vs GET, !ok→null, ad:null→null, dwell clamp, confirm normalization) via a stubbed fetch. - tipLink: renderSponsorLink takes an injectable `hyperlinks` flag so both branches are deterministically tested; added control-char/unsafe-URL cases. - gitlawbEarn.test.ts: restore global `ads` config in afterEach. - tipScheduler.test.ts: preserve OPENCLAUDE_ADS_TIP_EVERY in cleanup. Note: CodeRabbit's packages/memory/* findings are moot — that stray package was removed from the branch. Testing: tsc clean; ads+tips suite 56 pass; smoke green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(ads): address CodeRabbit follow-up review (round 2) - ads.tsx: remove warmOneEarn — it fetched AND confirmed a tip during /ads on that the user never saw, crediting an unshown impression (and could confirm after a quick opt-out). Earning now happens only on the per-turn rendered-tip path. Also drop the unsupported "Run /ads for your balance" instruction. - ads.ts: normalize confirm amounts (earned_micro/balance_micro) to finite integers via toFiniteInt, mirroring the dwell_ms clamp. - gitlawbEarn.ts: degrade to the static fallback when tip_text is blank, so a malformed ad never renders an empty line and credits a blank impression. - ads.test.ts (commands): restore global ads config in afterEach; assert the inline-code exposure warning (warnExposed) is shown. - ads.test.ts (services): build the AWS-shaped fixture at runtime so it isn't flagged by security:pr-scan; add a success-path test for buildEarningTip() rendering a fetched ad + a blank-ad fallback test. Testing: tsc clean; ads+tips suite 58 pass; smoke green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(ads): address CodeRabbit full review (round 3) + comments Blocking: - ads.ts: gate the serve response on a string `token` (the signed impression), not on an `ad` field. A served tip carries NO `ad` key, so CodeRabbit's suggested `data.ad == null` would have suppressed every real tip; the empty slot (`{ ad: null }`) and malformed responses both lack a token, so the token check covers them correctly. Documented the contract. - gitlawbEarn.ts: drop the dead try/catch around fetchNextTip — it is contractually non-throwing (catches everything, returns null), so the wrapper was unreachable/misleading. - ads.ts: make the base64-blob redaction boundary reliable — \b is meaningless around + and / (both \W), so bound the run with explicit look-around instead. Comments / clarity (per review): - ads.ts: note withAbortTimeout is a per-CALL deadline (shared across retries), not per-attempt. - tipLink.ts: clarify the CONTROL_CHARS_RE comment — the ESC/BEL literals above are intentional terminal-sequence constants, not part of the matcher. - config.ts: document that GlobalConfig.ads is managed via /ads and intentionally excluded from GLOBAL_CONFIG_KEYS (earnCode is a credential). (resetEarningCadenceForTesting export is an accepted in-repo pattern — no change.) Testing: tsc clean; ads+tips suite 58 pass; smoke green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com> |
||
|
|
9c0d5c61e2 |
fix(deps): remove deprecated uuid install path by replacing vertex-sdk with local client (#1771)
* fix(deps): remove deprecated uuid install path * fix(api): address PR #1232 review — type the local Vertex client surface Resolves the blocker raised by @Vasanthdev2004, @gnanam1990, and @jatmn: the in-repo AnthropicVertex replacement compiled under bun (no type-check) but added 4 `tsc --noEmit` errors that the upstream typed SDK did not. - Declare `messages`/`beta` as typed class fields (BaseAnthropic doesn't, but the upstream @anthropic-ai/vertex-sdk client did), so typed consumers — client.ts `new AnthropicVertex(...)` and the SDK calling `.messages` — keep the resource surface. (vertexClient.ts:145/146, test:53) - Widen the header-merge helpers to accept the base client's request header type (HeadersLike), and handle the NullableHeaders shape it actually passes so the merge stays correct, not just type-clean. (vertexClient.ts:182) Also drops the now-stale `@anthropic-ai/vertex-sdk` entries left behind by the dependency removal: - scripts/externals.ts INTENTIONALLY_BUNDLED (P3) - knip.json ignoreDependencies Testing: `tsc --noEmit` clean; vertex/client/gemini tests 51 pass; smoke green (INTENTIONALLY_BUNDLED back in sync, 57 entries); knip clean. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(api): address CodeRabbit review on PR #1232 — auth precedence + coverage - Security (vertexClient.ts): merge resolved Google auth headers LAST so a caller-supplied Authorization / x-goog-user-project can't override the Vertex credential and send the wrong token upstream. Other request headers still pass through unchanged. - Tests (vertexClient.test.ts): add focused regression coverage for the previously-unguarded routing/auth branches — * streaming → :streamRawPredict path (+ model stripped, stream preserved) * count_tokens → count-tokens:rawPredict path rewrite * auth-header precedence: caller Authorization does NOT override the Vertex token (guards the fix above + exercises the NullableHeaders merge branch). Testing: tsc clean; vertexClient tests 5 pass; full src/services/api 840 pass; smoke + knip green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(api): validate and encode Vertex model before building the URL CodeRabbit follow-up on PR #1232: `model` was interpolated straight into the Vertex endpoint path, so a missing/non-string model would silently route to `.../models/undefined:rawPredict` instead of failing fast. Now throw a clear error on a missing/empty model and encodeURIComponent the value before building the path. Adds a focused test for the missing-model case. Testing: tsc clean; vertexClient tests 6 pass; smoke + knip green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(api): address remaining review items from PR #1232 - [P2] Fix count_tokens method guard: apply method==='post' to both paths - [P3] Remove unused accessToken option from AnthropicVertex - [P3] Narrow batches type on messages/beta resources with Omit * test: add count_tokens?beta=true routing regression test --------- Co-authored-by: Kevin Codex <kevin@gitlawb.com> Co-authored-by: OpenClaude <openclaude@gitlawb.com> Co-authored-by: Gravirei <gravirei@users.noreply.github.com> |
||
|
|
3eb57c6d13 |
fix: upgrade shell-quote 1.8.3 -> 1.8.4 (CVE-2026-9277) (#1764)
Co-authored-by: Gravirei <gravirei@users.noreply.github.com> |
||
|
|
4dee44a542 |
feat(ux): long-turn visibility + default-on stream hang safety net (#1758)
Long-running turns looked frozen ("are you still working?") and a dropped
network stream could hang for the full 5-minute QueryGuard idle timeout.
Three focused changes:
- Spinner: show the elapsed-time counter at 5s instead of 30s (split the
timer gate from the token-count gate). The timer is wall-clock derived,
so it keeps ticking during long tool calls — proof of life even when no
tokens stream.
- Spinner: surface the currently-executing tool name in the status line
(reusing the spinnerSuffix channel; stop-hook progress still wins). A
long subagent/typecheck now reads "(↓ Bash · 1m 20s)" instead of a bare
spinner.
- claude.ts: enable the stream idle-timeout watchdog by default, matching
the always-on read-timeout already used by the OpenAI/Codex shims. A
silently dropped Anthropic-family stream now aborts and falls back to a
non-streaming retry within STREAM_IDLE_TIMEOUT_MS (90s) instead of
hanging to 5 minutes. Opt out with CLAUDE_DISABLE_STREAM_WATCHDOG=1.
Testing: tsc --noEmit clean; bugfixes.test.ts (31) and src/services/api
(835) pass; bun run smoke builds + runs.
Co-authored-by: OpenClaude <openclaude@gitlawb.com>
|
||
|
|
bcf9421824 |
feat(permissions): allow npm/bun/tsc --version as read-only (#1759)
`node --version` and `python --version` are auto-approved as read-only Bash, but `npm`, `bun`, and `tsc` version queries were missing from the allowlist, so they fell through to a permission prompt. Add them in the same exact-anchored form (no trailing args) so a version flag can't smuggle a script-running suffix past the check (the `node -v --run <task>` class of bypass). Closes the only read-only gap that the now-superseded #787 classifier covered, without a parallel classification surface. Testing: new readOnlyValidation.test.ts (18 cases — allows -v/--version, rejects install/suffixed forms); tsc clean; BashTool suite 117 pass. Co-authored-by: OpenClaude <openclaude@gitlawb.com> |
||
|
|
dc6a7781bf |
feat: auto-detect and persist project conventions to wiki (#1010)
* feat: auto-detect and persist project conventions to wiki Adds a convention scanner that reads project config files (package.json, tsconfig.json, eslint, prettier, Dockerfile, CI workflows, lockfiles) on startup and saves extracted conventions to .openclaude/wiki/pages/conventions.md. Includes a fingerprint cache to avoid redundant writes and a /wiki scan command for manual re-scans. New modules: - src/services/wiki/conventions.ts — scanner + cache + save - src/services/wiki/identity.ts — project identity (name, languages, monorepo) - src/services/wiki/conventions.test.ts — 7 tests Modified: - paths/types/init/status — extended wiki infrastructure for conventions - wiki.tsx/index — added /wiki scan command - main.tsx — fires scan via startDeferredPrefetches - init.test.ts/status.test.ts — updated for new conventions page/fields Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(wiki): address PR #1010 review — trust gate, cache, pre-init, indexing Resolves the three blockers raised by @jatmn and @Vasanthdev2004, plus a post-rebase integration gap. 1. Trust gate (blocker): the startup convention scan ran git (via project identity) before workspace trust was established. Now gated behind the same check as prefetchSystemContextIfSafe — runs only when non-interactive (implicit trust) or the trust dialog has been accepted. 2. Cache fingerprint (blocker): computeFingerprint hashed only the detected config sections, so identity-only changes (project name, language counts, monorepo, default branch) left conventions.md stale. Identity inputs are now folded into the fingerprint. (Hashing the rendered markdown isn't viable — it embeds a "Last scanned" timestamp.) 3. /wiki scan pre-init crash (blocker): forceScanConventions caught the page write failure but still wrote the cache, throwing ENOENT before /wiki init. It now only writes the cache on a successful page write and reports saved=false; /wiki scan surfaces "run /wiki init first". 4. Index integration: the conventions page is written to pages/, but the wiki index (rebuildWikiIndex, added after this PR was opened) was never refreshed, so the new page wasn't listed. Both save paths now reindex on success. Testing: 3 new regression tests (identity-only re-save, pre-init no-crash/no-cache, reindex); wiki suite 14 pass; tsc clean; smoke + knip green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(wiki): address CodeRabbit review on PR #1010 - main.tsx: add a terminal .catch(logError) to the deferred conventions-scan promise chain so a failed dynamic import or scan can't become an unhandled rejection on the startup path. (Major) - conventions.ts: narrow both page-write catch blocks to ENOENT (= wiki not initialized → skip); rethrow other failures (EACCES, EROFS, …) instead of masking them as "not initialized". (Major) - identity.ts: replace blocking execFileSync('git', …) with async execa (the service-layer subprocess convention); getProjectIdentity is now async, awaited in scanProjectConventions. Keeps the startup scan off the event loop. - commands/wiki/index.ts: restore `ingest` in the argumentHint — `[init|status|scan|ingest <path>]` — it's still an implemented subcommand. Testing: tsc clean; wiki suite 14 pass; smoke + knip green. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com> |
||
|
|
a23014b260 |
fix(atlas-cloud): sync static catalog with live /models metadata (#1754)
- Enrich every entry with maxOutputTokens (from max_output_length) and
capabilities (function calling, json mode, vision, reasoning) pulled from
the live catalog. Addresses discoveryService metadata gap.
- Add transportOverrides.openaiShim.removeBodyFields for xai/grok-build-0.1
to drop reasoning_effort (fixes 400 on Atlas Cloud).
- Curate current model set:
- Add: Kimi K2.7 Code, GLM 5.2, Qwen3.7 Max/Plus, Doubao Seed 2.0 variants,
Claude Sonnet 4.6 / Haiku 4.5 (base + coding), latest GPT/Gemini/Grok.
- Drop: K2 Thinking/Instruct 0905, older MiniMax M2.1/M2, duplicate Qwen.
- Keep source: 'static' only. Entries sorted in descending version order
within each vendor family.
- Preserve notes: 'Free' on the owl model.
.gitignore: ignore .tmp-* directories (test artifacts such as replay-index tests).
Follows static-over-hybrid, catalog-model-ordering, and grok-build-0.1 notes.
|
||
|
|
dd4c4abc81 |
feat(api): add OpenAI-compatible credential pool failover (#1706)
* feat(api): rotate OpenAI credential pools * fix(api): align pooled credential discovery * fix(cache-probe): preserve GitHub credential precedence * fix(provider): honor pooled OpenAI fallbacks * fix(provider): validate pooled profile credential labels * fix(api): harden OpenAI credential pool handling Reject placeholder values in pooled OpenAI credentials before requests, discovery, diagnostics, and profile generation can use them. Normalize pooled credentials to a single usable key for model discovery, runtime cache partitions, cache probing, and NVIDIA NIM cache lookups. Preserve documented profile precedence by letting live shell credentials override saved pools, carrying OpenCode fallback pools through launch, and redacting individual pool members in profile display. Add regression coverage for pooled credential validation, profile launch/rebuild behavior, discovery/cache callers, diagnostics, provider autodetect, and shim failover semantics. * fix(provider): cover pooled key recommendation path Import the pooled OpenAI credential validator in provider-recommend and split invalid credentials from unset credentials in user guidance. Add a script-level regression that runs the OpenAI recommendation path with OPENAI_API_KEYS so the ts-nocheck script cannot regress with runtime ReferenceErrors. Scrub pooled OpenAI keys before xAI OAuth profile env construction and loosen the invalid-pool discovery test to assert auth header absence instead of exact header shape. * fix(tests): stabilize rebased provider checks * fix(provider): address pooled credential review findings * test(api): cover opencode go credential failover * fix(provider): share OpenAI credential usability checks * fix(provider): respect pooled credential precedence * fix(model): preserve pooled discovery credential precedence * fix(model): fall back from unusable pooled discovery keys |
||
|
|
c2467eedad |
feat(atlas-cloud): add GLM 5.2 to vendor catalog (#1755)
Adds zai-org/glm-5.2 with the same 202,752 context window as GLM 5.1 so the model is selectable through the Atlas Cloud provider. Co-authored-by: OpenClaude <openclaude@gitlawb.com> |
||
|
|
38b0e27333 |
fix(opencode-go): sync model catalog with opencode.ai/go (#1745)
* fix(opencode-go): sync model catalog with opencode.ai/go The OpenCode Go subscription page (https://opencode.ai/go) lists 13 models, but the catalog had 20. Remove the 7 models no longer offered: glm-5, kimi-k2.5, minimax-m2.5, qwen3.5-plus, mimo-v2-pro, mimo-v2-omni, hy3-preview. Catalog now matches the page exactly: - OpenAI-compatible: GLM 5.2, GLM 5.1, Kimi K2.7 Code, Kimi K2.6, DeepSeek V4 Pro, DeepSeek V4 Flash, MiMo V2.5 Pro, MiMo V2.5 - Anthropic messages: MiniMax M3, MiniMax M2.7, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus Updates gateway catalog, model descriptors, generated artifacts, and tests. * fix(opencode-go): reorder model catalog to match opencode.ai/go listing Reorder both the gateway catalog and model descriptor lists to match the order models appear on https://opencode.ai/go, so the in-app model picker mirrors the subscription page. No model added or removed — purely a reorder. GLM-5.2 → Qwen3.7 Max → Kimi K2.7 Code → MiMo V2.5 Pro → DeepSeek V4 Pro → Qwen3.7 Plus → MiniMax M3 → MiMo V2.5 → DeepSeek V4 Flash → GLM 5.1 → Kimi K2.6 → Qwen3.6 Plus → MiniMax M2.7 * test(opencode-go): assert exact model set matches opencode.ai/go catalog Address CodeRabbit review on #1745 — the count-only test wouldn't catch catalog drift. Add a strict set assertion verifying the 13 expected IDs are present and no removed/unexpected IDs remain. * test(opencode-go): update Anthropic Messages route test for refreshed catalog The direct-env-routing test listed minimax-m2.5 and qwen3.5-plus, which were removed from the opencode-go catalog. Replace with the five /messages-endpoint models that remain: minimax-m3, minimax-m2.7, qwen3.7-max, qwen3.7-plus, qwen3.6-plus. Unblocks the smoke-and-tests CI check on #1745. * fix: update OpenCode Go model references to 13 models and add assertions |
||
|
|
820015fbaf | fix(bg): prefer exact session names over ID prefixes (#1753) | ||
|
|
091571f643 |
fix(provider): Add Xiaomi MiMo token plan provider (#1751)
* Add Xiaomi MiMo token plan provider Add a descriptor-backed Xiaomi MiMo Token Plan gateway with the token-plan SGP/CN routing hosts, raw api-key OpenAI-compatible shim settings, and MiMo catalog defaults. Wire the new preset through generated integration artifacts, provider flag handling, active route/provider resolution, provider profile env mirroring, and ProviderManager preset ordering. Cover the new path with gateway metadata, compatibility, provider flag, provider profile, and focused ProviderManager tests. Validated with bun run build, bun run smoke, bun run check, typechecks, provider suites, integrations:check, security:pr-scan -- --base upstream/main, and doctor:runtime. * Address Xiaomi MiMo token plan review feedback Reset stale known OpenAI-compatible base URLs when xiaomi-mimo-token is explicitly selected so prior provider routing cannot survive the provider flag. Use hostname-based Xiaomi MiMo base URL detection when mirroring MIMO_API_KEY from provider profiles, avoiding substring matches in unrelated URL paths or query strings. Add focused regression coverage for stale base URL replacement and token-plan CN profile env mirroring. |
||
|
|
aed42df19b |
fix: treat 5xx HTML overload pages as retryable provider_unavailable (#1750)
Reorders classifyOpenAIHttpFailure so the status >= 500 branch runs before the isMalformedProviderResponse check. Gateway 502/504 overload pages have HTML bodies (matching "<!doctype html" / "<html") that previously tripped the malformed-provider-response path, marking the error non-retryable and surfacing "Provider returned a malformed response" even though the failure was transient — users had to retry manually. Now any 5xx is classified as provider_unavailable (retryable) regardless of body shape, matching the actual semantics. 4xx HTML responses still classify as malformed_provider_response since those are genuine protocol failures. Adds three regression tests covering 502 HTML, 504 HTML, and the unchanged 400 HTML path. |
||
|
|
8c15feaa5a |
test(install-surfaces): stop execFileNoThrow mock leaking into later suites (#1708)
The cleanupNpmInstallations test replaces execFileNoThrowWithCwd with a stub that simulates a failed npm uninstall (E404). bun's mock.module is process-wide, and re-mocking the module back to the real implementation in afterEach does not reliably undo it, so the stub leaked into later test files that shell out for real (e.g. git worktree add), making them fail with a bogus "npm ERR! code E404". Install the override once at module load and gate it on a module-level flag that is cleared in afterEach, so the persisted mock transparently falls through to the real implementation whenever the flag is off. The real module is snapshotted into a plain object before the mock is installed, because bun live-updates the imported namespace to the mock — delegating through the namespace inside the override would otherwise recurse infinitely. |
||
|
|
1be9b86607 |
feat: Add session replay timeline (#1705)
* Add session replay timeline Implement a /replay command backed by replay sidecar indexes that capture user requests, tool execution inputs/results, modified files, durations, real retry events, and repeated tool attempts. Add replay summaries to /resume, persist replay indexes during session cleanup, and include replay sidecars in retention cleanup. Add focused coverage for replay index retry/repeated-attempt accounting and replay modified-file detection, including simulated Bash sed edits. Validation run: bun install; bun run build; bun run smoke; bun run typecheck; bun run typecheck:type-tests; bun run security:pr-scan; bun run doctor:runtime; bun test src/utils/replayIndexBuilder.test.ts; bun test src/services/tools/toolExecution.test.ts. bun run check reached test:full but the full suite still reports 10 unrelated order/environment-sensitive failures in export, marketplace cache, and secure-storage tests; those files pass when rerun directly. * Address replay review findings Move replay tool-start tracking to the final executable input for allowed tool calls while preserving permission-denied replay records. Harden replay sidecar persistence with session-id based paths and owner-only file mode, and compute replay summary timestamps from true min/max values. Reuse the shared SessionSummary component in /replay and add focused regression coverage for replay storage, timestamp bounds, replay lifecycle records, and resume summary confirmation keyboard behavior. Validation: bun run typecheck; bun run build; bun test src/utils/replayIndexBuilder.test.ts src/utils/replayIndex.test.ts src/commands/resume/resume.test.tsx src/services/tools/toolExecution.test.ts. * Resolve replay follow-up findings Use the shared formatDuration helper in replay summary UI, replace replay timeline non-null assertions with optional chaining, and align the resume Escape test with the single-escape input pattern plus async polling. Validation: bun run typecheck; bun run build; bun test src/commands/resume/resume.test.tsx src/utils/replayIndexBuilder.test.ts src/utils/replayIndex.test.ts src/services/tools/toolExecution.test.ts. * Fix replay timing and cancellation status Preserve millisecond precision for sub-second replay displays, classify abort-shaped tool failures as cancelled in replay records, and derive replay summary duration from elapsed session bounds instead of summed tool runtime. Validation: bun run typecheck; bun run build; bun test src/utils/replayFormat.test.ts src/utils/replayIndexBuilder.test.ts src/utils/replayIndex.test.ts src/commands/resume/resume.test.tsx src/services/tools/toolExecution.test.ts. * Normalize denied replay tool inputs Use the same replay input normalization for permission-denied file tools that the allowed execution path uses, preserving model-emitted relative paths and repeated-attempt signatures across denied-then-allowed retries. Validation: bun run typecheck; bun run build; bun test src/services/tools/toolExecution.test.ts src/utils/replayFormat.test.ts src/utils/replayIndexBuilder.test.ts src/utils/replayIndex.test.ts src/commands/resume/resume.test.tsx. * Address replay review follow-ups * Fix replay follow-up test findings * Stabilize replay cleanup test * Address replay resume follow-ups * Harden replay resume validation * Address replay review coverage gaps * Close remaining replay review findings * Fix replay review follow-ups after rebase |
||
|
|
5625f4217d |
fix: preserve provider route context metadata (#1741)
* fix: preserve provider route context metadata Resolve issue #1732 by keeping active provider-profile routes attached during context limit resolution and making gateway-prefixed model IDs resolve against provider-scoped metadata instead of falling through to global aliases. Preserve composite provider-path suffixes such as accounts/fireworks/models/... and fireworks/models/... before generic last-segment matching, so wrapped gateway IDs resolve Fireworks-specific limits instead of generic descriptors. Refresh OpenCode Zen and OpenCode Go catalog metadata, including route-specific context/output limits, regenerate integration artifacts, add DeepSeek V4 Pro on NVIDIA NIM, add the Gemini 3.1 Pro router alias, and update user-facing OpenCode model counts. Scope OpenCode descriptor default model names to OpenCode routes via providerModelMap so unprefixed vendor lookups are not hijacked by gateway descriptors. Add wrapper-path assertions for the user-facing max output token helper. Add regression coverage for provider-prefixed gateway models, active-profile route preservation, account-qualified composite paths, and OpenRouter-wrapped fireworks/models/... paths. Harden Windows/full-suite validation by normalizing plugin hook display paths and resetting status-redaction HOME/USERPROFILE state. Validation: bun install; bun run build; bun run smoke; bun run typecheck; bun run typecheck:type-tests; bun run check (4634 pass, 0 fail); bun run test:provider (857 pass, 0 fail); bun run test:provider-recommendation (91 pass, 0 fail); bun run integrations:check; bun run security:pr-scan -- --base upstream/main; git diff --check. Follow-up validation: bun test src/integrations/runtimeMetadata.test.ts --max-concurrency=1; bun test src/utils/context.test.ts src/integrations/runtimeMetadata.test.ts src/integrations/gateways/opencode.test.ts --max-concurrency=1; bun run test:provider; bun run integrations:check; bun run typecheck; git diff --check. # Conflicts: # src/integrations/gateways/opencode-go.ts # src/integrations/models/opencode.ts * fix: align OpenCode context metadata Refresh OpenCode Zen and Go descriptor context/output limits against the live OpenCode model lists and Models.dev provider metadata. Add a regression assertion for provider-specific OpenCode limits so route-scoped metadata does not fall back to generic model budgets. * fix: remove duplicate Gemini model descriptor Keep the canonical Gemini 3.1 Pro descriptor and rely on provider-prefixed suffix matching for google/gemini-3.1-pro runtime lookups. Also separate the OpenCode limit regression test from the following assertion for readability. * fix: preserve OpenCode Go messages auth metadata * test: cover OpenCode Go review cases |
||
|
|
ae66c30df5 |
feat(fireworks): add GLM-5.2 model support (#1728)
* feat(fireworks): add GLM-5.2 model support Add the Fireworks-hosted glm-5p2 model to both the vendor catalog and the merged model descriptor with 1M context window. * fix(fireworks): add glm-5p2 to brand modelIds list * fix(fireworks): use published 1040k context length for GLM-5.2 |
||
|
|
ab94a50a1a |
fix(mcp): support draft 2020-12 tool schemas (#1740)
Resolve issue #1739 by selecting the AJV dialect from MCP tool input schemas before compiling validators. MCP input schemas default to JSON Schema Draft 2020-12, including when $schema is omitted, while explicit Draft-07 schemas continue to use the legacy validator. This fixes Playwright MCP schemas such as browser_tabs, which default AJV rejected with no schema with key or ref. Add MCPTool regression coverage for Playwright-style Draft 2020-12 schemas, omitted-$schema Draft 2020-12 tuple keywords, and explicit Draft-07 compatibility. |
||
|
|
0e1fce45ca |
Fix OpenCode Go messages authentication (#1717)
* fix opencode go open model endpoints * fix(opencode): use x-api-key for Go messages * fix(opencode): keep Go x-api-key over stale auth A global OPENAI_AUTH_HEADER left over from another OpenAI-compatible route took precedence over the model-level x-api-key in _doOpenAIRequest, so OpenCode Go /messages models still sent Authorization: Bearer and hit the 401 this PR fixes. Ignore the global custom-auth env when the selected model's catalog entry defines an openaiShim.defaultAuthHeader, so the model-level x-api-key contract cannot be bypassed. Adds a stale-custom-auth regression for all five models via the direct-env path. |
||
|
|
059ec5e8b0 |
fix(code-indexing): guard command detection against prototype-chain names (#1710)
detectCodeIndexingFromCommand looked up the command's first word in a plain object (CLI_COMMAND_MAPPING). A command whose first word collides with an inherited Object.prototype member resolved to the prototype value instead of undefined: the bare lookup returned the Object constructor for `constructor`, and the npx/bunx branch used `in`, which walks the prototype chain. Both falsely reported a code-indexing tool, so running e.g. `constructor ...` emitted a bogus tengu_code_indexing_tool_used telemetry event whose `tool` field was a function. Switch CLI_COMMAND_MAPPING to a Map and look up via .get(), so unknown and inherited keys both return undefined. |
||
|
|
6c7d147387 |
fix(model): preserve [1m] tag for the codex aliases (#1709)
parseUserSpecifiedModel maps the codexplan/codexspark aliases to their gpt model ids but, unlike every Claude alias (opus/sonnet/haiku/best), dropped the trailing [1m] tag. That suffix is an explicit client-side opt-in to the 1M context window (has1mContext returns 1_000_000 whenever it is present, regardless of model family), so codexplan[1m]/codexspark[1m] silently resolved to a non-1M model and the session fell back to the model default window. Append the tag the same way the other aliases do so the opt-in survives. The bare aliases are unchanged. |
||
|
|
4aec353f9c |
fix(grep): relativize content-mode paths correctly on Windows (#1704)
* fix(grep): relativize content-mode paths correctly on Windows Grep output_mode "content" split each line at the first colon to separate path from content, but a Windows absolute path starts with a drive-letter colon (C:), so it split at "C" and reassembled the original absolute path — defeating relativization (count and files_with_matches modes were already correct). Skip a leading drive-letter colon when locating the boundary. Extracts a pure relativizeContentLine helper with cross-platform tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(grep): relativize Windows context rows, not just match rows ripgrep separates the path with `:` on match rows and `-` on context rows (-A/-B/-C). The helper only looked for a boundary colon, so Windows context rows like `C:\...\file.ts-1-before` kept their absolute path. Locate the boundary as the first `:<n>:` (match; unambiguous since paths have no non-drive colon) else the first `-<n>-` (context), falling back to the first colon for line-number-less rows. This also stops date-like `-2024-` runs in filenames from being mistaken for the context boundary. Adds tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(grep): relativize content rows by stripping the known search root Review follow-up: the previous delimiter heuristic chose the first `-<n>-`, which mis-split rows when the cwd or an ancestor directory contained a date-like segment (e.g. C:\Users\proj-2024-01-15\...), and left line-number-disabled context rows (`path-content`) with absolute paths. Strip the known absolute root prefix instead: every ripgrep path under the root starts with `<root><sep>`, so removing it yields the relative path + the original delimiter + content verbatim, independent of the delimiter or whether line numbers are enabled. Paths outside the root stay absolute, matching toRelativePath. Rewrites the tests, including the date-cwd and no-line-number context-row regressions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(grep): compare the root with Windows path semantics (case/slash-insensitive) Review follow-up: stripping the root used a literal startsWith, so when getCwd() and ripgrep spelled the same Windows root with different casing or slash style (e.g. C:\USERS\PROJ vs C:\Users\proj), the prefix did not match and absolute paths leaked. Normalize the comparison for Windows roots (lowercase + treat `/` as `\`) while slicing the original line by prefix length, mirroring toRelativePath's case-insensitive path.win32 behavior. Adds casing/slash regressions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
f5041e4d46 |
fix(format): roll formatFileSize over to the next unit at the 1024 boundary (#1703)
* fix(format): roll formatFileSize over to the next unit at the 1024 boundary formatFileSize selected the unit from the unrounded magnitude (kb < 1024) but displayed the rounded value, so sizes just under a boundary rendered as "1024KB"/"1024MB" instead of "1MB"/"1GB" (e.g. 1048575 bytes -> "1024KB"). Compare the rounded magnitude when choosing the unit so it promotes correctly. Adds format.test.ts covering the boundary bands. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(format): assert formatFileSize(1023) renders as raw bytes The sub-KB test labelled "raw bytes" expected formatFileSize(1023) to be "1KB", but the implementation returns "1023 bytes" for values below the 1024-byte threshold, so the focused test (and the smoke-and-tests check) was red. Correct the expectation to "1023 bytes". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
ba85aa6dd0 |
fix(frontmatter): expand nested brace globs in paths: correctly (#1701)
* fix(frontmatter): expand nested brace globs in paths: correctly
expandBraces used the regex `^([^{]*)\{([^}]+)\}(.*)$`, whose `[^}]+` stops
at the first `}`, so nested brace groups were corrupted: `{a,{b,c}}` became
`["a}","b","c}"]` and `src/**/*.{js,{ts,tsx}}` produced stray `}` and broken
globs — silently breaking path-scoped skill / CLAUDE.md activation. Replace
the regex with a depth-aware scan that finds the matching close brace and
splits on top-level commas only, recursing as before. Unbalanced braces fall
back to the literal input. Adds frontmatterParser.test.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(frontmatter): keep an empty brace group `{}` literal in paths
The balance-aware scanner expanded `{}` to a single empty alternative, so
`paths: "{}"` collapsed to [""]; parseSkillPaths and the CLAUDE.md path
parser drop that empty string and treat the file as having no path
restriction (activating everywhere). The previous regex required >=1 inner
char, so `{}` stayed literal. Restore that: treat an empty brace group as
literal while still expanding any later groups in the suffix. Adds tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
23bc49a01d |
feat(query): add lifecycle identity and terminal reasons (#1682)
* feat(query): add lifecycle identity and terminal reasons * fix(query): isolate lifecycle tracking context * fix(query): guard lifecycle metadata updates * fix(query): track lifecycle during tool waits * test(query): cover bash lifecycle metadata * fix(query): scope lifecycle tracking to request attempts * fix(query): disambiguate lifecycle abort log reason * fix(query): preserve foreground subagent lifecycle tracking * fix(query): clean up timeout and fallback lifecycle events * fix(query): emit timeout end after cleanup |
||
|
|
b9a5030b67 |
fix(status): show active provider route instead of legacy provider bucket (#1673)
* fix(status): show active provider route instead of legacy bucket The /status command collapsed many concrete providers (OpenRouter, Groq, Ollama, Fireworks AI, etc.) into a single "OpenAI-compatible" label, making multi-provider setups hard to verify and debug. When apiProvider resolves to the generic "openai" bucket, /status now uses route metadata to surface the real active route: Provider route: OpenRouter Transport: OpenAI-compatible API OpenAI base URL: https://openrouter.ai/api/v1 Model: anthropic/claude-sonnet-4.5 Credential: OPENROUTER_API_KEY configured The legacy "OpenAI-compatible" label and fallback are preserved for unknown custom base URLs. Dedicated provider buckets (nvidia-nim, minimax, codex, github, xai, gemini, bedrock, vertex, foundry, firstParty, mistral) already have accurate labels and are left untouched. Credential display uses env-var names only (never values). Transport kind and route label come from the existing descriptor-driven route metadata; no new hardcoded provider maps or network calls are introduced. * fix(status): include route status defaults * fix(status): address route status review findings * fix(status): cover route secret redaction review * fix(status): avoid duplicate route resolution * fix(status): redact base URL query credentials * fix(status): harden status URL secret redaction * fix(status): redact route secrets in status text * test(status): cover fallback URL fragment redaction * test(status): isolate route status provider imports * fix(status): redact encoded route secrets * fix(status): redact encoded query secrets safely * fix(status): redact nested encoded query secrets * fix(status): redact encoded secret substrings * fix(status): redact strict encoded secret variants |
||
|
|
02ee7c63e9 |
fix: type safety, defensive defaults, and unbounded retry prevention (#1553)
* fix: type safety, defensive defaults, and unbounded retry prevention
QueryEngine.ts:
- Import PERMISSION_MODES runtime constant and validate permissionMode
before casting in submitMessage — invalid mode strings fall back to
'default' instead of crashing with ReferenceError: PERMISSION_MODES is
not defined (fixes the runtime gap from the original PR)
- Use splice(0, length, ...messages) instead of length=0 + push() for
atomic array replacement in snip replay, so concurrent readers of
getMessages() never observe an empty state
withRetry.ts:
- Cap persistent retry loop at 100 attempts via PERSISTENT_RETRY_MAX_ATTEMPTS
constant — prevents unbounded retry (~8 hours max with exponential backoff
and 6-hour reset cap) when the unattended retry path is enabled
autoCompact.ts:
- Add MIN_AUTOCOMPACT_FAILURE_COOLDOWN_MS = 10_000 floor for
OPENCLAUDE_AUTOCOMPACT_FAILURE_COOLDOWN_MS override — prevents
misconfiguration from effectively disabling the circuit breaker
autoCompact.test.ts:
- Update test override from 5000 to 15000 to respect the new 10s minimum floor
- Add test case verifying values below the floor (5000, 9999) are rejected
and that the floor value (10000) is accepted
- Update circuit breaker retry-time expectation from 111_000 to 121_000
to account for the new 15s cooldown override
* test: enable UNATTENDED_RETRY feature in bun test scripts
The new persistent retry cap test in withRetry.test.ts needs the real
UNATTENDED_RETRY feature gate to fire, which requires passing
--feature=UNATTENDED_RETRY on the bun test command line. Enable it
in the standard test, test:full, test:coverage, and test:provider
scripts so the test sees the production gate behavior.
* test: cover persistent retry cap driven through real gate
Add a regression test that proves the persistent retry path stops
after PERSISTENT_MAX_ATTEMPTS=100 retryable 429s by driving the real
isPersistentRetryEnabled() gate (no test override seam). Also:
- Switch makeError to the new APIError() constructor so the test
errors match the real wire shape and exercise the production
canRetry/shouldRetry branches
- Add CLAUDE_CODE_UNATTENDED_RETRY to the envKeys clear list so the
gate isn't poisoned by leaked state from a prior test
- Mock src/utils/sleep.js in importFreshWithRetryModule so the
exponential-backoff delays don't slow the suite down
* test: defensively clear leaked env vars in client.test.ts
The 4 failing tests in CI (first-party Anthropic fetch wrapper, env-only
MiniMax routing, OPENAI_MODEL preservation, OpenAI shim options) all sit
at the top of the file and are sensitive to leaked env vars from prior
test files in the same process. Extend the beforeEach, afterEach, and
inline cleanup to clear OPENAI_AUTH_HEADER, OPENAI_AUTH_SCHEME,
OPENAI_AUTH_HEADER_VALUE, MIMO_API_KEY, VENICE_API_KEY, and
NVIDIA_API_KEY alongside the existing vars, and add ANTHROPIC_API_KEY /
ANTHROPIC_AUTH_TOKEN / ANTHROPIC_MODEL to the first test's inline
cleanup so it does not rely solely on the global beforeEach when run
in isolation.
* test: isolate countMcpToolTokens tests from mcp.ts env-var side effect
src/entrypoints/mcp.ts sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=true as
a top-level side effect on import. mcp.test.ts imports that file, so the
env var is leaked into the rest of the test process. In
analyzeContext.mcp.test.ts the kill switch forces getToolSearchMode() to
'standard', which makes isToolSearchEnabled() return false, so the
'keeps deferred MCP schemas excluded' test sees isDeferred=false and
returns mcpToolTokens=1000 instead of 0.
Clear the kill switch and ENABLE_TOOL_SEARCH in beforeEach (restoring
the original values in afterEach) so the test observes the production
default of tool search enabled regardless of test ordering.
* fix: validate against EXTERNAL_PERMISSION_MODES; confine persistent retry override to test-only module var
QueryEngine.ts:
- Switch SDK init permissionMode validation from the internal
PERMISSION_MODES set to EXTERNAL_PERMISSION_MODES. The internal
set can include the classifier-only 'auto' mode that is not a
valid wire value for the SDK system/init payload, so emitting
it would produce an unsupported configuration.
withRetry.ts:
- Rename PERSISTENT_RETRY_MAX_ATTEMPTS to PERSISTENT_MAX_ATTEMPTS
to match the convention of the other PERSISTENT_* constants and
re-export it as _PERSISTENT_MAX_ATTEMPTS_FOR_TEST for unit-test
assertion of the cap value (no runtime override seam).
- Hoist isPersistentRetryEnabled() into a local
persistentRetryEnabled const at the top of withRetry() so all
call sites see a consistent snapshot within a single retry
chain.
- Thread persistentRetryEnabled through shouldRetry() so its
branch is decided once per chain rather than re-reading the
feature flag and env var on every attempt.
* feat: emit telemetry when persistent retry cap is reached
* fix: surface changelog cache-write failures in migration
- Only swallow EEXIST (file already exists) errors in migrateChangelogFromConfig
- Rethrow all other write failures (permissions, disk full, etc.)
- Log migration errors instead of silently ignoring them
- This ensures migration failures are surfaced and will retry on next startup
* fix: split mkdir and writeFile in changelog migration
- Ensure mkdir runs before writeFile try/catch
- Only suppress EEXIST from writeFile, not mkdir
- Prevents EEXIST from mkdir incorrectly counting as successful write
* fix: stop overriding getGlobalConfig in user.test.ts mock
* Fix leftover conflict marker in QueryEngine.ts
* fix: remove stale retry guard and handle mkdir EEXIST
- Remove duplicate shouldRetry() call without persistentRetryEnabled arg in withRetry.ts
- Wrap mkdir in try-catch to handle EEXIST on Windows/Bun readonly folders in releaseNotes.ts
* test: don't restore OPENAI auth header env vars in afterEach
These vars were being restored from originalEnv which captures polluted values
from prior test files in the full suite. Removing the restoreEnv calls keeps
them cleared between tests, fixing 'Could not resolve authentication method'
failures in CI smoke-and-tests.
* fix: remove duplicate OPENAI_AUTH_* keys in originalEnv (TS1117)
* fix: restore OPENAI auth header snapshot and move MCP lock to top-level
- client.test.ts: restore OPENAI_AUTH_HEADER/SCHEME/HEADER_VALUE in afterEach
so the file doesn't permanently clear those globals in its worker
- analyzeContext.mcp.test.ts: move acquireSharedMutationLock/release to the
top-level beforeEach/afterEach so all env mutations in this file happen
while the shared mutation lock is held
* fix: use splice for atomic array replacement in snip replay
Restores the atomic array replacement using splice(0, length, ...messages)
instead of length=0 + push(...) that was claimed in commit
|
||
|
|
7d130e73ba |
perf: eliminate response.clone() memory doubling and cache lazy tool getters (#1478)
* perf: eliminate response.clone() memory doubling and cache lazy tool getters openaiShim.ts: - Replace response.clone() with response.text() + JSON.parse() + new Response() for non-streaming usage extraction — avoids doubling memory for large responses tools.ts: - Cache getSendMessageTool(), getTeamCreateTool/DeleteTool(), getPowerShellTool() results in local IIFEs — avoids double-invocation of lazy require() getters * test: preserve openai shim response body on parse failure * fix(openai-shim): preserve response.url routing metadata and fix regression test Addresses PR review feedback (two P2s). 1. Preserve response.url and response.type when recreating the Response after reading the body for usage extraction. new Response(bodyText) drops url to empty string, which broke create()'s /responses, /messages, and Gemini routing — descriptor routes (OpenCode /messages, Gemini /models/gemini-*) fell through to the generic OpenAI converter and returned the wrong message shape. Restore the original metadata via Object.defineProperty (shadowing the read-only prototype getter), guarded by try/catch for runtime safety. 2. Fix the flaky 'preserves response body when usage parsing fails' test. The original mock threw on the first global JSON.parse call and asserted parseCalls > 1, but Bun's native Response.json() does not go through JS-level JSON.parse, so parseCalls stayed at 1 and the assertion failed. Rewrite to scope the failure to the response body text and assert usageParseFailed + content correctness instead, which works in both Bun (native Response.json) and Node (undici). 3. Add 'preserves response.url routing metadata after body read' test that pins an Anthropic-shaped body behind a /messages URL — fails without the url fix (content becomes []), passes with it. * fix(typecheck): use 'as unknown as FetchType' to satisfy TS2352 |
||
|
|
b581bd9ece |
feat(zai): add GLM-5.2 support (#1689)
* feat(zai): add GLM-5.2 thinking support * fix(provider): derive GHE Copilot URL from base URL * fix(zai): gate GLM reasoning effort by model |
||
|
|
c4aa756689 |
feat(commands): add /update command with package-manager auto-detection (#1687)
Adds a `/update` slash command that updates OpenClaude to the latest published version, routing by how the running install is actually managed so it updates the installation the user is running. `globalPackageManager.ts` detects the owning package manager (npm, yarn, pnpm, bun) for npm-style installs and maps it to the correct global-install command. `installGlobalPackage()` and `getLatestVersion()` now consume it, so the legacy `openclaude update` CLI and the background auto-updater gain yarn/pnpm support; `getLatestVersion()` also falls back to a direct npm-registry HTTP lookup when npm isn't on the PATH. `updateStrategy.ts` factors the install-type routing and the third-party-build guard out of `src/cli/update.ts` (now shared by both entrypoints). `/update` uses it to refuse development/third-party builds, point package-manager/native/local installs at their safe update paths, and only do a global npm install when that's what's actually running — instead of always installing a stray global package. Co-authored-by: OpenClaude <openclaude@gitlawb.com> |
||
|
|
cc385a6490 |
fix(ink): reduce high-write-ratio diagnostic noise (#1699)
* fix(ink): reduce high-write-ratio diagnostic noise * test(ink): cover high-write diagnostic suppression * fix(ink): preserve churn warnings at suspicious widths |
||
|
|
8cd463383d |
fix(lsp): throttle diagnostic storms (#1698)
* fix(lsp): throttle diagnostic storms * fix(lsp): account for capped storm diagnostics |
||
|
|
916f2477f3 | fix(warnings): surface perf hooks buffer guidance (#1696) | ||
|
|
df986c9275 | fix(messages): make projections tool-pair safe (#1695) | ||
|
|
2aad6fc93e |
feat(config): add OPENCLAUDE_CONFIG_DIR override (#1683)
* feat(config): add OPENCLAUDE_CONFIG_DIR env var as preferred alias for CLAUDE_CONFIG_DIR (#454) The legacy CLAUDE_CONFIG_DIR name was the only way to point openclaude at a non-default config home, which leaked Anthropic branding for a fork that has otherwise rebranded to OpenClaude. Add OPENCLAUDE_CONFIG_DIR as the preferred name. CLAUDE_CONFIG_DIR continues to work for backward compatibility; when both are set with different values, OPENCLAUDE_CONFIG_DIR wins and a one-time warning is logged. - src/utils/envUtils.ts: introduce resolveConfigDirEnv() that picks OPENCLAUDE_CONFIG_DIR over CLAUDE_CONFIG_DIR and emits a conflict warning. Memoize cache key now tracks both env vars so changing either invalidates the cached result. - src/utils/env.ts: getGlobalClaudeFile() previously read CLAUDE_CONFIG_DIR directly, missing the new alias. Route through resolveConfigDirEnv() so the global config file path follows the same precedence. - src/utils/secureStorage/macOsKeychainHelpers.ts: the "is default dir" check used by keychain service-name scoping now considers both env vars. - src/utils/swarm/spawnUtils.ts: forward OPENCLAUDE_CONFIG_DIR to teammate processes alongside the legacy var. - src/utils/openclaudePaths.test.ts: +6 unit tests covering the new alias, fallthrough, conflict warning, and resolveConfigDirEnv() in isolation. - .env.example: document both env vars and the precedence rule. Verified locally on Linux: with only OPENCLAUDE_CONFIG_DIR set, with only CLAUDE_CONFIG_DIR set (legacy still works), with both set matching (silent), with both set conflicting (warn once + OPENCLAUDE wins), with neither set (default ~/.openclaude). Memo cache invalidates across 4 sequential env transitions. Built dist/cli.mjs honors the new var and emits the conflict warning to the user. * Fix config-dir warning and docs review findings Only mark the config-dir conflict warning as emitted when a warning callback actually receives it, add coverage for warn-once and silent callers, and update web configuration docs for OPENCLAUDE_CONFIG_DIR precedence. # Conflicts: # web/src/data/configuration.ts * Align configuration docs with openclaude paths Update the configuration page settings-file table to point default users at .openclaude settings and keybindings paths, matching the new config home behavior. * Align keybindings docs with openclaude config home Update the keybindings page, keybindings docs data, and skill index to point default users at ~/.openclaude/keybindings.json. * Align skill and hook labels with openclaude paths Update bundled config/keybindings skill prompts, public skills docs, hook/trust labels, and the user memory selector to use the active OpenClaude config home paths. # Conflicts: # src/components/TrustDialog/utils.ts # src/components/hooks/SelectEventMode.tsx # src/skills/bundled/updateConfig.ts # src/utils/hooks/hooksSettings.ts * Resolve config-home paths dynamically in skill prompts Use runtime settings/keybindings path helpers for bundled skill prompts and the restricted-hooks banner so custom OPENCLAUDE_CONFIG_DIR values are reflected in user-facing guidance. * Update active command prompts for openclaude paths Point statusline, setup/onboarding prompts, plugin messages, and the external user-memory warning at the active OpenClaude settings and memory paths. # Conflicts: # src/commands/auto-fix.ts # src/commands/onboard-github/onboard-github.tsx # src/commands/plugin/ManagePlugins.tsx # src/commands/statusline.tsx * Fix remaining config path review findings * Cover dynamic config paths in UI and storage tests * Fix config path smoke failures after rebase * Fix remaining config path review findings --------- Co-authored-by: gnanam1990 <gnanasekaran.sekareee@gmail.com> |
||
|
|
e5cb589031 |
security(status): redact proxy and TLS-sensitive values in /status (#1672)
* security(status): redact proxy and TLS-sensitive values in /status Make /status safe to share in public issues and screenshots by ensuring proxy credentials, mTLS private key/cert paths, CA bundle paths, and token-bearing URLs are never printed verbatim. - Proxy URL: wrap with redactUrlForStatus (reuses redactUrlForDisplay for credential + sensitive query-param masking; additionally strips the URL fragment, which can carry tokens). - NODE_EXTRA_CA_CERTS / CLAUDE_CODE_CLIENT_CERT: wrap with redactPathForStatus, which shortens a leading $HOME to ~ so paths stay useful without leaking usernames or home directory layout. - CLAUDE_CODE_CLIENT_KEY: show the literal 'configured' rather than the path or value of a private key. Adds two small reusable helpers in src/utils/statusRedaction.ts plus unit tests, and extends status.test.ts with an integration test that asserts the full buildAPIProviderProperties output is leak-free when proxy credentials and mTLS env vars are set. * fix(status): address status redaction review feedback * fix(status): redact provider base URL secrets * fix(status): unify URL status redaction |
||
|
|
5af6f95c46 |
feat(config): add explicit provider env-file loading (#1668)
* feat(config): add explicit provider env-file loading * fix(config): handle escaped quotes in provider env files * fix(config): polish env-file parser review feedback * fix(config): preserve provider env-file precedence * test(config): cover provider env-file precedence * fix(config): preserve provider env-file values * fix(config): allow documented env-file setup vars * fix(config): preserve provider flag precedence |