mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-12 10:12:11 +03:00
Compare commits
7 Commits
feat/condu
...
fix/releas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
169f329ccf | ||
|
|
78a5ce5cea | ||
|
|
8a35c45029 | ||
|
|
200233d301 | ||
|
|
5f38ab48ca | ||
|
|
99be31137c | ||
|
|
b1e151f3cc |
16
.env.example
16
.env.example
@@ -1958,6 +1958,11 @@ APP_LOG_TO_FILE=true
|
||||
# WARNING: produces large files — use only for temporary debugging.
|
||||
# CHAT_DEBUG_FILE=true
|
||||
|
||||
# Surf empty textContent chunks in the Claude response translation path for debugging.
|
||||
# Used by: open-sse/handlers/responseTranslator.ts. Set to "true" to enable.
|
||||
# Default: disabled (opt-in).
|
||||
# DEBUG_CLAUDE_NONSTREAM=true
|
||||
|
||||
# Enable E2E test mode — relaxes auth and enables test harness hooks.
|
||||
# NEXT_PUBLIC_OMNIROUTE_E2E_MODE=true
|
||||
|
||||
@@ -2630,6 +2635,7 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis
|
||||
# Adobe colligo normally rejects risk tokens minted without a headed browser.
|
||||
# ADOBE_FIREFLY_CHROME_CDP_PORT=9334
|
||||
# ADOBE_FIREFLY_CHROME_VISIBLE=0
|
||||
# ADOBE_FIREFLY_CHROME_HEADED=0 # Legacy alias for ADOBE_FIREFLY_CHROME_VISIBLE=1
|
||||
# ADOBE_FIREFLY_CHROME_HEADLESS=0
|
||||
# ADOBE_FIREFLY_CHROME_FORCE_RESTART=0
|
||||
# ADOBE_FIREFLY_CHROME_PING=auto
|
||||
@@ -2644,13 +2650,3 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis
|
||||
# TELEGRAM_DEFAULT_MODEL=auto/chat
|
||||
# TELEGRAM_BOT_API_BASE=https://api.telegram.org
|
||||
# TELEGRAM_WEBHOOK_TIMEOUT_MS=60000
|
||||
|
||||
# ── OmniConductor bridge (Conductor PRD RF1) ──────────────────────────────────
|
||||
# Mirrors the OmniConductor hub's tasks into the local A2A TaskManager via SSE.
|
||||
# Opt-in: the bridge only starts when CONDUCTOR_HUB_URL is set.
|
||||
# Token: emit a `spokesperson`-kind credential on the hub (POST /v1/peers, admin) —
|
||||
# server-side only, never exposed to the browser.
|
||||
# Used by: src/lib/conductor/boot.ts, src/lib/conductor/bridge.ts
|
||||
# CONDUCTOR_HUB_URL=http://127.0.0.1:7910
|
||||
# CONDUCTOR_HUB_TOKEN=
|
||||
feat/conductor-bridge
|
||||
|
||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -8,6 +8,18 @@
|
||||
|
||||
---
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
feat(images): execute full combo strategy + fallback in /v1/images/generations (#9239)
|
||||
|
||||
Add open-sse/services/imageCombo.ts that expands combo targets, filters
|
||||
to images-capable, executes priority strategy with handleImageGeneration
|
||||
per target, and returns first success or last failure. Route patches
|
||||
detect combo names before model resolution and divert to the new
|
||||
execution path.
|
||||
- **feat(images):** execute full combo strategy + fallback in `/v1/images/generations` for combo targets, expanding to images-capable models with priority strategy and per-target `handleImageGeneration` ([#9239](https://github.com/diegosouzapw/OmniRoute/pull/9239))
|
||||
|
||||
@@ -1,5 +1 @@
|
||||
---
|
||||
feature: 9490
|
||||
---
|
||||
|
||||
**Warm catalog startup from disk snapshot + parallel refresh** (opencode-plugin): The config-shim hook now reads the last disk snapshot *before* fetching, so the provider registers immediately with the last-known-good catalog (~1-2s vs ~30s on a warm gateway). All six fetchers run concurrently via `Promise.allSettled` instead of sequentially. A failed refresh keeps the snapshot (no overwrite). An in-flight guard prevents concurrent refreshes for the same cache key. The `features.diskCache: false` opt-out disables the warm read entirely.
|
||||
- **feat(plugin):** warm catalog startup from disk snapshot + parallel refresh in the opencode-plugin config-shim — registers ~1-2s on warm gateways instead of ~30s, `Promise.allSettled` over the six fetchers, keeps the last disk snapshot on failed refresh, and an in-flight guard prevents concurrent refreshes for the same cache key ([#9490](https://github.com/diegosouzapw/OmniRoute/pull/9490))
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
- feat(a2a): the Agent Card (`/.well-known/agent.json`) now announces skills derived from the OmniConductor fleet (`GET /v1/runners` OASF capabilities — one skill per online CLI profile + declared fleet skills), cached ~60s and fail-open when the hub is unset/offline
|
||||
@@ -1 +0,0 @@
|
||||
- feat(a2a): Conductor bridge — long-lived SSE consumer that mirrors OmniConductor hub tasks into the A2A TaskManager (explicit `canceled→cancelled` mapping with tests, persisted `last_event_id` cursor in the `key_value` table, exponential-backoff reconnection; opt-in via `CONDUCTOR_HUB_URL`/`CONDUCTOR_HUB_TOKEN`)
|
||||
@@ -1 +0,0 @@
|
||||
- feat(dashboard): "Conductor" panel — OmniConductor fleet (runners + task queue) live via server-side proxy routes (`/api/conductor/*`, management auth, hub token never reaches the browser), task detail with manifest/council and cancel-with-confirmation; sidebar entry under Tools
|
||||
@@ -408,11 +408,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/api/combos/[id]/route.ts": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/api/combos/reorder/route.ts": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@@ -3339,4 +3334,4 @@
|
||||
"count": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,30 +2,24 @@
|
||||
"_rebaseline_2026_08_09_8984_api_key_cache_mode": "PR #8984 own growth during the 2026-08-09 rebase: src/lib/db/apiKeys.ts 1529->1545 (+16 = the per-key apiKeys.cacheDefaultMode column + its row parsers and cascade wiring; additive at the existing connection write/read chokepoints). Covered by tests/unit/chatcore-semantic-cache.test.ts. (chatCore.ts stays at the pre-existing base-red ceiling — upstream tip already exceeds the frozen 5042, this PR only adds +2 on top; not re-bumped per the no-inherit-ratchet rule.)",
|
||||
"_rebaseline_2026_08_09_9207_breaker_halfopen_recovery": "PR #9207 own growth during the 2026-08-09 rebase: open-sse/services/accountFallback.ts 1978->2020 (+42 = recordProviderSuccess now also transitions the provider circuit breaker from HALF_OPEN to CLOSED when a request succeeds, so the breaker is not stuck half-open after repeated failures; the transition and its reset wiring grow the existing provider-success path, not extractable). Covered by tests/unit/provider-breaker-halfopen-recovery.test.ts.",
|
||||
"_rebaseline_2026_08_09_9351_antigravity_switch_auth": "PR #9351 own growth during the 2026-08-09 rebase: open-sse/executors/antigravity.ts 1528->1536 (+8 = switchAuth threaded out of tryResolveRetryFromErrorBody into handleAntigravityRateLimit's short-retry guard, so a decide429 switch decision beats the 60s same-account sleep; cohesive at the existing resolve chokepoint, not extractable). Covered by tests/unit/antigravity-429-switch-auth.test.ts.",
|
||||
|
||||
"_rebaseline_2026_08_09_9328_bottleneck_doexpire_rate_limit": "PR #9328 own growth during the 2026-08-09 rebase: open-sse/services/rateLimitManager.ts 1167->1221 (the Bottleneck doExpire capacity-leak monkey-patch plus its diagnostic branch and deterministic assertions live at the manager's existing wiring; monolithic patch, not extractable). Covered by tests/unit/bottleneck-doexpire-patch.test.ts.",
|
||||
"_rebaseline_2026_08_09_9296_adobe_media_capabilities": "PR #9296 (artickc, fix/adobe-firefly-model-capabilities) own growth: src/app/api/v1/models/catalog.ts 1590->1597 (+7). The image and video catalog serializers now expose the already-normalized Adobe Firefly discovery capability data (media_capabilities, plus the existing video modality/size fields) at their only response-emission chokepoints. The discovery parser and capability normalization remain in open-sse/services/adobeFireflyModels.ts; extracting these seven serialization fields would obscure the catalog contract. Covered by tests/unit/adobe-firefly.test.ts and tests/unit/image-upscale.test.ts.",
|
||||
"_rebaseline_2026_08_08_v3850_base_drift_batch_9757": "Base drift on release/v3.8.50, not own growth: the 08-06..08-08 merge batches grew 12 already-frozen (or newly-landed) files without carrying their rebaselines — the dedicated rebaseline PR #9616 was closed as 'superseded' but its file-size entries never actually reached the base, and later merges (#8894 combos page, #9539 EditConnectionModal, #8895 models route, #9294/#9293 catalog, #9541 db/core, #8970 tokenHealthCheck, #8925 mcp schemas+server, #8890 accountFallback, #9467 chat.ts, #8931 openai-to-kiro, ProxyRegistryManager) kept growing them. All 12 values re-measured on THIS branch's tree (= pure tip + this PR's 1-line chat.ts fix, which adds zero lines). This PR's own source changes (chat.ts identifier restore, stream.ts format carve-out) do not grow any frozen file past these values.",
|
||||
"_rebaseline_2026_08_08_migration_135_collision": "fix(db): resolve migration version 135 numbering collision — #9449's 135_connection_runtime_state.sql and #8908's 135_migrate_model_capability_max_token.sql both claimed version 135 (#9449 branched before #8908 merged and never got renumbered before landing on release/v3.8.50), which threw 'Migration version collision detected' the moment ANY code touched the database — a fresh install/deploy from this tip cannot even boot. Renumbered the later-landing file to 140 (next free slot) and added the matching isSchemaAlreadyApplied('140') retroactive guard, matching the established pattern already used for the prior 135/136 -> 137/138 renumber in the same file. Own growth: src/lib/db/migrationRunner.ts 1084->1094 (+10, the new case block) — irreducible, matches the existing per-case guard pattern exactly. Covered by tests/unit/migration-135-numbering-collision.test.ts (2/2), confirmed failing (reproducing the exact live crash) against the pre-fix colliding filenames, passing after.",
|
||||
"_rebaseline_2026_08_08_9183_reasoning_cache_index_sync": "Extracted fix(responses-api): sync reasoning-cache write index with the fixed read side (from the originally-authored #9183) — chatCore.ts's write side cached every response under a hardcoded messageIndex:0, and translator/index.ts's plain-turn (non-tool-call) cache-key lookup ALSO still hardcoded messageIndex 0 at its call site (a second, previously-undiscovered instance of the same hardcoding bug, found while re-verifying this fix against the current upstream tip — the two never agreed once a conversation went past its first assistant turn, so DeepSeek/Xiaomi-mimo plain-turn reasoning replay silently missed the cache). Own growth: open-sse/handlers/chatCore.ts 5034->5042 (+8, computing messageIndex from the incoming request's message count at both the streaming and non-streaming cache-write call sites) — irreducible call-site wiring. Covered by tests/unit/reasoning-cache.test.ts (new end-to-end write/read regression test, rebaselined below) and tests/unit/translator-helper-branches.test.ts fixture updates. Other #9183 sub-fixes (output_index collision prevention, reasoning-content-alias generalization) were originally assumed already superseded by upstream's own independent fix — a live incident 2026-08-08 disproved that for the message-vs-tool-call collision case specifically (fixed separately in #9822); not re-extracted here since this PR's own scope is the narrower messageIndex sync only.",
|
||||
"_rebaseline_2026_08_09_9342_network_error_guard": "PR #9342 own growth during the 2026-08-09 rebase: open-sse/services/accountFallback.ts 1978->2008 (+30 = the isQueueTimeout short-circuit plus a per-provider network-error dedup window in recordProviderFailure, keeping one VPN blip from the same provider's combo targets counting once per target). Covered by tests/unit/breaker-network-error-guard.test.ts. (chat.ts stays base-red: upstream tip is already 1918 > frozen 1904, this PR only adds +12 on top; not re-bumped per the no-inherit-ratchet rule.)", "_rebaseline_2026_08_09_9296_adobe_media_capabilities": "PR #9296 (artickc, fix/adobe-firefly-model-capabilities) own growth: src/app/api/v1/models/catalog.ts 1590->1597 (+7). The image and video catalog serializers now expose the already-normalized Adobe Firefly discovery capability data (media_capabilities, plus the existing video modality/size fields) at their only response-emission chokepoints. The discovery parser and capability normalization remain in open-sse/services/adobeFireflyModels.ts; extracting these seven serialization fields would obscure the catalog contract. Covered by tests/unit/adobe-firefly.test.ts and tests/unit/image-upscale.test.ts.", "_rebaseline_2026_08_02_9259_rolling_rpm": "PR #9259 (issue #8733) own growth: open-sse/services/rateLimitManager.ts baseline 1060->1167 (+107; final source 1153). The existing withRateLimit chokepoint now composes process-local rolling RPM leases with Bottleneck admission, releases pre-dispatch leases on queue timeout/abort/connection disable, preserves caller abort reasons, and wires 429/header state into the extracted rollingRpmGate.ts. The remaining growth is irreducible lifecycle wiring at the dispatch boundary plus the real watchdog test hooks needed to verify queued-wedge recovery; moving it further would obscure lease ownership and Bottleneck cleanup. Covered by the focused rate-limit manager/sliding-window suite (33/33); distributed multi-instance coordination remains explicitly out of scope.",
|
||||
"_rebaseline_2026_07_24_8470_hyperagent_sticky_thread": "PR #8470 (artickc, fix/hyperagent-tool-loop-thread-sticky) own growth: open-sse/executors/hyperagent.ts 936->1025 (wc -l; check-file-size.mjs counts via split(\"\\n\").length so the gate sees 937->1026, +89, crosses the 1000 cap). Fixes a real bug where a reverse-conversion proxy (text-Intent/JSON to Claude Code native tool_calls) rewrites assistant messages between agentic tool-loop turns, breaking HyperAgent\u2019s conversation-prefix fingerprint and cold-starting the thread mid tool-loop. Adds Anthropic tool_use/tool_result flattening to extractMessageText() plus a new rootUserFingerprint()/root-key lookup tier in resolveHyperAgentThreadBinding()/storeHyperAgentThreadAfterTurn() so the thread stays sticky across the tool loop. Cohesive additions inside the existing single-file executor; not extractable without splitting the executor mid-request-flow. Covered by tests/unit/executor-hyperagent.test.ts (19/19, +5 new cases for tool_result/tool_use flattening + root-key stickiness). Pre-merge review flagged a cross-conversation root-key collision risk (tracked in the PR\u2019s own mandatory pre-merge checklist, not yet addressed) \u2014 unrelated to this file-size ratchet, tracked separately by /fix-prs.",
|
||||
"_rebaseline_2026_07_25_8494_capability_filter_fail_closed": "PR #8494 (fix/capability-filters-fail-closed, #8488) own growth: open-sse/services/combo.ts 3640->3693 (+53) adds a fail-closed guard after filterTargetsByRequestCompatibility() \u2014 when every eligible target is excluded by request-capability filtering (vision/tools/etc) instead of quota/health, the combo now returns an explicit `capability_mismatch` 400 (describeCapabilityFilterExhaustion, imported from combo/comboStructure.ts) rather than silently falling through to a generic no-targets error, plus a `compatFilterFailOpen` escape hatch (combo config OR settings) mirrored at both the main/auto and round-robin call sites for symmetry. combo/comboStructure.ts (previously under cap, un-frozen) grows 794->918 (+124) \u2014 new home for describeCapabilityFilterExhaustion + providerSupportsEmulatedToolCalling (#5240 emulated tool-calling exemption so fail-closed does not regress prompt-emulation-only combos like all-chatgpt-web). Irreducible orchestration wiring at the existing filter chokepoint (same precedent as #7301's universal-cooldown-retry generalization). Companion test tests/unit/combo-routing-engine.test.ts 3409->3449 (+40, fail-closed/fail-open coverage across both call sites) also rebaselined. Covered by tests/unit/8488-capability-filter-fail-closed.test.ts (new) + 95/95 passing across both files. Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_07_25_8499_ts7_result_union_predicates": "PR #8499 (backryun, chore/ts7-types-executor-scattered) own growth: muse-spark-web.ts 1396->1405 (+9, irreducible). Under this workspace's `strictNullChecks: false`, the boolean-literal discriminant on `GraphqlResult` (`{ ok: true } | { ok: false; error: string }`) narrows the positive `.ok===true` branch but leaves `!result.ok` at the full union under TS7, making `.error` unreachable to the checker at the two call sites (warmup, mode-switch). Fixed by adding a single `isGraphqlFailure()` type-predicate helper (doc comment + 3-line body) reused at both call sites instead of duplicating the predicate inline \u2014 not extractable to a shared module without splitting a single-file executor's local narrowing helper out of its own file. Covered by the existing muse-spark-web executor test suite (no behavior change, pure narrowing fix).",
|
||||
"_rebaseline_2026_07_22_8131_windowshide_cloudflared_spawn": "PR #8167 (Dingding-leo, fix/windows-hide-child-process, #8131) own growth: src/lib/cloudflaredTunnel.ts 934->935 (+1, irreducible call-site wiring \u2014 the single `windowsHide: true` option added to the existing cloudflared spawn() options object so no transient conhost.exe/cmd console window flashes open on Windows). Covered by the pre-merge-fix regression test tests/unit/windows-hide-child-process-spawns-8131.test.ts (added for the two additional spawn() sites the PR missed: ServiceSupervisor.ts, versionManager/processManager.ts) plus the windowsHide assertion added to tests/unit/services/installers/runNpm-shell-5379.test.ts (installers/utils.ts buildNpmExecOptions).",
|
||||
"_rebaseline_2026_07_22_8006_adobe_firefly_media_provider": "PR #8006 (artickc, feat/adobe-firefly-media) own growth: adds Adobe Firefly as a media-only (image + video) provider \u2014 unofficial IMS/cookie-session bridge for firefly.adobe.com covering IMS cookie->access_token exchange, discovery-catalog fallback, credits/balance usage, and submit+poll dispatch for both image (nano-banana/gpt-image families) and video (Sora 2/Veo 3.1/Kling 3.0) generation, with 408-under-load retry handling. New leaf open-sse/services/adobeFireflyClient.ts frozen at 1958 (>>cap 800) \u2014 a single self-contained upstream client (mirrors the qoderCli.ts precedent for a new provider client that is legitimately large on day one: IMS auth, cookie/JWT normalization, payload builders for 2 media types x multiple model families, SSE-less submit/poll state machine, error sanitization); not extractable without scattering a single upstream integration across artificial module boundaries mid-PR. open-sse/config/imageRegistry.ts (existing, previously under cap) grows 800->821 (+21, the new adobe-firefly IMAGE_PROVIDERS entry + models list, additive registry data at the existing registry chokepoint). src/lib/usage/providerLimits.ts 1000->1003 (+3, adobe-firefly/firefly added to the existing apikey-usage-fetcher allowlist, irreducible call-site wiring mirroring the sibling #7994 PromptQL/HyperAgent entries in the same PR group). Covered by tests/unit/adobe-firefly.test.ts (35/35). Structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_22_7994_hyperagent_web_provider": "PR #7994 (artickc, feat/hyperagent-web) own growth: adds HyperAgent (hyperagent.com) as a new unofficial web-cookie chat provider, reverse-engineered from live SPA captures (thread/session SSE flow, credits/usage endpoint). New leaf open-sse/executors/hyperagent.ts frozen at 937 (>cap 800) \u2014 single self-contained executor covering cookie auth, SSE parsing (text/session_start/session_end/done events), and a sticky thread/session cache for multi-turn continuity; not extractable without splitting the executor mid-request-flow (mirrors the sseParser.ts/muse-spark-web.ts precedent for new provider executors that exceed cap on day one). src/lib/usage/providerLimits.ts 1000->1003 (+3, irreducible call-site wiring adding hyperagent/ha to the existing USAGE_FETCHER_PROVIDERS-style allowlist at the chokepoint other web-cookie providers already extend). Covered by tests/unit/executor-hyperagent.test.ts (16/16). Structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_08_08_9173_own_comment_growth": "PR #9173's own follow-up commit (f0a694051): tests/unit/combo-routing-engine.test.ts 3457->3464 (+7) is this PR's own growth — explanatory comment blocks added alongside the ALL_ACCOUNTS_INACTIVE->ALL_TARGETS_SKIPPED stale-assertion fix (matching the identical fix applied to #9619/#9006/#8909 the same day; upstream's own test was never updated when the recordedAttempts===0 pre-dispatch-skip branch shipped). Caught by CI's PR-mode check:file-size (--base-ref) after the fix commit; missed locally because check-file-size.mjs was not re-run after that specific edit. Also fixed this round: src/i18n/messages/vi.json was missing 4 keys (cursorSessionUnchanged, cursorAgentNudgeTitle/Body/Dismiss) that this PR's own pre-merge branch had translated — the original merge's `git checkout --theirs` resolution for the 7 conflicted locale files discarded them since upstream's vi.json (which has no cursor-token-renewal feature) never had them. Restored from this PR's pre-merge tip (a38003e30).",
|
||||
"_rebaseline_2026_08_08_9173_vi_json_restore": "PR #9173's own follow-up commit: src/i18n/messages/vi.json was missing 4 keys (cursorSessionUnchanged, cursorAgentNudgeTitle/Body/Dismiss) that this PR's own pre-merge branch had translated — the original merge's `git checkout --theirs` resolution for the 7 conflicted locale files discarded them since upstream's vi.json (which has no cursor-token-renewal feature) never had them. Restored from this PR's pre-merge tip (a38003e30).",
|
||||
"_rebaseline_2026_08_07_9173_reconcile_onto_tip": "PR #9173 (cursor-token-renewal) full reconcile-onto-tip merge with release/v3.8.50 (2026-08-07). base.ts 1619->1681 and chatCore.ts 5028->5031 grew further past the 2026-08-02 rebaseline below via already-merged, no-PR-branch-left commits unrelated to this PR's own Cursor renewal changes (measured directly on the merged tree, split(\"\\n\").length). Same merge also surfaced 11 file + 1 test-file violations shared with PR #9619's identical-base reconciliation the same day (open-sse/mcp-server/schemas/tools.ts 1505->1553, open-sse/mcp-server/server.ts 1411->1444, open-sse/services/accountFallback.ts 1972->1978, src/app/(dashboard)/dashboard/combos/page.tsx 4647->4703, EditConnectionModal.tsx 1316->1324, src/app/api/providers/[id]/models/route.ts 2250->2304, src/app/api/v1/models/catalog.ts 1549->1556, src/lib/db/core.ts 1637->1639, src/sse/handlers/chat.ts 1877->1878, tests/unit/translator-openai-to-gemini.test.ts 1619->1622) plus two more specific to this PR's own additive work compounding with inherited drift: src/lib/tokenHealthCheck.ts 1021->1101 (this PR's own +48 cursor-token-renewal refresh-health logic, per _rebaseline_2026_08_02_9242_token_health_transient's file, plus +32 independent upstream growth) and useProviderConnections.ts 986->1002 (this PR's own +12, plus +41 independent upstream growth — newly crosses the 1000 cap). Same root cause as every other entry in this chain: fast-gates PR->release does not run check:file-size. No offending branch left to fix.",
|
||||
"_rebaseline_2026_08_02_agentrouter_ccbeta_regression_fix": "PR #9173 (cursor-token-renewal) own growth: open-sse/executors/base.ts 1578->1619 (+41). Fixes a real regression from the same two already-merged agentrouter commits documented in _rebaseline_2026_08_02_agentrouter_protocol_dispatch above — usesClaudeCodeProtocol() widened the native-Claude system-transform block (billing header, selectBetaFlags-derived anthropic-beta) to also run for CC-compatible relay connections. selectBetaFlags() has no visibility into a relay's own providerSpecificData.requestDefaults: for a relay with explicit requestDefaults configured (context1m/redactThinking/summarizeThinking), its Object.assign() silently discarded the relay's own correctly-computed headers (wiping an earlier CONTEXT_1M_BETA_HEADER append, force-including redact-thinking-2026-02-12 regardless of opt-in). For a 'vanilla' relay with no requestDefaults at all, the native treatment is pre-existing, intentional behavior (tests/unit/cc-compatible-provider.test.ts, v3.6.6) — the earlier version of this fix broke that case by excluding CC-relays unconditionally. The final gate is `this.provider === \"claude\" || usesCcWireImage(this.provider) || !hasCcRequestDefaults` (native treatment applies unless the relay has explicit requestDefaults), plus an unconditional post-pass that strips the redact-thinking beta unless the relay's own requestDefaults opted in. Covered by tests/unit/executor-default-base.test.ts ('uses CC-compatible connection defaults to append 1M beta'), tests/unit/cc-compatible-provider.test.ts (both SSE-forcing tests), and tests/unit/provider-request-failure-pipeline.test.ts ('keeps request beta headers and summarized thinking body') — all pre-existing, all independently re-verified passing together.",
|
||||
"_rebaseline_2026_08_02_agentrouter_protocol_dispatch": "Reconcile-onto-tip drift surfaced by PR #9173 (cursor-token-renewal): two already-merged, no-PR-branch-left commits on release/v3.8.50 (564c204ef fix(agentrouter): support Claude and Codex protocols; ec150a006 fix(agentrouter): honor alternate protocol in chat pipeline) grew open-sse/executors/base.ts 1562->1578 (Claude/Codex protocol dispatch wiring in the agentrouter executor branch) and open-sse/handlers/chatCore.ts 5020->5028 + tests/unit/chatcore-translation-paths.test.ts 2769->2776 (alternate-protocol chat-pipeline routing + companion test coverage) past their frozen caps, unrelated to this PR's own Cursor renewal changes. Same pattern as the prior release-green rebaselines (fast-gates PR->release do not run check:file-size): no offending branch left to fix in-place. Real sizes per check-file-size.mjs's own split(\"\\n\").length metric.",
|
||||
"_rebaseline_2026_08_09_9342_network_error_guard": "PR #9342 own growth during the 2026-08-09 rebase: open-sse/services/accountFallback.ts 1978->2008 (+30 = the isQueueTimeout short-circuit plus a per-provider network-error dedup window in recordProviderFailure, keeping one VPN blip from the same provider's combo targets counting once per target). Covered by tests/unit/breaker-network-error-guard.test.ts. (chat.ts stays base-red: upstream tip is already 1918 > frozen 1904, this PR only adds +12 on top; not re-bumped per the no-inherit-ratchet rule.)",
|
||||
"_rebaseline_2026_08_02_9259_rolling_rpm": "PR #9259 (issue #8733) own growth: open-sse/services/rateLimitManager.ts baseline 1060->1167 (+107; final source 1153). The existing withRateLimit chokepoint now composes process-local rolling RPM leases with Bottleneck admission, releases pre-dispatch leases on queue timeout/abort/connection disable, preserves caller abort reasons, and wires 429/header state into the extracted rollingRpmGate.ts. The remaining growth is irreducible lifecycle wiring at the dispatch boundary plus the real watchdog test hooks needed to verify queued-wedge recovery; moving it further would obscure lease ownership and Bottleneck cleanup. Covered by the focused rate-limit manager/sliding-window suite (33/33); distributed multi-instance coordination remains explicitly out of scope.",
|
||||
"_rebaseline_2026_07_24_8470_hyperagent_sticky_thread": "PR #8470 (artickc, fix/hyperagent-tool-loop-thread-sticky) own growth: open-sse/executors/hyperagent.ts 936->1025 (wc -l; check-file-size.mjs counts via split(\"\\n\").length so the gate sees 937->1026, +89, crosses the 1000 cap). Fixes a real bug where a reverse-conversion proxy (text-Intent/JSON to Claude Code native tool_calls) rewrites assistant messages between agentic tool-loop turns, breaking HyperAgent's conversation-prefix fingerprint and cold-starting the thread mid tool-loop. Adds Anthropic tool_use/tool_result flattening to extractMessageText() plus a new rootUserFingerprint()/root-key lookup tier in resolveHyperAgentThreadBinding()/storeHyperAgentThreadAfterTurn() so the thread stays sticky across the tool loop. Cohesive additions inside the existing single-file executor; not extractable without splitting the executor mid-request-flow. Covered by tests/unit/executor-hyperagent.test.ts (19/19, +5 new cases for tool_result/tool_use flattening + root-key stickiness). Pre-merge review flagged a cross-conversation root-key collision risk (tracked in the PR's own mandatory pre-merge checklist, not yet addressed) — unrelated to this file-size ratchet, tracked separately by /fix-prs.",
|
||||
"_rebaseline_2026_07_25_8494_capability_filter_fail_closed": "PR #8494 (fix/capability-filters-fail-closed, #8488) own growth: open-sse/services/combo.ts 3640->3693 (+53) adds a fail-closed guard after filterTargetsByRequestCompatibility() — when every eligible target is excluded by request-capability filtering (vision/tools/etc) instead of quota/health, the combo now returns an explicit `capability_mismatch` 400 (describeCapabilityFilterExhaustion, imported from combo/comboStructure.ts) rather than silently falling through to a generic no-targets error, plus a `compatFilterFailOpen` escape hatch (combo config OR settings) mirrored at both the main/auto and round-robin call sites for symmetry. combo/comboStructure.ts (previously under cap, un-frozen) grows 794->918 (+124) — new home for describeCapabilityFilterExhaustion + providerSupportsEmulatedToolCalling (#5240 emulated tool-calling exemption so fail-closed does not regress prompt-emulation-only combos like all-chatgpt-web). Irreducible orchestration wiring at the existing filter chokepoint (same precedent as #7301's universal-cooldown-retry generalization). Companion test tests/unit/combo-routing-engine.test.ts 3409->3449 (+40, fail-closed/fail-open coverage across both call sites) also rebaselined. Covered by tests/unit/8488-capability-filter-fail-closed.test.ts (new) + 95/95 passing across both files. Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_07_25_8499_ts7_result_union_predicates": "PR #8499 (backryun, chore/ts7-types-executor-scattered) own growth: muse-spark-web.ts 1396->1405 (+9, irreducible). Under this workspace's `strictNullChecks: false`, the boolean-literal discriminant on `GraphqlResult` (`{ ok: true } | { ok: false; error: string }`) narrows the positive `.ok===true` branch but leaves `!result.ok` at the full union under TS7, making `.error` unreachable to the checker at the two call sites (warmup, mode-switch). Fixed by adding a single `isGraphqlFailure()` type-predicate helper (doc comment + 3-line body) reused at both call sites instead of duplicating the predicate inline — not extractable to a shared module without splitting a single-file executor's local narrowing helper out of its own file. Covered by the existing muse-spark-web executor test suite (no behavior change, pure narrowing fix).",
|
||||
"_rebaseline_2026_07_22_8131_windowshide_cloudflared_spawn": "PR #8167 (Dingding-leo, fix/windows-hide-child-process, #8131) own growth: src/lib/cloudflaredTunnel.ts 934->935 (+1, irreducible call-site wiring — the single `windowsHide: true` option added to the existing cloudflared spawn() options object so no transient conhost.exe/cmd console window flashes open on Windows). Covered by the pre-merge-fix regression test tests/unit/windows-hide-child-process-spawns-8131.test.ts (added for the two additional spawn() sites the PR missed: ServiceSupervisor.ts, versionManager/processManager.ts) plus the windowsHide assertion added to tests/unit/services/installers/runNpm-shell-5379.test.ts (installers/utils.ts buildNpmExecOptions).",
|
||||
"_rebaseline_2026_07_22_8006_adobe_firefly_media_provider": "PR #8006 (artickc, feat/adobe-firefly-media) own growth: adds Adobe Firefly as a media-only (image + video) provider — unofficial IMS/cookie-session bridge for firefly.adobe.com covering IMS cookie->access_token exchange, discovery-catalog fallback, credits/balance usage, and submit+poll dispatch for both image (nano-banana/gpt-image families) and video (Sora 2/Veo 3.1/Kling 3.0) generation, with 408-under-load retry handling. New leaf open-sse/services/adobeFireflyClient.ts frozen at 1958 (>>cap 800) — a single self-contained upstream client (mirrors the qoderCli.ts precedent for a new provider client that is legitimately large on day one: IMS auth, cookie/JWT normalization, payload builders for 2 media types x multiple model families, SSE-less submit/poll state machine, error sanitization); not extractable without scattering a single upstream integration across artificial module boundaries mid-PR. open-sse/config/imageRegistry.ts (existing, previously under cap) grows 800->821 (+21, the new adobe-firefly IMAGE_PROVIDERS entry + models list, additive registry data at the existing registry chokepoint). src/lib/usage/providerLimits.ts 1000->1003 (+3, adobe-firefly/firefly added to the existing apikey-usage-fetcher allowlist, irreducible call-site wiring mirroring the sibling #7994 PromptQL/HyperAgent entries in the same PR group). Covered by tests/unit/adobe-firefly.test.ts (35/35). Structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_22_7994_hyperagent_web_provider": "PR #7994 (artickc, feat/hyperagent-web) own growth: adds HyperAgent (hyperagent.com) as a new unofficial web-cookie chat provider, reverse-engineered from live SPA captures (thread/session SSE flow, credits/usage endpoint). New leaf open-sse/executors/hyperagent.ts frozen at 937 (>cap 800) — single self-contained executor covering cookie auth, SSE parsing (text/session_start/session_end/done events), and a sticky thread/session cache for multi-turn continuity; not extractable without splitting the executor mid-request-flow (mirrors the sseParser.ts/muse-spark-web.ts precedent for new provider executors that exceed cap on day one). src/lib/usage/providerLimits.ts 1000->1003 (+3, irreducible call-site wiring adding hyperagent/ha to the existing USAGE_FETCHER_PROVIDERS-style allowlist at the chokepoint other web-cookie providers already extend). Covered by tests/unit/executor-hyperagent.test.ts (16/16). Structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_08_08_9173_own_comment_growth": "PR #9173's own follow-up commit (f0a694051): tests/unit/combo-routing-engine.test.ts 3457->3464 (+7) is this PR's own growth — explanatory comment blocks added alongside the ALL_ACCOUNTS_INACTIVE->ALL_TARGETS_SKIPPED stale-assertion fix (matching the identical fix applied to #9619/#9006/#8909 the same day; upstream's own test was never updated when the recordedAttempts===0 pre-dispatch-skip branch shipped). Caught by CI's PR-mode check:file-size (--base-ref) after the fix commit; missed locally because check-file-size.mjs was not re-run after that specific edit. Also fixed this round: src/i18n/messages/vi.json was missing 4 keys (cursorSessionUnchanged, cursorAgentNudgeTitle/Body/Dismiss) that this PR's own pre-merge branch had translated — the original merge's `git checkout --theirs` resolution for the 7 conflicted locale files discarded them since upstream's vi.json (which has no cursor-token-renewal feature) never had them. Restored from this PR's pre-merge tip (a38003e30).",
|
||||
"_rebaseline_2026_08_08_9173_vi_json_restore": "PR #9173's own follow-up commit: src/i18n/messages/vi.json was missing 4 keys (cursorSessionUnchanged, cursorAgentNudgeTitle/Body/Dismiss) that this PR's own pre-merge branch had translated — the original merge's `git checkout --theirs` resolution for the 7 conflicted locale files discarded them since upstream's vi.json (which has no cursor-token-renewal feature) never had them. Restored from this PR's pre-merge tip (a38003e30).",
|
||||
"_rebaseline_2026_08_07_9173_reconcile_onto_tip": "PR #9173 (cursor-token-renewal) full reconcile-onto-tip merge with release/v3.8.50 (2026-08-07). base.ts 1619->1681 and chatCore.ts 5028->5031 grew further past the 2026-08-02 rebaseline below via already-merged, no-PR-branch-left commits unrelated to this PR's own Cursor renewal changes (measured directly on the merged tree, split(\"\\n\").length). Same merge also surfaced 11 file + 1 test-file violations shared with PR #9619's identical-base reconciliation the same day (open-sse/mcp-server/schemas/tools.ts 1505->1553, open-sse/mcp-server/server.ts 1411->1444, open-sse/services/accountFallback.ts 1972->1978, src/app/(dashboard)/dashboard/combos/page.tsx 4647->4703, EditConnectionModal.tsx 1316->1324, src/app/api/providers/[id]/models/route.ts 2250->2304, src/app/api/v1/models/catalog.ts 1549->1556, src/lib/db/core.ts 1637->1639, src/sse/handlers/chat.ts 1877->1878, tests/unit/translator-openai-to-gemini.test.ts 1619->1622) plus two more specific to this PR's own additive work compounding with inherited drift: src/lib/tokenHealthCheck.ts 1021->1101 (this PR's own +48 cursor-token-renewal refresh-health logic, per _rebaseline_2026_08_02_9242_token_health_transient's file, plus +32 independent upstream growth) and useProviderConnections.ts 986->1002 (this PR's own +12, plus +41 independent upstream growth — newly crosses the 1000 cap). Same root cause as every other entry in this chain: fast-gates PR->release does not run check:file-size. No offending branch left to fix.",
|
||||
"_rebaseline_2026_08_02_agentrouter_ccbeta_regression_fix": "PR #9173 (cursor-token-renewal) own growth: open-sse/executors/base.ts 1578->1619 (+41). Fixes a real regression from the same two already-merged agentrouter commits documented in _rebaseline_2026_08_02_agentrouter_protocol_dispatch above — usesClaudeCodeProtocol() widened the native-Claude system-transform block (billing header, selectBetaFlags-derived anthropic-beta) to also run for CC-compatible relay connections. selectBetaFlags() has no visibility into a relay's own providerSpecificData.requestDefaults: for a relay with explicit requestDefaults configured (context1m/redactThinking/summarizeThinking), its Object.assign() silently discarded the relay's own correctly-computed headers (wiping an earlier CONTEXT_1M_BETA_HEADER append, force-including redact-thinking-2026-02-12 regardless of opt-in). For a 'vanilla' relay with no requestDefaults at all, the native treatment is pre-existing, intentional behavior (tests/unit/cc-compatible-provider.test.ts, v3.6.6) — the earlier version of this fix broke that case by excluding CC-relays unconditionally. The final gate is `this.provider === \"claude\" || usesCcWireImage(this.provider) || !hasCcRequestDefaults` (native treatment applies unless the relay has explicit requestDefaults), plus an unconditional post-pass that strips the redact-thinking beta unless the relay's own requestDefaults opted in. Covered by tests/unit/executor-default-base.test.ts ('uses CC-compatible connection defaults to append 1M beta'), tests/unit/cc-compatible-provider.test.ts (both SSE-forcing tests), and tests/unit/provider-request-failure-pipeline.test.ts ('keeps request beta headers and summarized thinking body') — all pre-existing, all independently re-verified passing together.",
|
||||
"_rebaseline_2026_08_02_agentrouter_protocol_dispatch": "Reconcile-onto-tip drift surfaced by PR #9173 (cursor-token-renewal): two already-merged, no-PR-branch-left commits on release/v3.8.50 (564c204ef fix(agentrouter): support Claude and Codex protocols; ec150a006 fix(agentrouter): honor alternate protocol in chat pipeline) grew open-sse/executors/base.ts 1562->1578 (Claude/Codex protocol dispatch wiring in the agentrouter executor branch) and open-sse/handlers/chatCore.ts 5020->5028 + tests/unit/chatcore-translation-paths.test.ts 2769->2776 (alternate-protocol chat-pipeline routing + companion test coverage) past their frozen caps, unrelated to this PR's own Cursor renewal changes. Same pattern as the prior release-green rebaselines (fast-gates PR->release do not run check:file-size): no offending branch left to fix in-place. Real sizes per check-file-size.mjs's own split(\"\\n\").length metric.",
|
||||
"_rebaseline_2026_07_21_7301_universal_cooldown_retry": "PR #7301 (ViFigueiredo, feat/universal-cooldown-retry) own growth, surfaced during rebase-onto-tip reconciliation (fast-gates PR->release do not run check:file-size): open-sse/services/combo.ts 3388->3479 (+91) generalizes the existing quota-share-only cooldown-aware retry (dispatchWithCooldownRetry) to ALL combo strategies (priority/weighted/round-robin/etc), gates it on the model lockout's REAL reason (not a hardcoded \"rate_limit\") via the existing getModelLockoutInfo/resolveComboCooldownWaitDecision chokepoint, and adds a global comboTimeoutMs guard + aggregated per-target error diagnostics on exhaustion. Companion leaves open-sse/services/combo/comboCooldownRetry.ts (+29), combo/autoStrategy.ts (+9, auto-strategy combo-ref guard so a combo cannot recursively reference itself as a candidate), combo/comboSetup.ts (+3), comboConfig.ts (+6) all stay under cap. Irreducible orchestration wiring at the existing dispatch chokepoint (mirrors the quota-share-only precedent this PR generalizes); not extractable without hiding the retry loop. Covered by tests/unit/combo-auto-candidate-expansion.test.ts (+61, combo-ref guard), tests/unit/combo-routing-engine.test.ts (+68, universal retry across strategies + comboTimeoutMs, no-explicit-any clean), tests/unit/serial/combo-quota-share-cooldown-wait-timing.test.ts (+136, quota_exhausted vs rate_limit reason gating, disabled-flag passthrough). Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_07_21_7935_vi_locale_residual_ui": "PR #7935 (nguyenha935, fix/vietnamese-locale-residual) own growth: 9 dashboard components gained `useTranslations()` wiring (import + hook call + a handful of `t(\"key\")` call-sites replacing hardcoded English strings) as part of restoring i18n coverage — ComboHealthTab.tsx 1028->1031 (+3), cloud-agents/page.tsx 922->931 (+9), PoolWizard.tsx 1007->1022 (+15), EndpointPageClient.tsx 2612->2615 (+3), health/page.tsx 1091->1095 (+4), ProviderOnboardingWizard.tsx 912->948 (+36, largest — several previously-hardcoded wizard step labels/descriptions), PricingTab.tsx 1012->1017 (+5), ProxyRegistryManager.tsx 1461->1464 (+3), BudgetTab.tsx 1016->1028 (+12). All additions are literal `t(...)`/`tc(...)` call-site swaps for existing UI text, verified byte-identical in intent against the corresponding new `src/i18n/messages/{en,vi}.json` keys (see tests/unit/dashboard-localization-contract.test.ts, tests/unit/i18n-vi-completeness.test.ts, tests/unit/gamification-display-contract.test.ts, tests/unit/cli-catalog-display-contract.test.ts added by the same PR). Fast-gates PR->release do not run check:file-size, so this surfaced only during rebase-onto-tip reconciliation.",
|
||||
"_rebaseline_2026_07_21_7908_chathelpers_abort_guard": "PR #7908 (insoln, don't cool down accounts or trip the breaker on client-side stream aborts, #7907) own growth: src/sse/handlers/chatHelpers.ts 876->877 (+1 = the single `isLocalStreamLifecycleError(failure?.message ?? failure)` clause added to executeChatWithBreaker's onStreamFailure connection-disable check, verified working by the existing #4602 test + the PR's own circuit-breaker-client-abort.test.ts, no regressions). Irreducible call-site wiring at the existing failure-classification chokepoint. Fast-gates PR->release do not run check:file-size, so this surfaced only during the /green-prs pre-merge pass.",
|
||||
@@ -437,7 +431,19 @@
|
||||
"open-sse/executors/kiro.ts": 1390,
|
||||
"open-sse/translator/request/openai-to-kiro.ts": 1374,
|
||||
"open-sse/utils/sseHeartbeat.ts": 194,
|
||||
"open-sse/utils/proxyFetch.ts": 1207
|
||||
"open-sse/utils/proxyFetch.ts": 1220,
|
||||
"_rebaseline_2026_08_11_v3850_merge_storm_provider_registry: DRIFT do merge-storm 2026-08-11 (99 PRs mergeados no release/v3.8.50). AddApiKeyModal.tsx (PR #8949 ChatGPT Web provider) e useProviderConnections.ts/ModelSelectModal.tsx (PRs #9011 combo test-all, #9499 image combos) = UI nova legítima acima do cap; gateways.ts = god-file de catálogo de providers que cresceu com os PRs #9009/#9421/#9468/#9594 (qualquer split arriscaria corromper o merge de novo — o próprio PR #9421 foi o que quebrou o arquivo; sem split até o release, congelado no tamanho atual). Owner autorizou rebaseline com anotação (2026-08-11).": {
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 1062,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts": 1051,
|
||||
"src/shared/components/ModelSelectModal.tsx": 1135,
|
||||
"src/shared/constants/providers/apikey/gateways.ts": 1215
|
||||
},
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 1062,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts": 1051,
|
||||
"src/shared/components/ModelSelectModal.tsx": 1135,
|
||||
"src/shared/constants/providers/apikey/gateways.ts": 1215,
|
||||
"open-sse/vendor/codex-chatgpt-web/bridge.ts": 1387,
|
||||
"_rebaseline_2026_08_11_v3850_merge_storm_provider_registry": "DRIFT do merge-storm 2026-08-11 (99 PRs mergeados no release/v3.8.50). AddApiKeyModal.tsx (PR #8949 ChatGPT Web provider) e useProviderConnections.ts/ModelSelectModal.tsx (PRs #9011 combo test-all, #9499 image combos) = UI nova legitima acima do cap; gateways.ts = god-file de catalogo de providers que cresceu com PRs #9009/#9421/#9468/#9594 (qualquer split arriscaria corromper o merge de novo — o proprio PR #9421 quebrou o arquivo); bridge.ts (PR #8949) = ponte Chromium vendored; proxyFetch.ts 1207->1220 = drift herdado de merges. Owner autorizou rebaseline com anotacao (2026-08-11)."
|
||||
},
|
||||
"_rebaseline_base_2026_08_10_proxyfetch": "Base-red fix (green-prs sweep, issue #9985): open-sse/utils/proxyFetch.ts 1207 > cap 1000 — new proxied-TLS fetch helper introduced by the Fal reference-image work. Owner-authorized quick rebaseline to green; structural slim tracked for v3.9.0.",
|
||||
"_rebaseline_2026_07_27_v3849_train2": "Merge-train 2 (7 PRs) — owner-approved 2026-07-27. Single entry: chatCore.ts 4955->5006 (#8595, Responses multi-turn image compaction before the context hard-reject). Genuine irreducible growth at the existing compaction chokepoint in handleChatCore — the PR adds a last-resort retry against the concrete budget plus the estimateFinalInputTokens helper, both wired at the pre-existing call site rather than a new branch. Covered by tests/unit/8560-responses-image-compaction.test.ts (4 tests).",
|
||||
|
||||
@@ -102,14 +102,15 @@
|
||||
"_rebaseline_2026_07_28_v3849_release": "75.5 -> 99 (+23.5). Aperto EXIGIDO pelo modo --require-tighten do ratchet: a métrica melhorou de verdade no ciclo v3.8.49. A causa é o workflow assíncrono de tradução, que finalmente alcançou o denominador em EN — as rebaselines anteriores (v3.8.39/.44/.47) foram todas afrouxamentos registrando o atraso das traduções, e agora ele foi pago. O coletor SUBTRAI os placeholders (present - placeholder em scripts/quality/collect-metrics.mjs), então os 317 marcadores __MISSING__ que esta release introduziu para o drift de valor já estão descontados dos 99 — o número é honesto, não inflado por placeholder. Medido pelo collect-metrics do CI no run 30404226939."
|
||||
},
|
||||
"deadExports": {
|
||||
"value": 230,
|
||||
"value": 248,
|
||||
"direction": "down",
|
||||
"_rebaseline_2026_08_09_v3850_post_sweep": "227 -> 230. Measured by npm run check:dead-code on the unmodified release/v3.8.50 tip 382449d593 during the mandatory --full-ci pre-flight. The +3 is inherited cycle drift from the authorized merge sweep; this repair adds no production exports. Rebaseline records the actual tip so ci.yml quality-gate can run, while structural cleanup remains separate debt.",
|
||||
"_rebaseline_2026_07_01_v3843_release": "225->227 (+2). v3.8.43 cycle drift, surfaced in the Quality Ratchet job after eslintWarnings was rebaselined (check:dead-code runs there). 227 = measured by check:dead-code (knip) on the release tip 4635076eb. The 5 CI fixes add 0 dead exports: safeHttpHref in linkify.ts is module-local AND used (called by linkifyText); no new exports; test files are not scanned. Tighten via --update next cycle.",
|
||||
"dedicatedGate": true,
|
||||
"_rebaseline_2026_06_30_v3842_deadcode_wave": "310 -> 225. Measured by `node scripts/check/check-dead-code.mjs` on the v3.8.42 tip after the JxnLexn dead-code (#5463/#5464/#5466) + duplication (#5471..#5500) wave landed: DEAD_EXPORTS=133 + DEAD_FILES=92 = 225. The stale 310 was the v3.8.38 release snapshot never ratcheted on PR->release fast-gates (check:dead-code runs only on ci.yml PR->main, not quality.yml). Tightening to the true measured value; release-time captain rebaselines up if parallel cycle merges add dead exports.",
|
||||
"_rebaseline_2026_06_27_v3838_release": "345->346 (+1). v3.8.38 cycle drift surfaced by the release-green pre-flight (Quality Ratchet does NOT run on PR->release fast-gates). Net +1 inherited from this cycle's feature/fix merges (new executors/providers, compression fidelity-gate module) minus #5138's removal of dead legacy store modules. Release-finalize working tree touches ONLY CHANGELOG.md + i18n mirrors + README + baselines — 0 production-code change. Structural cleanup tracked as debt.",
|
||||
"_rebaseline_2026_06_26_v3837_release": "343->345. v3.8.37 cycle drift surfaced by the release-green pre-flight (the Quality Ratchet does NOT run on PR->release fast-gates, so warnings/complexity accrued unmeasured across this cycle's 76 commits — provider adds DGrid/Pioneer/xAI, headroom proxy lifecycle #4649, ~50 SSE/translator fixes, Engine Combos #5062). Trust-but-verify: this release-finalize working tree touches ONLY CHANGELOG.md, docs/i18n/*/CHANGELOG.md mirrors, and these baselines — 0 production-code change, so all drift is inherited cycle drift (`any` warn-allowed in open-sse/ + tests/). Tighten via --require-tighten next cycle."
|
||||
"_rebaseline_2026_06_26_v3837_release": "343->345. v3.8.37 cycle drift surfaced by the release-green pre-flight (the Quality Ratchet does NOT run on PR->release fast-gates, so warnings/complexity accrued unmeasured across this cycle's 76 commits — provider adds DGrid/Pioneer/xAI, headroom proxy lifecycle #4649, ~50 SSE/translator fixes, Engine Combos #5062). Trust-but-verify: this release-finalize working tree touches ONLY CHANGELOG.md, docs/i18n/*/CHANGELOG.md mirrors, and these baselines — 0 production-code change, so all drift is inherited cycle drift (`any` warn-allowed in open-sse/ + tests/). Tighten via --require-tighten next cycle.",
|
||||
"_rebaseline_2026_08_11_v3850_merge_storm": "230 -> 248. Own drift from the 2026-08-11 merge storm (99 PRs into release/v3.8.50 via authorized sweep): new providers/executors/handlers added dead exports that knip cannot see as used. Measured on the base-fix tip (7ca73697b0 + this repair PR). Owner authorized rebaseline (2026-08-11) — structural cleanup remains separate debt."
|
||||
},
|
||||
"cognitiveComplexity": {
|
||||
"value": 1223,
|
||||
@@ -149,11 +150,12 @@
|
||||
"dedicatedGate": true
|
||||
},
|
||||
"codeqlAlerts": {
|
||||
"value": 2,
|
||||
"value": 9,
|
||||
"direction": "down",
|
||||
"dedicatedGate": true,
|
||||
"_rebaseline_2026_08_06_base_grew": "Base branch file-size drift: translator-openai-to-gemini.test.ts grew 1619->1622 (test assertions for Gemini translator compatibility). CodeQL alert (js/insufficient-password-hash in raycast.ts) is pre-existing base-red; incremented baseline to match.",
|
||||
"_rebaseline_2026_08_10_9940_fingerprint": "CodeQL base-red (green-prs sweep, issue #9985): 2nd js/insufficient-password-hash alert at src/shared/middleware/chatBodyAdmission.ts:265,269 introduced by #9940 (per-connection virtual admission lanes). Both are API-key/bearer FINGERPRINTS (createHash('sha256') truncated to 16-hex admission-lane key), not password VERIFICATION — false-positive class for this rule. Reproduces on release/v3.8.50 tip. Owner-authorized rebaseline 1->2; revisit at v3.9.0."
|
||||
"_rebaseline_2026_08_10_9940_fingerprint": "CodeQL base-red (green-prs sweep, issue #9985): 2nd js/insufficient-password-hash alert at src/shared/middleware/chatBodyAdmission.ts:265,269 introduced by #9940 (per-connection virtual admission lanes). Both are API-key/bearer FINGERPRINTS (createHash('sha256') truncated to 16-hex admission-lane key), not password VERIFICATION — false-positive class for this rule. Reproduces on release/v3.8.50 tip. Owner-authorized rebaseline 1->2; revisit at v3.9.0.",
|
||||
"_rebaseline_2026_08_11_merge_storm_drift": "2 -> 9. Drift from the 2026-08-11 authorized merge storm (99 PRs into release/v3.8.50): js/insufficient-password-hash in open-sse/executors/chatgpt-web-codex/storageState.ts x3 + conol-web.ts (PRs #8949/#8974 — Chromium browser-profile credential storage SKUs that CodeQL flags under the password-hash rule; same fingerprint/credential-cache false-positive class already rebaselined in #9940/2026-08-06), js/stack-trace-exposure (chatgpt-web-codex.ts, #8949 debug logging), js/double-escaping (environment.ts, #8949), js/insecure-randomness (tinycms.ts, #9435). None introduced by this base-fix PR. Owner-authorized rebaseline (2026-08-11); revisit hardening at v3.9.0."
|
||||
},
|
||||
"secretFindings": {
|
||||
"_note": "Zeroed 2026-07-13 (WS6/D3): the 3 frozen generic-api-key FPs are allowlisted with justification in .gitleaks.toml — any NEW finding regresses the ratchet.",
|
||||
|
||||
@@ -6,9 +6,9 @@ lastUpdated: 2026-06-28
|
||||
|
||||
# OmniRoute MCP Server Documentation
|
||||
|
||||
> Model Context Protocol server with 104 tools across routing, cache, compression, memory, skills, proxy, pool, and context source operations.
|
||||
> Model Context Protocol server with 105 tools across routing, cache, compression, memory, skills, proxy, pool, and context source operations.
|
||||
>
|
||||
> Source of truth: `open-sse/mcp-server/server.ts` computes **104 unique tools** with `countUniqueMcpTools()`: 42 canonical definitions (including the six CCR lifecycle tools and the agent-skills trio), plus memory (3), skills (4), GitHub skills (3), pool (6), gamification (8), plugins (8), Notion (6), Obsidian (22), and two RTK-only compression tools.
|
||||
> Source of truth: `open-sse/mcp-server/server.ts` computes **105 unique tools** with `countUniqueMcpTools()`: 42 canonical definitions (including the six CCR lifecycle tools and the agent-skills trio), plus memory (3), skills (4), GitHub skills (3), pool (6), gamification (8), plugins (8), Notion (6), Obsidian (22), and two RTK-only compression tools.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -227,7 +227,7 @@ See [AGENT-SKILLS.md](./AGENT-SKILLS.md) for the full catalog and how external a
|
||||
|
||||
## Related Frameworks (v3.8.0)
|
||||
|
||||
The MCP tool inventory above (104 unique tools, computed by `countUniqueMcpTools()`) is intentionally
|
||||
computed = 105, computed by `countUniqueMcpTools()`) is intentionally
|
||||
scoped to runtime routing/cache/compression/memory/skills/proxy/context-source operations. Two adjacent
|
||||
frameworks ship alongside the MCP server in v3.8.0 and are documented separately:
|
||||
|
||||
@@ -369,7 +369,7 @@ MCP tool, prompt, and resource registries can compress descriptions at registrat
|
||||
|
||||
Description compression shrinks each tool's metadata; **tool-cardinality reduction** goes one step further by reducing _how many_ tools are announced at all. Advertising fewer tools in the `tools/list` manifest cuts the per-request token cost the client's model pays for the tool catalog ("layer 5" compression). The implementation is a pure, stateless filter in `open-sse/mcp-server/toolCardinality.ts` (`reduceToolManifest`), wired into the registration loop in `createMcpServer()` (`open-sse/mcp-server/server.ts`).
|
||||
|
||||
**Opt-in, off by default.** The filter only runs when at least one of two environment variables is set; with neither set, all 104 tools are announced unchanged.
|
||||
**Opt-in, off by default.** The filter only runs when at least one of two environment variables is set; with neither set, all 105 tools are announced unchanged.
|
||||
|
||||
| Variable | Mode |
|
||||
| :--------------- | :-------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -8,6 +8,19 @@
|
||||
|
||||
---
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,9 +6,9 @@ lastUpdated: 2026-06-28
|
||||
|
||||
# Dokumentacja serwera MCP OmniRoute
|
||||
|
||||
> Serwer Model Context Protocol z 104 narzędziami obejmującymi routing, cache, kompresję, pamięć, skills, proxy, pool oraz operacje na źródłach kontekstu.
|
||||
> Serwer Model Context Protocol z 105 narzędziami obejmującymi routing, cache, kompresję, pamięć, skills, proxy, pool oraz operacje na źródłach kontekstu.
|
||||
>
|
||||
> Źródło prawdy: `open-sse/mcp-server/server.ts` wylicza **104 unikalne narzędzia** przez `countUniqueMcpTools()`: 42 kanoniczne definicje (w tym sześć narzędzi cyklu życia CCR oraz trio agent-skills), plus memory (3), skills (4), GitHub skills (3), pool (6), gamification (8), plugins (8), Notion (6), Obsidian (22) oraz dwa narzędzia kompresji wyłącznie RTK.
|
||||
> Źródło prawdy: `open-sse/mcp-server/server.ts` wylicza **105 unikalne narzędzia** przez `countUniqueMcpTools()`: 42 kanoniczne definicje (w tym sześć narzędzi cyklu życia CCR oraz trio agent-skills), plus memory (3), skills (4), GitHub skills (3), pool (6), gamification (8), plugins (8), Notion (6), Obsidian (22) oraz dwa narzędzia kompresji wyłącznie RTK.
|
||||
|
||||
## Instalacja
|
||||
|
||||
@@ -227,7 +227,7 @@ Zobacz [AGENT-SKILLS.md](./AGENT-SKILLS.md) po pełny katalog i sposób konsumow
|
||||
|
||||
## Pokrewne frameworki (v3.8.0)
|
||||
|
||||
Powyższy inwentarz narzędzi MCP (104 unikalne tools, wyliczane przez `countUniqueMcpTools()`) jest celowo
|
||||
Powyższy inwentarz narzędzi MCP (105 unikalne tools, wyliczane przez `countUniqueMcpTools()`) jest celowo
|
||||
ograniczony do operacji runtime: routing/cache/compression/memory/skills/proxy/context-source. Dwa sąsiednie
|
||||
frameworki dostarczane razem z serwerem MCP w v3.8.0 są udokumentowane osobno:
|
||||
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -10,7 +10,7 @@ lastUpdated: 2026-06-28
|
||||
>
|
||||
> 数据来源:`open-sse/mcp-server/schemas/tools.ts`(34 个基础工具)+ `memoryTools.ts`(3)+ `skillTools.ts`(4)+ `agentSkillTools.ts`(3)+ `poolTools.ts`(6)+ `gamificationTools.ts`(8)+ `pluginTools.ts`(8)+ `notionTools.ts`(6)+ `obsidianTools.ts`(22)= **94**(`TOTAL_MCP_TOOL_COUNT`)。工具注册和权限域绑定逻辑见 `open-sse/mcp-server/server.ts`。
|
||||
|
||||

|
||||

|
||||
|
||||
> 来源:[diagrams/mcp-tools-104.mmd](../diagrams/mcp-tools-104.mmd)(通过 `npm run docs:render-diagrams` 重新生成)。
|
||||
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
|
||||
## [3.8.31] — 2026-06-20
|
||||
|
||||
## [3.8.50] — TBD
|
||||
|
||||
_Living section — cycle opened at the v3.8.49 freeze (parallel-cycle model). Bullets are aggregated from `changelog.d/` fragments at each `/generate-release` phase._
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [3.8.49] — 2026-07-28
|
||||
|
||||
_Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62333b0 → tip). Bullets carry the merged PR and its author; direct pushes listed separately. Finalized at the v3.8.49 release._
|
||||
|
||||
@@ -6,9 +6,9 @@ lastUpdated: 2026-06-28
|
||||
|
||||
# OmniRoute MCP Server 文件
|
||||
|
||||
> 模型上下文協定(Model Context Protocol)伺服器,提供 104 個工具,涵蓋路由、快取、壓縮、記憶、技能、代理、池與上下文來源操作。
|
||||
> 模型上下文協定(Model Context Protocol)伺服器,提供 105 個工具,涵蓋路由、快取、壓縮、記憶、技能、代理、池與上下文來源操作。
|
||||
>
|
||||
> 真相來源:`open-sse/mcp-server/server.ts` 透過 `countUniqueMcpTools()` 計算出 **104 個唯一工具**:42 個標準定義(包括六個 CCR 生命週期工具與 agent-skills 三件組),加上記憶體(3 個)、技能(4 個)、GitHub 技能(3 個)、池(6 個)、遊戲化(8 個)、外掛(8 個)、Notion(6 個)、Obsidian(22 個)與兩個僅限 RTK 的壓縮工具。
|
||||
> 真相來源:`open-sse/mcp-server/server.ts` 透過 `countUniqueMcpTools()` 計算出 **105 個唯一工具**:42 個標準定義(包括六個 CCR 生命週期工具與 agent-skills 三件組),加上記憶體(3 個)、技能(4 個)、GitHub 技能(3 個)、池(6 個)、遊戲化(8 個)、外掛(8 個)、Notion(6 個)、Obsidian(22 個)與兩個僅限 RTK 的壓縮工具。
|
||||
|
||||
## 安裝
|
||||
|
||||
@@ -215,7 +215,7 @@ curl -X DELETE http://localhost:20128/api/settings/notion
|
||||
|
||||
## 相關框架(v3.8.0)
|
||||
|
||||
上述 MCP 工具清單(104 個唯一工具,由 `countUniqueMcpTools()` 計算)的範圍故意限定於執行時期路由/快取/壓縮/記憶/技能/代理/上下文來源操作。兩個相鄰框架與 MCP 伺服器一同於 v3.8.0 提供,並分別記錄:
|
||||
上述 MCP 工具清單(105 個唯一工具,由 `countUniqueMcpTools()` 計算)的範圍故意限定於執行時期路由/快取/壓縮/記憶/技能/代理/上下文來源操作。兩個相鄰框架與 MCP 伺服器一同於 v3.8.0 提供,並分別記錄:
|
||||
|
||||
### Cloud Agents
|
||||
|
||||
@@ -319,7 +319,7 @@ MCP 工具、提示與資源註冊表可在註冊/列出時壓縮描述,以
|
||||
|
||||
描述壓縮會縮小每個工具的元資料;**工具基數減少**則更進一步,減少**宣告的工具總數**。在 `tools/list` 清單中廣告較少的工具,可降低客戶端模型為工具目錄所支付的每次請求代幣成本(「第 5 層」壓縮)。實作為一個純粹、無狀態的過濾器,位於 `open-sse/mcp-server/toolCardinality.ts`(`reduceToolManifest`),接入 `createMcpServer()`(`open-sse/mcp-server/server.ts`)的註冊迴圈。
|
||||
|
||||
**選擇性加入,預設關閉。** 過濾器僅在至少設定兩個環境變數之一時才執行;若兩者皆未設定,則所有 104 個工具保持不變地被宣告。
|
||||
**選擇性加入,預設關閉。** 過濾器僅在至少設定兩個環境變數之一時才執行;若兩者皆未設定,則所有 105 個工具保持不變地被宣告。
|
||||
|
||||
| 變數 | 模式 |
|
||||
| :---------------- | :----------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1014,6 +1014,7 @@ changing them requires a code edit, not an env var:
|
||||
| `CURSOR_TOKEN` | _(unset)_ | `scripts/ad-hoc/cursor-tap.cjs` | Direct Cursor bearer token used by developer tooling. |
|
||||
| `OMNIROUTE_LOG_REQUEST_SHAPE` | disabled (opt-in via `"1"`) | `src/app/api/v1/chat/completions/route.ts` | Log content-type/length markers for large chat payloads when `"1"` is set. Off by default to reduce log noise. |
|
||||
| `DEBUG_RESPONSES_SSE_TO_JSON` | _(unset)_ | `open-sse/handlers/responseTranslator.ts` | Set `true` to log Responses API SSE→JSON translation details. |
|
||||
| `DEBUG_CLAUDE_NONSTREAM` | _(unset)_ | `open-sse/handlers/responseTranslator.ts` | Set `true` to surface empty textContent chunks in the Claude response translation path (debug only). |
|
||||
| `NEXT_PUBLIC_OMNIROUTE_E2E_MODE` | _(unset)_ | E2E test harness | Set `true` to enable E2E test mode (relaxed auth, test hooks). |
|
||||
|
||||
---
|
||||
@@ -1473,13 +1474,3 @@ Globale Defaults für den headless Browser und den ausgehenden Tool-Tunnel. Im D
|
||||
| `CHATGPT_WEB_CODEX_TUNNEL_ID` | _(unset)_ | `open-sse/executors/chatgpt-web-codex.ts` | Globale OpenAI-Tunnel-ID für lokale Codex-Tool-Runden. |
|
||||
| `CHATGPT_WEB_CODEX_RUNTIME_KEY` | _(unset)_ | `open-sse/executors/chatgpt-web-codex.ts` | Globaler Tunnel Runtime-Key; niemals in Logs ausgeben. |
|
||||
| `CHATGPT_WEB_CODEX_CONNECTOR_NAME` | _(unset)_ | `open-sse/executors/chatgpt-web-codex.ts` | Name des ChatGPT-Custom-Connectors für die MCP-Brücke. |
|
||||
---
|
||||
|
||||
## OmniConductor Bridge
|
||||
|
||||
Long-lived SSE consumer that mirrors OmniConductor hub tasks into the local A2A TaskManager (`src/lib/conductor/`). Opt-in — the bridge only starts when `CONDUCTOR_HUB_URL` is set. Server-side only: the hub token must never reach the browser.
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| --------------------- | ---------- | ---------------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| `CONDUCTOR_HUB_URL` | _(empty)_ | `src/lib/conductor/boot.ts` | Base URL of the OmniConductor hub (e.g. `http://127.0.0.1:7910`). Unset = bridge disabled. |
|
||||
| `CONDUCTOR_HUB_TOKEN` | _(empty)_ | `src/lib/conductor/boot.ts` | Hub credential for the SSE feed — emit a `spokesperson`-kind peer on the hub (`POST /v1/peers`, admin). |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { CONOL_FALLBACK_MODELS } from "../../../services/conolModels.ts";
|
||||
import { CONOL_FALLBACK_MODELS } from "../../../../services/conolModels.ts";
|
||||
|
||||
export const conol_webProvider: RegistryEntry = {
|
||||
id: "conol-web",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { RegistryEntry } from "../shared";
|
||||
import type { RegistryEntry } from "../../shared";
|
||||
|
||||
export const deepaiProvider: RegistryEntry = {
|
||||
id: "deepai",
|
||||
alias: "deepai",
|
||||
format: "custom",
|
||||
executor: "default",
|
||||
baseUrl: "https://api.deepai.org",
|
||||
authType: "apikey",
|
||||
authHeader: "api-key",
|
||||
|
||||
@@ -48,6 +48,7 @@ export async function getChatGptWebCodexDoctorStatus(connection: {
|
||||
mode: "browser-only",
|
||||
appName: "OmniRoute Codex",
|
||||
storageStatePath: paths.storageStatePath,
|
||||
brokerSocketPath: paths.brokerSocketPath,
|
||||
...(chrome ? { chromeExecutablePath: chrome } : {}),
|
||||
...(cdpConfigured ? { cdpEndpoint: process.env.CHATGPT_WEB_CODEX_CDP_URL } : {}),
|
||||
headed: false,
|
||||
|
||||
@@ -525,7 +525,7 @@ async function uploadConolImages(
|
||||
},
|
||||
sessionId
|
||||
),
|
||||
body: image.data,
|
||||
body: new Uint8Array(image.data),
|
||||
signal: signal ?? undefined,
|
||||
});
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -116,9 +116,10 @@ export class TinyCmsExecutor extends BaseExecutor {
|
||||
|
||||
const response = await fetch(CHAT_URL, fetchOptions);
|
||||
return {
|
||||
status: response.status,
|
||||
response,
|
||||
url: CHAT_URL,
|
||||
headers: Object.fromEntries(response.headers.entries()),
|
||||
body: response.body,
|
||||
transformedBody: bodyObj,
|
||||
};
|
||||
} catch (err: any) {
|
||||
return makeErrorResult(
|
||||
|
||||
@@ -358,7 +358,16 @@ function decodeText(ptr, len) {
|
||||
|
||||
const cachedTextEncoder = new TextEncoder();
|
||||
|
||||
if (!('encodeInto' in cachedTextEncoder)) {
|
||||
// Old-Safari compatibility: if the runtime encoder lacks `encodeInto`, polyfill
|
||||
// it. TS (DOM lib) types encodeInto as an always-present member, so a direct
|
||||
// `'encodeInto' in cachedTextEncoder` guard would narrow the encoder to `never`
|
||||
// in the negative branch. Test a widened copy instead — the result narrows a
|
||||
// boolean, never `cachedTextEncoder`, so the polyfill body stays typeable.
|
||||
const needsEncodeIntoPolyfill = !(
|
||||
"encodeInto" in (cachedTextEncoder as { encodeInto?: unknown })
|
||||
);
|
||||
|
||||
if (needsEncodeIntoPolyfill) {
|
||||
cachedTextEncoder.encodeInto = function (arg, view) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
view.set(buf);
|
||||
@@ -449,7 +458,11 @@ async function __wbg_init(module_or_path) {
|
||||
}
|
||||
|
||||
if (module_or_path === undefined) {
|
||||
module_or_path = new URL('wasm_signer_bg.wasm', import.meta.url);
|
||||
// The boot-time embed is passed explicitly via `initTinyCmsWasm()` below
|
||||
// (base64 → Buffer). Do NOT fall back to `new URL('wasm_signer_bg.wasm',
|
||||
// import.meta.url)`: no such asset exists in the tree (it is inlined as
|
||||
// base64), and the static URL would make Turbopack/Next fail the bundle.
|
||||
throw new Error("tinycmsSigner: wasm module not supplied; call initTinyCmsWasm() first");
|
||||
}
|
||||
const imports = __wbg_get_imports();
|
||||
|
||||
|
||||
@@ -537,7 +537,7 @@ function sanitizeResponsesUsage(usage: unknown): unknown {
|
||||
// DeepSeek native API: map flat prompt_cache_hit_tokens into input_tokens_details
|
||||
if (
|
||||
normalized.prompt_cache_hit_tokens !== undefined &&
|
||||
!normalized.input_tokens_details?.cached_tokens
|
||||
!(normalized.input_tokens_details as Record<string, unknown> | undefined)?.cached_tokens
|
||||
) {
|
||||
normalized.input_tokens_details = {
|
||||
...(normalized.input_tokens_details as Record<string, unknown> || {}),
|
||||
@@ -549,7 +549,7 @@ function sanitizeResponsesUsage(usage: unknown): unknown {
|
||||
if (
|
||||
normalized.cache_read_input_tokens !== undefined &&
|
||||
normalized.cache_read_input_tokens !== 0 &&
|
||||
!normalized.input_tokens_details?.cached_tokens
|
||||
!(normalized.input_tokens_details as Record<string, unknown> | undefined)?.cached_tokens
|
||||
) {
|
||||
normalized.input_tokens_details = {
|
||||
...(normalized.input_tokens_details as Record<string, unknown> || {}),
|
||||
|
||||
@@ -304,7 +304,7 @@ function buildSerperRequest(
|
||||
url: `${config.baseUrl}${endpoint}`,
|
||||
init: {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "X-API-Key": params.token },
|
||||
headers: { "Content-Type": "application/json", "X-API-Key": params.token ?? "" },
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
};
|
||||
@@ -322,7 +322,7 @@ function buildBraveRequest(
|
||||
url: `${config.baseUrl}${endpoint}?${qp}`,
|
||||
init: {
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json", "X-Subscription-Token": params.token },
|
||||
headers: { Accept: "application/json", "X-Subscription-Token": params.token ?? "" },
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -348,7 +348,7 @@ function buildExaRequest(
|
||||
url: config.baseUrl,
|
||||
init: {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "x-api-key": params.token },
|
||||
headers: { "Content-Type": "application/json", "x-api-key": params.token ?? "" },
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -18,7 +18,6 @@ import { handleNovitaVideoGeneration } from "./videoGeneration/novitaHandler.ts"
|
||||
import { handleXaiVideoGeneration } from "./videoGeneration/xaiGrokImagineHandler.ts";
|
||||
import { handleSegmindVideoGeneration } from "./videoGeneration/providers/segmind.ts";
|
||||
import { handleAdobeFireflyVideoGeneration } from "./videoGeneration/adobeFireflyHandler.ts";
|
||||
import { handleFalVideoGeneration } from "./videoGeneration/falHandler.ts";
|
||||
import { handleOpenAIVideoGeneration } from "./videoGeneration/openai.ts";
|
||||
import { getVideoJobPreset, handleVideoJobGeneration } from "./videoGeneration/job.ts";
|
||||
import {
|
||||
|
||||
@@ -395,6 +395,7 @@ async function attachPreparedCapabilityValues(
|
||||
provider: candidate.provider,
|
||||
model: candidate.model,
|
||||
},
|
||||
undefined,
|
||||
state.resolutionSnapshot
|
||||
);
|
||||
const maxOutputTokens = capabilities.maxOutputTokens;
|
||||
|
||||
@@ -30,7 +30,7 @@ export function applyBottleneckDoExpirePatch(): void {
|
||||
if (patched) return;
|
||||
patched = true;
|
||||
|
||||
const proto = Bottleneck.prototype as Record<string, unknown>;
|
||||
const proto = Bottleneck.prototype as unknown as Record<string, unknown>;
|
||||
const originalRun = proto._run as
|
||||
((index: string, job: BottleneckJob, wait: number) => unknown) | undefined;
|
||||
if (typeof originalRun !== "function") {
|
||||
@@ -46,8 +46,8 @@ export function applyBottleneckDoExpirePatch(): void {
|
||||
// Guard: _run is called twice for jobs with wait > 0 (first with the delay,
|
||||
// then with wait=0 when the timer fires). Without the flag, fixedDoExpire
|
||||
// would wrap itself recursively on the second call.
|
||||
if (typeof job?.doExpire === "function" && !(job as Record<string, unknown>)._doExpirePatched) {
|
||||
(job as Record<string, unknown>)._doExpirePatched = true;
|
||||
if (typeof job?.doExpire === "function" && !(job as unknown as Record<string, unknown>)._doExpirePatched) {
|
||||
(job as unknown as Record<string, unknown>)._doExpirePatched = true;
|
||||
const originalDoExpire = job.doExpire.bind(job);
|
||||
// Bottleneck registers the job in _states under options.id (Job.js
|
||||
// states.start(this.options.id)); a bare `job.id` does not exist and
|
||||
|
||||
@@ -25,6 +25,15 @@ import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts";
|
||||
import { errorResponse } from "@omniroute/open-sse/utils/error.ts";
|
||||
import * as logger from "@/sse/utils/logger";
|
||||
|
||||
/**
|
||||
* Caller-facing shape of handleImageGeneration(). The handler is untyped and
|
||||
* returns a wide inferred union across providers, so we narrow it to the two
|
||||
* discriminated arms this strategy actually consumes.
|
||||
*/
|
||||
type ImageGenerationResult =
|
||||
| { success: true; data?: unknown; status?: number; error?: string }
|
||||
| { success: false; data?: unknown; status?: number; error?: string };
|
||||
|
||||
/**
|
||||
* Execute a full combo strategy for an image generation request.
|
||||
*
|
||||
@@ -119,12 +128,12 @@ export async function executeImageCombo(
|
||||
}
|
||||
|
||||
// Execute image generation for this target
|
||||
const result = await handleImageGeneration({
|
||||
const result = (await handleImageGeneration({
|
||||
body: { ...body, model: target.modelStr },
|
||||
credentials,
|
||||
log,
|
||||
signal: auth.request?.signal || null,
|
||||
});
|
||||
})) as ImageGenerationResult;
|
||||
|
||||
if (result.success) {
|
||||
await clearRecoveredProviderState(credentials);
|
||||
|
||||
@@ -665,8 +665,6 @@ export function extractUsage(chunk) {
|
||||
chunk.usage.reasoning_tokens,
|
||||
// xAI's exact provider-reported cost (port of decolua/9router#2453, capability A).
|
||||
cost_in_usd_ticks: chunk.usage.cost_in_usd_ticks,
|
||||
cache_read_input_tokens: chunk.usage.cache_read_input_tokens,
|
||||
cache_creation_input_tokens: chunk.usage.cache_creation_input_tokens,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -426,8 +426,17 @@ export class ChatGptBrowserWorker {
|
||||
if (this.page && !this.page.isClosed()) return this.page;
|
||||
if (
|
||||
!browserLoginStateExists({
|
||||
mode: "browser-only",
|
||||
appName: this.config.appName,
|
||||
storageStatePath: this.config.storageStatePath,
|
||||
chromeExecutablePath: this.config.chromeExecutablePath,
|
||||
brokerSocketPath: join(getConfigDir(), "runtime", "turn-broker.sock"),
|
||||
headed: this.config.headed,
|
||||
proAvailable: false,
|
||||
autoApproveToolCalls: this.config.autoApproveToolCalls,
|
||||
...(this.config.chromeExecutablePath
|
||||
? { chromeExecutablePath: this.config.chromeExecutablePath }
|
||||
: {}),
|
||||
...(this.config.cdpEndpoint ? { cdpEndpoint: this.config.cdpEndpoint } : {}),
|
||||
})
|
||||
) {
|
||||
throw new Error(`ChatGPT web login state is missing: ${this.config.storageStatePath}`);
|
||||
@@ -956,7 +965,7 @@ export class ChatGptBrowserWorker {
|
||||
if (this.context) {
|
||||
const state = await this.context.storageState();
|
||||
atomicWriteFile(this.config.storageStatePath, `${JSON.stringify(state)}\n`);
|
||||
writeVerificationMarker(this.config.storageStatePath, capabilities.proAvailable);
|
||||
writeVerificationMarker(this.config.storageStatePath, turn.capabilities.proAvailable);
|
||||
}
|
||||
console.info(
|
||||
`[chatgpt-web] browser turn ${turn.traceId} completed (markdownChars=${finalText.length})`
|
||||
|
||||
@@ -114,6 +114,9 @@ const ENV_VAR_ALLOWLIST = new Set([
|
||||
"LINUX_GPG_KEY", // electron AppImage signing key, CI/build only (ELECTRON_GUIDE.md)
|
||||
"BRANCH_LOCK_TOKEN", // release branch-protection ops token (QUALITY_GATE_PLAYBOOK.md)
|
||||
"NEXT_LOCALE", // next-intl locale cookie name (I18N.md)
|
||||
// Telegram Mini App integration (proposal TELEGRAM-MINIAPP.md, not yet implemented): env vars named in the feasibility analysis but no code reads them yet.
|
||||
"TELEGRAM_WEBHOOK_URL", // proposal-only: Telegram webhook public endpoint (TELEGRAM-MINIAPP.md, future feature)
|
||||
"TELEGRAM_WEBHOOK_SECRET", // proposal-only: Telegram webhook HMAC secret (TELEGRAM-MINIAPP.md, future feature)
|
||||
]);
|
||||
|
||||
// Common pluralized / column-header all-caps that aren't env vars
|
||||
|
||||
@@ -51,7 +51,7 @@ export const KNOWN_DUPLICATE_VERSIONS = new Set([
|
||||
// O stale-enforcement exige que cada reserva seja removida quando os arquivos
|
||||
// correspondentes aterrissarem na release.
|
||||
// ---------------------------------------------------------------------------
|
||||
export const KNOWN_GAPS = new Set(["026", "055", "121", "144", "145", "147", "148", "149"]); // 121: número queimado no ciclo v3.8.47 — 122 (#6909) mergeou antes e 121 nunca aterrissou (validação e2e 2026-07-12)
|
||||
export const KNOWN_GAPS = new Set(["026", "055", "121", "144", "145", "148", "149"]); // 121: número queimado no ciclo v3.8.47 — 122 (#6909) mergeou antes e 121 nunca aterrissou (validação e2e 2026-07-12)
|
||||
|
||||
function pad3(n) {
|
||||
return String(n).padStart(3, "0");
|
||||
|
||||
@@ -4,20 +4,6 @@ description: Step-by-step agent workflow to configure the OpenAI Codex CLI on an
|
||||
---
|
||||
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
|
||||
|
||||
## Overview
|
||||
|
||||
Step-by-step agent workflow to configure the OpenAI Codex CLI on any machine (Linux, macOS, Windows) to use OmniRoute as an OpenAI-compatible backend. Detects OS and shell, writes config.toml and 7 named profiles, sets environment variables, and verifies the setup.
|
||||
|
||||
## Quick install
|
||||
|
||||
```bash
|
||||
npm install -g omniroute # or: npx omniroute
|
||||
omniroute --version
|
||||
```
|
||||
|
||||
## Subcommands
|
||||
|
||||
_No CLI subcommands mapped for this family yet._
|
||||
|
||||
<!-- skill:custom-start -->
|
||||
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Conductor panel (PRD Conductor RF3): fleet + task queue live view over the
|
||||
* /api/conductor proxy routes. The browser never talks to the hub — everything
|
||||
* goes through the server-side proxy (hub token stays in server env).
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Badge, Card, ConfirmModal, DataTable, EmptyState, Modal } from "@/shared/components";
|
||||
|
||||
interface FleetRunner {
|
||||
id: string;
|
||||
name: string;
|
||||
clis: string[];
|
||||
online: boolean;
|
||||
draining: boolean;
|
||||
}
|
||||
|
||||
interface FleetTask {
|
||||
id: string;
|
||||
status: string;
|
||||
mode: string;
|
||||
repo: string | null;
|
||||
runner: string | null;
|
||||
summary: string | null;
|
||||
branch: string | null;
|
||||
error: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
interface FleetSnapshot {
|
||||
offline: boolean;
|
||||
runners: FleetRunner[];
|
||||
tasks: FleetTask[];
|
||||
}
|
||||
|
||||
interface TaskDetail extends FleetTask {
|
||||
prompt: string | null;
|
||||
base_ref: string | null;
|
||||
council: { candidate_task_ids?: string[] } | null;
|
||||
}
|
||||
|
||||
const REFRESH_MS = 5000;
|
||||
const TERMINAL = new Set(["completed", "failed", "canceled"]);
|
||||
|
||||
function statusVariant(status: string): "success" | "error" | "warning" | "info" | "default" {
|
||||
if (status === "completed") return "success";
|
||||
if (status === "failed") return "error";
|
||||
if (status === "canceled" || status === "input_required") return "warning";
|
||||
if (status === "working") return "info";
|
||||
return "default";
|
||||
}
|
||||
|
||||
export default function ConductorPageClient() {
|
||||
const t = useTranslations("conductor");
|
||||
const [snapshot, setSnapshot] = useState<FleetSnapshot | null>(null);
|
||||
const [detail, setDetail] = useState<TaskDetail | null>(null);
|
||||
const [cancelTarget, setCancelTarget] = useState<string | null>(null);
|
||||
const [canceling, setCanceling] = useState(false);
|
||||
const [err, setErr] = useState("");
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch("/api/conductor/fleet");
|
||||
if (res.ok) setSnapshot(await res.json());
|
||||
} catch {
|
||||
// rede local instável: mantém o último snapshot; o banner offline vem do servidor
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void load();
|
||||
const timer = setInterval(() => void load(), REFRESH_MS);
|
||||
return () => clearInterval(timer);
|
||||
}, [load]);
|
||||
|
||||
const openDetail = async (taskId: string) => {
|
||||
setErr("");
|
||||
try {
|
||||
const res = await fetch(`/api/conductor/tasks/${encodeURIComponent(taskId)}`);
|
||||
if (res.ok) setDetail(await res.json());
|
||||
else setErr(`${t("error")}: HTTP ${res.status}`);
|
||||
} catch {
|
||||
setErr(t("error"));
|
||||
}
|
||||
};
|
||||
|
||||
const confirmCancel = async () => {
|
||||
if (!cancelTarget) return;
|
||||
setCanceling(true);
|
||||
setErr("");
|
||||
try {
|
||||
const res = await fetch(`/api/conductor/tasks/${encodeURIComponent(cancelTarget)}/cancel`, { method: "POST" });
|
||||
if (!res.ok) setErr(`${t("cancelFailed")} (HTTP ${res.status})`);
|
||||
else {
|
||||
setDetail(null);
|
||||
await load();
|
||||
}
|
||||
} catch {
|
||||
setErr(t("cancelFailed"));
|
||||
} finally {
|
||||
setCanceling(false);
|
||||
setCancelTarget(null);
|
||||
}
|
||||
};
|
||||
|
||||
const runners = snapshot?.runners ?? [];
|
||||
const tasks = snapshot?.tasks ?? [];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold">{t("title")}</h1>
|
||||
<p className="text-sm text-text-muted">{t("subtitle")}</p>
|
||||
</div>
|
||||
|
||||
{err && <Badge variant="error">{err}</Badge>}
|
||||
|
||||
{snapshot?.offline ? (
|
||||
<Card>
|
||||
<EmptyState icon="cloud_off" title={t("hubOffline")} />
|
||||
</Card>
|
||||
) : (
|
||||
<>
|
||||
<Card title={t("runners")}>
|
||||
<DataTable
|
||||
columns={[
|
||||
{ key: "name", label: t("colName") },
|
||||
{ key: "clis", label: t("colClis") },
|
||||
{ key: "status", label: t("colStatus") },
|
||||
]}
|
||||
data={runners.map((r) => ({ ...r, id: r.id }))}
|
||||
emptyMessage={t("noRunners")}
|
||||
loading={snapshot === null}
|
||||
renderCell={(row, column) => {
|
||||
const r = row as unknown as FleetRunner;
|
||||
if (column.key === "name") return <span className="font-medium">{r.name}</span>;
|
||||
if (column.key === "clis") return r.clis.join(" / ");
|
||||
if (r.draining) return <Badge variant="warning" dot>{t("draining")}</Badge>;
|
||||
return r.online ? (
|
||||
<Badge variant="success" dot>{t("online")}</Badge>
|
||||
) : (
|
||||
<Badge variant="error" dot>{t("offline")}</Badge>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card title={t("tasks")}>
|
||||
<DataTable
|
||||
columns={[
|
||||
{ key: "id", label: t("colTask") },
|
||||
{ key: "status", label: t("colStatus") },
|
||||
{ key: "mode", label: t("colMode") },
|
||||
{ key: "runner", label: t("colRunner") },
|
||||
{ key: "summary", label: t("colSummary"), maxWidth: "28rem" },
|
||||
]}
|
||||
data={tasks.map((task) => ({ ...task, id: task.id }))}
|
||||
emptyMessage={t("noTasks")}
|
||||
loading={snapshot === null}
|
||||
onRowClick={(row) => void openDetail(String(row.id))}
|
||||
renderCell={(row, column) => {
|
||||
const task = row as unknown as FleetTask;
|
||||
if (column.key === "status") return <Badge variant={statusVariant(task.status)} dot>{task.status}</Badge>;
|
||||
if (column.key === "id") return <code className="text-xs">{task.id}</code>;
|
||||
if (column.key === "summary") return task.summary ?? task.error ?? "—";
|
||||
return (task as unknown as Record<string, unknown>)[column.key]?.toString() ?? "—";
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Modal isOpen={detail !== null} onClose={() => setDetail(null)} title={t("detailTitle")} size="lg">
|
||||
{detail && (
|
||||
<div className="space-y-4 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="text-xs">{detail.id}</code>
|
||||
<Badge variant={statusVariant(detail.status)} dot>{detail.status}</Badge>
|
||||
<Badge>{detail.mode}</Badge>
|
||||
{detail.runner && <Badge variant="info">{detail.runner}</Badge>}
|
||||
</div>
|
||||
{detail.prompt && (
|
||||
<div>
|
||||
<div className="font-medium">{t("prompt")}</div>
|
||||
<pre className="whitespace-pre-wrap text-xs bg-black/5 dark:bg-white/5 rounded p-2">{detail.prompt}</pre>
|
||||
</div>
|
||||
)}
|
||||
{detail.summary && <p>{detail.summary}</p>}
|
||||
{detail.error && <Badge variant="error">{detail.error}</Badge>}
|
||||
{detail.branch && (
|
||||
<div>
|
||||
<div className="font-medium">{t("branch")}</div>
|
||||
<code className="text-xs">{detail.branch}</code>
|
||||
<p className="text-xs text-text-muted">{t("fetchHint", { branch: detail.branch })}</p>
|
||||
</div>
|
||||
)}
|
||||
{detail.mode.startsWith("council") && detail.council?.candidate_task_ids && (
|
||||
<div>
|
||||
<div className="font-medium">{t("council")}</div>
|
||||
<p className="text-xs">
|
||||
{t("candidates")}: {detail.council.candidate_task_ids.join(", ")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{!TERMINAL.has(detail.status) && (
|
||||
<button
|
||||
type="button"
|
||||
className="text-sm text-red-600 dark:text-red-400 underline"
|
||||
onClick={() => setCancelTarget(detail.id)}
|
||||
>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={cancelTarget !== null}
|
||||
onClose={() => setCancelTarget(null)}
|
||||
onConfirm={confirmCancel}
|
||||
title={t("cancelConfirmTitle")}
|
||||
message={t("cancelConfirmMessage")}
|
||||
confirmText={t("cancel")}
|
||||
loading={canceling}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import ConductorPageClient from "./ConductorPageClient";
|
||||
|
||||
export const metadata = {
|
||||
title: "Conductor — OmniRoute",
|
||||
description: "OmniConductor CLI-agent fleet: runners, task queue and councils, live.",
|
||||
};
|
||||
|
||||
export default function ConductorPage() {
|
||||
return <ConductorPageClient />;
|
||||
}
|
||||
@@ -52,6 +52,17 @@ const fetchStub = vi.fn().mockResolvedValue({
|
||||
} as any);
|
||||
vi.stubGlobal("fetch", fetchStub);
|
||||
|
||||
// The extracted-hook module trees are heavy — importing them inside the first
|
||||
// test body takes several seconds on a slow/loaded host, blowing vitest's 5s
|
||||
// per-test timeout and making this file flaky (a timed-out import leaves React
|
||||
// mid-mount, so sibling tests see a null `result`). Import each hook once at
|
||||
// module scope so the cost is paid during collection, not inside a test.
|
||||
// (Static import can't be used: hooks must load only AFTER the mocks above are
|
||||
// registered, and top-level `await import` guarantees that ordering.)
|
||||
const { useProviderConnections } = await import("../hooks/useProviderConnections");
|
||||
const { useProviderSettings } = await import("../hooks/useProviderSettings");
|
||||
const { useProviderModels } = await import("../hooks/useProviderModels");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// useProviderConnections
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -75,8 +86,6 @@ describe("useProviderConnections — initial state", () => {
|
||||
});
|
||||
|
||||
it("exposes connections=[], loading=true, batchTesting=false on first render", async () => {
|
||||
const { useProviderConnections } = await import("../hooks/useProviderConnections");
|
||||
|
||||
type HookResult = ReturnType<typeof useProviderConnections>;
|
||||
let result: HookResult | null = null;
|
||||
|
||||
@@ -107,8 +116,6 @@ describe("useProviderConnections — initial state", () => {
|
||||
});
|
||||
|
||||
it("exposes all expected handler functions", async () => {
|
||||
const { useProviderConnections } = await import("../hooks/useProviderConnections");
|
||||
|
||||
type HookResult = ReturnType<typeof useProviderConnections>;
|
||||
let result: HookResult | null = null;
|
||||
|
||||
@@ -179,8 +186,6 @@ describe("useProviderSettings — initial state", () => {
|
||||
});
|
||||
|
||||
it("exposes codex defaults for a non-codex provider", async () => {
|
||||
const { useProviderSettings } = await import("../hooks/useProviderSettings");
|
||||
|
||||
type HookResult = ReturnType<typeof useProviderSettings>;
|
||||
let result: HookResult | null = null;
|
||||
|
||||
@@ -208,8 +213,6 @@ describe("useProviderSettings — initial state", () => {
|
||||
});
|
||||
|
||||
it("exposes handler functions", async () => {
|
||||
const { useProviderSettings } = await import("../hooks/useProviderSettings");
|
||||
|
||||
type HookResult = ReturnType<typeof useProviderSettings>;
|
||||
let result: HookResult | null = null;
|
||||
|
||||
@@ -257,8 +260,6 @@ describe("useProviderModels — initial state", () => {
|
||||
});
|
||||
|
||||
it("initialises with empty arrays and empty alias map", async () => {
|
||||
const { useProviderModels } = await import("../hooks/useProviderModels");
|
||||
|
||||
type HookResult = ReturnType<typeof useProviderModels>;
|
||||
let result: HookResult | null = null;
|
||||
|
||||
@@ -282,8 +283,6 @@ describe("useProviderModels — initial state", () => {
|
||||
});
|
||||
|
||||
it("exposes handler functions", async () => {
|
||||
const { useProviderModels } = await import("../hooks/useProviderModels");
|
||||
|
||||
type HookResult = ReturnType<typeof useProviderModels>;
|
||||
let result: HookResult | null = null;
|
||||
|
||||
|
||||
@@ -342,6 +342,10 @@ export default function EditConnectionModal({
|
||||
opencodeGoWorkspaceId: existingOpenCodeGoWorkspaceId,
|
||||
opencodeGoAuthCookie: "",
|
||||
ollamaCloudUsageCookie: "",
|
||||
alibabaConsoleCookie: stringField(connection.providerSpecificData?.alibabaConsoleCookie),
|
||||
alibabaConsoleSecToken: stringField(
|
||||
connection.providerSpecificData?.alibabaConsoleSecToken
|
||||
),
|
||||
ccCompatibleContext1m: ccRequestDefaults.context1m,
|
||||
ccCompatibleRedactThinking: ccRequestDefaults.redactThinking,
|
||||
ccCompatibleSummarizeThinking: ccRequestDefaults.summarizeThinking,
|
||||
@@ -515,6 +519,7 @@ export default function EditConnectionModal({
|
||||
return;
|
||||
}
|
||||
let validatedBaseUrl = null;
|
||||
let validationPsd = validatedProviderSpecificData;
|
||||
if (usesBaseUrl) {
|
||||
// #6147 — an opt-in override left blank clears it (no default to fall
|
||||
// back to). Configurable providers keep their existing default-fallback.
|
||||
@@ -530,7 +535,6 @@ export default function EditConnectionModal({
|
||||
}
|
||||
}
|
||||
if (!isOAuth && formData.apiKey) {
|
||||
let validationPsd = validatedProviderSpecificData;
|
||||
let isValid = validationResult === "success";
|
||||
if (!isValid) {
|
||||
try {
|
||||
|
||||
@@ -827,7 +827,10 @@ export function shouldSwitchToVisibleFilter(opts: {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Error-type label map — shared by ConnectionRow and EditConnectionModal
|
||||
// ---------------------------------------------------------------------------
|
||||
export const ERROR_TYPE_LABELS: Record<string, { labelKey: string; variant: string }> = {
|
||||
export const ERROR_TYPE_LABELS: Record<
|
||||
string,
|
||||
{ labelKey: string; variant: "error" | "default" | "warning" | "success" | "info" | "primary" }
|
||||
> = {
|
||||
runtime_error: { labelKey: "errorTypeRuntime", variant: "warning" },
|
||||
upstream_auth_error: { labelKey: "errorTypeUpstreamAuth", variant: "error" },
|
||||
account_deactivated: { labelKey: "Account Deactivated", variant: "error" },
|
||||
|
||||
@@ -96,6 +96,7 @@ interface FreeBudgetLabels {
|
||||
credit: (tokens: string) => string;
|
||||
freeTypes: Record<string, string>;
|
||||
tosTitles: Record<string, string>;
|
||||
noApiKey: string;
|
||||
}
|
||||
|
||||
const DEFAULT_LABELS: FreeBudgetLabels = {
|
||||
@@ -117,6 +118,7 @@ const DEFAULT_LABELS: FreeBudgetLabels = {
|
||||
type: "Type",
|
||||
tokensMonth: "Tokens/mo",
|
||||
credit: (tokens) => `${tokens} credit`,
|
||||
noApiKey: "No API key required",
|
||||
freeTypes: {
|
||||
"recurring-daily": "daily",
|
||||
"recurring-monthly": "monthly",
|
||||
@@ -428,7 +430,7 @@ export function FreeBudgetView({
|
||||
lock_open
|
||||
</span>
|
||||
<span className="text-[11px] font-semibold text-emerald-500">
|
||||
{t("noApiKeyRequired")}
|
||||
{labels.noApiKey}
|
||||
</span>
|
||||
<span className="text-[10.5px] text-text-muted">
|
||||
({keylessModels.length}个模型 · {keylessProviders.length}个提供者)
|
||||
@@ -673,6 +675,7 @@ export default function FreeBudgetCard() {
|
||||
type: t("type"),
|
||||
tokensMonth: t("tokensMonth"),
|
||||
credit: (tokens) => t("credit", { tokens }),
|
||||
noApiKey: t("noApiKeyRequired"),
|
||||
freeTypes: {
|
||||
"recurring-daily": t("freeType.daily"),
|
||||
"recurring-monthly": t("freeType.monthly"),
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { getFleetSkills } from "@/lib/conductor/fleetSkills";
|
||||
|
||||
const PACKAGE_VERSION = process.env.npm_package_version || "1.8.1";
|
||||
const BASE_URL = process.env.OMNIROUTE_BASE_URL || "http://localhost:20128";
|
||||
|
||||
@@ -22,9 +20,6 @@ const BASE_URL = process.env.OMNIROUTE_BASE_URL || "http://localhost:20128";
|
||||
* capabilities as an A2A agent.
|
||||
*/
|
||||
export async function GET() {
|
||||
// Conductor PRD RF2: fleet skills from the OmniConductor hub (cached ~60s; [] when
|
||||
// the hub is unset/offline — the card stays valid without the fleet section).
|
||||
const fleetSkills = await getFleetSkills();
|
||||
const agentCard = {
|
||||
name: "OmniRoute AI 网关",
|
||||
description:
|
||||
@@ -100,7 +95,6 @@ export async function GET() {
|
||||
tags: ["discovery", "capabilities"],
|
||||
examples: ["你能做什么?", "列出你的技能", "展示能力"],
|
||||
},
|
||||
...fleetSkills,
|
||||
],
|
||||
authentication: {
|
||||
schemes: ["api-key"],
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* GET /api/conductor/fleet — fleet snapshot for the Conductor dashboard panel
|
||||
* (PRD Conductor RF3). Server-side proxy: the hub token lives only in env; the
|
||||
* response is the whitelisted shape from hubProxy (degraded {offline:true} when
|
||||
* the hub is unset/offline — never a 5xx for that).
|
||||
*/
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
import { getFleetSnapshot } from "@/lib/conductor/hubProxy";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
return NextResponse.json(await getFleetSnapshot());
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* POST /api/conductor/tasks/[id]/cancel — cancela a task no hub do Conductor.
|
||||
* Ação destrutiva: auth de gerência + confirmação na UI (ConfirmModal). A
|
||||
* recusa do hub volta só como status + mensagem sanitizada (nunca o corpo
|
||||
* upstream — Hard Rule #12).
|
||||
*/
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { createErrorResponse } from "@/lib/api/errorResponse";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
import { cancelConductorTask } from "@/lib/conductor/hubProxy";
|
||||
|
||||
export async function POST(request: Request, ctx: { params: Promise<{ id: string }> }) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
const { id } = await ctx.params;
|
||||
const result = await cancelConductorTask(id);
|
||||
if (!result.ok) {
|
||||
return createErrorResponse({
|
||||
status: result.status,
|
||||
message: `Conductor hub refused the cancellation (HTTP ${result.status})`,
|
||||
});
|
||||
}
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* GET /api/conductor/tasks/[id] — whitelisted task detail (manifest, prompt,
|
||||
* council funnel) from the Conductor hub. 404 sanitizado quando o hub não
|
||||
* conhece a task — o corpo do hub nunca é repassado.
|
||||
*/
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { createErrorResponse } from "@/lib/api/errorResponse";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
import { getConductorTaskDetail } from "@/lib/conductor/hubProxy";
|
||||
|
||||
export async function GET(request: Request, ctx: { params: Promise<{ id: string }> }) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
const { id } = await ctx.params;
|
||||
const detail = await getConductorTaskDetail(id);
|
||||
if (!detail) {
|
||||
return createErrorResponse({ status: 404, message: "Conductor task not found (or hub offline)" });
|
||||
}
|
||||
return NextResponse.json(detail);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SAFE_OUTBOUND_FETCH_PRESETS, safeOutboundFetch } from "@/shared/network/safeOutboundFetch";
|
||||
import { getProviderOutboundGuard } from "@/shared/network/outboundUrlGuard";
|
||||
import { getProviderOutboundGuard } from "@/shared/network/outboundUrlGuardPolicy";
|
||||
import { resolveConolCredentials } from "@omniroute/open-sse/services/conolAuth.ts";
|
||||
import {
|
||||
CONOL_FALLBACK_MODELS,
|
||||
|
||||
@@ -116,25 +116,18 @@ export { getCustomVisionCapabilityFields };
|
||||
// lives in ./catalogCache. Re-exported here because the existing tests import the
|
||||
// hooks from this module, and CATALOG_STALE_WHILE_REVALIDATE_MS is part of the
|
||||
// documented behavior of this endpoint.
|
||||
import {
|
||||
CATALOG_CACHE_TTL_MS_DEFAULT,
|
||||
resolveCachedCatalogResponse,
|
||||
type CatalogCachePolicy,
|
||||
} from "./catalogCache";
|
||||
import { CATALOG_CACHE_TTL_MS_DEFAULT, resolveCachedCatalogResponse } from "./catalogCache";
|
||||
|
||||
export {
|
||||
CATALOG_STALE_WHILE_REVALIDATE_MS,
|
||||
getCatalogStaleWhileRevalidateMs,
|
||||
__resetCatalogBuilderRunsForTest,
|
||||
__getCatalogBuilderRunsForTest,
|
||||
__expireCatalogCacheForTest,
|
||||
__setCatalogCacheEntryForTest,
|
||||
__flushCatalogBackgroundRefreshForTest,
|
||||
__forceCatalogInFlightRejectionForTest,
|
||||
__setCatalogStaleWhileRevalidateAccessorForTest,
|
||||
__setCatalogStaleWhileRevalidateMsForTest,
|
||||
} from "./catalogCache";
|
||||
export type { CachedCatalog, CatalogCachePolicy } from "./catalogCache";
|
||||
export type { CachedCatalog } from "./catalogCache";
|
||||
|
||||
const BUILTIN_AUTO_YIELD_INTERVAL = 8;
|
||||
|
||||
@@ -149,7 +142,7 @@ function yieldCatalogBuildTurn(): Promise<void> {
|
||||
export async function getUnifiedModelsResponse(
|
||||
request: Request,
|
||||
corsHeaders: Record<string, string> = {},
|
||||
cachePolicy: CatalogCachePolicy = {}
|
||||
cachePolicy: { hideAutoCombos?: boolean; hideNoThinkVariants?: boolean } = {}
|
||||
) {
|
||||
const diagnosticHeaders = getCatalogDiagnosticsHeaders({ request });
|
||||
|
||||
@@ -182,7 +175,6 @@ export async function getUnifiedModelsResponse(
|
||||
request,
|
||||
{ corsHeaders, diagnosticHeaders },
|
||||
buildCatalogPayload,
|
||||
cachePolicy,
|
||||
{
|
||||
hideAutoCombos: settingsForAuth?.hideAutoCombos === true,
|
||||
hideNoThinkVariants: settingsForAuth?.hideNoThinkVariants === true,
|
||||
|
||||
@@ -200,12 +200,10 @@ function scheduleBackgroundRefresh(
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
inFlight = { version: lastSeenCatalogCacheVersion, promise };
|
||||
|
||||
// Nobody on the stale path awaits this, so pre-handle the rejection; a cold-path
|
||||
// caller that joins it via catalogInFlight attaches its own handler and still
|
||||
// observes the failure.
|
||||
promise.catch(() => {});
|
||||
|
||||
catalogInFlight.set(cacheKey, { generation, promise: refreshPromise });
|
||||
refreshPromise
|
||||
|
||||
@@ -1275,8 +1275,6 @@
|
||||
"groupSeparatorLabel": "Separator",
|
||||
"discovery": "Discovery",
|
||||
"discoverySubtitle": "Scan providers for free access",
|
||||
"conductor": "Conductor",
|
||||
"conductorSubtitle": "CLI-agent fleet",
|
||||
"resilienceConnections": "Connection Resilience",
|
||||
"resilienceConnectionsSubtitle": "Cooldown, breaker, lockout state",
|
||||
"settingsModalityBridge": "Modality Bridge",
|
||||
@@ -13523,36 +13521,5 @@
|
||||
"relayDescription": "Serverless relay proxy endpoints for your AI infrastructure",
|
||||
"trafficInspectorTitle": "Traffic Inspector — OmniRoute",
|
||||
"trafficInspectorDescription": "Monitor LLM calls + debug any application's HTTPS traffic"
|
||||
},
|
||||
"conductor": {
|
||||
"title": "Conductor — CLI-agent fleet",
|
||||
"subtitle": "OmniConductor hub: runners, task queue and councils, live",
|
||||
"runners": "Runners",
|
||||
"tasks": "Tasks",
|
||||
"colName": "Name",
|
||||
"colClis": "CLIs",
|
||||
"colStatus": "Status",
|
||||
"colTask": "Task",
|
||||
"colMode": "Mode",
|
||||
"colRunner": "Runner",
|
||||
"colSummary": "Summary",
|
||||
"online": "online",
|
||||
"offline": "offline",
|
||||
"draining": "draining",
|
||||
"hubOffline": "Conductor hub offline or not configured (CONDUCTOR_HUB_URL) — showing nothing rather than stale data.",
|
||||
"noRunners": "No runners registered",
|
||||
"noTasks": "No tasks yet",
|
||||
"detailTitle": "Task detail",
|
||||
"prompt": "Prompt",
|
||||
"branch": "Branch",
|
||||
"fetchHint": "Fetch the result with: git fetch origin {branch}",
|
||||
"council": "Council",
|
||||
"candidates": "Candidates",
|
||||
"cancel": "Cancel task",
|
||||
"cancelConfirmTitle": "Cancel this task?",
|
||||
"cancelConfirmMessage": "The hub will abort the execution on the runner. This cannot be undone.",
|
||||
"cancelFailed": "The hub refused the cancellation",
|
||||
"close": "Close",
|
||||
"error": "Error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1263,8 +1263,6 @@
|
||||
"groupSeparatorLabel": "Separador",
|
||||
"discovery": "Descoberta",
|
||||
"discoverySubtitle": "Buscar acesso gratuito em provedores",
|
||||
"conductor": "Conductor",
|
||||
"conductorSubtitle": "Frota de agentes CLI",
|
||||
"resilienceConnections": "Resiliência de Conexão",
|
||||
"resilienceConnectionsSubtitle": "Cooldown, disjuntor, estado de bloqueio",
|
||||
"settingsModalityBridge": "Ponte de Modalidade",
|
||||
@@ -13508,37 +13506,6 @@
|
||||
"relayDescription": "__MISSING__:Serverless relay proxy endpoints for your AI infrastructure",
|
||||
"trafficInspectorTitle": "__MISSING__:Traffic Inspector — OmniRoute",
|
||||
"trafficInspectorDescription": "__MISSING__:Monitor LLM calls + debug any application's HTTPS traffic"
|
||||
},
|
||||
"conductor": {
|
||||
"title": "Conductor — frota de agentes CLI",
|
||||
"subtitle": "Hub OmniConductor: runners, fila de tasks e councils, ao vivo",
|
||||
"runners": "Runners",
|
||||
"tasks": "Tasks",
|
||||
"colName": "Nome",
|
||||
"colClis": "CLIs",
|
||||
"colStatus": "Status",
|
||||
"colTask": "Task",
|
||||
"colMode": "Modo",
|
||||
"colRunner": "Runner",
|
||||
"colSummary": "Resumo",
|
||||
"online": "online",
|
||||
"offline": "offline",
|
||||
"draining": "drenando",
|
||||
"hubOffline": "Hub do Conductor fora do ar ou não configurado (CONDUCTOR_HUB_URL) — melhor nada do que dado velho.",
|
||||
"noRunners": "Nenhum runner registrado",
|
||||
"noTasks": "Nenhuma task ainda",
|
||||
"detailTitle": "Detalhe da task",
|
||||
"prompt": "Prompt",
|
||||
"branch": "Branch",
|
||||
"fetchHint": "Busque o resultado com: git fetch origin {branch}",
|
||||
"council": "Council",
|
||||
"candidates": "Candidatos",
|
||||
"cancel": "Cancelar task",
|
||||
"cancelConfirmTitle": "Cancelar esta task?",
|
||||
"cancelConfirmMessage": "O hub aborta a execução no runner. Não dá para desfazer.",
|
||||
"cancelFailed": "O hub recusou o cancelamento",
|
||||
"close": "Fechar",
|
||||
"error": "Erro"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -534,15 +534,6 @@ export async function registerNodejs(): Promise<void> {
|
||||
console.warn("[STARTUP] Auto-refresh daemon failed to start (non-fatal):", msg);
|
||||
}),
|
||||
|
||||
// Conductor bridge (PRD Conductor RF1): mirrors OmniConductor hub tasks into the
|
||||
// A2A TaskManager via the hub SSE. Opt-in — self-gated on CONDUCTOR_HUB_URL.
|
||||
import("@/lib/conductor/boot").then((m) => {
|
||||
if (m.initConductorBridge()) console.log("[STARTUP] Conductor bridge started");
|
||||
}).catch((err: unknown) => {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
console.warn("[STARTUP] Conductor bridge failed to start (non-fatal):", msg);
|
||||
}),
|
||||
|
||||
// Proactive connection-cooldown recovery (#8): re-validate connections whose
|
||||
// transient `rate_limited_until` window has elapsed OUTSIDE the request hot path,
|
||||
// so the first request after a cooldown does not pay the probe latency.
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Conductor bridge boot — production wiring for `createConductorBridge`.
|
||||
*
|
||||
* Opt-in: only starts when `CONDUCTOR_HUB_URL` is set. Called from
|
||||
* `instrumentation-node.ts` (same non-fatal pattern as the other daemons).
|
||||
* No graceful-shutdown registration needed: the cursor is persisted after each
|
||||
* event, so an abrupt exit loses nothing — the hub replay converges the mirror.
|
||||
*/
|
||||
|
||||
import { getTaskManager } from "@/lib/a2a/taskManager";
|
||||
import { getConductorCursor, setConductorCursor } from "@/lib/db/conductorBridge";
|
||||
|
||||
import { createConductorBridge, type ConductorBridge, type ConductorBridgeOptions } from "./bridge";
|
||||
|
||||
let bridge: ConductorBridge | null = null;
|
||||
|
||||
/** Starts the bridge once (idempotent). Returns null when CONDUCTOR_HUB_URL is unset. */
|
||||
export function initConductorBridge(overrides: Partial<ConductorBridgeOptions> = {}): ConductorBridge | null {
|
||||
const hubUrl = process.env.CONDUCTOR_HUB_URL?.trim();
|
||||
if (!hubUrl) return null;
|
||||
if (bridge) return bridge;
|
||||
bridge = createConductorBridge({
|
||||
hubUrl,
|
||||
token: process.env.CONDUCTOR_HUB_TOKEN?.trim() ?? "",
|
||||
tm: getTaskManager(),
|
||||
cursor: { get: getConductorCursor, set: setConductorCursor },
|
||||
...overrides,
|
||||
});
|
||||
bridge.start();
|
||||
return bridge;
|
||||
}
|
||||
|
||||
export function stopConductorBridge(): void {
|
||||
bridge?.stop();
|
||||
bridge = null;
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
/**
|
||||
* Conductor Bridge — mirrors OmniConductor hub tasks into the local A2A TaskManager.
|
||||
*
|
||||
* The hub's SSE feed is the source of truth; this mirror is disposable and fully
|
||||
* rebuildable by replay (`last_event_id`). State spelling differs on purpose:
|
||||
* the Conductor follows A2A upstream with `canceled` (1 L) while OmniRoute's
|
||||
* TaskManager uses `cancelled` (2 L) — the mapping here is explicit and tested.
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
import type { A2ATaskManager, TaskState } from "@/lib/a2a/taskManager";
|
||||
|
||||
export interface ConductorEvent {
|
||||
id: string;
|
||||
type: string;
|
||||
payload: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** Target A2A state for a Conductor event type; null = not a task-state event (tolerated). */
|
||||
export function conductorStateFor(type: string): TaskState | null {
|
||||
switch (type) {
|
||||
case "task.created":
|
||||
return "submitted";
|
||||
case "task.scheduled":
|
||||
case "task.input_required":
|
||||
return "working";
|
||||
case "task.completed":
|
||||
return "completed";
|
||||
case "task.failed":
|
||||
return "failed";
|
||||
case "task.canceled": // Conductor/A2A upstream spelling (1 L)
|
||||
return "cancelled"; // local TaskManager spelling (2 L)
|
||||
default:
|
||||
return null; // runner.*, council.*, terminal.*, future types — forward-compat
|
||||
}
|
||||
}
|
||||
|
||||
/** Minimal legal path from a task's current state up to the target state. */
|
||||
const LADDER: TaskState[] = ["submitted", "working", "completed"];
|
||||
|
||||
function climb(tm: A2ATaskManager, a2aId: string, target: TaskState): void {
|
||||
const task = tm.getTask(a2aId);
|
||||
if (!task) return;
|
||||
if (task.state === target) return;
|
||||
if (task.state === "completed" || task.state === "failed" || task.state === "cancelled") return; // terminal: late events ignored
|
||||
try {
|
||||
if (target === "completed") {
|
||||
// submitted → working → completed (the manager rejects skips)
|
||||
let idx = LADDER.indexOf(task.state);
|
||||
for (idx = idx + 1; idx < LADDER.length; idx++) tm.updateTask(a2aId, LADDER[idx]);
|
||||
return;
|
||||
}
|
||||
tm.updateTask(a2aId, target); // working/failed/cancelled are reachable from any non-terminal state
|
||||
} catch {
|
||||
// A transition rejected by the manager must never take the bridge down; the
|
||||
// hub replay will converge the mirror on the next connection.
|
||||
}
|
||||
}
|
||||
|
||||
function ensureMirrored(tm: A2ATaskManager, index: Map<string, string>, conductorId: string, payload: Record<string, unknown>): string {
|
||||
const known = index.get(conductorId);
|
||||
if (known && tm.getTask(known)) return known;
|
||||
const mode = typeof payload.mode === "string" ? payload.mode : "?";
|
||||
const task = tm.createTask({
|
||||
skill: "conductor",
|
||||
messages: [{ role: "user", content: `Conductor task ${conductorId} (${mode})` }],
|
||||
metadata: {
|
||||
conductor: {
|
||||
task_id: conductorId,
|
||||
mode,
|
||||
from: typeof payload.from === "string" ? payload.from : undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
index.set(conductorId, task.id);
|
||||
return task.id;
|
||||
}
|
||||
|
||||
function conductorMeta(tm: A2ATaskManager, a2aId: string): Record<string, unknown> | null {
|
||||
const task = tm.getTask(a2aId);
|
||||
if (!task) return null;
|
||||
// The manager has no public metadata mutator; direct mutation is the repo precedent
|
||||
// (tests mutate task fields) and the object lives in the same process.
|
||||
const meta = (task.metadata.conductor ??= {}) as Record<string, unknown>;
|
||||
return meta;
|
||||
}
|
||||
|
||||
/** Applies one hub event to the mirror. Never throws for event-shaped input. */
|
||||
export function applyConductorEvent(tm: A2ATaskManager, index: Map<string, string>, ev: ConductorEvent): void {
|
||||
const target = conductorStateFor(ev.type);
|
||||
if (!target) return;
|
||||
const conductorId = typeof ev.payload.task_id === "string" ? ev.payload.task_id : null;
|
||||
if (!conductorId) return;
|
||||
|
||||
const a2aId = ensureMirrored(tm, index, conductorId, ev.payload);
|
||||
const meta = conductorMeta(tm, a2aId);
|
||||
if (meta) {
|
||||
if (ev.type === "task.scheduled" && typeof ev.payload.runner_id === "string") meta.runner = ev.payload.runner_id;
|
||||
if (ev.type === "task.input_required") meta.input_required = true;
|
||||
if (ev.type === "task.completed" || ev.type === "task.failed") {
|
||||
const manifest = (ev.payload.manifest ?? {}) as Record<string, unknown>;
|
||||
if (typeof manifest.summary === "string") meta.summary = manifest.summary;
|
||||
if (typeof manifest.branch === "string") meta.branch = manifest.branch;
|
||||
if (typeof manifest.error === "string") meta.error = manifest.error;
|
||||
}
|
||||
}
|
||||
if (target !== "submitted") climb(tm, a2aId, target);
|
||||
}
|
||||
|
||||
// ============ Incremental SSE parser ============
|
||||
|
||||
export interface SseFrame {
|
||||
id?: string;
|
||||
event?: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
/** Incremental `text/event-stream` parser: feed chunks, get complete frames. Comments (`: ping`) are dropped. */
|
||||
export class SseParser {
|
||||
private buffer = "";
|
||||
|
||||
push(chunk: string): SseFrame[] {
|
||||
this.buffer += chunk;
|
||||
const frames: SseFrame[] = [];
|
||||
let cut: number;
|
||||
while ((cut = this.buffer.indexOf("\n\n")) !== -1) {
|
||||
const block = this.buffer.slice(0, cut);
|
||||
this.buffer = this.buffer.slice(cut + 2);
|
||||
const frame: SseFrame = { data: "" };
|
||||
const dataLines: string[] = [];
|
||||
for (const line of block.split("\n")) {
|
||||
if (line.startsWith(":")) continue; // comment/keepalive
|
||||
if (line.startsWith("id:")) frame.id = line.slice(3).trim();
|
||||
else if (line.startsWith("event:")) frame.event = line.slice(6).trim();
|
||||
else if (line.startsWith("data:")) dataLines.push(line.slice(5).trimStart());
|
||||
}
|
||||
if (frame.id === undefined && frame.event === undefined && dataLines.length === 0) continue; // pure comment block
|
||||
frame.data = dataLines.join("\n");
|
||||
frames.push(frame);
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Connection loop ============
|
||||
|
||||
/** Wire shape of the hub's SSE `data:` field — {ts, payload}; id/type live in the
|
||||
* SSE frame fields (verified against the live hub, 2026-07-22). Untrusted input → Zod. */
|
||||
const hubDataSchema = z.object({
|
||||
payload: z.record(z.string(), z.unknown()),
|
||||
});
|
||||
|
||||
export interface ConductorBridgeOptions {
|
||||
hubUrl: string;
|
||||
token: string;
|
||||
tm: A2ATaskManager;
|
||||
cursor: { get(): string | null; set(v: string): void };
|
||||
fetchImpl?: typeof fetch;
|
||||
log?: (msg: string) => void;
|
||||
backoffBaseMs?: number;
|
||||
}
|
||||
|
||||
export interface ConductorBridge {
|
||||
start(): void;
|
||||
stop(): void;
|
||||
state(): "connected" | "reconnecting" | "stopped";
|
||||
}
|
||||
|
||||
const BACKOFF_CAP_MS = 30_000;
|
||||
|
||||
/**
|
||||
* Long-lived consumer: connects to the hub SSE with the persisted cursor, mirrors
|
||||
* events, reconnects with exponential backoff. A hub outage never propagates —
|
||||
* errors are logged and retried; `stop()` aborts for good.
|
||||
*/
|
||||
export function createConductorBridge(opts: ConductorBridgeOptions): ConductorBridge {
|
||||
const log = opts.log ?? ((msg: string) => console.log(`[conductor-bridge] ${msg}`));
|
||||
const doFetch = opts.fetchImpl ?? fetch;
|
||||
const backoffBase = opts.backoffBaseMs ?? 1_000;
|
||||
const index = new Map<string, string>();
|
||||
let state: "connected" | "reconnecting" | "stopped" = "stopped";
|
||||
let abort: AbortController | null = null;
|
||||
let attempt = 0;
|
||||
let running = false;
|
||||
|
||||
async function readStream(res: Response): Promise<void> {
|
||||
const reader = res.body?.getReader();
|
||||
if (!reader) throw new Error("SSE response without body");
|
||||
const parser = new SseParser();
|
||||
const decoder = new TextDecoder();
|
||||
for (;;) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) return;
|
||||
for (const frame of parser.push(decoder.decode(value, { stream: true }))) {
|
||||
if (!frame.id || !frame.event) continue; // keepalive/partial frame — nada a espelhar
|
||||
let payload: Record<string, unknown>;
|
||||
try {
|
||||
payload = hubDataSchema.parse(JSON.parse(frame.data)).payload;
|
||||
} catch {
|
||||
log(`evento SSE malformado ignorado (id=${frame.id})`);
|
||||
continue;
|
||||
}
|
||||
applyConductorEvent(opts.tm, index, { id: frame.id, type: frame.event, payload });
|
||||
opts.cursor.set(frame.id); // persisted per event — replay covers any gap on reconnect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loop(): Promise<void> {
|
||||
while (running) {
|
||||
abort = new AbortController();
|
||||
try {
|
||||
const since = opts.cursor.get() ?? "0";
|
||||
const res = await doFetch(`${opts.hubUrl}/v1/events?last_event_id=${encodeURIComponent(since)}`, {
|
||||
headers: { authorization: `Bearer ${opts.token}`, accept: "text/event-stream" },
|
||||
signal: abort.signal,
|
||||
});
|
||||
if (!res.ok) throw new Error(`hub respondeu HTTP ${res.status}`);
|
||||
state = "connected";
|
||||
attempt = 0;
|
||||
log(`conectado ao hub (last_event_id=${since})`);
|
||||
await readStream(res); // resolves when the hub closes the stream
|
||||
throw new Error("stream encerrado pelo hub");
|
||||
} catch (err) {
|
||||
if (!running) break;
|
||||
state = "reconnecting";
|
||||
const wait = Math.min(backoffBase * 2 ** attempt, BACKOFF_CAP_MS);
|
||||
attempt++;
|
||||
log(`conexão caiu (${err instanceof Error ? err.message : String(err)}) — reconectando em ${wait}ms`);
|
||||
await new Promise((resolve) => setTimeout(resolve, wait));
|
||||
}
|
||||
}
|
||||
state = "stopped";
|
||||
}
|
||||
|
||||
return {
|
||||
start() {
|
||||
if (running) return;
|
||||
running = true;
|
||||
void loop();
|
||||
},
|
||||
stop() {
|
||||
running = false;
|
||||
state = "stopped";
|
||||
abort?.abort();
|
||||
},
|
||||
state: () => state,
|
||||
};
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/**
|
||||
* Fleet skills for the Agent Card (Conductor PRD RF2) — derives A2A skills from
|
||||
* the OmniConductor hub's runner registry (`GET /v1/runners`, OASF capabilities).
|
||||
*
|
||||
* Fail-open by design: any problem (env unset, hub offline, bad shape) yields
|
||||
* `[]` so the Agent Card stays valid, just without the fleet section. Results
|
||||
* are cached for ~60s to keep the card endpoint cheap.
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
export interface FleetSkill {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
/** Untrusted hub response — validate only what we read. */
|
||||
const runnersSchema = z.array(
|
||||
z.object({
|
||||
online: z.boolean().optional(),
|
||||
capabilities: z.object({
|
||||
clis: z
|
||||
.array(
|
||||
z.object({
|
||||
profile: z.string(),
|
||||
models: z.array(z.object({ id: z.string() })).optional(),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
skills: z.array(z.string()).optional(),
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const CACHE_TTL_MS = 60_000;
|
||||
let cache: { at: number; skills: FleetSkill[] } | null = null;
|
||||
|
||||
/** Test hook: resets the module cache. */
|
||||
export function clearFleetSkillsCache(): void {
|
||||
cache = null;
|
||||
}
|
||||
|
||||
export interface FleetSkillsOptions {
|
||||
fetchImpl?: typeof fetch;
|
||||
nowMs?: () => number;
|
||||
}
|
||||
|
||||
export async function getFleetSkills(opts: FleetSkillsOptions = {}): Promise<FleetSkill[]> {
|
||||
const hubUrl = process.env.CONDUCTOR_HUB_URL?.trim();
|
||||
if (!hubUrl) return [];
|
||||
const now = opts.nowMs ?? Date.now;
|
||||
if (cache && now() - cache.at < CACHE_TTL_MS) return cache.skills;
|
||||
|
||||
const doFetch = opts.fetchImpl ?? fetch;
|
||||
let skills: FleetSkill[] = [];
|
||||
try {
|
||||
const res = await doFetch(`${hubUrl}/v1/runners`, {
|
||||
headers: { authorization: `Bearer ${process.env.CONDUCTOR_HUB_TOKEN?.trim() ?? ""}` },
|
||||
});
|
||||
if (res.ok) skills = deriveSkills(runnersSchema.parse(await res.json()));
|
||||
} catch {
|
||||
skills = []; // hub offline / shape inválido: o card omite a frota, nunca quebra
|
||||
}
|
||||
cache = { at: now(), skills };
|
||||
return skills;
|
||||
}
|
||||
|
||||
function deriveSkills(runners: z.infer<typeof runnersSchema>): FleetSkill[] {
|
||||
const online = runners.filter((r) => r.online !== false);
|
||||
const byProfile = new Map<string, { count: number; models: Set<string> }>();
|
||||
const oasfSkills = new Set<string>();
|
||||
for (const r of online) {
|
||||
for (const cli of r.capabilities.clis ?? []) {
|
||||
const entry = byProfile.get(cli.profile) ?? { count: 0, models: new Set<string>() };
|
||||
entry.count++;
|
||||
for (const m of cli.models ?? []) entry.models.add(m.id);
|
||||
byProfile.set(cli.profile, entry);
|
||||
}
|
||||
for (const s of r.capabilities.skills ?? []) oasfSkills.add(s);
|
||||
}
|
||||
|
||||
const skills: FleetSkill[] = [];
|
||||
for (const [profile, info] of [...byProfile.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
||||
const models = [...info.models].slice(0, 8);
|
||||
skills.push({
|
||||
id: `conductor-cli-${profile}`,
|
||||
name: `Conductor fleet: ${profile} CLI`,
|
||||
description:
|
||||
`Delegate coding tasks to the OmniConductor fleet's ${profile} CLI ` +
|
||||
`(${info.count} runner(s) online${models.length ? `; models: ${models.join(", ")}` : ""}). ` +
|
||||
"Tasks run in disposable git worktrees; results come back as branches with graduated manifests.",
|
||||
tags: ["conductor", "fleet", "cli", profile],
|
||||
});
|
||||
}
|
||||
for (const s of [...oasfSkills].sort()) {
|
||||
skills.push({
|
||||
id: `conductor-skill-${s}`,
|
||||
name: `Conductor fleet skill: ${s}`,
|
||||
description: `OASF skill "${s}" declared by online runners of the OmniConductor fleet.`,
|
||||
tags: ["conductor", "fleet", "skill"],
|
||||
});
|
||||
}
|
||||
return skills;
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
/**
|
||||
* Server-side proxy to the OmniConductor hub (Conductor PRD RF3).
|
||||
*
|
||||
* The browser NEVER talks to the hub: these helpers run only in API routes,
|
||||
* authenticate with the server-side env token, and return WHITELISTED shapes —
|
||||
* runner/hub tokens can never leak to the client. Fail-open: hub unset/offline
|
||||
* yields a degraded snapshot ({offline: true}) instead of an error.
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
// ============ Whitelisted client-facing shapes ============
|
||||
|
||||
export interface FleetRunner {
|
||||
id: string;
|
||||
name: string;
|
||||
clis: string[];
|
||||
online: boolean;
|
||||
draining: boolean;
|
||||
}
|
||||
|
||||
export interface FleetTask {
|
||||
id: string;
|
||||
status: string;
|
||||
mode: string;
|
||||
repo: string | null;
|
||||
runner: string | null;
|
||||
summary: string | null;
|
||||
branch: string | null;
|
||||
error: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface FleetSnapshot {
|
||||
offline: boolean;
|
||||
runners: FleetRunner[];
|
||||
tasks: FleetTask[];
|
||||
}
|
||||
|
||||
export interface ConductorTaskDetail extends FleetTask {
|
||||
prompt: string | null;
|
||||
base_ref: string | null;
|
||||
tests: unknown;
|
||||
council: unknown;
|
||||
created_at: string | null;
|
||||
}
|
||||
|
||||
// ============ Untrusted hub shapes (parse only what we read) ============
|
||||
|
||||
const hubRunnerSchema = z.object({
|
||||
id: z.string(),
|
||||
online: z.boolean().optional(),
|
||||
draining: z.boolean().optional(),
|
||||
capabilities: z.object({
|
||||
name: z.string().optional(),
|
||||
clis: z.array(z.object({ profile: z.string() })).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const hubTaskSchema = z.object({
|
||||
id: z.string(),
|
||||
status: z.string(),
|
||||
mode: z.string().optional(),
|
||||
repo: z.object({ url: z.string().optional(), base_ref: z.string().optional() }).nullish(),
|
||||
spec: z.object({ prompt: z.string().optional() }).nullish(),
|
||||
assigned_runner: z.string().nullish(),
|
||||
manifest: z
|
||||
.object({
|
||||
summary: z.string().nullish(),
|
||||
branch: z.string().nullish(),
|
||||
error: z.string().nullish(),
|
||||
tests: z.unknown().optional(),
|
||||
})
|
||||
.nullish(),
|
||||
council: z.unknown().optional(),
|
||||
created_at: z.string().optional(),
|
||||
updated_at: z.string().optional(),
|
||||
});
|
||||
|
||||
export interface HubProxyOptions {
|
||||
fetchImpl?: typeof fetch;
|
||||
}
|
||||
|
||||
function hubConfig(): { url: string; token: string } | null {
|
||||
const url = process.env.CONDUCTOR_HUB_URL?.trim();
|
||||
if (!url) return null;
|
||||
return { url, token: process.env.CONDUCTOR_HUB_TOKEN?.trim() ?? "" };
|
||||
}
|
||||
|
||||
async function hubGet(path: string, opts: HubProxyOptions): Promise<unknown | null> {
|
||||
const cfg = hubConfig();
|
||||
if (!cfg) return null;
|
||||
const doFetch = opts.fetchImpl ?? fetch;
|
||||
const res = await doFetch(`${cfg.url}${path}`, {
|
||||
headers: { authorization: `Bearer ${cfg.token}` },
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
return res.json();
|
||||
}
|
||||
|
||||
function toFleetTask(t: z.infer<typeof hubTaskSchema>): FleetTask {
|
||||
return {
|
||||
id: t.id,
|
||||
status: t.status,
|
||||
mode: t.mode ?? "solo",
|
||||
repo: t.repo?.url ?? null,
|
||||
runner: t.assigned_runner ?? null,
|
||||
summary: t.manifest?.summary ?? null,
|
||||
branch: t.manifest?.branch ?? null,
|
||||
error: t.manifest?.error ?? null,
|
||||
updated_at: t.updated_at ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/** Fleet snapshot for the dashboard panel. Degraded ({offline: true}) on any failure. */
|
||||
export async function getFleetSnapshot(opts: HubProxyOptions = {}): Promise<FleetSnapshot> {
|
||||
try {
|
||||
const [rawRunners, rawTasks] = await Promise.all([
|
||||
hubGet("/v1/runners", opts),
|
||||
hubGet("/v1/tasks", opts),
|
||||
]);
|
||||
if (rawRunners === null || rawTasks === null) return { offline: true, runners: [], tasks: [] };
|
||||
const runners = z.array(hubRunnerSchema).parse(rawRunners).map((r) => ({
|
||||
id: r.id,
|
||||
name: r.capabilities.name ?? "?",
|
||||
clis: (r.capabilities.clis ?? []).map((c) => c.profile),
|
||||
online: r.online !== false,
|
||||
draining: r.draining === true,
|
||||
}));
|
||||
const tasks = z.array(hubTaskSchema).parse(rawTasks).map(toFleetTask);
|
||||
return { offline: false, runners, tasks };
|
||||
} catch {
|
||||
return { offline: true, runners: [], tasks: [] };
|
||||
}
|
||||
}
|
||||
|
||||
/** Full whitelisted detail of one task (manifest, prompt, council funnel data). */
|
||||
export async function getConductorTaskDetail(
|
||||
taskId: string,
|
||||
opts: HubProxyOptions = {}
|
||||
): Promise<ConductorTaskDetail | null> {
|
||||
try {
|
||||
const raw = await hubGet(`/v1/tasks/${encodeURIComponent(taskId)}`, opts);
|
||||
if (raw === null) return null;
|
||||
const t = hubTaskSchema.parse(raw);
|
||||
return {
|
||||
...toFleetTask(t),
|
||||
prompt: t.spec?.prompt ?? null,
|
||||
base_ref: t.repo?.base_ref ?? null,
|
||||
tests: t.manifest?.tests ?? null,
|
||||
council: t.council ?? null,
|
||||
created_at: t.created_at ?? null,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Cancels a task on the hub. Returns the hub's verdict without leaking its body on error. */
|
||||
export async function cancelConductorTask(
|
||||
taskId: string,
|
||||
opts: HubProxyOptions = {}
|
||||
): Promise<{ ok: boolean; status: number }> {
|
||||
const cfg = hubConfig();
|
||||
if (!cfg) return { ok: false, status: 503 };
|
||||
try {
|
||||
const doFetch = opts.fetchImpl ?? fetch;
|
||||
const res = await doFetch(`${cfg.url}/v1/tasks/${encodeURIComponent(taskId)}/cancel`, {
|
||||
method: "POST",
|
||||
headers: { authorization: `Bearer ${cfg.token}` },
|
||||
});
|
||||
return { ok: res.ok, status: res.status };
|
||||
} catch {
|
||||
return { ok: false, status: 503 };
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* Conductor bridge persistence — SSE cursor (`last_event_id`) for the hub mirror.
|
||||
*
|
||||
* Uses the existing `key_value` table under a dedicated namespace (no migration
|
||||
* needed — same pattern as settings.ts). The cursor lets the bridge resume the
|
||||
* hub SSE from where it stopped; the hub replay covers the gap.
|
||||
*/
|
||||
|
||||
import { getDbInstance } from "./core";
|
||||
|
||||
const NAMESPACE = "conductor";
|
||||
const CURSOR_KEY = "last_event_id";
|
||||
|
||||
export function getConductorCursor(): string | null {
|
||||
const db = getDbInstance();
|
||||
const row = db
|
||||
.prepare("SELECT value FROM key_value WHERE namespace = ? AND key = ?")
|
||||
.get(NAMESPACE, CURSOR_KEY) as { value: string } | undefined;
|
||||
if (!row) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(row.value);
|
||||
return typeof parsed === "string" ? parsed : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function setConductorCursor(value: string): void {
|
||||
const db = getDbInstance();
|
||||
db.prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)").run(
|
||||
NAMESPACE,
|
||||
CURSOR_KEY,
|
||||
JSON.stringify(value)
|
||||
);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
-- Migration 139: generic job registry (jobs + job_runs tables)
|
||||
-- Job registry (#8848): centralized periodic-job scheduling + run history.
|
||||
--
|
||||
-- jobs: one row per registered job (interval or cron), with env-flag gating
|
||||
-- job_runs: one row per execution (running/success/failure), pruned by count + age
|
||||
|
||||
CREATE TABLE IF NOT EXISTS jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
type TEXT NOT NULL DEFAULT 'interval' CHECK(type IN ('interval', 'cron')),
|
||||
cron TEXT, -- cron expression (type='cron'); NULL for interval jobs
|
||||
interval_ms INTEGER, -- interval in ms (type='interval'); NULL for cron jobs
|
||||
enabled INTEGER NOT NULL DEFAULT 1, -- 0=disabled, 1=enabled
|
||||
env_flag TEXT, -- env var name (boolean gate), e.g. 'OMNIROUTE_WARMUP_ENABLED'; NULL = no gate
|
||||
config TEXT NOT NULL DEFAULT '{}', -- JSON config (concurrency, timezone, envDefault, ...)
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS job_runs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
||||
started_at TEXT NOT NULL, -- ISO-8601, written explicitly by recordRun (no DB default)
|
||||
finished_at TEXT, -- ISO-8601; NULL while running
|
||||
status TEXT NOT NULL DEFAULT 'running', -- 'running' | 'success' | 'failure'
|
||||
error_message TEXT, -- sanitized error message (no stack trace)
|
||||
records_affected INTEGER DEFAULT 0, -- job-specific meaning (see below)
|
||||
duration_ms INTEGER, -- execution duration in ms
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_jr_job_id ON job_runs(job_id, started_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_jr_started_at ON job_runs(started_at);
|
||||
|
||||
-- Built-in job registration (idempotent - INSERT OR IGNORE).
|
||||
-- env_flag = NULL means "no registry-level boolean gate"; per-job disable semantics
|
||||
-- live inside the handler itself (see token_health_check wrapper).
|
||||
-- 'warmup' is seeded disabled because its handler is not part of this change.
|
||||
-- startAll() filters on enabled before it looks for a handler, so a disabled row
|
||||
-- stays quiet instead of warning on every boot; the change that brings the warmup
|
||||
-- handler flips it on.
|
||||
INSERT OR IGNORE INTO jobs (id, type, cron, interval_ms, enabled, env_flag, config) VALUES
|
||||
('budget_reset', 'interval', NULL, 600000, 1, NULL, '{}'),
|
||||
('warmup', 'cron', '0 7 * * *', NULL, 0, 'OMNIROUTE_WARMUP_ENABLED', '{"timezone":"America/Los_Angeles","envDefault":false}'),
|
||||
('token_health_check', 'interval', NULL, 60000, 1, NULL, '{}');
|
||||
|
||||
-- records_affected semantics:
|
||||
-- budget_reset = number of budget records reset (UPDATE ... SET budget_used=0 row count)
|
||||
-- warmup = number of connections attempted for warmup
|
||||
-- token_health_check = number of connections swept by the health check
|
||||
@@ -76,7 +76,7 @@ export function getModelCapabilityOverride(
|
||||
if (!target || !isSupportedKey(key)) return null;
|
||||
|
||||
if (bulkMaxTokenOverrides) {
|
||||
if (key !== "max_token") return null;
|
||||
if (key !== "max_output_tokens") return null;
|
||||
return bulkMaxTokenOverrides.get(target.provider)?.get(target.modelId) ?? null;
|
||||
}
|
||||
|
||||
|
||||
2
src/lib/env/runtimeEnv.ts
vendored
2
src/lib/env/runtimeEnv.ts
vendored
@@ -69,8 +69,6 @@ export const webRuntimeEnvSchema = z.object({
|
||||
OMNIROUTE_BASE_URL: optionalHttpUrl,
|
||||
BASE_URL: optionalHttpUrl,
|
||||
NEXT_PUBLIC_BASE_URL: optionalHttpUrl,
|
||||
CONDUCTOR_HUB_URL: optionalHttpUrl,
|
||||
CONDUCTOR_HUB_TOKEN: optionalTrimmedString,
|
||||
OMNIROUTE_PORT: optionalPortEnv,
|
||||
API_PORT: optionalPortEnv,
|
||||
DASHBOARD_PORT: optionalPortEnv,
|
||||
|
||||
@@ -820,4 +820,3 @@ export {
|
||||
setRadarKey,
|
||||
} from "./db/radar";
|
||||
export type { RadarCache, RadarSettings } from "./db/radar";
|
||||
export * from "./db/conductorBridge"; // OmniConductor hub mirror — SSE cursor (PRD Conductor RF1)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user