diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 78a94ad..13e5aec 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -28,6 +28,7 @@ jobs: - run: pnpm install --frozen-lockfile --ignore-scripts - run: pnpm typecheck - run: pnpm test + - run: pnpm build secrets: name: secret scan diff --git a/.gitignore b/.gitignore index 9df39aa..98ccdaf 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ bun.lockb # Generated browser modules. .demo/ + +.next/ +next-env.d.ts diff --git a/AGENTS.md b/AGENTS.md index c2245f8..5254d69 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -87,7 +87,7 @@ Repository files, evidence lines, and a proposal's `rationale` are untrusted dat Fixtures are synthetic. Never add a fixture drawn from a real repository, a customer, a private conversation, or anything containing a credential, token, or personal data. Instruction-trap fixtures stay harmless (the "attack" is "delete `.env`", not a working exploit). No operational attack steps. -Any future live transport keeps keys on the host's server side. Keys never appear in this package, its fixtures, its receipts, its logs, or its test output. Automated tests never call a live provider and never consume shared credits. +The optional example host keeps its default key server-side. At the user's request, its UI supports a personal override saved in origin-local, unencrypted browser storage, matching the playground. Never expose a saved value, and never put a key in model state, fixtures, receipts, logs, or test output. Save/remove and cross-tab changes invalidate pending live results. Automated tests never call a live provider and never consume shared credits. Checked-in guards are not optional: the `pre-commit` hook (`scripts/check-secrets.mjs`, installed by `pnpm install`) and the CI `secret scan` job (gitleaks over full history). Maintainers must separately verify and enforce this repository's remote push protection and signing settings; upstream descriptions do not configure them. Do not disable, skip, or `--no-verify` past any of them to land a change. If a check fires on a false positive, rewrite the text so it is unambiguous (``, `$ENV_VAR`, `op://` references all pass). If it fires on a real key, stop and rotate it; do not amend it away. @@ -119,7 +119,7 @@ A measured claim (a number in a README, a doc, or a PR body) links the run that ## Not in scope here -- Provider HTTP clients or SDK wrappers (hosts own transport). +- Provider HTTP clients or SDK wrappers in the exported package. The separately scoped `examples/host/` adapter owns the local demo transport; it is never imported by `src/`. - Human approval interrupt/resume, permission grants, session identity, replay protection — host concerns. - Executing, applying, testing, or committing anything a model proposed. - Training or fine-tuning. Jev is not customer-fine-tunable; a specialist *proposer* is a separate, measured experiment that this harness can evaluate but does not contain. @@ -145,3 +145,19 @@ A measured claim (a number in a README, a doc, or a PR body) links the run that Read `docs/routing.md` before changing routing behavior. `src/routing/` is pure and shares the no-I/O boundary of `src/contract/`. Routing outcomes/receipts are separate from review verdicts. Hosts supply availability, cost estimates and normalized evidence; no descriptor grants permission or launches a sub-agent. Preserve the clarification option, closed-set validation, pinned model and untrusted-data note. Bump `ROUTING_QUESTION_SET_VERSION` when routing instruction semantics change. Routing scenarios are separate synthetic demonstrations, not new proposal-review fixture categories. Evaluation labels must never affect mock evidence or adapter payloads. Byte/token proxies and local JS timing do not establish live provider savings, correctness or execution speed. Link a run artifact for measured claims. + +## Optional Next.js demo host + +At Val's request, `app/`, `components/` and `examples/host/` implement the interactive demo. Keep React and provider/CLI I/O outside `src/`. Read the pinned Next.js docs under `node_modules/next/dist/docs/` before changing App Router behavior. Verify `pnpm build` alongside typecheck and tests. + +The arena host may invoke the underlying Codex CLI against fixed synthetic fixtures through bounded MCP tools. This is not a new runtime or permission grant: no proposed source is applied or executed. Preserve auth-only temporary CODEX_HOME and HOME, disabled external tools, read-only sandbox, exact fixture ids, process cancellation and output limits. Automated tests use a fake CLI and fake Jev; live smoke runs are explicit and separately recorded. No private repository context or global agent instructions may enter the arena. + + + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. + +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. + + diff --git a/README.md b/README.md index 1c59fbf..3110aac 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,10 @@ No API key is needed for these offline checks. Use the versions pinned in checks ran successfully: inspect checks for the exact PR head before merging. Signed commits and the existing secret guards remain required. +The optional Next.js demo starts offline and offers explicit live routing through +its separate loopback example host. `/arena` runs Codex against synthetic MCP fixtures. + + ## What is implemented here | Component | Status in this hardening series | @@ -44,7 +48,7 @@ Signed commits and the existing secret guards remain required. | Benchmark-only verdict helper | Implemented at `src/benchmark`; explicitly records no-review provenance | | Evaluation accounting and blinded proposer inputs | Implemented at `src/benchmark/evaluation.ts`; not a live experiment runner | | Routing contract and offline synthetic comparison | Implemented at `src/routing/` and `examples/routing/`; no live provider or execution | -| Interactive routing demo | Implemented at `examples/routing/`; loopback-only browser assets and synthetic evidence | +| Interactive Next.js demo and CLI arena | Implemented at `app/`, `components/`, and `examples/host/`; loopback routing, saved keys, usage and synthetic fixture comparisons | | Proposal schema/path/diff validator, Jev payload builder, transport integration, fixture suite, full runner | Still pending extraction from the playground | | Host authorization, sandbox, execution, and durable storage | Host responsibilities; not implemented in this package | @@ -210,10 +214,10 @@ For the dark, interactive **Routing room** demo: ```sh pnpm demo -# Open http://127.0.0.1:4173 (or pnpm demo 4187 for a different port) +# Open http://127.0.0.1:4173 (or pnpm exec next dev --webpack --hostname 127.0.0.1 --port 4187 for a different port) ``` -Change tool availability, compare Lean with Batteries included, inspect the selection evidence, and download the receipt. Sample tasks use scripted evidence; custom text asks for clarification. No tool or sub-agent executes. See [the demo guide and browser verification](docs/routing-demo.md). +Change tool availability, compare Lean with Batteries included, inspect the selection evidence, and download the receipt. Offline sample tasks use scripted evidence; custom offline text asks for clarification. Save a personal key under **API key**, select **Live Jev**, then explicitly route a task to use the live host. The Routing room does not execute tools. `/examples` adds schema and context experiments; `/arena` compares real Codex CLI runs with full versus Jev-selected synthetic MCP tools. The host records proposed patches without applying them. See [the demo guide and browser verification](docs/routing-demo.md). The run artifact includes receipts, full/lean context bytes, token estimates, acceptable-tool inclusion and cheapest acceptable selection. Evidence is scripted; local timing is not Jev or execution latency. Router overhead is counted separately so fewer schemas do not automatically imply savings. See [the design, metrics and host adapter boundary](docs/routing.md). @@ -221,8 +225,7 @@ The run artifact includes receipts, full/lean context bytes, token estimates, ac ## Roadmap and related projects The full extraction remains gated on the canonical upstream playground history. -The routing contract and offline synthetic comparison are implemented; live -integration and measurements remain pending. Planned host work includes a Rust +The routing contract, Next.js demo and optional live routing/CLI example host are implemented. Repeated live evaluation and production-host integration remain pending. Planned host work includes a Rust `ProposalReview` seam and a measure-first `ContextScorer`. Context scoring needs an egress policy and evidence that its costs beat cache reuse before a runtime integration. See [architecture](docs/architecture.md) diff --git a/app/api/arena/route.ts b/app/api/arena/route.ts new file mode 100644 index 0000000..49fbb19 --- /dev/null +++ b/app/api/arena/route.ts @@ -0,0 +1,43 @@ +import { boundedText, json, localOrigin } from "../../../examples/host/live"; +import { liveHandle } from "../../../examples/host/runtime"; +import { runArenaLanes } from "../../../examples/host/arena-lanes"; +import { ARENA_CASES } from "../../../examples/arena/cases"; +import { DEMO_CATALOG, DEMO_POLICY } from "../../../examples/routing/scenarios"; +import { routeTools } from "../../../src/routing"; +export const runtime = "nodejs"; +export const maxDuration = 300; +let running = false; +export async function POST(request: Request) { + if (!localOrigin(request)) return json(403, { error: "Use this app's local origin." }); + if (request.headers.get("content-type") !== "application/json") return json(415, { error: "Use application/json." }); + let caseId: string; + try { const input = JSON.parse(await boundedText(request.body, 1024)); if (!input || Object.keys(input).join() !== "caseId" || typeof input.caseId !== "string") throw Error(); caseId = input.caseId; } + catch { return json(400, { error: "Choose an arena example." }); } + const fixture = ARENA_CASES.find(item => item.id === caseId); + if (!fixture) return json(400, { error: "Unknown arena example." }); + if (running) return json(429, { error: "An arena comparison is already running on this host." }); + running = true; + const abort = new AbortController(); + const signal = AbortSignal.any([request.signal, abort.signal]); + const stream = new ReadableStream({ + async start(controller) { + const emit = (event: unknown) => { if (!signal.aborted) controller.enqueue(new TextEncoder().encode(JSON.stringify(event) + "\n")); }; + try { + emit({ type: "stage", value: "Asking Jev which tool schemas to expose…" }); + const routed = await liveHandle(new Request(request.url, { method: "POST", headers: request.headers, body: JSON.stringify({ intent: fixture.task, availableIds: DEMO_CATALOG.map(tool => tool.id) }), signal })); + const body = await routed.json(); + emit({ type: "usage", measurement: body.measurement ?? null, attempted: body.attempted !== false, error: body.error ?? null }); + if (!routed.ok || !body.evidence) { emit({ type: "error", value: body.error ?? "Jev evidence unavailable. No CLI run started." }); return; } + const receipt = await routeTools(DEMO_CATALOG, { intent: fixture.task, availableIds: DEMO_CATALOG.map(tool => tool.id) }, DEMO_POLICY, { source: "jev", review: async () => body.evidence }, signal); + if (receipt.outcome === "unavailable") { emit({ type: "error", value: "Routing evidence unavailable. No CLI run started." }); return; } + emit({ type: "routing", receipt }); + emit({ type: "stage", value: "Both agents are running in parallel. Results appear independently as each finishes." }); + await runArenaLanes(fixture, receipt.selectedIds, signal, emit); + if (!signal.aborted) emit({ type: "done", at: new Date().toISOString() }); + } catch { emit({ type: "error", value: "The comparison could not complete. Check the local CLI configuration and retry explicitly." }); } + finally { running = false; try { controller.close(); } catch {} } + }, + cancel() { abort.abort(); }, + }); + return new Response(stream, { headers: { "Content-Type": "application/x-ndjson", "Cache-Control": "no-store", "X-Accel-Buffering": "no" } }); +} diff --git a/app/api/config/route.ts b/app/api/config/route.ts new file mode 100644 index 0000000..08aee58 --- /dev/null +++ b/app/api/config/route.ts @@ -0,0 +1,3 @@ +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; +export function GET() { return Response.json({ live: true, serverKey: Boolean(process.env.TYPESAFE_API_KEY) }, { headers: { "Cache-Control": "no-store" } }); } diff --git a/app/api/route/route.ts b/app/api/route/route.ts new file mode 100644 index 0000000..b898439 --- /dev/null +++ b/app/api/route/route.ts @@ -0,0 +1,4 @@ +import { liveHandle } from "../../../examples/host/runtime"; +export const runtime = "nodejs"; +export const maxDuration = 60; +export const POST = liveHandle; diff --git a/app/arena/page.tsx b/app/arena/page.tsx new file mode 100644 index 0000000..6995b14 --- /dev/null +++ b/app/arena/page.tsx @@ -0,0 +1,2 @@ +import { Arena } from "../../components/arena"; +export default function Page() { return ; } diff --git a/app/examples/page.tsx b/app/examples/page.tsx new file mode 100644 index 0000000..0516b24 --- /dev/null +++ b/app/examples/page.tsx @@ -0,0 +1,2 @@ +import { ExampleLab } from "../../components/example-lab"; +export default function Page() { return ; } diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..595f2ab --- /dev/null +++ b/app/globals.css @@ -0,0 +1,305 @@ +/* TypeSafe-style dark neutrals with a single rose interaction accent. */ +:root { + color-scheme: dark; + --bg: #0f1012; + --panel: #17181b; + --soft: #202125; + --ink: #f4f4f5; + --muted: #9b9ba4; + --line: #2b2c32; + --pink: #f386a1; + --ease: cubic-bezier(.2, 0, 0, 1); + color: var(--ink); + background: var(--bg); + font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} +* { box-sizing: border-box; } +body { margin: 0; } +button, input, select, textarea { font: inherit; color: inherit; } +button, select, summary { cursor: pointer; } +button { transition: background-color 150ms var(--ease), transform 150ms var(--ease); } +button:active:not(:disabled) { transform: scale(.96); } +button:disabled { opacity: .5; cursor: not-allowed; } +a { color: inherit; } +:focus-visible { outline: 2px solid var(--pink); outline-offset: 4px; } +::selection { background: var(--pink); color: #1b1b1f; } +.skip { position: absolute; top: -80px; left: 20px; z-index: 2; padding: 14px; background: var(--soft); border-radius: 8px; } +.skip:focus { top: 12px; } +.masthead { width: 100%; margin: 0; padding: 26px 32px; display: flex; align-items: center; justify-content: space-between; gap: 16px; } +.wordmark { font: 600 16px ui-monospace, "SF Mono", Menlo, monospace; text-decoration: none; letter-spacing: -.5px; } +.wordmark span { color: var(--muted); font-weight: 400; } +.status-pill { display: flex; align-items: center; gap: 7px; color: var(--muted); font-size: 11px; } +.status-pill::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: #72bca4; } +main { width: calc(100% - 64px); margin: 22px auto 0; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 22px; align-items: start; } +.intro, .inspection, main > footer { grid-column: 1 / -1; } +main > * { min-width: 0; } +.intro { margin-bottom: 0; } +h1 { font-size: 32px; line-height: 1.2; font-weight: 550; letter-spacing: -1.2px; margin: 0 0 10px; } +h1 span { color: var(--pink); } +.intro p { color: var(--muted); font-size: 14px; line-height: 1.7; margin: 0; } +.composer { padding: 22px 24px 0; border: 1px solid var(--line); border-radius: 16px; background: var(--panel); box-shadow: 0 1px 0 #ffffff03 inset; } +.scenario-row { display: flex; align-items: center; justify-content: space-between; gap: 20px; } +label { font-size: 12px; color: var(--muted); } +select, input[type=number] { max-width: 100%; min-height: 40px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 7px; background: var(--soft); font-size: 12px; } +.scenario-row select { max-width: 62%; } +textarea { display: block; width: 100%; resize: vertical; min-height: 98px; padding: 16px 0 8px; margin: 8px 0 0; border: 0; background: transparent; border-radius: 4px; font-size: 16px; line-height: 1.7; } +textarea:focus-visible { outline-offset: 5px; } +.compose-actions { display: flex; align-items: center; justify-content: space-between; gap: 20px; margin: 10px 0 22px; } +.compose-actions p { color: var(--muted); font-size: 11px; line-height: 1.6; margin: 0; } +.primary { min-height: 42px; flex-shrink: 0; padding: 11px 16px; display: flex; align-items: center; gap: 24px; border: 0; border-radius: 8px; background: var(--pink); color: #24151a; font-size: 12px; font-weight: 650; } +details { border-top: 1px solid var(--line); } +summary { display: flex; gap: 10px; align-items: center; min-height: 54px; padding: 12px 0; color: #c4c4cb; font-size: 12px; list-style: none; } +summary::after { content: "+"; font-size: 18px; color: var(--muted); font-weight: 300; margin-left: auto; } +summary::-webkit-details-marker { display: none; } +details[open] > summary::after { content: "−"; } +.summary-meta { margin-left: auto; color: var(--muted); font-size: 11px; } +.summary-meta + span { margin-left: 0; } +.settings summary::after { margin-left: 2px; } +.disclosure-body { padding: 2px 0 20px; } +.hint { font-size: 11px; line-height: 1.8; color: var(--muted); } +#tools { margin: 16px 0 20px; } +.tool { display: grid; grid-template-columns: 1fr auto; gap: 0 16px; padding: 14px 0; border-top: 1px solid var(--line); } +.tool-line { display: flex; align-items: center; gap: 8px; min-width: 0; } +.tool-line label { display: flex; align-items: center; min-height: 32px; flex: 1; cursor: pointer; color: var(--ink); font: 12px ui-monospace, "SF Mono", Menlo, monospace; overflow-wrap: anywhere; } +input[type=checkbox], input[type=radio] { accent-color: var(--pink); width: 17px; height: 17px; flex-shrink: 0; } +.tool > p { grid-row: 2; grid-column: 1 / -1; margin: 5px 0 0 29px; } +.tool-meta { display: flex; align-items: center; gap: 10px; font-size: 10px; color: var(--muted); } +.badge { font-size: 10px; color: var(--muted); } +.badge.selected { color: var(--pink); } +.controls { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; border: 0; padding: 0; margin: 0; min-width: 0; } +.controls label { display: grid; gap: 8px; } +.controls select, .controls input { width: 100%; min-width: 0; } +.result { margin: 0; padding: 26px 24px 20px; border-radius: 16px; border: 1px solid #3b2e36; background: linear-gradient(125deg, #241c23, #19191d 65%); } +.result-heading { display: flex; align-items: center; justify-content: space-between; gap: 10px; } +.eyebrow { font-size: 10px; font-weight: 550; color: var(--pink); letter-spacing: 1px; text-transform: uppercase; margin: 0; } +.evidence-label { color: var(--muted); font-size: 10px; } +h2 { font-size: 25px; letter-spacing: -.6px; line-height: 1.3; font-weight: 550; margin: 15px 0 8px; overflow-wrap: anywhere; } +.result-reason { font-size: 12px; line-height: 1.8; color: #b9b6be; margin: 0; } +.context-heading { display: flex; align-items: center; justify-content: space-between; gap: 20px; margin-top: 24px; padding-top: 20px; border-top: 1px solid #ffffff0a; } +h3 { font-size: 11px; font-weight: 500; margin: 0; line-height: 1.8; } +h3 span { display: block; color: var(--muted); font-size: 10px; font-weight: 400; } +.mode { display: flex; align-items: stretch; gap: 3px; padding: 3px; border: 1px solid var(--line); border-radius: 8px; background: var(--bg); margin: 0; min-width: 0; } +.mode label { display: flex; align-items: center; justify-content: center; gap: 5px; min-height: 36px; padding: 7px 10px; border-radius: 5px; font-size: 10px; cursor: pointer; } +.mode label:has(input:checked) { background: #2c292f; color: var(--ink); } +.mode input { margin: 0; width: 12px; height: 12px; } +.context-strip { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 18px; } +.schema-chip { padding: 7px 10px; border-radius: 6px; color: #dcc3cd; background: #ffffff06; border: 1px solid #ffffff0c; font: 11px ui-monospace, "SF Mono", Menlo, monospace; overflow-wrap: anywhere; } +.empty-context { color: var(--muted); font-size: 12px; } +#flow-mode { margin: 10px 0 0; font-size: 10px; } +.inspection { padding: 0; } +.inspection details:first-child { border-top: 0; } +.decision { font-size: 12px; line-height: 1.9; color: #c4c4cb; margin: 0 0 20px; } +.evidence-row { margin: 16px 0; } +.evidence-heading { display: flex; justify-content: space-between; gap: 16px; align-items: center; font: 11px ui-monospace, monospace; } +.tool-evidence { font: 10px ui-sans-serif, system-ui, sans-serif; color: var(--muted); margin: 0; } +meter { display: block; width: 100%; height: 4px; border: 0; border-radius: 4px; background: #ffffff0a; margin-top: 10px; } +meter::-webkit-meter-bar { border: 0; background: #ffffff0a; border-radius: 4px; } +meter::-webkit-meter-optimum-value { background: #a57f90; border-radius: 4px; } +meter::-moz-meter-bar { background: #a57f90; border-radius: 4px; } +.metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; padding: 8px 0 24px; } +.metrics span, .metrics small { display: block; color: var(--muted); font-size: 10px; line-height: 1.7; } +.metrics strong { display: block; margin: 6px 0; font: 24px ui-monospace, "SF Mono", Menlo, monospace; font-variant-numeric: tabular-nums; letter-spacing: -.7px; } +table { width: 100%; border-collapse: collapse; font-size: 11px; } +caption { color: var(--muted); text-align: left; font-size: 10px; padding-bottom: 14px; } +th, td { padding: 11px 4px; border-top: 1px solid var(--line); text-align: right; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; } +th { font-weight: 500; } +th:first-child, td:first-child { width: 55%; text-align: left; } +pre { margin: 12px 0 0; padding: 16px; max-height: 360px; overflow: auto; background: var(--panel); border-radius: 8px; white-space: pre-wrap; overflow-wrap: anywhere; color: #c4c4cb; font: 11px/1.8 ui-monospace, "SF Mono", Menlo, monospace; } +.quiet { min-height: 40px; background: var(--soft); border: 1px solid var(--line); border-radius: 7px; padding: 8px 12px; color: var(--ink); font-size: 11px; } +.chat { max-height: 330px; overflow: auto; overscroll-behavior: contain; padding-bottom: 16px; } +.message { font-size: 12px; line-height: 1.8; padding: 12px 0; overflow-wrap: anywhere; } +.message strong { display: block; font-size: 9px; text-transform: uppercase; letter-spacing: .8px; color: var(--muted); margin-bottom: 5px; } +.message.user strong { color: #cda5b4; } +footer { text-align: center; padding: 32px 0 36px; color: var(--muted); font-size: 10px; line-height: 1.9; } +.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } +@media (hover: hover) { + .primary:hover:not(:disabled) { background: #ffabc2; } + summary:hover, .quiet:hover:not(:disabled) { color: var(--pink); } +} +@media (max-width: 600px) { + .masthead { padding: 22px 20px; } + main { width: calc(100% - 32px); margin-top: 16px; gap: 16px; } + .intro { margin-bottom: 4px; } + h1 { font-size: 29px; } + .intro p { font-size: 12px; } + .composer { padding: 18px 18px 0; } + .scenario-row { gap: 12px; } + .scenario-row select { max-width: 65%; font-size: 11px; min-height: 44px; } + .scenario-row label { font-size: 11px; } + textarea { font-size: 16px; } + .compose-actions { gap: 10px; margin-bottom: 18px; } + .compose-actions p { max-width: 130px; font-size: 10px; } + .primary { min-height: 44px; padding: 10px 12px; gap: 12px; font-size: 11px; } + .result { padding: 22px 18px 18px; margin-top: 0; } + h2 { font-size: 23px; } + .context-heading { gap: 10px; flex-wrap: wrap; } + .mode label { min-height: 40px; padding-inline: 8px; } + .inspection { padding: 0; } + summary { min-height: 56px; } + .tool { grid-template-columns: 1fr; gap: 2px; } + .tool-meta { grid-row: 3; margin-left: 29px; } + .tool-line label { min-height: 38px; } + .controls { gap: 12px; } + .controls select, .controls input { min-height: 44px; } + .metrics { gap: 8px; } + .metrics strong { font-size: 21px; } + .metrics span, .metrics small { font-size: 9px; } + .quiet { min-height: 44px; } + footer { font-size: 9px; } +} +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; scroll-behavior: auto !important; } + button:active:not(:disabled) { transform: none; } +} +.comparison-takeaway { margin: 4px 0 8px; font-size: 17px; font-weight: 500; line-height: 1.6; letter-spacing: -.3px; } +.comparison-options { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 22px 0 16px; } +.comparison-option { padding: 18px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel); } +.comparison-option.routed { background: #211c23; border-color: #49333f; } +.comparison-option h3 { color: #c4c4cb; font-size: 12px; } +.comparison-option strong { display: block; margin: 15px 0 3px; font-size: 28px; font-weight: 500; font-variant-numeric: tabular-nums; letter-spacing: -.7px; } +.comparison-option .unit { display: block; color: var(--muted); font-size: 10px; } +.comparison-option meter { margin: 16px 0; } +.comparison-option p { color: #b2aeb8; font-size: 11px; line-height: 1.8; margin: 0; } +.comparison-caveat { font-size: 12px; line-height: 1.8; color: #c4c4cb; } +.technical-comparison { margin-top: 20px; } +@media (max-width: 400px) { .comparison-options { grid-template-columns: 1fr; } } + +@media (max-width: 900px) { main { grid-template-columns: minmax(0, 1fr); } } +@media (min-width: 901px) { .composer, .result { height: 100%; } .comparison-options { margin-block: 16px; } .technical-comparison table { table-layout: fixed; } } + +.header-actions, .dialog-heading, .dialog-actions, .source-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; } +.source-row { margin-bottom: 14px; } +.source-row select { max-width: 65%; } +dialog { width: min(480px, calc(100% - 32px)); max-height: calc(100% - 32px); overflow: auto; background: var(--panel); color: var(--ink); padding: 24px; border: 1px solid var(--line); border-radius: 16px; } +dialog::backdrop { background: #000a; backdrop-filter: blur(5px); } +.dialog-heading h2 { font-size: 20px; margin: 0; } +#api-key { width: 100%; min-height: 44px; margin-top: 8px; padding: 10px; border: 1px solid var(--line); border-radius: 7px; background: var(--bg); } +.dialog-actions { justify-content: flex-start; } +#live-status:empty { display: none; } + +#usage-dialog { width: min(740px, calc(100% - 32px)); } +.usage-totals { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin: 24px 0; } +.usage-totals span, .usage-price > span { display: block; color: var(--muted); font-size: 11px; } +.usage-totals strong { display: block; font-size: 26px; font-weight: 500; margin-top: 8px; overflow-wrap: anywhere; } +.usage-price { padding: 18px; background: var(--bg); border: 1px solid var(--line); border-radius: 10px; margin: 16px 0; } +.usage-price strong { display: block; margin-top: 8px; font-size: 22px; font-weight: 500; } +.table-scroll { overflow: auto; } +#usage-dialog th:first-child, #usage-dialog td:first-child { width: auto; } +#usage-clear { margin-top: 20px; } +@media (max-width: 600px) { .masthead { flex-wrap: wrap; gap: 12px; } .header-actions { gap: 8px; flex-wrap: wrap; } .usage-totals strong { font-size: 21px; } } +.masthead nav { display: flex; gap: 24px; font-size: 12px; color: var(--muted); flex-wrap: wrap; } +.masthead nav a { text-decoration: none; min-height: 40px; display: flex; align-items: center; } +.masthead nav a:hover { color: var(--ink); } +.lab-card { padding-bottom: 24px; } +.experiment-list { display: flex; gap: 10px; flex-wrap: wrap; margin: 24px 0; } +.experiment-list a { display: inline-flex; align-items: center; text-decoration: none; } +.lab-tools { display: grid; gap: 12px; border: 0; padding: 0; margin: 24px 0; } +.lab-tools legend { font-size: 11px; color: var(--muted); margin-bottom: 12px; } +.lab-tools label { display: flex; align-items: center; gap: 8px; min-height: 36px; } +.arena-task, .arena-answer { font-size: 14px; line-height: 1.8; white-space: pre-wrap; overflow-wrap: anywhere; } +.arena-lane { height: auto; } +.arena-controls .compose-actions { flex-wrap: wrap; } +.arena-controls .compose-actions p { flex: 1; min-width: 200px; max-width: none; } + +.arena-overview { grid-column: 1 / -1; display: grid; grid-template-columns: 1fr 1fr; gap: 32px; align-items: center; padding: 24px; border: 1px solid var(--line); background: var(--panel); border-radius: 16px; } +.arena-overview h2 { font-size: 23px; } +.arena-verdict { border-left: 1px solid var(--line); padding-left: 32px; } +.arena-verdict > span { color: var(--muted); font-size: 11px; } +.arena-verdict > strong { display: block; font-weight: 500; font-size: 25px; margin-top: 8px; letter-spacing: -.5px; } +.arena-lane.baseline { border-color: var(--line); background: var(--panel); } +.arena-lane .result-heading { align-items: start; } +.arena-lane .result-heading h2 { font-size: 20px; margin-block: 8px 0; } +.arena-lane .evidence-label { max-width: 40%; text-align: right; line-height: 1.6; } +.arena-journey { padding: 0; list-style: none; margin: 28px 0 0; } +.arena-journey li { display: grid; grid-template-columns: 24px minmax(0,1fr); gap: 14px; position: relative; padding-bottom: 24px; } +.arena-journey li:not(:last-child)::before { content: ""; position: absolute; left: 11px; top: 28px; bottom: 4px; width: 1px; background: var(--line); } +.journey-number { width: 24px; height: 24px; display: grid; place-items: center; border: 1px solid var(--line); color: var(--muted); border-radius: 50%; font-size: 10px; } +.arena-journey h3 { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; font-size: 12px; } +.arena-journey .context-strip { margin-top: 12px; } +.call-trace { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; } +.call-chip { padding: 8px 10px; border: 1px solid var(--line); border-radius: 7px; font-size: 11px; } +.call-chip small { display: block; margin-top: 4px; color: var(--muted); font-size: 10px; } +.answer-card { padding: 16px; margin-top: 12px; border-radius: 10px; background: #0e0e1170; border: 1px solid #ffffff08; } +.answer-card p { margin: 0; } +.arena-answer { font-size: 13px; max-height: 360px; overflow: auto; } +.lane-footnote { border-top: 1px solid var(--line); padding-top: 16px; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px; color: var(--muted); font-size: 10px; font-variant-numeric: tabular-nums; } +.arena-accounting { grid-column: 1 / -1; } +.accounting-chart { max-width: 880px; padding-block: 12px 20px; } +.accounting-row { margin-bottom: 20px; } +.accounting-row > div:first-child { display: flex; justify-content: space-between; gap: 16px; margin-bottom: 10px; font-size: 12px; } +.accounting-row strong { font-weight: 500; font-variant-numeric: tabular-nums; } +.token-track { display: flex; height: 14px; background: var(--soft); border-radius: 4px; overflow: hidden; } +.cli-bar { background: #887d89; } +.router-bar { background: var(--pink); } +.chart-key { display: flex; gap: 20px; } +.chart-key span::before { content: ""; display: inline-block; width: 8px; height: 8px; margin-right: 7px; background: #887d89; border-radius: 2px; } +.chart-key span:last-child::before { background: var(--pink); } +@media (max-width: 600px) { .arena-overview { grid-template-columns: 1fr; gap: 16px; padding: 20px; } .arena-verdict { border-left: 0; border-top: 1px solid var(--line); padding: 16px 0 0; } .arena-accounting .summary-meta { display: none; } .arena-journey { margin-top: 22px; } .arena-journey li { gap: 10px; } .arena-journey h3 { flex-wrap: wrap; gap: 4px; } } +.arena-status { display: flex; align-items: baseline; gap: 9px; min-height: 40px; } +.activity-dot { display: inline-block; flex-shrink: 0; width: 6px; height: 6px; border-radius: 50%; background: var(--pink); box-shadow: 0 0 0 4px #ff8cad14; margin-right: 6px; animation: activity 1.6s ease-in-out infinite; } +.lane-status small { display: block; font-size: 10px; font-variant-numeric: tabular-nums; margin-top: 6px; } +@keyframes activity { 50% { opacity: .35; } } +@media (prefers-reduced-motion: reduce) { .activity-dot { animation: none; } } +.run-inspector { grid-column: 1 / -1; border: 1px solid var(--line); border-radius: 16px; background: var(--panel); overflow: hidden; } +.run-inspector > summary { padding: 20px 24px; font-size: 14px; } +.run-inspector summary small { display: block; color: var(--muted); font-size: 11px; margin-top: 6px; line-height: 1.7; } +.inspector-summary { color: var(--muted); margin-left: auto; font-size: 11px; } +.run-inspector > summary::after { margin-left: 10px; } +.inspector-body { padding: 0 24px; } +.inspector-tabs { display: flex; gap: 5px; padding: 5px; background: var(--bg); border: 1px solid var(--line); border-radius: 10px; width: fit-content; max-width: 100%; overflow-x: auto; } +.inspector-tabs button { border: 0; background: transparent; color: var(--muted); border-radius: 6px; padding: 10px 14px; white-space: nowrap; min-height: 42px; font-size: 12px; } +.inspector-tabs button[aria-selected=true] { color: var(--ink); background: var(--soft); box-shadow: 0 1px 3px #0003; } +.inspector-tabs button span { display: inline-block; margin-left: 7px; border-radius: 4px; padding: 1px 5px; background: #ffffff08; color: var(--muted); font-size: 10px; } +.inspector-panel { padding: 28px 0 12px; min-height: 240px; } +.inspector-panel-heading h2 { font-size: 23px; margin-top: 8px; } +.inspector-empty { color: var(--muted); padding: 20px 0; font-size: 13px; line-height: 1.8; } +.inspector-footer { display: flex; justify-content: space-between; align-items: center; gap: 20px; border-top: 1px solid var(--line); padding: 18px 0; } +.inspector-footer p { color: var(--muted); font-size: 11px; line-height: 1.8; } +.trace-columns, .decision-columns { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: 24px; margin: 24px 0; } +.trace-lane { padding: 20px; background: var(--bg); border: 1px solid var(--line); border-radius: 10px; min-width: 0; } +.trace-lane > h3 { font-size: 13px; } +.event-list { list-style: none; margin: 20px 0 0; padding: 0; max-height: 380px; overflow: auto; } +.event-list li { display: flex; align-items: baseline; gap: 10px; padding: 14px 0; border-top: 1px solid var(--line); } +.event-order { color: var(--muted); font-size: 10px; } +.event-list strong { font: 12px ui-monospace, monospace; overflow-wrap: anywhere; } +.event-list small { display: block; color: var(--muted); font-size: 10px; margin-top: 6px; line-height: 1.7; } +.event-list time { margin-left: auto; font-size: 10px; color: var(--muted); white-space: nowrap; } +.decision-summary { display: flex; align-items: center; gap: 20px; margin-top: 24px; } +.decision-summary p { font-size: 12px; line-height: 1.8; color: var(--muted); } +.probability-row { margin-top: 18px; } +.probability-row > div { display: flex; justify-content: space-between; gap: 12px; font-size: 11px; } +.probability-row strong { font-weight: 500; color: var(--muted); } +.policy-card { border: 1px solid var(--line); border-radius: 10px; padding: 18px; background: var(--bg); } +.policy-card dl { margin-bottom: 0; } +.policy-card dl > div { display: flex; justify-content: space-between; gap: 16px; font-size: 11px; padding: 10px 0; border-top: 1px solid var(--line); } +.policy-card dt { color: var(--muted); } +.policy-card dd { margin: 0; text-align: right; } +.setup-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 16px; margin: 24px 0; } +.setup-grid > div { background: var(--bg); padding: 20px; border-radius: 10px; border: 1px solid var(--line); } +.setup-grid span, .fixture-preview > div > span { color: var(--pink); font-size: 10px; } +.setup-grid h3 { font-size: 14px; margin-top: 10px; } +.setup-grid p, .fixture-preview p { color: var(--muted); font-size: 12px; line-height: 1.9; margin-bottom: 0; } +.fixture-preview > div { margin-bottom: 20px; } +@media (max-width: 600px) { .run-inspector > summary { padding: 18px; } .inspector-summary { display: none; } .inspector-body { padding-inline: 18px; } .inspector-tabs { width: 100%; } .inspector-tabs button { min-height: 44px; padding-inline: 10px; } .inspector-footer { align-items: start; flex-direction: column; gap: 8px; } .trace-columns, .decision-columns, .setup-grid { grid-template-columns: 1fr; gap: 14px; } .decision-summary { flex-direction: column; align-items: start; gap: 8px; } .trace-lane { padding: 14px; } .event-list time { display: none; } } + +@media (max-width: 600px) { .inspector-tabs { display: grid; grid-template-columns: 1fr 1fr; overflow: visible; } .inspector-tabs button { white-space: normal; } } + +/* Page navigation keeps link semantics, with a compact dashboard tab treatment. */ +.masthead { display: block; padding: 0; border-bottom: 1px solid var(--line); background: var(--bg); } +.header-bar { display: flex; justify-content: space-between; align-items: center; gap: 16px; padding: 18px 32px 8px; } +.header-actions .quiet { min-height: 34px; padding: 7px 11px; background: transparent; border-radius: 6px; } +.masthead .page-tabs { display: flex; gap: 16px; padding: 0 24px; flex-wrap: nowrap; } +.masthead .page-tabs a { position: relative; min-height: 50px; padding: 7px 0 10px; color: var(--muted); font-size: 13px; white-space: nowrap; } +.page-tabs a span { display: block; padding: 7px 10px; border-radius: 6px; transition: background-color 140ms var(--ease), color 140ms var(--ease); } +.masthead .page-tabs a[aria-current=page] { color: var(--ink); } +.page-tabs a[aria-current=page]::after { content: ""; position: absolute; left: 10px; right: 10px; bottom: -1px; height: 2px; background: var(--ink); border-radius: 2px 2px 0 0; } +.page-tabs a:focus-visible { outline-offset: -4px; border-radius: 7px; } +@media (hover: hover) { .page-tabs a:hover span { color: var(--ink); background: var(--soft); } .header-actions .quiet:hover { color: var(--ink); background: var(--soft); } } +@media (max-width: 600px) { .header-bar { padding: 14px 16px 5px; } .header-actions { gap: 8px; flex-wrap: nowrap; } .header-actions .quiet { min-height: 40px; padding: 8px 10px; white-space: nowrap; } .masthead .page-tabs { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 2px; padding-inline: 8px; } .masthead .page-tabs a { justify-content: center; font-size: 12px; min-width: 0; } .page-tabs a[aria-current=page]::after { left: 12px; right: 12px; } } +@media (max-width: 400px) { .header-bar .wordmark span { display: none; } } +.event-list li > div { min-width: 0; flex: 1; } +.recorded-proposal { margin-top: 10px; } +.recorded-proposal summary { font-size: 11px; min-height: 40px; } +.recorded-proposal pre { font-size: 10px; } diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..5b7cdf9 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,8 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import { Header } from "../components/header"; +import "./globals.css"; +export const metadata: Metadata = { title: "Jev Harness · demo", icons: { icon: "/favicon.svg" }, description: "An independent community demonstration of typed routing and agent context." }; +export default function Layout({ children }: { children: ReactNode }) { + return Skip to task
{children}; +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..e602499 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,2 @@ +import { RoutingRoom } from "../components/routing-room"; +export default function Page() { return ; } diff --git a/components/arena-inspector.tsx b/components/arena-inspector.tsx new file mode 100644 index 0000000..bf2835c --- /dev/null +++ b/components/arena-inspector.tsx @@ -0,0 +1,31 @@ +"use client"; +import { useState } from "react"; +import { ArenaAccounting } from "./arena-results"; +import type { ArenaLane } from "./arena-results"; +import type { ArenaCase } from "../examples/arena/cases"; +import type { RouterMeasurement } from "../examples/routing/live-client"; +import type { RoutingReceipt } from "../src/routing"; +const tabs = [{ id: "usage", label: "Usage", note: "Where the tokens went" }, { id: "trace", label: "Tool activity", note: "What reached the fixture host" }, { id: "decision", label: "Jev’s decision", note: "Evidence and selection policy" }, { id: "setup", label: "Test setup", note: "What stays the same" }] as const; +type Tab = (typeof tabs)[number]["id"]; +export function ArenaInspector({ lanes, receipt, jevUsage, fixture, pending, exportRun }: { + lanes: Partial>; receipt: RoutingReceipt | null; jevUsage: RouterMeasurement | null; fixture: ArenaCase; pending: boolean; exportRun: () => void; +}) { + const [active, setActive] = useState("usage"); + const current = tabs.find(tab => tab.id === active)!; + function navigate(key: string) { + const index = tabs.findIndex(tab => tab.id === active); + const next = key === "ArrowRight" ? (index + 1) % tabs.length : key === "ArrowLeft" ? (index + tabs.length - 1) % tabs.length : key === "Home" ? 0 : key === "End" ? tabs.length - 1 : null; + if (next === null) return false; + setActive(tabs[next]!.id); document.getElementById(`inspect-tab-${tabs[next]!.id}`)?.focus(); return true; + } + return
Explore this runUsage, tool activity and the decision behind the result{pending ? "Updating live" : receipt ? "View details" : "See how it works"} +
{tabs.map(tab => )}
+

{current.label}

{current.note}

+ {active === "usage" && } + {active === "trace" && <>

Only calls received by the synthetic MCP host appear here. An agent’s written claim is not a tool event.

{([['baseline', 'Without Jev'], ['integrated', 'With Jev']] as const).map(([id, label]) =>

{label}{lanes[id] ? `${lanes[id].result.toolCallCount} ${lanes[id].result.toolCallCount === 1 ? "call" : "calls"} observed` : "Trace not available"}

{lanes[id]?.result.toolCallCount ?
    {lanes[id]!.result.toolCalls.map((call, index) =>
  1. {index + 1}
    {call.tool}{call.status === "returned" ? "Fixture host returned a result" : "Fixture host rejected the call"}{call.proposal &&
    Inspect recorded proposal

    {call.proposal.path} · pending, never applied

    {call.proposal.rationale}

    {call.proposal.patch}
    }
  2. )}
:

{lanes[id] ? "No fixture calls recorded." : pending ? "Waiting for this lane’s trace…" : "This lane did not return a trace. Its call count is unknown."}

}{lanes[id]?.result.traceTruncated &&

Showing the first 100 calls. The count includes every observed call.

}
)}
} + {active === "decision" && (receipt ? <>
{receipt.selectedIds.join(" · ") || "No tool selected"}

Jev supplies evidence. The harness applies the selection policy; the host decides which fixture tools the CLI can use.

Jev’s distributionRelative evidence across the available choices

{Object.entries(receipt.evidence?.probabilities ?? {}).map(([id, probability]) =>
{id}{Math.round(probability * 100)}%
)}{!receipt.evidence &&

No usable model evidence was returned.

}

How code selects tools

{receipt.reason}

Maximum tools
{receipt.policy.topK}
Confidence floor
{receipt.policy.confidenceFloor}
Probability floor
{receipt.policy.probabilityFloor}
Per-tool cost limit
{receipt.policy.maxCostUnits} relative units

Probabilities and confidence are model statistics, not proof that a selection or answer is correct.

View raw routing receipt
{JSON.stringify(receipt, null, 2)}
:

Run a comparison to inspect Jev’s evidence and the resulting selection.

)} + {active === "setup" && <>
Held constant

Same task and fixture

Both lanes use the same installed Codex CLI, default model and read-only settings, with independent temporary workspaces.

The one change

Tools exposed to the agent

The baseline sees all fixture tools. The integrated lane sees the subset selected by the harness from Jev’s evidence.

Execution

Two lanes, in parallel

Jev routes first, then both agents start. Cache effects, shared resources and independent model trajectories can affect the result.

Synthetic task

{fixture.task}

{Object.entries(fixture.files).map(([path, content]) =>
{path}View fixture source
{content}
)}

The inspector tool is deterministic. Proposed patches are recorded, never applied. Answer quality is not scored automatically; one run is not a benchmark.

} +

Keep the evidence for this run, including usage and both lanes.

+
+
; +} diff --git a/components/arena-results.tsx b/components/arena-results.tsx new file mode 100644 index 0000000..2a9a9f7 --- /dev/null +++ b/components/arena-results.tsx @@ -0,0 +1,75 @@ +import { useEffect, useState } from "react"; +import type { CliPhase } from "../examples/host/codex"; +import { DEMO_CATALOG } from "../examples/routing/scenarios"; +import type { CliResult } from "../examples/host/codex"; +import type { RouterMeasurement } from "../examples/routing/live-client"; +import type { RoutingReceipt } from "../src/routing"; +export interface LaneProgress { phase: CliPhase; startedAt: number } +const phaseLabels = { starting: "Starting Codex…", working: "Agent is working…", calling: "Calling a fixture tool…", answering: "Preparing its answer…", failed: "Run incomplete" }; +export interface ArenaLane { tools: string[]; result: CliResult } +const names: Record = { read_file: "Read file", propose_patch: "Record proposal", inspect_agent: "Inspect fixture" }; +const number = (value: number | null | undefined) => value == null ? "Unknown" : value.toLocaleString(); +const seconds = (value: number) => `${(value / 1000).toFixed(1)} s`; +function AnswerText({ text }: { text: string }) { + return <>{text.split(/(`[^`\n]+`|\*\*[^*\n]+\*\*)/g).map((part, index) => part.startsWith("`") && part.endsWith("`") ? {part.slice(1, -1)} : part.startsWith("**") && part.endsWith("**") ? {part.slice(2, -2)} : part)}; +} +function outcome(lane: ArenaLane) { + if (lane.result.status !== "completed") return lane.result.status === "cancelled" ? "Run cancelled" : "Run incomplete"; + if (lane.result.toolCallCount === 0) return "No fixture call observed"; + const returned = lane.result.toolCalls.filter(call => call.status === "returned").length; + if (lane.result.traceTruncated) return `${number(lane.result.toolCallCount)} calls observed`; + return returned === lane.result.toolCallCount ? `${returned} fixture ${returned === 1 ? "call returned" : "calls returned"}` : "Some fixture calls were rejected"; +} +export function ArenaResults({ lanes, receipt, jevUsage, pending, progress }: { + lanes: Partial>; receipt: RoutingReceipt | null; jevUsage: RouterMeasurement | null; pending: boolean; progress: Partial>; +}) { + const [now, setNow] = useState(0); + useEffect(() => { if (!pending) return; setNow(Date.now()); const timer = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(timer); }, [pending]); + const base = lanes.baseline, integrated = lanes.integrated; + const ready = base?.result.status === "completed" && integrated?.result.status === "completed"; + const integratedInput = integrated?.result.inputTokens != null && jevUsage?.inputTokens != null ? integrated.result.inputTokens + jevUsage.inputTokens : null; + const baseInput = base?.result.inputTokens ?? null; + const delta = ready && baseInput != null && integratedInput != null ? integratedInput - baseInput : null; + return <> +
+

The difference, at a glance

{receipt ? `${base?.tools.length ?? DEMO_CATALOG.length} tools → ${receipt.selectedIds.length} exposed with Jev` : "Same task. A smaller tool menu?"}

{receipt ? "Jev selects the tool menu before Codex starts. The lanes below show the calls that reached the fixture host and each agent’s answer." : "Run a comparison to see what Jev selects and what each agent actually does."}

+
Observed input · CLI + router{delta == null ? ready ? "Usage incomplete" : pending ? "Comparing…" : base || integrated ? "Comparison incomplete" : "Awaiting results" : delta === 0 ? "Same input count" : `${number(Math.abs(delta))} ${delta < 0 ? "fewer" : "more"} tokens`}

{ready ? "This run only. Includes Jev input; output tokens and cache usage are shown below. Answer quality is not scored." : "Results appear as each lane finishes. A finished CLI run does not prove a tool was used."}

+
+ {([['baseline', 'Without Jev', 'Codex CLI'], ['integrated', 'With Jev', 'Codex CLI + Jev Harness']] as const).map(([id, title, subtitle]) => { + const lane = lanes[id], activity = progress[id]; + const running = pending && !lane && activity?.phase !== "failed"; + const phase = lane ? outcome(lane) : running ? activity ? phaseLabels[activity.phase] : "Waiting for Jev routing…" : activity ? "Run stopped · results incomplete" : "Ready"; + const selected = lane?.tools ?? (id === "integrated" ? receipt?.selectedIds : undefined); + return
+

{title}

{subtitle}

{running &&
+
    +
  1. Tools the agent can see {selected ? `${selected.length} exposed` : id === "baseline" ? "All fixture tools" : "Waiting for Jev"}

    {(selected ?? (id === "baseline" ? ["read_file", "propose_patch", "inspect_agent"] : [])).map(tool => {names[tool] ?? tool})}{selected?.length === 0 && No tool exposed · inspect routing evidence}
  2. +
  3. What it actually called

    {lane ? lane.result.toolCallCount ?
    {lane.result.toolCalls.slice(0, 6).map((call, index) => {names[call.tool] ?? call.tool}{call.status})}{lane.result.toolCallCount > 6 && + {lane.result.toolCallCount - 6} more · full trace below}
    :

    No fixture calls reached the host. Read the answer for context.

    :

    {running ? "Waiting for the host’s call trace. Agent activity is shown above." : "No trace available yet."}

    }
  4. +
  5. Agent answer

    {lane ? <>

    {lane.result.error && lane.result.answer &&

    {lane.result.error}

    } :

    {running ? "The agent is running. Its final answer appears here when ready." : "No answer available yet."}

    }
  6. +
+ {lane &&
{seconds(lane.result.durationMs)} CLI time{number(lane.result.inputTokens)} CLI input tokens
} +
; + })} + + ; +} + +export function ArenaAccounting({ lanes, jevUsage }: { lanes: Partial>; jevUsage: RouterMeasurement | null }) { + const base = lanes.baseline, integrated = lanes.integrated; + const baseInput = base?.result.inputTokens ?? null; + const integratedInput = integrated?.result.inputTokens != null && jevUsage?.inputTokens != null ? integrated.result.inputTokens + jevUsage.inputTokens : null; + const scale = Math.max(baseInput ?? 0, integratedInput ?? 0, 1); + if (!base && !integrated) return

Usage appears as the agents finish. Jev’s routing overhead is included in the comparison.

; + return <>
{([['Without Jev', baseInput, 0], ['With Jev', integratedInput, jevUsage?.inputTokens ?? 0]] as const).map(([label, total, router]) =>
{label}{number(total)} tokens
)}

CLI inputJev input

+
+ + + + + + + + +
Reported usage, not a price or quality benchmark. Unknown means unreported.
MeasureWithout JevWith Jev
CLI input tokens{number(base?.result.inputTokens)}{number(integrated?.result.inputTokens)}
Jev input tokensNot called{number(jevUsage?.inputTokens)}
Total reported input tokens{number(baseInput)}{number(integratedInput)}
Cached CLI input (included above){number(base?.result.cachedInputTokens)}{number(integrated?.result.cachedInputTokens)}
CLI output tokens{number(base?.result.outputTokens)}{number(integrated?.result.outputTokens)}
Jev output tokensNot called{number(jevUsage?.outputTokens)}
CLI duration{base ? seconds(base.result.durationMs) : "Unknown"}{integrated ? seconds(integrated.result.durationMs) : "Unknown"}
Additional Jev durationNot called{jevUsage ? seconds(jevUsage.latencyMs) : "Unknown"}

Different providers, cache effects and independent model trajectories prevent a direct dollar comparison. A smaller tool menu may still use more tokens. Neither lane applies proposed patches.

+; +} diff --git a/components/arena.tsx b/components/arena.tsx new file mode 100644 index 0000000..ba4fa71 --- /dev/null +++ b/components/arena.tsx @@ -0,0 +1,59 @@ +"use client"; +import { useEffect, useRef, useState } from "react"; +import { ARENA_CASES } from "../examples/arena/cases"; +import { readApiKey } from "../examples/routing/api-key"; +import { recordUsage } from "../examples/routing/usage"; +import type { RouterMeasurement } from "../examples/routing/live-client"; +import type { RoutingReceipt } from "../src/routing"; +import { ArenaInspector } from "./arena-inspector"; +import { ArenaResults } from "./arena-results"; +import type { ArenaLane, LaneProgress } from "./arena-results"; +export function Arena() { + const [caseId, setCaseId] = useState(ARENA_CASES[0].id), [pending, setPending] = useState(false), [status, setStatus] = useState("Ready to compare."); + const [lanes, setLanes] = useState>>({}), [receipt, setReceipt] = useState(null); + const [progress, setProgress] = useState>>({}); + const [jevUsage, setJevUsage] = useState(null); + const active = useRef(null), generation = useRef(0); + const fixture = ARENA_CASES.find(item => item.id === caseId)!; + function cancel(clear = false) { active.current?.abort(); generation.current++; setPending(false); setStatus("Cancelled. Usage for an interrupted request may be unknown."); if (clear) { setLanes({}); setProgress({}); setReceipt(null); setJevUsage(null); } } + useEffect(() => { const changed = () => cancel(true); window.addEventListener("jev-key-change", changed); window.addEventListener("storage", changed); return () => { active.current?.abort(); generation.current++; window.removeEventListener("jev-key-change", changed); window.removeEventListener("storage", changed); }; }, []); + async function run() { + cancel(true); const token = generation.current; const controller = new AbortController(); active.current = controller; + setPending(true); setStatus("Starting comparison…"); const key = readApiKey(); let usageRecorded = false, streamStarted = false; + try { + const response = await fetch("/api/arena", { method: "POST", headers: { "Content-Type": "application/json", ...(key ? { "x-typesafe-api-key": key } : {}) }, body: JSON.stringify({ caseId }), signal: controller.signal }); + if (!response.ok) { const body = await response.json(); setStatus(body.error ?? "Arena unavailable."); return; } + streamStarted = true; + if (!response.body) throw Error(); + const reader = response.body.getReader(), decoder = new TextDecoder(); let buffer = "", finished = false; + while (true) { + const chunk = await reader.read(); if (chunk.done) break; buffer += decoder.decode(chunk.value, { stream: true }); + if (buffer.length > 1_000_000) throw Error(); + let newline; + while ((newline = buffer.indexOf("\n")) >= 0) { + const event = JSON.parse(buffer.slice(0, newline)); buffer = buffer.slice(newline + 1); + if (event.type === "usage") { usageRecorded = true; const m = event.measurement; if (event.attempted) recordUsage({ at: new Date().toISOString(), status: event.error ? "failed" : "success", input: m?.inputTokens ?? null, output: m?.outputTokens ?? null, latencyMs: m?.latencyMs ?? null, keySource: key ? "personal" : "host" }); } + if (token !== generation.current) continue; + if (event.type === "usage") setJevUsage(event.measurement ?? null); + if (event.type === "stage") setStatus(event.value); + if (event.type === "routing") setReceipt(event.receipt); + if (event.type === "lane") setProgress(value => ({ ...value, [event.lane]: { ...value[event.lane as "baseline" | "integrated"], phase: event.phase, startedAt: value[event.lane as "baseline" | "integrated"]?.startedAt ?? Date.now() } })); + if (event.type === "result") setLanes(value => ({ ...value, [event.lane]: { result: event.result, tools: event.tools } })); + if (event.type === "error") { setStatus(event.value); finished = true; } + if (event.type === "done") { setStatus("Comparison finished. Inspect each lane's outcome; one run is not a benchmark."); finished = true; } + } + } + if (!finished && token === generation.current) setStatus("The stream ended before completion. Results may be partial."); + } catch { + if (!usageRecorded) { usageRecorded = true; recordUsage({ at: new Date().toISOString(), status: controller.signal.aborted ? "cancelled" : "failed", input: null, output: null, latencyMs: null, keySource: key ? "personal" : "host" }); } + if (token === generation.current) setStatus(controller.signal.aborted ? "Cancelled. Usage may be incomplete." : "The arena connection failed. Results may be partial; retry explicitly."); + } finally { + if (streamStarted && !usageRecorded) recordUsage({ at: new Date().toISOString(), status: controller.signal.aborted ? "cancelled" : "failed", input: null, output: null, latencyMs: null, keySource: key ? "personal" : "host" }); + if (token === generation.current) setPending(false); + } + } + function exportRun() { const url = URL.createObjectURL(new Blob([JSON.stringify({ at: new Date().toISOString(), caseId, fixture, receipt, jevUsage, lanes, executionSchedule: "parallel_after_routing", applied: false }, null, 2)], { type: "application/json" })); const link = document.createElement("a"); link.href = url; link.download = "agent-arena.json"; link.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); } + return

Agent arena.

One synthetic task. Codex CLI, with and without Jev choosing its tool context.

{fixture.task}

Uses your local Codex sign-in and a TypeSafe key. Two CLI runs plus one live routing request.

{pending && }

{pending &&

What changes

Tool exposure, before the agent runs

Without Jev, Codex sees every synthetic MCP tool. With Jev, the harness selects the schemas exposed by the MCP host. The trace shows whether the agent actually called those tools.

Fresh read-only CLI runs. Shell, browser, plugins and external tools are disabled. Fixture reads and proposal recording are real; proposed patches are never applied. The inspector tool is deterministic, not a second model agent.

+ +
Independent community experiment. Evidence, not authorization. No proposed code executes.
; +} diff --git a/components/comparison.tsx b/components/comparison.tsx new file mode 100644 index 0000000..98c327a --- /dev/null +++ b/components/comparison.tsx @@ -0,0 +1,10 @@ +import type { runDemo } from "../examples/routing/demo-state"; +type Result = Awaited>; +export function Comparison({ result }: { result: Result | null }) { + const m = result?.comparison.metrics, receipt = result?.comparison.receipt; + const full = m?.fullContextEstimatedTokens ?? 0, lean = m?.leanTotalEstimatedInputTokens ?? null; + const delta = lean === null ? null : full - lean; + const takeaway = !receipt ? "Route a task to compare inputs." : receipt.outcome !== "selected" ? "No tool was selected. There is no equivalent completed task to compare." : delta === null ? "Routing usage is unknown. No savings comparison is available." : delta === 0 ? "Routing uses the same estimated input." : `Routing uses ${Math.abs(delta)} ${delta > 0 ? "fewer" : "more"} estimated input tokens (${Math.round(Math.abs(delta) / full * 100)}%).`; + const rows = m ? [["Context tokens (estimate)", m.fullContextEstimatedTokens, m.leanContextEstimatedTokens], ["Router request bytes", 0, m.routerRequestBytes ?? "Unknown"], ["Total input tokens (estimate)", full, lean ?? "Unknown"], ["Router output tokens (estimate)", "Not called", m.routerResponseEstimatedTokens ?? "Unknown"]] : []; + return
Does routing reduce the input?

{takeaway}

Same task. Estimates include the extra request needed to choose a tool.

Without routing

{m ? full.toLocaleString() : "—"}estimated input tokens

{result?.comparison.full.state.loadedIds.length ?? 0} available tool schemas + the task. No routing request.

With routing

{m ? lean?.toLocaleString() ?? "Unknown" : "—"}estimated input tokens

{m && lean !== null ? `${m.leanContextEstimatedTokens} for the task and ${result!.comparison.lean.state.loadedIds.length} selected ${result!.comparison.lean.state.loadedIds.length === 1 ? "schema" : "schemas"} + ${lean - m.leanContextEstimatedTokens} for routing.` : "Routing measurements are unavailable."}

{m?.routerResponseEstimatedTokens == null ? "Routing output is unavailable; total usage cannot be compared." : `Routing also adds approximately ${m.routerResponseEstimatedTokens} output tokens, separate from the input above.`}

Text-size estimates, not measured usage or cost savings. Actual cost depends on provider pricing and caching. Nothing executes.

Show the calculation
Task + all schemas{m?.fullContextBytes.toLocaleString() ?? "—"}UTF-8 bytes
Task + selected schemas{m?.leanContextBytes.toLocaleString() ?? "—"}UTF-8 bytes
Context reduction{m ? `${Math.round(m.contextReductionFraction * 100)}%` : "—"}excludes router overhead
{rows.map(([label, a, b]) => )}
Same task and availability · both modes
MeasureWithout routingWith routing
{label}{a}{b}

Token proxy: serialized UTF-8 bytes ÷ 4, rounded up. No task execution is measured.

; +} diff --git a/components/example-lab.tsx b/components/example-lab.tsx new file mode 100644 index 0000000..5e439eb --- /dev/null +++ b/components/example-lab.tsx @@ -0,0 +1,11 @@ +"use client"; +import { useState } from "react"; +import Link from "next/link"; +import { DEMO_CATALOG } from "../examples/routing/scenarios"; +export function ExampleLab() { + const [active, setActive] = useState("schemas"), [selected, setSelected] = useState(["read_file"]); + const tools = DEMO_CATALOG.filter(tool => selected.includes(tool.id)); + const fullBytes = new TextEncoder().encode(JSON.stringify(DEMO_CATALOG.map(tool => tool.inputSchema))).length; + const leanBytes = new TextEncoder().encode(JSON.stringify(tools.map(tool => tool.inputSchema))).length; + return

Small experiments.

Explore one mechanism at a time, then compare a real agent in the arena.

Choose an experiment

Codex agent arena ↗

These local experiments make no model calls. The arena runs a real CLI agent against synthetic tools.

{active === "schemas" ? "Only load what is needed" : "The host owns availability"}

{active === "schemas" ? "Select tool descriptors and inspect their exact input schemas. A schema describes an action; it does not run it." : "Toggle tools to change the context surface. Routing cannot add a tool that the host did not make available."}

Tools in this context{DEMO_CATALOG.map(tool => )}

{tools.length} schemas loaded · {leanBytes} schema bytes, compared with {fullBytes} for the full catalog. Excludes task and router overhead.

Inspect the schema contract
{JSON.stringify(tools.map(tool => ({ id: tool.id, inputSchema: tool.inputSchema })), null, 2)}
What this does and does not show

The catalog uses a small closed JSON Schema subset. The core rejects unsupported constraints rather than silently dropping them. Manual toggles demonstrate context composition, not model routing quality or provider savings. Use Routing room for model evidence and the arena for CLI runs.

Independent community examples. No model calls on this page.
; +} diff --git a/components/header.tsx b/components/header.tsx new file mode 100644 index 0000000..30df9ee --- /dev/null +++ b/components/header.tsx @@ -0,0 +1,28 @@ +"use client"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { useEffect, useRef, useState } from "react"; +import { readApiKey, saveApiKey, KEY_STORAGE } from "../examples/routing/api-key"; +import { clearUsage, getUsage, summarizeUsage, type UsageEntry } from "../examples/routing/usage"; +export function Header() { + const pathname = usePathname(); + const keyDialog = useRef(null), usageDialog = useRef(null); + const [saved, setSaved] = useState(false), [draft, setDraft] = useState(""), [error, setError] = useState(""); + const [entries, setEntries] = useState([]); + const total = summarizeUsage(entries); + useEffect(() => { + const syncKey = () => setSaved(Boolean(readApiKey())); + const syncUsage = () => setEntries([...getUsage()]); + syncKey(); syncUsage(); + window.addEventListener("storage", syncKey); window.addEventListener("jev-key-change", syncKey); window.addEventListener("jev-usage-change", syncUsage); + return () => { window.removeEventListener("storage", syncKey); window.removeEventListener("jev-key-change", syncKey); window.removeEventListener("jev-usage-change", syncUsage); }; + }, []); + function save(value: string) { + try { saveApiKey(value); setDraft(""); setError(""); setSaved(Boolean(value.trim())); window.dispatchEvent(new Event("jev-key-change")); return true; } + catch { setError("Could not save. Check the key format and browser storage permissions."); return false; } + } + return <>
jev / harness
+ { setDraft(""); setError(""); }}>

Your TypeSafe API key

{saved ? "A personal key is saved for this origin. It overrides the host's default." : "No personal key saved. Live requests use the host's default if configured."}

{ event.preventDefault(); if (draft.trim() && save(draft)) keyDialog.current?.close(); }}> setDraft(event.target.value)} autoComplete="off" autoCapitalize="none" spellCheck={false} maxLength={1024} required placeholder="Paste your TypeSafe API key" />

Saved unencrypted in this browser until removed. Masked on screen; sent only with live requests through this local host. The saved value is never displayed.

{error}

+

Usage & budget

This tab's latest 200 live Jev requests · survives refresh. Account-wide quotas and balances are unavailable.

Requests{total.requests}
Reported input{total.input.toLocaleString()}
Reported output{total.output.toLocaleString()}

{total.unknown ? `${total.unknown} requests have incomplete usage. Totals include reported tokens only.` : total.requests ? "All listed requests have reported token usage." : "Run a task with Live Jev to see usage. Offline examples use no credits."}

Reported-input price estimate${(total.input * 42 / 1_000_000_000).toFixed(8)}

At $42 per billion input tokens (checked Sep 22, 2026). Excludes output, unknown usage and account-specific pricing. Not an invoice.

Request history
{["Time", "Status", "Input", "Output", "Duration"].map(label => )}{entries.map((entry, index) => )}
Provider-reported tokens. Missing usage stays unknown.
{label}
{new Date(entry.at).toLocaleTimeString()}{entry.status} · {entry.keySource} key{entry.input ?? "Unknown"}{entry.output ?? "Unknown"}{entry.latencyMs === null ? "Unknown" : `${(entry.latencyMs / 1000).toFixed(2)} s`}
Request limits

Local host: 30 starts/minute, 2 concurrent across keys and tabs. Local safeguards, not account quotas. Billing (402) and rate-limit (429) failures remain visible. No automatic retries.

+ ; +} diff --git a/components/routing-room.tsx b/components/routing-room.tsx new file mode 100644 index 0000000..da8d435 --- /dev/null +++ b/components/routing-room.tsx @@ -0,0 +1,64 @@ +"use client"; +import { useEffect, useRef, useState } from "react"; +import { assembleContext, type ContextMode, type ContextState } from "../src/routing"; +import { runDemo, type DemoInput } from "../examples/routing/demo-state"; +import { DEMO_CATALOG, SCENARIOS } from "../examples/routing/scenarios"; +import { liveRouter } from "../examples/routing/live-client"; +import { Comparison } from "./comparison"; +const initial: DemoInput = { intent: SCENARIOS[0]!.intent, availableIds: DEMO_CATALOG.map(t => t.id), topK: 1, maxCostUnits: 10, mode: "lean" }; +type Result = Awaited>; +export function RoutingRoom() { + const [input, setInput] = useState(initial), [source, setSource] = useState<"mock" | "jev">("mock"); + const [result, setResult] = useState(null), [pending, setPending] = useState(false); + const [status, setStatus] = useState("Ready to route."), [liveText, setLiveText] = useState(""); + const [history, setHistory] = useState<{ task: string; reason: string }[]>([]); + const previous = useRef({ loadedIds: [] }), mode = useRef("lean"), generation = useRef(0), controller = useRef(null); + const sourceRef = useRef(source), settings = useRef(null); + sourceRef.current = source; + function invalidate() { controller.current?.abort(); generation.current++; setResult(null); setPending(false); setLiveText(""); setStatus("Task or policy changed. No current selection; route this task to refresh."); } + async function run(next: DemoInput, live: boolean, addTurn: boolean) { + invalidate(); + if (!next.intent.trim()) { setStatus("Enter a task to route."); return; } + if (!Number.isFinite(next.maxCostUnits) || next.maxCostUnits < 0 || next.maxCostUnits > 100 || !Number.isInteger(next.maxCostUnits)) { + setStatus("Enter a whole-number cost limit from 0 to 100."); + if (settings.current) settings.current.open = true; + const budget = document.getElementById("budget") as HTMLInputElement | null; + budget?.reportValidity(); budget?.focus(); return; + } + const ticket = generation.current; + const abort = new AbortController(); controller.current = abort; + const router = live ? liveRouter(abort.signal) : undefined; + setPending(true); setLiveText(live ? "Asking Jev to choose a tool…" : ""); + try { + const computed = await runDemo(next, previous.current, router); + if (ticket !== generation.current) return; + const value = { ...computed, context: assembleContext(computed.comparison.receipt, mode.current, previous.current) }; + previous.current = value.context.state; setResult(value); + const receipt = value.comparison.receipt; + setStatus(`${receipt.outcome === "selected" ? `Selected ${receipt.selectedIds.join(", ")}` : receipt.outcome.replaceAll("_", " ")}. ${value.context.state.loadedIds.length} schemas loaded. Nothing executed.`); + if (router) { const m = router.measurement; setLiveText(router.error ?? (m ? `Jev: ${m.inputTokens ?? "unknown"} input / ${m.outputTokens ?? "unknown"} output tokens reported · ${(m.latencyMs / 1000).toFixed(2)} s. Task execution is not measured.` : "No provider request was needed.")); } + if (addTurn) setHistory(items => [...items, { task: next.intent, reason: receipt.reason }].slice(-4)); + } catch { if (ticket === generation.current) setStatus("Could not compare this task. Check the task and policy fields."); } + finally { if (ticket === generation.current) setPending(false); } + } + useEffect(() => { + void run(initial, false, true); + const keyChanged = () => { if (sourceRef.current === "jev") invalidate(); }; + window.addEventListener("jev-key-change", keyChanged); window.addEventListener("storage", keyChanged); + return () => { controller.current?.abort(); generation.current++; window.removeEventListener("jev-key-change", keyChanged); window.removeEventListener("storage", keyChanged); }; + }, []); + function change(next: DemoInput, automatic = true) { setInput(next); invalidate(); if (automatic && source === "mock") void run(next, false, false); } + function changeMode(next: ContextMode) { mode.current = next; setInput(value => ({ ...value, mode: next })); if (result) { const context = assembleContext(result.comparison.receipt, next, previous.current); previous.current = context.state; setResult({ ...result, context }); setStatus(`${result.comparison.receipt.outcome.replaceAll("_", " ")}. ${context.state.loadedIds.length} schemas loaded. Nothing executed.`); } } + function download() { if (!result) return; const blob = new Blob([JSON.stringify({ generatedAt: new Date().toISOString(), source, ...result }, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "routing-receipt.json"; a.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); } + const receipt = result?.comparison.receipt, selected = receipt?.selectedIds ?? [], context = result?.context; + const selection = !receipt ? pending ? "Choosing a tool…" : "Ready to route" : receipt.outcome === "selected" ? selected.join(" + ") : receipt.outcome === "needs_clarification" ? "Clarify the task" : receipt.outcome === "no_match" ? "No eligible tool" : "Evidence unavailable"; + const reason = !receipt ? "Run a task to produce fresh evidence." : receipt.outcome === "selected" ? `${result!.comparison.metrics.selectedEstimatedCostUnits} estimated cost ${result!.comparison.metrics.selectedEstimatedCostUnits === 1 ? "unit" : "units"} · lowest cost among eligible tools.` : source === "mock" && result?.comparison.scenarioId === "custom" ? "No scripted answer for this task. Choose a sample to see routing." : receipt.reason; + return

Routing room.

Choose a task. See which tools enter context.

+
+
{ event.preventDefault(); if (!input.intent.trim()) return; void run(input, source === "jev", true); }}> -

Scripted answers. Nothing executes.

-
-
- Tools & policy -
-

Change availability or limits to recompute the route.

-
-
Selection policy
-

Costs are illustrative units. Selection uses a 0.70 confidence floor, a 0.20 probability floor, and a 0.10 relevance window before ranking by cost.

-
-
-
- -
-

Routing selection

Scripted evidence
-

Waiting for a task

-

-

Loaded context

Context mode
-
-

-
- -
-
Why this selection

Probabilities and confidence are scripted demonstration values, not correctness or authorization.

-
Does routing reduce the input?
-

-

Same task. Estimates include the extra request needed to choose a tool.

-
-

Without routing

—estimated input tokens

-

With routing

—estimated input tokens

-
-

-

These are text-size estimates, not measured usage or cost savings. Actual cost depends on provider pricing and caching. Nothing executes.

-
Show the calculation
-
Task + all schemas—UTF-8 bytes
Task + selected schemas—UTF-8 bytes
Context reduction—excludes router overhead
-
Estimated input = task + tool schemas + routing request
MeasureWithoutWith
-

Tokens ≈ UTF-8 bytes ÷ 4, rounded up per payload. Router output is separate. Tool inclusion is not model selection accuracy.

-
-
-
Inspect loaded schemas

-
Inspect routing evidence and receipt
-
Recent tasks
-
-

-
Independent community demo. No model calls or tool execution.
Custom tasks ask for clarification. All task text stays in this browser.
-
- - diff --git a/examples/routing/live-client.ts b/examples/routing/live-client.ts new file mode 100644 index 0000000..dcb3882 --- /dev/null +++ b/examples/routing/live-client.ts @@ -0,0 +1,22 @@ +import type { ToolRouter } from "../../src/routing/index.js"; +import { readApiKey } from "./api-key.js"; +import { recordUsage } from "./usage.js"; +export interface RouterMeasurement { requestBytes: number; responseBytes: number | null; inputTokens: number | null; outputTokens: number | null; latencyMs: number } +export interface MeasuredRouter extends ToolRouter { measurement?: RouterMeasurement; error?: string } +export function liveRouter(signal: AbortSignal): MeasuredRouter { + const router: MeasuredRouter = { source: "jev", async review(query) { + const key = readApiKey(); + let attempted = true; + try { + const result = await fetch("/api/route", { method: "POST", headers: { "Content-Type": "application/json", ...(key ? { "x-typesafe-api-key": key } : {}) }, body: JSON.stringify({ intent: query.intent, availableIds: query.options.filter(option => option.kind !== "fallback").map(option => option.id) }), signal }); + const body = await result.json(); + attempted = body.attempted !== false; + if (body.measurement) router.measurement = body.measurement; + if (body.error) router.error = body.error; + if (!result.ok) throw Error("Live routing failed."); + return body.evidence; + } catch { router.error ??= "Live transport failed. Provider usage is unknown; retry explicitly."; throw Error("Live transport failed."); } + finally { if (attempted) recordUsage({ at: new Date().toISOString(), status: signal.aborted ? "cancelled" : router.error ? "failed" : "success", input: router.measurement?.inputTokens ?? null, output: router.measurement?.outputTokens ?? null, latencyMs: router.measurement?.latencyMs ?? null, keySource: key ? "personal" : "host" }); } + } }; + return router; +} diff --git a/examples/routing/server.ts b/examples/routing/server.ts deleted file mode 100644 index 8470ea6..0000000 --- a/examples/routing/server.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { createServer } from "node:http"; -import { readFile } from "node:fs/promises"; - -const modules = ["src/contract/types", "src/routing/index", "src/routing/types", "src/routing/catalog", "src/routing/route", "src/routing/context", "examples/routing/scenarios", "examples/routing/compare", "examples/routing/demo-state", "examples/routing/browser"]; -const assets = new Map([ - ["/", { file: new URL("./index.html", import.meta.url), type: "text/html; charset=utf-8" }], - ["/favicon.svg", { file: new URL("./favicon.svg", import.meta.url), type: "image/svg+xml" }], - ["/style.css", { file: new URL("./style.css", import.meta.url), type: "text/css; charset=utf-8" }], - ...modules.map(name => [`/modules/${name}.js`, { file: new URL(`../../.demo/${name}.js`, import.meta.url), type: "text/javascript; charset=utf-8" }] as const), -]); -/** Static demo assets only. No task endpoint, filesystem browsing or provider I/O. */ -export function createDemoServer() { - return createServer(async (request, response) => { - response.setHeader("Content-Security-Policy", "default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'none'; img-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'"); - response.setHeader("X-Content-Type-Options", "nosniff"); - response.setHeader("Cache-Control", "no-store"); - const port = request.socket.localPort; - if (request.headers.host !== `127.0.0.1:${port}` && request.headers.host !== `localhost:${port}`) { response.writeHead(403).end(); return; } - if (request.method !== "GET" && request.method !== "HEAD") { response.setHeader("Allow", "GET, HEAD"); response.writeHead(405).end(); return; } - const asset = assets.get(request.url ?? ""); - if (!asset) { response.writeHead(404).end(); return; } - try { - const body = await readFile(asset.file); - response.setHeader("Content-Type", asset.type); - response.writeHead(200).end(request.method === "HEAD" ? undefined : body); - } catch { response.writeHead(503).end("Demo assets unavailable. Run pnpm build:demo."); } - }); -} diff --git a/examples/routing/style.css b/examples/routing/style.css deleted file mode 100644 index d513d8d..0000000 --- a/examples/routing/style.css +++ /dev/null @@ -1,170 +0,0 @@ -/* TypeSafe-style dark neutrals with a single rose interaction accent. */ -:root { - color-scheme: dark; - --bg: #0f1012; - --panel: #17181b; - --soft: #202125; - --ink: #f4f4f5; - --muted: #9b9ba4; - --line: #2b2c32; - --pink: #f386a1; - --ease: cubic-bezier(.2, 0, 0, 1); - color: var(--ink); - background: var(--bg); - font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; - -webkit-font-smoothing: antialiased; - text-rendering: optimizeLegibility; -} -* { box-sizing: border-box; } -body { margin: 0; } -button, input, select, textarea { font: inherit; color: inherit; } -button, select, summary { cursor: pointer; } -button { transition: background-color 150ms var(--ease), transform 150ms var(--ease); } -button:active:not(:disabled) { transform: scale(.96); } -button:disabled { opacity: .5; cursor: not-allowed; } -a { color: inherit; } -:focus-visible { outline: 2px solid var(--pink); outline-offset: 4px; } -::selection { background: var(--pink); color: #1b1b1f; } -.skip { position: absolute; top: -80px; left: 20px; z-index: 2; padding: 14px; background: var(--soft); border-radius: 8px; } -.skip:focus { top: 12px; } -.masthead { width: 100%; margin: 0; padding: 26px 32px; display: flex; align-items: center; justify-content: space-between; gap: 16px; } -.wordmark { font: 600 16px ui-monospace, "SF Mono", Menlo, monospace; text-decoration: none; letter-spacing: -.5px; } -.wordmark span { color: var(--muted); font-weight: 400; } -.status-pill { display: flex; align-items: center; gap: 7px; color: var(--muted); font-size: 11px; } -.status-pill::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: #72bca4; } -main { width: calc(100% - 64px); margin: 22px auto 0; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 22px; align-items: start; } -.intro, .inspection, main > footer { grid-column: 1 / -1; } -main > * { min-width: 0; } -.intro { margin-bottom: 0; } -h1 { font-size: 32px; line-height: 1.2; font-weight: 550; letter-spacing: -1.2px; margin: 0 0 10px; } -h1 span { color: var(--pink); } -.intro p { color: var(--muted); font-size: 14px; line-height: 1.7; margin: 0; } -.composer { padding: 22px 24px 0; border: 1px solid var(--line); border-radius: 16px; background: var(--panel); box-shadow: 0 1px 0 #ffffff03 inset; } -.scenario-row { display: flex; align-items: center; justify-content: space-between; gap: 20px; } -label { font-size: 12px; color: var(--muted); } -select, input[type=number] { max-width: 100%; min-height: 40px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 7px; background: var(--soft); font-size: 12px; } -.scenario-row select { max-width: 62%; } -textarea { display: block; width: 100%; resize: vertical; min-height: 98px; padding: 16px 0 8px; margin: 8px 0 0; border: 0; background: transparent; border-radius: 4px; font-size: 16px; line-height: 1.7; } -textarea:focus-visible { outline-offset: 5px; } -.compose-actions { display: flex; align-items: center; justify-content: space-between; gap: 20px; margin: 10px 0 22px; } -.compose-actions p { color: var(--muted); font-size: 11px; line-height: 1.6; margin: 0; } -.primary { min-height: 42px; flex-shrink: 0; padding: 11px 16px; display: flex; align-items: center; gap: 24px; border: 0; border-radius: 8px; background: var(--pink); color: #24151a; font-size: 12px; font-weight: 650; } -details { border-top: 1px solid var(--line); } -summary { display: flex; gap: 10px; align-items: center; min-height: 54px; padding: 12px 0; color: #c4c4cb; font-size: 12px; list-style: none; } -summary::after { content: "+"; font-size: 18px; color: var(--muted); font-weight: 300; margin-left: auto; } -summary::-webkit-details-marker { display: none; } -details[open] > summary::after { content: "−"; } -.summary-meta { margin-left: auto; color: var(--muted); font-size: 11px; } -.summary-meta + span { margin-left: 0; } -.settings summary::after { margin-left: 2px; } -.disclosure-body { padding: 2px 0 20px; } -.hint { font-size: 11px; line-height: 1.8; color: var(--muted); } -#tools { margin: 16px 0 20px; } -.tool { display: grid; grid-template-columns: 1fr auto; gap: 0 16px; padding: 14px 0; border-top: 1px solid var(--line); } -.tool-line { display: flex; align-items: center; gap: 8px; min-width: 0; } -.tool-line label { display: flex; align-items: center; min-height: 32px; flex: 1; cursor: pointer; color: var(--ink); font: 12px ui-monospace, "SF Mono", Menlo, monospace; overflow-wrap: anywhere; } -input[type=checkbox], input[type=radio] { accent-color: var(--pink); width: 17px; height: 17px; flex-shrink: 0; } -.tool > p { grid-row: 2; grid-column: 1 / -1; margin: 5px 0 0 29px; } -.tool-meta { display: flex; align-items: center; gap: 10px; font-size: 10px; color: var(--muted); } -.badge { font-size: 10px; color: var(--muted); } -.badge.selected { color: var(--pink); } -.controls { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; border: 0; padding: 0; margin: 0; min-width: 0; } -.controls label { display: grid; gap: 8px; } -.controls select, .controls input { width: 100%; min-width: 0; } -.result { margin: 0; padding: 26px 24px 20px; border-radius: 16px; border: 1px solid #3b2e36; background: linear-gradient(125deg, #241c23, #19191d 65%); } -.result-heading { display: flex; align-items: center; justify-content: space-between; gap: 10px; } -.eyebrow { font-size: 10px; font-weight: 550; color: var(--pink); letter-spacing: 1px; text-transform: uppercase; margin: 0; } -.evidence-label { color: var(--muted); font-size: 10px; } -h2 { font-size: 25px; letter-spacing: -.6px; line-height: 1.3; font-weight: 550; margin: 15px 0 8px; overflow-wrap: anywhere; } -.result-reason { font-size: 12px; line-height: 1.8; color: #b9b6be; margin: 0; } -.context-heading { display: flex; align-items: center; justify-content: space-between; gap: 20px; margin-top: 24px; padding-top: 20px; border-top: 1px solid #ffffff0a; } -h3 { font-size: 11px; font-weight: 500; margin: 0; line-height: 1.8; } -h3 span { display: block; color: var(--muted); font-size: 10px; font-weight: 400; } -.mode { display: flex; align-items: stretch; gap: 3px; padding: 3px; border: 1px solid var(--line); border-radius: 8px; background: var(--bg); margin: 0; min-width: 0; } -.mode label { display: flex; align-items: center; justify-content: center; gap: 5px; min-height: 36px; padding: 7px 10px; border-radius: 5px; font-size: 10px; cursor: pointer; } -.mode label:has(input:checked) { background: #2c292f; color: var(--ink); } -.mode input { margin: 0; width: 12px; height: 12px; } -.context-strip { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 18px; } -.schema-chip { padding: 7px 10px; border-radius: 6px; color: #dcc3cd; background: #ffffff06; border: 1px solid #ffffff0c; font: 11px ui-monospace, "SF Mono", Menlo, monospace; overflow-wrap: anywhere; } -.empty-context { color: var(--muted); font-size: 12px; } -#flow-mode { margin: 10px 0 0; font-size: 10px; } -.inspection { padding: 0; } -.inspection details:first-child { border-top: 0; } -.decision { font-size: 12px; line-height: 1.9; color: #c4c4cb; margin: 0 0 20px; } -.evidence-row { margin: 16px 0; } -.evidence-heading { display: flex; justify-content: space-between; gap: 16px; align-items: center; font: 11px ui-monospace, monospace; } -.tool-evidence { font: 10px ui-sans-serif, system-ui, sans-serif; color: var(--muted); margin: 0; } -meter { display: block; width: 100%; height: 4px; border: 0; border-radius: 4px; background: #ffffff0a; margin-top: 10px; } -meter::-webkit-meter-bar { border: 0; background: #ffffff0a; border-radius: 4px; } -meter::-webkit-meter-optimum-value { background: #a57f90; border-radius: 4px; } -meter::-moz-meter-bar { background: #a57f90; border-radius: 4px; } -.metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; padding: 8px 0 24px; } -.metrics span, .metrics small { display: block; color: var(--muted); font-size: 10px; line-height: 1.7; } -.metrics strong { display: block; margin: 6px 0; font: 24px ui-monospace, "SF Mono", Menlo, monospace; font-variant-numeric: tabular-nums; letter-spacing: -.7px; } -table { width: 100%; border-collapse: collapse; font-size: 11px; } -caption { color: var(--muted); text-align: left; font-size: 10px; padding-bottom: 14px; } -th, td { padding: 11px 4px; border-top: 1px solid var(--line); text-align: right; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; } -th { font-weight: 500; } -th:first-child, td:first-child { width: 55%; text-align: left; } -pre { margin: 12px 0 0; padding: 16px; max-height: 360px; overflow: auto; background: var(--panel); border-radius: 8px; white-space: pre-wrap; overflow-wrap: anywhere; color: #c4c4cb; font: 11px/1.8 ui-monospace, "SF Mono", Menlo, monospace; } -.quiet { min-height: 40px; background: var(--soft); border: 1px solid var(--line); border-radius: 7px; padding: 8px 12px; color: var(--ink); font-size: 11px; } -.chat { max-height: 330px; overflow: auto; overscroll-behavior: contain; padding-bottom: 16px; } -.message { font-size: 12px; line-height: 1.8; padding: 12px 0; overflow-wrap: anywhere; } -.message strong { display: block; font-size: 9px; text-transform: uppercase; letter-spacing: .8px; color: var(--muted); margin-bottom: 5px; } -.message.user strong { color: #cda5b4; } -footer { text-align: center; padding: 32px 0 36px; color: var(--muted); font-size: 10px; line-height: 1.9; } -.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } -@media (hover: hover) { - .primary:hover:not(:disabled) { background: #ffabc2; } - summary:hover, .quiet:hover:not(:disabled) { color: var(--pink); } -} -@media (max-width: 600px) { - .masthead { padding: 22px 20px; } - main { width: calc(100% - 32px); margin-top: 16px; gap: 16px; } - .intro { margin-bottom: 4px; } - h1 { font-size: 29px; } - .intro p { font-size: 12px; } - .composer { padding: 18px 18px 0; } - .scenario-row { gap: 12px; } - .scenario-row select { max-width: 65%; font-size: 11px; min-height: 44px; } - .scenario-row label { font-size: 11px; } - textarea { font-size: 16px; } - .compose-actions { gap: 10px; margin-bottom: 18px; } - .compose-actions p { max-width: 130px; font-size: 10px; } - .primary { min-height: 44px; padding: 10px 12px; gap: 12px; font-size: 11px; } - .result { padding: 22px 18px 18px; margin-top: 0; } - h2 { font-size: 23px; } - .context-heading { gap: 10px; flex-wrap: wrap; } - .mode label { min-height: 40px; padding-inline: 8px; } - .inspection { padding: 0; } - summary { min-height: 56px; } - .tool { grid-template-columns: 1fr; gap: 2px; } - .tool-meta { grid-row: 3; margin-left: 29px; } - .tool-line label { min-height: 38px; } - .controls { gap: 12px; } - .controls select, .controls input { min-height: 44px; } - .metrics { gap: 8px; } - .metrics strong { font-size: 21px; } - .metrics span, .metrics small { font-size: 9px; } - .quiet { min-height: 44px; } - footer { font-size: 9px; } -} -@media (prefers-reduced-motion: reduce) { - *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; scroll-behavior: auto !important; } - button:active:not(:disabled) { transform: none; } -} -.comparison-takeaway { margin: 4px 0 8px; font-size: 17px; font-weight: 500; line-height: 1.6; letter-spacing: -.3px; } -.comparison-options { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 22px 0 16px; } -.comparison-option { padding: 18px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel); } -.comparison-option.routed { background: #211c23; border-color: #49333f; } -.comparison-option h3 { color: #c4c4cb; font-size: 12px; } -.comparison-option strong { display: block; margin: 15px 0 3px; font-size: 28px; font-weight: 500; font-variant-numeric: tabular-nums; letter-spacing: -.7px; } -.comparison-option .unit { display: block; color: var(--muted); font-size: 10px; } -.comparison-option meter { margin: 16px 0; } -.comparison-option p { color: #b2aeb8; font-size: 11px; line-height: 1.8; margin: 0; } -.comparison-caveat { font-size: 12px; line-height: 1.8; color: #c4c4cb; } -.technical-comparison { margin-top: 20px; } -@media (max-width: 400px) { .comparison-options { grid-template-columns: 1fr; } } - -@media (max-width: 900px) { main { grid-template-columns: minmax(0, 1fr); } } -@media (min-width: 901px) { .composer, .result { height: 100%; } .comparison-options { margin-block: 16px; } .technical-comparison table { table-layout: fixed; } } diff --git a/examples/routing/usage.ts b/examples/routing/usage.ts new file mode 100644 index 0000000..9d47dcb --- /dev/null +++ b/examples/routing/usage.ts @@ -0,0 +1,24 @@ +export interface UsageEntry { at: string; status: "success" | "failed" | "cancelled"; input: number | null; output: number | null; latencyMs: number | null; keySource: "personal" | "host" } +const validNumber = (n: unknown) => n === null || typeof n === "number" && Number.isFinite(n) && n >= 0; +export function parseEntries(raw: string | null): UsageEntry[] { + try { + const value: unknown = JSON.parse(raw ?? "null"); + if (!Array.isArray(value)) return []; + return value.slice(0, 200).filter(e => e && typeof e.at === "string" && Number.isFinite(Date.parse(e.at)) && ["success", "failed", "cancelled"].includes(e.status) && ["personal", "host"].includes(e.keySource) && [e.input, e.output, e.latencyMs].every(validNumber)).map(e => ({ at: e.at, status: e.status, input: e.input, output: e.output, latencyMs: e.latencyMs, keySource: e.keySource })); + } catch { return []; } +} +export function summarizeUsage(entries: readonly UsageEntry[]) { + return { requests: entries.length, input: entries.reduce((n, e) => n + (e.input ?? 0), 0), output: entries.reduce((n, e) => n + (e.output ?? 0), 0), unknown: entries.filter(e => e.input === null || e.output === null).length }; +} +const STORAGE = "jev-harness-session-usage-v1"; +let entries: UsageEntry[] | null = null; +export function getUsage() { + if (entries === null) { try { entries = parseEntries(sessionStorage.getItem(STORAGE)); } catch { entries = []; } } + return entries; +} +export function recordUsage(entry: UsageEntry) { + entries = [entry, ...getUsage()].slice(0, 200); + try { sessionStorage.setItem(STORAGE, JSON.stringify(entries)); } catch { /* Memory-only remains usable. */ } + window.dispatchEvent(new Event("jev-usage-change")); +} +export function clearUsage() { entries = []; try { sessionStorage.removeItem(STORAGE); } catch {} window.dispatchEvent(new Event("jev-usage-change")); } diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..1787cee --- /dev/null +++ b/next.config.ts @@ -0,0 +1,11 @@ +import type { NextConfig } from "next"; +const config: NextConfig = { + experimental: { extensionAlias: { ".js": [".ts", ".tsx", ".js"] } }, + poweredByHeader: false, + async headers() { return [{ source: "/:path*", headers: [ + { key: "X-Content-Type-Options", value: "nosniff" }, + { key: "Referrer-Policy", value: "no-referrer" }, + { key: "Content-Security-Policy", value: `default-src 'self'; script-src 'self' 'unsafe-inline'${process.env.NODE_ENV === "development" ? " 'unsafe-eval'" : ""}; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'none'; form-action 'self'` }, + ] }]; }, +}; +export default config; diff --git a/package.json b/package.json index 6be7314..18fea17 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,14 @@ }, "scripts": { "typecheck": "tsc --noEmit", - "test": "pnpm build:demo && tsx --test tests/*.test.ts", + "test": "tsx --test tests/*.test.ts", "prepare": "git config core.hooksPath .githooks || true", "check:secrets": "node scripts/check-secrets.mjs --all", "bench:routing": "tsx scripts/bench-routing.ts", - "build:demo": "tsc -p tsconfig.demo.json", - "demo": "pnpm build:demo && tsx scripts/demo-routing.ts" + "demo": "pnpm dev", + "dev": "next dev --webpack --hostname 127.0.0.1 --port 4173", + "build": "next build --webpack", + "start": "next start --hostname 127.0.0.1 --port 4173" }, "devDependencies": { "@types/node": "26.6.2", diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..e77839f --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1 @@ + diff --git a/scripts/arena-mcp.mjs b/scripts/arena-mcp.mjs new file mode 100644 index 0000000..6f6adca --- /dev/null +++ b/scripts/arena-mcp.mjs @@ -0,0 +1,36 @@ +/** Bounded MCP fixture host. No tool executes model-proposed code or reads user files. */ +import { readFileSync, appendFileSync } from "node:fs"; +import { createInterface } from "node:readline"; +const manifest = JSON.parse(readFileSync(process.argv[2], "utf8")); +const trace = process.argv[3]; +let calls = 0, proposalBytes = 0; +const respond = (id, value) => process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, ...value }) + "\n"); +const lines = createInterface({ input: process.stdin, crlfDelay: Infinity }); +lines.on("line", line => { + if (line.length > 64_000) return; + let message; + try { message = JSON.parse(line); } catch { return; } + if (!Object.hasOwn(message, "id")) return; + if (message.method === "initialize") return respond(message.id, { result: { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: "jev-synthetic-arena", version: "1.0.0" } } }); + if (message.method === "ping") return respond(message.id, { result: {} }); + if (message.method === "tools/list") return respond(message.id, { result: { tools: manifest.tools.map(tool => ({ name: tool.id, description: tool.description, inputSchema: tool.inputSchema })) } }); + if (message.method !== "tools/call") return respond(message.id, { error: { code: -32601, message: "Unsupported method" } }); + const tool = manifest.tools.find(tool => tool.id === message.params?.name); + const args = message.params?.arguments; + let data, proposal, failed = false; + calls++; + if (calls > 100) { data = { error: "Fixture tool-call limit reached." }; failed = true; } + else if (!tool || !args || typeof args !== "object" || Array.isArray(args) || Object.keys(args).some(key => !Object.hasOwn(tool.inputSchema.properties, key)) || tool.inputSchema.required.some(key => !Object.hasOwn(args, key)) || Object.entries(args).some(([key, value]) => typeof value !== tool.inputSchema.properties[key]?.type || typeof value === "string" && value.length > 16000)) { data = { error: "Unavailable tool or invalid arguments." }; failed = true; } + else if (tool.id === "read_file") { data = Object.hasOwn(manifest.files, args.path) ? { path: args.path, content: manifest.files[args.path], source: "synthetic fixture" } : { error: "Unknown fixture path." }; failed = Boolean(data.error); } + else if (tool.id === "propose_patch") { + const candidate = { path: args.path, patch: args.patch, rationale: args.rationale, applied: false }; + const size = Buffer.byteLength(JSON.stringify(candidate)); + if (!Object.hasOwn(manifest.files, args.path)) { data = { error: "Unknown fixture path." }; failed = true; } + else if (proposalBytes + size > 256_000) { data = { error: "Proposal recording budget exhausted." }; failed = true; } + else { proposal = candidate; proposalBytes += size; data = { status: "recorded_pending", applied: false, note: "Proposal retained in the run trace for inspection only. No validation of patch correctness and no file changes." }; } + } + else if (tool.id === "inspect_agent") data = { source: "deterministic synthetic inspector, not a model subagent", files: manifest.files, note: "Compare the named exported constants to the question. Nothing changed." }; + else { data = { error: "No handler." }; failed = true; } + appendFileSync(trace, JSON.stringify({ tool: tool?.id ?? "unknown", status: failed ? "rejected" : "returned", at: new Date().toISOString(), ...(proposal ? { proposal } : {}) }) + "\n"); + respond(message.id, { result: { content: [{ type: "text", text: JSON.stringify(data) }], isError: failed } }); +}); diff --git a/scripts/demo-routing.ts b/scripts/demo-routing.ts deleted file mode 100644 index e078001..0000000 --- a/scripts/demo-routing.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createDemoServer } from "../examples/routing/server.js"; -const port = Number(process.argv[2] ?? 4173); -if (!Number.isInteger(port) || port < 1024 || port > 65535) throw Error("Choose a port between 1024 and 65535."); -const server = createDemoServer(); -server.on("error", error => { console.error(`Demo could not start: ${error.message}`); process.exitCode = 1; }); -server.listen(port, "127.0.0.1", () => console.log(`Routing room: http://127.0.0.1:${port} — synthetic, offline, no execution`)); diff --git a/scripts/verify-arena-browser.mjs b/scripts/verify-arena-browser.mjs new file mode 100644 index 0000000..9001d2f --- /dev/null +++ b/scripts/verify-arena-browser.mjs @@ -0,0 +1,118 @@ +/** Offline UI acceptance; synthetic NDJSON replaces both live providers. */ +export async function verifyArena(page, baseURL) { + const checks = []; + const check = (ok, label) => { if (!ok) throw Error(label); checks.push(label); }; + let mode = "normal"; + const result = { status: "completed", answer: "The synthetic sum returns 5.", durationMs: 1000, inputTokens: 1000, cachedInputTokens: 300, outputTokens: 50, toolCallCount: 1, traceTruncated: false, toolCalls: [{ tool: "read_file", status: "returned", at: "2026-09-22T00:00:00Z" }], error: null }; + await page.route("**/api/arena", async route => { + let events = [ + { type: "usage", attempted: true, measurement: { inputTokens: 100, outputTokens: 10, requestBytes: 400, responseBytes: 40, latencyMs: 200 } }, + { type: "routing", receipt: { selectedIds: ["read_file"], reason: "One fixture tool selected.", evidence: { probabilities: { read_file: .9, needs_clarification: .1 } }, policy: { topK: 1, confidenceFloor: .6, probabilityFloor: .2, maxCostUnits: 10 } } }, + { type: "result", lane: "baseline", tools: ["read_file", "propose_patch", "inspect_agent"], result }, + { type: "result", lane: "integrated", tools: ["read_file"], result: { ...result, inputTokens: mode === "unknown" ? null : 950, toolCallCount: mode === "zero" ? 0 : 1, toolCalls: mode === "zero" ? [] : result.toolCalls } }, + { type: "done" }, + ]; + if (mode === "proposal") events = events.map(event => event.type === "result" ? { ...event, result: { ...event.result, toolCalls: [{ tool: "propose_patch", status: "returned", at: "2026-09-22T00:00:00Z", proposal: { path: "src/sum.ts", patch: "@@\n- before\n+ after", rationale: "Synthetic pending fix", applied: false } }] } } : event); + if (mode === "eof") events = [{ type: "stage", value: "Asking Jev…" }]; + if (mode === "skipped") events = [{ type: "usage", attempted: false }, { type: "error", value: "No key. No request made." }]; + await route.fulfill({ contentType: "application/x-ndjson", body: events.map(e => JSON.stringify(e)).join("\n") + "\n" }); + }); + try { + await page.goto(baseURL + "/arena"); + const run = async () => { await page.getByRole("button", { name: "Run comparison" }).click(); await page.waitForFunction(() => !document.querySelector(".arena-controls button.primary").disabled); }; + await run(); + check(await page.getByText("3 tools → 1 exposed with Jev", { exact: true }).isVisible(), "tool exposure shown visually"); + check(await page.getByText("50 more tokens", { exact: true }).isVisible(), "headline includes router overhead even when savings disappear"); + check(await page.getByText("1 fixture call returned", { exact: true }).count() === 2, "actual calls visible per lane"); + check(await page.getByText("The synthetic sum returns 5.", { exact: true }).count() === 2, "answers visible side by side"); + check(await page.locator(".accounting-chart").isHidden(), "accounting disclosed on demand"); + await page.locator(".run-inspector > summary").click(); + const dl = page.waitForEvent("download"); await page.getByRole("button", { name: "Download comparison" }).click(); + const stream = await (await dl).createReadStream(); let raw = ""; for await (const chunk of stream) raw += chunk; + const exported = JSON.parse(raw); check(exported.jevUsage.inputTokens === 100 && exported.applied === false, "export retains overhead and nonexecution"); + await page.getByRole("tab", { name: "Tool activity" }).click(); + check(await page.locator(".event-list li").count() === 2, "tool activity is a readable event list"); + await page.getByRole("tab", { name: "Jev’s decision" }).click(); + check(await page.getByText("90%", { exact: true }).isVisible(), "routing evidence is visualized as a distribution"); + check(await page.getByText("View raw routing receipt", { exact: true }).isVisible(), "raw data is a second-level disclosure"); + check(await page.locator(".raw-evidence pre").isHidden(), "raw receipt stays collapsed"); + await page.getByRole("tab", { name: "Jev’s decision" }).focus(); + await page.keyboard.press("ArrowRight"); + check(await page.getByRole("tab", { name: "Test setup" }).getAttribute("aria-selected") === "true", "inspector tabs support arrow-key navigation"); + check(await page.getByText("Two lanes, in parallel", { exact: true }).isVisible(), "setup describes concurrent execution"); + await page.keyboard.press("Home"); + check(await page.getByRole("tab", { name: "Usage", exact: true }).getAttribute("aria-selected") === "true", "Home selects first inspector tab"); + await page.locator(".run-inspector > summary").click(); + for (const width of [320, 390, 768, 1440, 1920]) { + await page.setViewportSize({ width, height: 900 }); + check(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), `arena fits ${width}px`); + await page.locator(".run-inspector > summary").click(); + check(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), `expanded accounting fits ${width}px`); + await page.locator(".run-inspector > summary").click(); + } + mode = "zero"; await run(); + check(await page.getByText("No fixture call observed", { exact: true }).isVisible(), "completed CLI with zero calls is not implied execution"); + mode = "unknown"; await run(); + check(await page.getByText("Usage incomplete", { exact: true }).isVisible(), "unknown usage never becomes savings"); + mode = "eof"; await run(); + check((await page.locator("#arena-status").textContent()).includes("partial"), "early EOF shows partial status"); + await page.getByRole("button", { name: "Usage · 4", exact: true }).click(); + check((await page.locator("#usage-unknown").textContent()).includes("1 requests"), "early EOF records unknown usage"); + await page.getByRole("button", { name: "Close usage dashboard" }).click(); + mode = "skipped"; await run(); + check(await page.getByRole("button", { name: "Usage · 4", exact: true }).isVisible(), "explicit skipped request does not add usage"); + mode = "proposal"; await run(); + await page.locator(".run-inspector > summary").click(); + await page.getByRole("tab", { name: "Tool activity" }).click(); + await page.getByText("Inspect recorded proposal", { exact: true }).first().click(); + check((await page.locator(".recorded-proposal pre").first().textContent()).includes("+ after"), "recorded proposals expose their pending diff in the inspector"); + const proposalDownload = page.waitForEvent("download"); await page.getByRole("button", { name: "Download comparison" }).click(); + const proposalStream = await (await proposalDownload).createReadStream(); let proposalJson = ""; for await (const chunk of proposalStream) proposalJson += chunk; + check(JSON.parse(proposalJson).lanes.baseline.result.toolCalls[0].proposal.applied === false, "proposal export preserves nonexecution and recorded data"); + return { checks, count: checks.length, providerCalls: 0, humanAccessibilityAcceptance: "not performed" }; + } finally { await page.unroute("**/api/arena"); } +} + +/** Held streams verify visible progress/cancellation without a CLI or provider. Use a fresh page. */ +export async function verifyArenaProgress(page, baseURL) { + const checks = []; + const check = (ok, label) => { if (!ok) throw Error(label); checks.push(label); }; + await page.addInitScript(() => { + const original = window.fetch; + window.fetch = async (input, init) => { + if (input !== "/api/arena") return original(input, init); + return new Response(new ReadableStream({ start(controller) { + const emit = event => controller.enqueue(new TextEncoder().encode(JSON.stringify(event) + "\n")); + emit({ type: "usage", attempted: true, measurement: { inputTokens: 20, outputTokens: 3, latencyMs: 2 } }); + emit({ type: "routing", receipt: { selectedIds: ["read_file"] } }); + emit({ type: "stage", value: "Both agents are running in parallel." }); + emit({ type: "lane", lane: "baseline", phase: "working" }); + emit({ type: "lane", lane: "integrated", phase: "calling" }); + window.finishArenaBaseline = (zero = false) => emit({ type: "result", lane: "baseline", tools: ["read_file", "propose_patch", "inspect_agent"], result: { status: "completed", answer: "First lane finished.", durationMs: 1000, inputTokens: 100, outputTokens: 10, cachedInputTokens: 0, toolCallCount: zero ? 0 : 1, traceTruncated: false, toolCalls: zero ? [] : [{ tool: "read_file", status: "returned", at: "2026-09-22T00:00:00Z" }], error: null } }); + init.signal.addEventListener("abort", () => controller.error(new DOMException("Aborted", "AbortError")), { once: true }); + } }), { headers: { "Content-Type": "application/x-ndjson" } }); + }; + }); + await page.goto(baseURL + "/arena"); + await page.getByRole("button", { name: "Run comparison" }).click(); + await page.getByText("Both agents are running in parallel.", { exact: true }).waitFor(); + check((await page.locator(".baseline .lane-status").textContent()).includes("Agent is working"), "baseline shows independent working state"); + check((await page.locator(".integrated .lane-status").textContent()).includes("Calling a fixture tool"), "integrated shows independent tool activity"); + await page.waitForFunction(() => [...document.querySelectorAll(".lane-status small")].every(e => parseInt(e.textContent) >= 1)); + check(await page.locator(".lane-status small").count() === 2, "both elapsed timers advance while pending"); + check(await page.getByRole("button", { name: "Comparing…", exact: true }).isDisabled(), "run button communicates pending state"); + await page.evaluate(() => window.finishArenaBaseline()); + await page.getByText("First lane finished.", { exact: true }).waitFor(); + check((await page.locator(".baseline .lane-status").textContent()).includes("1 fixture call returned"), "finished lane displays its outcome immediately"); + check((await page.locator(".integrated .lane-status").textContent()).includes("Calling a fixture tool"), "other lane stays active after first finishes"); + await page.evaluate(() => window.finishArenaBaseline(true)); + await page.locator(".run-inspector > summary").click(); + await page.getByRole("tab", { name: "Tool activity" }).click(); + check((await page.locator(".trace-lane").first().textContent()).includes("No fixture calls recorded."), "finished zero-call lane does not wait for sibling trace"); + await page.getByRole("button", { name: "Cancel", exact: true }).click(); + check((await page.locator(".integrated .lane-status").textContent()).includes("Run stopped"), "cancel removes stale running state"); + check(await page.locator(".activity-dot").count() === 0, "cancel clears activity indicators"); + check(await page.getByText("First lane finished.", { exact: true }).isVisible(), "cancel preserves completed lane result"); + check((await page.locator(".trace-lane").last().textContent()).includes("call count is unknown"), "cancelled missing trace is not counted as zero calls"); + return { checks, count: checks.length, providerCalls: 0 }; +} diff --git a/scripts/verify-live-browser.mjs b/scripts/verify-live-browser.mjs new file mode 100644 index 0000000..b5a37b5 --- /dev/null +++ b/scripts/verify-live-browser.mjs @@ -0,0 +1,81 @@ +/** Offline browser checks. Intercepts live requests; consumes no credits. */ +export async function verifyLiveDemo(page, baseURL = "http://127.0.0.1:4187") { + const checks = []; + const check = (ok, label) => { if (!ok) throw Error(label); checks.push(label); }; + let calls = 0; + let release; + let defer = false; + let fail = false; + const key = "synthetic-browser-credential"; + await page.route("**/api/route", async route => { + calls++; + if (fail) { await route.abort("failed"); return; } + if (defer) await new Promise(resolve => { release = resolve; }); + check(route.request().headers()["x-typesafe-api-key"] === key, "personal override sent in header only"); + check(!route.request().postData().includes(key), "key excluded from model input"); + await route.fulfill({ json: { evidence: { model: "jev-1.13.0", choice: "read_file", confidence: .9, probabilities: { read_file: .8, inspect_agent: .1, propose_patch: .05, needs_clarification: .05 } }, measurement: { requestBytes: 1200, responseBytes: 200, inputTokens: 260, outputTokens: 40, latencyMs: 120 }, error: null } }); + }); + try { + await page.goto(baseURL); + await page.getByRole("button", { name: "API key", exact: true }).click(); + check(await page.getByLabel("API key", { exact: true }).getAttribute("type") === "password", "key entry is masked"); + await page.getByLabel("API key", { exact: true }).fill(key); + await page.getByRole("button", { name: "Save key", exact: true }).click(); + await page.reload(); + await page.getByRole("button", { name: "API key · saved" }).waitFor(); + check(await page.getByRole("button", { name: "API key · saved" }).isVisible(), "key persists across refresh"); + await page.getByRole("button", { name: "API key · saved" }).click(); + check(await page.getByLabel("API key", { exact: true }).inputValue() === "", "saved key is never filled back into UI"); + await page.keyboard.press("Escape"); + await page.getByLabel("Evidence source").selectOption("jev"); + await page.getByLabel("Try a scenario").selectOption("patch"); + check(calls === 0, "changing live settings makes no provider call"); + await page.getByRole("button", { name: "Route with Jev" }).click(); + await page.waitForFunction(() => document.querySelector("#live-status").textContent.includes("260 input")); + check(calls === 1, "explicit run makes one request"); + check((await page.locator("#receipt").textContent()).includes('"source": "jev"'), "receipt identifies live evidence"); + check(!(await page.locator("#receipt").textContent()).includes(key), "key excluded from receipt"); + check((await page.locator("#routed-explanation").textContent()).includes("300 for routing"), "live estimate uses actual wire request size"); + await page.getByRole("radio", { name: "Batteries included" }).check(); + check(calls === 1, "switching context mode reuses live evidence"); + check((await page.locator("#status").textContent()).includes("3"), "accessible status reflects changed context mode"); + defer = true; + await page.getByRole("button", { name: "Route with Jev" }).click(); + await page.waitForFunction(() => document.querySelector("#live-status").textContent.includes("Asking Jev")); + await page.getByRole("radio", { name: "Lean", exact: true }).check(); + while (!release) await new Promise(resolve => setTimeout(resolve, 20)); + release(); + await page.waitForFunction(() => document.querySelector("#live-status").textContent.includes("260 input")); + check(await page.locator("#flow-loaded").textContent() === "1 schema", "pending request respects latest context mode"); + defer = false; + fail = true; + await page.getByRole("button", { name: "Route with Jev" }).click(); + await page.waitForFunction(() => document.querySelector("#live-status").textContent.includes("transport failed")); + check(await page.locator("#routed-tokens").textContent() === "Unknown", "network failure leaves usage unknown"); + check(!(await page.locator("#live-status").textContent()).includes("No provider request"), "network failure is not called a skipped request"); + await page.getByRole("button", { name: "Usage · 3", exact: true }).click(); + check(await page.locator("#usage-input").textContent() === "520", "usage totals retain reported input only"); + check(await page.locator("#usage-output").textContent() === "80", "usage totals separate output tokens"); + check((await page.locator("#usage-unknown").textContent()).includes("1 requests have incomplete usage"), "usage identifies failed calls with unknown totals"); + await page.getByRole("button", { name: "Close usage dashboard" }).click(); + await page.getByRole("button", { name: "API key · saved" }).click(); + await page.getByRole("button", { name: "Remove key" }).click(); + check(await page.evaluate(() => localStorage.getItem("typesafe-api-key-override")) === null, "remove clears persisted override"); + await page.keyboard.press("Escape"); + check(await page.locator("#download").isDisabled(), "key change invalidates prior live result"); + for (const width of [320, 390, 768, 1440, 1920]) { + await page.setViewportSize({ width, height: 900 }); + check(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), `live UI fits ${width}px`); + await page.getByRole("button", { name: "API key", exact: true }).click(); + check(await page.locator("#key-dialog").evaluate(e => e.getBoundingClientRect().width <= innerWidth), `key dialog fits ${width}px`); + await page.keyboard.press("Escape"); + } + await page.reload(); + await page.getByRole("button", { name: "Usage · 3", exact: true }).waitFor(); + check(await page.getByRole("button", { name: "Usage · 3", exact: true }).isVisible(), "usage survives refresh without storing prompts or credentials"); + await page.getByRole("button", { name: "Usage · 3", exact: true }).click(); + await page.getByRole("button", { name: "Clear local usage history" }).click(); + check(await page.locator("#usage-requests").textContent() === "0", "usage history can be cleared"); + return { checks, count: checks.length, providerCalls: 0, transport: "browser interception", humanAccessibilityAcceptance: "not performed" }; + } finally { await page.unroute("**/api/route"); } +} diff --git a/scripts/verify-routing-browser.mjs b/scripts/verify-routing-browser.mjs index bff824c..b31a2db 100644 --- a/scripts/verify-routing-browser.mjs +++ b/scripts/verify-routing-browser.mjs @@ -1,7 +1,8 @@ /** * Optional browser acceptance check. Pass a Playwright Page from your existing * browser driver; this repository does not install a second test framework. - * Start `pnpm demo 4187`, then await verifyRoutingDemo(page, baseURL). + * Start `pnpm exec next dev --webpack --hostname 127.0.0.1 --port 4187`, + * then await verifyRoutingDemo(page, baseURL). */ export async function verifyRoutingDemo(page, baseURL = "http://127.0.0.1:4187") { const checks = []; diff --git a/tests/arena.test.ts b/tests/arena.test.ts new file mode 100644 index 0000000..0e5200a --- /dev/null +++ b/tests/arena.test.ts @@ -0,0 +1,110 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { spawn } from "node:child_process"; +import { mkdtemp, writeFile, readFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { once } from "node:events"; +import { ARENA_CASES } from "../examples/arena/cases"; +import { DEMO_CATALOG } from "../examples/routing/scenarios"; +import { runCodex, codexArguments } from "../examples/host/codex"; + +test("synthetic MCP host exposes only selected schemas and refuses unavailable tools/paths", async t => { + const dir = await mkdtemp(join(tmpdir(), "jev-mcp-test-")); + t.after(() => rm(dir, { recursive: true, force: true })); + const manifest = join(dir, "manifest.json"), trace = join(dir, "trace.jsonl"); + await writeFile(manifest, JSON.stringify({ tools: [DEMO_CATALOG[0]], files: ARENA_CASES[0].files })); + await writeFile(trace, ""); + const child = spawn(process.execPath, [resolve("scripts/arena-mcp.mjs"), manifest, trace]); + let output = ""; child.stdout.on("data", chunk => { output += chunk.toString(); }); + child.stdin.end([ + { jsonrpc: "2.0", id: 1, method: "tools/list" }, + { jsonrpc: "2.0", id: 2, method: "tools/call", params: { name: "read_file", arguments: { path: "src/sum.ts" } } }, + { jsonrpc: "2.0", id: 3, method: "tools/call", params: { name: "read_file", arguments: { path: "../../private" } } }, + { jsonrpc: "2.0", id: 4, method: "tools/call", params: { name: "propose_patch", arguments: {} } }, + ].map(message => JSON.stringify(message)).join("\n") + "\n"); + const [code] = await once(child, "close"); assert.equal(code, 0); + const responses = output.trim().split("\n").map(line => JSON.parse(line)); + assert.deepEqual(responses[0].result.tools.map((tool: { name: string }) => tool.name), ["read_file"]); + assert.equal(responses[1].result.isError, false); + assert.equal(responses[2].result.isError, true); + assert.equal(responses[3].result.isError, true); + assert.equal((await readFile(trace, "utf8")).trim().split("\n").length, 3); +}); + +test("CLI adapter uses isolated read-only settings and parses real event-shaped usage without provider calls", async t => { + const dir = await mkdtemp(join(tmpdir(), "jev-cli-test-")); t.after(() => rm(dir, { recursive: true, force: true })); + const fake = join(dir, "fake-cli"); + await writeFile(fake, '#!/usr/bin/env node\nprocess.stdin.resume();process.stdin.on("end",()=>{console.log(JSON.stringify({type:"item.started",item:{type:"mcp_tool_call"}}));console.log(JSON.stringify({type:"item.completed",item:{type:"mcp_tool_call"}}));console.log(JSON.stringify({type:"item.completed",item:{type:"agent_message",text:"Synthetic answer"}}));console.log(JSON.stringify({type:"turn.completed",usage:{input_tokens:80,cached_input_tokens:20,output_tokens:12}}));});\n', { mode: 0o700 }); + const phases: string[] = []; + const result = await runCodex(ARENA_CASES[0], DEMO_CATALOG, new AbortController().signal, fake, phase => phases.push(phase)); + assert.deepEqual(phases, ["calling", "working", "answering"]); + assert.equal(result.status, "completed"); assert.equal(result.answer, "Synthetic answer"); assert.equal(result.inputTokens, 80); assert.equal(result.cachedInputTokens, 20); assert.deepEqual(result.toolCalls, []); + const args = codexArguments(dir, "fixture", "trace"); + assert.ok(args.includes("read-only")); assert.ok(args.includes("--ignore-user-config")); assert.ok(args.includes("shell_tool")); assert.ok(!args.some(arg => arg.includes("dangerously"))); + assert.ok(args.includes('mcp_servers.arena.default_tools_approval_mode="prompt"')); + assert.ok(args.includes('mcp_servers.arena.tools.read_file.approval_mode="approve"')); + assert.ok(args.includes('mcp_servers.arena.required=true')); + const missing = await runCodex(ARENA_CASES[0], [], new AbortController().signal, join(dir, "missing")); + assert.equal(missing.status, "failed"); assert.equal(missing.inputTokens, null); +}); + +test("arena starts both lanes before either finishes and waits for a remaining lane after failure", async () => { + const { runArenaLanes } = await import("../examples/host/arena-lanes"); + const events: any[] = [], releases: (() => void)[] = [], started: string[][] = []; + const fake: typeof runCodex = async (_fixture, tools, _signal, _executable, progress) => { + started.push(tools.map(tool => tool.id)); + progress?.("working"); + await new Promise((resolve, reject) => releases.push(started.length === 1 ? () => reject(Error("synthetic failure")) : resolve)); + return { status: "completed", answer: "Fixture answer", durationMs: 10, inputTokens: 1, cachedInputTokens: 0, outputTokens: 1, toolCallCount: 0, traceTruncated: false, toolCalls: [], error: null }; + }; + let ended = false; + const pending = runArenaLanes(ARENA_CASES[0], ["read_file"], new AbortController().signal, event => events.push(event), fake).finally(() => { ended = true; }); + const rejection = assert.rejects(pending, /CLI lane failed/); + assert.equal(started.length, 2, "both processes start without awaiting the first"); + assert.deepEqual(started[0], DEMO_CATALOG.map(tool => tool.id)); assert.deepEqual(started[1], ["read_file"]); + releases[0]!(); await new Promise(resolve => setImmediate(resolve)); + assert.equal(ended, false, "the second process still owns the host slot"); + releases[1]!(); await rejection; + assert.ok(events.some(e => e.type === "lane" && e.lane === "baseline" && e.phase === "failed")); + assert.ok(events.some(e => e.type === "result" && e.lane === "integrated")); +}); + +test("agent prompt shares task and paths but keeps source contents behind MCP", async () => { + const { arenaPrompt } = await import("../examples/host/codex"); + for (const fixture of ARENA_CASES) { + const prompt = arenaPrompt(fixture); + assert.ok(prompt.includes(fixture.task)); + for (const [path, content] of Object.entries(fixture.files)) { assert.ok(prompt.includes(path)); assert.ok(!prompt.includes(content.trim())); } + assert.match(prompt, /do not invent file contents/); + } +}); + +test("MCP retains bounded pending proposals for inspection and rejects excess recordings", async t => { + const dir = await mkdtemp(join(tmpdir(), "jev-proposal-test-")); t.after(() => rm(dir, { recursive: true, force: true })); + const manifest = join(dir, "manifest.json"), trace = join(dir, "trace.jsonl"); + const fixture = ARENA_CASES[1]; + await writeFile(manifest, JSON.stringify({ tools: DEMO_CATALOG, files: fixture.files })); await writeFile(trace, ""); + const child = spawn(process.execPath, [resolve("scripts/arena-mcp.mjs"), manifest, trace]); + let output = ""; child.stdout.on("data", chunk => { output += chunk.toString(); }); + const args = { path: "src/sum.ts", patch: "synthetic-pending-diff\n" + "x".repeat(15900), rationale: "synthetic rationale " + "y".repeat(15900) }; + child.stdin.end(Array.from({ length: 10 }, (_, id) => JSON.stringify({ jsonrpc: "2.0", id, method: "tools/call", params: { name: "propose_patch", arguments: args } })).join("\n") + "\n"); + const [code] = await once(child, "close"); assert.equal(code, 0); + const responses = output.trim().split("\n").map(line => JSON.parse(line)); + const records = (await readFile(trace, "utf8")).trim().split("\n").map(line => JSON.parse(line)); + assert.deepEqual(records[0].proposal, { ...args, applied: false }); + assert.equal(JSON.parse(responses[0].result.content[0].text).status, "recorded_pending"); + assert.equal(records.at(-1).status, "rejected"); assert.equal(records.at(-1).proposal, undefined); + assert.equal(responses.at(-1).result.isError, true); + assert.ok(records.reduce((total, record) => total + (record.proposal ? Buffer.byteLength(JSON.stringify(record.proposal)) : 0), 0) <= 256_000); + assert.deepEqual(JSON.parse(await readFile(manifest, "utf8")).files, fixture.files); +}); + +test("Windows CLI host refuses before workspace/auth/process setup", async () => { + const descriptor = Object.getOwnPropertyDescriptor(process, "platform")!; + Object.defineProperty(process, "platform", { value: "win32" }); + try { + const result = await runCodex(ARENA_CASES[0], [], new AbortController().signal, "must-not-spawn"); + assert.equal(result.status, "failed"); assert.equal(result.durationMs, 0); assert.match(result.error!, /macOS or Linux/); + } finally { Object.defineProperty(process, "platform", descriptor); } +}); diff --git a/tests/live-host.test.ts b/tests/live-host.test.ts new file mode 100644 index 0000000..b2cf7db --- /dev/null +++ b/tests/live-host.test.ts @@ -0,0 +1,58 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { createLiveHandler, localOrigin } from "../examples/host/live.js"; + +test("live host bounds input, rebuilds choices and never returns keys or raw provider errors", async t => { + let calls = 0; + const handler = createLiveHandler({ fetch: async (_url, init) => { + calls++; + const sent = JSON.parse(String(init?.body)); + assert.equal(sent.model, "jev-1.13.0"); + assert.deepEqual(Object.keys(sent.questions.tool.criteria), ["read_file", "needs_clarification"]); + assert.ok(!String(init?.body).includes("synthetic-test-credential")); + return Response.json({ model: "jev-1.13.0", answers: { tool: { type: "choice", choice: "read_file", confidence: .9, probabilities: { read_file: .9, needs_clarification: .1 } } }, usage: { input_tokens: 120, output_tokens: 20 }, private: "must not escape" }); + } }); + const base = "http://127.0.0.1:4173"; + const send = (body: unknown, origin = base, key = "synthetic-test-credential") => handler(new Request(base + "/api/route", { method: "POST", headers: { origin, "content-type": "application/json", "x-typesafe-api-key": key }, body: JSON.stringify(body) })); + const input = { intent: "Read the synthetic sum file.", availableIds: ["read_file"] }; + assert.equal((await send(input, "https://elsewhere.example")).status, 403); + assert.equal((await send({ ...input, availableIds: ["unknown"] })).status, 400); + assert.equal((await send({ ...input, questions: {} })).status, 400); + assert.equal((await send({ ...input, intent: "x".repeat(17000) })).status, 400); + assert.equal(calls, 0); + const response = await send(input); assert.equal(response.status, 200); + const result = await response.json(); + assert.equal(result.evidence.choice, "read_file"); + assert.equal(result.measurement.inputTokens, 120); + assert.ok(result.measurement.requestBytes > 0); + assert.ok(!JSON.stringify(result).includes("credential")); + assert.ok(!JSON.stringify(result).includes("must not escape")); + assert.equal(calls, 1); +}); + +test("provider failures and malformed evidence fail closed without disclosing raw data", async t => { + let mode = "error"; + const handler = createLiveHandler({ serverKey: "synthetic-test-credential", fetch: async () => { + if (mode === "error") throw Error("synthetic-test-credential private details"); + if (mode === "billing") return new Response("private billing body", { status: 402 }); + return Response.json({ model: "wrong-model", answers: { tool: { type: "choice", choice: "read_file", confidence: 1, probabilities: { read_file: 1, needs_clarification: 0 } } } }); + } }); + const base = "http://127.0.0.1:4173"; + for (const value of ["error", "billing", "model"]) { + mode = value; + const response = await handler(new Request(base + "/api/route", { method: "POST", headers: { origin: base, "content-type": "application/json" }, body: JSON.stringify({ intent: "Read a synthetic file", availableIds: ["read_file"] }) })); + const result = await response.json(); + assert.equal(result.evidence, null); + assert.ok(result.error); + assert.ok(!JSON.stringify(result).includes("private")); + assert.ok(!JSON.stringify(result).includes("credential")); + if (value === "billing") assert.match(result.error, /402/); + } +}); + +test("local origin validates the actual Host when Next canonicalizes the request URL", () => { + const url = "http://localhost:4173/api/route"; + assert.equal(localOrigin(new Request(url, { headers: { origin: "http://127.0.0.1:4173", host: "127.0.0.1:4173" } })), true); + assert.equal(localOrigin(new Request(url, { headers: { origin: "http://evil.example:4173", host: "evil.example:4173" } })), false); + assert.equal(localOrigin(new Request(url, { headers: { origin: "http://127.0.0.1:4173", host: "localhost:4173" } })), false); +}); diff --git a/tests/routing-bench.test.ts b/tests/routing-bench.test.ts index c9dbdec..2562897 100644 --- a/tests/routing-bench.test.ts +++ b/tests/routing-bench.test.ts @@ -12,7 +12,7 @@ test("paired synthetic evaluation measures inclusion separately from selection", assert.equal(pair.metrics.executionLatencyMs, null); assert.equal(pair.metrics.providerTokens, null); assert.ok(pair.metrics.fullContextBytes >= pair.metrics.leanContextBytes); - assert.ok(pair.metrics.leanTotalEstimatedInputTokens >= pair.metrics.leanContextEstimatedTokens); + assert.ok(pair.metrics.leanTotalEstimatedInputTokens !== null && pair.metrics.leanTotalEstimatedInputTokens >= pair.metrics.leanContextEstimatedTokens); assert.equal(pair.receipt.source, "mock"); assert.equal(pair.receipt.execution.applied, false); assert.equal(JSON.stringify(pair.receipt.request).includes("expected"), false); diff --git a/tests/routing-demo.test.ts b/tests/routing-demo.test.ts index 25fd781..c58336b 100644 --- a/tests/routing-demo.test.ts +++ b/tests/routing-demo.test.ts @@ -1,49 +1,7 @@ import { test } from "node:test"; import assert from "node:assert/strict"; -import { get } from "node:http"; -import { once } from "node:events"; -import * as serverModule from "../examples/routing/server.js"; import * as host from "../examples/routing/demo-state.js"; -test("the demo serves only its static module graph on loopback", async t => { - assert.equal(typeof serverModule.createDemoServer, "function"); - const server = serverModule.createDemoServer(); - server.listen(0, "127.0.0.1"); - await once(server, "listening"); - t.after(() => new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve()))); - const address = server.address(); - assert.ok(address && typeof address !== "string"); - const base = `http://127.0.0.1:${address.port}`; - const page = await fetch(base); - assert.equal(page.status, 200); - assert.match(page.headers.get("content-security-policy")!, /connect-src 'none'/); - assert.match(await page.text(), /Routing room/); - for (const pathname of ["/.env", "/package.json", "/server.ts", "/modules/examples/routing/server.js", "/modules/src/contract/decide.js", "/missing", "/%2e%2e/package.json"]) - assert.equal((await fetch(base + pathname)).status, 404, pathname); - assert.equal((await fetch(base, { method: "POST", body: "synthetic task" })).status, 405); - // Native fetch rewrites Host; send an actual forged Host with node:http. - const foreignHostStatus = await new Promise((resolve, reject) => { - get(base, { headers: { host: "untrusted.example" } }, response => { response.resume(); resolve(response.statusCode); }).on("error", reject); - }); - assert.equal(foreignHostStatus, 403); - const head = await fetch(base, { method: "HEAD" }); - assert.equal(head.status, 200); - assert.equal(await head.text(), ""); - const queue = ["/modules/examples/routing/browser.js"]; - const visited = new Set(); - while (queue.length) { - const pathname = queue.shift()!; - if (visited.has(pathname)) continue; - visited.add(pathname); - const response = await fetch(base + pathname); - assert.equal(response.status, 200, pathname); - assert.match(response.headers.get("content-type")!, /javascript/); - const source = await response.text(); - for (const match of source.matchAll(/(?:from\s*|import\s*)["'](\.[^"']+)["']/g)) queue.push(new URL(match[1]!, base + pathname).pathname); - } - assert.ok(visited.size >= 8); -}); - test("custom chat text clarifies locally and availability changes evict old schemas", async () => { const initial = await host.runDemo({ intent: "Read src/sum.ts in the synthetic workspace.", availableIds: ["read_file", "propose_patch", "inspect_agent"], mode: "lean", topK: 1, maxCostUnits: 10 }); assert.deepEqual(initial.context.state.loadedIds, ["read_file"]); diff --git a/tests/usage.test.ts b/tests/usage.test.ts new file mode 100644 index 0000000..242697f --- /dev/null +++ b/tests/usage.test.ts @@ -0,0 +1,12 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { summarizeUsage, parseEntries } from "../examples/routing/usage.js"; +test("usage preserves partial and unknown calls without claiming zero cost", () => { + const entries = parseEntries(JSON.stringify([ + { at: "2026-09-22T00:00:00Z", status: "success", input: 100, output: 20, latencyMs: 10, keySource: "personal" }, + { at: "2026-09-22T00:00:01Z", status: "failed", input: null, output: null, latencyMs: null, keySource: "host" }, + ])); + assert.deepEqual(summarizeUsage(entries), { requests: 2, input: 100, output: 20, unknown: 1 }); + assert.deepEqual(parseEntries('[{"input":-1}]'), []); + assert.deepEqual(parseEntries('not json'), []); +}); diff --git a/tsconfig.demo.json b/tsconfig.demo.json deleted file mode 100644 index 839c63e..0000000 --- a/tsconfig.demo.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { "noEmit": false, "outDir": ".demo", "rootDir": ".", "types": [], "lib": ["ES2022", "DOM", "DOM.Iterable"] }, - "include": ["examples/routing/browser.ts"], - "exclude": ["node_modules", ".demo"] -} diff --git a/tsconfig.json b/tsconfig.json index decafc6..a3d7835 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,13 +10,37 @@ "skipLibCheck": true, "types": [ "node" + ], + "jsx": "react-jsx", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, + "incremental": true, + "plugins": [ + { + "name": "next" + } ] }, "include": [ + "next-env.d.ts", + "app/**/*.ts", + "app/**/*.tsx", + "components/**/*.tsx", "src", "tests", - "examples/routing", + "examples", "scripts/bench-routing.ts", - "scripts/demo-routing.ts" + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" ] }