Combo dispatch and the combo test button used to crash with
'TypeError: e.startsWith is not a function' when a step's modelStr
failed to resolve (regression after #2338 added per-account LKGP
routing for local/Docker providers). The TypeScript annotation says
the field is always a string, but malformed combo rows leaked through
to the dispatch path.
Two defensive boundary guards:
1. open-sse/services/combo.ts LKGP fallback findIndex — type-check
target.modelStr before calling startsWith.
2. src/app/api/combos/test/route.ts testComboTarget — coerce
target.modelStr at the entry, surface a clean 'Combo step is
missing a model id' error instead of crashing.
Regression test parses the source and asserts no unguarded
target.modelStr.<string-method> usages remain in combo.ts, so a
future refactor that reintroduces the pattern fails loudly.
The native claude OAuth path (base.ts:794 applySystemTransformPipeline)
early-exited because DEFAULT_SYSTEM_TRANSFORMS_CONFIG.providers[claude]
was {enabled:false, pipeline:[]}. Result: third-party-agent fingerprints
(github.com/anomalyco/opencode, 'You are OpenCode', 'Here is some useful
information about the environment...') leaked into /v1/messages system
blocks, triggering Anthropic billing-gate:
[400] Third-party apps now draw from extra usage, not plan limits.
Mirror the cc-bridge defaults:
- DEFAULT_PARAGRAPH_REMOVAL_ANCHORS + OPENWEBUI_PARAGRAPH_ANCHORS
- DEFAULT_IDENTITY_PREFIXES + OPENWEBUI_IDENTITY_PREFIXES
- DEFAULT_TEXT_REPLACEMENTS as replace_text ops (allOccurrences:true)
- obfuscate_words
Omit prepend_system_block + inject_billing_header — base.ts:759-784
already handles those on the native claude OAuth path.
UI mirror RoutingTab.tsx and snapshot in system-transforms.test.ts
updated for parity. 56/56 transform-related tests pass.
Verified end-to-end 2026-05-18 after npm run build:cli + restart:
- request v8tq04 -> 200 in 1.84s
- request shape {max_tokens:32000, reasoning_effort:high} -> 200 in 1.99s
- x-omniroute-provider=cc (claude-code OAuth pool, account 62875e01)
- telemetry: [SystemTransforms] claude-native: drop_paragraph_if_contains,
drop_paragraph_if_starts_with, replace_text, replace_text, obfuscate_words
Adds an import flow that lets users bring an existing Codex auth.json
into OmniRoute without a fresh OAuth login. Both a file-upload tab and
a paste-JSON tab are supported.
- `codexAuthImport.ts`: pure parser + createConnectionFromAuthFile
(conflict detection, overwriteExisting, JWT email/exp extraction)
- `POST /api/providers/codex-auth/import`: Zod-validated endpoint with
audit log (`provider.credentials.imported`)
- `importCodexAuthSchema` in schemas.ts (discriminated union json/text,
256 KB cap on paste source)
- `<ImportCodexAuthModal>` in providers/[id]/page.tsx with upload/paste
tabs, email auto-detection, name/email/overwrite fields
- "Import auth" toolbar button shown only on the Codex provider page
- 29 unit tests (17 parser + 12 schema) — all passing
- Add addFirstProvider, addFirstProviderDesc, learnMore to providers namespace in en.json
(keys existed only in common namespace, causing raw key display on fresh installs)
- Add primary 'Add Provider' button to empty state that reveals provider grid
(only 'Learn more' external link existed, leaving users with no way to add a provider)
- Remove || fallback strings now that i18n keys are correctly placed
Export filename change:
- Drop the redundant `codex-` prefix; embed the account email so multiple
exported files can coexist in the same downloads folder.
- Email is extracted from the id_token JWT `email` claim, with fallback
to connection.email and finally to the sanitized connection label.
- sanitizeFileNamePart now preserves @ so addresses survive intact
(e.g. `auth-diego@example.com.json`).
Apply Local refinement:
- ApplyCodexAuthModal: confirmation modal showing the resolved target
path, the side-by-side .bak location, and the centralized backup
trail. User must tick a confirmation checkbox before Apply enables.
- writeCodexAuthFileToLocalCli now writes a side-by-side
`auth-<timestamp>.bak` inside the .codex/ directory before replacing
the live file, in addition to the existing centralized backup. Both
inputs to the .bak path are server-controlled (dirname from the
static CLI_TOOLS table; basename from a server-generated ISO
timestamp), so no user input touches path APIs.
- apply-local route now emits a `provider.credentials.applied` audit
event with the resolved authPath and savedBakPath, and routes all
errors through sanitizeErrorMessage() per the security guide.
Tests: tests/unit/codexAuthFile.test.ts covers sanitization, JWT email
extraction, filename format for both branches (email/label), and the
ISO-timestamp .bak basename safety.
Scope: this is PR1 of the import/export work tracked under
_tasks/features-v3.8.0/importexport/. PR2 (import single) and PR3
(import bulk) will follow.
feat(ui): simple/advanced mode for Caveman & RTK + newbie UX improvements
Adapts oyi77's UX rework on top of our refactor/pages overhaul. Layout
priority: our 9-section sidebar restructure stays; PR's additions
(subtitles, intros, simple/advanced toggles, empty states, error labels)
are integrated into our structure.
Conflict resolution:
- index.tsx: union — exports InfoTooltip, PresetSlider (new shared
components from PR) alongside our NoAuthProviderCard.
- en.json: union — kept our "OmniSkills"/"AgentSkills" labels and
"API Key Manager" naming; added PR's subtitleKey strings, settings
intro keys, and empty state keys.
- sidebarVisibility.ts: kept our 9-section structure; added subtitleKey?:
string to SidebarItemDefinition and mapped subtitle keys onto the 7
matching items (endpoints, api-manager, combos, batch, context-caveman,
context-rtk, webhooks).
- Sidebar.tsx: kept our collapsible-section rendering; integrated PR's
subtitle support into resolveItem() and renderNavLink() label area.
- CavemanContextPageClient.tsx: took PR's version — adds SegmentedControl
for simple/advanced mode (gates full settings tab in advanced).
- RtkContextPageClient.tsx: took PR's version — adds SegmentedControl +
Collapsible filter catalog.
- settings/page.tsx: kept our redirect (we converted tabs→pages). Ported
PR's intro text paragraphs to /settings/ai, /settings/routing, and
/settings/resilience subpages using the auto-merged i18n keys.
- HomePageClient.tsx: kept ours — we removed Providers Overview card in
the refactor, and PR's empty state for that card is now redundant.
PR's equivalent empty state at /dashboard/providers (in
providers/page.tsx) auto-merged cleanly and serves the same purpose.
Closes#2316
Resolves conflicts in 9 files to bring 181 commits from release/v3.8.0 into
the dashboard refactor branch ahead of merging back to release.
Layout strategy: our pages overhaul (tabs→pages, restructured sidebar,
removed redundant headers, OpenCode Free no-auth card) is the source of
truth. Release's functional additions are adapted into our layout.
Conflict resolution:
- package.json/package-lock.json: take release's deps (axios bump, CLI v4
deps, tls-client-node/wreq-js move to optionalDependencies); re-add our
@xyflow/react addition; regenerate lockfile.
- src/shared/constants/sidebarVisibility.ts: keep our 9-section restructure
— release's new IDs (limits, media, cli-tools, agents, cloud-agents,
memory, skills, agent-skills, context-*) are all already present in our
groups.
- src/i18n/messages/en.json: auto-merge picked up all release's new keys
(autoCatalog*, quotaCutoffs*, systemTransforms*, schema-coercion, vision);
only naming conflict was OmniSkills/AgentSkills — kept ours (no space).
- src/app/(dashboard)/dashboard/HomePageClient.tsx: kept our Provider
Topology card; ported release's TierCoverageWidget (placed before
topology).
- src/app/(dashboard)/dashboard/settings/page.tsx: kept our redirect to
/settings/general (we moved tabs to separate pages); release's sticky
tab CSS change is moot in our structure.
- src/app/(dashboard)/dashboard/skills/page.tsx: rerere applied — release
hardcoded "OmniSkills" h1 was already removed by our header-cleanup
refactor.
- src/app/(dashboard)/dashboard/agent-skills/page.tsx: both branches
created this file independently with identical data source; kept our
Tailwind-themed 2-column grid (release's version used inline styles).
- src/app/(dashboard)/dashboard/batch/page.tsx: kept our single-tab
structure (FilesListTab moved to /batch/files page); ported release's
onRefresh prop addition.
- src/app/(dashboard)/dashboard/batch/files/page.tsx (not in conflict but
updated): added batches fetch + batches prop to preserve release's
feature of showing related batches in the file detail modal.
Pre-existing typecheck errors in open-sse/services/contextManager.ts
(lines 141, 154, 167) come from release/v3.8.0 and are not introduced by
this merge.
- Providers page: empty state with guided 'Add your first provider' card
- Providers page: home page shows only configured providers by default
- Settings: add intro text to Routing, Resilience, and AI tabs
- i18n: fix 'Api Key Mgmt' to 'API Key Management', remove duplicate key
- i18n: add keys for empty state, settings intros, free-only filter
Requests to opencode (noAuth: true) were failing with "No credentials for
provider: opencode" because getProviderCredentials found no DB connections and
returned null — triggering the 400 error path in chatHelpers.
Inject a synthetic credential object (connectionId: "noauth", no apiKey/token)
before the regular connection lookup so the executor receives valid credentials
and skips the Authorization header. Also enable model import for noAuth providers
(canImportModels = true when isFreeNoAuth).
OpenCode Free (noAuth: true) was routed through OAuthModal which tried to call
a non-existent /api/oauth/opencode/authorize endpoint, resulting in a 500 error.
Detect noAuth free providers via FREE_PROVIDERS[id]?.noAuth and render a
NoAuthProviderCard (lock_open + description) instead of the connections section
with the "+ Add" button that triggered the broken flow.
Simple mode shows stats, language packs, and output mode only.
Advanced mode reveals the full CompressionSettingsTab with all
engine internals, thresholds, and tool strategies.
Tailscale and ngrok expandable rows used <button> as outer container while also
containing inner <button> elements (copy URL, action buttons). Nested buttons are
invalid HTML and caused a React hydration error that prevented the app from
loading in the browser (stuck on Loading... spinner).
- Use Input's built-in icon prop so the search icon renders inside the correct
positioned context (Input's internal div.relative) instead of misaligned outside
- Switch from className to inputClassName so padding applies to the actual <input>
element, not the outer wrapper div
- Remove flex-1 spacer; make search container flex-1 so it fills available width
- ProviderCard: add cloud-agent dot (violet) to DOT_COLORS
- page.tsx: web-cookie/search/audio/cloud-agent/local/upstream-proxy
sections now pass their actual type as authType instead of displayAuthType
(which was always "apikey" for all static catalog groups)
- page.tsx: split search bar row into 25% input + 75% dot-type legend
showing all 10 auth types with their colors and translated labels
Add passthroughModels: true to the opencode registry entry so that unknown
model IDs trigger model lockout instead of connection cooldown. Fix dot color
for FREE_PROVIDERS in the Free Tier section by using toggleAuthType === "free"
to select the green dot instead of the blue oauth dot.
Moves Compatible Providers to the top (before Expiration Banner) so users can
add custom OpenAI/Anthropic compatible providers without scrolling. Reduces
ProviderCard icon from 32px to 28px and increases grid density by one column at
each breakpoint (gap-4→gap-3). Adds a curated Free Tier Providers section with
27 providers (OAuth/noAuth group + API-key free-tier group), positioned between
Expiration Banner and OAuth Providers. Cards in the free section suppress the
hasFree badge since context makes it implicit.
The existing /api/combos GET requires a management token, which broke
read-only integrations (opencode-omniroute-auth plugin and similar) that
need to enrich combo capabilities from a normal Bearer API key. Those
clients got 403 AUTH_001 'Invalid management token' even though the same
API key could list models via /v1/models.
This adds GET /v1/combos with the same auth model as /v1/models:
- Accepts valid Bearer API key OR dashboard session cookie.
- Falls back to anonymous when REQUIRE_API_KEY=false (single-user local).
- Projects ONLY public metadata: name, strategy, description, model id,
providerId, comboName (for combo-refs). Internal routing details
(connectionId, weights, labels, sortOrder, config) are stripped.
/api/combos (management writes) is unchanged.
- Add /dashboard/audit/a2a page with A2aAuditTab: lists tasks with skill/state
filters, colored state badges, duration, events and artifacts counts
- Add "A2A Audit" item to Audit sidebar group (Monitoring section)
- Remove duplicate "MCP Audit" from MCP Server sidebar group — it stays
only in the Audit group under Monitoring
- Improve McpAuditTab: fetch /api/mcp/audit/stats and show 4-card stat bar
(calls 24h, success rate, avg duration, top tool) above the filters
- Add audit-a2a to HIDEABLE_SIDEBAR_ITEM_IDS
- Add i18n keys: auditA2a in sidebar + header sections, a2a* in compliance namespace
- Rename sidebar/page titles: "mcp" → "MCP Server", "a2a" → "A2A Server" (en.json)
- Wrap top header section in <Card> on both MCP and A2A pages for consistent styling
- Remove redundant "hub MCP Server" / "group_work A2A Server" headings from page body
- Add MCP_GROUP collapsible sidebar group (MCP Server + MCP Audit) in Agentic Features
- Update AGENTIC_FEATURES_ITEMS type to SidebarSectionChild[] to support groups
- Remove Integration Surface card (tab switcher + Protocols tab card)
- API endpoints list always visible (no tab toggle needed)
- Cloud OmniRoute moved to first position inside Tunnels accordion section
- Tunnels header always visible (was conditional on tunnel flags)
- Cloudflare row: no longer collapsible — flat row with inline notice/error
- Remove leading comma from LAN IP and Tailscale IP inline displays
- Remove Cloudflare URL notice text (always-shown description removed)
- Remove double border between Tunnels header and Cloud OmniRoute row
- ngrok authtoken label: "not set in environment" (was "not set")
- MCP page: description + 3-step quick start merged into header area
- A2A page: description + 3-step quick start merged into header area
- Local Server: show LAN IPs inline (comma-separated) instead of chips below name
- Tailscale row: show Tailscale IP (100.x) inline with comma in row header
- Cloudflare expanded: remove readOnly URL input (URL visible in Active Endpoints bar)
- Tailscale expanded: remove both URL inputs; sudo field always visible (not conditional on running state)
- ngrok expanded: remove readOnly URL input; keep only authtoken field
- tailscaleTunnel: add 'serve status' fallback for older/permissioned CLI builds
- tailscaleTunnel: use settings.tailscaleEnabled as fallback when live funnel detection fails
- tailscaleTunnel: use path.format() to avoid CWE-22 false positive on sibling binary path
- api/network/info: new endpoint returning LAN IPs and Tailscale interface IP
- endpoint page: show machine LAN IP chips in Local Server row for network access
- endpoint page: show Tailscale 100.x IP URL in expanded Tailscale panel
- endpoint page: fix Tailscale Stop Funnel icon (vpn_lock_off -> vpn_key_off, valid glyph)
- Remove inline URL <code> blocks from all 5 connection rows (URLs now show only in the Active Endpoints bar)
- Show Active Endpoints bar when any URL is active (was: only when > 1)
- Fix Tailscale missing from Active bar by falling back to tunnelUrl when apiUrl is null
- Persist ngrok authtoken to /api/settings after first successful enable; restore on startup so it never needs re-entering