From efc92c695517d60615216ac7c4b1986f0871bfd0 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Sun, 5 Jul 2026 17:50:10 -0300 Subject: [PATCH] ci(quality): merge-integrity fast-gates + pre-flight hermetic mode (#6300) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merged into release/v3.8.45. CI merge-integrity fast-gates (changelog-integrity + agent-skills-sync) + pre-flight hermetic mode. O gate novo detectou 11 SKILL.md gerados fora de sync (drift pré-existente no tip: omni-api-keys endpoints, omni-github-skills do #6186) — regenerados neste PR, Merge-integrity GREEN no CI. Reds restantes base-red (dast-smoke/file-size drift/live-data flaky). Testes novos 17/17. --- .github/workflows/quality.yml | 34 ++ .gitignore | 1 + package.json | 2 + scripts/check/check-changelog-integrity.mjs | 121 ++++++ scripts/quality/validate-release-green.mjs | 42 ++- skills/cli-contexts/SKILL.md | 9 +- skills/cli-serve/SKILL.md | 3 +- skills/cli-setup/SKILL.md | 8 + skills/config-codex-cli/SKILL.md | 367 +------------------ skills/omni-api-keys/SKILL.md | 31 ++ skills/omni-cli-tools/SKILL.md | 70 ++++ skills/omni-github-skills/SKILL.md | 2 - skills/omni-inference/SKILL.md | 59 +++ skills/omni-providers/SKILL.md | 44 +++ skills/omni-settings/SKILL.md | 166 +++++++++ skills/omni-version-manager/SKILL.md | 178 +++++++++ tests/unit/check-changelog-integrity.test.ts | 58 +++ tests/unit/validate-release-green.test.ts | 22 +- 18 files changed, 850 insertions(+), 367 deletions(-) create mode 100644 scripts/check/check-changelog-integrity.mjs create mode 100644 tests/unit/check-changelog-integrity.test.ts diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 2d13fe3dc4..1adfdd0230 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -174,3 +174,37 @@ jobs: - run: npm ci - name: ESLint (baseline congelado — warning novo = vermelho) run: npx eslint . --suppressions-location config/quality/eslint-suppressions.json --max-warnings 0 + + # Merge-integrity: pega no PR os dois vazamentos crônicos de merge que hoje só + # explodem na release-PR. (1) CHANGELOG-eat — o auto-resolve do merge come + # bullets vizinhos/seções inteiras (incidente #6193, 2026-07-05: 212 linhas / + # 130 bullets); o checkout de PR é refs/pull/N/merge, então comparar contra a + # base detecta o eat ANTES do merge. (2) SKILL.md gerado stale vs o catálogo de + # agent-skills (#6186 mergeou um id de catálogo sem rodar o gerador → 8 reds de + # integration invisíveis até a release). + # + # Princípio Zero: bloqueante SÓ para branches internas; PR de FORK roda em modo + # report (continue-on-error) — a campanha corrige via co-autoria, o contribuidor + # nunca é bloqueado. + merge-integrity: + name: Merge integrity (changelog + generated skills) + runs-on: ubuntu-latest + continue-on-error: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }} + env: + JWT_SECRET: ci-lint-secret-with-sufficient-length-for-validation + API_KEY_SECRET: ci-lint-api-key-secret-long + DISABLE_SQLITE_AUTO_BACKUP: "true" + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-node@v6 + with: + node-version: ${{ env.CI_NODE_VERSION }} + cache: npm + - run: npm ci + - name: CHANGELOG integrity (nenhum bullet da base pode sumir no merge-result) + run: npm run check:changelog-integrity + - name: Agent-skills generator sync (SKILL.md gerado ≡ catálogo) + run: npm run check:agent-skills-sync diff --git a/.gitignore b/.gitignore index bb05ae4c44..b2261dfff7 100644 --- a/.gitignore +++ b/.gitignore @@ -232,3 +232,4 @@ omniroute.md # mise configuration mise.toml +_artifacts/ diff --git a/package.json b/package.json index 3105eecbe5..37bc494c8b 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,8 @@ "check:tracked-artifacts": "node scripts/check/check-tracked-artifacts.mjs", "check:test-masking": "node scripts/check/check-test-masking.mjs", "check:test-runner-api": "node scripts/check/check-test-runner-api.mjs", + "check:changelog-integrity": "node scripts/check/check-changelog-integrity.mjs", + "check:agent-skills-sync": "node --import tsx/esm scripts/skills/generate-agent-skills.mjs", "check:build-scope": "node scripts/check/check-build-scope.mjs", "check:error-helper": "node scripts/check/check-error-helper.mjs", "check:migration-numbering": "node scripts/check/check-migration-numbering.mjs", diff --git a/scripts/check/check-changelog-integrity.mjs b/scripts/check/check-changelog-integrity.mjs new file mode 100644 index 0000000000..b07c3f99f4 --- /dev/null +++ b/scripts/check/check-changelog-integrity.mjs @@ -0,0 +1,121 @@ +#!/usr/bin/env node +// scripts/check/check-changelog-integrity.mjs +// +// Anti "CHANGELOG-eat" gate: no bullet line that exists in the BASE branch's +// CHANGELOG.md may disappear in the merge result. The chronic failure mode is +// git's merge auto-resolve silently dropping sibling bullets (or whole version +// sections) when two branches touch adjacent CHANGELOG lines — incident +// 2026-07-05: PR #6193's merge ate 212 lines (the entire [3.8.45] + [3.8.44] +// sections, 130 bullets), only recovered by hand from the pre-merge ref. +// +// On pull_request CI the checkout is refs/pull/N/merge — the auto-resolved +// merge result — so comparing it against origin/ catches the eat BEFORE +// the merge lands, in the PR that would cause it. +// +// Policy (Princípio Zero): this only ever ADDS work for the maintainer side — +// quality.yml runs it blocking for own-origin PRs and report-only for forks. +// The release captain's reconciliation rewrites the CHANGELOG legitimately, +// but that happens on the release PR (PR → main, ci.yml), which does not run +// this gate. Escape hatch for intentional removals (e.g. reverting a reverted +// feature's bullet): ALLOW_CHANGELOG_REMOVALS=1 turns failures into a report. +// +// Usage: +// node scripts/check/check-changelog-integrity.mjs +// env GITHUB_BASE_REF PR base branch (CI); local fallback: current release/* +// env CHANGELOG_BASE_REF explicit ref override (e.g. origin/release/v3.8.45) +// env ALLOW_CHANGELOG_REMOVALS=1 report-only (never fails) + +import { execFileSync } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", ".."); +const CHANGELOG = "CHANGELOG.md"; + +/** Extract the set of bullet lines (trimmed) from a CHANGELOG text. */ +export function extractBullets(text) { + const bullets = new Set(); + for (const raw of String(text || "").split("\n")) { + const line = raw.trim(); + if (line.startsWith("- ") && line.length > 4) bullets.add(line); + } + return bullets; +} + +/** + * Bullet lines present in the base CHANGELOG but absent from the head + * CHANGELOG — the "eaten" set. Pure so it has a unit test. + */ +export function findLostBullets(baseText, headText) { + const headBullets = extractBullets(headText); + const lost = []; + for (const b of extractBullets(baseText)) { + if (!headBullets.has(b)) lost.push(b); + } + return lost; +} + +function git(args) { + return execFileSync("git", args, { cwd: ROOT, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }); +} + +function resolveBaseRef() { + if (process.env.CHANGELOG_BASE_REF) return process.env.CHANGELOG_BASE_REF; + if (process.env.GITHUB_BASE_REF) return `origin/${process.env.GITHUB_BASE_REF}`; + // Local fallback: the highest release/v* on origin (the active development base). + try { + const branches = git(["branch", "-r", "--list", "origin/release/v*", "--format=%(refname:short)"]) + .split("\n") + .map((s) => s.trim()) + .filter(Boolean) + .sort((a, b) => a.localeCompare(b, undefined, { numeric: true })); + return branches[branches.length - 1] || null; + } catch { + return null; + } +} + +function main() { + const baseRef = resolveBaseRef(); + if (!baseRef) { + console.log("[changelog-integrity] SKIP — could not resolve a base ref (offline/fresh clone)."); + return 0; + } + + let baseText; + try { + baseText = git(["show", `${baseRef}:${CHANGELOG}`]); + } catch { + console.log(`[changelog-integrity] SKIP — ${CHANGELOG} not readable at ${baseRef}.`); + return 0; + } + const headText = readFileSync(join(ROOT, CHANGELOG), "utf8"); + + const lost = findLostBullets(baseText, headText); + if (lost.length === 0) { + console.log(`[changelog-integrity] OK — no base bullets lost vs ${baseRef}.`); + return 0; + } + + console.error( + `[changelog-integrity] ${lost.length} bullet(s) present in ${baseRef} are MISSING from this tree's ${CHANGELOG}:` + ); + for (const b of lost.slice(0, 15)) console.error(` ✗ ${b.slice(0, 160)}`); + if (lost.length > 15) console.error(` … and ${lost.length - 15} more`); + console.error( + "\nThis is the CHANGELOG-eat pattern (merge auto-resolve dropping sibling bullets)." + + "\nFix: restore the base CHANGELOG (`git checkout -- CHANGELOG.md`), re-insert ONLY" + + "\nyour own bullet, and prove the net diff is additive. Intentional removals (rare):" + + "\nre-run with ALLOW_CHANGELOG_REMOVALS=1 and justify in the PR body." + ); + if (process.env.ALLOW_CHANGELOG_REMOVALS === "1") { + console.error("[changelog-integrity] ALLOW_CHANGELOG_REMOVALS=1 — reporting only, not failing."); + return 0; + } + return 1; +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + process.exit(main()); +} diff --git a/scripts/quality/validate-release-green.mjs b/scripts/quality/validate-release-green.mjs index 3c1323b3d8..0e17e9f5ab 100644 --- a/scripts/quality/validate-release-green.mjs +++ b/scripts/quality/validate-release-green.mjs @@ -33,14 +33,20 @@ // orchestration lives in the /green-prs + review-prs flows that call it. // // Usage: -// node scripts/quality/validate-release-green.mjs [--json] [--with-build] [--quick] +// node scripts/quality/validate-release-green.mjs [--json] [--with-build] [--quick] [--hermetic] // --json emit machine-readable JSON to stdout (report goes to stderr) // --with-build also run check:pack-artifact (needs a dist/ build — slow) // --quick skip the slow unit + vitest + integration suites (drift + fast // gates only) +// --hermetic scrub OMNIROUTE_API_KEY/OMNIROUTE_URL from gate env so live +// tests self-skip exactly like CI (dev machines otherwise run +// them against localhost and produce false-positive reds) +// +// Per-gate output is saved to _artifacts/release-green/.log (gitignored) — +// diagnose a red from the file instead of re-running the gate. import { execFileSync } from "node:child_process"; -import { readFileSync } from "node:fs"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; @@ -48,6 +54,19 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = join(__dirname, "..", ".."); const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm"; +// Per-gate captured output. execFileSync buffers everything and the report only +// shows a one-line summary, so without these files every red requires RE-RUNNING +// the gate just to see the detail (the dominant cost of the 2026-07-05 pre-flight). +const LOG_DIR = join(ROOT, "_artifacts", "release-green"); +function saveGateLog(id, out) { + try { + mkdirSync(LOG_DIR, { recursive: true }); + writeFileSync(join(LOG_DIR, `${id}.log`), String(out ?? "")); + } catch { + /* log persistence is best-effort — never fails a gate */ + } +} + // ─── Pure helpers (exported for tests) ────────────────────────────────────── /** Read the committed ratchet baseline value for a metric (null if unknown). */ @@ -141,6 +160,17 @@ export function classifyRunError(err, timeoutMs) { }; } +// --hermetic: scrub the live-test trigger vars so the pre-flight behaves like CI +// (a dev machine with OMNIROUTE_API_KEY set runs 17+ live tests that CI skips — +// every one a false-positive red against the release branch). +const HERMETIC_SCRUB = ["OMNIROUTE_API_KEY", "OMNIROUTE_URL"]; +let hermetic = false; +function buildGateEnv(extra) { + const env = { ...process.env, FORCE_COLOR: "0", ...(extra || {}) }; + if (hermetic) for (const k of HERMETIC_SCRUB) delete env[k]; + return env; +} + function run(cmd, cmdArgs, opts = {}) { try { const out = execFileSync(cmd, cmdArgs, { @@ -148,7 +178,7 @@ function run(cmd, cmdArgs, opts = {}) { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 256 * 1024 * 1024, - env: { ...process.env, FORCE_COLOR: "0", ...(opts.env || {}) }, + env: buildGateEnv(opts.env), // A hard ceiling for the long, silent test suites (execFileSync buffers all output until // exit, so they show no progress while running). undefined = no timeout for fast gates. ...(opts.timeout ? { timeout: opts.timeout } : {}), @@ -164,6 +194,7 @@ function main() { const JSON_OUT = args.has("--json"); const WITH_BUILD = args.has("--with-build"); const QUICK = args.has("--quick"); + hermetic = args.has("--hermetic"); const results = []; const record = (r) => { @@ -180,6 +211,7 @@ function main() { const hardCmd = (id, label, cmd, cmdArgs, opts) => { announce(label); const { code, out } = run(cmd, cmdArgs, opts); + saveGateLog(id, out); record({ id, label, @@ -197,6 +229,7 @@ function main() { const driftCmd = (id, label, cmd, cmdArgs, okDetail = "within baseline", opts) => { announce(label); const { code, out } = run(cmd, cmdArgs, opts); + saveGateLog(id, out); record({ id, label, @@ -229,6 +262,7 @@ function main() { ], { timeout: 30 * 60 * 1000 } ); + saveGateLog("lint", out); const parsed = parseEslintJson(out); if (!parsed) { record({ @@ -269,6 +303,7 @@ function main() { { announce("Cognitive complexity (ratchet)"); const { out } = run(npmCmd, ["run", "check:cognitive-complexity"]); + saveGateLog("cognitive", out); const current = parseCognitiveCount(out); const base = baselineValue("cognitiveComplexity"); const over = isDrift(current, base); @@ -308,6 +343,7 @@ function main() { const { code, out } = run(npmCmd, ["run", "check:test-masking"], { env: { GITHUB_BASE_REF: "main" }, }); + saveGateLog("test-masking", out); record({ id: "test-masking", label: "Test-masking (weakened-assert guard)", diff --git a/skills/cli-contexts/SKILL.md b/skills/cli-contexts/SKILL.md index f7faefa731..c25640c92f 100644 --- a/skills/cli-contexts/SKILL.md +++ b/skills/cli-contexts/SKILL.md @@ -227,6 +227,9 @@ Add a new context - `--api-key ` - `--api-key-stdin` +- `--access-token ` +- `--access-token-stdin` +- `--scope ` - `--description ` **Example:** @@ -247,7 +250,11 @@ omniroute contexts use ### `contexts current` -Show current active context name +Show the active context (server, auth, scope) + +**Flags:** + +- `--name-only` **Example:** diff --git a/skills/cli-serve/SKILL.md b/skills/cli-serve/SKILL.md index 2818910229..19dfd0ddc4 100644 --- a/skills/cli-serve/SKILL.md +++ b/skills/cli-serve/SKILL.md @@ -2,7 +2,6 @@ name: cli-serve description: Start, stop, and restart the OmniRoute server from the CLI. Manage daemon mode, port configuration, auto-recovery, system tray integration, and the dashboard open shortcut. --- - ## Overview @@ -56,6 +55,8 @@ omniroute restart - `--max-restarts ` - `--tray` - `--no-tray` +- `--tls-cert ` +- `--tls-key ` **Example:** diff --git a/skills/cli-setup/SKILL.md b/skills/cli-setup/SKILL.md index 34c0a62892..4df7cb49bb 100644 --- a/skills/cli-setup/SKILL.md +++ b/skills/cli-setup/SKILL.md @@ -41,6 +41,14 @@ omniroute autostart enable omniroute autostart disable ``` +### `autostart toggle` + +**Example:** + +```bash +omniroute autostart toggle +``` + ### `autostart status` **Example:** diff --git a/skills/config-codex-cli/SKILL.md b/skills/config-codex-cli/SKILL.md index 8c2daf46c9..2b57a62a17 100644 --- a/skills/config-codex-cli/SKILL.md +++ b/skills/config-codex-cli/SKILL.md @@ -1,369 +1,20 @@ --- name: config-codex-cli -description: Step-by-step agent workflow to configure the OpenAI Codex CLI on any machine (Linux, macOS, Windows) to use OmniRoute as backend. Detects OS and shell, writes config.toml and 7 named profiles, sets environment variables, and verifies the setup. +description: 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. --- + -# /config-codex-cli — Codex CLI Configuration Workflow +## Overview -Configure the Codex CLI on this machine to use an OmniRoute instance as backend. +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. -After this skill completes, `codex` will use OmniRoute by default with `cx/gpt-5.5` (xhigh reasoning), 7 named profiles for quick switching, and proper token limits configured for each model. - ---- - -## Step 0 — Collect required inputs from the user - -Before doing anything, ask the user for the two required values. Do not proceed until both are provided: - -1. **OmniRoute host** — the IP or hostname of the OmniRoute server (e.g. `192.168.0.1`, `100.x.x.x` for Tailscale, or `localhost`) -2. **OmniRoute API key** — the API key for OmniRoute (starts with `sk-`) - -Store these as local variables for the rest of the skill: -- `OMNI_HOST` = the host the user provided (no trailing slash, no port — port 20128 is appended by this skill) -- `OMNI_KEY` = the API key - ---- - -## Step 1 — Detect environment - -Run the following to gather machine facts. Store results — they are used in later steps. +## Quick install ```bash -# OS detection -uname -s # Linux / Darwin (macOS) / MINGW*/CYGWIN*/MSYS* = Windows/Git Bash - -# Home directory -echo $HOME # Linux / macOS / Git Bash -echo $USERPROFILE # Windows native (PowerShell / cmd) - -# Current shell -echo $SHELL # Linux / macOS: /bin/bash, /bin/zsh, /bin/fish, etc. - -# Shell profile file -# Resolve which file to append env vars to: -# bash → ~/.bashrc (Linux) or ~/.bash_profile (macOS) -# zsh → ~/.zshrc -# fish → ~/.config/fish/config.fish -# PowerShell (Windows) → $PROFILE (run: echo $PROFILE inside PowerShell) - -# PATH and common tool directories (to populate shell_environment_policy later) -echo $PATH -which node 2>/dev/null && node --version -echo ${NVM_DIR:-not-set} -echo ${BUN_INSTALL:-not-set} -echo ${SDKMAN_DIR:-not-set} -echo ${JAVA_HOME:-not-set} +npm install -g omniroute # or: npx omniroute +omniroute --version ``` -Based on the OS result, set the **Codex config directory**: -- Linux / macOS / Git Bash: `~/.codex/` -- Windows native (PowerShell): `$env:USERPROFILE\.codex\` +## Subcommands ---- - -## Step 2 — Verify Codex CLI is installed - -```bash -codex --version -``` - -If this fails, stop and tell the user to install the Codex CLI first: - -```bash -npm install -g @openai/codex -``` - -Then re-run the skill from Step 1. - ---- - -## Step 3 — Create the Codex config directory - -```bash -mkdir -p ~/.codex # Linux / macOS -# Windows PowerShell: New-Item -ItemType Directory -Force "$env:USERPROFILE\.codex" -``` - ---- - -## Step 4 — Write `~/.codex/config.toml` - -Read the existing file first if it exists (to avoid overwriting MCP server entries, skills, projects, or notify configurations the user may already have). - -If the file **does not exist**: create it with the full content below. - -If the file **already exists**: apply only the fields listed in the "Model/Inference", "Behaviour", "Auth/Credentials", "Features", "TUI", "Notice", and "Model providers" sections — do **not** remove existing `[mcp_servers.*]`, `[projects.*]`, `[[skills.config]]`, or `notify` entries. - -Replace `` with the value collected in Step 0. - -**Content to write (or merge):** - -```toml -# ── Model / Inference ───────────────────────────────────────────────────────── -model = "cx/gpt-5.5" -model_provider = "omniroute" -model_reasoning_effort = "xhigh" -model_reasoning_summary = "detailed" -model_verbosity = "high" -model_context_window = 400000 -model_auto_compact_token_limit = 350000 -model_max_output_tokens = 65536 -tool_output_token_limit = 32768 - -# ── Behaviour ───────────────────────────────────────────────────────────────── -approval_policy = "never" -sandbox_mode = "danger-full-access" -personality = "pragmatic" -web_search = "live" -check_for_update_on_startup = true - -# ── Auth / Credentials ──────────────────────────────────────────────────────── -cli_auth_credentials_store = "file" -mcp_oauth_credentials_store = "file" - -# ── Features ────────────────────────────────────────────────────────────────── -[features] -shell_snapshot = true -unified_exec = true -multi_agent = true -memories = true -js_repl = true -apps = false -terminal_resize_reflow = true - -# ── TUI ─────────────────────────────────────────────────────────────────────── -[tui] -theme = "dracula" -status_line = ["model-with-reasoning", "current-dir", "context-remaining", "context-used", "five-hour-limit"] - -[tui.model_availability_nux] -"gpt-5.5" = 4 - -# ── Shell environment passed into sandboxed commands ────────────────────────── -# Populate [shell_environment_policy.set] with the PATH and tool dirs discovered -# in Step 1. Only include paths that actually exist on this machine. -# Minimum required: SHELL. -[shell_environment_policy] -inherit = "all" -experimental_use_profile = true - -[shell_environment_policy.set] -SHELL = "" -# Add any of the following that exist on this machine: -# PATH = "" -# NVM_DIR = "" -# BUN_INSTALL = "" -# SDKMAN_DIR = "" -# JAVA_HOME = "" - -# ── Notice / UI flags ───────────────────────────────────────────────────────── -[notice] -hide_full_access_warning = true -hide_rate_limit_model_nudge = true -fast_default_opt_out = true - -# ── Model providers ─────────────────────────────────────────────────────────── -# env_key = NAME of the environment variable (not the value). -# The actual key is stored in the shell profile (Step 5), never here. -[model_providers.omniroute] -name = "OmniRoute" -base_url = "http://:20128/v1" -env_key = "OMNIROUTE_API_KEY" -requires_openai_auth = false -wire_api = "responses" -``` - -> **TOML rule:** `[[skills.config]]` array-of-tables must be the **last** section in the file. If the file already has `[[skills.config]]` entries, keep them at the end after inserting the new provider block. - ---- - -## Step 5 — Write profile files - -> **Naming rule (Codex CLI v0.137+):** files must be `~/.codex/.config.toml` — **no `profile-` prefix**. The CLI resolves `-p chat` to `~/.codex/chat.config.toml`. If the file is not found, the default applies silently with no error. - -Create each file below in the Codex config directory (`~/.codex/`). If a file already exists, overwrite it. - -### `chat.config.toml` — no reasoning (server default = medium) - -```toml -model = "cx/gpt-5.5" -model_provider = "omniroute" -``` - -### `low.config.toml` - -```toml -model = "cx/gpt-5.5" -model_reasoning_effort = "low" -model_provider = "omniroute" -``` - -### `medium.config.toml` - -```toml -model = "cx/gpt-5.5" -model_reasoning_effort = "medium" -model_provider = "omniroute" -``` - -### `high.config.toml` - -```toml -model = "cx/gpt-5.5" -model_reasoning_effort = "high" -model_provider = "omniroute" -``` - -### `xhigh.config.toml` - -```toml -model = "cx/gpt-5.5" -model_reasoning_effort = "xhigh" -model_provider = "omniroute" -``` - -### `deepseek.config.toml` — DeepSeek V4 Pro, 1M context - -```toml -model = "ds/deepseek-v4-pro" -model_provider = "omniroute" - -model_context_window = 1000000 -model_auto_compact_token_limit = 900000 -model_max_output_tokens = 65536 -tool_output_token_limit = 65536 -``` - -### `mistral.config.toml` — Mistral Large Latest, 256k context - -```toml -model = "mistral/mistral-large-latest" -model_provider = "omniroute" - -model_context_window = 262144 -model_auto_compact_token_limit = 220000 -model_max_output_tokens = 32768 -tool_output_token_limit = 16384 -``` - ---- - -## Step 6 — Set environment variables in the shell profile - -Determine the correct shell profile file from Step 1, then append the following block **only if the variables are not already present**. - -Before writing, check: -```bash -grep -l "OMNIROUTE_API_KEY" ~/.bashrc ~/.zshrc ~/.bash_profile 2>/dev/null -``` - -If the variable already exists in any profile file, update the value in-place instead of appending a duplicate. - -**Block to append (replace `` with the key collected in Step 0):** - -```bash -# OmniRoute API key — used by Codex CLI (env_key = "OMNIROUTE_API_KEY" in config) -export OMNIROUTE_API_KEY="" - -# Codex CLI / Claude Code output cap (64k — covers any file or diff a coding assistant generates) -export CLAUDE_CODE_MAX_OUTPUT_TOKENS=65536 -``` - -**Shell profile file by OS/shell:** - -| OS / Shell | Profile file | -|------------|-------------| -| Linux — bash | `~/.bashrc` | -| Linux — zsh | `~/.zshrc` | -| macOS — zsh (default) | `~/.zshrc` | -| macOS — bash | `~/.bash_profile` | -| Linux/macOS — fish | `~/.config/fish/config.fish` (use `set -Ux` syntax instead of `export`) | -| Windows — PowerShell | `$PROFILE` (run `echo $PROFILE` in PowerShell to get the path) | -| Windows — Git Bash | `~/.bashrc` | - -**fish syntax:** - -```fish -set -Ux OMNIROUTE_API_KEY "" -set -Ux CLAUDE_CODE_MAX_OUTPUT_TOKENS 65536 -``` - -**PowerShell syntax:** - -```powershell -[System.Environment]::SetEnvironmentVariable("OMNIROUTE_API_KEY", "", "User") -[System.Environment]::SetEnvironmentVariable("CLAUDE_CODE_MAX_OUTPUT_TOKENS", "65536", "User") -``` - ---- - -## Step 7 — Apply and verify - -```bash -# Apply the shell profile (Linux/macOS) -source ~/.bashrc # or ~/.zshrc depending on Step 1 - -# Verify variables are set -echo $OMNIROUTE_API_KEY # must print the key -echo $CLAUDE_CODE_MAX_OUTPUT_TOKENS # must print 65536 - -# Verify Codex picks up the provider -codex config get model_provider # must print: omniroute - -# Smoke test — must return a response without auth errors -codex -p chat "reply with only the word OK" -``` - -If the smoke test returns an authentication error: -- Re-check that `source ~/.bashrc` was run (or open a new terminal) -- Run `curl http://:20128/v1/models` to confirm OmniRoute is reachable -- Confirm the key is correct with `echo $OMNIROUTE_API_KEY` - ---- - -## Reference — profiles quick table - -| Profile | Command | Best for | -|---------|---------|----------| -| `chat` | `codex -p chat "..."` | Explain, light questions | -| `low` | `codex -p low "..."` | Rename, format, trivial edits | -| `medium` | `codex -p medium "..."` | Debug, moderate refactor | -| `high` | `codex -p high "..."` | New features, complex tests | -| `xhigh` | *(default)* | Architecture, deep analysis | -| `deepseek` | `codex -p deepseek "..."` | Long codebase analysis (1M context) | -| `mistral` | `codex -p mistral "..."` | Cost-conscious tasks | - -Per-invocation overrides (bypass profiles entirely): - -```bash -codex -m cx/gpt-5.5 -c model_reasoning_effort=low "rename var x to count" -codex -m ds/deepseek-v4-pro "analyze the entire repo" -``` - ---- - -## Reference — why `wire_api = "responses"` works for all models - -Codex CLI deprecated `wire_api = "chat"` in February 2026. OmniRoute bridges the gap transparently: - -``` -Codex CLI → POST /v1/responses → OmniRoute → POST /chat/completions → DeepSeek / Mistral / any provider -``` - -All profiles use the same `wire_api = "responses"`. OmniRoute handles translation for every upstream provider. - ---- - -## Reference — token fields - -| Field | Controls | -|-------|----------| -| `model_context_window` | Total token budget | -| `model_auto_compact_token_limit` | Compaction trigger (max 90% of context window) | -| `model_max_output_tokens` | Max tokens per API response (sent on every request) | -| `tool_output_token_limit` | Max tokens stored per tool call in session history | -| `CLAUDE_CODE_MAX_OUTPUT_TOKENS` | Same concept for the Claude Code CLI | - ---- - -*Full reference: `docs/guides/CODEX-CLI-CONFIGURATION.md` in the OmniRoute repository.* +_No CLI subcommands mapped for this family yet._ diff --git a/skills/omni-api-keys/SKILL.md b/skills/omni-api-keys/SKILL.md index a2563b0ea2..7e6169879d 100644 --- a/skills/omni-api-keys/SKILL.md +++ b/skills/omni-api-keys/SKILL.md @@ -34,6 +34,26 @@ curl -X POST https://localhost:20128/api/keys \ -d '{}' ``` +### GET /api/keys/{id} + +Get API key + +```bash +curl https://localhost:20128/api/keys/{id} \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + +### PATCH /api/keys/{id} + +Update API key + +```bash +curl -X PATCH https://localhost:20128/api/keys/{id} \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + ### DELETE /api/keys/{id} Delete API key @@ -43,6 +63,17 @@ curl -X DELETE https://localhost:20128/api/keys/{id} \ -H "Authorization: Bearer $OMNIROUTE_TOKEN" ``` +### GET /api/keys/{id}/devices + +List devices for an API key + +Lists the distinct devices (masked IP + User-Agent fingerprints) tracked for an API key by the in-memory device tracker. IPs are masked before storage; the route never sees the raw client IP. + +```bash +curl https://localhost:20128/api/keys/{id}/devices \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + ## Payloads See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/openapi.yaml` for detailed request/response schemas. diff --git a/skills/omni-cli-tools/SKILL.md b/skills/omni-cli-tools/SKILL.md index ff468040af..76c3e54b23 100644 --- a/skills/omni-cli-tools/SKILL.md +++ b/skills/omni-cli-tools/SKILL.md @@ -315,6 +315,76 @@ curl -X DELETE https://localhost:20128/api/cli-tools/openclaw-settings \ -H "Authorization: Bearer $OMNIROUTE_TOKEN" ``` +### GET /api/cli-tools/crush-settings + +Read Crush CLI OmniRoute config + +Local-only. Reads the OmniRoute provider block in Crush's config. + +```bash +curl https://localhost:20128/api/cli-tools/crush-settings \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + +### POST /api/cli-tools/crush-settings + +Write Crush CLI OmniRoute config + +Local-only. Registers OmniRoute as an `openai-compat` provider in Crush's config. + +```bash +curl -X POST https://localhost:20128/api/cli-tools/crush-settings \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### DELETE /api/cli-tools/crush-settings + +Remove OmniRoute from Crush CLI config + +Local-only. Removes the OmniRoute provider block from Crush's config. + +```bash +curl -X DELETE https://localhost:20128/api/cli-tools/crush-settings \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + +### GET /api/cli-tools/codewhale-settings + +Read CodeWhale CLI OmniRoute config + +Local-only. Reads the OmniRoute config block from `~/.codewhale/config.toml` (with `~/.deepseek/config.toml` legacy fallback). + +```bash +curl https://localhost:20128/api/cli-tools/codewhale-settings \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + +### POST /api/cli-tools/codewhale-settings + +Write CodeWhale CLI OmniRoute config + +Local-only. Writes the OmniRoute config block in CodeWhale TOML format. + +```bash +curl -X POST https://localhost:20128/api/cli-tools/codewhale-settings \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### DELETE /api/cli-tools/codewhale-settings + +Remove OmniRoute from CodeWhale CLI config + +Local-only. Removes the OmniRoute config block from CodeWhale's config. + +```bash +curl -X DELETE https://localhost:20128/api/cli-tools/codewhale-settings \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + ## Payloads See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/openapi.yaml` for detailed request/response schemas. diff --git a/skills/omni-github-skills/SKILL.md b/skills/omni-github-skills/SKILL.md index da835d38e1..355bfe6d61 100644 --- a/skills/omni-github-skills/SKILL.md +++ b/skills/omni-github-skills/SKILL.md @@ -2,7 +2,6 @@ name: omni-github-skills description: Search, score, scan, and import agent skills from GitHub repositories that contain SKILL.md, CLAUDE.md, .cursorrules, and similar agent skill files. Discover community skills across 160+ provider categories, evaluate relevance with heuristic scoring, check for malware or hardcoded secrets, and install into Hermes, Claude Code, Gemini CLI, or OpenCode agent directories. --- - ## Overview @@ -16,7 +15,6 @@ All requests require a valid Bearer token or session cookie. Obtain a token via ## Endpoints _No endpoints mapped for this area yet._ - ## Payloads See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/openapi.yaml` for detailed request/response schemas. diff --git a/skills/omni-inference/SKILL.md b/skills/omni-inference/SKILL.md index 8c9e0e0a42..40079c16f9 100644 --- a/skills/omni-inference/SKILL.md +++ b/skills/omni-inference/SKILL.md @@ -27,6 +27,17 @@ curl -X POST https://localhost:20128/api/v1/chat/completions \ -d '{}' ``` +### GET /api/v1/ws + +Chat completion over WebSocket (handshake + upgrade) + +OpenAI-compatible chat over a WebSocket connection. `GET` with `?handshake=1` returns the connection descriptor (auth path, message protocol and live-event channels) as JSON; a plain `GET` without an Upgrade returns `426 Upgrade Required`. After upgrading, the client exchanges JSON frames — `{type:"request", id, payload:{model, messages}}` to start a completion and `{type:"cancel", id}` to abort it. A separate live channel (default port `LIVE_WS_PORT=20129`, path `/live`) streams dashboard events on the `requests`, `combo` and `credentials` topics with a 15s heartbeat. Requires an API key. + +```bash +curl https://localhost:20128/api/v1/ws \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + ### POST /api/v1/providers/{provider}/chat/completions Create chat completion (provider-specific) @@ -208,6 +219,54 @@ curl https://localhost:20128/api/v1/providers/{provider}/models \ -H "Authorization: Bearer $OMNIROUTE_TOKEN" ``` +### POST /api/v1/ocr + +Document OCR + +Mistral OCR–compatible document OCR endpoint. Accepts a JSON body referencing a document/image and returns extracted text. Success responses carry the `X-OmniRoute-*` cost-telemetry headers. + +```bash +curl -X POST https://localhost:20128/api/v1/ocr \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/v1/audio/translations + +Translate audio to English + +OpenAI Whisper–compatible audio translation (multipart/form-data). Unlike `/api/v1/audio/transcriptions`, output is always English regardless of the source language. Success responses carry the `X-OmniRoute-*` cost-telemetry headers. + +```bash +curl -X POST https://localhost:20128/api/v1/audio/translations \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### GET /api/v1/providers/suggested-models + +Suggested media models + +Read-only server-side proxy to the public HuggingFace Hub models search API, used by the dashboard to suggest models for a media provider kind without exposing an HF token client-side. Never accepts or returns credentials. + +```bash +curl https://localhost:20128/api/v1/providers/suggested-models \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + +### GET /api/v1/provider-plugin-manifest + +Provider plugin manifest + +Returns the manifest describing installed provider plugins. + +```bash +curl https://localhost:20128/api/v1/provider-plugin-manifest \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + ## Payloads See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/openapi.yaml` for detailed request/response schemas. diff --git a/skills/omni-providers/SKILL.md b/skills/omni-providers/SKILL.md index 1856ca0ff8..acda134239 100644 --- a/skills/omni-providers/SKILL.md +++ b/skills/omni-providers/SKILL.md @@ -114,6 +114,50 @@ curl https://localhost:20128/api/providers/client \ -H "Authorization: Bearer $OMNIROUTE_TOKEN" ``` +### POST /api/providers/agy-auth/import + +Import an Antigravity CLI (agy) token file as an `agy` connection + +```bash +curl -X POST https://localhost:20128/api/providers/agy-auth/import \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/providers/agy-auth/import-bulk + +Bulk-import multiple Antigravity CLI (agy) token files (up to 50) + +```bash +curl -X POST https://localhost:20128/api/providers/agy-auth/import-bulk \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/providers/agy-auth/zip-extract + +Extract `.json` token files from an uploaded ZIP for agy bulk import + +```bash +curl -X POST https://localhost:20128/api/providers/agy-auth/zip-extract \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/providers/agy-auth/apply-local + +Auto-detect and import the local Antigravity CLI (agy) login from disk + +```bash +curl -X POST https://localhost:20128/api/providers/agy-auth/apply-local \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + ### GET /api/provider-nodes List provider nodes diff --git a/skills/omni-settings/SKILL.md b/skills/omni-settings/SKILL.md index fc9bcae210..55fae9a557 100644 --- a/skills/omni-settings/SKILL.md +++ b/skills/omni-settings/SKILL.md @@ -14,6 +14,102 @@ All requests require a valid Bearer token or session cookie. Obtain a token via ## Endpoints +### GET /api/settings/memory + +Get memory settings + +Returns the extended memory settings including 7 new fields added in plan 21 (embeddingSource, embeddingProviderModel, transformersEnabled, staticEnabled, rerankEnabled, rerankProviderModel, vectorStore). + +```bash +curl https://localhost:20128/api/settings/memory \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + +### PUT /api/settings/memory + +Update memory settings + +Update any subset of the extended memory settings. All fields are optional; only provided fields are updated. Schema: `MemorySettingsExtendedSchema`. + +```bash +curl -X PUT https://localhost:20128/api/settings/memory \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### GET /api/settings/qdrant + +Get Qdrant settings + +Returns current Qdrant configuration. The `apiKey` field is never returned raw — use `hasApiKey` / `apiKeyMasked` instead. + +```bash +curl https://localhost:20128/api/settings/qdrant \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + +### PUT /api/settings/qdrant + +Update Qdrant settings + +Update Qdrant configuration. Pass `apiKey: ""` to remove the stored key. Schema: `QdrantSettingsUpdateSchema`. + +```bash +curl -X PUT https://localhost:20128/api/settings/qdrant \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### GET /api/settings/qdrant/health + +Qdrant health probe + +Performs a liveness check against the configured Qdrant instance. Returns latency and any connection error (sanitized — no stack traces). + +```bash +curl https://localhost:20128/api/settings/qdrant/health \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + +### POST /api/settings/qdrant/search + +Qdrant semantic search test + +Performs a test semantic search against the Qdrant collection. Useful for validating that the integration works end-to-end. Schema: `QdrantSearchSchema`. + +```bash +curl -X POST https://localhost:20128/api/settings/qdrant/search \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/settings/qdrant/cleanup + +Clean up expired Qdrant points + +Removes Qdrant points for memories that have expired or exceeded the configured retention window. + +```bash +curl -X POST https://localhost:20128/api/settings/qdrant/cleanup \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### GET /api/settings/qdrant/embedding-models + +List Qdrant embedding models + +Returns the list of embedding models available for use with Qdrant. + +```bash +curl https://localhost:20128/api/settings/qdrant/embedding-models \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + ### GET /api/settings Get application settings @@ -34,6 +130,19 @@ curl -X PATCH https://localhost:20128/api/settings \ -d '{}' ``` +### POST /api/settings/purge-request-history + +Clear request log history + +Deletes `call_logs`, legacy `request_detail_logs`, and local request artifact files under `DATA_DIR/call_logs`. + +```bash +curl -X POST https://localhost:20128/api/settings/purge-request-history \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + ### GET /api/settings/compression Get global compression settings @@ -54,6 +163,28 @@ curl -X PUT https://localhost:20128/api/settings/compression \ -d '{}' ``` +### GET /api/settings/compression/mcp-accessibility + +Get the MCP tool-output accessibility (trimming) config + +```bash +curl https://localhost:20128/api/settings/compression/mcp-accessibility \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + +### PUT /api/settings/compression/mcp-accessibility + +Update the MCP tool-output accessibility (trimming) config + +Partial-merge update. Numeric floors (e.g. a maxTextChars below the truncation-tail reserve) are folded back to the safe defaults server-side, so the response reflects the effective config. + +```bash +curl -X PUT https://localhost:20128/api/settings/compression/mcp-accessibility \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + ### GET /api/settings/payload-rules Get payload rules configuration @@ -217,6 +348,41 @@ curl https://localhost:20128/api/tags \ -H "Authorization: Bearer $OMNIROUTE_TOKEN" ``` +### GET /api/settings/quota-store + +Get current quota store driver settings + +Redis URL is masked in the response (shows only scheme+host). + +```bash +curl https://localhost:20128/api/settings/quota-store \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + +### PUT /api/settings/quota-store + +Update quota store driver settings + +```bash +curl -X PUT https://localhost:20128/api/settings/quota-store \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/settings/purge-usage-history + +Purge usage history + +Dashboard-only. Purges stored usage-history records. + +```bash +curl -X POST https://localhost:20128/api/settings/purge-usage-history \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + ## Payloads See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/openapi.yaml` for detailed request/response schemas. diff --git a/skills/omni-version-manager/SKILL.md b/skills/omni-version-manager/SKILL.md index 167b5815a7..85f21839c4 100644 --- a/skills/omni-version-manager/SKILL.md +++ b/skills/omni-version-manager/SKILL.md @@ -205,6 +205,184 @@ curl -X POST https://localhost:20128/api/services/cliproxy/auto-start \ -d '{}' ``` +### POST /api/services/mux/install + +Install Mux from npm + +Installs the `mux` npm package (coder/mux — local agent-orchestration daemon) under DATA_DIR/services/mux/. **LOCAL_ONLY** — loopback only. + +```bash +curl -X POST https://localhost:20128/api/services/mux/install \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/services/mux/start + +Start Mux + +Spawns `mux server --host 127.0.0.1 --port `. Idempotent if already running. **LOCAL_ONLY** — loopback only. + +```bash +curl -X POST https://localhost:20128/api/services/mux/start \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/services/mux/stop + +Stop Mux + +Gracefully stops Mux. Idempotent. **LOCAL_ONLY** — loopback only. + +```bash +curl -X POST https://localhost:20128/api/services/mux/stop \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/services/mux/restart + +Restart Mux + +stop() then start() under the operation lock. **LOCAL_ONLY** — loopback only. + +```bash +curl -X POST https://localhost:20128/api/services/mux/restart \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/services/mux/update + +Update Mux to a newer npm version + +Stops, installs newer version, restarts. **LOCAL_ONLY** — loopback only. + +```bash +curl -X POST https://localhost:20128/api/services/mux/update \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### GET /api/services/mux/status + +Get Mux status + +Returns live supervisor state and DB metadata. **LOCAL_ONLY** — loopback only. + +```bash +curl https://localhost:20128/api/services/mux/status \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + +### POST /api/services/mux/auto-start + +Toggle Mux auto-start + +When enabled, Mux starts automatically on the next OmniRoute boot. **LOCAL_ONLY** — loopback only. + +```bash +curl -X POST https://localhost:20128/api/services/mux/auto-start \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/services/bifrost/install + +Install Bifrost + +Installs the `@maximhq/bifrost` npm package under DATA_DIR/services/bifrost/. The package downloads the Go binary on first run. Accepts an optional `version` field (semver or `latest`). **LOCAL_ONLY** — loopback only. + +```bash +curl -X POST https://localhost:20128/api/services/bifrost/install \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/services/bifrost/start + +Start Bifrost + +Starts the supervised Bifrost process. **LOCAL_ONLY** — loopback only. + +```bash +curl -X POST https://localhost:20128/api/services/bifrost/start \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/services/bifrost/stop + +Stop Bifrost + +Stops the supervised Bifrost process. **LOCAL_ONLY** — loopback only. + +```bash +curl -X POST https://localhost:20128/api/services/bifrost/stop \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/services/bifrost/restart + +Restart Bifrost + +Restarts the supervised Bifrost process. **LOCAL_ONLY** — loopback only. + +```bash +curl -X POST https://localhost:20128/api/services/bifrost/restart \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### POST /api/services/bifrost/update + +Update Bifrost + +Updates Bifrost to the latest npm version. Stops the running process, installs the new version, and restarts if it was previously running. **LOCAL_ONLY** — loopback only. + +```bash +curl -X POST https://localhost:20128/api/services/bifrost/update \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + +### GET /api/services/bifrost/status + +Get Bifrost status + +Returns live and DB status for the supervised Bifrost service. **LOCAL_ONLY** — loopback only. + +```bash +curl https://localhost:20128/api/services/bifrost/status \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + +### POST /api/services/bifrost/auto-start + +Toggle Bifrost auto-start + +When enabled, Bifrost starts automatically on the next OmniRoute boot. **LOCAL_ONLY** — loopback only. + +```bash +curl -X POST https://localhost:20128/api/services/bifrost/auto-start \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" + -H "Content-Type: application/json" \ + -d '{}' +``` + ### GET /api/services/{name}/logs Stream service logs via SSE diff --git a/tests/unit/check-changelog-integrity.test.ts b/tests/unit/check-changelog-integrity.test.ts new file mode 100644 index 0000000000..a06bf4318e --- /dev/null +++ b/tests/unit/check-changelog-integrity.test.ts @@ -0,0 +1,58 @@ +// Guards the anti CHANGELOG-eat gate (scripts/check/check-changelog-integrity.mjs): +// a merge auto-resolve that drops sibling bullets must be detected by comparing +// the merge result against the base branch's CHANGELOG (incident 2026-07-05, +// PR #6193: 212 lines / 130 bullets eaten). +import { test } from "node:test"; +import assert from "node:assert/strict"; + +const { extractBullets, findLostBullets } = await import( + "../../scripts/check/check-changelog-integrity.mjs" +); + +const BASE = `# Changelog + +## [Unreleased] + +### Bug Fixes + +- **fix(a):** first bullet ([#1](https://x/1)) +- **fix(b):** second bullet ([#2](https://x/2)) + +## [3.8.44] — TBD + +- **feat(c):** shipped bullet ([#3](https://x/3)) +`; + +test("extractBullets collects trimmed bullet lines only", () => { + const b = extractBullets(BASE); + assert.equal(b.size, 3); + assert.ok(b.has("- **fix(a):** first bullet ([#1](https://x/1))")); +}); + +test("no loss when head is a superset (normal additive merge)", () => { + const head = BASE + "- **fix(d):** new bullet ([#4](https://x/4))\n"; + assert.deepEqual(findLostBullets(BASE, head), []); +}); + +test("detects an eaten sibling bullet", () => { + const head = BASE.replace("- **fix(b):** second bullet ([#2](https://x/2))\n", ""); + const lost = findLostBullets(BASE, head); + assert.deepEqual(lost, ["- **fix(b):** second bullet ([#2](https://x/2))"]); +}); + +test("detects a whole eaten version section (#6193 pattern)", () => { + const head = BASE.split("## [3.8.44]")[0]; + const lost = findLostBullets(BASE, head); + assert.deepEqual(lost, ["- **feat(c):** shipped bullet ([#3](https://x/3))"]); +}); + +test("bullets moved between sections are NOT reported (line content preserved)", () => { + const head = BASE.replace( + "- **fix(a):** first bullet ([#1](https://x/1))\n", + "" + ).replace( + "- **feat(c):** shipped bullet ([#3](https://x/3))", + "- **feat(c):** shipped bullet ([#3](https://x/3))\n- **fix(a):** first bullet ([#1](https://x/1))" + ); + assert.deepEqual(findLostBullets(BASE, head), []); +}); diff --git a/tests/unit/validate-release-green.test.ts b/tests/unit/validate-release-green.test.ts index d2d165bd53..26113cb265 100644 --- a/tests/unit/validate-release-green.test.ts +++ b/tests/unit/validate-release-green.test.ts @@ -137,6 +137,24 @@ test("pre-flight wires the test-masking PR-context gate against origin/main (v3. /id:\s*"test-masking"[\s\S]*?kind:\s*"hard"/, "test-masking must be a HARD gate (non-allowlisted weakening blocks the release)" ); - // run() must honor a per-gate env override so GITHUB_BASE_REF actually reaches the child. - assert.match(src, /\.\.\.\(opts\.env \|\| \{\}\)/, "run() must merge opts.env into the child env"); + // run() must honor a per-gate env override so GITHUB_BASE_REF actually reaches the child + // (routed through buildGateEnv since the --hermetic scrub was added). + assert.match(src, /env:\s*buildGateEnv\(opts\.env\)/, "run() must merge opts.env into the child env"); + assert.match(src, /\.\.\.\(extra \|\| \{\}\)/, "buildGateEnv must spread the per-gate env override"); +}); + +test("pre-flight --hermetic scrubs the live-test trigger vars (2026-07-05 false-positive fix)", async () => { + const fs = await import("node:fs"); + const src = fs.readFileSync( + new URL("../../scripts/quality/validate-release-green.mjs", import.meta.url), + "utf8" + ); + // A dev machine with OMNIROUTE_API_KEY set runs 17+ live tests that CI skips — + // the pre-flight must be able to reproduce the CI env exactly. + assert.match(src, /HERMETIC_SCRUB\s*=\s*\["OMNIROUTE_API_KEY",\s*"OMNIROUTE_URL"\]/); + assert.match(src, /args\.has\("--hermetic"\)/, "--hermetic flag must be parsed"); + // Per-gate logs: a red must be diagnosable from _artifacts/release-green/.log + // without re-running the gate. + assert.match(src, /saveGateLog/, "per-gate output must be persisted"); + assert.match(src, /_artifacts[/", ]+release-green/, "logs must land in _artifacts/release-green"); });