mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
ci(quality): merge-integrity fast-gates + pre-flight hermetic mode (#6300)
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.
This commit is contained in:
committed by
GitHub
parent
aabefc8026
commit
efc92c6955
34
.github/workflows/quality.yml
vendored
34
.github/workflows/quality.yml
vendored
@@ -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
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -232,3 +232,4 @@ omniroute.md
|
||||
|
||||
# mise configuration
|
||||
mise.toml
|
||||
_artifacts/
|
||||
|
||||
@@ -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",
|
||||
|
||||
121
scripts/check/check-changelog-integrity.mjs
Normal file
121
scripts/check/check-changelog-integrity.mjs
Normal file
@@ -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/<base> 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 <base> -- 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());
|
||||
}
|
||||
@@ -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/<gate>.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)",
|
||||
|
||||
@@ -227,6 +227,9 @@ Add a new context
|
||||
|
||||
- `--api-key <k>`
|
||||
- `--api-key-stdin`
|
||||
- `--access-token <t>`
|
||||
- `--access-token-stdin`
|
||||
- `--scope <s>`
|
||||
- `--description <d>`
|
||||
|
||||
**Example:**
|
||||
@@ -247,7 +250,11 @@ omniroute contexts use <name>
|
||||
|
||||
### `contexts current`
|
||||
|
||||
Show current active context name
|
||||
Show the active context (server, auth, scope)
|
||||
|
||||
**Flags:**
|
||||
|
||||
- `--name-only`
|
||||
|
||||
**Example:**
|
||||
|
||||
|
||||
@@ -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.
|
||||
---
|
||||
|
||||
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
|
||||
|
||||
## Overview
|
||||
@@ -56,6 +55,8 @@ omniroute restart
|
||||
- `--max-restarts <n>`
|
||||
- `--tray`
|
||||
- `--no-tray`
|
||||
- `--tls-cert <path>`
|
||||
- `--tls-key <path>`
|
||||
|
||||
**Example:**
|
||||
|
||||
|
||||
@@ -41,6 +41,14 @@ omniroute autostart enable
|
||||
omniroute autostart disable
|
||||
```
|
||||
|
||||
### `autostart toggle`
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
omniroute autostart toggle
|
||||
```
|
||||
|
||||
### `autostart status`
|
||||
|
||||
**Example:**
|
||||
|
||||
@@ -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.
|
||||
---
|
||||
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
|
||||
|
||||
# /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 `<OMNI_HOST>` 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 = "<detected shell binary, e.g. /bin/bash or /bin/zsh>"
|
||||
# Add any of the following that exist on this machine:
|
||||
# PATH = "<full PATH from Step 1>"
|
||||
# NVM_DIR = "<NVM_DIR from Step 1>"
|
||||
# BUN_INSTALL = "<BUN_INSTALL from Step 1>"
|
||||
# SDKMAN_DIR = "<SDKMAN_DIR from Step 1>"
|
||||
# JAVA_HOME = "<JAVA_HOME from Step 1>"
|
||||
|
||||
# ── 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://<OMNI_HOST>: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/<name>.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 `<OMNI_KEY>` 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="<OMNI_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 "<OMNI_KEY>"
|
||||
set -Ux CLAUDE_CODE_MAX_OUTPUT_TOKENS 65536
|
||||
```
|
||||
|
||||
**PowerShell syntax:**
|
||||
|
||||
```powershell
|
||||
[System.Environment]::SetEnvironmentVariable("OMNIROUTE_API_KEY", "<OMNI_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://<OMNI_HOST>: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._
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
---
|
||||
|
||||
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 <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
|
||||
|
||||
58
tests/unit/check-changelog-integrity.test.ts
Normal file
58
tests/unit/check-changelog-integrity.test.ts
Normal file
@@ -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), []);
|
||||
});
|
||||
@@ -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/<gate>.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");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user