mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 02:34:15 -05:00
main
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c461a0363d |
feat: merge knowledge graph + conversation arc into memdir (#1811)
* feat: merge knowledge graph + conversation arc into memdir
Replace the standalone KG/ARC system (SQLite + JSON + Orama storage)
with direct integration into the existing auto-memory directory.
What changed:
- New memdir/vectorIndex.ts — Orama full-text index over all memory/ .md
files, replacing the separate knowledge.orama binary
- New memdir/autoExtractFacts.ts — auto-detects env vars, paths,
versions, URLs, IPs, backtick concepts from conversation and writes
them as structured .md files into memory/.facts/ with frontmatter
- conversationArc.ts now persists arc state (goals, decisions,
milestones, phase) to memory/.arc.json sidecar instead of the KG
- knowledgeGraph.ts gutted from 728→165 lines — now a thin
compatibility layer that reads .facts/ files from memdir and
delegates vector search to vectorIndex.ts
- build.ts: enabled CONVERSATION_ARC and MULTI_TURN_CONTEXT feature
flags (previously undefined → dead-code eliminated in production)
Removed:
- src/utils/storage/ (SQLiteProvider, JSONProvider, 3 test files) —
unused after KG migration
- src/utils/knowledgeGraph.test.ts, .stress.test.ts
- src/utils/conversationArc.test.ts, .perf.test.ts
- ~1700 lines of redundant storage code
Benefits:
- Single memory system (memdir) instead of two parallel systems
- Auto-extracted facts are plain .md files — visible to the model,
discoverable by the existing Sonnet prefetch
- Vector search indexes real memory content, not a separate DB
- Arc state survives across sessions via .arc.json
- ~700 lines removed from the production bundle
* fix: type errors, add test suites, fix resetArc() disk-write bug
- Fix vectorIndex.ts: parseFrontmatter returns nested {frontmatter, content},
Orama DB typed as 'any' matching existing code pattern
- Fix resetArc(): was overwriting .arc.json on disk — now clears only in-memory state
- Add vectorIndex.test.ts (6 tests): build/search/persist/rebuild
- Add autoExtractFacts.test.ts (10 tests): env vars, paths, versions,
URLs, backtick concepts, PascalCase, React/Redux, file signatures, frontmatter
- Add conversationArc.test.ts (14 tests): arc init, persistence, goals,
decisions, milestones, phase detection, arc summary, finalize, stats
- Update verify-kg-merge.sh: add test suite check (33 tests)
* fix: remove generic type param from restore() call
* fix: address CodeRabbit findings — YAML injection, secrets leak, cache invalidation, frontmatter parsing, test weakness
* fix: redact URL credentials/query/hash in endpoint extraction; add persistence + reindex regression test
* test: add regression for URL credential/query/hash redaction in fact extraction
* feat: wire getOrchestratedMemory into query.ts prompt; remove dead promises array in autoExtractFacts
* feat: enhance memory management by adding clearArcArtifacts function and integrating it into the clear command; implement file count tracking in vector index
* feat: enhance fact extraction by adding tests for absolute paths, backtick concepts, technical terms, project file signatures, and IP addresses; implement clearArcArtifacts function in tests
* Review-fix: scoped IP tagging, scrubbedContent, arcMemoryDir null, vector-index cleanup
* Review-fix: isAutoMemoryEnabled gate, scrubbed paths, clearIndex in cleanup
* Review-fix: URL-stripped path scan, digit-key/quoted-value env redaction, rm isAutoMemory gate
* Review-fix: quoted multi-token env values fully redacted, regression test
* Review-fix: freshness check on each search, integration test, typecheck in verifier
* Review-fix: missing-index-file reinit, full-pipeline integration test
* Review-fix: gate arc/RAG on isAutoMemoryEnabled, add integration+stale-index tests
Addresses three P1/P2 findings from code review:
1. P1: Honor auto-memory opt-out before writing arc facts
- Add isAutoMemoryEnabled() checks in query.ts before calling
updateArcPhase() and getOrchestratedMemory()
- Add same check inside conversationArc.ts extractFactsAutomatically()
- Prevents .facts file writes when auto-memory disabled via --bare,
CLAUDE_CODE_DISABLE_AUTO_MEMORY=1, or memory.autoWrite: false
2. P2: Add query-level integration test coverage
- New test in conversationArc.test.ts verifies query.ts path
- Confirms arc functions called behind feature gates and results
appended to system prompt (lines 555-575)
3. P2: Add stale-index regression tests
- 6 new tests in vectorIndex.test.ts cover:
* Searching after adding files
* Searching after editing files
* Searching after removing files
* Searching when .vector-index missing
* Searching when .vector-index-meta.json missing
* Mixed stale conditions
All tests pass (31 total, 99 assertions), typecheck clean.
* Review-fix: use >= for mtime staleness check to catch same-ms edits
The verification agent discovered a timing-dependent bug in the stale
index detection. When a file edit and index save occur within the same
millisecond, latestMtime equals indexMtime, causing the check
`latestMtime > indexMtime` to return false. The stale index is not
refreshed and searches miss the updated content.
Changed line 220 from `>` to `>=` in the mtime comparison. The file
count check catches add/remove operations, but edits that don't change
the file count rely on mtime comparison.
All 12 vector index tests now pass consistently, including the
"searching after editing a file picks up changes" test that previously
failed intermittently.
* Review-fix: move auto-memory gate to persistence layer
Addresses inline review comment: query.ts was incorrectly skipping
updateArcPhase() entirely when isAutoMemoryEnabled() returned false,
leaving the in-memory arc state stale.
Fixed by:
- Removed isAutoMemoryEnabled() gate from query.ts line 449
- Added gate inside conversationArc.ts updateArcPhase() at persistence
layer (line 223), so phase advances but only persistence is disabled
- Arc state tracking now works regardless of auto-memory setting
- Only disk writes (.arc.json, .facts files, index rebuilds) are gated
The 2ms delay in vectorIndex.test.ts is kept as a pragmatic fix for the
timing race. Content-hash detection would be ideal but adds complexity;
the mtime check works reliably in production.
All 31 tests pass, typecheck clean.
* Refactor memory handling: consolidate metadata retrieval and improve auto-memory checks
* Fix: update expectation to use toEqual for prompt comparison in conversationArc tests
* fix: detect same-size content changes via content hash; handle text-block user messages in arc query
* fix: skip symlinked dirs in vector index walk; enable arc/multi-turn flags; add production-path tests
* fix: follow symlinked dirs in vector index walk per review
* fix: skip symlinked dirs in vector index walk; add symlink-boundary regression tests
* fix: skip indexing symlinked directories and ensure only files are processed
* fix: show cmd output on failure in verifier; add multi-turn coverage; clear mempath cache; cover arc reset in knowledge clear test
* fix: clear memoized auto-mem path after teardown in knowledge + conversationArc tests
* fix: isolate vector index per memdir, filter secrets from backtick facts, clean trailing ws
* fix: extend credential filter for AWS/GitLab tokens; reject all symlinks in vector index
* fix: use repo's redactSecretSubstringsForDisplay; add npm/glpat/AKIA/ASIA/xox to shared patterns; cover NPM+JWT in tests
* fix: P1 backtick credential safety + untrusted-data boundary; P2 legacy migration + non-fatal writes; P3 type safety + build regression
* fix: B1-B5, M6, M8 — no message mutation, migration data loss, empty-file, non-fatal writes, probe, rebuildIndex resilience, dead import
* fix: address 8 reviewer findings (R1-R8)
P1:
- Keep retrieved facts in DATA ONLY block with strict system instruction
- Catch lowercase config secrets (api_key=...) in env scrubber
- Migrate SQLite working store (knowledge.db) before deleting provider
- /knowledge clear atomically archives legacy sources
P2:
- Run legacy migration on getOrchestratedMemory retrieval path
- Preserve entity attributes in migration frontmatter
- Only rebuild vector index when facts actually changed
- Fix feature-flag verifier --define syntax (declare const)
* fix: close 9 memory findings — approval gate, non-fatal writes, secret scrub, per-project migration, attribute/relation preservation, WAL cleanup, cheaper index
- Gate auto fact extraction on isMemoryWriteApprovalRequired() + isAutoMemoryEnabled() so default projects cannot silently persist conversation content
- Make ensureFactsDir/writeFactMemory degrade non-fatally (no turn-breaking throw on read-only dirs)
- Scrub token-like URL/path/hyphenated segments from durable facts via looksLikeSecret (reuses providerSecrets.looksLikeSecretValue)
- Restore passive project-rule extraction as rule facts
- Honor isAutoMemoryEnabled() before legacy migration; scope the migration guard per project (Set) instead of a single global
- Preserve legacy entity attributes and relations through migration (indented attributes + relation fact file, reconstructed in getGlobalGraph)
- Clear SQLite WAL/SHM sidecars on /knowledge clear
- Replace per-turn content hashing in vectorIndex getMdStats with size+mtime metadata; drop redundant initMemdirIndex call in getOrchestratedMemory
- Add knowledgeGraph tests covering P1#2/P1#4/P2#5/P2#8 and extend autoExtractFacts tests
* test: avoid global cwd pollution in knowledgeGraph tests
Replace process.chdir with a per-test setFsImplementation mock cwd that is
reset via setOriginalFsImplementation in afterEach, so the test no longer
leaks a changed process.cwd() into other test files. Assert the auto-memory
gate via the project-specific legacy file rather than the shared resolved
memdir dir (which bun runs concurrently across it blocks).
* fix: address 15 reviewer findings (R1-R15)
P1:
- Gate saveArcToDisk/finalizeArcTurn/saveIndex/migration on memory-write approval
- Retire legacy sources after successful migration (rmSync, backup preserved)
- Slugify entity.type and summary.id in migration filenames (path traversal)
- Fall back to JSON when SQLite has zero entities
- Scrub rule-fact extraction on scrubbedContent + looksLikeSecret/redact check
P2:
- Track skipped vs completed migration; re-enable clears skip marker
- Walk back to latest human text for tool-round vector queries
- Auto-extract goals/decisions from user messages in updateArcPhase
- /knowledge clear message says durable wipe, not session-only
- Bind arc state to projectKey (re-resolve on cwd change)
- clearIndex(memoryDir?) scopes to one memdir
- yamlQuote all migration frontmatter fields
- Real SHA-256 contentHash alongside fileFingerprint for same-size edits
- Stop claiming production-pipeline coverage in tests/verifier
* test: isolate governance mock by removing afterEach clear
Remove setGovernancePolicySettingsForSourceForTesting(null) from afterEach
in autoExtractFacts, conversationArc, and knowledgeGraph test files. The
module-level mock is set in each file's beforeEach and since there is no
afterEach cleardown, parallel test execution can no longer corrupt the
mock state across files.
This fixes 26 CI test failures caused by one file's afterEach clearing
the mock that another concurrently-running file had set in its beforeEach.
* fix: isolate governance mock per async context via executionAsyncId tracking
Replace the module-level variable in governancePolicy.ts with an
executionAsyncId-keyed Map and an async_hooks.createHook that propagates
the override from parent to child async resources. This ensures each
concurrent test's beforeEach/afterEach cannot corrupt the override set
by another test file, even when test bodies directly mutate the mock.
Also restore setGovernancePolicySettingsForSourceForTesting(null) calls
in afterEach hooks (removed in 39c68376), which are now safe because
each afterEach only clears its own async context.
Fixes 26 CI test failures across knowledgeGraph (2), conversationArc (7),
and autoExtractFacts (17/22, governance-gate tests).
* Refactor knowledge graph legacy migration, secure secrets and IP octets, optimize build-time feature flags, cap conversation arc collections, and resolve all verification check gaps
* Fix governancePolicy enablement in full test runs by checking for test runner globals
* fix: ensure auto memory is enabled in conversationArc, knowledgeGraph, autoExtractFacts tests
Delete CLAUDE_CODE_DISABLE_AUTO_MEMORY and CLAUDE_CODE_SIMPLE env vars
in beforeEach hooks so tests are not blocked when CI sets these vars.
Also revert governancePolicy.ts to the simple module-level variable
(removing the async ID tracking approach that broke with Bun v1.3.13).
Fixes 33 CI test failures where isAutoMemoryEnabled() returned false
causing all disk-persistence guards to fire.
* fix: address P1/P2 review findings — redaction, bounds, legacy backup
[P1] Redact goal/decision descriptions with redactLikelySecrets before
persisting to .arc.json, session summaries, and prompt summaries so
credentials captured by auto-extraction regexes are not durably stored.
[P1] Bound multi-turn tool input serialization to 2000 chars and apply
redactLikelySecrets, preventing oversized/credential-bearing tool inputs
from overflowing the next provider request or exposing secrets.
[P2] Archive the non-selected legacy store (JSON or SQLite) and its WAL
sidecars before retiring both sources, ensuring a recoverable snapshot
exists if generated fact files are incomplete or a migration bug surfaces.
* fix: address P1 findings — safe legacy retirement, multi-turn aggregate budget
[P1] Do not retire a legacy store unless every existing source was
successfully archived. Track archived sources in a Set and skip deletion
of any source whose backup failed (knowledgeGraph.ts).
[P1] Archive the selected SQLite WAL/SHM sidecars alongside its migration
backup, since committed state may reside only in the WAL file and the
advertised recovery backup would otherwise be incomplete (knowledgeGraph.ts).
[P1] Bound the aggregate multi-turn tool replay to 10KB total and stop
appending further turns once the budget is exceeded, preventing many
Agent/MCP calls per turn from adding unbounded text to system prompts
(conversationArc.ts).
* fix: address P1/P2 review findings — rule extraction gate, SQLite read status, atomic WAL/SHAM, KG status gate, byte budgets
* fix: tighten looksLikeOpaqueToken to avoid flagging compound model names
* fix: address P1/P2 review findings — rebase, test isolation, SQLite retry, attribute redaction, entity aliases, body content, project-scoped multiturn, skip unchanged writes, knowledge list gate
* fix: address P1/P2/P3 review findings — secret scrub on migrate, lean decision gate, git-root legacy lookup, backup retention, index rebuild chaining, single vector search, drop unreferenced fixture
* fix: scrub secret entity names on migrate, exclude summary facts from entities, reset multi-turn on /knowledge clear
* fix: address P1 review findings for legacy graph redaction, recovery-safe clear, stable change guards
- Redact embedded secrets in migrated legacy knowledge-graph entities,
summaries, and rules via shared sanitizeLegacyText() policy
- Preserve legacy artifacts (json/db/wal/shm) as migration-backup before
/knowledge clear; resetGlobalGraph returns { archived, failures }
- Skip rewrite + index rebuild on unchanged turns by stripping the volatile
detectedAt timestamp (facts and arc session summaries)
- Always recompute the authoritative content hash in getMdStats so edits of
equal size with preserved mtime are detected and served correctly
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix: redact protocol-relative URL userinfo in legacy migration
new URL() throws for scheme-less URLs, so the catch branch now redacts
obvious //user:pass@host userinfo instead of persisting credentials.
* fix(memory): harden memdir migration and retrieval
---------
Co-authored-by: Gravirei <gravirei@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Kevin Codex <kevin@gitlawb.com>
|
||
|
|
aa936cda11 |
Centralize credential redaction in src/utils/redaction.ts + channel gate tests (#1711)
* feat(utils): add centralized redaction utility Single source of truth for stripping API keys, tokens, and other secrets from strings and JSON. Provider env-var coverage is generated from getKnownProviderSecretEnvKeys() so adding a new provider cannot silently create an unredacted path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(Feedback): import redactSensitiveInfo from utils Remove the inline 40-line regex implementation in favor of the centralized redaction utility, eliminating drift between Feedback and the transcript share path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(submitTranscriptShare): import redactSensitiveInfo from utils Update import path to point at the centralized utility instead of the Feedback component. Removes the implicit re-export contract that required Feedback.tsx to keep redactSensitiveInfo exported. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(log,debug): redact secrets in default error and debug output Wire the centralized redaction utility into logError and logForDebugging so secrets cannot leak into in-memory error logs or the debug file even if a caller forgets to pass through redactSensitiveInfo. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(api/logging): redact error message in logAPIError Apply the centralized redaction utility to the error string passed to logEvent so analytics events cannot capture unredacted credentials from upstream API failures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve merge conflict from upstream sync Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(channelNotification): allow null in getEffectiveChannelAllowlist signature ChannelsNotice.tsx passes getSubscriptionType() which returns SubscriptionType | null, but the signature only accepted string | undefined. Widen to string | null so the call site typechecks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(redaction): exclude specific token fields from redaction process * fix(redaction): lower AIza minimum length to {10,} Real GCP/Gemini keys are 39 chars total (4 prefix + 35 suffix), but the {35} suffix bound missed short tokens like 'AIzaSyDUMMY-secret-token' (21 chars after AIza). Lower to {10,} to match the diagnostics module and catch any AIza-shaped value. Same precision trade-off the diagnostics redaction makes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(redaction,log): address review feedback - Drop quotes from ANTHROPIC/OPENAI key negative lookarounds so JSON-shaped values like "sk-ant-..." redact. - Add private_key pattern to GENERIC_HEADER_FIELD_PATTERN and privatekey to SENSITIVE_FIELD_SUBSTRINGS. - logError now builds a sanitized Error (redacted message + stack) before passing to the sink and queue, not just the in-memory log. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(redaction): consolidate into single module + add channel gate tests Address the three P2 review findings on the central-redaction PR: [1] Consolidate four redaction modules into src/utils/redaction.ts. Previously lived in: - src/utils/redaction.ts (logs/bug reports/transcript shares) - src/utils/urlRedaction.ts (URL display) - src/utils/statusRedaction.ts (/status output) - src/utils/diagnostics/redaction.ts (doctor reports) The four surfaces share the same regex set / credential lists but had drifted into separate per-domain files. Merged into one module; deleted the three shim files. Updated six direct consumers (openaiShim.ts, ProviderManager.tsx, status.tsx, requestSizeBreakdown.ts, diagnostics/issueReport.ts, scripts/system-check.ts) and three test files to import from redaction.js. [2] Add gateChannelServer() test coverage. src/services/mcp/channelNotification.test.ts: 13 cases for the six gate paths (capability, runtime, session, marketplace, plugin allowlist, server-entry dev) plus end-to-end register. Mocks channelAllowlist.js (GrowthBook-backed) so tests stay independent of feature-flag state. [3] Apply jsonRedactor in transcript share. src/components/FeedbackSurvey/submitTranscriptShare.ts now does redactSensitiveInfo(jsonStringify(data, jsonRedactor)) — the key-aware redaction applies during serialization, and the text pass stays as defense in depth for free-form fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(channelNotification): cover findChannelEntry multi-candidate branch Regression test for the disambiguation path in `findChannelEntry` (channelNotification.ts:201-230): when two same-name plugin entries exist in the allowed-channels list with different marketplaces, `pluginSource` must select the matching entry before the marketplace and allowlist gates evaluate. Without this branch being exercised, the gate could lock onto whichever entry sorts first and either skip the user's real installation or wrongly authorize a typo-squatted one. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(redaction): align URL fallback regex + add path-prefix boundary check Two related redaction correctness fixes: [1] URL fallback regex covers the same parameter set as the primary path. The malformed-URL branch in `redactUrlForDisplay` previously had a hand-rolled alternation of credential parameter names that could drift behind `SENSITIVE_URL_QUERY_PARAM_TOKENS`. New `MALFORMED_URL_PARAM_PATTERN` derives from that same list, so the two paths can never diverge. Tests cover the full credential set (`api_key`, `access_token`, `refresh_token`, `signature`, `sig`, `secret`, `password`, `apikey`) plus a non-sensitive `model` that must survive. [2] `redactPathForStatus` now requires a path-separator boundary after the home prefix. The previous `startsWith` check matched `/home/alice2/project` against `/home/alice` and emitted `~2/project`. The fix requires the character at `normalizedCandidate.length` to be `/` or `\` so `alice` no longer matches `alice2` or `alice.bak`. Test pins the false-positive paths and the true-positive (`/home/alice/project` → `~/project`). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(channel,redaction): restore dev-channel warning + align URL fallback Two related security fixes: [1] Restore DevChannelsDialog when --dangerously-load-development-channels is passed and the channels feature is enabled. The previous logic skipped the dialog when OAuth was absent, which was safe only while gateChannelServer() blocked no-OAuth sessions. With the OAuth/org- policy gates removed in this PR, an API-key session could pass the flag, skip the warning, and still register the dev channel. The only remaining skip is the genuinely-disabled feature case (`!isChannelsEnabled()`), where the dialog is moot. [2] Malformed-URL fallback now uses the same substring predicate as the primary `URL` parser path. The previous regex matched only exact parameter names (`api_key=`, `access_token=`, …), so `my_api_key=SECRET` and `x_access_token=TOKEN` slipped through unchanged even though `shouldRedactUrlQueryParam` flags them as sensitive. New `redactMalformedQuery` walks the query pairs and runs the predicate on each key. Three new tests cover prefixed keys, non-sensitive keys, and fragment preservation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(redaction): widen key boundary class + tighten dev-channel comment Two small follow-ups from the latest CodeRabbit review: [1] Boundary class on key-prefix patterns widened from `[A-Za-z0-9]` to `[A-Za-z0-9_-]` so a raw key embedded in a JSON string value (`"sk-ant-..."`, `"AIza..."`, `"ghp_..."`, etc.) is still caught. Quotes act as delimiters, not blockers — the previous boundary class was correct for unquoted text but let quoted keys slip through. [2] Tighten the dev-channel dialog comment in interactiveHelpers.tsx so future readers don't misread the security boundary. Skip condition is `isChannelsEnabled()` (the channels feature flag gate), not KAIROS / KAIROS_CHANNELS as the previous wording implied. Comment now matches the code. Skipped with reason: - getEffectiveChannelAllowlist divergence from gateChannelServer allowlist — by design; the effective-list override is a UI hint consumed only by ChannelsNotice for the org-override indicator. Trust boundary is enforced by gateChannelServer() reading the hardcoded ledger. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(redaction,channel): address P1/P2 review findings P1 - malformed URL fallback secrets: - Decode percent-encoded query param keys via decodeURIComponent() before applying shouldRedactUrlQueryParam (e.g. %74oken -> token) - Stop userinfo regex at ? and # delimiters to avoid consuming query params when matching @ signs in email addresses or fragment delimiters P2 - channel notice/gate allowlist sync: - Remove org override path from getEffectiveChannelAllowlist() so ChannelsNotice startup guidance uses the same ledger source as gateChannelServer's runtime enforcement - Simplify ChannelsNotice to drop unused sub/policy params and the source === 'org' conditional * fix(channel): apply marketplace matching to permission relays, remove stale OAuth/org-policy blockers, add dev-channel dialog coverage P1: Thread runtime pluginSource through filterPermissionRelayClients so findChannelEntry disambiguates same-name plugin entries from different marketplaces before sending permission request previews. P2: Remove stale noAuth and policyBlocked branches from ChannelsNotice that would render '--channels ignored' before reaching the listening message, confusing non-OAuth users. P2: Add test coverage that mocks isChannelsEnabled() both true and false, verifies DevChannelsDialog appears with onAccept marking entries dev:true in the enabled case, and verifies the disabled branch registers entries directly without dialog. * test(dev-channel): clarify count assertion comment + add afterEach with mock.restore() * fix(channel): mirror marketplace gate in permission relay + restore mock Two follow-ups from the latest review: [1] Permission relay predicate no longer relies on findChannelEntry alone. After resolving the entry, the predicate now requires a runtime pluginSource whose marketplace matches the session entry's marketplace for plugin-kind entries — mirroring the gateChannelServer check at channelNotification.ts:303-312. A `plugin:slack@evilcorp` client whose session allows `plugin:slack@anthropic` is now rejected instead of piggy-backing on the approved entry to receive permission-request previews. Server-kind entries still match on bare name. [2] bugfixes.test.ts now re-registers the real channelAllowlist module in afterEach via a cache-busted reference, so the neighbor channelNotification.test.ts continues to import getChannelAllowlist after this suite runs. mock.restore() does not clear module-level mock.module() overrides in bun (the registry is process-global). Pattern matches compact.test.ts:27-36. Also expanded the dev-map count comment in bugfixes.test.ts to document the security invariant (a dev entry must never be confused with a production entry in the allowlist check) per CodeRabbit's request. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(redaction): consolidate into single module + add channel gate tests Address the three P2 review findings on the central-redaction PR: [1] Consolidate four redaction modules into src/utils/redaction.ts. Previously lived in: - src/utils/redaction.ts (logs/bug reports/transcript shares) - src/utils/urlRedaction.ts (URL display) - src/utils/statusRedaction.ts (/status output) - src/utils/diagnostics/redaction.ts (doctor reports) The four surfaces share the same regex set / credential lists but had drifted into separate per-domain files. Merged into one module; deleted the three shim files. Updated six direct consumers (openaiShim.ts, ProviderManager.tsx, status.tsx, requestSizeBreakdown.ts, diagnostics/issueReport.ts, scripts/system-check.ts) and three test files to import from redaction.js. [2] Add gateChannelServer() test coverage. src/services/mcp/channelNotification.test.ts: 13 cases for the six gate paths (capability, runtime, session, marketplace, plugin allowlist, server-entry dev) plus end-to-end register. Mocks channelAllowlist.js (GrowthBook-backed) so tests stay independent of feature-flag state. [3] Apply jsonRedactor in transcript share. src/components/FeedbackSurvey/submitTranscriptShare.ts now does redactSensitiveInfo(jsonStringify(data, jsonRedactor)) — the key-aware redaction applies during serialization, and the text pass stays as defense in depth for free-form fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(test): align malformed URL fragment expectation with preservation behavior * fix: address review findings P1 and P2 [P1] Enforce dev flag for server-kind entries in permission relay predicate, matching gateChannelServer() behavior. Add coverage for both dev and non-dev server relay paths. [P2] Drop fragments in malformed URL fallback (redactMalformedQuery) to match the valid-URL path, preventing credential leaks via fragment-carried tokens. Update existing tests and add regression for fragment-only malformed URLs. * test(relay): add plugin-kind marketplace regression tests * fix: address review findings P1 and P2 [P1] Add PEM private key redaction pattern to redactSensitiveInfo so multi-line PEM values are fully consumed instead of leaking after the first whitespace. Add [ to generic header pattern's value exclusion set to prevent re-consuming [REDACTED] tokens. [P2] Use truthy check (Boolean()) for claude/channel capability in filterPermissionRelayClients to match gateChannelServer's behavior, rejecting explicit false capabilities. * fix(debug): redact before JSON-stringify multiline messages Reorder logForDebugging so redactSensitiveInfo runs before jsonStringify, ensuring PEM/private-key patterns match the raw (unescaped) message text rather than the JSON-encoded form where colons and quotes are escaped. * test(debug): add end-to-end regression for multiline PEM redaction in logForDebugging Uses mock.module on process.js to capture stderr output and exercises the full logForDebugging path with multiline PEM private_key input, verifying the redact-before-JSON-stringify ordering produces redacted output. * fix(test): preserve original process.env.DEBUG and process.argv in logForDebugging test hooks * fix: address PR review findings P1-P3/P5-P7 - P1: clear isDebugMode/isDebugToStdErr memoize caches in test beforeEach + cache-busting query param for fresh debug.ts imports - P2: restore mock.module afterAll instead of leaking mock + mutate err in-place in logError to preserve name/cause - P3: post-processing regex absorbs trailing bracket content after [REDACTED] - P5: (was P3) expand jsonRedactor EXCLUDED_KEYS for maxTokens etc. - P7: capture HOME/USERPROFILE per-test instead of at module scope * fix: address CodeRabbit review findings - interactiveHelpers.tsx: update dev-channel comment — OAuth/org-policy gates removed from gateChannelServer(), org policy is not enforced - channelNotification.test.ts: add afterAll mock.restore() to clean up process-global channelAllowlist.js mock - channelNotification.ts: fix comments — isChannelsEnabled() still reads tengu_harbor, not always true - log.ts: sanitize err.message and err.stack separately so message doesn't get replaced with full stack trace - redaction.ts: add 'i' flag to redactHomePath regex for Windows case-insensitive path matching * fix: address second review round - interactiveHandler.ts: [P2] redact input_preview via redactSensitiveInfo before sending to channel servers - log.ts: [P3] copy error via Object.assign(Object.create(err), err) before sanitizing instead of mutating in-place * fix: address CodeRabbit second round - channelPermissions.ts: redact before truncate in truncateForPreview so partial credentials don't leak at the 200-char boundary - interactiveHandler.ts: remove outer redactSensitiveInfo — now handled inside truncateForPreview - log.ts: derive errorInfo.error from already-sanitized sanitizedErr; fix Object.assign comment to accurately describe what is copies * fix: improve permission relay client filtering and enhance redaction functions * fix: address third review round (P1, P2, P3) - P1: update test expectations for [REDACTED_*] output format - P2: add total_tokens, prompt_tokens, completion_tokens to jsonRedactor EXCLUDED_KEYS - P3: remove ) and } from GENERIC_HEADER_FIELD_PATTERN value capture to prevent content leak after embedded parens - Fix buildKnownEnvVarPattern capture group to preserve env-var separator ([REDACTED]) - Add & to GENERIC_CREDENTIAL_ENV_PATTERN value exclusion to prevent URL query over-consumption * fix: address latest reviewer P2/P3 findings (errorLogSink redaction, X_API_KEY/AUTHORIZATION patterns, regression tests) * fix: address reviewer P1/P2 — bracketed values and multi-word header values - P1: Remove and from value captures in X_API_KEY_PATTERN, AUTHORIZATION_PATTERN, GENERIC_HEADER_FIELD_PATTERN, GENERIC_CREDENTIAL_ENV_PATTERN so bracketed secrets like are fully redacted instead of passing through unchanged. - P2: Widen header-style value captures to include spaces by removing from exclusions, using as delimiter (stops at newlines and URL query separators). Fixes multi-word leaks: , , , . - GENERIC_CREDENTIAL_ENV_PATTERN: add to negative lookbehind to prevent matching inside when the latter is already redacted. - GENERIC_HEADER_FIELD_PATTERN replacer: skip values starting with to preserve specific labels from earlier passes. - Add 7 regression tests covering both finding categories. * fix: address reviewer findings P1-P4 P1: Custom enumerable error properties now redacted in log.ts logError iterates all own enumerable properties on the original error and applies redactSensitiveInfo to string values and jsonRedactor to object values, preventing credential-bearing custom fields from leaking through the sanitized error. Regression tests added in log.test.ts. P2: Soften single-source-of-truth claim; migrate easy call sites Header comment in redaction.ts updated to acknowledge that specialized scanners (secretScanner.ts, xaa.ts) are intentional exceptions. src/services/mcp/client.ts and src/services/mcp/auth.ts now use jsonRedactor for header redaction instead of ad-hoc key checks. P3: Fix mock.restore cleanup in channelNotification.test.ts Cache-bust the real channelAllowlist module at describe-entry and re-register it in afterAll, following the pattern from bugfixes.test.ts. mock.restore alone does not clear mock.module overrides in Bun. P4: Remove unused ChannelGateResult kinds Removed 'auth' and 'policy' from the skip kind union and removed corresponding dead branches in useManageMCPConnections.ts. * fix: extract sanitizeError() to fix CI test fragility The logError tests were failing in CI due to parallel test execution racing on the module-level errorLogSink singleton. Extract the inline sanitization logic into an exported sanitizeError() helper and test that directly — it's pure, has no env-var or sink dependencies, and doesn't interact with shared mutable state. * fix: use Object.getPrototypeOf(err) instead of err as prototype in sanitizeError Object.create(err) sets the original error instance as the prototype of the sanitized copy, leaking non-enumerable own properties through the prototype chain. Use Object.getPrototypeOf(err) instead so the prototype is the error constructor's prototype (e.g. TypeError.prototype), preserving instanceof checks without exposing the original error's non-enumerable fields. Add a regression test verifying non-enumerable properties do not leak and update the prototype-chain test to assert Object.getPrototypeOf result. * fix: apply key-aware redaction and fail closed on non-serializable error props - String properties: use jsonRedactor(key, value) instead of redactSensitiveInfo(value) so keys like apiKey with innocuous values (e.g. 'my-key') are still caught via SENSITIVE_FIELD_SUBSTRINGS. - Object path: catch now replaces non-serializable/circular references with '[REDACTED]' instead of leaving the original object reference. - Add 2 regression tests for key-aware redaction and fail-closed behavior. * Update src/utils/log.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: redact bare auth header keys in JSON/header objects - Add 'auth' to SENSITIVE_FIELD_SUBSTRINGS in src/utils/redaction.ts:109 to match URL/diagnostic redactors treatment of auth - Add regression test for bare auth header keys in src/utils/diagnostics/redaction.test.ts:88 Co-authored-by: openhands <openhands@all-hands.dev> * fix: narrow auth matching, redact nested transcript JSONL, fix channel skip message * fix: address CodeRabbit nits — comment, hint, JSONL fallback redaction * fix: key-aware malformed JSONL fallback and auth/x-auth in free-form text * fix: strengthen redactJsonLines trailing rest redaction and auth test assertions * fix: preserve non-JSON prefix in redactJsonLines fallback and redact it * fix: tighten redactJsonLines prefix test to exact output assertion * fix: redact MCP log sink payloads and errorStr before writing to disk * fix: address P1 findings — URL #-in-password, ;-delimited query params, split channel trust-boundary - Allow in URL userinfo password on malformed-URL fallback path (new URL() fails when password contains fragment delimiter). - Redact -delimited sensitive query params by splitting on both & and ; in redactMalformedQuery, plus redactSemicolonQueryParams post-processor for valid-URL output. - Restore channelNotification.ts to upstream/main to fully split OAuth/org-policy trust-boundary changes from credential redaction PR. * fix: update callers to match upstream/main function signatures channelNotification.ts was restored to upstream/main to split trust-boundary changes from the redaction PR. This commit updates the three caller sites that previously passed extra arguments: - ChannelsNotice.tsx: pass getSubscriptionType() + undefined to getEffectiveChannelAllowlist (needs 2 args upstream) - interactiveHandler.ts, channelNotification.test.ts: drop 3rd pluginSource arg from findChannelEntry (takes 2 args upstream) * fix: address reviewer findings — OAuth mock, notice states, marketplace disambiguation P1: Mock getClaudeAIOAuthTokens and getSubscriptionType in channel notification tests so they pass on CI where no real OAuth exists. P2: Restore blocked-auth/org-policy notice states in ChannelsNotice.tsx so the UI shows the correct blocker when gateChannelServer rejects unauthenticated users or orgs without channelsEnabled. P2: Add pluginSource disambiguation to findChannelEntry so same-name plugin entries from different marketplaces are matched by runtime source rather than first-match order. Add regression test with non-matching marketplace first to cover the bug. * fix: address reviewer findings — relay gate parity and allowlist regression test - Replace filterPermissionRelayClients in interactiveHandler with inline gateChannelServer call so the relay predicate checks ALL gates including disabled-channel, auth, org policy, and approved-plugin allowlist, not just session entry + marketplace. - Clean up unused imports (getAllowedChannels, parsePluginIdentifier, findChannelEntry, filterPermissionRelayClients). - Add regression test: gateChannelServer rejects marketplace-matched plugin not on approved allowlist (full-gate path). * fix: redact mixed semicolon secrets in valid-URL path and route OpenAI shim through centralized redactor P1: Pre-redact semicolon-delimited sensitive query params from the raw query string in redactUrlForDisplay BEFORE URLSearchParams encodes ; as %3B. Previously model=ok;token=SECRET leaked because parsed.toString() reserialized to model=ok%3Btoken%3DSECRET, making it invisible to the post-process pass. P1: Route openaiShim's redactUrlForDiagnostics through the centralized redactUrlForDisplay so the semicolon fix, malformed-URL fallback, and all future redaction improvements apply to OpenAI-compatible diagnostic logs too. Keep redactSecretValueForDisplay as an additional safety net after the centralized pass. Add 3 regression tests for mixed-separator queries. * Update src/utils/redaction.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: add fragment-query credential regression test and correct dev-channel gate comments P2: Add regression test for redactUrlForDisplay with query-like credential in fragment (e.g. #debug?token=SECRET). Fix raw-query pre-processing to only extract query before the first #, preventing fragment content from being treated as query parameters. P3: Update comments in interactiveHelpers.tsx to match the actual gate order — OAuth and org-policy gates still exist in gateChannelServer() after restoring to upstream/main; the --dangerously-load-development- channels flag only bypasses the allowlist gate. * fix: add port+fragment+@ fallback test and restructure dev-channels dialog tests * fix: registerDevChannels seam, bare-host #-in-password heuristic, and coverage restructure * fix: add OAuth and org-policy gate test coverage - Refactor auth module mock to use mutable variables per test - Auth gate test: empty OAuth tokens -> kind:auth - Policy gate test: team subscription without channelsEnabled -> kind:policy * fix: prefer exact server channel entries before plugin disambiguation - Return exact server-kind candidate first when candidates include both server and plugin entries with same name - Added regression test covering mixed server/plugin --channels entries to ensure exact server opt-in is not overridden by plugin candidate - This prevents a plugin marketplace mismatch from incorrectly rejecting a server the user explicitly selected via server:plugin:slack * fix: only trust exact [REDACTED] placeholder in generic header field pattern - Changed GENERIC_HEADER_FIELD_PATTERN to only bypass exact '[REDACTED]' canonical placeholder - Prevents non-canonical placeholders like '[REDACTED_API_KEY]' or '[REDACTED_actual_secret]' from leaking through - Updated tests to expect canonical '[REDACTED]' output for generic pattern * fix: handle bare hosts in malformed URL userinfo fallback - Added regex to recognize bare hostnames (with optional port) in the fragment heuristic - Added tests for //alice:sec#ret@host and //alice:sec#ret@host:443 * fix: add relay dispatch path test for non-allowlisted plugin - Added test using full gateChannelServer predicate in filterPermissionRelayClients - Mirrors the exact relay dispatch path used in interactiveHandler - Ensures marketplace-matched plugin not on allowlist is excluded from permission preview * fix: enhance URL redaction logic to handle valid hosts before fragment * fix: refine URL redaction logic to ensure valid host checks before fragment * fix: enhance redaction logic to handle embedded URLs in free-form text * fix: update redaction logic to remove user info from OpenAI base URL in diagnostic report * fix: ensure findChannelEntry returns undefined when no exact matches are found * fix: improve URL redaction logic to remove user info and ensure proper formatting * fix: enhance redactDiagnosticUrl to preserve query-param values and trailing slashes * fix: refine redaction logic to preserve meaningful path segments and handle trailing slashes correctly * fix: enhance redactDiagnosticUrl to preserve literal path segments and handle trailing slashes correctly * fix: preserve semicolon-delimited query params during redaction * fix: update redaction logic to support semicolon-delimited query parameters * fix: enhance redactUrlForDisplay to handle bare hosts and improve fragment redaction * fix: enhance redactUrlForDisplay to correctly handle username-only userinfo with fragments * fix: address privacy findings — URL redaction in jsonRedactor, base URL redaction, diagnostic object collapsing, structural channel previews, pluginSource telemetry * fix: preserve falsey env-presence values in diagnostic redaction - false, "", and 0 under isEnvPresenceKey keys are now preserved as-is instead of misrepresented as "[set]" - Added regression test for absent/falsey env-presence inputs * fix: address CodeRabbit findings — sync describe, heartbeat emitter, responsesBody filtering, dev entry precedence * chore: remove stray Windows path artifact * fix: update redaction import path in taskReport module * fix: address CodeRabbit P1-P3 findings and rebase regressions - F1: rebase onto upstream/main, fix taskReport.ts import path - F2: Ollama native chat code recovered via rebase (6 functions) - F3: &-truncation in credential regexes fixed via post-processing pass - F4: 'tokens' added to jsonRedactor EXCLUDED_KEYS - F5: redactHomePath case-sensitivity aligned with redactPathForStatus - F6: credential metadata object preserved in issue report (sensitive-key check moved inside type branches) - F7: heartbeat tests updated for pre-drain write behavior - F8: reportTask test expects [REDACTED] (matches centralized output) - rm: stray C:\repo\ Windows path artifact * fix: address reviewer findings — generic regex &-handling and diagnostic secret-key masking - Remove & from excluded char classes in 4 generic patterns so they consume full secret values (URL-query &-splitting belongs in redactUrlForDisplay). - Remove now-obsolete &-tail post-processor pass. - Remove credential from DIAGNOSTIC_SECRET_KEY_PATTERN so issue report credential metadata objects are traversed, not collapsed. - Restore broad isDiagnosticSecretKey check before type dispatch in redactDiagnosticObjectInternal so objects/arrays under secret-marked keys (auth, password, token, etc.) are masked. - Update issue report test baseUrl expectation (no trailing &mode=test after generic redactor consumes past &). * fix: address reviewer findings — URL delimiter safety, jsonRedactor #-drop, embedded URL query redaction - Restore &#; delimiters in generic pattern value classes (F1) so safe query tails (&mode=test) survive. Re-add &-tail post-processor for non-URL abc&def case. - Gate redactUrlForDisplay in jsonRedactor to https?:// strings only (F2) to prevent #-drop on ordinary text like 'fails after #setup'. - Add URL query redaction step to redactSensitiveInfo (F3) that extracts https?:// URLs from free-form text and routes them through redactUrlForDisplay, catching signature/sig params that generic patterns miss. Skip already-redacted URLs to avoid double-redaction. * fix: add Cookie/Set-Cookie semicolon-safe redaction pass, tighten &-tail regex * fix: COOKIE_PATTERN consume comma-joined multi-cookie values * fix: address P2 findings — URL redact skip, pre-drain write promise, permission truthy check * fix: update log.test.ts expectation, add protocol-relative URL support * fix: enhance redaction for provider env-vars in URLs, preserve safe query params * fix: enhance redaction for uppercase provider keys and cookie query params * fix: enhance redaction for bare Bearer and JWT tokens in sensitive info * fix: update report task test expectations for new redaction format * fix: limit token exemption to numeric values, protect semicolon cookie query tails * test: add tests for truncateForPreview to ensure sensitive data redaction --------- Co-authored-by: Gravirei <gravirei@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: openhands <openhands@all-hands.dev> |
||
|
|
22fa5b4227 |
fix(copilot): auto-refresh Copilot token on 401 instead of only showing re-auth hint (#1766)
* fix(copilot): auto-refresh Copilot token on 401 instead of only showing re-auth hint When GitHub Copilot returns 401 'token expired', the existing flow only showed a hint to run /onboard-github (the #1042 fix) but never actually refreshed the token mid-session. Now _doRequest detects the 401 + 'token expired' body in GitHub mode, calls refreshCopilotTokenOn401() to exchange the stored OAuth token for a fresh Copilot token, updates process.env, and retries the request. Fixes #1746 * fix: promote refreshedCopilotToken in auth precedence and add 401 retry integration test - Fix auth value precedence in buildHeadersForAttempt so refreshedCopilotToken takes effect even when a CredentialPool is active (single key still creates a pool, shadowing the refreshed token on retry) - Add integration test verifying GitHub Copilot 401 'token expired' triggers refreshCopilotTokenOn401() and retries chat_completions with the new token (P2 for PR #1766 review) * test: add codex_responses 401 retry integration test (CodeRabbit P2) Adds a new test 'GitHub Copilot 401 codex_responses retries with refreshed token' that verifies the 401 retry path for the codex_responses transport (lines 2349-2390). Mocks performCodexRequest to throw APIError(401, 'token expired') on first call and return a valid SSE response on second, verifies refreshCopilotTokenOn401 is invoked and the retry uses the refreshed token. * review: shared token-expired helper, credential-pool test, edge case tests Finding 1: Promote token-expired detection into isCopilotTokenExpiredError() helper used by both chat_completions and codex_responses paths. Finding 2: Add credential-pool integration test verifying that refreshedCopilotToken takes precedence over credentialPool.next() when OPENAI_API_KEYS is active. Finding 3: Add edge case tests - 'token has expired' variant, 401 without expired substring (no refresh), and same-token refresh (retries with original token, exhausted, then fails). * review: credential source check in codex retry path, fix mock leak - Add credential source gate (apiKey === process.env.OPENAI_API_KEY) to codex_responses 401 retry path, matching the chat_completions path guard - Add same-token guard (newApiKey !== apiKey) after refresh in codex path - Remove leaking mock.module('./codexShim.js') from providerOverride test, use fetch mock instead so performCodexRequest converts 401 to APIError - Remove duplicate old test that was left behind by previous edit - Add providerOverride test verifying the credential source gate blocks refresh * review: P2 refreshedCopilotCodexToken, P3 guard continue on last attempt P2: Add refreshedCopilotCodexToken variable that takes precedence over providerOverride.apiKey on retry, mirroring the chat path's refreshedCopilotToken in buildHeadersForAttempt. Prevents stale override key from being reused after a successful refresh. P3: Guard the chat path's continue with attempt < maxAttempts - 1 so the original 401 is surfaced when the 401 lands on the final retry slot, instead of falling through to the generic 500 exit error. * review: P1 chat path credential gate, P3 log refresh failures P1: Add oldToken === (process.env.OPENAI_API_KEY ?? '') check in chat path before calling refreshCopilotTokenOn401(), matching the codex path guard. Prevents credential substitution when providerOverride.apiKey, route credential, or custom auth header was the failing credential. Add regression test for chat path with mismatched providerOverride. P3: Log caught error with logForDebugging before returning false in refreshCopilotTokenOn401() catch block, so secure storage read failures, token exchange rejections, and persistence failures are visible in debug logs instead of silently swallowed. * fix: restrict codex refresh path to Copilot/GHE endpoints only isGithubWithCodexTransport was using isGithubMode which is true for all GitHub modes including GitHub Models API and custom routes. Changed to isGithubCopilotEndpoint that checks githubEndpointType === 'copilot' || githubEndpointType === 'ghe', matching the chat path's isGithubCopilot guard. Prevents COPILOT_HEADERS and refreshCopilotTokenOn401 from being triggered for non-Copilot endpoints. * fix: move didRefreshCopilotToken assignment after credential source check The refresh flag was set before confirming oldToken matches process.env.OPENAI_API_KEY, burning the single refresh chance when the credential pool rotates to a non-matching key first. Moved inside the credential source check so it's only consumed for eligible credentials. * fix: add oldToken guard to chat-path credential source check Prevents '' === '' match when both Authorization and OPENAI_API_KEY are empty from burning the refresh flag on an unauthenticated request. --------- Co-authored-by: Gravirei <gravirei@users.noreply.github.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> |
||
|
|
1aabe261db |
feat(bughunter): make /bughunter public + add /bughunter-security & /bughunter-perf with robust fallback prompts (#1621)
* feat(bughunter): split into /bughunter, /bughunter-security, /bughunter-perf
Replace the single /bughunter command with three siblings that share a
common prefix:
/bughunter — general bug hunt (existing prompt, untouched)
/bughunter-security — OWASP-aligned, exploit-driven, confidence ≥ 8
/bughunter-perf — hot-path complexity, sync I/O, leaks, N+1
Both new subcommands are prompt commands built with
createMovedToPluginCommand so they migrate to the bughunter marketplace
plugin unchanged once it ships. While the marketplace is private they
inline the full audit prompt (frontmatter + !`git ...` blocks) just like
the existing /bughunter.
All three stay in the public COMMANDS list (not INTERNAL_ONLY_COMMANDS)
so non-ant users can invoke them. clearCommandMemoizationCaches() now
also flushes the zero-arg COMMANDS() and builtInCommandNames() memos so
tests can switch USER_TYPE mid-run without poisoning the cache.
Adds regression tests in src/commands.test.ts covering:
- bughunter stays public for non-ant users
- bughunter-security and bughunter-perf are in the public list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(bughunter): remove orphan index.js after .js → .ts rename
The bughunter command directory was renamed from a single .js file to
index.ts in the previous commit, but git tracked them as separate paths
so the old .js was left in the tree. Drop it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(bughunter): enhance fallback prompts for robustness in non-git environments
- Add graceful error handling to all git commands in fallback prompts (|| echo fallbacks)
- Add explicit non-git fallback guidance in Phase 1 for all three commands
- /bughunter: search for entry points, core business logic, recently modified files
- /bughunter-security: search for auth/middleware, validation, DB, config, upload code
- /bughunter-perf: search for handlers, loops, data access, serialization, build configs
- Improve context labels to clarify git context may be empty
* fix(bughunter): address CodeRabbit feedback
- Fix test isolation: restore USER_TYPE/IS_DEMO env vars in finally blocks
- Add non-git fallback test cases for all three bughunter commands
- Fix bash pipeline issue: replace if/then/else subshells with simple git commands + static fallback text in template
- Fix output format contradiction: remove LOW confidence from scoring (Phase 3 drops LOW, so scoring only includes Critical/Medium)
* fix(test): correct case and prefix in git fallback assertions for bughunter-security and bughunter-perf tests
* fix(test): add missing opening parenthesis in bughunter test assertions
* fix(bughunter): complete non-git fallback and propagate allowedTools
- Fix git commands in all three prompts to always succeed with fallback text (using || echo)
- Modify createMovedToPluginCommand to accept allowedTools parameter
- Add allowedTools to all three bughunter commands so slash-command turn grants declared tools
- Parse allowed-tools from frontmatter at command creation time
* fix(bughunter): complete non-git fallback and allowedTools propagation
- Fix git commands in prompts to always succeed with fallback text (using || echo)
- Modify createMovedToPluginCommand to accept allowedTools parameter
- Add allowedTools to all three bughunter commands so slash-command turn grants declared tools
- Fix RECENTLY COMMITTED FILES command to avoid command substitution (permission check rejects )
- Update tests to accept shell tool's '(Bash completed with no output)' for empty results
- Use runWithCwdOverride and additionalWorkingDirectories for proper test isolation
* fix(bughunter): prevent shell injection via user-provided args
The user-provided scope was interpolated into the prompt template BEFORE
executeShellCommandsInPrompt() ran, so any !command or ```! block
syntax in the args would be interpreted and executed as shell commands.
Fix: parse frontmatter from the raw template and run shell execution first
(with {{ARGS}} still in place — inert to shell patterns), then replace
{{ARGS}} with the user scope on the processed output. This ensures args
are never fed through the shell command parser.
* refactor(bughunter): use createGetAppStateWithAllowedTools helper
Replaces duplicate inline getAppState overrides across all three bughunter
commands (bughunter, bughunter-security, bughunter-perf) with the shared
helper from src/utils/forkedAgent.ts. This:
- Eliminates ~30 lines of duplicated permission context modification
- Merges allowedTools with existing alwaysAllowRules.command (vs overwrite)
* fix(bughunter): address jatmn review - String.replace special patterns + test isolation
- Replace '{{ARGS}}' with a replacer function () => scope instead of
the plain string 'scope'. JavaScript's String.replace treats $&, $',
$', , 32855 specially even in string replacements, so a scope like
'src/auth $&' would render as 'src/auth {{ARGS}}' instead of literal
text. The replacer function bypasses all special patterns.
- Restore USER_TYPE and IS_DEMO env vars in the injection regression
test's finally block, matching the isolation pattern used by all other
bughunter tests.
* fix(bughunter): make fallback prompt generation work on Windows
Wrap executeShellCommandsInPrompt() in a try/catch in all three bughunter
commands. On platforms where bash is unavailable (e.g. Windows without Git
Bash), the bash-specific shell syntax (2>/dev/null, | head -N) would cause
executeShellCommandsInPrompt to throw MalformedCommandError, preventing the
prompt from being generated at all.
The catch handler replaces the !`command` inline patterns with a static
placeholder, allowing the LLM to still receive the full audit instructions
and non-git search strategies in Phase 1.
* fix(bughunter-perf): remove Low severity contradiction
The summary line included Low: L but Phase 3 drops non-measurable findings
and exclusions remove micro-optimizations. Low findings (measurable but
not user-visible) would never survive the filter, so remove Low from the
severity categories and summary line.
fix(bughunter-security): align log-forging exclusion with A9 criteria
Exclusion #11 blocked all log spoofing/forging, but A9 says to flag
log injection when it enables audit-trail forgery. Narrowed the exclusion
to allow concrete audit-trail attacks through while still excluding
generic non-exploitable logging suggestions.
* fix(bughunter-security): tighten log-forging exclusion threshold
Reword exclusion #11 to require concrete evidence of a log-entry or
structured-field forgery path, not merely unsanitized user input.
* fix(bughunter): preserve fallback text on Windows/no-bash path
Replace generic '(Shell execution unavailable)' placeholder with a regex
that extracts the || echo "..." fallback text from each shell command.
This ensures the prompt shows meaningful messages like
'(If empty: not a git repository or git unavailable)' even when bash is
unavailable (e.g. Windows without Git Bash), matching what Linux users see
from working shell execution.
Also make injection test assertion platform-agnostic — accept either bash
output or the static echo fallback text.
* refactor(test): extract duplicate mockContext into createMockToolContext helper
The three non-git fallback tests each had an identical ~42-line mockContext
object. Moved it to a shared createMockToolContext(cwd, commands) helper
and a FULL_GIT_COMMANDS constant. Also updated the injection test to use
the same helper. Net -89 lines.
* fix(createMovedToPluginCommand): only grant allowedTools when fallback prompt runs
The ant (USER_TYPE === 'ant') branch returns a plugin-install notice that
doesn't need Read/Glob/Grep/Bash tools, but allowedTools was statically
attached to the command object. This caused processSlashCommand to grant
turn-scoped permissions for tools that were never used.
Changed to a getter that returns undefined in the ant branch, so the
plugin-install notice runs without unnecessary tool permissions.
* fix(bughunter): simplify shell commands to single git commands, narrow catch to surface interruptions
* fix(bughunter): surface permission-denied/aborted shell preprocessing, fix Windows cleanup
* fix(dragDropPaths.test): resolve package.json relative to test file, not process.cwd()
* fix(commands.test): restore original cwd in rmRetry, guarantee env/cache cleanup on rm failure
* fix(bughunter): bound diff to 400 lines, swap HEAD~10 for git log -10
Address both P2 reviewer findings on feat/bughunter-command-v3-new.
(1) Fresh-repo HEAD~10 lookup stripped every snippet. In a one-commit
repo, `git diff --name-only HEAD~10..HEAD --diff-filter=AM` exits
128 (HEAD~10 doesn't resolve). The shell-execution catch then ran
the outer "strip all snippets" fallback, leaving git status /
diff --cached / diff HEAD empty even though those commands would
have produced useful context. Switched to `git log -10 --name-only
--diff-filter=AM`, which works at any history depth and yields the
same file list. Applied to bughunter, bughunter-security, and
bughunter-perf.
(2) Diff cap removed in
|
||
|
|
c2cf603344 |
feat(ctx): add /ctx context window visualization and token bars to /cost (#1610)
* feat(ctx): add /ctx context window visualization and token bars to /cost Adds a new /ctx slash command that surfaces exactly what the model sees on the next API call, with per-category token bars, last-response breakdown, session token usage, per-model totals, and a session summary. The command reuses the same pipeline as /context (compact boundary, optional context-collapse transform, microcompact, analyzeContextUsage) so the totals match what is actually sent, not a rough estimate. A single 'local' command with supportsNonInteractive: true is registered in the public COMMANDS array (replacing the disabled /ctx_viz stub) and added to REMOTE_SAFE_COMMANDS and BRIDGE_SAFE_COMMANDS, so /ctx works identically in interactive REPL, headless -p, remote, and bridge modes. /cost gains a Token usage section with colored bars for input, output, cache read, and cache write tokens, appended after the existing cost/duration/code-changes block without changing the per-model line. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ctx-viz: fix bar scale to use contextWindow denominator - Current Context block: use contextWindow as barMax so bars visually match percentage column - Session Token Usage: use sessionTotalTokens as sessionMax for same reason - Update test assertions for new tokens/contextWindow scale * cost-tracker: add format test for token bar display * test: add missing mocks for ctx_viz rendering test * fix(ctx_viz): restore mocks after test; filter capacity rows from model-seen breakdown * test: verify capacity rows are filtered from ctx viz output - Add 'Free space' capacity row to mocked analyzeContextUsage categories - Add assertion that rendered output does not contain 'Free space' - Verifies CAPACITY_ROWS filtering logic in ctx-noninteractive.ts * fix: isolate ctx_viz mocks and filter deferred categories - Restore real modules in afterEach to prevent mock.module() leakage into downstream tests (autoCompact, compression) - Filter deferred categories (isDeferred: true) from model-visible rows to avoid overstating context usage * fix: eliminate mock.module() for leaky modules in ctx_viz test Only analyzeContext.js is mocked (single data fixture). All other modules (autoCompact, microCompact, context, model, state) use their real implementations to avoid process-global mock.module() leakage into downstream autoCompact and compression tests. * fix: eliminate mock.module() entirely via renderCtxReport extraction Extract renderCtxReport() from call() so the rendering test can construct a hand-crafted RenderInput directly, requiring zero mock.module() calls. This avoids process-global mock leakage into downstream autoCompact/compression tests AND makes the test immune to mock pollution from any preceding test file. * chore: derive RenderInput from collectCtxData return type, use it in test - RenderInput is now Awaited<ReturnType<typeof collectCtxData>> — single source of truth, no manual property duplication. - Test imports RenderInput type and uses it in the renderCtxReport assertion instead of 'unknown', enabling compile-time fixture validation. --------- Co-authored-by: Gravirei <gravirei@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
0b24b60ce9 |
feat(provider): add Fireworks AI as official OpenAI-compatible provider (#1590)
* feat(provider): add Fireworks AI as official OpenAI-compatible provider
Includes vendor descriptor, brand descriptor (276 models), model
descriptors (full + merged), routing metadata, env auto-detection,
profile support, client defaults, and docs.
* test: add focused regression tests for Fireworks AI auth and routing
- Add 7 env-only routing tests in client.test.ts (shim routing,
stale model replacement, base URL override, shim option cleanup,
non-Fireworks override ignored, priority with MiniMax, Bedrock yield)
- Add FIREWORKS_API_KEY auto-detection test in providerAutoDetect.test.ts
- Add profile apply/persistence/env-drift tests in providerProfiles.test.ts
- Fix FIREWORKS_API_KEY propagation in strictEnv early return path
* fix: address reviewer comments on Fireworks integration
- Remove OPENAI_API_KEY exclusion so Fireworks cred wins over stale OpenAI key
- Fix TS type error in test by using String() wrapper
- Add Fireworks to detection priority comment in providerAutoDetect.ts
- Add useFireworksEnvOnlyProvider to shim condition for pattern consistency
- Replace loose .includes('fireworks.ai') with isFireworksBaseUrl() exact hostname check
* fix: add explicit case 'fireworks' in applyProviderFlag for credential precedence
- Add 'fireworks' to PREFERRED_PROVIDER_ORDER
- Add case 'fireworks' with dedicated key winning pattern (mirrors atlas-cloud)
- Add FIREWORKS_API_KEY to copiedOpenAIKeyProvider detection so stale
keys are cleaned up when switching away from Fireworks
* fix: guard fireworks defaultModel assignment against 'undefined' string coercion
* fix: remove leftover conflict marker in providerProfiles.ts
* docs(fireworks): add JSDoc to Fireworks functions for coderabbit docstring coverage
Adds JSDoc annotations to isFireworksBaseUrl, getFireworksBaseUrlOverride,
hasFireworksEnvOnlyProviderIntent, isFireworksModelName, and
applyFireworksEnvOnlyDefaults.
* fix(fireworks): cross-check NEARAI_API_KEY in env-only intent functions
hasNearaiEnvOnlyProviderIntent and hasFireworksEnvOnlyProviderIntent were
missing mutual cross-checks. When both NEARAI_API_KEY and FIREWORKS_API_KEY
are set, neither excludes the other, and nearai silently wins by ordering.
Adding !hasNonEmptyEnvValue(processEnv.FIREWORKS_API_KEY) to the nearai intent
and !hasNonEmptyEnvValue(processEnv.NEARAI_API_KEY) to the fireworks intent
ensures both return false, forcing explicit provider selection.
* fix(fireworks): fix typo in JSDoc — OPENAI_API_API_BASE -> OPENAI_API_BASE
* fix(fireworks): remove merge artifact and preserve no-key auth headers
- src/utils/providerAutoDetect.ts: remove leftover ======= conflict
marker and stale duplicate priority lines
- src/utils/providerProfiles.ts: preserve apiFormat, authHeader,
authScheme, authHeaderValue in the no-key OpenAI-compatible
fallback path so saved Responses mode / custom auth config
survives restart
* fix: Fireworks env-only startup preservation and MIMO priority comment
- Add FIREWORKS_API_KEY check to hasConcreteProviderSelection() so env-only
Fireworks setup is not overwritten by Gitlawb Opengateway default
- Add regression test verifying FIREWORKS_API_KEY survives no-profile startup
- Fix providerAutoDetect.ts priority comment to include MIMO_API_KEY (position 8)
and renumber subsequent entries to match actual detection order
* fix: also preserve env-only NEAR AI startup in hasConcreteProviderSelection()
* fix: remove duplicate Fireworks model descriptor, add FIREWORKS_API_KEY to test env cleanup
* fix: move duplicate model check to generation-time, add OPENAI_AUTH_* env cleanup to test harness
---------
Co-authored-by: Gravirei <gravirei@users.noreply.github.com>
|
||
|
|
eacc7d8fac |
feat: add NEAR AI provider integration (#1594)
* feat: add NEAR AI provider integration
- Create vendor, brand, and model descriptors for NEAR AI (22 models)
- Add NEAR AI to route metadata, client, provider auto-detect, and profiles
- Update compatibility tests and ProviderManager test PRESET_ORDER
- Add README and docs entries for NEAR AI provider
- Update .env.example with NEAR AI configuration
* fix: address CodeRabbit review comments
- Fix .env.example: change 'Option N' to 'Option 11' in quick reference
- Narrow isNearaiModelName to use explicit NearAI model prefixes instead of broad includes('/')
- Add NEARAI_API_KEY propagation in strictEnv startup path
* fix: align NEAR AI validation host matching with wildcard subdomain routing
- Add *.completions.near.ai to matchBaseUrlHosts in vendor descriptor
- Add matchHostnameAgainstRouteHosts helper with wildcard (*.) prefix support
- Use helper in both resolveRouteIdFromBaseUrl and getRuntimeValidationTarget
- Add regression test for qwen35-122b.completions.near.ai TEE endpoint
- Add NEARAI_API_KEY to test env cleanup list
* fix: align Near AI integration with env-only provider best practices
- Replace loose .includes('near.ai') with isNearaiBaseUrl() in providerProfiles.ts
for exact hostname validation (all 4 instances)
- Add NEARAI_API_KEY to copiedOpenAIKeyProvider detection in providerFlag.ts
- Add case 'nearai' to applyProviderFlag switch with dedicated key precedence
- Add 'nearai' to PREFERRED_PROVIDER_ORDER
- Add useNearaiEnvOnlyProvider to OpenAI shim condition in client.ts
- Remove OPENAI_API_KEY exclusion from hasNearaiEnvOnlyProviderIntent (dedicated
key wins over stale generic key, consistent with xAI pattern)
- Update detection priority comment in providerAutoDetect.ts to include
MIMO_API_KEY, XAI_API_KEY, and NEARAI_API_KEY
* fix: add exact completions.near.ai host to isNearaiBaseUrl
* fix: add higher-precedence provider key exclusions to hasNearaiEnvOnlyProviderIntent
* fix: add OPENAI_API_KEY and MINIMAX_API_KEY exclusions to hasNearaiEnvOnlyProviderIntent
* fix(near-ai): don't let stale OPENAI_API_KEY suppress Near AI routing
---------
Co-authored-by: Gravirei <gravirei@users.noreply.github.com>
|
||
|
|
286d403093 |
Update(zen-go): add claude-opus-4-8, minimax-m3, mimo-v2.5-free models and proper effort level integration for Zen/Go models (#1505)
* feat(provider): add OpenCode Zen/Go subscription support
Add OpenCode as a first-class provider, enabling users to connect their
Zen (pay-as-you-go) and Go ($10/mo) subscriptions via the /provider command.
New integration descriptors:
- vendors/opencode.ts — OpenCode Zen vendor (41 models)
- gateways/opencode-go.ts — OpenCode Go gateway (12 models)
- brands/opencode.ts — brand descriptor
- models/opencode.ts — full model catalog (GPT, Claude, Gemini, Qwen,
GLM, Kimi, MiniMax, Grok, DeepSeek, MiMo, Nemotron)
Modified files:
- integrationArtifacts.generated.ts — register descriptors and presets
- providerProfile.ts — add OPENCODE_API_KEY env/secret key, 'opencode'
profile type, and buildLaunchEnv handler
- providerConfig.ts — add DEFAULT_OPENCODE_BASE_URL constants
Auth: OPENCODE_API_KEY env var or interactive key entry in /provider
Transport: openai-compatible (chat_completions)
Base URLs: https://opencode.ai/zen/v1 (Zen), /zen/go/v1 (Go)
* feat(provider): add [Zen]/[Go] tags to OpenCode preset labels
Add visual tags in the /provider preset selection to distinguish
OpenCode Zen (pay-as-you-go) from OpenCode Go (subscription).
* feat(provider): enable dynamic model discovery for OpenCode
Switch OpenCode vendor and Go gateway from static to hybrid model
catalog with openai-compatible discovery. Models are fetched from
/v1/models on startup and cached for 1 hour. Manual refresh is
supported via the /provider UI.
Static model list is preserved as fallback when discovery fails.
* test(provider): add comprehensive OpenCode Zen/Go test suite
97 tests across 2 files covering:
Integration tests (72 tests):
- Vendor descriptor: id, label, classification, base URL, model, auth,
transport, preset, validation, catalog, discovery, usage metadata
- Gateway descriptor: id, label, vendorId, category, base URL, model,
auth, transport, preset, catalog, discovery
- Brand descriptor: id, label, canonicalVendorId, capabilities, modelIds
- Model catalog: registration, vendor/gateway associations, required
fields, valid classifications, reasoning/coding tags, no duplicates,
model counts (41 Zen, 12 Go), modelDescriptorId consistency
- Cross-reference: brand↔model, vendor↔model, gateway↔model,
shared OPENCODE_API_KEY
- Registry validation: no errors, no preset conflicts
- Edge cases: unique ids, unique apiNames, non-empty labels, valid
contextWindow/maxOutputTokens, valid defaultModel format, validation
message content, discovery config
Profile tests (25 tests):
- Type guard: isProviderProfile('opencode'), rejects invalid values
- buildLaunchEnv: persisted env, defaults, process env precedence,
OPENCODE_API_KEY mapping, whitespace/null/undefined/empty handling,
very long keys, special characters, concurrent access, boundary
values, no credential leakage
* fix(provider): add per-model endpoint routing (P1)
Add endpointPath field to OpenAIShimTransportConfig so catalog entries
can specify which API path to use per model. This addresses the
maintainer's [P1] finding that all models were routed to
/chat/completions regardless of their upstream endpoint.
Changes:
- descriptors.ts: add endpointPath?: string to OpenAIShimTransportConfig
- openaiShim.ts: buildRequestUrl checks shimConfig.endpointPath first
- vendors/opencode.ts: add transportOverrides to 31 catalog entries
(GPT→/responses, Claude/Qwen→/messages, Gemini→/models/<id>)
+ switch to source: 'static' to prevent free models from live API
- gateways/opencode-go.ts: add transportOverrides to 4 entries
(MiniMax/Qwen→/messages) + switch to source: 'static'
- opencode.test.ts: update tests for static source, remove discovery tests
* refactor(opencode): model OpenCode Zen/Go as gateways (P2)
* docs(provider): document OpenCode setup and move badge metadata to descriptors
- Add OpenCode Zen/Go rows to README supported providers table
- Add OpenCode Zen/Go examples and OPENCODE_API_KEY to advanced-setup.md
- Add PresetBadge type to descriptor/manifest with badge propagation in
artifact generator
- Move 4 hard-coded preset badges ([FREE], [Sponsor], [Zen], [Go]) from
ProviderManager.tsx into descriptor preset metadata
- Add badge field to providerUiMetadata so UI components read from manifest
- Update integration overview docs to recommend preset.badge for future
gateways
* fix(provider): match request body to endpoint format for OpenCode /messages and /responses (P1)
Extend the openaiShim transport so that endpointPath overrides select
both the URL and the correct body/response format:
- /responses → OpenAI Responses API body (input, max_output_tokens)
- /messages → Anthropic Messages API body (content blocks, system, max_tokens)
Also fixes: abort listener leak in SSE passthrough, system prompt
content-block flattening, and removes [Zen]/[Go] badge entries (P3).
Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
* fix(provider): add Google AI SDK body/response format for OpenCode Zen Gemini models (P1)
The three Gemini models in the OpenCode Zen catalog (gemini-3.5-flash,
gemini-3.1-pro, gemini-3-flash) were sending chat-completions body to
the /models/gemini-* endpoint, which expects Google AI SDK format.
- effectiveTransport now detects /models/gemini- endpointPath → 'gemini'
- buildGeminiBody() converts Anthropic messages → Google contents[]
with role mapping, systemInstruction, generationConfig, functionDeclarations
- geminiSseToAnthropic() parses Google SSE frames → Anthropic stream events
with text deltas, functionCall tool_use, finishReason mapping
- _convertGeminiToAnthropicResponse() for non-streaming responses
- Streaming/non-streaming routing via URL detection (/models/gemini-)
- serializeBody(), hasToolsPayload, omitGeminiTools all updated
* fix: prevent OpenCode model descriptors from shadowing canonical limits
P1: Prefix all defaultModel values in opencode.ts with 'opencode-'
so the fallback findModelDescriptorForApiName() doesn't match
canonical model names. The OpenCode descriptors are still found
via catalog entry lookup when the OpenCode route is active.
P2: Add 'OpenCode Go' and 'OpenCode Zen' to PRESET_ORDER in
ProviderManager.test.tsx between 'OpenAI' and 'OpenRouter'
so navigateToPreset() sends the correct number of j keypresses.
* fix: align OpenCode Go descriptor metadata with Zen
- category: 'hosted' → 'aggregating' (both are aggregating gateways)
- add validation block with OPENCODE_API_KEY guidance
- update test assertion from 'hosted' to 'aggregating'
* fix: accept OPENAI_API_KEY as fallback in OpenCode validation
When users set up OpenCode Zen/Go via /provider, the key is saved as
OPENAI_API_KEY (via buildCompatibilityProcessEnv). The validation block
only checked OPENCODE_API_KEY, causing a startup warning even though
the runtime auth header had the key it needed.
Add OPENAI_API_KEY to validation.credentialEnvVars for both gateways,
matching the pattern used by Hicap and Gitlawb Opengateway.
* chore: trigger mergeability recheck
* feat(shim): forward effort/thinking to OpenCode Zen/Go endpoints
- buildResponsesBody: add reasoning_effort + reasoning_summary + include
- buildAnthropicMessagesBody: add thinking config (adaptive/enabled/budget)
- buildGeminiBody: add thinkingConfig with thinkingLevel mapping
- modelSupportsEffort: allow OpenCode Claude and Gemini models
- modelSupportsMaxEffort: add opus-4-7
- getAvailableEffortLevels: show standard levels for OpenCode native models
- opencode-go: add missing validation block
* feat: update OpenCode Zen and Go model counts, add new models, and enhance effort level handling
* feat: implement xhigh effort support for specific models and adjust effort level handling
* fix(effort): address reviewer feedback on xhigh + new models
- docs/advanced-setup.md: bump OpenCode Go count 12 → 13
- openaiShim.ts: include opus-4-8 / opus-4.8 in the adaptive thinking
detection so the new model uses the adaptive + effort path instead
of falling back to budgetTokens
- effort.ts: modelUsesOpenAIEffort now also rejects models that include
'claude-' or 'gemini-' — without this, OpenCode Claude/Gemini
routes (provider=openai) were misclassified as OpenAI-style and
could leak xhigh past the new gate
- effort.codex.test.ts: lock in the new exclusion with a regression
test against the openai provider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(effort): address reviewer feedback on xhigh effort + new models
Closes the three P2 findings from PR #1505 review:
1. Settings schema now accepts 'xhigh' so a persisted xhigh survives
restart instead of being silently dropped by .catch(undefined).
2. ModelPicker /effort cycle is driven by getAvailableEffortLevels(model)
instead of a boolean includeMax, so models supporting xhigh
(opus-4-7/4-8, OpenAI/Codex) can actually select it from the picker.
displayEffort clamp now uses the available levels list, so stale
xhigh also clamps to high when the focused model doesn't support it.
3. SDK/control metadata uses getAvailableEffortLevels(model) instead of
the EFFORT_LEVELS fallback that advertised xhigh to every max-capable
model. SDK schema + generated types extended to include 'xhigh'.
Also fixes a latent generator bug: the array case in generate-sdk-types
now parenthesizes union/intersection elements so the trailing [] binds
the whole type, e.g. ("a"|"b")[] rather than "a"|"b[]. Without this,
the regenerated xhigh levels ended up typed as the single-literal
"xhigh"[] and broke the modelInfo assignability check.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(effort): order xhigh before max in EFFORT_LEVELS
EFFORT_LEVELS now matches getAvailableEffortLevels() output order
(['low', 'medium', 'high', 'xhigh', 'max']), and the order asserted by
the existing effort.codex.test.ts tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(effort): order xhigh before max in settings + SDK schemas
Matches the EFFORT_LEVELS / getAvailableEffortLevels order from the
previous commit. The Zod enum order doesn't affect runtime validation,
but keeps the source consistent and avoids confusion if anyone reads
the enum literal to infer display order.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(effort): clamp ModelPicker selection and mark xhigh as current
- ModelPicker.handleSelect: clamp the emitted/persisted effort to the
focused model's available levels so a toggled-but-unsupported level
(e.g. 'xhigh' on a model that doesn't support it) is never written
to settings.json or handed to the consumer. Add focusedAvailableLevels
+ focusedDefaultEffort to the memo guard so the function regenerates
when the focused model changes.
- EffortPicker: compare the xhigh option against the persisted 'xhigh'
level directly. The 'max' alias path is kept only for legacy
settings.json values that still hold 'max' from before xhigh was
introduced.
* docs(effort): fix stale EffortPicker comment about xhigh normalization
openAIEffortToStandard is a type cast that passes 'xhigh' through as a
first-class EffortLevel — the shim only converts to 'max' at the
Anthropic request boundary, not here. Update the comment to match.
* docs(effort): update /effort help to match xhigh support matrix
The /effort --help output still described max as "Opus 4.6 only" and
xhigh as an "alias for max", but this PR promotes xhigh to a first-class
EffortLevel and allows it for OpenCode Claude Opus 4.7/4.8 (with max
also allowed for those Opus variants). Update the help so it matches
the picker/runtime behavior:
- max: "(Opus 4.6+)"
- xhigh: "(OpenAI/Codex and Opus 4.7+)"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(sdk): address reviewer P2 — sync xhigh across override union, schemas, CLI
- Add 'xhigh_effort' to ModelCapabilityOverride union so the new
call at effort.ts:93 typechecks (P2 finding 1).
- Add 'xhigh' to AgentDefinition.effort enum (coreSchemas.ts) and
control.applied.effort enum (controlSchemas.ts), then regenerate
coreTypes.generated.ts so the SDK public contract matches the
first-class effort level (P2 finding 2).
- Add 'xhigh' to the --effort CLI flag allowed list and help text
(main.tsx:945-951) so users can actually pass --effort xhigh
instead of hitting "It must be one of: low, medium, high, max".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(effort): narrow allowlist to shim-serialized models; sync max description
Address reviewer findings on PR #1505:
P2: The broad `m.includes('opus-4') || m.includes('sonnet-4')` branch
made older variants (claude-opus-4-1, claude-sonnet-4-5) advertise
effort support, but the Anthropic /messages shim only serializes
low/medium as anthropicBody.effort for the isAdaptive || isOpus45
set (opus-4-5/4-6/4-7/4-8, sonnet-4-6). For other models the shim
only emits thinking for high/max, so low/medium on those models
was silently dropped on the wire. Collapse the two 4-model branches
into one that matches the shim's serialization set; the substring
match still covers prefix variations (claude-, opencode-claude-).
P3: getEffortLevelDescription('max') said "Opus 4.6 only" but
modelSupportsMaxEffort now allows opus-4-6, opus-4-7, opus-4-8.
Update the shared description to "Opus 4.6+" so the picker and
/effort confirmation agree with the new support matrix (matching
the /effort --help text from
|
||
|
|
5a22d604f8 |
feat(provider): add OpenCode Zen/Go subscription support (#1350)
* feat(provider): add OpenCode Zen/Go subscription support Add OpenCode as a first-class provider, enabling users to connect their Zen (pay-as-you-go) and Go ($10/mo) subscriptions via the /provider command. New integration descriptors: - vendors/opencode.ts — OpenCode Zen vendor (41 models) - gateways/opencode-go.ts — OpenCode Go gateway (12 models) - brands/opencode.ts — brand descriptor - models/opencode.ts — full model catalog (GPT, Claude, Gemini, Qwen, GLM, Kimi, MiniMax, Grok, DeepSeek, MiMo, Nemotron) Modified files: - integrationArtifacts.generated.ts — register descriptors and presets - providerProfile.ts — add OPENCODE_API_KEY env/secret key, 'opencode' profile type, and buildLaunchEnv handler - providerConfig.ts — add DEFAULT_OPENCODE_BASE_URL constants Auth: OPENCODE_API_KEY env var or interactive key entry in /provider Transport: openai-compatible (chat_completions) Base URLs: https://opencode.ai/zen/v1 (Zen), /zen/go/v1 (Go) * feat(provider): add [Zen]/[Go] tags to OpenCode preset labels Add visual tags in the /provider preset selection to distinguish OpenCode Zen (pay-as-you-go) from OpenCode Go (subscription). * feat(provider): enable dynamic model discovery for OpenCode Switch OpenCode vendor and Go gateway from static to hybrid model catalog with openai-compatible discovery. Models are fetched from /v1/models on startup and cached for 1 hour. Manual refresh is supported via the /provider UI. Static model list is preserved as fallback when discovery fails. * test(provider): add comprehensive OpenCode Zen/Go test suite 97 tests across 2 files covering: Integration tests (72 tests): - Vendor descriptor: id, label, classification, base URL, model, auth, transport, preset, validation, catalog, discovery, usage metadata - Gateway descriptor: id, label, vendorId, category, base URL, model, auth, transport, preset, catalog, discovery - Brand descriptor: id, label, canonicalVendorId, capabilities, modelIds - Model catalog: registration, vendor/gateway associations, required fields, valid classifications, reasoning/coding tags, no duplicates, model counts (41 Zen, 12 Go), modelDescriptorId consistency - Cross-reference: brand↔model, vendor↔model, gateway↔model, shared OPENCODE_API_KEY - Registry validation: no errors, no preset conflicts - Edge cases: unique ids, unique apiNames, non-empty labels, valid contextWindow/maxOutputTokens, valid defaultModel format, validation message content, discovery config Profile tests (25 tests): - Type guard: isProviderProfile('opencode'), rejects invalid values - buildLaunchEnv: persisted env, defaults, process env precedence, OPENCODE_API_KEY mapping, whitespace/null/undefined/empty handling, very long keys, special characters, concurrent access, boundary values, no credential leakage * fix(provider): add per-model endpoint routing (P1) Add endpointPath field to OpenAIShimTransportConfig so catalog entries can specify which API path to use per model. This addresses the maintainer's [P1] finding that all models were routed to /chat/completions regardless of their upstream endpoint. Changes: - descriptors.ts: add endpointPath?: string to OpenAIShimTransportConfig - openaiShim.ts: buildRequestUrl checks shimConfig.endpointPath first - vendors/opencode.ts: add transportOverrides to 31 catalog entries (GPT→/responses, Claude/Qwen→/messages, Gemini→/models/<id>) + switch to source: 'static' to prevent free models from live API - gateways/opencode-go.ts: add transportOverrides to 4 entries (MiniMax/Qwen→/messages) + switch to source: 'static' - opencode.test.ts: update tests for static source, remove discovery tests * refactor(opencode): model OpenCode Zen/Go as gateways (P2) * docs(provider): document OpenCode setup and move badge metadata to descriptors - Add OpenCode Zen/Go rows to README supported providers table - Add OpenCode Zen/Go examples and OPENCODE_API_KEY to advanced-setup.md - Add PresetBadge type to descriptor/manifest with badge propagation in artifact generator - Move 4 hard-coded preset badges ([FREE], [Sponsor], [Zen], [Go]) from ProviderManager.tsx into descriptor preset metadata - Add badge field to providerUiMetadata so UI components read from manifest - Update integration overview docs to recommend preset.badge for future gateways * fix(provider): match request body to endpoint format for OpenCode /messages and /responses (P1) Extend the openaiShim transport so that endpointPath overrides select both the URL and the correct body/response format: - /responses → OpenAI Responses API body (input, max_output_tokens) - /messages → Anthropic Messages API body (content blocks, system, max_tokens) Also fixes: abort listener leak in SSE passthrough, system prompt content-block flattening, and removes [Zen]/[Go] badge entries (P3). Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com> * fix(provider): add Google AI SDK body/response format for OpenCode Zen Gemini models (P1) The three Gemini models in the OpenCode Zen catalog (gemini-3.5-flash, gemini-3.1-pro, gemini-3-flash) were sending chat-completions body to the /models/gemini-* endpoint, which expects Google AI SDK format. - effectiveTransport now detects /models/gemini- endpointPath → 'gemini' - buildGeminiBody() converts Anthropic messages → Google contents[] with role mapping, systemInstruction, generationConfig, functionDeclarations - geminiSseToAnthropic() parses Google SSE frames → Anthropic stream events with text deltas, functionCall tool_use, finishReason mapping - _convertGeminiToAnthropicResponse() for non-streaming responses - Streaming/non-streaming routing via URL detection (/models/gemini-) - serializeBody(), hasToolsPayload, omitGeminiTools all updated * fix: prevent OpenCode model descriptors from shadowing canonical limits P1: Prefix all defaultModel values in opencode.ts with 'opencode-' so the fallback findModelDescriptorForApiName() doesn't match canonical model names. The OpenCode descriptors are still found via catalog entry lookup when the OpenCode route is active. P2: Add 'OpenCode Go' and 'OpenCode Zen' to PRESET_ORDER in ProviderManager.test.tsx between 'OpenAI' and 'OpenRouter' so navigateToPreset() sends the correct number of j keypresses. * fix: align OpenCode Go descriptor metadata with Zen - category: 'hosted' → 'aggregating' (both are aggregating gateways) - add validation block with OPENCODE_API_KEY guidance - update test assertion from 'hosted' to 'aggregating' * fix: accept OPENAI_API_KEY as fallback in OpenCode validation When users set up OpenCode Zen/Go via /provider, the key is saved as OPENAI_API_KEY (via buildCompatibilityProcessEnv). The validation block only checked OPENCODE_API_KEY, causing a startup warning even though the runtime auth header had the key it needed. Add OPENAI_API_KEY to validation.credentialEnvVars for both gateways, matching the pattern used by Hicap and Gitlawb Opengateway. * chore: trigger mergeability recheck --------- Co-authored-by: Gravirei <gravirei@users.noreply.github.com> Co-authored-by: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com> |