ci: speed up e2e shards with build and browser cache

Upload the Next.js build from the build job and reuse it across E2E
shards to avoid rebuilding in each shard. Increase Playwright sharding
from 6 to 9, cache Chromium browsers, and lower the E2E timeout to match
the faster expected runtime.

Add a Codex CLI configuration skill for OmniRoute setup and ignore local
credential-bearing setup prompts.
This commit is contained in:
diegosouzapw
2026-06-08 09:49:28 -03:00
parent b145e41a42
commit 6ec4ca3f67
12 changed files with 442 additions and 20 deletions

View File

@@ -144,6 +144,12 @@ jobs:
- run: npm ci
- run: npm run check:node-runtime
- run: npm run build
- name: Upload Next.js build for E2E shards
uses: actions/upload-artifact@v4
with:
name: e2e-next-build
path: .build/next/
retention-days: 1
package-artifact:
name: Package Artifact
@@ -498,19 +504,18 @@ jobs:
}
test-e2e:
name: E2E Tests (${{ matrix.shard }}/6)
name: E2E Tests (${{ matrix.shard }}/9)
runs-on: ubuntu-latest
# The heaviest shard (responsive viewport matrix + studio/smoke) re-runs
# `npm run build` (~5m) then ~24 serial tests; at 35m it was still cancelled
# mid-run, so it genuinely needs more wall-clock. 50m gives ample headroom
# while the per-test cap (playwright.config.ts) bounds any real hang to a fast
# visible failure and the `line` reporter streams which test is slow.
timeout-minutes: 50
# Build artifact from the `build` job is downloaded instead of rebuilding
# (~5min saved per shard). 9 shards (up from 6) reduces tests per shard by
# ~33%. Playwright browser is cached across runs (~1.5min saved per shard).
# Heavy shard target: ≤20min (was ~40min).
timeout-minutes: 30
needs: build
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6]
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9]
env:
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
API_KEY_SECRET: ci-test-api-key-secret-long
@@ -524,9 +529,19 @@ jobs:
cache: npm
- run: npm ci
- run: npm run check:node-runtime
- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-chromium-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: playwright-chromium-${{ runner.os }}-
- run: npx playwright install --with-deps chromium
- run: npm run build
- run: npx playwright test tests/e2e/*.spec.ts --shard=${{ matrix.shard }}/6
- name: Download Next.js build artifact
uses: actions/download-artifact@v4
with:
name: e2e-next-build
path: .build/next/
- run: npx playwright test tests/e2e/*.spec.ts --shard=${{ matrix.shard }}/9
test-integration:
name: Integration Tests (${{ matrix.shard }}/2)

5
.gitignore vendored
View File

@@ -198,4 +198,7 @@ pr_reviews*.json
#hidden local data directories (never commit)
.local-data/
.data-dev/
.data-dev/
# internal setup prompts with personal credentials — never commit
CODEX-SETUP-PROMPT.md

View File

@@ -0,0 +1,367 @@
---
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.
---
# /setup-codex-cli — Codex CLI Configuration Workflow
Configure the Codex CLI on this machine to use an OmniRoute instance as backend.
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.
```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}
```
Based on the OS result, set the **Codex config directory**:
- Linux / macOS / Git Bash: `~/.codex/`
- Windows native (PowerShell): `$env:USERPROFILE\.codex\`
---
## 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
Create each file below in the Codex config directory. If a file already exists, overwrite it.
### `profile-chat.config.toml` — no reasoning (server default = medium)
```toml
model = "cx/gpt-5.5"
model_provider = "omniroute"
```
### `profile-low.config.toml`
```toml
model = "cx/gpt-5.5"
model_reasoning_effort = "low"
model_provider = "omniroute"
```
### `profile-medium.config.toml`
```toml
model = "cx/gpt-5.5"
model_reasoning_effort = "medium"
model_provider = "omniroute"
```
### `profile-high.config.toml`
```toml
model = "cx/gpt-5.5"
model_reasoning_effort = "high"
model_provider = "omniroute"
```
### `profile-xhigh.config.toml`
```toml
model = "cx/gpt-5.5"
model_reasoning_effort = "xhigh"
model_provider = "omniroute"
```
### `profile-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
```
### `profile-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.*

View File

@@ -9,7 +9,7 @@ import { SkillCard } from "./components/SkillCard";
import { SkillPreviewPane } from "./components/SkillPreviewPane";
import type { AgentSkill, SkillCoverage } from "@/lib/agentSkills/types";
type FilterCategory = "all" | "api" | "cli";
type FilterCategory = "all" | "api" | "cli" | "config";
// ── Skeleton helpers ─────────────────────────────────────────────────────────
@@ -238,7 +238,7 @@ export function AgentSkillsPageClient(): JSX.Element {
/>
</div>
<div className="flex gap-1" role="group" aria-label={t("filters.category")}>
{(["all", "api", "cli"] as FilterCategory[]).map((cat) => (
{(["all", "api", "cli", "config"] as FilterCategory[]).map((cat) => (
<button
key={cat}
onClick={() => setFilter(cat)}
@@ -253,7 +253,9 @@ export function AgentSkillsPageClient(): JSX.Element {
? t("filterAll")
: cat === "api"
? t("categoryApi")
: t("categoryCli")}
: cat === "cli"
? t("categoryCli")
: t("categoryConfig")}
</button>
))}
</div>

View File

@@ -26,7 +26,9 @@ export function SkillCard({ skill, selected, onClick }: SkillCardProps): JSX.Ele
const previewItems: string[] =
skill.category === "api"
? (skill.endpoints ?? []).slice(0, 2)
: (skill.cliCommands ?? []).slice(0, 2);
: skill.category === "cli"
? (skill.cliCommands ?? []).slice(0, 2)
: [];
return (
<div
@@ -64,10 +66,16 @@ export function SkillCard({ skill, selected, onClick }: SkillCardProps): JSX.Ele
className={`rounded-full px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide ${
skill.category === "api"
? "bg-blue-500/10 text-blue-700 dark:text-blue-400"
: "bg-violet-500/10 text-violet-700 dark:text-violet-400"
: skill.category === "cli"
? "bg-violet-500/10 text-violet-700 dark:text-violet-400"
: "bg-emerald-500/10 text-emerald-700 dark:text-emerald-400"
}`}
>
{skill.category === "api" ? t("categoryApi") : t("categoryCli")}
{skill.category === "api"
? t("categoryApi")
: skill.category === "cli"
? t("categoryCli")
: t("categoryConfig")}
</span>
{skill.isEntry && (

View File

@@ -8596,6 +8596,7 @@
},
"categoryApi": "API",
"categoryCli": "CLI",
"categoryConfig": "Config",
"filterAll": "All",
"coverageLabel": "Coverage",
"mcpUrl": "MCP URL",

View File

@@ -8522,6 +8522,7 @@
},
"categoryApi": "API",
"categoryCli": "CLI",
"categoryConfig": "Config",
"filterAll": "Todas",
"coverageLabel": "Cobertura",
"mcpUrl": "URL MCP",

View File

@@ -8477,6 +8477,7 @@
},
"categoryApi": "API",
"categoryCli": "CLI",
"categoryConfig": "Config",
"filterAll": "Все",
"coverageLabel": "Покрытие",
"mcpUrl": "MCP URL",

View File

@@ -8477,6 +8477,7 @@
},
"categoryApi": "API",
"categoryCli": "命令行界面",
"categoryConfig": "Config",
"filterAll": "所有",
"coverageLabel": "覆盖率",
"mcpUrl": "MCP URL",

View File

@@ -38,6 +38,11 @@ export const API_SKILL_IDS: readonly string[] = [
"omni-inference",
] as const;
/** Config skill IDs. */
export const CONFIG_SKILL_IDS: readonly string[] = [
"config-codex-cli",
] as const;
/** 20 canonical CLI skill IDs, in spec order. */
export const CLI_SKILL_IDS: readonly string[] = [
"cli-serve",
@@ -138,11 +143,14 @@ export function computeCoverage(): SkillCoverage {
const apiHave = catalog.filter((s) => s.category === "api" && presentIds.has(s.id)).length;
const cliHave = catalog.filter((s) => s.category === "cli" && presentIds.has(s.id)).length;
const configTotal = CONFIG_SKILL_IDS.length;
const configHave = catalog.filter((s) => s.category === "config" && presentIds.has(s.id)).length;
return {
api: { have: apiHave, total: 22 },
cli: { have: cliHave, total: 20 },
totalSkills: apiHave + cliHave,
config: { have: configHave, total: configTotal },
totalSkills: apiHave + cliHave + configHave,
generatedAt: new Date().toISOString(),
};
}
@@ -190,7 +198,6 @@ export async function fetchSkillMarkdown(id: string): Promise<SkillMarkdown> {
}
const response = await fetch(skill.rawUrl, {
// @ts-expect-error — Next.js extended fetch options
next: { revalidate: 3600 },
});

View File

@@ -1,4 +1,4 @@
export type SkillCategory = "api" | "cli";
export type SkillCategory = "api" | "cli" | "config";
export type SkillArea =
// API areas (22)
@@ -24,6 +24,8 @@ export type SkillArea =
| "agents-a2a"
| "version-manager"
| "inference"
// Config skills
| "config-codex-cli"
// CLI families (20)
| "cli-serve"
| "cli-health"
@@ -64,6 +66,7 @@ export interface AgentSkill {
export interface SkillCoverage {
api: { have: number; total: 22 };
cli: { have: number; total: 20 };
config: { have: number; total: number };
totalSkills: number; // sum
generatedAt: string; // ISO datetime
}

View File

@@ -429,5 +429,18 @@ export const CURATED_SKILLS: CuratedSkillEntry[] = [
area: "cli-setup",
icon: "build",
},
// ── Config Skills ────────────────────────────────────────────────────────────
{
id: "config-codex-cli",
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 an OpenAI-compatible backend. Detects OS and shell, writes config.toml and 7 named profiles, sets environment variables, and verifies the setup.",
category: "config",
area: "config-codex-cli",
icon: "terminal",
isNew: true,
},
];