Files
OmniRoute/src/instrumentation-node.ts
Brandon Bennett 6615a5445b feat: combo-lane awareness + activation UX + MCP visibility (Wave 2 of #9654) (#10039)
* feat(admission): per-target lane-aware probes for combo/fusion fan-out (#9654 Wave 2)

Combo and fusion fan out N targets without ever consulting the adaptive-admission
layer: the parent request holds one lease, but each fan-out target is dispatched
unconditionally. With virtual lanes enabled (OMNIROUTE_CHAT_VIRTUAL_LANES=1), a
connection whose lane queue is full now SKIPS additional fan-out targets instead
of piling more queued work onto an already-congested session.

Adds PerTargetAdmissionHook (admission/types.ts) + createPerTargetAdmissionHook
factory (chatAdmission.ts): strictly non-blocking (maxWaitMs 0 - skip, never
queue), a no-op when virtual lanes are off, keyed to the parent tenantKey, and
release-on-admit so the probe is a capacity gate, not a hold.

Threaded through every parallel fan-out path:
- priority/weighted executeTarget + round-robin skip chains (combo.ts)
- fusion panel before fan-out (fusion.ts), judge fallback prefers survivors
- chaos parallel panel (autoCombo/chaosEngine.ts)
- tryFusionDispatch / tryRuntimeUnitDispatch / buildBaseOptions (dispatchPrelude.ts)
- chat.ts primary + safety-net redirect call sites

Snapshot exposes virtualLanes so the no-op gate is cheap and honest.

Tests: tests/unit/combo-lane-awareness-9654.test.ts (10 tests) - factory
semantics, priority/RR skip, fusion panel drop + all-skipped 503, no-hook
backward-compat baseline.

* feat(flags): activation UX - env-wins adaptive virtual-lanes flag + env docs (#9654 Wave 2)

U7: make adaptive virtual admission lanes discoverable + activatable.
- New OMNIROUTE_CHAT_VIRTUAL_LANES feature flag (boolean/runtime/requiresRestart) in featureFlagDefinitions + en.json i18n key.
- lib/admissionVirtualLanes.ts: env-wins resolver (env > DB > default) + boot warm folding a DB-sourced override into the process-global runtime env via reloadAdaptiveAdmissionRuntime(options.env) - no process.env mutation, no open-sse changes. Env still wins; DB toggle gates at next boot.
- GET /api/settings/feature-flags special-cases the flag to report the gate true source (ccDiscoveryAliases precedent); flagPayload helper dedupes the payload shape.
- Wire the warm into instrumentation-node registerNodejs (non-fatal, DB-ready).
- Document the master switch in .env.example + ENVIRONMENT.md with the system-1/system-2 distinction; zero new env-doc-sync drift.
- 11 new tests (resolver precedence + warm); 60/60 across feature-flag suites; typecheck core clean; ESLint + doc gates green.

* feat(mcp): surface adaptive admission lane data in omniroute_get_health (#9654 Wave 2)

U8: make adaptive virtual-lane admission visible to agents via the MCP health tool. handleGetHealth now surfaces a curated adaptiveAdmission block from the health payload (which already carried the runtime snapshot but was dropping it): virtualLanes/pressure/utilization/laneCount/laneQueuedCount/laneQueuedCost, laneTenants capped at top-10 by queued cost, admitted/rejected/wouldReject counts, shutdown. Block omitted entirely when the health endpoint reports none.

isLaneFlagOn mirrors the runtime 1|true convention so a string serialization can never invert a boolean lane report. getHealthOutput schema extended with the matching optional shape; tool description updated.

4 new dispatch tests (full block, top-10 cap/order, omission, defensive coercion of string flags + malformed lane entries) - 22/22 in essentialTools.test.ts. README: Adaptive Admission Lane Data table + Skills & Tool Navigability audit (29/43 schema entries covered, 14 undocumented, tool_search keyword runtime discovery, full catalog in docs/frameworks/MCP-SERVER.md).

No new lint errors (4 pre-existing in server.ts), typecheck core clean, doc counts + fabricated-docs gates green.

* docs: add changelog entry for #9654 Wave 2 (#10039)

* fix(codeql): suppress js/insufficient-password-hash false positive in lane-key fingerprinting (#10039)

resolveSessionId sha256-hashes bearer/x-api-key/x-goog-api-key to derive a deterministic, non-reversible per-key lane-bucket ID for virtual admission lanes (#9654). This is not password storage or verification, so the rule is a false positive; suppress it inline (same house style as src/lib/sync/tokens.ts) to clear the codeqlAlerts ratchet (2 > baseline 1) that blocks #10039 and every PR against release/v3.8.50.

* docs(mcp): complete MCP server README tool reference (#10039)

The MCP server README covered only 29 of the 43 schema entries, listing the
remaining tools solely as a gap note with omniroute_tool_search as the runtime
fallback. Add tool-reference tables for the agent-skills trio, oneproxy trio,
web_fetch/web_search, tool_search, create_combo, set_routing_strategy,
pick_fastest_model, sync_pricing, and db_health_check so the README covers the
full schemas catalog, and fold the coverage note into the tool_search discovery
paragraph.

* fix(chat): drop unused correlationId from safety-net combo redirect (#10039)

handleComboChat's HandleComboChatOptions has no correlationId member and
the combo pipeline never consumes it; the property was copied from the
handleSingleModelChat options shape by accident and introduced a new
TS2353 under the open-sse workspace typecheck gate.

* fix(i18n): translate featureFlagChatVirtualLanesEnabledDescription into 42 locales (#10039)

en.json gained the flag description in this PR but the locale catalogs
were never mirrored, failing the pt-BR key-parity (#6695) and vi
completeness gates. Adds a real translation to every locale, keeping the
zh-CN/zh-TW glossary canonical terms (提供者/儀表板) and no ICU drift.

* chore(quality): ratchet open-sse-typecheck baseline down (#10039)

The Wave 2 admission refactor removed 66 baselined open-sse type errors;
re-freeze the baseline so the gate pins the new, tighter state.

* docs: resync provider reference to 341 and CLI tools to 34

The release branch gained an 11th no-auth provider (freeaiapikey registry
resync, #10233) and a 26th CLI Code tool without regenerating the
auto-generated docs, leaving every PR against release/v3.8.50 failing the
Docs Gates strict validator (code 341 vs doc 340, CLI 34 vs "33 tools").

Regenerate docs/reference/PROVIDER_REFERENCE.md and sync the provider/tool
counts across README.md, AGENTS.md, llm.txt plus 42 i18n mirrors,
package.json description, and the four diagram SVGs.

* fix(tests): align count expectations with live catalogs (pre-existing release drift)

Release/v3.8.50 currently fails five gates on its own tree; this PR inherits
them. Fix the stale expectations to match live code:

- feature-flags-settings: 48 -> 49 flags (Wave 2 adds OMNIROUTE_CHAT_VIRTUAL_LANES)
- cli-tools-schema / cli-catalog-counts: 33 -> 34 tools (zcode added; 26 code = 21 visible + 5 none)
- optional-transformers-dependency: onnxruntime-node ~1.24.3 -> ~1.27.0 (bump #10382)
- stryker.conf.json: register chatcore-header-drop-warn-dedupe-10315 test
- check-public-creds: freeze zcodeProtocol clientId false positive (client identifier, not a credential)

* fix(tests): follow release's onnxruntime-node revert to ~1.24.3

release/v3.8.50's #10543 pinned onnxruntime-node back to ~1.24.3 after
#10403's ~1.27.0 bump caused npm to nest a second native copy under
@huggingface/transformers and broke the Docker SONAME contract. This
PR's own drift-alignment commit (57b9c033) predates that revert and
still expected ~1.27.0; the 3-way merge did not flag it as a textual
conflict since only one side touched this exact line, but the merged
tree became internally inconsistent (package.json ~1.24.3 vs test
expecting ~1.27.0). Align the test with the now-canonical release
value.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(quality): dedupe stryker.conf.json chatcore-header-drop-warn-dedupe entry

The 3-way merge applied both sides' insertion of the same test-file entry
at different positions, producing a duplicate with broken indentation.
Adopted release's clean version of the file.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Brandon Bennett <brandonbennett@macbookair.myfiosgateway.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: Brandon Bennett <branben@users.noreply.github.com>
2026-08-18 11:31:46 -03:00

695 lines
31 KiB
TypeScript
Executable File

/**
* Node.js-only instrumentation logic.
*
* Separated from instrumentation.ts so that Turbopack's Edge bundler
* does not trace into Node.js-only modules (fs, path, os, better-sqlite3, etc.)
* and emit spurious "not supported in Edge Runtime" warnings.
*/
import { markServerReady, markServerStarting } from "@/lib/serverLifecycle";
import { normalizeBootError } from "@/lib/instrumentationBootError";
function getRandomBytes(byteLength: number): Uint8Array {
const bytes = new Uint8Array(byteLength);
globalThis.crypto.getRandomValues(bytes);
return bytes;
}
function toBase64(bytes: Uint8Array): string {
return btoa(String.fromCodePoint(...bytes));
}
function toHex(bytes: Uint8Array): string {
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
}
/**
* Rename a Node process title so OmniRoute is identifiable in `ps`/`htop`
* instead of the generic Next.js standalone server name.
*
* Only rewrites titles that start with "next-server", preserving any
* trailing suffix (e.g. " (v16.2.9)"). Every other title — including one
* that has already been renamed, or one that merely contains
* "next-server" elsewhere — passes through unchanged. Empty/undefined-safe.
*/
export function renameProcessTitle(currentTitle: string): string {
if (!currentTitle) return currentTitle;
if (!currentTitle.startsWith("next-server")) return currentTitle;
return `omniroute${currentTitle.slice("next-server".length)}`;
}
// `normalizeBootError` now lives in `@/lib/instrumentationBootError` (imported
// above) — shared, dependency-free, and reused by `src/instrumentation.ts`'s
// outermost boot boundary (#10171) so both boot-failure logging sites agree
// on the same normalization instead of maintaining two copies of the same
// one-liner. Re-exported here so existing callers/tests importing it from
// this module keep working unchanged.
export { normalizeBootError };
// Matches sql.js's raw `throw "Database closed"` (and similarly-worded
// variants) thrown when a query runs against an already-closed WASM handle —
// typically a stale globalThis-cached adapter left over by a prior
// close/reload racing with this boot (#6560).
const TRANSIENT_DB_CLOSED_RE = /database\s*(connection\s*)?(is\s*)?closed/i;
/**
* Initialize the SQLite singleton for boot, tolerating one transient
* "database closed" failure (#6560) by retrying once — the driverFactory
* cache-eviction fix (`preInitSqlJs`) makes the retry create a fresh adapter
* instead of reusing the dead one. Any other failure (or a second consecutive
* "database closed") is re-thrown as a real `Error` via `normalizeBootError`
* so it can never crash instrumentation with a masking TypeError — the caller
* (`registerNodejs`) still surfaces it as a real boot failure.
*
* `ensureDbInitializedFn` is only for tests to inject a fake without
* module-mocking (`node:test` does not support `mock.module` reliably here).
*/
export async function ensureDbReadyForBoot(
ensureDbInitializedFn?: () => Promise<void>
): Promise<void> {
const ensureDbInitialized =
ensureDbInitializedFn ?? (await import("@/lib/db/core")).ensureDbInitialized;
try {
await ensureDbInitialized();
} catch (err: unknown) {
const normalized = normalizeBootError(err);
if (!TRANSIENT_DB_CLOSED_RE.test(normalized.message)) {
// Fatal, non-transient boot-time DB init failure (e.g. the entire
// better-sqlite3 -> node:sqlite -> sql.js driver cascade failed, as on
// Termux/Android when no SQLite driver is usable). This runs BEFORE
// initConsoleInterceptor() is wired up, so this is the only chance to
// get the real root cause into stdout/app.log — without it, the
// process keeps its HTTP listener up while every DB-touching route
// 500s forever with a permanently empty log (#7773).
console.error("[STARTUP] Fatal: Database driver initialization failed:", normalized.message);
throw normalized;
}
console.warn(
"[STARTUP] Database was closed by a prior reload/shutdown — retrying with a fresh connection (#6560):",
normalized.message
);
try {
await ensureDbInitialized();
} catch (retryErr: unknown) {
const normalizedRetryErr = normalizeBootError(retryErr);
console.error(
"[STARTUP] Fatal: Database driver initialization failed after retry:",
normalizedRetryErr.message
);
throw normalizedRetryErr;
}
}
}
function isBackgroundServicesDisabled(): boolean {
const raw = process.env.OMNIROUTE_DISABLE_BACKGROUND_SERVICES;
if (!raw) return false;
return new Set(["1", "true", "yes", "on"]).has(raw.trim().toLowerCase());
}
async function ensureSecrets(): Promise<void> {
let getPersistedSecret = (_key: string): string | null => null;
let persistSecret = (_key: string, _value: string): void => {};
try {
({ getPersistedSecret, persistSecret } = await import("@/lib/db/secrets"));
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn(
"[STARTUP] Secret persistence unavailable; falling back to process-local secrets:",
msg
);
}
if (!process.env.JWT_SECRET || process.env.JWT_SECRET.trim() === "") {
const persisted = getPersistedSecret("jwtSecret");
if (persisted) {
process.env.JWT_SECRET = persisted;
console.log("[STARTUP] JWT_SECRET restored from persistent store");
} else {
const generated = toBase64(getRandomBytes(48));
process.env.JWT_SECRET = generated;
persistSecret("jwtSecret", generated);
console.log("[STARTUP] JWT_SECRET auto-generated and persisted (random 64-char secret)");
}
}
if (!process.env.API_KEY_SECRET || process.env.API_KEY_SECRET.trim() === "") {
const persisted = getPersistedSecret("apiKeySecret");
if (persisted) {
process.env.API_KEY_SECRET = persisted;
} else {
const generated = toHex(getRandomBytes(32));
process.env.API_KEY_SECRET = generated;
persistSecret("apiKeySecret", generated);
console.log(
"[STARTUP] API_KEY_SECRET auto-generated and persisted (random 64-char hex secret)"
);
}
}
}
/**
* Warm the model catalog's durable, apiKey-independent sub-caches at startup
* so real /v1/models traffic (any client, any API key) avoids paying their
* cold-build cost on first use. Fire-and-forget from the caller, non-fatal.
*
* getUnifiedModelsResponse()'s own top-level Response cache (`catalogCache`
* in catalog.ts) is keyed by prefix/isCodex/apiKey AND has only a 1.5s TTL
* (CATALOG_CACHE_TTL_MS — a burst-dedup window added for #6408 to coalesce
* concurrent SDK/dashboard requests, not a startup-warm cache). Warming that
* cache with an unauthenticated dummy request has essentially no lasting
* effect: real traffic almost never arrives within 1.5s of this warmup
* completing, regardless of whether its cache key happens to match a real
* client's apiKey. The one genuinely durable, apiKey-independent cost in the
* catalog build is getOpenRouterCatalog()'s 24h disk-cached network fetch
* (src/lib/catalog/openrouterCatalog.ts) — buildUnifiedModelsResponseCore()
* calls it unconditionally whenever an OpenRouter connection is configured,
* decoupled from the per-key Response cache entirely, so warming it directly
* here benefits every subsequent /v1/models request regardless of that
* request's own apiKey. Only fetched when an OpenRouter connection actually
* exists, so deployments that never use OpenRouter don't pay an unconditional
* third-party network call at every boot.
*
* Exported (rather than left inline in registerNodejs()) so it can be unit
* tested directly without exercising the rest of the startup sequence.
*/
export async function warmModelCatalogCache(): Promise<void> {
try {
const { getUnifiedModelsResponse } = await import("@/app/api/v1/models/catalog");
await getUnifiedModelsResponse(new Request("http://127.0.0.1/v1/models"));
console.log("[STARTUP] Model catalog cache warmed");
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Model catalog warmup failed (non-fatal):", msg);
}
try {
const [{ getProviderConnections }, { getOpenRouterCatalog }] = await Promise.all([
import("@/lib/db/providers"),
import("@/lib/catalog/openrouterCatalog"),
]);
const openrouterConnections = await getProviderConnections({
provider: "openrouter",
isActive: true,
});
if (openrouterConnections.length > 0) {
await getOpenRouterCatalog();
console.log("[STARTUP] OpenRouter model catalog cache warmed");
}
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] OpenRouter catalog warmup failed (non-fatal):", msg);
}
}
/**
* #8530: enumerate existing combos whose name shadows a real model id and
* log a startup warning. Never rejects/throws — #6940 documents a combo
* named after a bare model id as the supported mechanism for per-model
* provider fallback, so a collision here is expected in some deployments;
* this only gives operators who hit it accidentally a signal.
*
* Exported (rather than left inline in registerNodejs()) so it can be unit
* tested directly without exercising the rest of the startup sequence.
*/
export async function scanComboModelNameCollisionsAtBoot(): Promise<void> {
try {
const [{ getCombos }, { scanCombosForModelCollisions }] = await Promise.all([
import("@/lib/db/combos"),
import("@/lib/combos/modelNameCollision"),
]);
const collisions = scanCombosForModelCollisions(await getCombos());
if (collisions.length > 0) {
console.warn(
`[STARTUP] ${collisions.length} combo(s) share a name with a real model id (#8530) — ` +
"intentional per #6940 bare-model-id fallback, but confirm each is expected: " +
collisions.map((c) => `${c.comboName}${c.providerId}/${c.modelId}`).join(", ")
);
}
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Could not scan combos for model-name collisions (non-fatal):", msg);
}
}
/**
* #9654 U7: fold a dashboard DB toggle for the adaptive virtual-lanes flag into
* the process-global admission runtime's env at boot. Env-wins: no-op when the
* operator's OMNIROUTE_CHAT_VIRTUAL_LANES env var is set (the lazy runtime
* already reads process.env correctly). The runtime reads env only at
* construction, so this must run before the first request touches it — hence
* awaited here, after ensureDbReadyForBoot(). Non-fatal.
*
* Exported (rather than inline in registerNodejs()) so it can be unit tested
* directly without exercising the rest of the startup sequence.
*/
export async function warmAdaptiveVirtualLanesIntoRuntime(): Promise<void> {
try {
const { warmAdaptiveVirtualLanesIntoRuntime: warm } =
await import("@/lib/admissionVirtualLanes");
const materialized = await warm();
if (materialized) {
console.log(
"[STARTUP] Adaptive virtual lanes flag materialized from dashboard override (#9654)"
);
}
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Could not warm adaptive virtual lanes flag (non-fatal):", msg);
}
}
export async function registerNodejs(): Promise<void> {
markServerStarting();
// Rename the process title so OmniRoute is identifiable in ps/htop instead
// of the generic "next-server" standalone server name.
process.title = renameProcessTitle(process.title);
// Initialize proxy fetch patch FIRST (before any HTTP requests)
await import("@omniroute/open-sse/index.ts");
console.log("[STARTUP] Global fetch proxy patch initialized");
// Guarantee the SQLite singleton — including a sql.js WASM pre-init when
// both synchronous drivers (better-sqlite3, node:sqlite) are unavailable —
// is ready before ANY other startup step reaches getDbInstance(). This
// MUST run before ensureSecrets, clearStaleCrashCooldowns,
// getSettings, initAuditLog below: those all reach getDbInstance()
// transitively, and used to run ahead of this call (previously at the end
// of this function), throwing the misleading "sql.js WASM ainda não foi
// pré-inicializado" error for an existing DB file when both sync drivers
// failed (#7288 / #7494). ensureDbInitialized() itself is idempotent and
// caches the singleton, so every later getDbInstance() call below is a
// free no-op re-read of the same connection — no double-init cost.
await ensureDbReadyForBoot();
await ensureSecrets();
await Promise.all([
import("@/lib/env/runtimeEnv").then(({ enforceWebRuntimeEnv }) => enforceWebRuntimeEnv()),
import("@/lib/usage/migrations"),
import("@/lib/consoleInterceptor").then(({ initConsoleInterceptor }) =>
initConsoleInterceptor()
),
]);
// Clear stale transient connection cooldowns persisted from an unclean crash.
// A crash mid-burst can leave far-future `rate_limited_until` values in the DB
// that cause every connection to be skipped by getProviderCredentials(), making
// all subsequent requests time out at Bottleneck's maxWaitMs (120 s default).
// Terminal states (banned / expired / credits_exhausted) are intentionally kept.
// See: https://github.com/diegosouzapw/OmniRoute/issues/3625 (Part A)
try {
const { clearStaleCrashCooldowns } = await import("@/lib/db/providers");
const { cleared } = clearStaleCrashCooldowns();
if (cleared > 0) {
console.log(
`[STARTUP] Cleared ${cleared} stale transient connection cooldown(s) from prior crash (#3625)`
);
}
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Could not clear stale crash cooldowns (non-fatal):", msg);
}
await scanComboModelNameCollisionsAtBoot();
await warmAdaptiveVirtualLanesIntoRuntime();
const [
{ initGracefulShutdown },
{ initApiBridgeServer },
{ startBackgroundRefresh },
{ ensureCloudSyncInitialized },
{ startProviderLimitsSyncScheduler },
{ getSettings },
{ applyRuntimeSettings },
{ startRuntimeConfigHotReload },
{ startSpendBatchWriter },
{ startCleanupScheduler },
{ registerDefaultGuardrails },
{ ensurePersistentManagementPasswordHash },
{ skillExecutor },
{ registerBuiltinSkills },
] = await Promise.all([
import("@/lib/gracefulShutdown"),
import("@/lib/apiBridgeServer"),
import("@/domain/quotaCache"),
import("@/lib/initCloudSync"),
import("@/shared/services/providerLimitsSyncScheduler"),
import("@/lib/db/settings"),
import("@/lib/config/runtimeSettings"),
import("@/lib/config/hotReload"),
import("@/lib/spend/batchWriter"),
import("@/lib/db/cleanup"),
import("@/lib/guardrails"),
import("@/lib/auth/managementPassword"),
import("@/lib/skills/executor"),
import("@/lib/skills/builtins"),
]);
// Proxy health scheduler (auto-removes dead proxies on interval)
await import("@/lib/proxyHealth/scheduler");
// Free-proxy auto-sync scheduler (re-fetches free-proxy sources on interval, #7079)
await import("@/lib/freeProxyProviders/scheduler");
initGracefulShutdown();
initApiBridgeServer();
startSpendBatchWriter();
registerDefaultGuardrails();
registerBuiltinSkills(skillExecutor);
console.log("[STARTUP] Spend batch writer started");
console.log("[STARTUP] Guardrail registry initialized");
console.log("[STARTUP] Builtin skill handlers registered");
if (!isBackgroundServicesDisabled()) {
startBackgroundRefresh();
console.log("[STARTUP] Quota cache background refresh started");
startProviderLimitsSyncScheduler();
console.log("[STARTUP] Provider limits sync scheduler started");
const { startQuotaAutoPing } = await import("@/lib/services/quotaAutoPing");
startQuotaAutoPing();
console.log("[STARTUP] Quota auto-ping scheduler started (opt-in, no-op until enabled)");
const cloudSyncInitialized = await ensureCloudSyncInitialized();
console.log(
`[STARTUP] Cloud/model sync background bootstrap ${cloudSyncInitialized ? "initialized" : "skipped"}`
);
const { initBatchProcessor } = await import("@omniroute/open-sse/services/batchProcessor");
initBatchProcessor();
console.log("[STARTUP] Batch processor started");
}
try {
const [
{ migrateCodexConnectionDefaultsFromLegacySettings },
{ startSessionAccountAffinityCleanup },
{ seedDefaultModelAliases },
] = await Promise.all([
import("@/lib/providers/codexConnectionDefaults"),
import("@/lib/db/sessionAccountAffinity"),
import("@/lib/modelAliasSeed"),
]);
let settings = await getSettings();
const passwordState = await ensurePersistentManagementPasswordHash({
logger: console,
settings,
source: "startup",
});
settings = passwordState.settings;
const runtimeChanges = await applyRuntimeSettings(settings, { force: true, source: "startup" });
if (runtimeChanges.length > 0) {
console.log(
`[STARTUP] Runtime settings hydrated: ${runtimeChanges
.map((entry) => entry.section)
.join(", ")}`
);
}
// Restore Global System Prompt into in-memory config (#2468/#2470)
if (settings.systemPrompt) {
const { setSystemPromptConfig } =
await import("@omniroute/open-sse/services/systemPrompt.ts");
setSystemPromptConfig(settings.systemPrompt);
console.log("[STARTUP] Global System Prompt restored from settings");
}
// Restore the proxy-level Thinking-Budget config (#5312 RC-A). It lives in
// `settings.thinkingBudget` and is NOT covered by applyRuntimeSettings, so
// without this the dashboard mode (auto/custom/adaptive) silently reverts to
// the passthrough default on every restart. Previously this was only wired into
// the unused `server-init.ts`, so it never ran in production.
const { hydrateThinkingBudgetConfig } =
await import("@omniroute/open-sse/services/thinkingBudget.ts");
if (hydrateThinkingBudgetConfig(settings)) {
console.log("[STARTUP] Thinking-Budget config restored from settings");
}
// Restore the Task-Aware Smart Routing config (#8601). It lives in
// `settings.taskRouting` (written as a JSON string by PUT /api/settings/task-routing)
// and is NOT covered by applyRuntimeSettings, so without this the feature silently
// reverts to disabled + the default model map on every restart. Same shape as the
// Thinking-Budget restore above; must live here, not in the unused server-init.ts.
const { hydrateTaskRoutingConfig } =
await import("@omniroute/open-sse/services/taskAwareRouter.ts");
if (hydrateTaskRoutingConfig(settings)) {
console.log("[STARTUP] Task-Aware Routing config restored from settings");
}
const seededModelAliases = await seedDefaultModelAliases();
console.log(
`[STARTUP] Model alias seed: applied=${seededModelAliases.applied.length}, skipped=${seededModelAliases.skipped.length}, removed=${seededModelAliases.removed.length}, failed=${seededModelAliases.failed.length}`
);
startSessionAccountAffinityCleanup();
const migration = await migrateCodexConnectionDefaultsFromLegacySettings();
if (migration.migrated) {
console.log(
`[STARTUP] Migrated Codex connection defaults for ${migration.updatedConnectionIds.length} connection(s)`
);
if (settings.cloudEnabled === true) {
const [{ syncToCloud }, { getConsistentMachineId }] = await Promise.all([
import("@/lib/cloudSync"),
import("@/shared/utils/machineId"),
]);
const machineId = await getConsistentMachineId();
await syncToCloud(machineId);
console.log("[STARTUP] Synced migrated Codex connection defaults to cloud");
}
}
startRuntimeConfigHotReload();
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Could not restore runtime settings:", msg);
}
// Proactively start the credential-health sweep at boot so stale web-session
// connections (cookies that expired overnight) get re-probed and recovered on
// startup — instead of staying red until the first real request lazily imports
// the on-demand credentialGate. Idempotent; self-disables via
// OMNIROUTE_DISABLE_CREDENTIAL_HEALTH_CHECK and its cadence is tunable via
// CREDENTIAL_HEALTH_CHECK_INTERVAL. NOTE: this MUST live here (the real Next.js
// instrumentation startup), NOT in the unused src/server-init.ts.
try {
const { initCredentialHealthCheck } = await import("@/lib/credentialHealth/scheduler");
initCredentialHealthCheck();
console.log("[STARTUP] Credential health scheduler started");
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Could not start credential health scheduler:", msg);
}
try {
const { initAuditLog, cleanupExpiredLogs } = await import("@/lib/compliance/index");
initAuditLog();
console.log("[COMPLIANCE] Audit log table initialized");
const cleanup = await cleanupExpiredLogs();
if (
cleanup.deletedUsage ||
cleanup.deletedCallLogs ||
cleanup.deletedProxyLogs ||
cleanup.deletedRequestDetailLogs ||
cleanup.deletedAuditLogs ||
cleanup.deletedMcpAuditLogs
) {
console.log("[COMPLIANCE] Expired log cleanup:", cleanup);
}
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[COMPLIANCE] Could not initialize audit log:", msg);
}
// Storage-configured scheduled VACUUM (#4437): registers the timer from
// Settings > System & Storage and persists lastVacuumAt for the UI.
try {
const { initVacuumScheduler } = await import("@/lib/db/vacuumScheduler");
initVacuumScheduler();
console.log("[STARTUP] Scheduled VACUUM initialized (#4437)");
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Could not initialize vacuum scheduler (non-fatal):", msg);
}
// Retention cleanup scheduler (#4691/#6988, #9624): runs the general retention
// cleanup once after startup and then every 6 hours. Previously this was only
// wired into the unused src/server-init.ts, so telemetry tables grew unboundedly
// even with retention.autoCleanupEnabled=true. Idempotent (guarded internally).
try {
startCleanupScheduler();
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Could not start cleanup scheduler (non-fatal):", msg);
}
// Warm the model catalog's durable, apiKey-independent sub-caches at
// startup — see warmModelCatalogCache() for why the top-level Response
// cache alone doesn't deliver this. Fire-and-forget, non-fatal.
void warmModelCatalogCache();
if (!isBackgroundServicesDisabled()) {
// All services are independent — run in parallel for faster cold start.
await Promise.allSettled([
import("@/lib/services/bootstrap")
.then(async (m) => {
await m.bootstrapEmbeddedServices();
console.log("[STARTUP] Embedded services bootstrap complete");
})
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Embedded services bootstrap failed (non-fatal):", msg);
}),
import("@/lib/services/embedWsProxy")
.then((m) => m.initEmbedWsProxy())
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Embed WS proxy failed to start (non-fatal):", msg);
}),
import("@omniroute/open-sse/services/autoRefreshDaemon")
.then((m) => m.autoRefreshDaemon.start())
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
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.
import("@/lib/quota/connectionRecovery")
.then((m) => m.initConnectionRecoveryScheduler())
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Connection recovery scheduler failed to start (non-fatal):", msg);
}),
// Arena ELO sync: model intelligence from the Arena AI leaderboard, powering the
// Free Provider Rankings page. On by default; non-blocking, never fatal.
import("@/lib/arenaEloSync")
.then(async (m) => {
const started = await m.initArenaEloSync();
if (started) console.log("[STARTUP] Arena ELO sync initialized");
})
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Arena ELO sync failed to start (non-fatal):", msg);
}),
// Radar daily feed sync: only arms itself when RADAR_ENABLED AND the user
// opt-in are already on (flag-off boot stays timer-free — Radar inertia
// contract). Non-blocking, never fatal.
import("@/lib/radar/scheduler")
.then((m) => {
const started = m.initRadarSyncScheduler();
if (started) console.log("[STARTUP] Radar sync scheduler initialized");
})
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Radar sync scheduler failed to start (non-fatal):", msg);
}),
// Pricing sync: opt-in external pricing data (self-gated by PRICING_SYNC_ENABLED inside
// initPricingSync). Non-blocking, never fatal.
import("@/lib/pricingSync")
.then((m) => m.initPricingSync())
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Pricing sync failed to start (non-fatal):", msg);
}),
// OpenRouter provider stats sync: provider directory + popularity enrichment
// for the dashboard Providers page. On by default; opt out with
// OPENROUTER_PROVIDER_STATS_ENABLED=false. Non-blocking, never fatal.
import("@/lib/catalog/openrouterProviderStats")
.then((m) => {
const started = m.initOpenRouterProviderStatsSync();
if (started) console.log("[STARTUP] OpenRouter provider stats sync initialized");
})
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn(
"[STARTUP] OpenRouter provider stats sync failed to start (non-fatal):",
msg
);
}),
// models.dev capability sync: opt-in via Settings > AI (self-gated by
// settings.modelsDevSyncEnabled inside initModelsDevSync). Non-blocking, never fatal.
import("@/lib/modelsDevSync")
.then((m) => m.initModelsDevSync())
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] models.dev sync failed to start (non-fatal):", msg);
}),
// Context-window self-correction (5004): periodically reconcile provider-declared
// windows (from /models discovery) into auto:discovery overrides. Never fatal.
import("@/lib/contextWindowResolver")
.then((m) => m.startContextWindowReconcile())
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] context-window reconcile failed to start (non-fatal):", msg);
}),
// TV6 typed memory decay: optional periodic sweep of decayed episodic memories.
// Doubly opt-in (no-op unless MEMORY_TYPED_DECAY_ENABLED=true AND
// MEMORY_TYPED_DECAY_SWEEP_INTERVAL>0). Never deletes by default. Never fatal.
import("@/lib/memory/typedDecay")
.then((m) => m.startMemoryDecaySweep())
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] memory decay sweep failed to start (non-fatal):", msg);
}),
// MemoryBackend provider pattern (PR #8752): initialize configured memory
// backends from settings (sqlite, obsidian, notion, custom HTTP, etc.).
// Reads the DB settings synchronously (non-blocking, never fatal). Must
// run after the DB is ready AND after getSettings/applyRuntimeSettings so
// memory backend config is hydrated.
import("@/lib/memory/index")
.then((m) => m.initMemoryBackends())
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] memory backend initialization failed (non-fatal):", msg);
}),
// Backup schedule (#8513): execute `backup-schedule.json` cron server-side.
// Reads the schedule written by `omniroute backup auto enable` and fires
// `runBackupCommand` when the cron expression matches. Self-gated: no-op
// when no schedule file exists or the schedule is disabled. Never fatal.
import("@/lib/jobs/backupScheduleJob")
.then((m) => m.startBackupScheduleJob())
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] backup schedule job failed to start (non-fatal):", msg);
}),
// Real-time dashboard WebSocket daemon (port 20132): powers Combo Studio Live,
// the Home live-pulse, and Live Compression. Side-effect import triggers the
// flag-gated auto-start (OMNIROUTE_ENABLE_LIVE_WS, default ON).
import("@/server/ws/liveServer")
.then(() => {
console.log("[STARTUP] Live dashboard WebSocket daemon bootstrap invoked");
})
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn(
"[STARTUP] Live dashboard WebSocket daemon failed to start (non-fatal):",
msg
);
}),
]);
}
markServerReady();
}