From 5cec0a33ec4cc7a5e82b9e034dde83b8e9560202 Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 31 Aug 2026 19:48:58 +0900 Subject: [PATCH 01/42] test(gui): guard the properties the Aside and Integrations work relies on (#3074) Six regression tests over the Aside and Integrations work, each pinning a property the merged code already has and nothing was checking, and each driven red against a mutation of the real source first. MONOCHROME_CLIENT_MARKS membership had no guard, and the rule is asymmetric: masking a multi-color mark flattens its palette, while leaving a single-ink mark out makes it invisible against one theme. dsh is pinned separately because it is single-ink but that ink is the brand. A file client must appear in all three of CLIENTS, TABS, and FILE_CLIENTS; miss one and it half-ships while both halves compile. Aside's ownership sentence must keep its three facts through translation. The rollback reveal count must survive the refresh a restore triggers, and a folded row must restore itself rather than the newest one. Every mark must have a provenance entry, since these are other vendors' trademarks and the README is the only record of the claim. Also records the unit outcome in the devlog: what the two audit rounds caught, and the mark-availability claim that turned out to be wrong. --- .../050_wp6_stacked_prs.md | 42 ++++++++++ gui/tests/client-marks-assets.test.ts | 77 ++++++++++++++++++- .../integrations-rollback-history.test.tsx | 68 ++++++++++++++++ gui/tests/integrations-surfaces.test.tsx | 26 +++++++ gui/tests/locale-parity.test.ts | 27 +++++++ 5 files changed, 239 insertions(+), 1 deletion(-) diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/050_wp6_stacked_prs.md b/devlog/_plan/260831_aside_client_and_integrations_ux/050_wp6_stacked_prs.md index 20bcc7a6c9..3b17a30488 100644 --- a/devlog/_plan/260831_aside_client_and_integrations_ux/050_wp6_stacked_prs.md +++ b/devlog/_plan/260831_aside_client_and_integrations_ux/050_wp6_stacked_prs.md @@ -89,3 +89,45 @@ And `integrations.catalog.title` -- the new `h3` that fixes the overview's headi outline -- reads "Clients" in both English and French, which the French accidental-English guard is right to flag. It is on the intentional-English allowlist now. + +## Outcome + +Seven PRs on `dev`: #3047 (`8c1294828`) the Aside client, #3050 (`efa2ba5ad`) the +bounded rollback surface, #3049 (`704d0d91a`) the first-party marks, #3048 +(`93b7ee80a`) the Aside GUI surface, #3065 (`7853e8e05`) Aside's own mark and the +single-ink fix, #3060 (`a1c332e9a`) the MAINTAINERS correction, and #3074 the +regression guards. Issue #3059 tracks the one deferral. + +### What the pre-merge audit changed + +An adversarial reviewer ran two rounds against the stack and neither round was +ceremonial. Round 1 found that squash-merging the parent would strand the child: +the repo merges by squash, so `git rebase` of the child onto the new `dev` +conflicts add/add, and `enforce-pr-target` resolves `stackedBase` from **open** +PRs only, so #3048 flips to `wrong_base` the moment its parent lands and its +branch is deleted. The fix is `git rebase --onto `, +with the tip captured *before* the merge because `delete_branch_on_merge` removes +the name. + +Round 2 caught the more dangerous one. A fix had landed on the parent after the +child was cut, so the child was *behind* its own base while touching the same two +files. Replaying it would have auto-merged cleanly and silently reverted the +guard. The bad outcome there is not a conflict you resolve; it is the clean +rebase you do not look at. Cascade first, then `--onto`, and verify by grepping +for the guard rather than trusting the exit code. + +Both are properties of *this* repository's settings, not general stacking advice. + +### The claim that was wrong + +`aside` was recorded here as having no first-party mark, on the evidence that +`aside.com/favicon.svg` is a 404. True of the web, wrong about the product: the +installed application ships the mark in a module the vendor named +`official-brand-symbol`. The lookup had been scoped to what a vendor publishes +for the web, and an app that ships no web assets falls through it. + +Rendering all nine marks at 28px against both themes then showed three were +invisible to somebody -- `prime` white-on-transparent in light mode, `opencode` +and `kimi` near-black in dark. Every existing assertion passed: the files +existed, the `src` values were right, the geometry check was satisfied. Only +looking at them found it. diff --git a/gui/tests/client-marks-assets.test.ts b/gui/tests/client-marks-assets.test.ts index 59388cc21b..36da09dc6d 100644 --- a/gui/tests/client-marks-assets.test.ts +++ b/gui/tests/client-marks-assets.test.ts @@ -1,10 +1,16 @@ import { expect, test } from "bun:test"; import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; -import { CLIENT_MARKS } from "../src/components/apikeys-workspace/client-config-clients"; +import { CLIENT_MARKS, MONOCHROME_CLIENT_MARKS } from "../src/components/apikeys-workspace/client-config-clients"; const PUBLIC_DIR = join(import.meta.dir, "..", "public"); +/** Every literal color a mark paints with, lowercased; `currentColor` is not one. */ +function inksOf(body: string): Set { + const matches = body.match(/(?:fill|stop-color)\s*[:=]\s*"?#[0-9a-fA-F]{3,8}/g) ?? []; + return new Set(matches.map(raw => raw.split(/[:=]/).pop()!.replace(/"/g, "").trim().toLowerCase())); +} + /* * A mark that 404s renders as a broken image, which is worse than the monogram * it replaced. CLIENT_MARKS is a plain string map, so nothing else checks that @@ -31,3 +37,72 @@ test("every client mark is drawn geometry, not text or an embedded raster", () = .toMatch(/<(path|circle|rect|polygon|ellipse|line|polyline)[\s>]/); } }); + +/* + * Membership in MONOCHROME_CLIENT_MARKS decides whether a mark draws as an + * or as a themed mask, and the wrong answer is invisible in code review. Masking + * a multi-color mark silently flattens its palette into one ink; that direction + * is what this guards, and it is the destructive one, because the mark still + * renders and still looks deliberate. + * + * The other direction is not symmetric, so it is not asserted here: a single-ink + * mark is only a candidate for masking. Whether it SHOULD be masked depends on + * whether that ink is neutral or is the brand itself, which no property of the + * file can answer -- see the dsh case below. + */ +test("no multi-color mark is masked, which would flatten its palette", () => { + const flattened: string[] = []; + for (const [clientId, src] of Object.entries(CLIENT_MARKS)) { + if (!MONOCHROME_CLIENT_MARKS.has(clientId as never)) continue; + const body = readFileSync(join(PUBLIC_DIR, src!.replace(/^\//, "")), "utf8"); + const inks = inksOf(body); + const gradient = /<(linearGradient|radialGradient)[\s>]/.test(body); + if (gradient || inks.size > 1) { + flattened.push(`${clientId}: ${inks.size} ink(s)${gradient ? " + gradient" : ""}`); + } + } + expect(flattened).toEqual([]); +}); + +/* + * The failure that shipped: prime is white-on-transparent and was invisible in + * light mode, opencode (#211E1E) and kimi (#1A1A1A) invisible in dark. Each has + * exactly one ink and no way to follow the theme, so each must be masked. Pinned + * by name because the general rule cannot express it -- dsh is also single-ink + * and must NOT be masked. + */ +test("the marks that were invisible against a theme are masked", () => { + for (const clientId of ["prime", "opencode", "kimi", "aside"] as const) { + expect(MONOCHROME_CLIENT_MARKS.has(clientId), `${clientId} must be masked`).toBe(true); + } +}); + +/* + * A masked mark is painted with the row's text color, so whatever ink the file + * carries is discarded. For `dsh` that would be wrong in the other direction -- + * it is a single ink, but that ink is DeepSeek blue and part of the brand, so it + * stays an . Recorded here because "single ink" alone would have masked it. + */ +test("a single-ink mark whose ink is a brand color is not masked", () => { + expect(MONOCHROME_CLIENT_MARKS.has("dsh")).toBe(false); + const body = readFileSync(join(PUBLIC_DIR, CLIENT_MARKS.dsh!.replace(/^\//, "")), "utf8"); + expect([...inksOf(body)]).toEqual(["#4d6bfe"]); +}); + +/* + * Every mark here is somebody else's trademark, used on the strength of being + * that vendor's own published asset. The README is where that claim lives -- the + * source it came from and when -- and it is the only record of it. A mark added + * without an entry is one nobody can later confirm the provenance of, which is + * the state this directory is specifically trying not to be in. + * + * Prose is checked here rather than a manifest because prose is what the README + * is; the assertion is only that each committed mark is named somewhere in it. + */ +test("every mark's provenance is recorded in the README", () => { + const readme = readFileSync(join(PUBLIC_DIR, "provider-icons", "README.md"), "utf8"); + const undocumented = Object.values(CLIENT_MARKS) + .map(src => src!.split("/").pop()!) + .filter(file => !readme.includes(file)); + expect(undocumented).toEqual([]); +}); diff --git a/gui/tests/integrations-rollback-history.test.tsx b/gui/tests/integrations-rollback-history.test.tsx index 5cdce4fee9..57446d2044 100644 --- a/gui/tests/integrations-rollback-history.test.tsx +++ b/gui/tests/integrations-rollback-history.test.tsx @@ -93,6 +93,17 @@ async function mount( }); } +/** Re-render the same tree with a new journal, the way a refresh does. */ +async function rerender(journal: IntegrationJournalRow[]) { + await act(async () => { + root!.render( + + {}} /> + , + ); + }); +} + function visibleRows(): HTMLElement[] { return Array.from(container.querySelectorAll(".integration-history-row")) as unknown as HTMLElement[]; } @@ -175,6 +186,31 @@ test("an expired snapshot offers no control anywhere in the list", async () => { expect(expired!.querySelector("button")).toBeNull(); }); +test("restoring from inside the fold passes that row, not the newest one", async () => { + /* + * The newest row's Undo was covered; a folded row's control was not, and the + * two are wired separately -- the fold maps over a sliced copy. Passing the + * wrong row here is the most destructive defect this component could have: the + * user asks to roll back to a specific point in their history and silently + * gets a different one, with a confirmation dialog that names the operation + * they chose. Nothing downstream can catch it, because the request is + * well-formed and the server has no way to know it was not what was meant. + */ + let restored: IntegrationJournalRow | null = null; + const journal = rows(12); + await mount(journal, { onRestore: value => { restored = value; } }); + await act(async () => { disclosure()!.open = true; }); + + const folded = visibleRows().filter(node => node.closest(".integration-history-older")); + const third = folded[2]!; + await act(async () => { third.querySelector("button")!.click(); }); + + // Row 0 is outside the fold, so the third folded row is journal[3]. + expect(restored).not.toBeNull(); + expect(restored!.opId).toBe(journal[3]!.opId); + expect(restored!.opId).not.toBe(journal[0]!.opId); +}); + test("the overview names the client on every row; a client tab does not", async () => { /* * The overview is the only surface showing one chronology across clients, so @@ -226,3 +262,35 @@ test("the disclosure is keyboard-operable and its summary is the only added tab .filter(node => node.getAttribute("tabindex") !== "-1"); expect(extraStops).toHaveLength(0); }); + +test("a refresh that prepends an entry does not re-collapse what was expanded", async () => { + /* + * Every restore refreshes the journal, and the refresh prepends the operation + * the user just performed. If the reveal count reset on that re-render, a user + * paging back through history would be thrown to the top by their own undo -- + * the one moment they are certainly reading older rows. The count lives in + * component state and the component is not remounted, so it survives; this + * pins that, because moving the state up or keying the element on the newest + * row would silently break it. + */ + await mount(rows(20)); + const details = disclosure()!; + await act(async () => { details.open = true; }); + await act(async () => { buttonByText("Show 6 more")!.click(); }); + const revealed = visibleRows().length; + expect(revealed).toBeGreaterThan(7); + + // The restore lands and the journal comes back one row longer. + const refreshed = [row({ opId: "op-new", at: new Date(Date.UTC(2026, 7, 31, 11, 0, 0)).toISOString() }), ...rows(20)]; + await rerender(refreshed); + + expect(disclosure()!.open).toBe(true); + // Same count, not a reset to one visible row plus a fresh page. The reveal + // budget applies to the older list, so prepending shifts what fills it rather + // than adding to it -- what matters is that the budget itself survived. + expect(visibleRows().length).toBe(revealed); + // And the row the user just created is the one now sitting outside the fold. + const outside = Array.from(container.querySelectorAll(".integration-history-row")) + .filter(node => !(node as unknown as HTMLElement).closest(".integration-history-older")); + expect(outside).toHaveLength(1); +}); diff --git a/gui/tests/integrations-surfaces.test.tsx b/gui/tests/integrations-surfaces.test.tsx index c4f7b4b736..5889c1619b 100644 --- a/gui/tests/integrations-surfaces.test.tsx +++ b/gui/tests/integrations-surfaces.test.tsx @@ -750,3 +750,29 @@ test("a populated overview journal collapses instead of flooding the page", asyn await act(async () => { details.open = true; }); expect(container.querySelectorAll(".integration-history-older .integration-history-row").length).toBeGreaterThan(1); }); + +/* + * Adding a file client means editing three hand-maintained lists that no type + * relates to each other: CLIENTS in client-config-clients.ts, INTEGRATION_TABS, + * and FILE_CLIENTS. Miss one and the client half-ships -- it exports from the + * API tab but has no Integrations tab to toggle from, or it owns a tab that + * renders a page for a client the file surface does not recognize. Both compile, + * and both look complete from whichever half you happen to open. + * + * Aside is the reason this exists: it needed all three, and nothing would have + * failed if it had landed in two. + */ +test("every export client has both an Integrations tab and a file-surface entry", async () => { + const { CLIENTS } = await import("../src/components/apikeys-workspace/client-config-clients"); + const { TABS, FILE_CLIENTS } = await import("../src/pages/integrations/integration-tabs"); + + const tabIds = new Set(TABS.map(tab => tab.id as string)); + const missing = CLIENTS.filter(id => !tabIds.has(id) || !FILE_CLIENTS.has(id as never)); + expect(missing).toEqual([]); + + // And no tab claims a client that does not exist, which would render a page + // for an id the config surface cannot answer for. + const clientIds = new Set(CLIENTS); + const orphaned = [...FILE_CLIENTS].filter(id => !clientIds.has(id)); + expect(orphaned).toEqual([]); +}); diff --git a/gui/tests/locale-parity.test.ts b/gui/tests/locale-parity.test.ts index 047702d324..0f2924bd91 100644 --- a/gui/tests/locale-parity.test.ts +++ b/gui/tests/locale-parity.test.ts @@ -248,3 +248,30 @@ test("every locale carries the exact DSH label and ownership semantics", async ( expect(dict.get("integrations.semantics.dsh")).toBe(expected[2]); } }); + +/* + * Aside's ownership sentence carries three facts a user acts on, and each is + * wrong in a different way if a translation drops it: which key OpenCodex + * touches (`providers.opencodex`, so the rest of the file is untouched), where + * the file lives (`~/.aside/u/`, which is per-account and not the bare + * `~/.aside`), and that Aside rewrites the file while running, so a change does + * not take until the app is fully quit and reopened. A translator working from + * the English can render the prose naturally and still lose one. + * + * The identifiers are asserted rather than the sentence, unlike the DSH case + * above: pinning full translated strings freezes wording, and these three tokens + * are the part that must survive translation unchanged. + */ +test("every locale keeps the three facts Aside's ownership sentence carries", async () => { + for (const locale of LOCALES) { + const dict = await readDict(locale); + expect(dict.get("api.clientConfig.clientAside"), locale).toBe("Aside"); + expect(dict.get("integrations.tab.aside"), locale).toBe("Aside"); + + const semantics = dict.get("integrations.semantics.aside") ?? ""; + expect(semantics, `${locale} names the managed key`).toContain("providers.opencodex"); + expect(semantics, `${locale} names the per-account root`).toContain("~/.aside/u/"); + // Aside rewrites models.json as it runs, so a restart hint is not optional. + expect(semantics.length, `${locale} keeps the restart warning`).toBeGreaterThan(80); + } +}); From 71bd7bec6a8d840db39b33a614ea8f46a1f6f65c Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 31 Aug 2026 22:31:44 +0900 Subject: [PATCH 02/42] chore(release): move dev to 2.39.0 after the v2.38.0 release (#3076) dev was left carrying the just-published 2.38.0, which fails tests/release-version-line.test.ts on dev and on every PR against it: the in-tree version must never be behind a released one. bun scripts/bump-dev-version.ts 2.38.0 package.json chose 2.39.0, per the documented rule that a stable release consumes its core and dev moves to the next minor. dev-version-bump.yml did not fire on its own. It resolves the workflow from the default branch on a release event, and the version it would have used was the one this commit writes by hand. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5fc493b829..9da46cb61c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitkyc08/opencodex", - "version": "2.38.0", + "version": "2.39.0", "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code", "type": "module", "main": "./bin/package-main.mjs", From 873d08e636a868127ce5b9ac0ab4408614359749 Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 31 Aug 2026 22:57:57 +0900 Subject: [PATCH 03/42] docs(devlog): plan the remaining marks, page-wide logos and conflict overwrite (#3081) Three implementation phases get a diff-level document each, plus a provenance record for the marks the earlier pass left as monograms. 005 records where hermes, gajae and mcode marks come from, including the two candidates that were rejected and why: the Nous safari-pinned-tab trace opens with the full 512-unit frame as its first path, so it renders as a black square, and the MiniMax docs asset is a 129x32 wordmark that turns to mush at 20px. It also establishes the rule this unit adds -- a mark may be traced from the product's own raster when no vector exists, provided the trace follows the source pixels, the parameters are recorded, and the result is verified by rendering at the size it will actually be used. 060 is the asset and registration diff. 070 is a shared mark component reaching the overview cards, the tab strip, the per-client page headers and the four non-file rows. 080 is an explicit opt-in overwrite for a conflicted config, which today has no recovery path in the GUI at all. An adversarial audit against the real tree corrected four things before this landed. The largest: 070 had openai.svg down as single-ink-therefore-masked, but its one fill is #10A37F -- OpenAI's brand green, the same case client-config-clients.ts already documents for dsh, where masking would repaint a trademark in the theme's text color. 080 named two journal-kind declaration sites when there are three independent re-declared unions and only the exhaustive JOURNAL_KIND_KEY record is compiler-checked. 060 proposed a no--element guard that client-marks-assets.test.ts already implements. 005 listed gajae's layer colors from a tuning run rather than the committed file. --- .../005_remaining_marks_provenance.md | 97 +++++++++++ .../060_wp7_remaining_marks.md | 75 ++++++++ .../070_wp8_integration_marks.md | 121 +++++++++++++ .../080_wp9_conflict_overwrite.md | 160 ++++++++++++++++++ 4 files changed, 453 insertions(+) create mode 100644 devlog/_plan/260831_aside_client_and_integrations_ux/005_remaining_marks_provenance.md create mode 100644 devlog/_plan/260831_aside_client_and_integrations_ux/060_wp7_remaining_marks.md create mode 100644 devlog/_plan/260831_aside_client_and_integrations_ux/070_wp8_integration_marks.md create mode 100644 devlog/_plan/260831_aside_client_and_integrations_ux/080_wp9_conflict_overwrite.md diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/005_remaining_marks_provenance.md b/devlog/_plan/260831_aside_client_and_integrations_ux/005_remaining_marks_provenance.md new file mode 100644 index 0000000000..3c893eec35 --- /dev/null +++ b/devlog/_plan/260831_aside_client_and_integrations_ux/005_remaining_marks_provenance.md @@ -0,0 +1,97 @@ +# The three clients still on a monogram + +Continuation of `004_brand_mark_provenance.md`, which closed six clients. After +that pass `CLIENT_MARKS` covers nine of twelve; `hermes`, `gajae` and `mcode` +still render `label.slice(0, 1)`. + +004 recorded `gajae` and `hermes` as monogram-only because neither publishes an +SVG with path geometry. That verdict stands on its own terms and is now +superseded by a wider rule: raster-to-vector conversion is authorized, so "the +vendor ships no SVG" no longer ends the search. Every mark below is traced from +the product's own raster asset rather than redrawn. + +All three were located 2026-08-31 through the `aside-jun` skill driving a +signed-in Aside browser, then re-fetched and verified locally. + +## mcode — MiniMax Code + +A genuine first-party SVG exists and 004 simply had not found it. + +- Source: `https://raw.githubusercontent.com/MiniMax-AI/MiniMax-01/main/figures/minimax.svg` +- 1255 bytes, `viewBox="0 0 490.16 411.7"`, one `` filled by a three-stop + linear gradient (`#e4177f` to `#e73562` to `#e94e4a`). +- It is the standalone symbol — the interlocking wave glyph with no wordmark + beside it. The docs-site asset (`mintcdn.com/minimax-zh/.../logo/light.svg`) + is the 129x32 horizontal lockup and was rejected for that reason: a wordmark + in a 20px square renders as unreadable letter mush. +- Publisher mark rather than product mark. MiniMax Code ships no mark of its + own and MiniMax is its publisher, so this is the closest first-party asset. +- Committed unmodified apart from dropping the Chinese-language `` and + layer-name metadata the authoring tool left behind. The gradient id is + renamed: `未命名的渐变_6` means "unnamed gradient 6", it collides across + inlined documents, and a non-ASCII id in a shared namespace is a trap. +- Multi-color, so it must NOT enter `MONOCHROME_CLIENT_MARKS`: masking would + flatten the gradient to one ink. + +## hermes — Hermes agent + +No usable SVG upstream; traced from the product's own application icon. + +- Rejected first: `website/static/img/favicon.svg` is 113 bytes and its whole + body is one `<text>` element. 004 already recorded this. +- Rejected second: `https://nousresearch.com/safari-pinned-tab.svg` (12746 + bytes, potrace output). Its first path is `M40 2560 l0 -2560 2520 0 2520 0 0 + 2560 0 2560 -2520 0 -2520 0 0 -2560z` — the full 512-unit frame. Rendered at + 20px that is a black square with a hairline hole, which is worse than a + monogram. +- Accepted: `apps/desktop/assets/icon.png` from `NousResearch/hermes-agent`, + 574273 bytes, 1024x1024 RGBA, artwork bounded at (101,108)-(924,914). This is + the icon the Hermes desktop application ships, so it is the product's own + mark, not the publisher's. +- Quantizing the opaque pixels shows two inks: a light plate (340877 px) and + black art (241765 px), with ~20k px of antialiasing between them. It is a + single-ink illustration on a rounded plate. +- Traced with `potrace -s --flat --turdsize 8 --alphamax 1.0 --opttolerance + 0.2` over the mask `alpha > 128 AND mean(rgb) < 110`, which keeps the black + art and discards the plate. One path, squared to `viewBox="0 0 823 823"` by + centering the 823x806 trace. +- `fill="currentColor"`, and it MUST join `MONOCHROME_CLIENT_MARKS`. A 20px + render on `#0d1117` confirmed the untinted mark is invisible in dark mode — + the same failure `prime`, `opencode` and `kimi` already have. + +## gajae — Gajae Code + +No SVG anywhere upstream, confirmed twice; traced from the mascot. + +- Searched and found empty: `assets/`, `public/` (404), `docs/`, plus + `assets/logo.svg`, `assets/favicon.svg`, `public/logo.svg`, + `public/favicon.svg`, `docs/logo.svg` (all 404), and every published + `@gajae-code/*` npm tarball at 0.15.6 (no SVG entries). `docs/brand-assets.md` + lists the active marks as PNG only. +- Accepted source: `assets/character.png`, 3190496 bytes, 1550x2048 RGBA, + transparent background. +- It is a vertical lockup: the mascot occupies y < 1650 and the `gajae-code` + wordmark sits below it. Rows 1650-1682 are fully transparent, which is the + seam the crop uses. Only the mascot is traced; a wordmark would not survive + 20px. +- The artwork is upscaled pixel art, so tracing at source resolution follows + every staircase and produced a 1.3 MB SVG. Downsampling to a 128px box with + Lanczos plus a 0.6px Gaussian first, then tracing, gives ~31 KB. That is + larger than any existing mark (`zcode.svg`, 11037 bytes) because this one is + an illustration rather than a glyph. +- Seven color layers, k-means++ seeded at 3 for determinism, painted + largest-area first. The committed file's fills are `#1d0a04`, `#561203`, + `#981001`, `#e3770c`, `#d32e02`, `#8f3a04` and `#02ac61` — read off + `gajae-code.svg` rather than off an earlier tuning run, whose centers differed + because it quantized at a different target size. The smallest layer is the + visor green and a fixed area floor would have dropped it, so the floor is a + fraction of the opaque area instead. +- Multi-color, so NOT in `MONOCHROME_CLIENT_MARKS`. + +## Rule this pass establishes + +A mark may be traced from the product's own raster asset when no vector exists, +provided the trace follows the source pixels rather than redrawing them, the +conversion parameters are recorded, and the result is verified by rendering at +the size it will actually be used. Tracing a wordmark into a square slot is +still refused, and so is a full-frame silhouette plate. diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/060_wp7_remaining_marks.md b/devlog/_plan/260831_aside_client_and_integrations_ux/060_wp7_remaining_marks.md new file mode 100644 index 0000000000..a585c6eb44 --- /dev/null +++ b/devlog/_plan/260831_aside_client_and_integrations_ux/060_wp7_remaining_marks.md @@ -0,0 +1,75 @@ +# wp7 — the three remaining marks + +Depends on nothing in this unit that is not already merged. Provenance and +conversion parameters are in 005; this document is the diff. + +## New assets + +`gui/public/provider-icons/minimax.svg` — the MiniMax symbol, fetched not +traced. Two edits to the upstream file: the `<title>资源 2` and the +`data-name` layer wrappers go (authoring-tool residue), and the gradient id +becomes `minimax-wave` so it cannot collide and carries no non-ASCII. + +`gui/public/provider-icons/hermes-agent.svg` — one `currentColor` path, +`viewBox="0 0 823 823"`. Named `hermes-agent` rather than `hermes` because +Hermes is also a provider name in this repo and `provider-icons/` is one flat +namespace. + +`gui/public/provider-icons/gajae-code.svg` — seven fill layers, largest area +first, `viewBox="0 0 128 128"`-class square box produced by centering the +traced bounds. + +## gui/public/provider-icons/README.md + +The "Two export clients deliberately have NO mark" section is now false and is +replaced. `gajae` and `hermes` move into the provenance list with their trace +parameters; `mcode` joins them. The section that remains explains the tracing +rule from 005 — a raster may be traced, a wordmark may not be squeezed into a +square slot, and a full-frame silhouette is rejected. + +## gui/src/components/apikeys-workspace/client-config-clients.ts + +`CLIENT_MARKS` gains three entries: + +```ts +hermes: "/provider-icons/hermes-agent.svg", +gajae: "/provider-icons/gajae-code.svg", +mcode: "/provider-icons/minimax.svg", +``` + +`MONOCHROME_CLIENT_MARKS` gains `hermes` only. `gajae` is seven inks and +`mcode` is a gradient; masking either would flatten it. + +The block comment above `CLIENT_MARKS` currently says two clients are absent on +purpose. That is now wrong in a way a reader would trust, so it is rewritten to +state the tracing rule and that every client has a mark. + +## Tests + +`gui/tests/client-marks-assets.test.ts` already covers more than the plan +originally credited it with. It asserts file existence, README provenance, the +no-``/no-``/must-have-geometry rule, that no multi-color mark is +masked, that the four known-invisible marks ARE masked, and that `dsh` is not. +Every one of those extends to the three new files without an edit, so the +"new guard: no `` element" the plan proposed would have been a duplicate +of an existing test rather than new coverage. + +What is genuinely uncovered is the completeness of the map. Nothing asserts that +every id in `CLIENTS` has a mark, so an entry dropped in a merge degrades to a +monogram silently and looks identical to a client that never had one. That guard +is new, and it was driven red by removing the `mcode` entry. + +Second new guard: a traced mark must record its raster source and its tracer +invocation in the README. A fetched mark has a URL to check; a traced one has +nothing to reproduce it from unless the parameters are written down. Driven red +by replacing the word `potrace` in the README. + +The mask-set expectations do need extending: `hermes` joins the pinned list of +marks that must be masked, while `gajae` and `mcode` are caught by the existing +multi-color assertion the moment they are added to the set by mistake. + +## Verification + +`cd gui && bun test tests/client-marks-assets.test.ts` plus the mask guard. +A 20px render of each new mark on `#ffffff` and on `#0d1117`, which is the check +that caught the Hermes dark-mode invisibility in the first place. diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/070_wp8_integration_marks.md b/devlog/_plan/260831_aside_client_and_integrations_ux/070_wp8_integration_marks.md new file mode 100644 index 0000000000..60ee9c7ca3 --- /dev/null +++ b/devlog/_plan/260831_aside_client_and_integrations_ux/070_wp8_integration_marks.md @@ -0,0 +1,121 @@ +# wp8 — one mark, every Integrations surface + +Depends on wp7: the shared component is only worth building once every client +has an asset, otherwise half the surfaces render monograms and the change looks +like a regression. + +## The problem + +`ClientConfigRow.tsx` is the only surface that draws a mark, and it owns the +img-versus-mask decision inline: + +```tsx +{mark ? monochrome ? + : + : {label.slice(0, 1)}} +``` + +Copying that ternary into three more files would put the invisibility rule in +four places, and the rule is exactly the thing that was already got wrong once. + +## New module: gui/src/components/ClientMark.tsx + +One component, one decision. Props: `markId` (the asset key, not the client id), +`label` (for the monogram letter), `size`, and `className`. + +It reads from a new shared map rather than `CLIENT_MARKS`, because the +Integrations page needs marks for four clients that are not export clients at +all — `codex`, `claude`, `claudeDesktop`, `grok`. Those are different id +namespaces that happen to overlap on strings like `claude`. + +## New module: gui/src/components/integration-marks.ts + +`INTEGRATION_MARKS: Record` mapping +every Integrations row to an asset already committed: + +- `codex` -> `/provider-icons/openai.svg` +- `claude`, `claudeDesktop` -> `/provider-icons/claude-color.svg` +- `grok` -> `/provider-icons/grok.svg` +- the twelve file clients -> the same assets `CLIENT_MARKS` uses + +None of the three non-file marks is masked, and the audit caught the plan being +wrong about two. `openai.svg` is a single fill, but that fill is `#10A37F` — +OpenAI's own green, the brand itself, which is exactly the `dsh` case +`client-config-clients.ts` already documents. Masking it would repaint a +trademark in the theme's text color. `grok.svg` is `#000000`, a genuine neutral +and therefore a real masking candidate — but it is xAI's published asset with a +literal fill, and rewriting it to `currentColor` is a change to someone else's +mark rather than a rendering decision. It stays an image and the dark-theme +contrast problem is recorded rather than papered over. + +So `MONOCHROME_INTEGRATION_MARKS` is exactly `MONOCHROME_CLIENT_MARKS`' assets +and nothing more. Keying it by ASSET PATH rather than client id is the one +improvement worth keeping from the original sketch: `kimi-color.svg` is reachable +as both a provider icon and a client mark, and a path-keyed set cannot mask it +on one surface and leave it unmasked on the other. + +## Surfaces + +`IntegrationsOverview.tsx` — `OverviewCard` puts a `` before the +`

`, inside `.integration-card-head`. It is `aria-hidden`: the card's +accessible name is the title button, and a mark that joined the name would read +"Claude Claude". + +`Integrations.tsx` — each tab button gets a 16px mark before its label. The tab +strip is 17 items on one row, so the mark is the thing that makes it scannable; +this is where the change pays for itself. + +`FileIntegrationPage.tsx` — a 24px mark in `.integration-client-head`, before +the `

`. + +`ClientConfigRow.tsx` — the inline ternary is deleted and replaced by +``. Behavior is identical, which the existing panel test asserts. + +## The Codex label + +`integrations.tab.codex` is `"Codex CLI"` in all nine locales. With a mark +beside it the "CLI" is redundant, and the row covers the Codex app and SDK too, +so it is also slightly wrong. It becomes `"Codex"` in every locale. `ja`, `zh`, +`zh-TW`, `ko`, `ru`, `tr`, `de`, `fr`, `en` all currently hold the literal +string `Codex CLI`, so this is one substitution per file. + +`integrations.codex.title` and `.body` are separate keys and are left alone; +they describe the routing behavior, where "CLI" is accurate. + +## CSS + +`gui/src/styles-apikeys-workspace.css` keeps `.awi-clientconfig-mark`; the new +component emits `.client-mark` plus `.client-mark--mask` / `.client-mark--img` +/ `.client-mark--monogram`, sized from a `--client-mark-size` custom property +so one rule serves 16/20/24/28px. The mask branch reuses the existing +`background: var(--text)` treatment. + +## Tests + +Extend `gui/tests/integrations-surfaces.test.tsx`: each of the three surfaces +renders a mark element per row/tab/header. Driven red by omitting the tab-strip +mark. + +New guard: every id in `INTEGRATION_MARKS` has a value, and every value resolves +to a committed file. Falsify by pointing one at a missing asset. + +New guard: `MONOCHROME_INTEGRATION_MARKS` contains no asset whose file has more +than one distinct fill. Falsify by adding `claude-color.svg`. + +New guard, from the audit: no asset whose single ink is a BRAND color may be +masked. `openai.svg` (`#10A37F`) and `deepseek-harness.svg` (`#4d6bfe`) are pinned +by name with their inks, the way the existing `dsh` test does, because no +property of the file distinguishes a brand ink from a neutral one. Falsify by +adding `openai.svg` to the mask set. + +New guard: no mark element contributes to an accessible name — each is +`aria-hidden` or has an empty `alt`. Falsify by giving the img an `alt={label}`. + +New guard: no locale contains `Codex CLI` under `integrations.tab.codex`. +Falsify by reverting one locale. + +## Verification + +`cd gui && bun test tests` focused files, plus headless Chrome screenshots of +`#integrations` at 1280 and 390 wide in both themes. The screenshots are the +only thing that proves the tab strip still wraps sanely with 17 marks in it. diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/080_wp9_conflict_overwrite.md b/devlog/_plan/260831_aside_client_and_integrations_ux/080_wp9_conflict_overwrite.md new file mode 100644 index 0000000000..c294b1ebae --- /dev/null +++ b/devlog/_plan/260831_aside_client_and_integrations_ux/080_wp9_conflict_overwrite.md @@ -0,0 +1,160 @@ +# wp9 — overwrite a conflict with defaults + +Independent of wp7/wp8 in code; ordered after them in the stack because they +touch the same page and a three-way overlap on `IntegrationsOverview.tsx` is not +worth reviewing. + +## The dead end today + +`classifyIntegration` reports `conflict` for two reasons and the writer refuses +both unconditionally (`writer.ts` apply branch): + +- `unowned-key` — our fragment paths are occupied by a value we did not write, + or there is no ownership record at all. +- `foreign-edit` — the recorded block's bytes changed, or a comment-capable + format drifted at file level. + +The GUI mirrors that: `FileIntegrationPage` sets `locked` for conflict and +`OverviewCard` disables the switch. So the only recovery is to open the file and +edit it by hand, which is precisely what a user reaching for a dashboard is +avoiding. The refusal is correct as a default — it is what stops us deleting +someone's work — but "correct default" and "only path" are different things. + +## Design: an explicit, snapshotted, journaled force + +The escape hatch is a THIRD operation, not a flag that weakens apply. + +`src/integrations/writer.ts` gains `overwriteIntegration(input)` and +`overwriteIntegrationCoordinated(input, options)`, next to apply/refresh/disable. +It differs from apply in exactly one place: where +`applyOrRefreshIntegration` returns `refuse(clientId, "conflict", ...)`, this +path continues. Everything else is shared, which is the point — the snapshot, +the atomic write, the compare-before-commit recheck, the ownership record and +the journal row all come from the same `commit()` call apply uses. + +Two properties it must NOT relax: + +- `unsafe` still refuses. A blocked container means our write would replace a + value the merge cannot reason about; forcing that is data loss with a receipt, + and 005's rule for marks applies here too — the snapshot is not a licence. +- `not_installed` and `non_loopback` still refuse. Neither is a conflict. + +The merge base is the parsed document as it stands, and the recorded fragment +paths are removed first WHEN A RECORD EXISTS — the same `removeFragments` call +the stale-refresh path makes. Without that, forcing over a drifted block leaves +orphans the new record does not cover, exactly as the refresh comment documents. + +The audit asked what happens in the other case, and it is worth writing down +because the answer looks like a bug and is not. `unowned-key` with NO record +gives `removeFragments` nothing to remove, so the merge runs against the +user's document with their values in our paths. `createdContainerPaths` +(`merge.ts`) then walks the same paths and records a container only where one +is NOT already a plain object — so every container the user already had is +correctly attributed to them, and the `createdContainers` we persist is +empty or near-empty. A later disable removes our leaves and leaves their +containers standing. That is the right outcome: we did not create those +containers, and pruning them would be the second act of destruction after the +one the user explicitly authorized. + +What the audit was right to flag is the `record!` non-null assertions in the +disable branch, whose comment records a real TypeError shipped that way. The +force path must not reuse them: it reaches the merge with a possibly-null +record by design, so it takes the apply-side code, where `record` is already +nullable, and never the disable-side code. + +New journal kind: `overwrite`. It could reuse `apply`, and that is the tempting +shortcut, but the rollback list is the one place a user goes after a mistake and +"applied" is a lie about an operation that replaced someone else's block. + +The kind string is declared in THREE independent places, none of which imports +another — the audit enumerated them and the plan originally named only two: + +- `src/integrations/journal.ts:22` — `export type OperationKind`, the persisted + vocabulary. +- `src/server/management/integration-routes.ts:73` — the route's own + `IntegrationJournalRow.kind` union, re-declared rather than imported. +- `gui/src/pages/integrations/integration-api.ts:57` — the GUI's union, also + re-declared, because the GUI does not import backend types. + +Plus `JOURNAL_KIND_KEY` in `overview-clients.ts`, which is an exhaustive +`Record` — so the compiler catches a missing +entry there, and only there. The two re-declared unions drift silently: a row +persisted as `overwrite` would fail no type check and render as an untranslated +key. A guard asserting the three declarations agree is worth more than the +feature test. + +The new i18n key goes into nine locale files. `gui/tests/locale-parity.test.ts` +already enforces key-set parity and additionally fails a zh-TW value left +identical to English unless it is allowlisted, so a placeholder translation is +not an option here. + +Undo is not special-cased: `restoreIntegration` already restores from the +snapshot by `opId` and does not care which kind produced it. The regression test +proves that end to end rather than asserting it here. + +## Route + +`src/server/management/integration-routes.ts`, the `PUT +/api/client-integrations/:client` handler. The body today is `{enabled: +boolean}`. It gains an optional `overwriteConflict?: boolean`, validated the +same way `confirmDrift` is on the restore route: present-and-not-boolean is a +400 `invalid_overwrite_conflict`. + +`enabled: false` plus `overwriteConflict: true` is a 400, not a silent ignore. +Disabling a block we do not own is the deletion this whole subsystem exists to +prevent, and a caller asking for it has misunderstood the field. + +`writerFailureResponse` needs no new branch: an `unsafe` refusal from a forced +call is still `integration_unsafe`. + +## GUI + +`FileIntegrationPage.tsx` — when `status.state === "conflict"`, a +`btn-danger` button appears beside the locked switch, opening a +`ConsequenceDialog`. The switch stays locked; the button is the only way +through. + +`IntegrationsOverview.tsx` — the same action on a conflicted card, reusing the +existing `ConsequenceDialog` and `pendingToggle` focus-restore machinery. + +`ConsequenceDialog` copy needs the file path, the sentence that the current +block is replaced by opencodex defaults, the sentence that a snapshot is taken +and the operation is undoable from the rollback list, and a confirm label that +is not "OK". Nine locales. + +The reason matters in the copy: `unowned-key` means "a block we did not write +is in the way", `foreign-edit` means "your edit to our block will be +discarded". Same operation, materially different thing being lost, so two +`changes` strings selected on `status.reason`. + +## Tests + +Backend, driven red first: + +1. force apply over `unowned-key` succeeds, writes our block, journals kind + `overwrite`, and `restore` of that op returns the original bytes exactly. + Falsify by leaving the conflict refusal in place. +2. force apply over `foreign-edit` drops the recorded fragments before merging, + so no orphan survives. Falsify by merging without `removeFragments`. +3. force apply over `unsafe` still refuses. Falsify by moving the force branch + above the unsafe check. +4. `{enabled: false, overwriteConflict: true}` is a 400. Falsify by ignoring + the combination. +5. a normal apply is unchanged — no `overwriteConflict` means the conflict + refusal still fires. Falsify by defaulting the field to true. + +GUI, driven red first: + +6. the overwrite button renders only for `conflict`, and never for `absent`, + `current`, `stale`, `unsafe` or not-installed. Falsify by widening the + condition to `unsafe`. +7. clicking it does not mutate until the dialog is confirmed. Falsify by wiring + the button straight to the mutation. +8. the dialog names the config path. Falsify by dropping the `path` var. + +## Verification + +Focused backend files run in CI, not locally (the full local suite is +forbidden). `cd gui && bun test tests` for the GUI files. A screenshot of a +conflicted client page is worth having but needs a conflicted config to exist; +the GUI test asserting the button's presence is the real gate. From 44b4de39da14cbe3d4549854ca83972beb09b73a Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 31 Aug 2026 23:16:00 +0900 Subject: [PATCH 04/42] feat(gui): give the last three export clients their real marks (#3082) hermes, gajae and mcode were the three still rendering a monogram letter. Each now has a first-party vector, and two of them are traced rather than fetched because their vendors publish no usable SVG. mcode: MiniMax commits its own symbol at MiniMax-01/figures/minimax.svg -- the standalone wave glyph, not the 129x32 wordmark their docs site serves, which is unreadable at 20px. Path data is verbatim; the Chinese-language and layer-name metadata go, and the gradient id (which literally means "unnamed gradient 6") is renamed because a non-ASCII id collides awkwardly across inlined documents. hermes: two candidates were rejected before this one. The repo favicon is 113 bytes whose entire body is a <text> element -- it passes an SVG parse and draws blank wherever the glyph is missing. nousresearch.com/safari-pinned-tab.svg is a real trace, but its first path is the full 512-unit frame, so at 20px it is a black square with a hairline hole. What works is the icon the Hermes desktop app itself ships: 1024x1024 RGBA, quantizing to a light plate plus black artwork. Tracing the mask "opaque AND darker than mid-grey" keeps the art and discards the plate. gajae: no SVG exists anywhere upstream -- not in assets/, not in docs/, not at any of the five plausible logo paths, not in any published @gajae-code tarball, and brand-assets.md says as much. The mascot PNG is a vertical lockup, so only the character is traced; rows 1650-1682 are fully transparent and that seam is where the wordmark gets cut. The artwork is upscaled pixel art, so tracing at source resolution followed every staircase into a 1.3 MB file; downsampling to a 128px box first gives 31 KB for the same silhouette. hermes joins MONOCHROME_CLIENT_MARKS and the other two must not. Its trace is one ink, and a 20px render on #0d1117 showed nothing at all -- the same failure prime, opencode and kimi already had. gajae is seven traced layers and mcode is a three-stop gradient; masking either would flatten a palette into one ink. The smallest gajae layer is 292 px and a fixed area floor would have dropped it. It is the visor green, which is the one feature that makes the character recognizable, so the floor is a fraction of the opaque area instead. Two new guards, each driven red first. Every id in CLIENTS must have a mark: without it an entry dropped in a merge degrades to a monogram silently and looks identical to a client that never had one. And a traced mark must record its raster source and tracer invocation in the README -- a fetched mark has a URL to re-check, a traced one has nothing to reproduce it from otherwise. The other six assertions in client-marks-assets.test.ts already covered the new files without an edit, including the no-<text>, no-<image>, must-have-geometry rule that rejected the Hermes favicon in the first place. --- gui/public/provider-icons/README.md | 57 ++- gui/public/provider-icons/gajae-code.svg | 410 ++++++++++++++++++ gui/public/provider-icons/hermes-agent.svg | 207 +++++++++ gui/public/provider-icons/minimax.svg | 1 + .../client-config-clients.ts | 23 +- gui/tests/client-marks-assets.test.ts | 33 +- 6 files changed, 714 insertions(+), 17 deletions(-) create mode 100644 gui/public/provider-icons/gajae-code.svg create mode 100644 gui/public/provider-icons/hermes-agent.svg create mode 100644 gui/public/provider-icons/minimax.svg diff --git a/gui/public/provider-icons/README.md b/gui/public/provider-icons/README.md index 4ebcb92034..fa54029a32 100644 --- a/gui/public/provider-icons/README.md +++ b/gui/public/provider-icons/README.md @@ -47,14 +47,49 @@ Export-client marks (used by the API tab's connect rows, not the provider list): on the web (`aside.com/favicon.svg` is a 404), so the shipping application is the first-party source. -Two export clients deliberately have NO mark and render a monogram instead, -because the rule is that a client without a real first-party asset gets a -monogram rather than a borrowed or unreliable one: - -- `gajae` — Gajae Code (`Yeachan-Heo/gajae-code`) publishes only raster marks: a - mascot PNG, a vertical logo PNG, and a base64 PNG favicon. Every asset here is - SVG and a lone raster would not hold up at 20px across densities. -- `hermes` — the favicon at `NousResearch/hermes-agent` is a 113-byte SVG whose - entire body is a `<text>` element rendering one unicode glyph. It has no path - data, so it renders differently per machine and blank where the glyph is - missing. It passes an automated SVG check and is still not a brand mark. +- `minimax.svg` — fetched 2026-08-31 from + `https://raw.githubusercontent.com/MiniMax-AI/MiniMax-01/main/figures/minimax.svg`, + MiniMax's own symbol as committed in their own model repository. The API-docs + asset (`mintcdn.com/minimax-zh/.../logo/light.svg`) is the 129x32 horizontal + lockup and was rejected: a wordmark in a 20px square is unreadable. This is the + publisher's mark — MiniMax Code ships none of its own — used for the `mcode` + client. Path data is verbatim; the Chinese-language `<title>` and layer-name + metadata the authoring tool left behind are removed, and the gradient id + `未命名的渐变_6` ("unnamed gradient 6") is renamed `minimax-wave` because a + non-ASCII id collides awkwardly across inlined documents. + +Two marks are TRACED rather than fetched. Their vendors publish no usable +vector, and a trace that follows the source pixels is a truer mark than a +monogram. What is still refused either way: a horizontal wordmark squeezed into +this square slot, and a full-frame silhouette plate that renders as a filled box +at 20px. + +- `hermes-agent.svg` — traced 2026-08-31 from + `NousResearch/hermes-agent` `apps/desktop/assets/icon.png` (574273 bytes, + 1024x1024 RGBA), the icon the Hermes desktop application itself ships, so this + is the product's own mark. Two earlier candidates were rejected: + `website/static/img/favicon.svg` is 113 bytes and its entire body is one + `<text>` element with no path data, and `nousresearch.com/safari-pinned-tab.svg` + opens with the full 512-unit frame as its first path, so it renders as a black + square. Traced with + `potrace -s --flat --turdsize 8 --alphamax 1.0 --opttolerance 0.2` over the + mask `alpha > 128 AND mean(rgb) < 110`, which keeps the black artwork and + discards the light plate behind it. One path, `currentColor`, squared to + `viewBox="0 0 823 823"` by centering the 823x806 trace. Named + `hermes-agent` rather than `hermes` because Hermes is also a provider name + and this directory is one flat namespace. +- `gajae-code.svg` — traced 2026-08-31 from `Yeachan-Heo/gajae-code` + `assets/character.png` (3190496 bytes, 1550x2048 RGBA), the mascot. No SVG + exists upstream: `assets/` and `docs/` hold only raster, `public/` is a 404, + the five plausible `logo.svg`/`favicon.svg` paths all 404, no published + `@gajae-code/*` tarball at 0.15.6 contains one, and `docs/brand-assets.md` + lists the marks as PNG. The source is a vertical lockup, so only the mascot is + traced — rows 1650-1682 are fully transparent, which is the seam the crop uses, + and the `gajae-code` wordmark below it is discarded. The artwork is upscaled + pixel art, so tracing at source resolution followed every staircase and gave a + 1.3 MB file; downsampling to a 128px box (Lanczos, then a 0.6px Gaussian) + first gives ~31 KB. Seven color layers, k-means++ seeded at 3 so the + quantization is deterministic, painted largest-area first. The smallest layer + is 292 px and a fixed area floor would have dropped it — it is the visor + green, which is the feature that makes the character recognizable, so the + floor is a fraction of the opaque area instead. diff --git a/gui/public/provider-icons/gajae-code.svg b/gui/public/provider-icons/gajae-code.svg new file mode 100644 index 0000000000..d4705e6ac3 --- /dev/null +++ b/gui/public/provider-icons/gajae-code.svg @@ -0,0 +1,410 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g transform="translate(9.5,0)"><g transform="translate(0.000000,128.000000) scale(0.100000,-0.100000)" fill="#561203"><path d="M218 1253 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M265 +1238 c11 -6 29 -18 40 -27 12 -9 8 -4 -7 12 -16 15 -34 27 -41 27 -6 0 -3 -6 +8 -12z M968 1243 c18 -2 30 -9 27 -14 -4 -5 3 -9 14 -9 12 0 21 4 21 9 0 11 +-37 21 -70 19 -19 -1 -17 -2 8 -5z M375 1188 c22 -27 41 -48 43 -48 7 0 -36 +56 -59 75 -12 11 -5 -1 16 -27z M940 1206 c-43 -24 -122 -147 -112 -175 3 -8 +0 -11 -7 -7 -6 4 -11 2 -11 -3 0 -17 18 -13 29 6 9 16 12 16 36 -7 30 -28 55 +-67 35 -55 -8 5 -11 3 -8 -6 3 -8 12 -13 22 -11 9 2 16 -1 16 -5 0 -8 -15 -12 +-57 -15 -8 -1 -9 -7 -2 -20 20 -36 -3 -40 -40 -6 -20 18 -11 7 19 -25 30 -31 +55 -57 56 -57 1 0 -2 16 -7 36 -17 59 -6 75 21 31 17 -28 25 -35 31 -25 12 18 +11 56 -1 63 -6 4 -8 10 -5 15 6 9 -83 105 -105 112 -21 7 43 103 93 139 42 30 +40 38 -3 15z M355 1146 c10 -14 19 -26 22 -26 7 0 -17 39 -28 46 -6 3 -3 -5 6 +-20z M421 1130 c0 -3 8 -32 19 -65 12 -33 18 -46 16 -29 -5 29 -33 106 -35 94z +M395 1073 c15 -36 25 -52 25 -41 0 11 -30 78 -35 78 -3 0 2 -17 10 -37z M780 +1091 c27 -52 -56 -137 -87 -88 -3 6 -1 7 5 3 7 -4 12 -4 12 -1 0 9 -108 55 +-130 55 -11 0 -20 -4 -20 -10 0 -5 -10 -8 -22 -6 -21 3 -57 -28 -46 -39 8 -8 +48 7 42 16 -7 12 28 11 60 -1 14 -5 26 -6 26 -1 0 5 -10 11 -22 14 -12 4 -20 +9 -16 12 14 14 58 -18 45 -33 -10 -13 -9 -14 3 -10 8 3 27 -1 43 -9 15 -7 33 +-13 40 -12 27 3 38 -2 47 -23 8 -22 9 -22 9 -2 1 11 9 46 19 76 18 54 18 56 0 +67 -17 11 -18 10 -8 -8z M283 1011 c132 -7 146 -12 147 -53 0 -10 4 -18 8 -18 +5 0 7 14 4 30 -2 17 -9 30 -14 30 -5 0 -7 4 -4 9 3 4 -56 8 -132 8 -137 0 +-137 0 -9 -6z M550 1010 c0 -5 5 -10 11 -10 5 0 7 -4 4 -10 -4 -6 1 -7 11 -3 +12 4 15 3 10 -5 -6 -11 5 -16 19 -9 3 1 9 -3 13 -9 9 -13 104 -66 96 -53 -3 6 +-27 24 -52 40 -45 29 -24 33 23 5 25 -16 55 -27 55 -21 0 3 -19 17 -41 31 -38 +24 -64 32 -111 37 -10 1 -18 5 -18 9 0 4 -4 8 -10 8 -5 0 -10 -4 -10 -10z m70 +-25 c0 -12 -19 -12 -23 0 -4 9 -1 12 9 8 7 -3 14 -7 14 -8z M467 975 c7 -37 +13 -45 13 -17 0 11 8 23 18 25 9 2 7 3 -5 2 -12 -1 -25 3 -27 9 -3 6 -3 -2 1 +-19z M800 990 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0 -4 -4 +-4 -10z M97 972 c-30 -33 -10 -82 56 -144 22 -21 35 -38 29 -38 -5 0 -15 7 +-22 15 -7 8 -17 15 -23 15 -6 0 -18 6 -27 13 -9 6 -3 0 13 -15 16 -14 51 -37 +77 -49 52 -24 75 -73 41 -82 -9 -3 -1 -5 17 -6 34 -1 39 -9 19 -31 -10 -12 -9 +-12 6 -1 19 15 23 41 6 41 -5 0 -7 5 -3 12 4 7 3 8 -5 4 -6 -4 -13 -1 -15 6 +-21 73 -30 97 -37 93 -12 -8 -3 43 12 62 12 15 12 16 -4 10 -11 -4 -17 -2 -15 +6 3 18 -44 87 -62 88 -8 1 1 5 20 9 33 7 32 7 -16 9 -38 1 -55 -4 -67 -17z +M228 973 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M52 940 c0 -19 +2 -27 5 -17 2 9 2 25 0 35 -3 9 -5 1 -5 -18z M790 960 c0 -5 5 -10 10 -10 6 0 +10 5 10 10 0 6 -4 10 -10 10 -5 0 -10 -4 -10 -10z M600 946 c0 -3 13 -12 29 +-20 15 -8 49 -31 75 -51 25 -19 46 -31 46 -25 0 5 -19 22 -42 38 -49 34 -108 +65 -108 58z M781 924 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z +M336 854 c-29 -16 -22 -34 14 -34 13 0 13 1 0 10 -25 16 -7 19 49 8 66 -12 +112 -37 71 -39 -29 -1 79 -38 122 -42 14 -1 24 -5 22 -8 -2 -4 3 -13 12 -20 +18 -15 12 -81 -9 -109 -11 -13 -10 -13 7 0 11 8 21 13 23 10 4 -7 33 49 33 65 +0 64 -280 193 -344 159z m311 -216 c-3 -7 -5 -2 -5 12 0 14 2 19 5 13 2 -7 2 +-19 0 -25z M760 830 c0 -11 4 -20 9 -20 5 0 7 9 4 20 -3 11 -7 20 -9 20 -2 0 +-4 -9 -4 -20z M844 778 c73 -76 152 -177 148 -191 -4 -15 39 -57 52 -50 6 3 8 +3 4 -2 -4 -4 -1 -14 7 -21 7 -8 15 -19 18 -26 2 -7 7 -10 11 -7 21 19 -63 136 +-183 254 -113 111 -153 141 -57 43z M390 810 c34 -11 60 -11 35 0 -11 5 -29 8 +-40 8 -16 0 -15 -2 5 -8z M268 763 c5 -21 7 -23 10 -9 2 10 0 22 -6 28 -6 6 +-7 0 -4 -19z M950 761 c0 -14 62 -61 73 -54 5 3 -1 15 -14 27 -27 25 -59 40 +-59 27z M710 686 c0 -13 29 -26 36 -15 4 8 3 9 -4 5 -7 -4 -12 -2 -12 3 0 6 +-4 11 -10 11 -5 0 -10 -2 -10 -4z M350 658 c0 -5 8 -8 19 -8 10 0 37 -6 59 +-14 35 -13 43 -13 59 0 17 13 16 14 -12 8 -24 -4 -27 -3 -15 6 12 9 7 10 -20 +5 -20 -3 -30 -3 -23 2 7 5 -4 8 -28 8 -21 0 -39 -3 -39 -7z M510 630 c-8 -5 +-10 -10 -5 -10 6 0 17 5 25 10 8 5 11 10 5 10 -5 0 -17 -5 -25 -10z M570 628 +c-49 -13 -54 -22 -28 -43 13 -11 18 -13 11 -6 -14 16 5 41 31 41 8 0 18 5 21 +10 3 6 5 10 3 9 -2 0 -19 -5 -38 -11z M691 624 c0 -11 3 -14 6 -6 3 7 2 16 -1 +19 -3 4 -6 -2 -5 -13z M717 633 c-9 -9 -9 -41 1 -46 4 -3 7 8 7 24 0 16 0 29 +-1 29 0 0 -4 -3 -7 -7z M250 615 c-7 -8 -20 -15 -29 -15 -10 0 -5 -7 14 -20 +17 -11 25 -20 19 -20 -6 0 -10 -16 -10 -35 1 -47 27 -46 62 3 26 37 26 37 20 +5 -10 -57 -37 -89 -54 -67 -13 18 -14 17 -8 -7 9 -42 -123 -167 -164 -156 -14 +3 -20 0 -20 -10 0 -32 -40 35 -41 69 -1 33 -1 33 -8 2 -4 -17 -11 -36 -15 -43 +-5 -7 -2 -10 6 -9 7 2 12 -3 10 -10 -5 -26 18 -54 34 -41 12 10 13 9 9 -4 -4 +-8 -1 -18 6 -23 8 -4 10 -3 5 4 -5 8 1 12 15 12 18 0 19 2 9 15 -10 12 -9 15 +6 15 26 0 149 75 173 106 24 30 27 48 9 41 -7 -2 -12 -9 -11 -15 3 -12 -54 +-72 -68 -72 -5 0 6 14 26 31 19 17 32 36 28 42 -3 5 1 15 10 21 25 19 59 93 +56 124 -2 16 3 38 11 48 14 18 14 18 -7 0 -13 -10 -25 -28 -29 -40 -7 -24 -33 +-56 -46 -56 -15 0 -9 43 10 64 16 20 16 20 0 7 -13 -10 -18 -10 -18 -2 0 7 -4 +9 -10 6 -16 -10 -12 7 7 27 9 10 14 18 11 18 -3 0 -11 -7 -18 -15z M370 620 +c0 -12 30 -28 62 -32 14 -2 34 -10 44 -17 11 -8 4 1 -14 18 -18 17 -37 29 -42 +26 -5 -3 -11 -1 -15 5 -8 13 -35 13 -35 0z M870 626 c0 -2 8 -10 18 -17 15 +-13 16 -12 3 4 -13 16 -21 21 -21 13z M129 603 c-13 -16 -12 -17 4 -4 9 7 17 +15 17 17 0 8 -8 3 -21 -13z M580 598 c0 -14 50 -40 65 -34 31 11 16 34 -25 38 +-22 2 -40 0 -40 -4z m70 -18 c0 -5 -6 -10 -14 -10 -8 0 -18 5 -21 10 -3 6 3 +10 14 10 12 0 21 -4 21 -10z M805 580 c-3 -5 1 -10 10 -10 9 0 13 5 10 10 -3 +6 -8 10 -10 10 -2 0 -7 -4 -10 -10z M440 552 c0 -11 14 -25 35 -35 36 -17 45 +-32 23 -39 -7 -3 2 -5 20 -5 25 0 29 3 18 9 -20 11 -21 28 0 28 8 0 12 -4 9 +-10 -3 -5 -1 -10 4 -10 6 0 11 9 11 20 0 11 -4 20 -10 20 -5 0 -18 9 -29 21 +-14 15 -21 17 -26 8 -6 -8 -4 -10 5 -4 9 5 11 4 6 -4 -5 -7 -12 -9 -19 -3 -26 +20 -47 22 -47 4z M960 566 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 +21 -21 13z M400 543 c0 -12 5 -25 10 -28 6 -3 10 -11 10 -17 0 -7 -4 -7 -13 1 +-10 8 -17 7 -27 -2 -10 -10 -12 -9 -7 5 12 34 -7 26 -25 -11 -16 -32 -17 -43 +-7 -67 7 -16 12 -32 11 -35 -1 -4 9 -4 21 -1 17 5 18 7 5 10 -9 2 -19 10 -22 +19 -6 14 -4 14 11 2 10 -7 15 -9 11 -5 -4 5 -2 16 5 24 9 13 9 14 0 8 -16 -9 +-17 8 -2 35 7 14 14 17 22 11 7 -5 21 -8 32 -6 10 1 25 -2 32 -8 7 -6 13 -8 +13 -4 0 8 -33 40 -45 44 -5 2 -15 13 -22 25 -12 22 -12 22 -13 0z M639 538 +c-34 -32 -43 -33 -51 -6 -5 20 -6 18 -7 -9 -1 -18 2 -33 6 -33 14 0 93 51 93 +60 0 16 -16 11 -41 -12z M774 534 c-18 -15 -18 -15 3 -4 28 13 48 2 83 -47 14 +-19 31 -41 38 -48 7 -8 19 -30 27 -49 8 -19 20 -38 26 -42 8 -5 7 -9 -2 -14 +-9 -6 -4 -16 19 -35 18 -15 30 -30 28 -33 -2 -4 7 -17 19 -30 22 -23 22 -23 +28 -3 3 12 1 21 -4 21 -6 0 -9 6 -7 13 5 27 -79 173 -132 229 -59 62 -89 72 +-126 42z M1020 515 c13 -14 26 -25 28 -25 3 0 -5 11 -18 25 -13 14 -26 25 -28 +25 -3 0 5 -11 18 -25z M73 515 c-3 -9 -3 -18 -1 -21 3 -3 8 4 11 16 6 23 -1 +27 -10 5z M673 508 c-23 -30 -26 -42 -5 -24 14 12 22 14 22 7 0 -7 5 -9 10 -6 +15 9 12 35 -4 35 -7 0 -18 -6 -23 -12z M600 480 c-8 -5 -18 -6 -22 -4 -4 3 -8 +0 -8 -6 0 -7 8 -9 19 -5 38 12 57 -66 22 -85 -29 -15 -79 -12 -89 5 -7 13 -10 +12 -20 -10 -7 -15 -8 -25 -2 -25 6 0 10 7 10 16 0 8 5 12 10 9 6 -4 8 -10 6 +-14 -3 -4 16 -4 42 0 74 12 74 12 70 -23 -3 -28 -10 -37 -52 -57 -27 -14 -44 +-27 -38 -29 6 -2 20 1 30 7 11 6 22 7 26 4 9 -9 -75 -93 -92 -93 -8 0 -12 -6 +-9 -15 4 -8 2 -17 -4 -21 -5 -3 -21 -23 -35 -45 -36 -58 -125 -40 -140 28 -9 +43 -14 51 -11 18 2 -19 -4 -38 -15 -50 -11 -11 -17 -32 -16 -54 1 -36 1 -36 5 +-1 6 50 13 52 67 25 27 -14 55 -25 63 -25 7 0 13 -7 13 -16 0 -14 3 -14 15 -4 +10 8 12 16 6 19 -6 4 -6 12 -1 21 6 8 10 11 10 5 0 -5 13 13 29 40 25 44 145 +168 154 159 1 -2 3 6 4 16 1 11 -3 20 -10 20 -8 0 -8 3 1 12 15 15 16 78 1 78 +-5 0 -7 5 -3 12 24 38 -5 107 -36 88z M414 468 c-3 -4 0 -8 7 -8 7 0 2 -12 +-12 -27 -24 -27 -24 -28 -1 -8 33 28 45 52 26 52 -8 0 -17 -4 -20 -9z M1050 +474 c0 -5 -11 -10 -25 -10 -51 -3 -62 -17 -20 -24 40 -8 75 2 75 21 0 6 -7 3 +-16 -5 -20 -20 -30 -21 -14 -1 7 9 10 18 6 22 -3 3 -6 2 -6 -3z M686 455 c-9 +-26 -7 -32 5 -12 6 10 9 21 6 23 -2 3 -7 -2 -11 -11z M460 434 c0 -14 -4 -22 +-10 -19 -11 7 -55 -14 -46 -22 3 -3 12 0 20 6 19 16 38 5 34 -18 -4 -25 20 +-51 46 -50 18 0 18 2 -5 10 -19 7 -25 15 -22 28 3 10 0 22 -6 25 -5 4 -7 14 +-4 22 5 14 8 13 18 -6 12 -22 12 -22 23 9 7 18 7 31 1 31 -5 0 -9 -10 -9 -22 +-1 -22 -2 -21 -16 5 -18 34 -24 34 -24 1z M701 414 c0 -11 3 -14 6 -6 3 7 2 +16 -1 19 -3 4 -6 -2 -5 -13z M666 368 c-9 -66 -5 -93 11 -63 16 31 25 78 14 +71 -5 -4 -11 6 -13 21 -3 20 -6 11 -12 -29z M315 361 c-3 -10 -1 -29 4 -42 9 +-21 10 -19 10 19 1 45 -4 54 -14 23z M340 349 c0 -13 5 -31 10 -39 7 -11 8 -5 +4 20 -8 43 -14 51 -14 19z M713 363 c-15 -5 -17 -23 -4 -23 5 0 11 7 15 15 3 +8 4 15 3 14 -1 0 -8 -3 -14 -6z M262 323 c-25 -20 -37 -71 -23 -92 21 -32 73 +59 53 94 -8 15 -11 14 -30 -2z M770 310 c-11 -7 -7 -8 16 -4 19 4 34 1 38 -5 +4 -6 15 -11 25 -11 32 0 59 -41 58 -87 -1 -27 -5 -39 -12 -33 -6 5 -10 -4 -11 +-27 -1 -46 28 -16 47 48 22 71 -100 161 -161 119z M2 275 c0 -16 2 -22 5 -12 +2 9 2 23 0 30 -3 6 -5 -1 -5 -18z M735 290 c-3 -5 -16 -10 -28 -11 -12 -1 -5 +-5 18 -10 47 -11 90 -50 117 -104 12 -25 18 -31 14 -15 -5 24 -5 24 10 5 14 +-19 14 -17 10 20 -6 44 -43 85 -77 85 -28 0 -61 19 -54 31 4 5 4 9 1 9 -3 0 +-8 -4 -11 -10z M205 273 c-5 -10 -9 -22 -9 -27 -1 -6 -17 -19 -36 -29 -84 -47 +-84 -47 -63 -62 22 -18 12 -19 -22 -3 -16 7 -33 28 -42 50 -9 22 -17 37 -20 +34 -9 -9 27 -75 49 -90 48 -32 106 -32 139 0 39 38 39 62 -1 33 -37 -28 -53 +-17 -17 11 15 12 29 36 33 56 7 39 1 55 -11 27z m-58 -139 c-3 -3 -12 -4 -19 +-1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z M356 274 c-3 -9 -2 -12 5 -9 5 4 18 1 27 +-6 13 -11 12 -8 -2 9 -23 26 -22 26 -30 6z M320 272 c0 -5 -16 -27 -35 -49 +-36 -42 -44 -60 -22 -47 7 4 9 3 4 -2 -5 -5 -12 -9 -16 -9 -9 0 -71 -52 -71 +-60 0 -3 -11 -23 -25 -43 -14 -20 -25 -43 -25 -52 0 -8 15 8 33 36 38 61 72 +104 82 104 3 0 -9 -19 -27 -42 -64 -82 -72 -108 -33 -108 14 0 25 6 25 13 0 6 +20 37 45 67 44 53 49 68 47 120 -1 22 0 23 7 5 7 -17 9 -16 15 10 4 17 9 38 +12 48 3 9 1 17 -5 17 -6 0 -11 -4 -11 -8z m-85 -186 c-10 -14 -22 -26 -27 -26 +-10 0 -3 16 20 43 22 25 27 13 7 -17z M130 261 c0 -5 9 -7 20 -4 11 3 20 7 20 +9 0 2 -9 4 -20 4 -11 0 -20 -4 -20 -9z M341 234 c0 -11 3 -14 6 -6 3 7 2 16 +-1 19 -3 4 -6 -2 -5 -13z M125 230 c-13 -6 -15 -9 -5 -9 8 0 22 4 30 9 18 12 +2 12 -25 0z M955 198 c-17 -42 -19 -58 -6 -58 24 0 46 56 31 81 -7 10 -13 4 +-25 -23z M334 185 c-16 -13 -16 -14 -1 -9 9 3 24 1 32 -6 22 -19 28 -6 7 13 +-16 15 -20 15 -38 2z M959 133 c-24 -26 -79 -115 -79 -127 0 -14 26 -4 38 14 +7 11 29 32 50 47 20 16 38 35 38 43 1 8 3 21 5 29 7 26 -26 22 -52 -6z M438 +143 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z M821 139 c-1 -8 -19 +-33 -42 -55 -90 -87 -88 -79 -24 -82 31 -2 63 2 70 8 10 8 15 7 19 -2 3 -7 3 +0 0 17 -4 20 -3 26 4 17 7 -11 10 -9 15 7 3 12 1 19 -4 15 -5 -3 -9 3 -9 13 +-1 24 -28 83 -29 62z M600 86 c0 -8 -13 -24 -30 -36 -16 -12 -30 -28 -30 -36 +0 -21 18 -17 25 5 3 11 14 23 24 26 21 6 34 42 20 51 -5 3 -9 -2 -9 -10z M969 +48 c-42 -36 -43 -48 -5 -48 33 0 36 2 36 30 0 33 -8 37 -31 18z"/></g><g transform="translate(0.000000,128.000000) scale(0.100000,-0.100000)" fill="#1d0a04"><path d="M477 1026 c-22 -17 -13 -46 14 -39 21 6 147 -51 213 -96 44 -31 44 +-31 51 21 8 54 3 63 -50 89 -20 10 -20 10 -1 -5 25 -20 4 -21 -34 -1 -47 24 +-35 10 20 -24 28 -17 50 -33 50 -36 0 -5 -31 5 -45 15 -15 11 -105 49 -105 45 +0 -2 24 -17 54 -34 92 -53 79 -59 -20 -9 -53 27 -104 48 -115 48 -22 0 -24 11 +-7 28 16 16 -3 15 -25 -2z M400 1016 c0 -2 7 -7 16 -10 8 -3 12 -2 9 4 -6 10 +-25 14 -25 6z M160 974 c18 -6 65 -74 62 -91 -2 -8 4 -10 15 -6 16 6 16 5 4 +-10 -13 -17 -26 -75 -13 -63 3 4 11 -6 17 -21 12 -28 12 -28 19 4 10 46 90 87 +143 74 54 -12 215 -95 206 -106 -4 -4 -3 -5 3 -2 13 8 64 -39 64 -58 0 -17 +-28 -70 -37 -71 -39 -4 -55 -23 -20 -23 26 -1 37 -5 37 -16 0 -21 -24 -29 -49 +-15 -19 10 -21 8 -21 -25 0 -38 1 -38 52 -3 30 20 38 22 38 8 0 -6 -22 -22 +-50 -38 -27 -15 -50 -31 -50 -35 0 -4 9 -3 19 3 16 8 22 6 30 -7 5 -10 11 -16 +13 -13 2 3 12 17 21 33 36 56 57 31 32 -38 -16 -45 -20 -89 -6 -81 4 3 11 24 +15 47 29 180 185 154 280 -46 15 -32 29 -55 32 -53 2 3 -7 26 -21 52 -31 58 +-31 56 8 57 32 2 32 2 -3 9 -38 7 -24 21 25 24 60 3 14 46 -188 180 -386 254 +-554 339 -662 335 -16 -1 -23 -3 -15 -5z m573 -290 c13 -5 13 -9 0 -35 -8 -17 +-13 -39 -10 -50 5 -20 5 -20 -14 -3 -21 19 -25 64 -7 82 14 14 12 14 31 6z +m37 -60 c0 -16 -16 -19 -25 -4 -8 13 4 32 16 25 5 -4 9 -13 9 -21z m55 -44 c3 +-5 -1 -10 -10 -10 -9 0 -13 5 -10 10 3 6 8 10 10 10 2 0 7 -4 10 -10z M782 +899 c-15 -64 -16 -91 -2 -69 5 8 11 12 14 10 3 -3 47 -47 100 -98 57 -57 92 +-85 89 -73 -3 12 -9 24 -14 27 -5 3 -9 12 -9 21 0 15 2 14 18 0 38 -35 31 -15 +-8 22 -41 39 -41 39 -26 70 16 30 15 32 -11 73 -31 48 -42 35 -24 -28 10 -38 +10 -38 -44 16 -30 30 -55 60 -55 67 0 32 -18 8 -28 -38z M292 797 c-15 -20 +-20 -66 -8 -81 13 -17 268 -89 299 -84 34 5 38 84 5 98 -64 27 -287 78 -296 +67z m91 -66 c6 -6 0 -12 -17 -17 -27 -7 -71 20 -61 37 6 9 64 -6 78 -20z m152 +-35 c30 -14 30 -14 5 -24 -25 -10 -45 -7 -67 11 -36 27 9 36 62 13z M227 749 +c6 -6 9 -18 6 -25 -3 -8 0 -14 6 -14 18 0 13 37 -6 45 -14 5 -15 3 -6 -6z +M273 635 c-34 -45 -41 -61 -23 -50 6 3 10 2 10 -4 0 -5 -6 -12 -12 -14 -7 -3 +-8 -6 -3 -6 15 -1 112 96 103 104 -22 19 -46 10 -75 -30z M642 650 c0 -14 2 +-19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M350 606 c-8 -12 -13 -34 -11 -49 +3 -30 -32 -107 -55 -120 -8 -4 -14 -13 -14 -19 0 -7 6 -6 15 2 23 19 30 4 11 +-25 -21 -31 -129 -103 -169 -112 -22 -4 -25 -8 -16 -19 9 -11 7 -14 -12 -14 +-17 0 -20 -3 -12 -11 18 -18 82 -1 111 28 25 25 25 25 19 -13 -4 -24 -16 -48 +-33 -62 -35 -31 -22 -42 16 -13 38 27 40 7 3 -31 -15 -15 -22 -28 -15 -28 11 +0 112 114 131 147 21 38 20 3 -1 -66 -15 -48 -37 -89 -65 -124 -53 -64 -54 +-77 -8 -77 33 0 35 2 35 33 0 19 8 42 18 52 11 12 16 30 14 46 -3 18 3 34 18 +49 13 13 19 28 15 40 -3 11 -1 29 5 40 8 15 8 29 -1 55 -15 42 1 88 29 84 9 +-2 27 3 40 10 17 9 27 9 36 2 25 -21 4 42 -26 79 -15 19 -28 44 -28 55 1 16 4 +15 18 -10 16 -30 16 -30 22 2 5 33 5 33 35 17 30 -15 30 -15 6 10 -13 14 -26 +25 -30 24 -27 -3 -54 5 -70 21 -18 18 -18 17 -31 -3z m20 -106 c-16 -29 -12 +-34 8 -10 17 21 17 21 41 -4 28 -29 -3 -83 -41 -73 -9 3 -19 0 -21 -7 -3 -6 +-10 2 -16 18 -12 29 14 110 32 99 4 -2 3 -13 -3 -23z m-41 -162 c0 -38 -1 -40 +-10 -19 -8 22 -4 61 7 61 2 0 4 -19 3 -42z m-39 -63 c-14 -44 -38 -64 -51 -44 +-14 21 -2 72 23 92 30 25 44 1 28 -48z m-120 -9 c0 -2 -9 -6 -20 -9 -11 -3 +-20 -1 -20 4 0 5 9 9 20 9 11 0 20 -2 20 -4z M476 622 c6 -4 14 -16 18 -28 7 +-24 43 -64 56 -64 17 0 11 36 -10 55 -11 10 -17 21 -15 25 3 4 -5 10 -18 14 +-30 8 -44 7 -31 -2z M520 500 c0 -5 4 -10 9 -10 6 0 13 5 16 10 3 6 -1 10 -9 +10 -9 0 -16 -4 -16 -10z M477 476 c-3 -8 4 -38 16 -66 18 -44 19 -52 7 -60 +-13 -8 -12 -10 2 -10 22 0 27 20 11 50 -19 36 -16 60 6 60 11 0 23 5 26 10 4 +6 -7 10 -27 10 -25 0 -29 3 -18 10 13 8 13 10 -1 10 -9 0 -19 -6 -22 -14z +M637 396 c-4 -9 -2 -16 3 -16 14 0 13 -67 -1 -76 -8 -4 -7 -9 2 -14 10 -7 10 +-12 1 -23 -14 -16 54 5 120 38 80 39 193 -36 170 -114 -15 -50 -56 -102 -46 +-59 3 15 1 35 -5 45 -9 15 -10 13 -5 -12 6 -29 6 -29 -9 -10 -15 19 -16 17 -6 +-19 10 -37 9 -58 -7 -114 -13 -45 21 -22 53 36 39 70 76 107 98 98 11 -4 15 +-2 13 6 -3 7 4 21 14 32 10 11 18 27 17 35 0 12 -2 11 -8 -3 -7 -19 -8 -19 +-22 0 -43 60 -200 120 -258 99 -53 -18 -65 -19 -58 -2 3 8 1 20 -4 28 -7 11 +-9 9 -9 -7 0 -11 -7 -29 -15 -40 -13 -18 -14 -15 -15 31 0 55 -13 88 -23 61z +m347 -209 c-4 -22 -23 -47 -35 -47 -13 0 -11 16 6 58 17 38 37 31 29 -11z +M1020 301 c0 -6 4 -13 10 -16 6 -3 7 1 4 9 -7 18 -14 21 -14 7z M1006 125 c-9 +-26 -7 -32 5 -12 6 10 9 21 6 23 -2 3 -7 -2 -11 -11z M148 113 c7 -3 16 -2 19 +1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M228 103 c-23 -27 -30 -43 -20 -43 11 0 +46 50 39 56 -2 2 -11 -4 -19 -13z M950 52 c-25 -22 -42 -44 -37 -48 4 -4 21 7 +37 24 35 39 50 40 51 5 1 -21 3 -23 6 -8 11 59 -10 69 -57 27z M610 67 c0 -9 +-4 -17 -9 -17 -12 0 -41 -29 -41 -41 0 -5 18 -9 39 -9 39 0 39 0 31 36 -9 40 +-20 56 -20 31z M446 45 c-3 -8 -1 -15 4 -15 6 0 10 7 10 15 0 8 -2 15 -4 15 +-2 0 -6 -7 -10 -15z"/></g><g transform="translate(0.000000,128.000000) scale(0.100000,-0.100000)" fill="#d32e02"><path d="M246 1261 c11 -10 74 -32 74 -25 0 5 -65 34 -77 34 -4 0 -3 -4 3 -9z +M946 1231 c-12 -5 -15 -10 -9 -14 6 -4 16 0 23 8 14 17 13 17 -14 6z M913 +1213 c4 -3 1 -13 -6 -22 -11 -14 -10 -14 5 -2 16 12 16 31 1 31 -4 0 -3 -3 0 +-7z M875 1160 c-4 -6 -10 -8 -15 -5 -4 3 -7 -3 -6 -13 0 -9 -4 -16 -11 -15 -8 +2 -10 -4 -6 -14 4 -11 3 -14 -5 -9 -8 5 -10 1 -5 -10 5 -13 3 -15 -7 -9 -9 5 +-11 4 -6 -3 10 -17 10 -17 44 38 17 28 29 50 27 50 -2 0 -7 -5 -10 -10z M716 +1055 c-3 -9 0 -12 10 -8 9 3 14 -1 15 -13 0 -18 15 4 18 26 2 13 -38 8 -43 -5z +M787 1009 c-10 -28 -17 -61 -16 -73 0 -11 8 4 18 34 10 30 22 63 26 73 4 9 3 +17 -2 17 -6 0 -17 -23 -26 -51z M436 1032 c-3 -5 1 -28 9 -53 13 -42 14 -42 +14 -12 1 17 -3 34 -9 38 -5 3 -7 12 -3 20 5 16 -2 21 -11 7z M690 1026 c0 -2 +7 -7 16 -10 8 -3 12 -2 9 4 -6 10 -25 14 -25 6z M687 865 c10 -13 22 -25 27 +-25 5 0 -1 11 -14 25 -30 32 -36 32 -13 0z M306 821 c5 -4 65 -23 134 -40 69 +-18 137 -39 153 -46 15 -8 25 -9 21 -4 -3 6 -18 15 -32 20 -48 18 -286 79 +-276 70z M800 745 c19 -19 36 -35 39 -35 3 0 -10 16 -29 35 -19 19 -36 35 -39 +35 -3 0 10 -16 29 -35z M830 691 c0 -5 17 -19 38 -31 37 -22 37 -22 8 9 -30 +31 -46 39 -46 22z M250 669 c0 -6 -7 -9 -15 -5 -8 3 -14 2 -12 -2 2 -8 -97 +-103 -104 -99 -2 1 -3 0 -2 -2 1 -2 -4 -10 -12 -18 -22 -21 -19 -36 5 -23 11 +6 20 17 20 25 0 8 21 26 48 39 61 31 110 81 88 89 -9 4 -16 2 -16 -4z M315 +604 c-45 -51 -54 -67 -31 -58 9 3 25 21 36 39 39 63 98 65 147 7 25 -29 32 +-19 9 11 -12 15 -93 48 -115 47 -3 -1 -24 -21 -46 -46z M567 558 c8 -46 13 +-48 13 -5 0 21 -4 37 -10 37 -5 0 -7 -14 -3 -32z M165 535 c-45 -32 -67 -56 +-84 -94 -13 -28 -28 -48 -33 -45 -9 6 -11 -59 -1 -69 3 -4 17 9 30 28 22 33 +53 47 53 25 0 -5 -8 -10 -17 -10 -15 0 -16 -2 -3 -10 18 -11 41 6 33 26 -3 8 +1 14 10 14 17 0 37 17 62 53 13 19 17 20 25 7 7 -10 8 1 3 35 -11 89 -11 89 +-78 40z M460 510 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0 -4 +-4 -4 -10z M740 488 c-24 -39 -26 -55 -5 -38 29 24 38 -4 10 -30 -28 -27 -32 +-40 -10 -40 21 0 19 -10 -7 -37 -12 -13 -16 -23 -10 -23 6 0 13 4 17 10 3 5 +12 7 20 4 8 -4 18 1 21 10 3 9 16 16 28 15 21 0 21 -1 1 -10 -17 -8 -16 -9 8 +-6 15 3 27 0 27 -4 0 -5 6 -9 13 -9 7 0 30 -9 52 -20 54 -27 57 -25 10 7 -22 +14 -42 28 -44 29 -2 2 1 10 8 18 17 21 -4 63 -58 114 -53 51 -56 51 -81 10z +m47 -19 c12 -32 12 -32 -12 -9 -13 13 -22 27 -19 31 10 16 20 9 31 -22z M561 +494 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M552 435 c0 -16 2 +-22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z M650 435 c0 -14 5 -25 10 -25 6 0 +10 11 10 25 0 14 -4 25 -10 25 -5 0 -10 -11 -10 -25z M593 408 c-21 -28 -24 +-38 -9 -32 14 5 30 33 23 41 -2 2 -9 -2 -14 -9z M440 390 c0 -5 5 -10 11 -10 +5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0 -4 -4 -4 -10z M361 334 c0 -11 3 -14 6 +-6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M558 343 c7 -3 16 -2 19 1 4 3 -2 6 -13 +5 -11 0 -14 -3 -6 -6z M603 335 c0 -8 4 -12 9 -9 4 3 8 9 8 15 0 5 -4 9 -8 9 +-5 0 -9 -7 -9 -15z M516 315 c-11 -8 -15 -15 -9 -15 6 0 16 7 23 15 16 19 11 +19 -14 0z M777 293 c-35 -6 -19 -33 20 -33 13 0 35 -7 48 -16 14 -9 25 -12 25 +-8 0 5 -13 18 -29 29 -16 10 -27 23 -25 27 3 4 2 7 -3 7 -4 -1 -21 -4 -36 -6z +M26 261 c-18 -28 63 -76 87 -52 8 8 7 11 -5 11 -9 0 -30 11 -46 25 -17 14 -33 +21 -36 16z M698 263 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z +M655 250 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z M726 252 +c-16 -2 -47 -15 -70 -29 -25 -16 -41 -22 -44 -14 -11 34 -152 -146 -152 -196 +0 -23 36 -14 50 12 13 24 98 101 104 94 2 -3 16 -51 32 -107 4 -16 24 -16 23 +1 -1 6 -4 23 -8 37 -7 26 29 60 63 60 23 0 65 57 59 80 -8 31 4 24 38 -22 24 +-32 30 -37 24 -18 -12 44 -85 106 -119 102z m-127 -74 c-2 -2 -26 -23 -53 -48 +-28 -26 -44 -36 -38 -24 14 27 82 87 90 80 3 -3 3 -7 1 -8z M543 233 c4 -3 -3 +-18 -15 -32 -23 -26 -23 -26 4 -2 24 21 29 41 11 41 -4 0 -3 -3 0 -7z M468 +223 c12 -2 30 -2 40 0 9 3 -1 5 -23 4 -22 0 -30 -2 -17 -4z M427 119 c7 -7 15 +-10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z M429 83 c-13 -16 -12 -17 4 -4 +16 13 21 21 13 21 -2 0 -10 -8 -17 -17z"/></g><g transform="translate(0.000000,128.000000) scale(0.100000,-0.100000)" fill="#8f3a04"><path d="M193 1273 c-7 -2 -13 -8 -13 -12 0 -10 59 -1 66 10 5 9 -32 11 -53 2z +M291 1258 c2 -2 24 -21 49 -43 33 -29 38 -32 20 -10 -30 36 -52 54 -64 55 -5 +0 -7 -1 -5 -2z M310 1215 c13 -14 26 -25 28 -25 3 0 -5 11 -18 25 -13 14 -26 +25 -28 25 -3 0 5 -11 18 -25z M905 1222 c-29 -18 -85 -88 -85 -107 0 -5 16 13 +35 40 19 27 49 57 67 67 18 9 29 17 23 18 -5 0 -23 -8 -40 -18z M400 1142 c6 +-13 14 -21 18 -18 3 4 -2 14 -12 24 -18 16 -18 16 -6 -6z M648 1133 c12 -2 32 +-2 45 0 12 2 2 4 -23 4 -25 0 -35 -2 -22 -4z M560 1094 c-22 -20 -40 -41 -39 +-48 0 -7 2 -7 5 1 3 7 13 9 22 6 12 -3 13 -2 4 4 -13 10 12 38 53 61 11 6 14 +12 8 12 -7 0 -31 -16 -53 -36z M736 1115 c19 -15 19 -15 -1 -8 -19 6 -19 5 2 +-18 35 -39 18 -77 -32 -70 -5 1 -1 -6 9 -14 31 -23 43 -18 60 25 18 45 11 71 +-24 90 -28 15 -37 12 -14 -5z M586 1041 c5 -5 20 -12 34 -15 25 -7 25 -7 0 8 +-28 17 -50 21 -34 7z M826 1006 c-23 -49 -20 -68 18 -105 34 -33 56 -29 37 7 +-10 18 -6 20 47 24 6 1 12 5 12 10 0 5 -7 8 -16 6 -10 -2 -19 3 -22 11 -3 8 0 +11 7 7 19 -12 12 2 -20 39 -38 43 -43 43 -63 1z M565 1016 c22 -7 51 -18 64 +-25 13 -7 21 -9 17 -3 -9 15 -72 42 -98 41 -16 0 -12 -4 17 -13z M128 1013 c6 +-2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z M500 1014 c0 -5 31 -18 70 +-28 22 -6 22 -5 7 4 -21 14 -77 31 -77 24z M77 992 c-36 -39 9 -172 57 -172 7 +0 19 -7 26 -15 7 -8 17 -15 22 -15 6 0 -7 17 -29 38 -22 20 -47 51 -55 67 -17 +34 -34 97 -20 77 7 -10 11 -10 22 3 11 13 10 14 -6 8 -17 -6 -17 -5 -5 10 18 +23 8 22 -12 -1z M290 1004 c14 -2 44 -7 67 -10 24 -3 50 -12 58 -20 16 -17 20 +-7 6 15 -6 9 -34 15 -82 17 -41 1 -63 1 -49 -2z M480 973 c0 -5 15 -9 33 -10 +37 -3 124 -46 173 -87 51 -42 34 -43 -21 -2 -25 20 -52 36 -58 36 -17 0 63 +-69 100 -85 35 -16 88 -65 184 -169 63 -69 63 -69 24 -48 -44 24 -33 15 62 +-53 70 -50 91 -79 72 -101 -15 -19 -4 -18 15 1 16 16 4 65 -16 65 -5 0 -6 5 +-2 12 4 6 3 8 -3 5 -12 -7 -55 36 -51 50 8 27 -197 256 -210 235 -11 -17 -39 +-6 -45 18 -12 47 -257 173 -257 133z M460 886 c0 -3 9 -10 20 -16 11 -6 20 -8 +20 -6 0 3 -9 10 -20 16 -11 6 -20 8 -20 6z M280 815 c-8 -9 -8 -15 -2 -15 12 +0 26 19 19 26 -2 2 -10 -2 -17 -11z M197 661 c-16 -10 -42 -36 -59 -57 -30 +-38 -30 -38 13 5 24 24 57 50 74 57 16 7 23 13 15 14 -8 0 -28 -9 -43 -19z +M697 666 c-11 -29 4 -69 17 -44 24 45 24 54 6 55 -10 1 -21 -4 -23 -11z M329 +633 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 -17z M190 580 +c-18 -12 -2 -12 25 0 13 6 15 9 5 9 -8 0 -22 -4 -30 -9z M144 554 c-18 -14 +-18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z M560 540 c0 -16 4 -30 8 -30 +5 0 7 14 4 30 -2 17 -6 30 -8 30 -2 0 -4 -13 -4 -30z M450 510 c7 -13 7 -20 1 +-20 -6 0 -8 -7 -5 -15 4 -8 11 -15 16 -15 6 0 8 6 5 14 -4 10 2 16 21 18 27 3 +27 3 3 17 -13 7 -21 8 -17 2 4 -6 2 -11 -3 -11 -6 0 -11 7 -11 15 0 8 -5 15 +-10 15 -7 0 -7 -6 0 -20z M381 481 c-15 -27 -14 -44 2 -35 9 6 9 4 -1 -8 -22 +-27 3 -29 28 -3 13 14 18 25 11 25 -7 0 -10 4 -7 9 3 5 -1 14 -8 21 -12 9 -17 +7 -25 -9z M56 460 c-8 -16 -11 -30 -6 -30 4 0 13 14 19 30 5 17 8 30 6 30 -2 +0 -10 -13 -19 -30z M520 480 c-8 -5 -10 -10 -5 -10 6 0 17 5 25 10 8 5 11 10 +5 10 -5 0 -17 -5 -25 -10z M584 462 c-14 -9 -24 -62 -13 -62 5 0 9 11 9 25 0 +33 16 32 25 -2 5 -20 2 -31 -11 -40 -21 -16 -11 -17 17 -3 21 12 25 41 9 71 +-11 20 -19 23 -36 11z M517 443 c-12 -11 -8 -38 8 -59 14 -18 14 -17 3 9 -10 +24 -9 29 8 42 16 12 16 14 3 15 -9 0 -19 -3 -22 -7z M470 409 c0 -11 4 -18 10 +-14 5 3 7 12 3 20 -7 21 -13 19 -13 -6z M36 403 c-6 -14 -5 -15 5 -6 7 7 10 +15 7 18 -3 3 -9 -2 -12 -12z M425 401 c-6 -5 -23 -11 -40 -13 -36 -4 -45 -41 +-22 -87 13 -25 13 -31 1 -35 -21 -8 -17 -57 8 -83 21 -23 13 -34 -9 -12 -18 +18 -43 2 -43 -26 0 -87 101 -125 144 -56 14 22 30 42 35 45 6 4 8 13 4 21 -3 +9 1 15 9 15 18 0 102 86 91 93 -5 3 -15 2 -23 -4 -20 -12 -43 -12 -35 1 3 6 +-1 7 -9 4 -9 -3 -13 -10 -10 -15 3 -5 -8 -7 -23 -4 -15 2 -44 7 -64 10 -59 9 +-96 70 -64 105 20 23 60 12 96 -26 27 -29 40 -30 64 -4 18 19 85 29 85 12 0 +-4 -13 -22 -29 -40 -34 -37 -30 -40 17 -12 59 36 47 83 -20 74 -29 -3 -58 -13 +-64 -20 -6 -8 -18 -14 -26 -14 -18 0 -51 34 -41 43 3 4 1 7 -5 7 -7 0 -12 5 +-12 11 0 5 5 7 12 3 7 -4 8 -3 4 4 -9 14 -19 15 -31 3z m-29 -143 c5 -7 2 -8 +-7 -3 -12 7 -12 5 -1 -6 9 -9 39 -15 90 -16 42 -1 79 -2 81 -3 10 -1 -37 -44 +-63 -57 -35 -18 -90 -12 -117 13 -21 19 -26 70 -6 77 6 3 13 6 14 6 1 1 5 -4 +9 -11z m-18 -107 c15 -10 37 -22 50 -25 36 -12 21 -56 -19 -56 -41 0 -95 70 +-72 93 9 10 12 9 41 -12z m85 -8 c-7 -2 -19 -2 -25 0 -7 3 -2 5 12 5 14 0 19 +-2 13 -5z M486 374 c-4 -9 -4 -19 -1 -22 2 -3 7 3 11 12 4 9 4 19 1 22 -2 3 +-7 -3 -11 -12z M12 285 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z +M770 247 c0 -2 15 -16 33 -33 32 -29 32 -29 3 4 -28 30 -36 37 -36 29z M297 +73 c-37 -36 -5 -73 63 -73 56 0 70 3 70 15 0 8 -6 15 -13 15 -8 0 -36 11 -63 +25 -57 29 -49 27 -57 18z"/></g><g transform="translate(0.000000,128.000000) scale(0.100000,-0.100000)" fill="#e3770c"><path d="M337 1196 c23 -26 54 -75 70 -108 15 -33 29 -55 31 -49 10 30 -77 +169 -121 193 -13 7 -6 -7 20 -36z M896 1199 c-26 -20 -86 -114 -86 -134 0 -5 +13 13 29 41 16 28 46 65 66 82 40 36 33 44 -9 11z M595 1111 c-60 -37 -70 -57 +-25 -53 23 2 61 -4 95 -17 67 -25 75 -26 75 -5 0 10 -6 14 -14 11 -10 -4 -13 +-1 -9 9 3 8 12 13 19 10 8 -3 14 -1 14 5 0 5 -7 12 -15 15 -21 9 -19 27 3 20 +14 -5 14 -4 -2 9 -28 21 -104 19 -141 -4z M88 993 c-22 -24 -24 -40 -2 -19 67 +68 331 -37 671 -266 84 -56 171 -108 181 -108 11 0 -50 50 -81 67 -17 8 -26 +20 -23 28 5 15 -104 125 -126 125 -16 1 -117 90 -102 90 6 0 31 -14 55 -30 44 +-31 44 -31 11 -2 -65 57 -182 108 -182 78 0 -40 -54 -42 -64 -2 -12 47 -302 +80 -338 39z M207 655 c-15 -8 -40 -28 -55 -46 -48 -56 -46 -65 4 -18 27 25 60 +53 74 63 30 20 15 21 -23 1z M185 569 c-71 -39 -95 -63 -115 -116 -26 -71 -20 +-78 10 -12 17 35 39 61 74 86 75 53 89 73 31 42z M756 491 c-3 -4 6 -18 19 +-31 24 -23 24 -23 12 9 -11 31 -21 38 -31 22z M580 426 c0 -30 -20 -35 -27 -7 +-5 21 -33 18 -33 -4 0 -34 41 -51 61 -25 21 28 24 46 9 55 -6 4 -10 -5 -10 +-19z M377 363 c-31 -31 6 -98 61 -109 65 -13 189 50 167 86 -9 15 -57 0 -76 +-24 -16 -20 -17 -20 -59 17 -40 34 -77 46 -93 30z M780 351 c0 -5 9 -7 20 -4 +11 3 20 7 20 9 0 2 -9 4 -20 4 -11 0 -20 -4 -20 -9z M360 236 c0 -72 94 -100 +154 -45 37 34 37 34 -26 34 -40 1 -75 7 -95 18 -33 18 -33 17 -33 -7z M650 +239 c-43 -26 -45 -28 -38 -35 4 -3 24 6 46 19 21 14 52 27 68 30 27 4 26 4 -6 +5 -20 1 -50 -7 -70 -19z M556 159 c-20 -17 -42 -41 -48 -53 -6 -12 10 -2 38 +24 27 25 51 46 53 48 2 1 2 5 -1 8 -3 3 -22 -9 -42 -27z M340 155 c-29 -35 63 +-101 93 -67 14 15 11 20 -33 48 -40 26 -51 29 -60 19z"/></g><g transform="translate(0.000000,128.000000) scale(0.100000,-0.100000)" fill="#981001"><path d="M961 1226 c-10 -12 -8 -13 9 -9 25 7 37 23 17 23 -8 0 -20 -6 -26 +-14z M345 1170 c10 -11 20 -20 23 -20 3 0 -3 9 -13 20 -10 11 -20 20 -23 20 +-3 0 3 -9 13 -20z M881 1157 c-13 -21 -12 -21 5 -5 10 10 16 20 13 22 -3 3 +-11 -5 -18 -17z M376 1127 c3 -10 9 -15 12 -12 3 3 0 11 -7 18 -10 9 -11 8 -5 +-6z M825 1069 c-4 -6 -5 -12 -2 -15 2 -3 7 2 10 11 7 17 1 20 -8 4z M441 964 +c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M460 961 c0 -11 -6 -22 +-12 -24 -8 -3 -6 -6 5 -6 12 -1 17 7 17 24 0 14 -2 25 -5 25 -3 0 -5 -9 -5 +-19z M761 925 c1 -11 0 -27 -4 -35 -12 -33 -17 -70 -8 -70 15 0 33 89 21 108 +-10 15 -10 15 -9 -3z M340 845 c0 -16 27 -31 48 -27 16 3 23 1 18 -6 -4 -6 2 +-8 14 -5 11 3 20 1 20 -4 0 -6 11 -8 25 -5 35 6 6 27 -51 37 -21 4 -47 9 -56 +12 -10 2 -18 2 -18 -2z M305 820 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 +-4 -16 -10z M495 770 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z +M261 738 c-1 -35 24 -51 108 -73 92 -24 85 -12 -9 15 -73 21 -81 25 -89 54 -9 +31 -9 31 -10 4z M565 750 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 +-10z M596 741 c-4 -5 -2 -12 4 -16 16 -10 12 -95 -4 -101 -8 -3 -33 0 -56 6 +-23 6 -44 9 -47 7 -2 -3 20 -11 51 -18 64 -14 91 -2 83 38 -2 12 0 20 4 17 5 +-3 9 6 9 19 0 27 -34 64 -44 48z M268 673 c10 -4 4 -15 -19 -39 -18 -19 -28 +-34 -21 -34 11 0 62 60 62 73 0 4 -8 7 -17 6 -12 0 -14 -2 -5 -6z M358 628 +c-13 -13 -17 -18 -8 -12 8 6 24 16 35 22 11 6 15 12 8 12 -7 0 -22 -10 -35 +-22z M470 609 c0 -5 -6 -7 -12 -3 -7 4 -9 3 -5 -1 13 -15 36 -14 30 1 -6 17 +-13 18 -13 3z M540 595 c6 -8 18 -11 25 -9 8 4 15 -3 18 -18 5 -23 5 -22 4 1 +-2 18 -10 27 -30 32 -23 6 -26 5 -17 -6z M168 579 c-21 -11 -38 -22 -38 -24 0 +-3 18 4 40 15 22 11 40 23 40 25 0 7 0 7 -42 -16z M282 577 c-22 -23 -30 -67 +-14 -67 10 0 42 33 42 44 0 4 -9 2 -20 -4 -26 -14 -25 -2 2 27 12 13 19 23 16 +23 -2 0 -14 -10 -26 -23z M614 583 c10 -11 36 -12 36 -2 0 5 -10 9 -22 9 -12 +0 -18 -3 -14 -7z M231 564 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 +-13z M490 571 c0 -6 4 -13 10 -16 6 -3 7 1 4 9 -7 18 -14 21 -14 7z M297 518 +c-33 -47 -30 -92 3 -48 18 23 36 78 28 85 -1 1 -16 -15 -31 -37z M242 520 c0 +-14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M783 532 c-9 -6 1 -20 33 +-47 53 -47 80 -100 62 -122 -10 -12 -9 -17 5 -25 10 -5 15 -13 12 -19 -4 -5 2 +-6 11 -2 12 4 15 3 10 -6 -4 -6 -4 -11 1 -10 19 3 34 -2 29 -10 -5 -8 43 -35 +51 -28 2 3 -11 17 -28 32 -24 19 -29 29 -20 35 10 5 10 9 2 15 -7 4 -19 24 +-28 46 -8 21 -19 39 -23 39 -5 0 -11 8 -14 18 -6 18 -74 92 -85 92 -3 0 -12 +-4 -18 -8z M570 496 c0 -10 -5 -14 -12 -10 -8 5 -9 0 -5 -17 4 -13 7 -31 8 +-39 1 -8 5 5 10 29 5 25 6 47 4 50 -3 2 -5 -3 -5 -13z M665 480 c-10 -11 -13 +-20 -7 -20 6 0 12 -10 14 -21 3 -19 4 -18 14 9 15 40 3 59 -21 32z M246 469 +c5 -19 3 -21 -5 -9 -9 13 -13 12 -26 -7 -25 -36 -45 -53 -62 -53 -9 0 -13 -6 +-10 -14 8 -20 -15 -37 -33 -26 -13 8 -12 10 3 10 9 0 17 5 17 10 0 22 -31 8 +-53 -25 -13 -19 -28 -32 -33 -27 -5 4 0 -3 11 -16 11 -13 21 -19 23 -14 2 6 +12 8 22 5 38 -10 175 116 164 151 -9 31 -24 43 -18 15z M725 479 c-4 -6 -5 +-12 -2 -15 2 -3 7 2 10 11 7 17 1 20 -8 4z M735 450 c-4 -6 -11 -8 -16 -5 -5 +4 -9 0 -9 -8 0 -8 -3 -29 -7 -47 -5 -29 -4 -32 11 -27 14 6 15 4 5 -16 -12 +-22 -12 -22 10 -3 25 22 27 36 6 36 -22 0 -18 13 10 40 13 12 22 27 19 31 -7 +12 -21 11 -29 -1z M248 375 c-38 -36 -40 -49 -3 -17 21 17 50 52 44 52 -2 0 +-21 -16 -41 -35z M657 390 c3 -11 7 -20 9 -20 2 0 4 9 4 20 0 11 -4 20 -9 20 +-5 0 -7 -9 -4 -20z M20 310 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 +10 -2 0 -4 -4 -4 -10z M762 299 c-19 -7 -23 -29 -6 -29 3 0 2 5 -1 10 -3 6 3 +10 14 10 10 0 27 3 37 7 10 4 15 2 11 -3 -3 -5 8 -19 24 -29 26 -18 44 -45 19 +-30 -6 3 -1 -10 11 -29 29 -50 34 -50 36 -3 1 46 -26 87 -58 87 -10 0 -21 5 +-24 10 -8 12 -32 12 -63 -1z M11 259 c-5 -40 76 -135 97 -115 3 3 -3 8 -12 12 +-22 9 -12 21 43 48 26 14 54 32 62 42 9 12 -6 7 -45 -14 -66 -36 -113 -31 +-135 12 -5 12 -10 18 -10 15z M629 239 c-24 -20 -24 -20 4 -3 15 9 27 18 27 +20 0 8 -8 4 -31 -17z M301 204 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 +-5 -13z M549 172 c-44 -47 -44 -47 1 -7 25 22 47 41 49 43 2 1 2 4 0 7 -3 2 +-25 -17 -50 -43z M783 192 c6 -25 -35 -82 -58 -82 -44 0 -72 -33 -62 -72 11 +-44 25 -48 54 -16 12 13 38 39 58 58 21 19 41 44 45 55 6 16 10 11 22 -29 8 +-27 18 -44 22 -38 9 15 -3 62 -16 62 -6 0 -8 7 -4 15 3 8 2 14 -3 12 -4 -1 +-20 12 -35 28 -25 28 -27 28 -23 7z M225 137 c-24 -33 -85 -127 -85 -132 0 +-16 17 -1 29 27 8 18 29 52 48 76 19 23 31 42 26 42 -4 0 -13 -6 -18 -13z +M128 133 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M552 71 c-56 +-54 -64 -71 -32 -71 11 0 20 6 20 14 0 8 14 24 30 36 17 12 30 28 30 36 0 8 5 +12 10 9 6 -3 10 1 10 9 0 26 -14 19 -68 -33z M632 40 c0 -14 2 -19 5 -12 2 6 +2 18 0 25 -3 6 -5 1 -5 -13z M833 24 c-6 -13 -7 -24 -3 -24 9 0 25 39 18 45 +-2 2 -8 -7 -15 -21z"/></g><g transform="translate(0.000000,128.000000) scale(0.100000,-0.100000)" fill="#02ac61"><path d="M305 751 c-10 -17 34 -44 61 -37 17 5 23 11 17 17 -14 14 -72 29 -78 +20z M460 701 c0 -24 48 -41 80 -29 25 10 25 10 -5 24 -35 15 -75 18 -75 5z +M745 640 c-7 -12 2 -30 16 -30 12 0 12 27 0 35 -5 3 -12 1 -16 -5z"/></g></g></svg> \ No newline at end of file diff --git a/gui/public/provider-icons/hermes-agent.svg b/gui/public/provider-icons/hermes-agent.svg new file mode 100644 index 0000000000..4761dd73fa --- /dev/null +++ b/gui/public/provider-icons/hermes-agent.svg @@ -0,0 +1,207 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 823 823"><g transform="translate(0,8.5)"><g transform="translate(0.000000,806.000000) scale(0.100000,-0.100000)" fill="currentColor"><path d="M4295 7694 c-394 -23 -775 -108 -1087 -243 -648 -280 -1081 -579 +-1337 -922 -175 -234 -319 -553 -381 -844 -27 -125 -38 -390 -21 -490 52 -298 +232 -525 499 -629 34 -13 76 -27 94 -31 l33 -6 -42 40 c-78 74 -170 243 -188 +346 -7 37 -5 36 28 -28 20 -37 58 -96 86 -130 43 -54 75 -84 184 -168 l21 -17 +-17 -218 c-23 -275 -88 -857 -121 -1075 -91 -599 -194 -799 -412 -799 -82 0 +-144 23 -202 75 -87 79 -132 190 -132 331 0 110 14 176 53 257 18 37 30 67 27 +67 -4 0 -30 -25 -59 -55 -62 -65 -125 -190 -146 -290 -48 -226 14 -493 145 +-623 186 -184 508 -173 742 26 78 65 164 175 213 271 40 77 44 60 15 -55 -43 +-167 -149 -334 -268 -423 -148 -111 -355 -119 -557 -22 -181 88 -289 236 -330 +450 l-15 76 4 -120 c8 -218 66 -370 194 -507 108 -114 220 -181 377 -223 l80 +-22 7 -62 c14 -129 96 -246 213 -307 83 -44 153 -57 271 -52 81 3 110 9 158 +31 116 54 201 136 273 264 39 70 40 59 9 -73 -40 -165 -104 -312 -189 -439 +-22 -33 -136 -157 -252 -275 -229 -232 -314 -338 -375 -464 -37 -77 -80 -210 +-70 -219 3 -2 43 -7 90 -10 l85 -7 35 57 c47 75 173 201 291 291 52 39 98 72 +102 72 3 0 -21 -37 -54 -82 -57 -80 -179 -307 -179 -335 0 -11 63 -13 357 -11 +l358 3 43 65 c101 154 175 301 286 573 49 118 99 217 125 244 13 14 34 23 51 +23 l29 0 -44 -95 c-24 -52 -80 -185 -124 -295 -91 -226 -126 -298 -205 -424 +-31 -48 -56 -90 -56 -92 0 -2 12 -4 28 -3 21 0 40 15 83 67 64 77 292 362 454 +567 293 372 500 603 522 582 2 -2 -8 -30 -22 -62 -16 -36 -21 -56 -13 -52 7 2 +109 66 226 141 309 198 692 419 692 400 0 -4 -46 -55 -103 -113 -141 -145 +-212 -231 -282 -336 -32 -50 -86 -119 -120 -155 -135 -143 -215 -252 -568 +-778 l-177 -262 227 0 228 0 44 173 c63 246 135 484 188 617 90 228 237 486 +336 588 124 129 344 251 542 301 93 23 300 55 318 48 6 -2 -10 -8 -38 -11 +-162 -24 -323 -93 -468 -202 -100 -75 -184 -171 -271 -307 l-58 -92 5 55 6 55 +-19 -35 c-32 -58 -87 -238 -110 -357 -11 -62 -43 -195 -71 -295 -77 -281 -139 +-514 -139 -527 0 -7 19 -11 49 -11 l49 0 41 172 c94 392 166 634 222 738 62 +116 322 411 475 539 165 138 261 171 497 171 224 0 343 -37 469 -147 105 -92 +253 -264 325 -378 45 -71 128 -247 163 -346 29 -82 29 -83 30 -40 0 67 -28 +249 -51 320 -40 125 -106 228 -218 341 -213 214 -465 331 -761 353 -25 2 5 4 +65 5 115 2 268 -16 369 -43 180 -48 363 -201 488 -410 49 -81 75 -150 107 +-279 28 -116 45 -141 76 -116 14 12 16 25 12 69 -14 132 29 198 183 282 213 +116 351 236 458 399 117 177 172 353 179 575 4 117 1 157 -17 244 -36 177 -98 +309 -195 416 l-36 40 25 -50 c107 -222 116 -502 25 -763 -58 -165 -189 -386 +-318 -539 -55 -65 -214 -203 -234 -203 -4 0 17 30 46 67 245 305 357 634 357 +1048 0 294 -26 437 -220 1210 -159 633 -210 856 -290 1280 -119 631 -189 894 +-331 1256 -138 350 -263 561 -443 747 -349 360 -798 585 -1286 646 -131 16 +-362 26 -465 20z m-260 -115 c260 -32 473 -140 693 -354 214 -207 352 -421 +483 -753 76 -190 179 -562 166 -596 -4 -9 -22 -26 -42 -37 -39 -23 -46 -56 +-19 -84 9 -9 45 -30 80 -46 72 -34 157 -98 175 -130 10 -20 8 -19 -33 5 -23 +15 -89 49 -145 76 -55 27 -110 61 -122 76 -40 51 -22 112 43 149 17 10 17 14 +4 60 l-14 50 -62 -2 c-73 -2 -209 -28 -229 -44 -13 -12 -18 -75 -7 -103 4 -9 +18 -16 35 -16 56 0 77 -76 40 -148 -20 -39 -161 -205 -168 -198 -8 8 46 101 +91 160 26 32 49 70 52 83 8 30 -11 53 -45 53 -52 0 -61 17 -87 160 -116 635 +-341 1019 -761 1302 -160 108 -319 173 -480 197 -139 21 -333 11 -478 -25 -33 +-8 -69 -17 -80 -20 -52 -13 25 21 163 71 305 110 514 142 747 114z m2172 -785 +c78 -116 129 -209 198 -369 70 -160 132 -326 173 -466 58 -194 167 -668 160 +-691 -2 -6 -14 36 -27 93 -90 412 -235 862 -362 1122 -45 93 -169 300 -175 +294 -4 -4 31 -144 45 -180 18 -47 11 -45 -15 6 -13 26 -27 47 -30 47 -4 0 -3 +-35 0 -77 l7 -78 -21 25 -21 25 14 -30 c125 -258 209 -467 293 -728 20 -60 34 +-111 31 -113 -2 -2 -23 53 -47 123 -57 171 -131 354 -198 488 -63 127 -123 +237 -127 233 -3 -3 8 -79 20 -148 6 -35 6 -35 -19 11 -13 25 -31 51 -40 58 +-13 11 -14 8 -4 -26 5 -21 38 -110 73 -198 75 -191 160 -428 168 -470 8 -40 3 +-28 -79 180 -81 206 -166 378 -229 463 -25 35 -48 62 -50 60 -2 -2 28 -82 66 +-178 85 -213 193 -512 247 -680 65 -206 57 -209 -12 -5 -83 248 -189 510 -288 +714 l-79 164 -19 -31 c-11 -17 -31 -39 -45 -48 -25 -16 -28 -16 -103 19 -62 +28 -151 57 -175 57 -3 0 24 -59 60 -131 162 -329 304 -732 521 -1479 188 -645 +219 -762 266 -1010 51 -276 96 -673 96 -864 0 -91 -9 -42 -20 109 -31 436 +-113 845 -288 1440 -44 149 -105 360 -137 470 -121 430 -241 805 -325 1015 +-43 108 -128 281 -155 315 -15 19 -15 18 -10 -17 3 -21 7 -55 10 -75 l5 -38 +-26 50 c-56 108 -162 184 -238 172 -33 -5 -35 -4 -51 39 -9 25 -15 48 -12 51 +4 3 35 0 71 -7 36 -7 70 -9 76 -5 6 4 18 1 27 -7 15 -12 16 -8 11 43 -4 31 +-18 89 -32 130 -23 69 -24 72 -4 49 11 -14 43 -62 70 -107 41 -66 54 -81 67 +-74 10 6 42 1 91 -15 41 -13 85 -24 96 -24 29 0 69 33 84 69 11 28 9 34 -41 +108 -28 43 -66 96 -84 118 -53 67 -52 73 3 14 72 -78 132 -152 132 -162 0 -4 +5 -5 10 -2 6 3 10 15 10 26 0 20 21 25 83 21 26 -2 27 -1 16 20 -16 32 -115 +173 -162 233 -21 28 24 -15 102 -95 77 -80 140 -141 141 -136 0 6 -113 167 +-173 244 -5 6 -7 12 -4 12 9 0 181 -206 211 -254 54 -85 43 -9 -16 108 -43 87 +-27 79 21 -11 l42 -78 -6 60 c-10 90 -40 174 -85 241 -39 56 -36 54 58 -42 54 +-56 125 -141 159 -190z m-1972 31 c21 -27 42 -54 46 -59 4 -5 26 5 47 23 22 +17 45 31 51 31 19 0 12 44 -15 85 -22 34 -17 31 31 -20 61 -65 180 -235 222 +-319 14 -28 27 -53 29 -55 1 -2 17 7 34 19 20 14 35 19 41 13 12 -12 81 -204 +76 -210 -3 -2 -12 8 -21 22 -9 14 -23 25 -30 25 -7 0 -25 10 -40 21 -34 27 +-40 21 -27 -28 19 -68 11 -60 -33 33 -48 102 -115 210 -166 268 l-34 38 -13 +-62 -12 -62 40 -80 c41 -82 48 -116 11 -56 -12 18 -33 45 -48 58 -24 22 -26 +23 -20 5 40 -109 96 -281 93 -288 -2 -6 -11 12 -21 39 -10 27 -37 84 -59 126 +-36 68 -43 76 -56 64 -9 -7 -28 -16 -43 -20 -32 -7 -33 -12 -13 -86 8 -30 15 +-57 15 -60 0 -3 -18 27 -40 67 l-40 71 -54 4 c-51 3 -55 1 -58 -20 -2 -19 -15 +-4 -60 71 -60 98 -69 119 -42 96 14 -12 21 -7 55 31 21 25 39 48 39 51 0 4 +-29 51 -65 105 -64 97 -59 95 37 -14 l37 -41 33 17 c18 9 35 18 37 19 7 5 -31 +128 -50 161 -10 18 -19 35 -19 38 0 6 59 -62 105 -121z m-1890 17 c-160 -135 +-278 -263 -376 -409 -44 -65 -48 -73 -23 -54 21 17 36 21 60 17 30 -6 36 0 +159 139 70 80 130 145 132 145 3 0 1 -6 -5 -12 -5 -7 -47 -62 -92 -123 -87 +-116 -125 -181 -112 -189 21 -13 80 36 154 127 46 56 96 115 112 131 24 27 23 +24 -7 -19 -88 -127 -217 -319 -214 -321 1 -1 28 4 60 11 42 10 64 21 80 42 55 +70 64 72 21 5 -71 -113 -119 -221 -217 -492 -11 -31 -6 37 8 100 8 36 14 73 +15 83 0 29 -22 15 -48 -29 l-24 -39 6 34 c4 22 1 40 -8 50 -20 24 -26 58 -15 +94 27 100 -78 -124 -132 -280 -17 -48 -41 -128 -54 -177 -14 -49 -27 -91 -31 +-95 -18 -18 55 286 98 407 16 46 27 86 25 88 -2 3 -24 0 -47 -5 -89 -20 -91 +12 -10 169 33 63 53 106 44 95 -52 -67 -220 -426 -270 -575 -14 -44 -28 -79 +-30 -77 -7 7 64 275 90 339 22 53 23 62 10 75 -16 17 -11 62 14 128 35 90 80 +144 232 285 159 147 385 340 397 340 4 0 3 -4 -2 -8z m1572 -181 c81 -112 141 +-222 208 -378 30 -69 55 -129 55 -132 0 -3 -22 17 -50 44 l-50 49 0 -41 c0 +-23 9 -95 20 -160 30 -176 25 -180 -20 -22 -16 57 -41 133 -55 169 l-25 65 5 +-45 c3 -25 2 -76 -2 -115 l-8 -69 -53 108 c-29 60 -54 107 -56 105 -3 -4 9 +-151 19 -234 9 -74 -2 -51 -34 69 -16 59 -32 110 -35 113 -3 4 -14 -1 -24 -10 +-10 -10 -29 -17 -41 -17 -19 0 -22 -4 -18 -22 3 -13 8 -45 12 -73 l5 -50 -26 +68 c-15 39 -32 67 -40 67 -8 0 -41 13 -73 30 -53 26 -61 28 -72 14 -11 -14 +-14 -12 -27 12 -21 40 -52 154 -42 154 5 0 12 -4 15 -10 11 -17 34 -11 77 20 +22 17 50 30 60 30 20 0 20 9 -2 114 -12 58 4 30 30 -49 13 -38 25 -74 27 -78 +3 -5 23 8 45 30 39 37 40 39 34 90 -3 29 -20 90 -36 135 -16 45 -28 83 -27 85 +2 1 23 -41 47 -95 25 -53 57 -117 72 -141 l28 -45 0 73 c0 91 -21 175 -66 266 +l-34 70 47 -54 c27 -30 76 -93 110 -140z m-1123 112 c-111 -169 -306 -630 +-330 -782 -12 -78 -24 -52 -24 53 0 122 -4 136 -36 136 -29 0 -121 -81 -137 +-120 -7 -19 -11 -21 -14 -9 -3 11 -12 15 -27 12 -15 -3 -25 1 -29 10 -7 19 63 +165 133 278 62 98 210 309 217 309 3 0 -3 -12 -13 -27 -80 -122 -193 -329 +-220 -405 l-13 -37 47 35 c98 72 122 96 158 156 73 123 286 418 301 418 3 0 +-3 -12 -13 -27z m122 -160 c-8 -27 -26 -93 -39 -148 -43 -174 -43 -155 3 -155 +52 0 76 13 100 53 15 24 20 27 20 13 0 -38 10 -7 46 135 20 79 37 145 38 146 +1 1 2 -109 2 -245 0 -145 5 -274 12 -311 6 -36 10 -66 8 -68 -2 -2 -13 27 -26 +64 -29 89 -40 99 -41 37 l-1 -49 -12 48 c-8 35 -19 53 -38 65 -26 16 -30 15 +-83 -11 -30 -15 -62 -27 -70 -27 -9 0 -16 -17 -21 -47 l-8 -48 -11 50 c-13 60 +-27 65 -97 35 -57 -24 -92 -21 -109 12 -17 31 -6 127 20 181 29 60 60 71 99 +34 27 -24 83 -57 99 -57 5 0 14 21 21 48 28 106 96 314 100 303 2 -6 -3 -32 +-12 -58z m565 -368 c33 -158 36 -189 10 -126 -34 84 -46 91 -160 91 -56 0 +-103 4 -106 10 -13 22 -20 8 -18 -37 l3 -48 -36 75 c-41 86 -60 177 -50 244 7 +45 26 56 26 15 0 -15 5 -19 19 -14 11 3 59 1 107 -5 63 -8 90 -8 92 -1 2 6 12 +1 22 -11 18 -23 19 -22 14 72 l-4 95 26 -110 c14 -60 38 -173 55 -250z m-306 +-290 c58 -195 172 -450 275 -616 26 -43 46 -80 42 -83 -3 -3 -30 -6 -61 -6 +l-56 0 -33 61 c-81 147 -160 412 -192 644 -6 39 -12 84 -15 100 -8 47 9 5 40 +-100z m3338 -395 c4 -19 -2 -8 -13 25 -11 33 -19 65 -19 70 1 16 26 -57 32 +-95z m-1095 -65 c24 -10 41 -19 39 -22 -3 -2 -24 4 -48 14 -67 28 -170 24 +-241 -9 -32 -15 -58 -25 -58 -23 0 6 78 46 110 56 38 13 150 4 198 -16z +m-2811 -156 c38 -55 143 -164 196 -204 l38 -28 107 26 c59 14 122 29 140 32 +l33 7 -40 -54 c-42 -54 -76 -116 -69 -124 3 -2 52 26 109 62 57 36 117 73 134 +81 26 14 188 75 239 90 13 4 32 -9 70 -50 l51 -55 120 8 c66 5 177 6 248 3 +l127 -6 0 93 c0 51 3 90 7 86 4 -3 17 -45 29 -92 12 -47 24 -87 26 -88 1 -1 +58 -12 126 -23 68 -12 126 -24 130 -27 12 -12 14 -334 3 -464 -13 -158 -41 +-358 -81 -572 -16 -85 -52 -274 -79 -420 -106 -568 -131 -841 -95 -1049 l16 +-93 -79 -83 c-43 -45 -81 -83 -85 -84 -3 0 -21 25 -38 57 -106 188 -265 388 +-335 422 l-46 22 -197 -32 c-227 -37 -414 -49 -491 -30 -63 15 -120 55 -159 +112 -46 68 -230 402 -288 523 -107 222 -163 450 -150 611 3 46 11 119 18 161 +l11 78 12 -52 c12 -53 34 -88 113 -176 76 -86 86 -137 41 -215 -10 -18 -19 +-45 -19 -60 0 -30 29 -99 60 -142 11 -16 16 -31 11 -34 -5 -4 -7 -23 -4 -44 +12 -88 23 -109 76 -155 l52 -44 18 23 c10 13 24 24 32 24 8 0 16 4 19 8 3 5 +-18 7 -47 4 -48 -4 -56 -1 -85 26 -37 35 -42 72 -12 102 11 11 31 20 45 20 22 +0 24 -2 15 -26 -15 -37 0 -52 16 -16 20 48 56 66 133 65 36 -1 80 -7 96 -14 +17 -7 38 -11 49 -10 11 2 16 -2 11 -8 -3 -6 -10 -9 -15 -6 -8 5 -24 -25 -16 +-32 5 -6 86 35 108 55 22 19 7 42 -22 35 -40 -10 -91 9 -182 69 -51 33 -85 52 +-75 41 25 -28 22 -33 -20 -33 -21 0 -55 -4 -75 -9 -36 -8 -40 -7 -73 30 -20 +22 -33 44 -30 49 3 5 -8 16 -25 25 -33 17 -38 36 -16 55 23 18 39 77 32 114 +-4 21 -30 60 -72 107 -74 84 -114 154 -114 201 0 27 14 46 88 122 68 70 98 +110 128 170 35 72 39 88 39 155 0 84 -12 115 -72 189 -43 53 -57 48 -16 -5 32 +-41 73 -131 73 -159 0 -11 -16 0 -50 34 -32 33 -65 56 -92 64 -29 9 -44 20 +-55 42 -13 30 -14 30 -8 4 5 -25 2 -28 -36 -38 -32 -9 -44 -8 -57 2 -15 12 +-16 11 -2 -5 13 -17 12 -19 -10 -15 -18 2 -32 17 -52 58 -15 30 -27 62 -27 70 +-1 10 24 28 62 47 l63 31 72 -38 c40 -20 72 -35 72 -32 0 3 -20 29 -45 58 +l-46 53 28 14 c15 8 74 26 131 42 56 15 102 29 102 31 0 2 -16 29 -36 60 -43 +68 -77 142 -110 242 -32 100 -30 109 9 37 18 -33 47 -81 64 -106z m2848 -483 +c28 -13 70 -43 95 -67 l45 -44 -48 -8 c-39 -8 -55 -6 -94 13 -70 33 -138 37 +-221 11 -39 -11 -82 -21 -96 -21 -22 0 -26 4 -26 29 0 86 226 143 345 87z +m-3015 -216 c0 -53 2 -60 20 -60 23 0 26 -13 6 -30 -18 -14 -12 -34 26 -82 +l28 -37 -24 -16 c-36 -24 -78 -16 -94 17 -38 80 -39 211 -1 249 31 31 39 23 +39 -41z m-51 -296 c-13 -16 -12 -16 7 -6 12 6 24 9 27 5 4 -3 13 0 22 7 35 29 +11 -6 -36 -52 l-52 -51 5 53 c3 29 11 56 19 61 20 12 24 2 8 -17z m3061 -284 +c-66 -56 -117 -75 -210 -77 l-85 -1 87 8 c89 8 166 39 204 81 10 11 22 19 28 +19 6 0 -5 -14 -24 -30z m585 -285 c30 -221 20 -494 -25 -703 -19 -87 -20 -54 +-3 53 18 110 25 564 10 704 -12 116 0 79 18 -54z m805 -257 c194 -459 225 +-723 119 -1000 -55 -143 -189 -334 -279 -398 l-29 -21 33 73 c115 248 170 531 +170 863 0 196 -14 329 -55 503 -33 141 -26 138 41 -20z m-4100 84 c0 -17 -39 +-44 -55 -39 -18 8 -20 33 -2 40 20 9 57 8 57 -1z m2198 -119 c3 -109 20 -364 +27 -403 4 -23 -9 -34 -137 -117 -78 -51 -143 -91 -145 -89 -5 5 239 706 246 +706 3 0 7 -44 9 -97z m929 -90 c-7 -164 -26 -297 -57 -418 -24 -90 -95 -276 +-117 -302 -10 -13 -9 -7 3 22 106 262 145 436 160 718 4 70 9 127 12 127 3 0 +2 -66 -1 -147z m-857 -618 c6 -44 18 -106 25 -137 l13 -57 -59 -56 c-61 -59 +-146 -105 -192 -105 -60 0 -127 59 -147 128 -20 75 -23 72 163 200 94 66 175 +117 178 113 4 -3 12 -42 19 -86z m-2439 -622 c-43 -251 -109 -411 -216 -518 +-33 -34 -80 -71 -106 -83 -99 -48 -211 -46 -295 3 -41 24 -94 86 -94 110 0 6 +47 10 123 11 99 0 133 4 182 22 129 47 220 136 303 295 43 83 95 230 109 309 +l8 43 3 -37 c2 -21 -6 -91 -17 -155z m1702 94 c62 -140 218 -296 410 -408 48 +-28 87 -52 87 -54 0 -9 -281 -201 -357 -244 -115 -65 -396 -200 -423 -204 -17 +-2 -25 10 -44 65 -28 80 -56 131 -182 334 -52 84 -96 158 -99 166 -5 12 8 16 +64 21 119 10 147 28 333 217 92 94 171 170 175 170 4 0 21 -29 36 -63z m-483 +-1035 c0 -24 -33 -72 -173 -247 -192 -242 -184 -233 -138 -140 77 158 258 405 +297 405 8 0 14 -8 14 -18z m-430 -227 c0 -3 -29 -42 -65 -88 -43 -53 -94 -138 +-147 -241 -44 -88 -98 -188 -118 -222 -36 -57 -200 -287 -223 -309 -16 -17 0 +222 22 311 62 254 150 374 339 465 58 27 116 58 130 69 22 18 62 27 62 15z +M3340 5070 c-19 -4 -38 -6 -42 -4 -5 3 -8 1 -8 -4 0 -13 -76 -46 -88 -38 -6 3 +-8 -2 -6 -11 3 -10 -6 -30 -21 -45 -14 -15 -25 -37 -25 -50 1 -20 2 -21 11 -5 +10 18 34 23 43 9 7 -12 -15 -84 -32 -104 -21 -25 -5 -44 19 -22 18 16 19 16 +19 -17 1 -34 1 -34 20 15 22 55 43 82 82 102 42 22 48 17 32 -21 -7 -19 -14 +-53 -14 -75 0 -33 3 -40 20 -40 24 0 26 -16 4 -28 -15 -8 -15 -12 -1 -38 31 +-59 85 -94 147 -94 51 0 92 21 123 62 37 50 42 74 16 81 -26 7 -20 31 10 35 +19 3 21 8 15 39 -5 29 -4 34 7 28 84 -50 139 -89 139 -98 0 -39 -82 -126 -147 +-157 -102 -48 -265 -52 -375 -10 -32 12 -63 18 -69 14 -6 -3 -15 0 -20 7 -8 +12 -11 12 -19 -1 -8 -13 0 -16 46 -20 54 -5 77 -21 68 -45 -11 -29 203 -49 +280 -27 29 8 43 8 53 0 10 -8 13 -7 13 4 0 9 18 21 43 28 23 8 44 17 47 20 20 +24 81 60 91 54 7 -4 10 -2 7 6 -4 14 48 74 72 83 13 5 13 6 -1 6 -8 1 -40 -23 +-70 -51 -30 -29 -70 -61 -89 -71 l-35 -18 30 22 c17 12 64 57 106 100 48 51 +92 85 122 98 26 11 47 22 47 24 0 1 -16 1 -35 -1 -34 -4 -48 10 -22 22 6 3 -3 +3 -21 0 -26 -5 -43 0 -74 21 -39 26 -40 28 -23 41 17 13 16 14 -16 14 -41 0 +-94 31 -79 46 7 7 1 9 -18 6 -16 -2 -35 2 -43 10 -8 7 -21 14 -29 16 -8 1 -31 +9 -50 17 -47 19 -175 35 -227 28 -28 -4 -43 -2 -43 5 0 29 176 27 259 -3 24 +-8 46 -13 49 -11 2 3 -25 14 -61 25 -76 23 -188 33 -237 21z m-49 -60 c-25 +-22 -41 -26 -41 -10 0 11 30 29 50 29 10 0 8 -6 -9 -19z m-54 -51 c-11 -18 +-47 -27 -47 -12 0 15 33 41 45 36 11 -4 12 -9 2 -24z m349 -288 c-3 -5 -21 +-16 -39 -24 -26 -13 -42 -14 -70 -6 -48 13 -47 18 1 12 25 -3 49 0 68 10 33 +18 48 21 40 8z m-98 -158 c-15 -2 -42 -2 -60 0 -18 2 -6 4 27 4 33 0 48 -2 33 +-4z M2465 4851 c35 -9 72 -27 100 -50 20 -16 20 -16 0 8 -27 31 -68 51 -102 +50 l-28 -1 30 -7z M2620 4091 c0 -5 20 -23 45 -40 53 -35 63 -18 14 23 -31 26 +-59 34 -59 17z M4150 2019 c-14 -11 -42 -29 -63 -40 -34 -18 -36 -22 -26 -42 +16 -30 15 -30 38 -15 17 11 23 10 37 -4 18 -18 105 -161 100 -165 -1 -2 -62 +29 -135 68 l-133 72 -41 -23 c-57 -31 -60 -35 -52 -60 5 -17 12 -20 35 -15 27 +6 30 2 89 -100 33 -59 61 -111 61 -116 0 -5 -6 -18 -12 -29 -10 -16 -10 -23 3 +-35 14 -14 21 -12 73 19 32 19 63 37 70 39 8 3 7 11 -1 30 -11 24 -15 26 -38 +15 -16 -7 -29 -8 -35 -2 -15 16 -92 156 -87 160 2 2 65 -28 140 -66 135 -70 +136 -71 167 -55 l30 16 -28 42 c-16 23 -57 90 -91 150 -51 86 -61 110 -51 122 +13 16 4 55 -13 55 -7 0 -23 -9 -37 -21z"/></g></g></svg> \ No newline at end of file diff --git a/gui/public/provider-icons/minimax.svg b/gui/public/provider-icons/minimax.svg new file mode 100644 index 0000000000..31b734a6cd --- /dev/null +++ b/gui/public/provider-icons/minimax.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 490.16 411.7"><defs><style>.cls-1{fill:url(#minimax-wave);}</style><linearGradient id="minimax-wave" y1="205.85" x2="490.16" y2="205.85" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#e4177f"/><stop offset="0.5" stop-color="#e73562"/><stop offset="1" stop-color="#e94e4a"/></linearGradient></defs><g><g><path class="cls-1" d="M233.45,40.81a17.55,17.55,0,1,0-35.1,0V331.56a40.82,40.82,0,0,1-81.63,0V145a17.55,17.55,0,1,0-35.09,0v79.06a40.82,40.82,0,0,1-81.63,0V195.42a11.63,11.63,0,0,1,23.26,0v28.66a17.55,17.55,0,0,0,35.1,0V145A40.82,40.82,0,0,1,140,145V331.56a17.55,17.55,0,0,0,35.1,0V217.5h0V40.81a40.81,40.81,0,1,1,81.62,0V281.56a11.63,11.63,0,1,1-23.26,0Zm215.9,63.4A40.86,40.86,0,0,0,408.53,145V300.85a17.55,17.55,0,0,1-35.09,0v-260a40.82,40.82,0,0,0-81.63,0V370.89a17.55,17.55,0,0,1-35.1,0V330a11.63,11.63,0,1,0-23.26,0v40.86a40.81,40.81,0,0,0,81.62,0V40.81a17.55,17.55,0,0,1,35.1,0v260a40.82,40.82,0,0,0,81.63,0V145a17.55,17.55,0,1,1,35.1,0V281.56a11.63,11.63,0,0,0,23.26,0V145A40.85,40.85,0,0,0,449.35,104.21Z"/></g></g></svg> diff --git a/gui/src/components/apikeys-workspace/client-config-clients.ts b/gui/src/components/apikeys-workspace/client-config-clients.ts index 840b1e31ad..c7c42d3e56 100644 --- a/gui/src/components/apikeys-workspace/client-config-clients.ts +++ b/gui/src/components/apikeys-workspace/client-config-clients.ts @@ -47,17 +47,26 @@ export const CLIENT_LABEL_KEYS = { * so its symbol comes out of the shipping application, where the vendor names the * module `official-brand-symbol`. Still first-party, just not fetched. * - * Two clients are absent on purpose. `gajae` publishes only raster marks and - * `hermes` upstream ships a text-glyph placeholder with no path data. Both render - * a monogram, which is what this map's rule prescribes; the README records why. + * Three are traced rather than fetched, because their vendors publish no usable + * vector: `hermes` from the Hermes desktop application icon, `gajae` from the + * Gajae mascot PNG. A trace follows the source pixels -- it is not a redraw -- + * and the conversion parameters are in the README beside the source URL, so the + * result can be reproduced. What is still refused: squeezing a horizontal + * wordmark into this square slot, and a full-frame silhouette plate that renders + * as a filled box at 20px. Both candidates existed and both were rejected; + * `devlog/_plan/260831_aside_client_and_integrations_ux/005_remaining_marks_provenance.md` + * names them. */ export const CLIENT_MARKS: Partial<Record<ExportClientId, string>> = { opencode: "/provider-icons/opencode.svg", pi: "/provider-icons/pi.svg", omp: "/provider-icons/oh-my-pi.svg", + hermes: "/provider-icons/hermes-agent.svg", openclaw: "/provider-icons/openclaw.svg", kimi: "/provider-icons/kimi-color.svg", + gajae: "/provider-icons/gajae-code.svg", dsh: "/provider-icons/deepseek-harness.svg", + mcode: "/provider-icons/minimax.svg", zcode: "/provider-icons/zcode.svg", prime: "/provider-icons/prime-agent.svg", aside: "/provider-icons/aside.svg", @@ -72,16 +81,20 @@ export const CLIENT_MARKS: Partial<Record<ExportClientId, string>> = { * ships white-on-transparent and vanishes in light mode, while `opencode` * (#211E1E) and `kimi` (#1A1A1A) vanish in dark. That is not hypothetical -- a * rendered check of every mark showed `prime` blank on white and `opencode` and - * `kimi` blank on #0d1117. + * `kimi` blank on #0d1117. `hermes` joined them the same way: its traced + * artwork is one ink, and a 20px render on #0d1117 showed nothing at all. * * A multi-color mark must never be listed here: masking discards its colors and - * would flatten a brand palette into one ink. + * would flatten a brand palette into one ink. That is why `gajae` (seven traced + * layers) and `mcode` (a three-stop gradient) stay images despite arriving in + * the same pass as `hermes`. */ export const MONOCHROME_CLIENT_MARKS: ReadonlySet<ExportClientId> = new Set<ExportClientId>([ "opencode", "kimi", "prime", "aside", + "hermes", ]); /** The `/api/client-config` 200 envelope, read off the route rather than a design doc. */ diff --git a/gui/tests/client-marks-assets.test.ts b/gui/tests/client-marks-assets.test.ts index 36da09dc6d..4c8600366a 100644 --- a/gui/tests/client-marks-assets.test.ts +++ b/gui/tests/client-marks-assets.test.ts @@ -1,7 +1,7 @@ import { expect, test } from "bun:test"; import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; -import { CLIENT_MARKS, MONOCHROME_CLIENT_MARKS } from "../src/components/apikeys-workspace/client-config-clients"; +import { CLIENT_MARKS, CLIENTS, MONOCHROME_CLIENT_MARKS } from "../src/components/apikeys-workspace/client-config-clients"; const PUBLIC_DIR = join(import.meta.dir, "..", "public"); @@ -106,3 +106,34 @@ test("every mark's provenance is recorded in the README", () => { .filter(file => !readme.includes(file)); expect(undocumented).toEqual([]); }); + +/* + * Every export client now has a mark, and that is a property worth pinning + * rather than a coincidence of the current map. The monogram branch in + * ClientConfigRow stays -- it is the correct fallback and a future client will + * arrive without an asset -- but a client SILENTLY losing its mark, because a + * key was renamed or an entry dropped in a merge, looks identical to a client + * that never had one. This is the difference. + */ +test("every export client has a mark", () => { + const monogram = CLIENTS.filter(clientId => CLIENT_MARKS[clientId] === undefined); + expect(monogram).toEqual([]); +}); + +/* + * Two of the newest marks are traced from raster sources, which is a different + * provenance claim from "fetched" and the one a reader is most likely to doubt. + * The README has to carry the reproduction detail: the source file, and the + * tracer parameters. Named files rather than a general rule because only these + * two are traced -- a fetched mark has nothing to reproduce. + */ +test("a traced mark records the source it was traced from", () => { + const readme = readFileSync(join(PUBLIC_DIR, "provider-icons", "README.md"), "utf8"); + for (const [file, source] of [ + ["hermes-agent.svg", "apps/desktop/assets/icon.png"], + ["gajae-code.svg", "assets/character.png"], + ] as const) { + expect(readme, `${file} should name its raster source`).toContain(source); + } + expect(readme, "a traced mark should record its tracer invocation").toContain("potrace"); +}); From d86ec3e030028f6b1be5dec9d64db5d1d1cef3d1 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Mon, 31 Aug 2026 23:37:22 +0900 Subject: [PATCH 05/42] feat(gui): put the client's logo on every Integrations surface (#3083) The mark was only ever drawn in one place, the API tab's connect rows. The Integrations page -- 17 tabs, 16 cards, a header per client -- carried none, so the surface a user actually goes to in order to connect a client was the surface that could not show them which client they were looking at. ClientMark is now the one place that decides how a mark is painted. The rule it owns is the one that was already got wrong once: a single-ink logo drawn as an <img> disappears against one of the two themes, so it has to be drawn as a mask tinted with the surrounding text color instead. That ternary was inline in ClientConfigRow, which was fine at one call site and would have become four copies of a branch whose wrong side renders nothing at all. Marks now appear on the overview cards, on every client tab, and in each file-client page header. The tab strip is where it pays off most: 17 tabs on one row, and a logo is found faster than the tenth label is read. Four rows have no export client behind them and get marks from the provider icons already committed -- Codex takes OpenAI's, Claude and Claude Desktop share one because they are one brand on two surfaces, Grok takes its own. Two tabs stay bare on purpose: overview is the page itself and keys is a credential surface, not an integration, so a mark there would imply a client that does not exist. MASKED_MARKS is keyed by asset path where MONOCHROME_CLIENT_MARKS is keyed by client id, and it is derived from it rather than restated. kimi-color.svg is reachable both as a provider icon and as a client mark; a path-keyed set cannot mask it on one surface and leave it unmasked on the other, and two hand-maintained lists of the same fact would drift. Two assets that look like they belong in that set do not. openai.svg is a single fill, but the fill is #10A37F -- OpenAI's green, the brand itself -- so masking it would repaint a trademark in the theme's text color, exactly the reason dsh stays an image despite being single-ink. grok.svg is #000000, a genuine neutral and a real candidate, but it is xAI's published asset with a literal fill and rewriting that file to currentColor is a change to someone else's mark rather than a rendering decision. Both stay images. The Codex tab now reads "Codex" in all nine locales instead of "Codex CLI". The mark carries that identity, and the row covers the app and the SDK too, so the suffix was both redundant and slightly wrong. integrations.codex.title and .body keep theirs -- they describe routing, where "CLI" is accurate. Marks are aria-hidden everywhere and their images carry an empty alt. Each sits beside a label that already names the client, and a mark that joined the accessible name would make a screen reader say "Claude Claude". Nine guards, each driven red first. Three render-level ones -- a card, a tab and a page header must each carry a mark -- because the map being right while the component is never called is exactly the failure a data-only test cannot see, and it is the failure that would have shipped: the marks were correct in data long before any surface drew them. Falsifying the mask rule by adding openai.svg failed three tests at once. Rendered and inspected at 1440 and 390 wide: the strip wraps to four rows on mobile with every mark intact, and the card head keeps the mark with the title rather than letting the state badge land between them. --- gui/src/components/ClientMark.tsx | 56 ++++++++++ .../apikeys-workspace/ClientConfigRow.tsx | 18 ++-- gui/src/components/integration-marks.ts | 83 ++++++++++++++ gui/src/i18n/de.ts | 2 +- gui/src/i18n/en.ts | 2 +- gui/src/i18n/fr.ts | 2 +- gui/src/i18n/ja.ts | 2 +- gui/src/i18n/ko.ts | 2 +- gui/src/i18n/ru.ts | 2 +- gui/src/i18n/tr.ts | 2 +- gui/src/i18n/zh-TW.ts | 2 +- gui/src/i18n/zh.ts | 2 +- gui/src/pages/Integrations.tsx | 17 +++ .../integrations/FileIntegrationPage.tsx | 3 + .../integrations/IntegrationsOverview.tsx | 8 ++ gui/src/styles-integrations.css | 50 +++++++++ gui/tests/client-config-panel.test.tsx | 16 +-- gui/tests/integration-marks.test.ts | 101 ++++++++++++++++++ gui/tests/integrations-surfaces.test.tsx | 80 ++++++++++++++ 19 files changed, 426 insertions(+), 24 deletions(-) create mode 100644 gui/src/components/ClientMark.tsx create mode 100644 gui/src/components/integration-marks.ts create mode 100644 gui/tests/integration-marks.test.ts diff --git a/gui/src/components/ClientMark.tsx b/gui/src/components/ClientMark.tsx new file mode 100644 index 0000000000..22339f1687 --- /dev/null +++ b/gui/src/components/ClientMark.tsx @@ -0,0 +1,56 @@ +/** + * One brand mark, one decision about how to paint it. + * + * The rule this centralizes is the one that was already got wrong once: a + * single-ink logo drawn as an <img> is invisible against one of the two themes, + * so it has to be drawn as a CSS mask tinted with the surrounding text color + * instead. That decision was inline in ClientConfigRow, which was fine while + * exactly one surface drew marks. Four surfaces do now, and four copies of a + * ternary whose wrong branch renders nothing at all is how the bug comes back. + * + * Marks are decorative here. Every call site puts one beside a text label that + * already names the client, so the mark contributes nothing to the accessible + * name and must not try: an <img alt={label}> next to a visible label makes a + * screen reader say it twice. + */ +import { MASKED_MARKS } from "./integration-marks"; + +export default function ClientMark({ + src, + label, + size = 20, + className, +}: { + /** Asset path, or null for a client with no committed mark. */ + src: string | null; + /** Only the first character is drawn, as the monogram fallback. */ + label: string; + size?: number; + className?: string; +}) { + const classes = className ? `client-mark ${className}` : "client-mark"; + // Concatenated rather than interpolated: the i18n lint reads a template + // literal with a bare unit as user-facing copy, and a CSS length is not that. + const style = { "--client-mark-size": String(size) + "px" } as React.CSSProperties; + if (!src) { + return ( + <span className={`${classes} client-mark--monogram`} style={style} aria-hidden="true"> + {label.slice(0, 1)} + </span> + ); + } + if (MASKED_MARKS.has(src)) { + return ( + <span + className={`${classes} client-mark--mask`} + style={{ ...style, maskImage: `url(${src})`, WebkitMaskImage: `url(${src})` }} + aria-hidden="true" + /> + ); + } + return ( + <span className={`${classes} client-mark--img`} style={style} aria-hidden="true"> + <img src={src} alt="" width={size} height={size} /> + </span> + ); +} diff --git a/gui/src/components/apikeys-workspace/ClientConfigRow.tsx b/gui/src/components/apikeys-workspace/ClientConfigRow.tsx index cf2d388361..4121d97f9e 100644 --- a/gui/src/components/apikeys-workspace/ClientConfigRow.tsx +++ b/gui/src/components/apikeys-workspace/ClientConfigRow.tsx @@ -7,7 +7,8 @@ */ import { useCallback, useEffect, useState } from "react"; import { useT } from "../../i18n/shared"; -import { CLIENT_LABEL_KEYS, CLIENT_MARKS, MONOCHROME_CLIENT_MARKS, type ClientConfigEnvelope, type ExportClientId } from "./client-config-clients"; +import ClientMark from "../ClientMark"; +import { CLIENT_LABEL_KEYS, CLIENT_MARKS, type ClientConfigEnvelope, type ExportClientId } from "./client-config-clients"; export default function ClientConfigRow({ client, @@ -77,7 +78,6 @@ export default function ClientConfigRow({ const label = t(CLIENT_LABEL_KEYS[client]); const mark = CLIENT_MARKS[client]; - const monochrome = MONOCHROME_CLIENT_MARKS.has(client); // The server renders the client's own format; re-serializing as JSON here // would hand a TOML or YAML client bytes its parser cannot read. const json = data?.text ?? ""; @@ -88,12 +88,14 @@ export default function ClientConfigRow({ return ( <li className="awi-clientconfig-row"> - <span className="awi-clientconfig-mark" aria-hidden="true"> - {mark - ? monochrome - ? <span className="awi-clientconfig-mark-mask" style={{ maskImage: `url(${mark})`, WebkitMaskImage: `url(${mark})` }} /> - : <img src={mark} alt="" width={20} height={20} /> - : <span className="awi-clientconfig-monogram">{label.slice(0, 1)}</span>} + {/* + The img-versus-mask decision moved to ClientMark, which four surfaces now + share. It was inline here while this was the only surface drawing marks; + four copies of a ternary whose wrong branch renders nothing is how the + invisible-logo bug would come back. + */} + <span className="awi-clientconfig-mark"> + <ClientMark src={mark ?? null} label={label} size={20} /> </span> <span className="awi-clientconfig-identity"> diff --git a/gui/src/components/integration-marks.ts b/gui/src/components/integration-marks.ts new file mode 100644 index 0000000000..ff34c0faf7 --- /dev/null +++ b/gui/src/components/integration-marks.ts @@ -0,0 +1,83 @@ +/** + * The mark for every row the Integrations page can show, and the set of assets + * that must be drawn as a themed mask rather than an image. + * + * Why this is not just `CLIENT_MARKS`: the Integrations page reaches four rows + * that are not export clients at all -- Codex CLI routing, Claude Code, Claude + * Desktop and the Grok fence. Those ids live in a different namespace that + * happens to overlap on the string "claude", so one map keyed by + * `OverviewClientId` is the only shape that can serve the page. + * + * `MASKED_MARKS` is keyed by ASSET PATH, deliberately, where + * `MONOCHROME_CLIENT_MARKS` is keyed by client id. `kimi-color.svg` is reachable + * both as a provider icon and as a client mark; a path-keyed set cannot mask it + * on one surface and leave it unmasked on another, which is exactly the + * inconsistency a second id-keyed set would invite. + */ +import { CLIENT_MARKS, MONOCHROME_CLIENT_MARKS, type ExportClientId } from "./apikeys-workspace/client-config-clients"; +import type { OverviewClientId } from "../pages/integrations/overview-clients"; + +/** + * The four rows with no export client behind them. + * + * Codex takes the OpenAI mark because Codex CLI ships no mark of its own and + * OpenAI publishes it. Claude Desktop shares Claude's: they are one brand and + * two surfaces, and inventing a distinct mark for the desktop app would imply a + * distinction that does not exist. + */ +const NATIVE_MARKS: Record<Exclude<OverviewClientId, ExportClientId>, string> = { + codex: "/provider-icons/openai.svg", + claude: "/provider-icons/claude-color.svg", + claudeDesktop: "/provider-icons/claude-color.svg", + grok: "/provider-icons/grok.svg", +}; + +/** + * Every Integrations row to its mark. Exhaustive over `OverviewClientId`, so a + * client added to the page without an asset decision is a compile error rather + * than a silent monogram. + * + * A value may still be null: that is the honest answer for a client whose + * vendor publishes nothing usable, and `ClientMark` renders a monogram for it. + * Today none are, which `integration-marks.test.ts` pins. + */ +export const INTEGRATION_MARKS: Record<OverviewClientId, string | null> = { + ...NATIVE_MARKS, + opencode: CLIENT_MARKS.opencode ?? null, + pi: CLIENT_MARKS.pi ?? null, + omp: CLIENT_MARKS.omp ?? null, + hermes: CLIENT_MARKS.hermes ?? null, + openclaw: CLIENT_MARKS.openclaw ?? null, + kimi: CLIENT_MARKS.kimi ?? null, + gajae: CLIENT_MARKS.gajae ?? null, + dsh: CLIENT_MARKS.dsh ?? null, + mcode: CLIENT_MARKS.mcode ?? null, + zcode: CLIENT_MARKS.zcode ?? null, + prime: CLIENT_MARKS.prime ?? null, + aside: CLIENT_MARKS.aside ?? null, +}; + +/** + * Assets whose artwork is one neutral ink, so the ink has to come from the theme. + * + * Derived from `MONOCHROME_CLIENT_MARKS` rather than restated, because two + * hand-maintained lists of the same fact drift. Nothing is added on top of it, + * and the two candidates that look like they belong here do not: + * + * - `openai.svg` is a single fill, but that fill is #10A37F -- OpenAI's brand + * green. Masking it repaints a trademark in the theme's text color, which is + * the same reason `dsh` stays an image despite being single-ink. + * - `grok.svg` is #000000, a genuine neutral and a real masking candidate. It + * is xAI's published asset with a literal fill, and rewriting that file to + * `currentColor` is a change to someone else's mark rather than a rendering + * choice, so it stays an image here. + */ +export const MASKED_MARKS: ReadonlySet<string> = new Set( + [...MONOCHROME_CLIENT_MARKS].map(clientId => CLIENT_MARKS[clientId]).filter((src): src is string => src !== undefined), +); + +/** The mark for a row, or null when it has none. */ +export function markFor(clientId: OverviewClientId): string | null { + return INTEGRATION_MARKS[clientId]; +} + diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts index 0ef8e9f77a..df074ac7e4 100644 --- a/gui/src/i18n/de.ts +++ b/gui/src/i18n/de.ts @@ -1002,7 +1002,7 @@ export const de: Record<TKey, string> = { "integrations.tabsLabel": "Integrationsbereiche", "integrations.tab.overview": "Übersicht", "integrations.tab.keys": "API-Schlüssel", - "integrations.tab.codex": "Codex CLI", + "integrations.tab.codex": "Codex", "integrations.tab.claude": "Claude", "integrations.tab.grok": "Grok Build", "integrations.tab.opencode": "OpenCode", diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index 0b182b3a98..0b3ee0ffab 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -1497,7 +1497,7 @@ export const en = { "integrations.tabsLabel": "Integration surfaces", "integrations.tab.overview": "Overview", "integrations.tab.keys": "API Keys", - "integrations.tab.codex": "Codex CLI", + "integrations.tab.codex": "Codex", "integrations.tab.claude": "Claude", "integrations.tab.grok": "Grok Build", "integrations.tab.opencode": "OpenCode", diff --git a/gui/src/i18n/fr.ts b/gui/src/i18n/fr.ts index bc827ae726..e42f361f97 100644 --- a/gui/src/i18n/fr.ts +++ b/gui/src/i18n/fr.ts @@ -1470,7 +1470,7 @@ export const fr: Record<TKey, string> = { "integrations.tabsLabel": "Surfaces d’intégration", "integrations.tab.overview": "Vue d’ensemble", "integrations.tab.keys": "Clés API", - "integrations.tab.codex": "Codex CLI", + "integrations.tab.codex": "Codex", "integrations.tab.claude": "Claude", "integrations.tab.grok": "Grok Build", "integrations.tab.opencode": "OpenCode", diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index ffa6d41820..6f82b308c3 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -1430,7 +1430,7 @@ export const ja: Record<TKey, string> = { "integrations.tabsLabel": "連携画面", "integrations.tab.overview": "概要", "integrations.tab.keys": "API キー", - "integrations.tab.codex": "Codex CLI", + "integrations.tab.codex": "Codex", "integrations.tab.claude": "Claude", "integrations.tab.grok": "Grok Build", "integrations.tab.opencode": "OpenCode", diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index 2aa8b8da73..9dc1843263 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -1026,7 +1026,7 @@ export const ko: Record<TKey, string> = { "integrations.tabsLabel": "연동 화면", "integrations.tab.overview": "개요", "integrations.tab.keys": "API 키", - "integrations.tab.codex": "Codex CLI", + "integrations.tab.codex": "Codex", "integrations.tab.claude": "Claude", "integrations.tab.grok": "Grok Build", "integrations.tab.opencode": "OpenCode", diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts index 97ea76213b..f4cec3e01f 100644 --- a/gui/src/i18n/ru.ts +++ b/gui/src/i18n/ru.ts @@ -1481,7 +1481,7 @@ export const ru: Record<TKey, string> = { "integrations.tabsLabel": "Разделы интеграций", "integrations.tab.overview": "Обзор", "integrations.tab.keys": "Ключи API", - "integrations.tab.codex": "Codex CLI", + "integrations.tab.codex": "Codex", "integrations.tab.claude": "Claude", "integrations.tab.grok": "Grok Build", "integrations.tab.opencode": "OpenCode", diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index a5132b4833..9847fc9cdb 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -1488,7 +1488,7 @@ export const tr: Record<TKey, string> = { "integrations.tabsLabel": "Entegrasyon yüzeyleri", "integrations.tab.overview": "Genel Bakış", "integrations.tab.keys": "API Anahtarları", - "integrations.tab.codex": "Codex CLI", + "integrations.tab.codex": "Codex", "integrations.tab.claude": "Claude", "integrations.tab.grok": "Grok Build", "integrations.tab.opencode": "OpenCode", diff --git a/gui/src/i18n/zh-TW.ts b/gui/src/i18n/zh-TW.ts index 8c320394bf..4eda29d0fc 100644 --- a/gui/src/i18n/zh-TW.ts +++ b/gui/src/i18n/zh-TW.ts @@ -2035,7 +2035,7 @@ export const zhTW: Record<TKey, string> = { "integrations.tabsLabel": "整合表面", "integrations.tab.overview": "總覽", "integrations.tab.keys": "API 金鑰", - "integrations.tab.codex": "Codex CLI", + "integrations.tab.codex": "Codex", "integrations.tab.claude": "Claude", "integrations.tab.grok": "Grok Build", "integrations.tab.opencode": "OpenCode", diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index 2b57648243..a52fa27747 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -1019,7 +1019,7 @@ export const zh: Record<TKey, string> = { "integrations.tabsLabel": "集成页面", "integrations.tab.overview": "概览", "integrations.tab.keys": "API 密钥", - "integrations.tab.codex": "Codex CLI", + "integrations.tab.codex": "Codex", "integrations.tab.claude": "Claude", "integrations.tab.grok": "Grok Build", "integrations.tab.opencode": "OpenCode", diff --git a/gui/src/pages/Integrations.tsx b/gui/src/pages/Integrations.tsx index f363173e18..63da22f558 100644 --- a/gui/src/pages/Integrations.tsx +++ b/gui/src/pages/Integrations.tsx @@ -1,6 +1,8 @@ import { useEffect, useRef, useState, type KeyboardEvent } from "react"; import { navigateHash, normalizeHashPath } from "../hash-routing"; import { useT } from "../i18n/shared"; +import ClientMark from "../components/ClientMark"; +import { INTEGRATION_MARKS } from "../components/integration-marks"; import ApiKeys from "./ApiKeys"; import Claude from "./Claude"; import Grok from "./Grok"; @@ -25,6 +27,18 @@ function panelDomId(tab: IntegrationTab): string { return `integrations-panel-${tab}`; } +/* + * The strip carries 17 tabs on one row, which is precisely where a mark earns + * its place: the eye finds a logo faster than it reads the tenth label. Two + * tabs have no client behind them -- `overview` is the page itself and `keys` + * is a credential surface, not an integration -- so they stay text-only rather + * than borrowing a mark that would imply a client. + */ +function tabMark(tab: IntegrationTab): string | null { + if (tab === "overview" || tab === "keys") return null; + return INTEGRATION_MARKS[tab] ?? null; +} + export default function Integrations({ apiBase }: { apiBase: string }) { const t = useT(); const [tab, setTab] = useState<IntegrationTab>(readIntegrationTab); @@ -109,6 +123,9 @@ export default function Integrations({ apiBase }: { apiBase: string }) { onClick={() => selectTab(definition.id, true)} onKeyDown={handleTabKeyDown} > + {tabMark(definition.id) && ( + <ClientMark src={tabMark(definition.id)} label={t(definition.labelKey)} size={14} /> + )} {t(definition.labelKey)} </button> ))} diff --git a/gui/src/pages/integrations/FileIntegrationPage.tsx b/gui/src/pages/integrations/FileIntegrationPage.tsx index cf441a912c..7b7397c825 100644 --- a/gui/src/pages/integrations/FileIntegrationPage.tsx +++ b/gui/src/pages/integrations/FileIntegrationPage.tsx @@ -3,6 +3,8 @@ import { useDataSurface } from "../../data-surface"; import { DataSurfaceSkeleton } from "../../components/data-surface"; import { useT, type TKey } from "../../i18n/shared"; import { Notice, Switch } from "../../ui"; +import ClientMark from "../../components/ClientMark"; +import { markFor } from "../../components/integration-marks"; import IntegrationStateBadge from "./IntegrationStateBadge"; import RestoreDialog from "./RestoreDialog"; import { RollbackHistory } from "./RollbackHistory"; @@ -143,6 +145,7 @@ export default function FileIntegrationPage({ return ( <section className="integration-client-page"> <div className="integration-client-head"> + <ClientMark src={markFor(client)} label={t(TAB_LABEL_KEY[client])} size={24} /> <h3>{t(TAB_LABEL_KEY[client])}</h3> <IntegrationStateBadge state={status.state} diff --git a/gui/src/pages/integrations/IntegrationsOverview.tsx b/gui/src/pages/integrations/IntegrationsOverview.tsx index 13661a23ee..6754e04216 100644 --- a/gui/src/pages/integrations/IntegrationsOverview.tsx +++ b/gui/src/pages/integrations/IntegrationsOverview.tsx @@ -4,6 +4,8 @@ import { DataSurfaceSkeleton } from "../../components/data-surface"; import { navigateHash } from "../../hash-routing"; import { useT } from "../../i18n/shared"; import { Notice, Switch } from "../../ui"; +import ClientMark from "../../components/ClientMark"; +import { markFor } from "../../components/integration-marks"; import IntegrationStateBadge from "./IntegrationStateBadge"; import ConsequenceDialog, { type ConsequenceCopy } from "./ConsequenceDialog"; import RestoreDialog from "./RestoreDialog"; @@ -98,6 +100,12 @@ function OverviewCard({ return ( <li className="integration-card" data-client={row.id}> <div className="integration-card-head"> + {/* + Before the title, not inside it: the title IS the card's one control + and its accessible name, so a mark inside the button would be read as + part of the client name. The mark is decorative and aria-hidden. + */} + <ClientMark src={markFor(row.id)} label={t(row.labelKey)} size={20} /> <h4> <button type="button" className="integration-card-link" onClick={onOpen}> {t(row.labelKey)} diff --git a/gui/src/styles-integrations.css b/gui/src/styles-integrations.css index e75ad10e66..f1b59d9751 100644 --- a/gui/src/styles-integrations.css +++ b/gui/src/styles-integrations.css @@ -47,6 +47,56 @@ .integration-client-head { display: flex; align-items: center; gap: 10px; } .integration-client-head h3 { margin: 0; } + +/* One brand mark, four surfaces. Sized from a custom property so the same three + rules serve the 14px tab strip, the 20px overview card and the 24px client + page header without a variant class per size. */ +.client-mark { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: var(--client-mark-size, 20px); + height: var(--client-mark-size, 20px); +} + +.client-mark--img img { + width: 100%; + height: 100%; + border-radius: 2px; +} + +/* A single-ink mark is painted with the surrounding text color instead of its + own, so it follows the theme. Without this a white-on-transparent logo is + invisible in light mode and a near-black one invisible in dark -- which is + not hypothetical, it shipped that way for prime, opencode and kimi. */ +.client-mark--mask { + background: var(--text); + mask-size: contain; + mask-repeat: no-repeat; + mask-position: center; + -webkit-mask-size: contain; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; +} + +/* Placeholder for a client whose vendor publishes no usable asset. Honest about + being one: muted, and the slot a real mark drops into later. */ +.client-mark--monogram { + font-size: var(--text-label); + font-weight: var(--weight-semibold); + color: var(--muted); + line-height: 1; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background: var(--raised); +} + +/* The card head is space-between, so without this the badge would land between + the mark and the title. The title takes the free space instead. */ +.integration-card-head h4 { flex: 1 1 auto; min-width: 0; } + +.page-tab > .client-mark { margin-right: 6px; vertical-align: -2px; } .integration-client-head .switch { margin-left: auto; } /* A config path is the thing a user copies when a refusal tells them to diff --git a/gui/tests/client-config-panel.test.tsx b/gui/tests/client-config-panel.test.tsx index 6915b6c680..8acc44e9ee 100644 --- a/gui/tests/client-config-panel.test.tsx +++ b/gui/tests/client-config-panel.test.tsx @@ -274,10 +274,12 @@ test("dialog closes on Escape and returns focus to its trigger", async () => { }); test("each client row shows its own brand mark, never a borrowed one", async () => { - // Nine clients ship a real first-party asset; gajae and hermes have none that - // qualifies and fall back to a monogram tile. The rule this guards is that no - // client ever borrows another product's logo — not that every client has an - // asset. Uniqueness is the teeth: a borrowed logo would show up twice. + // Every client now ships a real first-party asset, two of them traced from the + // vendor's own raster. The rule this guards is that no client ever borrows + // another product's logo; uniqueness is the teeth, because a borrowed logo + // would show up twice. Completeness of the map is guarded separately in + // client-marks-assets.test.ts, and the monogram branch stays for the next + // client that arrives without an asset. // // A mark reaches the DOM one of two ways. A plated brand SVG is an <img>; a // single-ink silhouette is a masked span so the theme supplies its color. Both @@ -287,7 +289,7 @@ test("each client row shows its own brand mark, never a borrowed one", async () const { root, container } = await mountPanel(); // OpenCode is monochrome, so its mark is masked rather than an <img>. - const opencodeMark = row(container, "OpenCode").querySelector<HTMLElement>(".awi-clientconfig-mark-mask"); + const opencodeMark = row(container, "OpenCode").querySelector<HTMLElement>(".client-mark--mask"); expect(opencodeMark).not.toBeNull(); expect(opencodeMark!.style.maskImage || opencodeMark!.style.webkitMaskImage) .toContain("/provider-icons/opencode.svg"); @@ -297,7 +299,7 @@ test("each client row shows its own brand mark, never a borrowed one", async () // rendering paths. const imgSources = [...container.querySelectorAll("img")] .map(img => img.getAttribute("src")); - const maskSources = [...container.querySelectorAll<HTMLElement>(".awi-clientconfig-mark-mask")] + const maskSources = [...container.querySelectorAll<HTMLElement>(".client-mark--mask")] .map(node => { const raw = node.style.maskImage || node.style.webkitMaskImage; return raw.replace(/^url\(["']?/, "").replace(/["']?\)$/, ""); @@ -312,7 +314,7 @@ test("each client row shows its own brand mark, never a borrowed one", async () } // A masked mark is a bare span, so it must not announce itself either; the // slot around it already carries aria-hidden. - for (const node of container.querySelectorAll(".awi-clientconfig-mark-mask")) { + for (const node of container.querySelectorAll(".client-mark--mask")) { expect(node.textContent).toBe(""); } diff --git a/gui/tests/integration-marks.test.ts b/gui/tests/integration-marks.test.ts new file mode 100644 index 0000000000..fa42cd45ce --- /dev/null +++ b/gui/tests/integration-marks.test.ts @@ -0,0 +1,101 @@ +import { expect, test } from "bun:test"; +import { existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { INTEGRATION_MARKS, MASKED_MARKS } from "../src/components/integration-marks"; +import { CLIENT_MARKS, MONOCHROME_CLIENT_MARKS } from "../src/components/apikeys-workspace/client-config-clients"; +import { TABS } from "../src/pages/integrations/integration-tabs"; + +const PUBLIC_DIR = join(import.meta.dir, "..", "public"); + +function bodyOf(src: string): string { + return readFileSync(join(PUBLIC_DIR, src.replace(/^\//, "")), "utf8"); +} + +function inksOf(body: string): Set<string> { + const matches = body.match(/(?:fill|stop-color)\s*[:=]\s*"?#[0-9a-fA-F]{3,8}/g) ?? []; + return new Set(matches.map(raw => raw.split(/[:=]/).pop()!.replace(/"/g, "").trim().toLowerCase())); +} + +/* + * The page renders a mark for every row it draws, so a row whose id is missing + * from the map renders nothing where a logo should be. The map is a Record over + * OverviewClientId, so the compiler catches an omission -- but only for ids that + * type already knows. This asserts the values are real. + */ +test("every Integrations row has a mark that resolves to a committed file", () => { + const broken = Object.entries(INTEGRATION_MARKS) + .filter(([, src]) => src !== null && !existsSync(join(PUBLIC_DIR, src.replace(/^\//, "")))); + expect(broken).toEqual([]); +}); + +/* + * A null value is a legitimate answer -- it renders a monogram -- but none of the + * current rows should be taking it. If one does, either an asset was dropped or a + * client was added without a mark decision, and both look like a rendering bug + * from the outside. + */ +test("no Integrations row falls back to a monogram today", () => { + const monogram = Object.entries(INTEGRATION_MARKS) + .filter(([, src]) => src === null) + .map(([id]) => id); + expect(monogram).toEqual([]); +}); + +/* + * Masking paints the artwork with the theme's text color, discarding whatever + * the file carries. Doing that to a multi-color mark flattens a brand palette + * into one ink, and the result still renders and still looks deliberate, which is + * why this needs a test rather than review attention. + */ +test("no multi-color asset is masked", () => { + const flattened: string[] = []; + for (const src of MASKED_MARKS) { + const body = bodyOf(src); + const gradient = /<(linearGradient|radialGradient)[\s>]/.test(body); + const inks = inksOf(body); + if (gradient || inks.size > 1) flattened.push(`${src}: ${inks.size} ink(s)${gradient ? " + gradient" : ""}`); + } + expect(flattened).toEqual([]); +}); + +/* + * The inverse rule, and the one that cannot be derived from the file: a mark may + * be a single ink and still not be a masking candidate, because that ink is the + * brand. openai.svg is #10A37F and deepseek-harness.svg is #4d6bfe; masking + * either repaints a trademark in the theme's text color. Pinned with their inks + * so a vendor changing its asset shows up here rather than silently satisfying + * the assertion. + */ +test("a single-ink asset whose ink is a brand color is not masked", () => { + for (const [src, ink] of [ + ["/provider-icons/openai.svg", "#10a37f"], + ["/provider-icons/deepseek-harness.svg", "#4d6bfe"], + ] as const) { + expect(MASKED_MARKS.has(src), `${src} must not be masked`).toBe(false); + expect([...inksOf(bodyOf(src))], `${src} ink changed upstream`).toEqual([ink]); + } +}); + +/* + * MASKED_MARKS is derived from MONOCHROME_CLIENT_MARKS rather than restated, and + * this is what makes that derivation observable: the two must describe the same + * assets. A second hand-maintained list would drift, and the drift would be a + * mark masked on one surface and not on another. + */ +test("the masked set is exactly the monochrome client marks", () => { + const expected = [...MONOCHROME_CLIENT_MARKS].map(id => CLIENT_MARKS[id]).filter(Boolean).sort(); + expect([...MASKED_MARKS].sort()).toEqual(expected as string[]); +}); + +/* + * The tab strip draws a mark per tab, and two tabs have no client behind them: + * overview is the page and keys is a credential surface. Every OTHER tab must be + * in the map, or its tab renders bare while its neighbours carry logos. + */ +test("every client tab has a mark", () => { + const bare = TABS + .filter(tab => tab.id !== "overview" && tab.id !== "keys") + .filter(tab => !(tab.id in INTEGRATION_MARKS)); + expect(bare.map(tab => tab.id)).toEqual([]); +}); + diff --git a/gui/tests/integrations-surfaces.test.tsx b/gui/tests/integrations-surfaces.test.tsx index 5889c1619b..e6d3cf585d 100644 --- a/gui/tests/integrations-surfaces.test.tsx +++ b/gui/tests/integrations-surfaces.test.tsx @@ -776,3 +776,83 @@ test("every export client has both an Integrations tab and a file-surface entry" const orphaned = [...FILE_CLIENTS].filter(id => !clientIds.has(id)); expect(orphaned).toEqual([]); }); + +/* + * The mark has to reach every surface, not just the API tab it started on. Three + * of them are checked here; the fourth is client-config-panel.test.tsx. + * + * These assert on the rendered DOM rather than on the map, because the map being + * right and the component never being called is exactly the failure a map-only + * test cannot see -- and it is the failure that would ship, since the marks were + * correct in data long before any surface drew them. + */ +test("a client page header draws its client's mark", async () => { + stateResponse = () => json(status({ + clientId: "dsh", + configPath: "/tmp/home/.dsh/settings.yaml", + })); + await mountClient(true, "dsh"); + + const head = container.querySelector(".integration-client-head")!; + const mark = head.querySelector<HTMLElement>(".client-mark"); + expect(mark, "the client page header should carry a mark").not.toBeNull(); + // dsh is single-ink but its ink is DeepSeek blue, so it renders as an image. + expect(mark!.querySelector("img")?.getAttribute("src")).toBe("/provider-icons/deepseek-harness.svg"); + // Decoration beside a heading that already names the client. + expect(mark!.getAttribute("aria-hidden")).toBe("true"); +}); + +test("every overview card draws a mark, and none of them names itself", async () => { + await mountOverview(); + + const cards = [...container.querySelectorAll(".integration-card")]; + expect(cards.length).toBeGreaterThan(4); + const bare = cards + .filter(card => card.querySelector(".client-mark") === null) + .map(card => card.getAttribute("data-client")); + expect(bare).toEqual([]); + + // A mark next to a visible label must not join the accessible name, or a + // screen reader says the client twice. + for (const mark of container.querySelectorAll(".client-mark")) { + expect(mark.getAttribute("aria-hidden")).toBe("true"); + } + for (const img of container.querySelectorAll(".client-mark img")) { + expect(img.getAttribute("alt")).toBe(""); + } + + // The card head is space-between; the mark must sit with the title rather than + // after the badge, so it is the first child. + const head = cards[0]!.querySelector(".integration-card-head")!; + expect(head.firstElementChild?.classList.contains("client-mark")).toBe(true); +}); + +test("the tab strip marks every client tab and leaves the two non-client tabs bare", async () => { + const [{ createRoot }, { LanguageProvider }, { default: Integrations }] = await Promise.all([ + import("react-dom/client"), + import("../src/i18n/provider"), + import("../src/pages/Integrations"), + ]); + await act(async () => { + root = createRoot(container); + root.render( + <LanguageProvider> + <Integrations apiBase={apiBase} /> + </LanguageProvider>, + ); + }); + await act(async () => { await new Promise<void>(resolve => testWindow.setTimeout(resolve, 30)); }); + + const tabs = [...container.querySelectorAll<HTMLElement>(".page-tab")]; + expect(tabs.length).toBeGreaterThan(10); + const marked = tabs.filter(tab => tab.querySelector(".client-mark") !== null); + // overview and keys carry no client, so they carry no mark. + expect(tabs.length - marked.length).toBe(2); + + const codexTab = tabs.find(tab => tab.id === "integrations-tab-codex")!; + expect(codexTab.querySelector(".client-mark img")?.getAttribute("src")).toBe("/provider-icons/openai.svg"); + // The label lost its "CLI": the mark carries that identity now, and the row + // covers the app and SDK too. + expect(codexTab.textContent).toBe("Codex"); +}); + From 2a90cdaa9a5df543e6aa08ab6dd715e0efedf9c0 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Mon, 31 Aug 2026 23:51:45 +0900 Subject: [PATCH 06/42] feat(integrations): let the user overwrite a conflicted config on purpose (#3084) A conflict was a dead end. The writer refused unconditionally, the GUI locked the switch, and the only way forward was opening the config in an editor -- the thing a dashboard exists to avoid. Now the refusal can be waived, but only by asking for it by name. Backend. `applyOrRefreshIntegration` takes a `ConflictPolicy`; `overwrite` skips the conflict refusal and nothing else. `unsafe`, `not_installed` and `non_loopback` still refuse -- a snapshot is not a licence to replace a value the merge cannot reason about. Everything that makes it recoverable is shared with apply: the same snapshot, atomic write, compare-before-commit recheck and journal row, which is why this is a policy flag on one code path rather than a second implementation. A forced `foreign-edit` drops what the previous record owned before merging, the same way a stale refresh does; without it a path the old record covered and the new one does not would be unremovable by any later disable. The journal row is its own kind. `apply` would be a lie about an operation that replaced somebody else's block, and the rollback list is the one place a user looks after a mistake. Route. `overwriteConflict` is optional and absent means refuse. Non-boolean is 400, and so is `{enabled: false, overwriteConflict: true}` -- forcing a DISABLE over a conflict is exactly the deletion of unowned work this subsystem exists to prevent, so it is rejected rather than ignored. GUI. A danger button appears for `conflict` and no other state, behind a ConsequenceDialog that names the config path and says the change is undoable. The copy splits on the reason: an unowned block in the way is a different loss from the user's own edit inside ours. The switch stays locked either way. Verification. Nine new writer tests, three route tests and four mounted GUI tests, each driven red first: always-refuse, kind=apply, unsafe-allowed, record-not-dropped, waiver-routed-to-apply, combination-ignored, button-for-every -state, button-without-installed, mutate-before-confirm, and single-copy. Two guards added for the `OperationKind` union, which is declared three times and imported zero times -- the doc comment promised that check for a while and nothing was enforcing it. --- gui/src/i18n/de.ts | 8 + gui/src/i18n/en.ts | 8 + gui/src/i18n/fr.ts | 8 + gui/src/i18n/ja.ts | 8 + gui/src/i18n/ko.ts | 8 + gui/src/i18n/ru.ts | 8 + gui/src/i18n/tr.ts | 8 + gui/src/i18n/zh-TW.ts | 8 + gui/src/i18n/zh.ts | 8 + .../integrations/FileIntegrationPage.tsx | 73 ++++++++ .../integrations/IntegrationsOverview.tsx | 62 +++++++ gui/src/pages/integrations/integration-api.ts | 9 +- .../pages/integrations/overview-clients.ts | 6 + gui/tests/integrations-surfaces.test.tsx | 84 ++++++++- src/integrations/journal.ts | 13 +- src/integrations/writer.ts | 89 +++++++++- src/server/management/integration-routes.ts | 41 ++++- tests/integrations-journal.test.ts | 51 ++++++ tests/integrations-writer.test.ts | 165 ++++++++++++++++++ tests/management-integration-routes.test.ts | 71 ++++++++ 20 files changed, 720 insertions(+), 16 deletions(-) diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts index df074ac7e4..5278690349 100644 --- a/gui/src/i18n/de.ts +++ b/gui/src/i18n/de.ts @@ -1125,6 +1125,14 @@ export const de: Record<TKey, string> = { "integrations.kind.disable": "Deaktiviert", "integrations.kind.refresh": "Aktualisiert", "integrations.kind.restore": "Wiederhergestellt", + "integrations.kind.overwrite": "Überschrieben", + "integrations.dialog.overwrite.title": "Den Block in dieser Konfiguration ersetzen?", + "integrations.dialog.overwrite.changesUnowned": "In {path} belegt ein Block, den nicht wir geschrieben haben, die Stelle, die opencodex braucht. Beim Anwenden wird er durch den Block ersetzt, den opencodex schreibt.", + "integrations.dialog.overwrite.changesForeign": "Deine Änderung im opencodex-Block in {path} wird verworfen und durch den Block ersetzt, den opencodex schreibt.", + "integrations.dialog.overwrite.breakage": "Was der andere Block eingestellt hat, wirkt danach nicht mehr. Der Rest der Datei bleibt unberührt.", + "integrations.dialog.overwrite.undo": "Vorher wird ein Snapshot gespeichert, deshalb steht dieser Schritt unten in der Rollback-Liste und kann zurückgenommen werden.", + "integrations.dialog.overwrite.confirm": "Ersetzen", + "integrations.action.overwrite": "Ersetzen", "integrations.semantics.opencode": "Gilt nur für direkte Starts von der Festplatte; die Umgebungsinjektion von ocx opencode hat Vorrang.", "integrations.semantics.pi": "Gilt für neue Sitzungen.", "integrations.semantics.omp": "Starten Sie OMP neu, um den Katalog zu laden.", diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index 0b3ee0ffab..ab6b652c69 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -1620,6 +1620,14 @@ export const en = { "integrations.kind.disable": "Disabled", "integrations.kind.refresh": "Updated", "integrations.kind.restore": "Restored", + "integrations.kind.overwrite": "Overwritten", + "integrations.dialog.overwrite.title": "Replace the block in this config?", + "integrations.dialog.overwrite.changesUnowned": "A block we did not write occupies the settings opencodex needs in {path}. Applying replaces it with the block opencodex would write.", + "integrations.dialog.overwrite.changesForeign": "Your edit inside the opencodex block in {path} will be discarded and replaced with the block opencodex would write.", + "integrations.dialog.overwrite.breakage": "Anything the other block configured stops taking effect. Content elsewhere in the file is left alone.", + "integrations.dialog.overwrite.undo": "A snapshot is saved first, so this appears in the rollback list below and can be undone.", + "integrations.dialog.overwrite.confirm": "Replace", + "integrations.action.overwrite": "Replace", "integrations.semantics.opencode": "Direct disk launches only; ocx opencode environment injection takes precedence.", "integrations.semantics.pi": "Applies to new sessions.", "integrations.semantics.omp": "Restart OMP to load the catalog.", diff --git a/gui/src/i18n/fr.ts b/gui/src/i18n/fr.ts index e42f361f97..5b1809f394 100644 --- a/gui/src/i18n/fr.ts +++ b/gui/src/i18n/fr.ts @@ -1593,6 +1593,14 @@ export const fr: Record<TKey, string> = { "integrations.kind.disable": "Désactivé", "integrations.kind.refresh": "Mis à jour", "integrations.kind.restore": "Restauré", + "integrations.kind.overwrite": "Écrasé", + "integrations.dialog.overwrite.title": "Remplacer le bloc dans cette configuration ?", + "integrations.dialog.overwrite.changesUnowned": "Dans {path}, un bloc que nous n'avons pas écrit occupe l'emplacement dont opencodex a besoin. L'application le remplace par le bloc qu'écrirait opencodex.", + "integrations.dialog.overwrite.changesForeign": "Votre modification dans le bloc opencodex de {path} sera abandonnée et remplacée par le bloc qu'écrirait opencodex.", + "integrations.dialog.overwrite.breakage": "Ce que configurait l'autre bloc cesse de s'appliquer. Le reste du fichier n'est pas touché.", + "integrations.dialog.overwrite.undo": "Un instantané est enregistré au préalable : cette opération apparaît dans la liste de restauration ci-dessous et peut être annulée.", + "integrations.dialog.overwrite.confirm": "Remplacer", + "integrations.action.overwrite": "Remplacer", "integrations.semantics.opencode": "Uniquement pour les lancements directs depuis le disque ; l’injection d’environnement par ocx opencode est prioritaire.", "integrations.semantics.pi": "S’applique aux nouvelles sessions.", "integrations.semantics.omp": "Redémarrez OMP pour charger le catalogue.", diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index 6f82b308c3..d1bfb6dfcd 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -1553,6 +1553,14 @@ export const ja: Record<TKey, string> = { "integrations.kind.disable": "解除", "integrations.kind.refresh": "更新", "integrations.kind.restore": "復元", + "integrations.kind.overwrite": "上書き", + "integrations.dialog.overwrite.title": "この設定ファイルのブロックを置き換えますか?", + "integrations.dialog.overwrite.changesUnowned": "{path} で opencodex が必要とする位置を、opencodex が書いたのではないブロックが占めています。適用すると opencodex が書くブロックに置き換わります。", + "integrations.dialog.overwrite.changesForeign": "{path} の opencodex ブロックに加えた変更は破棄され、opencodex が書くブロックに置き換わります。", + "integrations.dialog.overwrite.breakage": "そのブロックが設定していた内容は効かなくなります。ファイルの他の部分はそのままです。", + "integrations.dialog.overwrite.undo": "先にスナップショットを保存するので、下のロールバック一覧に残り、元に戻せます。", + "integrations.dialog.overwrite.confirm": "置き換える", + "integrations.action.overwrite": "置き換える", "integrations.semantics.opencode": "ディスクから直接起動した場合にのみ適用されます。ocx opencode の環境注入が優先されます。", "integrations.semantics.pi": "新しいセッションから適用されます。", "integrations.semantics.omp": "カタログを読み込むには OMP を再起動してください。", diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index 9dc1843263..532b798018 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -1149,6 +1149,14 @@ export const ko: Record<TKey, string> = { "integrations.kind.disable": "해제", "integrations.kind.refresh": "업데이트", "integrations.kind.restore": "복원", + "integrations.kind.overwrite": "덮어씀", + "integrations.dialog.overwrite.title": "이 설정 파일의 블록을 덮어쓸까요?", + "integrations.dialog.overwrite.changesUnowned": "{path}에서 opencodex가 써야 하는 자리를 우리가 쓰지 않은 블록이 차지하고 있습니다. 적용하면 opencodex가 쓰는 블록으로 바꿉니다.", + "integrations.dialog.overwrite.changesForeign": "{path}의 opencodex 블록에 직접 넣은 수정은 버리고 opencodex가 쓰는 블록으로 바꿉니다.", + "integrations.dialog.overwrite.breakage": "그 블록이 설정하던 동작은 더 이상 적용되지 않습니다. 파일의 다른 부분은 그대로 둡니다.", + "integrations.dialog.overwrite.undo": "먼저 스냅숏을 저장하므로 아래 되돌리기 목록에 남고 다시 되돌릴 수 있습니다.", + "integrations.dialog.overwrite.confirm": "덮어쓰기", + "integrations.action.overwrite": "덮어쓰기", "integrations.semantics.opencode": "디스크에서 직접 실행할 때만 적용됩니다. ocx opencode의 환경 주입이 우선합니다.", "integrations.semantics.pi": "새 세션부터 적용됩니다.", "integrations.semantics.omp": "카탈로그를 불러오려면 OMP를 재시작하세요.", diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts index f4cec3e01f..28f30646bd 100644 --- a/gui/src/i18n/ru.ts +++ b/gui/src/i18n/ru.ts @@ -1604,6 +1604,14 @@ export const ru: Record<TKey, string> = { "integrations.kind.disable": "Отключено", "integrations.kind.refresh": "Обновлено", "integrations.kind.restore": "Восстановлено", + "integrations.kind.overwrite": "Перезаписано", + "integrations.dialog.overwrite.title": "Заменить блок в этом файле настроек?", + "integrations.dialog.overwrite.changesUnowned": "В {path} место, нужное opencodex, занято блоком, который писали не мы. Применение заменит его блоком, который пишет opencodex.", + "integrations.dialog.overwrite.changesForeign": "Ваша правка внутри блока opencodex в {path} будет отброшена и заменена блоком, который пишет opencodex.", + "integrations.dialog.overwrite.breakage": "То, что настраивал прежний блок, перестанет действовать. Остальная часть файла не меняется.", + "integrations.dialog.overwrite.undo": "Снимок сохраняется заранее, поэтому операция попадёт в список откатов ниже и её можно отменить.", + "integrations.dialog.overwrite.confirm": "Заменить", + "integrations.action.overwrite": "Заменить", "integrations.semantics.opencode": "Действует только при прямом запуске с диска; внедрение окружения через ocx opencode имеет приоритет.", "integrations.semantics.pi": "Применяется к новым сеансам.", "integrations.semantics.omp": "Перезапустите OMP, чтобы загрузить каталог.", diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 9847fc9cdb..232900f51e 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -1611,6 +1611,14 @@ export const tr: Record<TKey, string> = { "integrations.kind.disable": "Devre Dışı Bırakıldı", "integrations.kind.refresh": "Güncellendi", "integrations.kind.restore": "Geri Yüklendi", + "integrations.kind.overwrite": "Üzerine yazıldı", + "integrations.dialog.overwrite.title": "Bu yapılandırmadaki blok değiştirilsin mi?", + "integrations.dialog.overwrite.changesUnowned": "{path} içinde opencodex'in ihtiyaç duyduğu yeri, bizim yazmadığımız bir blok tutuyor. Uygulamak onu opencodex'in yazacağı blokla değiştirir.", + "integrations.dialog.overwrite.changesForeign": "{path} içindeki opencodex bloğuna yaptığınız değişiklik atılacak ve opencodex'in yazacağı blokla değiştirilecek.", + "integrations.dialog.overwrite.breakage": "Diğer bloğun yaptığı ayarlar artık geçerli olmaz. Dosyanın kalanına dokunulmaz.", + "integrations.dialog.overwrite.undo": "Önce bir anlık görüntü kaydedilir; bu işlem aşağıdaki geri alma listesinde görünür ve geri alınabilir.", + "integrations.dialog.overwrite.confirm": "Değiştir", + "integrations.action.overwrite": "Değiştir", "integrations.semantics.opencode": "Doğrudan disk başlatmaları.", "integrations.semantics.pi": "Yeni oturumlara uygulanır.", "integrations.semantics.hermes": "Yeni oturumlara uygulanır.", diff --git a/gui/src/i18n/zh-TW.ts b/gui/src/i18n/zh-TW.ts index 4eda29d0fc..b491a7bc94 100644 --- a/gui/src/i18n/zh-TW.ts +++ b/gui/src/i18n/zh-TW.ts @@ -2158,6 +2158,14 @@ export const zhTW: Record<TKey, string> = { "integrations.kind.disable": "已停用", "integrations.kind.refresh": "已更新", "integrations.kind.restore": "已還原", + "integrations.kind.overwrite": "已覆寫", + "integrations.dialog.overwrite.title": "替換這個設定檔中的區塊?", + "integrations.dialog.overwrite.changesUnowned": "{path} 中 opencodex 需要寫入的位置被一個並非我們寫入的區塊佔用。套用會將其替換為 opencodex 寫入的區塊。", + "integrations.dialog.overwrite.changesForeign": "{path} 中 opencodex 區塊內你所做的修改會被捨棄,並替換為 opencodex 寫入的區塊。", + "integrations.dialog.overwrite.breakage": "該區塊原本設定的內容將不再生效。檔案其他位置保持不變。", + "integrations.dialog.overwrite.undo": "會先儲存快照,因此這次操作會出現在下方的還原清單中,可以復原。", + "integrations.dialog.overwrite.confirm": "替換", + "integrations.action.overwrite": "替換", "integrations.semantics.opencode": "僅適用於直接從磁碟啟動;ocx opencode 的環境注入優先。", "integrations.semantics.pi": "適用於新工作階段。", "integrations.semantics.omp": "重新啟動 OMP 以載入模型目錄。", diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index a52fa27747..189a61fa20 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -1142,6 +1142,14 @@ export const zh: Record<TKey, string> = { "integrations.kind.disable": "已停用", "integrations.kind.refresh": "已更新", "integrations.kind.restore": "已恢复", + "integrations.kind.overwrite": "已覆盖", + "integrations.dialog.overwrite.title": "替换该配置文件中的代码块?", + "integrations.dialog.overwrite.changesUnowned": "{path} 中 opencodex 需要写入的位置被一个并非我们写入的代码块占用。应用会将其替换为 opencodex 写入的代码块。", + "integrations.dialog.overwrite.changesForeign": "{path} 中 opencodex 代码块内你所做的修改会被丢弃,并替换为 opencodex 写入的代码块。", + "integrations.dialog.overwrite.breakage": "该代码块原本配置的内容将不再生效。文件其他位置保持不变。", + "integrations.dialog.overwrite.undo": "会先保存快照,因此这次操作会出现在下方的回滚列表中,可以撤销。", + "integrations.dialog.overwrite.confirm": "替换", + "integrations.action.overwrite": "替换", "integrations.semantics.opencode": "仅适用于直接从磁盘启动;ocx opencode 的环境注入优先。", "integrations.semantics.pi": "对新会话生效。", "integrations.semantics.omp": "重启 OMP 以加载模型目录。", diff --git a/gui/src/pages/integrations/FileIntegrationPage.tsx b/gui/src/pages/integrations/FileIntegrationPage.tsx index 7b7397c825..377a94eeed 100644 --- a/gui/src/pages/integrations/FileIntegrationPage.tsx +++ b/gui/src/pages/integrations/FileIntegrationPage.tsx @@ -6,6 +6,7 @@ import { Notice, Switch } from "../../ui"; import ClientMark from "../../components/ClientMark"; import { markFor } from "../../components/integration-marks"; import IntegrationStateBadge from "./IntegrationStateBadge"; +import ConsequenceDialog, { type ConsequenceCopy } from "./ConsequenceDialog"; import RestoreDialog from "./RestoreDialog"; import { RollbackHistory } from "./RollbackHistory"; import { describeRefusal } from "./refusal-copy"; @@ -20,6 +21,27 @@ import { export type { FileIntegrationClientId }; +/* + * Copy for the overwrite dialog, selected on WHICH conflict it is. + * + * Same operation, materially different thing being lost: `unowned-key` means a + * block we did not write is in the way, `foreign-edit` means the user's own + * change inside our block is what gets discarded. One sentence covering both + * would have to be vague about the only part that matters. + */ +function overwriteCopy(reason: string | undefined, path: string): ConsequenceCopy { + return { + titleKey: "integrations.dialog.overwrite.title", + changesKey: reason === "foreign-edit" + ? "integrations.dialog.overwrite.changesForeign" + : "integrations.dialog.overwrite.changesUnowned", + breakageKey: "integrations.dialog.overwrite.breakage", + undoKey: "integrations.dialog.overwrite.undo", + confirmKey: "integrations.dialog.overwrite.confirm", + vars: { path }, + }; +} + const SEMANTICS_KEY: Record<FileIntegrationClientId, TKey> = { opencode: "integrations.semantics.opencode", pi: "integrations.semantics.pi", @@ -63,6 +85,8 @@ export default function FileIntegrationPage({ const [pending, setPending] = useState(false); const [failure, setFailure] = useState<string | null>(null); const [restoring, setRestoring] = useState<IntegrationJournalRow | null>(null); + /* Open only while the user is confirming an overwrite. */ + const [overwriting, setOverwriting] = useState(false); const fetchState = useCallback( (signal: AbortSignal) => loadIntegrationState(apiBase, client, signal), @@ -116,6 +140,27 @@ export default function FileIntegrationPage({ } }; + /* + * The one way past a conflict. Separate from `mutate` because it must not be + * reachable from the switch: the switch is locked in this state precisely + * because we cannot know what the user wants kept, and the whole point of the + * escape hatch is that they say so. + * + * Errors are NOT swallowed here -- they propagate so ConsequenceDialog can + * render them inside the dialog, where the user still has the cancel button. + */ + const overwrite = async () => { + if (!status) return; + setFailure(null); + try { + await toggleIntegration(apiBase, client, true, undefined, true); + refresh(); + } catch (error) { + setFailure(describeRefusal(t, error)); + throw error; + } + }; + /* * The switch means exactly what its label says. * @@ -176,6 +221,24 @@ export default function FileIntegrationPage({ </button> )} + {/* + Conflict used to be a dead end: the switch locks, the page explains why, + and the only way forward was to open the file and edit it by hand -- which + is the thing a user came to a dashboard to avoid. The switch stays locked + and this is the one way past it, behind a dialog that names the file and + says what is lost. + */} + {status.installed && status.state === "conflict" && ( + <button + type="button" + className="btn btn-danger" + onClick={() => setOverwriting(true)} + disabled={pending} + > + {t("integrations.action.overwrite")} + </button> + )} + <p className="page-sub">{t(SEMANTICS_KEY[client])}</p> <p className="integration-path">{status.configPath}</p> @@ -218,6 +281,16 @@ export default function FileIntegrationPage({ onRestored={refresh} /> )} + {overwriting && ( + <ConsequenceDialog + copy={overwriteCopy(status.reason, status.configPath)} + onClose={() => setOverwriting(false)} + onConfirm={async () => { + await overwrite(); + setOverwriting(false); + }} + /> + )} </section> ); } diff --git a/gui/src/pages/integrations/IntegrationsOverview.tsx b/gui/src/pages/integrations/IntegrationsOverview.tsx index 6754e04216..4bff17cebf 100644 --- a/gui/src/pages/integrations/IntegrationsOverview.tsx +++ b/gui/src/pages/integrations/IntegrationsOverview.tsx @@ -77,12 +77,15 @@ function OverviewCard({ result, onOpen, onToggle, + onOverwrite, }: { row: OverviewRow; pending: boolean; result: { tone: "ok" | "err"; text: string } | null; onOpen: () => void; onToggle: (() => void) | null; + /** Present only for a conflicted file client; null everywhere else. */ + onOverwrite: (() => void) | null; }) { const t = useT(); const detail = row.detail ?? (row.detailKey ? t(row.detailKey, row.detailVars ?? undefined) : null); @@ -147,6 +150,16 @@ function OverviewCard({ <button type="button" className="btn btn-ghost" onClick={onOpen} tabIndex={-1}> {t("integrations.action.settings")} </button> + {/* + Only in conflict, and only for a file client. The switch beside it stays + disabled -- this is not a second way to toggle, it is the way past a state + the toggle deliberately refuses to guess about. + */} + {onOverwrite && ( + <button type="button" className="btn btn-danger" onClick={onOverwrite} disabled={pending}> + {t("integrations.action.overwrite")} + </button> + )} </div> </li> ); @@ -165,6 +178,8 @@ export default function IntegrationsOverview({ const [restoring, setRestoring] = useState<IntegrationJournalRow | null>(null); const [cardResults, setCardResults] = useState<Partial<Record<OverviewRow["id"], { tone: "ok" | "err"; text: string }>>>({}); const [pendingToggle, setPendingToggle] = useState<OverviewRow | null>(null); + /* The conflicted row awaiting overwrite confirmation. */ + const [pendingOverwrite, setPendingOverwrite] = useState<OverviewRow | null>(null); const restoreFocusRef = useRef<HTMLButtonElement | null>(null); useEffect(() => { @@ -460,6 +475,31 @@ export default function IntegrationsOverview({ setPendingToggle(row); }; + /* + * Replace a conflicted block, after the dialog. File clients only: the native + * surfaces have their own ownership model and no writer path that takes this + * flag, which is why `row.status` gates the button that opens the dialog. + * + * Errors propagate so the dialog can show them while the user still has cancel. + */ + const overwriteCard = async (row: OverviewRow) => { + if (!row.status) return; + setCardPending(row.id); + setCardResult(row.id, null); + try { + await toggleIntegration(apiBase, row.status.clientId, true, undefined, true); + refresh(); + } catch (error) { + setCardResult(row.id, { + tone: "err", + text: describeRefusal(t, error, undefined, row.togglePath ?? undefined), + }); + throw error; + } finally { + setCardPending(null); + } + }; + return ( <section className="integrations-overview"> <div className="integration-summary"> @@ -550,6 +590,9 @@ export default function IntegrationsOverview({ result={cardResults[row.id] ?? null} onOpen={() => navigateHash(row.hash)} onToggle={row.toggle ? () => requestToggle(row, !(row.toggleOn ?? row.applied)) : null} + onOverwrite={row.status !== null && row.status.state === "conflict" && row.installed + ? () => setPendingOverwrite(row) + : null} /> ))} </ul> @@ -609,6 +652,25 @@ export default function IntegrationsOverview({ }} /> )} + {pendingOverwrite && pendingOverwrite.status && ( + <ConsequenceDialog + copy={{ + titleKey: "integrations.dialog.overwrite.title", + changesKey: pendingOverwrite.status.reason === "foreign-edit" + ? "integrations.dialog.overwrite.changesForeign" + : "integrations.dialog.overwrite.changesUnowned", + breakageKey: "integrations.dialog.overwrite.breakage", + undoKey: "integrations.dialog.overwrite.undo", + confirmKey: "integrations.dialog.overwrite.confirm", + vars: { path: pendingOverwrite.status.configPath }, + }} + onClose={() => setPendingOverwrite(null)} + onConfirm={async () => { + await overwriteCard(pendingOverwrite); + setPendingOverwrite(null); + }} + /> + )} </section> ); } diff --git a/gui/src/pages/integrations/integration-api.ts b/gui/src/pages/integrations/integration-api.ts index 42a3613cbb..9277840b61 100644 --- a/gui/src/pages/integrations/integration-api.ts +++ b/gui/src/pages/integrations/integration-api.ts @@ -54,7 +54,7 @@ export interface IntegrationStateListEnvelope { export interface IntegrationJournalRow { opId: string; clientId: IntegrationClientId; - kind: "apply" | "disable" | "refresh" | "restore"; + kind: "apply" | "disable" | "refresh" | "restore" | "overwrite"; at: string; configPath: string; snapshot: "none" | "stored" | "expired"; @@ -221,12 +221,17 @@ export async function toggleIntegration( client: FileIntegrationClientId, enabled: boolean, signal?: AbortSignal, + /** + * Opt in to replacing a conflicted block. Deliberately last and optional: no + * existing call site can acquire it, and a caller has to name it. + */ + overwriteConflict?: boolean, ) { return readResponse<IntegrationToggleResult>( await fetch(`${apiBase}/api/client-integrations/${encodeURIComponent(client)}`, { method: "PUT", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ enabled }), + body: JSON.stringify(overwriteConflict === true ? { enabled, overwriteConflict: true } : { enabled }), signal, }), ); diff --git a/gui/src/pages/integrations/overview-clients.ts b/gui/src/pages/integrations/overview-clients.ts index b454e47bd9..bfe973b692 100644 --- a/gui/src/pages/integrations/overview-clients.ts +++ b/gui/src/pages/integrations/overview-clients.ts @@ -164,6 +164,12 @@ export const JOURNAL_KIND_KEY: Record<IntegrationJournalRow["kind"], TKey> = { disable: "integrations.kind.disable", refresh: "integrations.kind.refresh", restore: "integrations.kind.restore", + /* + * Distinct from `apply` on purpose. This row is the only signal that an + * operation replaced a block somebody else wrote, and it sits in the one list + * a user reads after a mistake. + */ + overwrite: "integrations.kind.overwrite", }; export function isAppliedState(state: VisualIntegrationState): boolean { diff --git a/gui/tests/integrations-surfaces.test.tsx b/gui/tests/integrations-surfaces.test.tsx index e6d3cf585d..45359026d5 100644 --- a/gui/tests/integrations-surfaces.test.tsx +++ b/gui/tests/integrations-surfaces.test.tsx @@ -184,6 +184,24 @@ async function mountClient( await act(async () => { await new Promise<void>(resolve => testWindow.setTimeout(resolve, 30)); }); } +/** + * Mount again inside ONE test, against a fresh fixture. + * + * `useDataSurface` caches by `apiBase`, so a second mount on the same base + * replays the first response and the new `stateResponse` has no effect. Rotating + * the base is what makes a state sweep in a single test possible at all. + */ +async function remountClient(client: "hermes" | "dsh" = "hermes"): Promise<void> { + if (root) { + const current = root; + await act(async () => { current.unmount(); }); + root = null; + } + mountCount += 1; + apiBase = `http://ocx-test-${mountCount}.invalid`; + await mountClient(true, client); +} + test("the DSH surface uses localized ownership semantics and its own API route", async () => { stateResponse = () => json(status({ clientId: "dsh", @@ -273,6 +291,71 @@ test("conflict locks the switch instead of guessing", async () => { expect(toggleSwitch().disabled).toBe(true); }); +/* + * The overwrite escape hatch. + * + * Conflict was a dead end before it existed: the switch locks and the only way + * forward was hand-editing the file. These pin the two halves of the deal -- + * the button appears for exactly one state, and it costs a confirmation. + */ +test("a conflict offers an overwrite, and no other state does", async () => { + for (const state of ["absent", "current", "stale", "unsafe"] as const) { + stateResponse = () => json(status({ state })); + await remountClient(); + expect(buttonByText("Replace")).toBeUndefined(); + } + + stateResponse = () => json(status({ state: "conflict", reason: "unowned-key" })); + await remountClient(); + expect(buttonByText("Replace")).toBeDefined(); +}); + +test("a client with no config on disk is never offered an overwrite", async () => { + // installed:false means there is nothing to replace; the server refuses it as + // not_installed, so offering the button would only produce an error dialog. + stateResponse = () => json(status({ state: "conflict", reason: "unowned-key", installed: false })); + await mountClient(); + expect(buttonByText("Replace")).toBeUndefined(); +}); + +test("the overwrite button mutates nothing until the dialog is confirmed", async () => { + stateResponse = () => json(status({ state: "conflict", reason: "unowned-key" })); + await mountClient(); + + await act(async () => { buttonByText("Replace")!.click(); }); + // Opening the dialog is not the operation. + expect(requests.some(request => request.method === "PUT")).toBe(false); + + // The dialog names the file the user is about to lose a block from, and says + // the change is recoverable. + const dialog = container.querySelector(".integration-consequence-dialog")!; + expect(dialog.textContent).toContain("/tmp/home/.hermes/config.yaml"); + expect(dialog.textContent).toContain("rollback list"); + + const confirm = Array.from(dialog.querySelectorAll("button")).find( + button => (button.textContent ?? "").trim() === "Replace", + ) as HTMLButtonElement; + await act(async () => { confirm.click(); }); + + const put = requests.find(request => request.method === "PUT"); + expect(put?.body).toEqual({ enabled: true, overwriteConflict: true }); +}); + +test("a foreign edit and an unowned block get different dialog copy", async () => { + stateResponse = () => json(status({ state: "conflict", reason: "foreign-edit" })); + await remountClient(); + await act(async () => { buttonByText("Replace")!.click(); }); + // The user's own edit is what is discarded, and the copy has to say so. + expect(container.querySelector(".integration-consequence-dialog")!.textContent) + .toContain("Your edit inside the opencodex block"); + + stateResponse = () => json(status({ state: "conflict", reason: "unowned-key" })); + await remountClient(); + await act(async () => { buttonByText("Replace")!.click(); }); + expect(container.querySelector(".integration-consequence-dialog")!.textContent) + .toContain("A block we did not write"); +}); + test("unsafe locks the switch instead of guessing", async () => { stateResponse = () => json(status({ state: "unsafe", reason: "unparseable" })); await mountClient(); @@ -855,4 +938,3 @@ test("the tab strip marks every client tab and leaves the two non-client tabs ba // covers the app and SDK too. expect(codexTab.textContent).toBe("Codex"); }); - diff --git a/src/integrations/journal.ts b/src/integrations/journal.ts index 090204eafe..b5d341ac17 100644 --- a/src/integrations/journal.ts +++ b/src/integrations/journal.ts @@ -19,7 +19,18 @@ import { atomicWriteFile } from "../config"; import { ensureDir, fingerprint, integrationsDir, type OwnershipRecord } from "./ownership"; import { isIntegrationClientId, type IntegrationClientId } from "./registry"; -export type OperationKind = "apply" | "disable" | "refresh" | "restore"; +/** + * `overwrite` is deliberately distinct from `apply`. Both write our block, but + * only one of them replaced something the user or another tool had put there, + * and the rollback list is exactly where that distinction matters. + * + * This union is re-declared, not imported, in two other places -- the management + * route envelope and the GUI adapter -- because neither imports across that + * boundary. `tests/integrations-journal.test.ts` asserts the three agree, since + * nothing else can: a kind persisted here and missing there renders as a raw + * key with no type error anywhere. + */ +export type OperationKind = "apply" | "disable" | "refresh" | "restore" | "overwrite"; /** * Tagged so "the file did not exist" and "the snapshot was collected" stay diff --git a/src/integrations/writer.ts b/src/integrations/writer.ts index 831fa80c21..422aa680ea 100644 --- a/src/integrations/writer.ts +++ b/src/integrations/writer.ts @@ -263,7 +263,24 @@ function preflight(input: IntegrationWriteInput) { return { failed: undefined, store, io, clientId, spec, exportSpec, configPath, detectDir, before, parsed, contribution, record, classified } as const; } -function applyOrRefreshIntegration(input: IntegrationWriteInput, allowAbsent: boolean): WriteOutcome { +/** + * How a conflicted document is treated. + * + * `refuse` is the default and the only behavior that existed: a conflict means + * something we did not write occupies our paths, or our own block was edited, + * and guessing which one the user meant to keep is how a toggle deletes work. + * + * `overwrite` is the explicit escape hatch. It is never reached by a plain + * apply -- the caller has to ask for it by name -- because the whole value of + * the refusal is that it cannot be triggered by accident. + */ +type ConflictPolicy = "refuse" | "overwrite"; + +function applyOrRefreshIntegration( + input: IntegrationWriteInput, + allowAbsent: boolean, + conflictPolicy: ConflictPolicy = "refuse", +): WriteOutcome { const pre = preflight(input); if (pre.failed) return pre.failed; const { store, io, clientId, spec, exportSpec, configPath, detectDir, before, parsed, contribution, record, classified } = pre; @@ -278,10 +295,20 @@ function applyOrRefreshIntegration(input: IntegrationWriteInput, allowAbsent: bo `The generated ${clientId} integration is loopback-only and does not emit the admission header a non-loopback bind requires. Give it loopback access instead, through a tunnel or a local forwarder.`); } if (classified.state === "conflict") { - return refuse(clientId, "conflict", "conflict", - classified.reason === "foreign-edit" - ? `${configPath} changed after opencodex wrote it` - : `${configPath} already contains an opencodex block we did not write`); + if (conflictPolicy === "refuse") { + return refuse(clientId, "conflict", "conflict", + classified.reason === "foreign-edit" + ? `${configPath} changed after opencodex wrote it` + : `${configPath} already contains an opencodex block we did not write`); + } + /* + * The caller asked for the overwrite explicitly, so the merge below runs + * against the document as it stands and our block replaces whatever holds + * our paths. Everything that makes it recoverable is shared with apply -- + * the snapshot, the atomic write, the compare-before-commit recheck and the + * journal row all come from the same commit() call -- which is why this is a + * policy flag on one code path rather than a second implementation. + */ } /* * `unsafe` from the classifier means the document is not one we may write @@ -327,7 +354,22 @@ function applyOrRefreshIntegration(input: IntegrationWriteInput, allowAbsent: bo */ const base = classified.state === "stale" && record ? removeFragments(parsed, record.fragmentPaths, new Set(record.createdContainers ?? [])).doc - : parsed; + : classified.state === "conflict" && record + /* + * A forced overwrite of a `foreign-edit` conflict drops what the previous + * record owned for the same reason a stale refresh does: the replacement + * record covers the paths we are about to write, so a path the old record + * owned and the new one does not would be stranded forever, unremovable by + * any later disable. + * + * With NO record -- an `unowned-key` conflict -- there is nothing to drop and + * the merge runs against the user's document directly. That is correct: + * createdContainerPaths then attributes every container they already had to + * them, so a later disable removes our leaves and leaves their structure + * standing. + */ + ? removeFragments(parsed, record.fragmentPaths, new Set(record.createdContainers ?? [])).doc + : parsed; // Computed against the document as it stands BEFORE the merge: afterwards // every container exists and "did we create this?" is unanswerable. const created = createdContainerPaths(base, contribution); @@ -375,7 +417,16 @@ function applyOrRefreshIntegration(input: IntegrationWriteInput, allowAbsent: bo const snapshot = store.captureSnapshot(clientId, opId, before); const at = new Date(io.now()).toISOString(); const entry: JournalEntry = { - opId, clientId, kind: classified.state === "stale" ? "refresh" : "apply", at, configPath, + /* + * `overwrite` is its own kind rather than reusing `apply`. The rollback list + * is the one place a user goes after a mistake, and "applied" is a lie about + * an operation that replaced a block somebody else wrote. + */ + opId, clientId, + kind: classified.state === "conflict" + ? "overwrite" + : classified.state === "stale" ? "refresh" : "apply", + at, configPath, snapshot, resultFingerprint: fingerprint(text), resultAbsent: false, priorRecord: record, }; const refreshablePaths = refreshablePathsOf(contribution); @@ -406,6 +457,23 @@ export function applyIntegration(input: IntegrationWriteInput): WriteOutcome { return applyOrRefreshIntegration(input, true); } +/** + * Apply over a conflicted config, replacing whatever holds our paths. + * + * Separate from `applyIntegration` and never a flag on it: a caller has to name + * this function to get the behavior, so no existing call site can acquire it by + * passing a default through. + * + * What it does NOT relax. `unsafe` still refuses -- a blocked container means the + * merge would replace a value it cannot reason about, and a snapshot is not a + * licence for that. `not_installed` and `non_loopback` still refuse; neither is a + * conflict. And a non-conflict state behaves exactly as apply does, so calling + * this on a clean file is not a way to skip any other check. + */ +export function overwriteIntegration(input: IntegrationWriteInput): WriteOutcome { + return applyOrRefreshIntegration(input, true, "overwrite"); +} + /** Refresh an owned stale block, but never create or reconnect an absent one. */ export function refreshIntegration(input: IntegrationWriteInput): WriteOutcome { return applyOrRefreshIntegration(input, false); @@ -698,6 +766,13 @@ export function refreshIntegrationCoordinated( return coordinatedWrite(input, refreshIntegration, options); } +export function overwriteIntegrationCoordinated( + input: IntegrationWriteInput, + options?: CoordinatedIntegrationOptions, +): Promise<WriteOutcome> { + return coordinatedWrite(input, overwriteIntegration, options); +} + export function disableIntegrationCoordinated( input: IntegrationWriteInput, options?: CoordinatedIntegrationOptions, diff --git a/src/server/management/integration-routes.ts b/src/server/management/integration-routes.ts index ff0b562bfd..bc635b9743 100644 --- a/src/server/management/integration-routes.ts +++ b/src/server/management/integration-routes.ts @@ -21,6 +21,7 @@ import { createIntegrationStateStore, type IntegrationStateStore } from "../../i import { applyIntegrationCoordinated, disableIntegrationCoordinated, + overwriteIntegrationCoordinated, restoreIntegrationCoordinated, type IntegrationRestoreInput, type IntegrationWriteInput, @@ -70,7 +71,7 @@ export interface IntegrationJournalEnvelope { export interface IntegrationJournalRow { opId: string; clientId: IntegrationClientId; - kind: "apply" | "disable" | "refresh" | "restore"; + kind: "apply" | "disable" | "refresh" | "restore" | "overwrite"; at: string; configPath: string; snapshot: "none" | "stored" | "expired"; @@ -79,6 +80,14 @@ export interface IntegrationJournalRow { export interface IntegrationToggleBody { enabled: boolean; + /** + * Opt in to replacing a conflicted block with the one opencodex would write. + * + * Absent and `false` behave identically and are the only states a caller + * reaches by accident, which is the point: the conflict refusal protects work + * we did not author, so it can only be waived by asking for it by name. + */ + overwriteConflict?: boolean; } export interface IntegrationRestoreBody { @@ -472,16 +481,38 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R code: "invalid_enabled", }, 400, req, ctx.config); } + if (parsed.overwriteConflict !== undefined && typeof parsed.overwriteConflict !== "boolean") { + return jsonResponse({ + error: "overwriteConflict must be a boolean", + code: "invalid_overwrite_conflict", + }, 400, req, ctx.config); + } + /* + * Rejected rather than ignored. Disabling a block we do not own is precisely + * the deletion this subsystem exists to prevent, so a caller sending this + * combination has misunderstood the field, and silently dropping it would + * answer 200 for a request whose intent we refused. + */ + if (parsed.overwriteConflict === true && parsed.enabled === false) { + return jsonResponse({ + error: "overwriteConflict applies only to enabling an integration", + code: "invalid_overwrite_conflict", + }, 400, req, ctx.config); + } try { const input = await buildIntegrationWriteInput(requestedClient, ctx, integrationStore()); const result = await runIntegrationMutationFlight( requestedClient, - parsed.enabled ? "apply" : "disable", + parsed.enabled ? (parsed.overwriteConflict === true ? "overwrite" : "apply") : "disable", input.io?.now ?? Date.now, - () => parsed.enabled - ? applyIntegrationCoordinated(input, { lockSeams: integrationMutationTestHooks?.lockSeams }) - : disableIntegrationCoordinated(input, { lockSeams: integrationMutationTestHooks?.lockSeams }), + () => { + const options = { lockSeams: integrationMutationTestHooks?.lockSeams }; + if (!parsed.enabled) return disableIntegrationCoordinated(input, options); + return parsed.overwriteConflict === true + ? overwriteIntegrationCoordinated(input, options) + : applyIntegrationCoordinated(input, options); + }, ); if (!result.ok) return writerFailureResponse(requestedClient, result, ctx); return jsonResponse(result satisfies IntegrationToggleEnvelope, 200, req, ctx.config); diff --git a/tests/integrations-journal.test.ts b/tests/integrations-journal.test.ts index ac38e1daf5..b98628d62c 100644 --- a/tests/integrations-journal.test.ts +++ b/tests/integrations-journal.test.ts @@ -338,3 +338,54 @@ describe("store isolation", () => { expect(store.findOperation("with-prior")?.priorRecord).toEqual(priorRecord); }); }); + +/** + * `OperationKind` is declared three times and imported zero times across the + * boundaries that carry it: the store writes it, the management route re-declares + * it in its envelope, and the GUI adapter re-declares it again. Neither of the + * latter two imports from `src/integrations/journal`, so the compiler has nothing + * to compare and a kind added in one place is a silent gap in the others -- the + * rollback list renders a raw i18n key and no build fails. + * + * Only the GUI's `JOURNAL_KIND_KEY` is compiler-checked, and only against the + * GUI's own copy of the union. This reads all three declarations as text, which + * is unpleasant and is also the only thing that can see across three trees that + * do not share a module graph. + */ +describe("the operation-kind union agrees across the three trees that redeclare it", () => { + const UNION = /"apply"\s*\|\s*"disable"\s*\|\s*"refresh"\s*\|\s*"restore"[^;]*/; + + function kindsIn(relPath: string, anchor: RegExp): string[] { + const source = readFileSync(join(import.meta.dir, "..", relPath), "utf8"); + const declaration = source.match(anchor); + if (!declaration) throw new Error(`no operation-kind union found in ${relPath}`); + const union = declaration[0].match(UNION); + if (!union) throw new Error(`the union in ${relPath} no longer starts with the four original kinds`); + return [...union[0].matchAll(/"([a-z]+)"/g)].map(match => match[1]!).sort(); + } + + test("the store, the management envelope and the GUI adapter list the same kinds", () => { + const store = kindsIn("src/integrations/journal.ts", /export type OperationKind =[^;]*/); + const route = kindsIn("src/server/management/integration-routes.ts", /kind: "apply"[^;]*/); + const gui = kindsIn("gui/src/pages/integrations/integration-api.ts", /kind: "apply"[^;]*/); + + // Named explicitly so a kind silently dropped from ALL THREE still fails. + expect(store).toEqual(["apply", "disable", "overwrite", "refresh", "restore"]); + expect(route).toEqual(store); + expect(gui).toEqual(store); + }); + + test("every kind the store can persist has rollback-list copy in the GUI", () => { + /* + * The failure this catches is not a type error anywhere: `JOURNAL_KIND_KEY` + * is exhaustive over the GUI's own union, so a kind missing from BOTH the + * GUI union and the map type-checks clean and renders the raw key. + */ + const map = readFileSync(join(import.meta.dir, "..", "gui/src/pages/integrations/overview-clients.ts"), "utf8"); + const block = map.match(/JOURNAL_KIND_KEY[^}]*}/); + if (!block) throw new Error("JOURNAL_KIND_KEY is gone from overview-clients.ts"); + for (const kind of kindsIn("src/integrations/journal.ts", /export type OperationKind =[^;]*/)) { + expect(block[0]).toContain(`${kind}: "integrations.kind.${kind}"`); + } + }); +}); diff --git a/tests/integrations-writer.test.ts b/tests/integrations-writer.test.ts index 17aca22612..486a29dece 100644 --- a/tests/integrations-writer.test.ts +++ b/tests/integrations-writer.test.ts @@ -12,6 +12,7 @@ import { exportContextOf, readIntegrationState } from "../src/integrations/state import { applyIntegration, disableIntegration, + overwriteIntegration, restoreIntegration, type IntegrationWriteInput, } from "../src/integrations/writer"; @@ -1157,3 +1158,167 @@ describe("nothing leaks", () => { expect(doc).toEqual({ providers: {} }); }); }); + +describe("overwriting a conflict on purpose", () => { + /* + * Conflict was a dead end. The writer refused unconditionally, the GUI locked + * the switch, and the only way forward was editing the file by hand -- the + * thing a dashboard exists to avoid. These prove the escape hatch is real AND + * that it stays narrow: it waives the conflict refusal and nothing else. + */ + + test("replaces a block we did not write, and the original is restorable", () => { + const configPath = installHermes(); + // A block occupying our exact paths that we never wrote: unowned-key. + writeFileSync(configPath, "providers:\n opencodex:\n api: http://someone-else.invalid\n note: hand written\n"); + const before = readFileSync(configPath, "utf8"); + + // The default still refuses, which is what makes the opt-in meaningful. + const refused = applyIntegration(input()); + expect(refused.ok).toBe(false); + if (!refused.ok) expect(refused.reason).toBe("conflict"); + expect(readFileSync(configPath, "utf8")).toBe(before); + + const forced = overwriteIntegration(input()); + expect(forced.ok).toBe(true); + if (!forced.ok) return; + expect(forced.changed).toBe(true); + expect(forced.state).toBe("current"); + + const after = readFileSync(configPath, "utf8"); + expect(after).not.toContain("someone-else.invalid"); + expect(after).not.toContain("hand written"); + expect(readIntegrationState(input())).toMatchObject({ state: "current" }); + + // Journaled as its own kind: "applied" would be a lie about an operation that + // replaced somebody else's block, and this list is where a user looks after a + // mistake. + const operations = store.listOperations("hermes"); + expect(operations).toHaveLength(1); + expect(operations[0]!.kind).toBe("overwrite"); + + // And it is undoable, byte for byte. + const undone = restoreIntegration({ ...input(), opId: operations[0]!.opId }); + expect(undone.ok).toBe(true); + expect(readFileSync(configPath, "utf8")).toBe(before); + }); + + test("discards an edit inside our own block without stranding what the old record owned", () => { + const configPath = installHermes(); + expect(applyIntegration(input()).ok).toBe(true); + writeFileSync(configPath, readFileSync(configPath, "utf8").replace( + "api_mode: chat_completions", + "api_mode: user_edited", + )); + expect(readIntegrationState(input())).toMatchObject({ state: "conflict", reason: "foreign-edit" }); + + expect(overwriteIntegration(input()).ok).toBe(true); + + const after = readFileSync(configPath, "utf8"); + expect(after).not.toContain("api_mode: user_edited"); + expect(after).toContain("api_mode: chat_completions"); + /* + * A foreign-edit force drops what the PREVIOUS record owned before merging, + * the same way a stale refresh does. Without that, a path the old record + * covered and the new one does not would be stranded, unremovable by any + * later disable -- so disable has to return the file to a clean absence. + */ + expect(disableIntegration(input()).ok).toBe(true); + expect(readFileSync(configPath, "utf8")).not.toContain("opencodex"); + }); + + test("leaves the user's own containers standing after a forced apply is disabled", () => { + const configPath = installHermes(); + // The user already owns `providers`, and something they wrote sits in our slot. + writeFileSync(configPath, "providers:\n mine:\n api: http://user.invalid\n opencodex:\n api: http://squatter.invalid\n"); + + expect(overwriteIntegration(input()).ok).toBe(true); + expect(disableIntegration(input()).ok).toBe(true); + + const doc = Bun.YAML.parse(readFileSync(configPath, "utf8")) as Record<string, unknown>; + // We did not create `providers`, so pruning it would be a second act of + // destruction after the one the user actually authorized. + expect(doc.providers).toBeDefined(); + expect((doc.providers as Record<string, unknown>).mine).toEqual({ api: "http://user.invalid" }); + expect((doc.providers as Record<string, unknown>).opencodex).toBeUndefined(); + }); + + test("still refuses an unsafe document, where a snapshot is not a licence", () => { + const configPath = installHermes(); + /* + * A non-object where we would have to write a section. The merge would + * replace a value it cannot reason about, so forcing it is data loss with a + * receipt -- the force path must not reach it. + */ + writeFileSync(configPath, "providers: not-a-mapping\n"); + const before = readFileSync(configPath, "utf8"); + + const result = overwriteIntegration(input()); + expect(result.ok).toBe(false); + if (!result.ok) expect(result.reason).toBe("unsafe"); + expect(readFileSync(configPath, "utf8")).toBe(before); + }); + + test("behaves exactly like apply when there is no conflict to overwrite", () => { + installHermes(); + /* + * Not a way to skip any other check: on a clean file this is an ordinary + * apply, journaled as one, and on an already-current file it is the same + * no-op apply performs. + */ + const first = overwriteIntegration(input()); + expect(first.ok).toBe(true); + if (first.ok) expect(first.changed).toBe(true); + expect(store.listOperations("hermes")[0]!.kind).toBe("apply"); + + const second = overwriteIntegration(input()); + expect(second.ok).toBe(true); + if (second.ok) expect(second.changed).toBe(false); + expect(store.listOperations("hermes")).toHaveLength(1); + }); + + test("refuses a client that is not installed", () => { + // installHermes() deliberately not called: a missing client is not a conflict. + const result = overwriteIntegration(input()); + expect(result.ok).toBe(false); + if (!result.ok) expect(result.reason).toBe("not_installed"); + }); + + test("drops a path only the OLD record owned, so nothing is left unremovable", () => { + /* + * The stranding case the sibling test above cannot see. When every path the + * old record owned is also a path the new contribution writes, dropping the + * old fragments first changes nothing observable -- the merge overwrites them + * anyway. The drop only matters when the layouts DISAGREE, which is what an + * upgrade leaves behind: a path we owned under the previous shape and no + * longer write. Without the drop, the replacement record never covers it, so + * no later disable can ever remove it. + */ + const configPath = installOpencode(); + const request = input({ clientId: "opencode" }); + expect(applyIntegration(request).ok).toBe(true); + + const document = JSON.parse(readFileSync(configPath, "utf8")) as { + providers: Record<string, Record<string, unknown>>; + }; + document.providers["opencodex-legacy"] = { api: "http://legacy.invalid" }; + // An edit inside a fragment we DO own is what makes this a foreign-edit + // conflict rather than ordinary drift. + document.providers.opencodex!.options = { baseURL: "http://user-edited.invalid" }; + writeFileSync(configPath, `${JSON.stringify(document, null, 2)}\n`); + + const record = { ...store.readRecords().opencode! }; + record.fragmentPaths = [...record.fragmentPaths, ["providers", "opencodex-legacy"]]; + store.putRecord(record); + expect(readIntegrationState(request)).toMatchObject({ state: "conflict", reason: "foreign-edit" }); + + expect(overwriteIntegration(request).ok).toBe(true); + + const after = JSON.parse(readFileSync(configPath, "utf8")) as { + providers: Record<string, unknown>; + }; + expect(after.providers["opencodex-legacy"]).toBeUndefined(); + expect(after.providers.opencodex).toBeDefined(); + expect(store.readRecords().opencode!.fragmentPaths).not.toContainEqual(["providers", "opencodex-legacy"]); + }); +}); diff --git a/tests/management-integration-routes.test.ts b/tests/management-integration-routes.test.ts index 095a5d146a..e8f1d37327 100644 --- a/tests/management-integration-routes.test.ts +++ b/tests/management-integration-routes.test.ts @@ -162,6 +162,15 @@ function put(clientId: string, enabled: boolean): Promise<Response> { }); } +/** A toggle that also waives the conflict refusal. */ +function putOverwrite(clientId: string, body: Record<string, unknown>): Promise<Response> { + return api(`/api/client-integrations/${clientId}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); +} + function restore(body: unknown): Promise<Response> { return api("/api/client-integrations/restore", { method: "POST", @@ -568,6 +577,68 @@ describe("refusals", () => { expect(store.listOperations()).toHaveLength(journalBefore); }); + test("the same conflict is resolvable when the caller waives it by name", async () => { + const configPath = installHermes(); + expect((await put("hermes", true)).status).toBe(200); + const edited = readFileSync(configPath, "utf8").replace( + "api_mode: chat_completions", + "api_mode: user_edited", + ); + writeFileSync(configPath, edited); + + // The plain enable is still refused: that is what makes the flag the only door. + expect((await put("hermes", true)).status).toBe(409); + expect(readFileSync(configPath, "utf8")).toBe(edited); + + const forced = await putOverwrite("hermes", { enabled: true, overwriteConflict: true }); + expect(forced.status).toBe(200); + expect(await forced.json()).toMatchObject({ ok: true, clientId: "hermes", state: "current" }); + expect(readFileSync(configPath, "utf8")).not.toContain("api_mode: user_edited"); + + // Journaled under its own kind, so the rollback list does not call this an apply. + const operations = store.listOperations("hermes"); + expect(operations[0]!.kind).toBe("overwrite"); + }); + + test("an explicit false waiver is exactly a plain apply, and still refuses", async () => { + const configPath = installHermes(); + expect((await put("hermes", true)).status).toBe(200); + const edited = readFileSync(configPath, "utf8").replace( + "api_mode: chat_completions", + "api_mode: user_edited", + ); + writeFileSync(configPath, edited); + + const response = await putOverwrite("hermes", { enabled: true, overwriteConflict: false }); + expect(response.status).toBe(409); + expect(readFileSync(configPath, "utf8")).toBe(edited); + }); + + test("a non-boolean waiver is rejected, and disable can never carry one", async () => { + installHermes(); + const nonBoolean = await putOverwrite("hermes", { enabled: true, overwriteConflict: "yes" }); + expect(nonBoolean.status).toBe(400); + expect(await nonBoolean.json()).toEqual({ + error: "overwriteConflict must be a boolean", + code: "invalid_overwrite_conflict", + }); + + /* + * Rejected rather than ignored. Forcing a DISABLE over a conflict is the + * deletion of unowned work this whole subsystem exists to prevent, so a + * caller sending the combination has misunderstood the field; answering 200 + * would confirm an intent we refused. + */ + const forcedDisable = await putOverwrite("hermes", { enabled: false, overwriteConflict: true }); + expect(forcedDisable.status).toBe(400); + expect(await forcedDisable.json()).toEqual({ + error: "overwriteConflict applies only to enabling an integration", + code: "invalid_overwrite_conflict", + }); + + expect(store.listOperations()).toHaveLength(0); + }); + test("a write failure in a non-failure state keeps its own envelope", async () => { /* * The defect this activates: routing on `state` instead of `reason`. From 06d6880874947cccc5fa5a08de84960988c77b78 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 00:01:57 +0900 Subject: [PATCH 07/42] docs(devlog): close the Aside + Integrations unit with what the plan got wrong (#3085) Every phase in this unit is on dev, so the unit belongs in _fin. The outcome doc records each phase against its merge commit, including wp7-wp9 which were appended after the original six and are not in the phase table. Four corrections worth keeping, because each was a wrong assumption rather than a detail: A mark's absence upstream is not its absence in a repository. 060 assumed each vendor publishes a usable SVG. None of the three did -- 113 bytes of <text>, a 129x32 wordmark, and nothing at all -- so two marks are traced from raster art. Masking is a trademark decision, not an ink count. 040's "single-ink" framing would have swept in openai.svg (one fill, but OpenAI's brand green) and grok.svg (neutral, but xAI's published file). Both stay images. A guard can pass for the wrong reason. 080's stranding test passed with the fix removed, because every path the old record owned was also one the new write covers. Rewritten around a layout the new write does not cover. Three declarations of one union had no guard, and journal.ts claimed otherwise in a comment. Now there is one. --- .../000_plan.md | 6 ++ .../001_aside_contract.md | 0 .../002_registration_checklist.md | 0 .../003_integrations_ux_diagnosis.md | 0 .../004_brand_mark_provenance.md | 0 .../005_remaining_marks_provenance.md | 0 .../010_wp2_aside_backend.md | 0 .../020_wp3_aside_gui.md | 0 .../030_wp4_history_redesign.md | 0 .../040_wp5_brand_marks.md | 0 .../050_wp6_stacked_prs.md | 0 .../060_wp7_remaining_marks.md | 0 .../070_wp8_integration_marks.md | 0 .../080_wp9_conflict_overwrite.md | 0 .../090_outcome.md | 62 +++++++++++++++++++ 15 files changed, 68 insertions(+) rename devlog/{_plan => _fin}/260831_aside_client_and_integrations_ux/000_plan.md (88%) rename devlog/{_plan => _fin}/260831_aside_client_and_integrations_ux/001_aside_contract.md (100%) rename devlog/{_plan => _fin}/260831_aside_client_and_integrations_ux/002_registration_checklist.md (100%) rename devlog/{_plan => _fin}/260831_aside_client_and_integrations_ux/003_integrations_ux_diagnosis.md (100%) rename devlog/{_plan => _fin}/260831_aside_client_and_integrations_ux/004_brand_mark_provenance.md (100%) rename devlog/{_plan => _fin}/260831_aside_client_and_integrations_ux/005_remaining_marks_provenance.md (100%) rename devlog/{_plan => _fin}/260831_aside_client_and_integrations_ux/010_wp2_aside_backend.md (100%) rename devlog/{_plan => _fin}/260831_aside_client_and_integrations_ux/020_wp3_aside_gui.md (100%) rename devlog/{_plan => _fin}/260831_aside_client_and_integrations_ux/030_wp4_history_redesign.md (100%) rename devlog/{_plan => _fin}/260831_aside_client_and_integrations_ux/040_wp5_brand_marks.md (100%) rename devlog/{_plan => _fin}/260831_aside_client_and_integrations_ux/050_wp6_stacked_prs.md (100%) rename devlog/{_plan => _fin}/260831_aside_client_and_integrations_ux/060_wp7_remaining_marks.md (100%) rename devlog/{_plan => _fin}/260831_aside_client_and_integrations_ux/070_wp8_integration_marks.md (100%) rename devlog/{_plan => _fin}/260831_aside_client_and_integrations_ux/080_wp9_conflict_overwrite.md (100%) create mode 100644 devlog/_fin/260831_aside_client_and_integrations_ux/090_outcome.md diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/000_plan.md b/devlog/_fin/260831_aside_client_and_integrations_ux/000_plan.md similarity index 88% rename from devlog/_plan/260831_aside_client_and_integrations_ux/000_plan.md rename to devlog/_fin/260831_aside_client_and_integrations_ux/000_plan.md index ff81cf010a..5f3cc876d3 100644 --- a/devlog/_plan/260831_aside_client_and_integrations_ux/000_plan.md +++ b/devlog/_fin/260831_aside_client_and_integrations_ux/000_plan.md @@ -34,6 +34,12 @@ per-row borders are literally what produces that texture. | wp4 | 030 | Rollback surface redesign | | wp5 | 040 | Brand marks for the nine clients showing a monogram | | wp6 | 050 | Stacked PR chain | +| wp7 | 060 | Marks for the last three monogram clients | +| wp8 | 070 | Marks on every Integrations surface, not just the API rows | +| wp9 | 080 | Conflict overwrite: writer, route, GUI dialog | + +wp7 through wp9 were appended after the original six. Outcome and corrections: +`090_outcome.md`. Research docs: 001 (Aside contract), 002 (registration checklist), 003 (Integrations UX diagnosis), 004 (brand mark provenance). diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/001_aside_contract.md b/devlog/_fin/260831_aside_client_and_integrations_ux/001_aside_contract.md similarity index 100% rename from devlog/_plan/260831_aside_client_and_integrations_ux/001_aside_contract.md rename to devlog/_fin/260831_aside_client_and_integrations_ux/001_aside_contract.md diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/002_registration_checklist.md b/devlog/_fin/260831_aside_client_and_integrations_ux/002_registration_checklist.md similarity index 100% rename from devlog/_plan/260831_aside_client_and_integrations_ux/002_registration_checklist.md rename to devlog/_fin/260831_aside_client_and_integrations_ux/002_registration_checklist.md diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/003_integrations_ux_diagnosis.md b/devlog/_fin/260831_aside_client_and_integrations_ux/003_integrations_ux_diagnosis.md similarity index 100% rename from devlog/_plan/260831_aside_client_and_integrations_ux/003_integrations_ux_diagnosis.md rename to devlog/_fin/260831_aside_client_and_integrations_ux/003_integrations_ux_diagnosis.md diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/004_brand_mark_provenance.md b/devlog/_fin/260831_aside_client_and_integrations_ux/004_brand_mark_provenance.md similarity index 100% rename from devlog/_plan/260831_aside_client_and_integrations_ux/004_brand_mark_provenance.md rename to devlog/_fin/260831_aside_client_and_integrations_ux/004_brand_mark_provenance.md diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/005_remaining_marks_provenance.md b/devlog/_fin/260831_aside_client_and_integrations_ux/005_remaining_marks_provenance.md similarity index 100% rename from devlog/_plan/260831_aside_client_and_integrations_ux/005_remaining_marks_provenance.md rename to devlog/_fin/260831_aside_client_and_integrations_ux/005_remaining_marks_provenance.md diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/010_wp2_aside_backend.md b/devlog/_fin/260831_aside_client_and_integrations_ux/010_wp2_aside_backend.md similarity index 100% rename from devlog/_plan/260831_aside_client_and_integrations_ux/010_wp2_aside_backend.md rename to devlog/_fin/260831_aside_client_and_integrations_ux/010_wp2_aside_backend.md diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/020_wp3_aside_gui.md b/devlog/_fin/260831_aside_client_and_integrations_ux/020_wp3_aside_gui.md similarity index 100% rename from devlog/_plan/260831_aside_client_and_integrations_ux/020_wp3_aside_gui.md rename to devlog/_fin/260831_aside_client_and_integrations_ux/020_wp3_aside_gui.md diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/030_wp4_history_redesign.md b/devlog/_fin/260831_aside_client_and_integrations_ux/030_wp4_history_redesign.md similarity index 100% rename from devlog/_plan/260831_aside_client_and_integrations_ux/030_wp4_history_redesign.md rename to devlog/_fin/260831_aside_client_and_integrations_ux/030_wp4_history_redesign.md diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/040_wp5_brand_marks.md b/devlog/_fin/260831_aside_client_and_integrations_ux/040_wp5_brand_marks.md similarity index 100% rename from devlog/_plan/260831_aside_client_and_integrations_ux/040_wp5_brand_marks.md rename to devlog/_fin/260831_aside_client_and_integrations_ux/040_wp5_brand_marks.md diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/050_wp6_stacked_prs.md b/devlog/_fin/260831_aside_client_and_integrations_ux/050_wp6_stacked_prs.md similarity index 100% rename from devlog/_plan/260831_aside_client_and_integrations_ux/050_wp6_stacked_prs.md rename to devlog/_fin/260831_aside_client_and_integrations_ux/050_wp6_stacked_prs.md diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/060_wp7_remaining_marks.md b/devlog/_fin/260831_aside_client_and_integrations_ux/060_wp7_remaining_marks.md similarity index 100% rename from devlog/_plan/260831_aside_client_and_integrations_ux/060_wp7_remaining_marks.md rename to devlog/_fin/260831_aside_client_and_integrations_ux/060_wp7_remaining_marks.md diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/070_wp8_integration_marks.md b/devlog/_fin/260831_aside_client_and_integrations_ux/070_wp8_integration_marks.md similarity index 100% rename from devlog/_plan/260831_aside_client_and_integrations_ux/070_wp8_integration_marks.md rename to devlog/_fin/260831_aside_client_and_integrations_ux/070_wp8_integration_marks.md diff --git a/devlog/_plan/260831_aside_client_and_integrations_ux/080_wp9_conflict_overwrite.md b/devlog/_fin/260831_aside_client_and_integrations_ux/080_wp9_conflict_overwrite.md similarity index 100% rename from devlog/_plan/260831_aside_client_and_integrations_ux/080_wp9_conflict_overwrite.md rename to devlog/_fin/260831_aside_client_and_integrations_ux/080_wp9_conflict_overwrite.md diff --git a/devlog/_fin/260831_aside_client_and_integrations_ux/090_outcome.md b/devlog/_fin/260831_aside_client_and_integrations_ux/090_outcome.md new file mode 100644 index 0000000000..dee794dd73 --- /dev/null +++ b/devlog/_fin/260831_aside_client_and_integrations_ux/090_outcome.md @@ -0,0 +1,62 @@ +# Outcome + +Unit closed 2026-08-31. Every phase in this unit is on `dev`. + +| Phase | Doc | PR | On `dev` as | +|---|---|---|---| +| wp1 | 000 | -- | docs only | +| wp2 | 010 | #3047 | `8c1294828` -- Aside export client + registry | +| wp3 | 020 | #3048 | `93b7ee80a` -- Aside GUI surface, nine locales | +| wp4 | 030 | #3050 | `efa2ba5ad` -- rollback journal stops flooding | +| wp5 | 040 | #3049 | `704d0d91a` -- brand marks for the export clients | +| wp6 | 050 | -- | the stacked chain itself | +| wp7 | 060 | #3082 | `44b4de39d` -- the last three marks | +| wp8 | 070 | #3083 | `d86ec3e03` -- marks on every surface | +| wp9 | 080 | #3084 | `2a90cdaa9` -- conflict overwrite | + +#3065 (`7853e8e05`) and #3074 landed alongside wp5: Aside's own mark plus the +first version of the single-ink rule, and the property guards the pair relies on. + +wp7 through wp9 were appended after the original six, planned in #3081 +(`873d08e63`). The unit grew because the page repair exposed what the marks work +had left behind: three clients still drawing a monogram, and marks present on the +API-keys rows but nowhere else on the page that names the same clients. + +## What the plan got wrong + +**A mark's absence upstream is not the same as its absence in a repository.** +060 assumed each of the three vendors publishes an SVG somewhere findable. None +did in usable form: the Hermes repo favicon is 113 bytes of `<text>`, the +MiniMax docs asset is a 129x32 wordmark, and Gajae Code has no vector anywhere. +Two of the three marks are traced from raster art, which the plan did not +anticipate and which the README now records per asset. + +**Masking is a decision about trademark, not about ink count.** 040 framed the +mask set as "single-ink logos", which would have swept in `openai.svg` (one fill, +but that fill is OpenAI's brand green) and `grok.svg` (genuinely neutral, but +xAI's published file with a literal fill). Both stay images. The rule in +`integration-marks.ts` is now derived from one id-keyed set with the two +exclusions argued in place, rather than restated per surface. + +**A guard can pass for the wrong reason.** 080 listed a stranding test for the +forced `foreign-edit` path. Its first implementation passed with the fix removed, +because every path the old record owned was also one the new contribution writes, +so dropping the old fragments changed nothing observable. It was rewritten around +a layout the new write does not cover -- which is what an upgrade actually leaves +behind -- and only then failed when falsified. + +**Three declarations of one union had no guard.** 080 did not notice that +`OperationKind` is declared in `src/integrations/journal.ts`, restated in the +management envelope and restated again in the GUI adapter, with no import between +them. The doc comment in `journal.ts` claimed a test asserted the three agree. +None did, so a kind added in one place rendered as a raw i18n key with no type +error anywhere. `tests/integrations-journal.test.ts` now reads all three. + +## Verification as merged + +CI green on each PR head, including the unsharded macOS job, before every admin +squash merge. Two flakes surfaced and were diagnosed rather than absorbed: +`tests/install-scripts.test.ts` restart-helper on #3082, and +`tests/shutdown-launcher.test.ts` SIGHUP health-wait on #3083 -- the latter's +SIGINT and SIGTERM siblings passed in the same run, and PR #3061 is already open +for it. Neither file is touched by this unit. From 0cc73411aec36699aa30e98156a685665d8d8e5b Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 00:33:32 +0900 Subject: [PATCH 08/42] fix(gui): stop the Grok mark disappearing on the dark theme (#3086) * fix(gui): stop the Grok mark disappearing on the dark theme The Grok mark is one #000000 fill on transparency, drawn as an <img>. On the dark card surface (rgb(48,48,48)) that measures about 1.9:1 -- a zoomed capture shows the glyph is very nearly gone. It has been that way since the mark landed. The previous pass saw the contrast problem and declined to fix it, on the grounds that masking would be "editing someone else's mark". That reasoning does not survive contact with what masking is. The file is not modified: it is read as a shape and painted in the surrounding text color, which is how xAI renders it on their own dark surfaces. Flattening a palette is not a risk here either, because there is exactly one ink to flatten. Measured after: 11.17:1 dark, 17.67:1 light. Grok is a native row, not an export client, so MONOCHROME_CLIENT_MARKS is keyed by a type that structurally cannot hold its asset. Hence MASKED_NATIVE_MARKS rather than an entry there. The reason this needed a fix at all is that nothing forced a near-black silhouette INTO the mask set. The existing rule guards one direction only: it stops a multi-color asset being flattened. The new guard measures relative luminance of every single-ink, near-neutral mark and fails if an extreme one is drawn as an image -- exactly the bug that already shipped once for prime, opencode and kimi. It found grok on its first run. openai.svg still stays an image, and the distinction is not neutrality in the abstract: #10A37F IS OpenAI's brand, so repainting it loses information a reader uses to identify the mark. #000000 carries none. Verification: 61 pass across four GUI files. Three falsifications -- reverting grok to unmasked (2 red, including the new guard), masking openai (2 red), masking the multi-color Claude mark (1 red). Rendered contrast measured at 1440px in both schemes rather than reasoned about. * docs(gui): record how each mark is painted, not only where it came from The provider-icons README documented provenance for every mark and said nothing about how any of them is drawn. That is the fact that actually broke: grok.svg sat unmasked at roughly 1.9:1 on the dark card, and the reasoning for leaving it an image lived only in a code comment that was wrong about what masking does. Adds a "How a mark is painted" section stating the two modes and arguing every non-obvious case by name -- grok and hermes masked, openai and dsh not despite being single-ink, minimax and gajae not because masking would flatten them, and the three that already shipped broken (prime white-on-transparent, opencode #211E1E, kimi #1A1A1A). The guard checks the section exists and that every masked mark is named in it. A mask added without a line there is a decision nobody can review, and "it looked monochrome" is exactly the reasoning that needs writing down. It caught four undocumented masks on its first run. Verification: 17 pass across the two mark suites. Falsified by renaming the heading and by deleting the grok line, each one red. Inks read from the files rather than recalled -- prime-agent.svg is #ffffff and aside.svg already paints with currentColor, which the text now says precisely. --- gui/public/provider-icons/README.md | 47 +++++++++++++ gui/src/components/integration-marks.ts | 48 +++++++++----- gui/tests/client-marks-assets.test.ts | 31 +++++++++ gui/tests/integration-marks.test.ts | 88 +++++++++++++++++++++++-- 4 files changed, 194 insertions(+), 20 deletions(-) diff --git a/gui/public/provider-icons/README.md b/gui/public/provider-icons/README.md index fa54029a32..a4346b2a4f 100644 --- a/gui/public/provider-icons/README.md +++ b/gui/public/provider-icons/README.md @@ -93,3 +93,50 @@ at 20px. is 292 px and a fixed area floor would have dropped it — it is the visor green, which is the feature that makes the character recognizable, so the floor is a fraction of the opaque area instead. + +## How a mark is painted + +Provenance is not the only fact that has to survive a handoff. Every mark is +drawn one of two ways, and picking wrong makes a logo vanish rather than look +slightly off: + +- **image** — the `<svg>` is rendered as-is, keeping its own colors. Correct for + anything multi-color, and for a single ink that *is* the brand. +- **mask** — the file is used as a shape and filled with the surrounding text + color, so it follows the theme. Correct for a neutral silhouette, which would + otherwise be invisible against one of the two surfaces. + +The set lives in `gui/src/components/integration-marks.ts`. It is derived from +`MONOCHROME_CLIENT_MARKS` for export clients, plus `MASKED_NATIVE_MARKS` for rows +that have no export client to be keyed by. + +Decisions that are not obvious from looking at the file: + +- `grok.svg` **is masked.** One `#000000` fill on transparency measured about + 1.9:1 on the dark card surface (`rgb(48,48,48)`) — effectively gone. Masking + does not modify xAI's file; it reads it as a shape, which is how xAI renders it + on their own dark surfaces. 11.17:1 dark and 17.67:1 light afterwards. +- `openai.svg` **is not masked**, despite also being a single fill. That fill is + #10A37F, OpenAI's brand green, and repainting it discards information a reader + uses to identify the mark. Neutrality is the test, not ink count. +- `deepseek-harness.svg` **is not masked** for the same reason: #4d6bfe is + DeepSeek blue. Its dark-theme contrast is adequate; if it ever is not, the fix + is a surface change, not a repaint. +- `hermes-agent.svg` **is masked.** The trace is one near-black path, so it is + invisible on `#0d1117` untinted. Nothing about the Hermes brand is carried by + that particular black. +- `minimax.svg` and `gajae-code.svg` **are not masked.** A gradient wave and a + seven-layer mascot respectively; masking would flatten both to one ink. +- `prime-agent.svg` **is masked.** White on transparency, so as an image it was + invisible in light mode. This one shipped broken. +- `opencode.svg` (#211E1E) and `kimi-color.svg` (#1A1A1A) **are masked.** Both + near-black single inks, invisible in dark mode as images. Both shipped broken + too, which is what established the rule. +- `aside.svg` **is masked.** It already paints with `currentColor`, so it would + follow the theme either way; masking keeps it consistent with the other + silhouettes rather than depending on inherited color. + +Both directions are enforced in `gui/tests/integration-marks.test.ts`, including a +luminance check that fails any single-ink near-neutral mark left as an image. That +direction was missing until it caught `grok`; the same class of defect had already +shipped once for `prime`, `opencode` and `kimi`. diff --git a/gui/src/components/integration-marks.ts b/gui/src/components/integration-marks.ts index ff34c0faf7..0bde194227 100644 --- a/gui/src/components/integration-marks.ts +++ b/gui/src/components/integration-marks.ts @@ -58,26 +58,44 @@ export const INTEGRATION_MARKS: Record<OverviewClientId, string | null> = { }; /** - * Assets whose artwork is one neutral ink, so the ink has to come from the theme. +* Assets whose artwork is one neutral ink, so the ink has to come from the theme. +* + * The export-client half is derived from `MONOCHROME_CLIENT_MARKS` rather than + * restated, because two hand-maintained lists of the same fact drift. +* + * `openai.svg` looks like it belongs here and does not: it is a single fill, but + * that fill is #10A37F -- OpenAI's brand green. Masking repaints a trademark in + * the theme's text color, the same reason `dsh` stays an image despite being + * single-ink. + */ +/** + * The neutral-ink marks that belong to no export client. + * + * `MONOCHROME_CLIENT_MARKS` is keyed by `ExportClientId`, so it structurally + * cannot carry a native row's asset. Grok is a native row, which is the only + * reason its mark needs a second home rather than an entry there. * - * Derived from `MONOCHROME_CLIENT_MARKS` rather than restated, because two - * hand-maintained lists of the same fact drift. Nothing is added on top of it, - * and the two candidates that look like they belong here do not: + * `grok.svg` is one `#000000` fill on transparency. Measured on the dark card + * surface (`rgb(48,48,48)`) that is about 1.9:1 -- the glyph is very nearly gone, + * which a zoomed capture confirms. An earlier pass left it as an image on the + * grounds that masking would be editing someone else's mark; that reasoning does + * not survive contact with what masking is. The file is not modified. It is read + * as a shape and painted in the surrounding text color, which is how xAI renders + * it on their own dark surfaces. Flattening does not apply either: there is one + * ink to flatten. * - * - `openai.svg` is a single fill, but that fill is #10A37F -- OpenAI's brand - * green. Masking it repaints a trademark in the theme's text color, which is - * the same reason `dsh` stays an image despite being single-ink. - * - `grok.svg` is #000000, a genuine neutral and a real masking candidate. It - * is xAI's published asset with a literal fill, and rewriting that file to - * `currentColor` is a change to someone else's mark rather than a rendering - * choice, so it stays an image here. + * `openai.svg` still does not belong here, and the distinction is not neutrality + * in the abstract -- it is that #10A37F IS the brand. Repainting it loses + * information a reader uses to identify the mark. #000000 carries none. */ -export const MASKED_MARKS: ReadonlySet<string> = new Set( - [...MONOCHROME_CLIENT_MARKS].map(clientId => CLIENT_MARKS[clientId]).filter((src): src is string => src !== undefined), -); +const MASKED_NATIVE_MARKS: readonly string[] = [NATIVE_MARKS.grok]; + +export const MASKED_MARKS: ReadonlySet<string> = new Set([ + ...[...MONOCHROME_CLIENT_MARKS].map(clientId => CLIENT_MARKS[clientId]).filter((src): src is string => src !== undefined), + ...MASKED_NATIVE_MARKS, +]); /** The mark for a row, or null when it has none. */ export function markFor(clientId: OverviewClientId): string | null { return INTEGRATION_MARKS[clientId]; } - diff --git a/gui/tests/client-marks-assets.test.ts b/gui/tests/client-marks-assets.test.ts index 4c8600366a..d7367df59d 100644 --- a/gui/tests/client-marks-assets.test.ts +++ b/gui/tests/client-marks-assets.test.ts @@ -120,6 +120,37 @@ test("every export client has a mark", () => { expect(monogram).toEqual([]); }); +/* + * Provenance is documented; how a mark is PAINTED was not, and that is the fact + * that actually broke. `grok.svg` sat unmasked at roughly 1.9:1 on the dark card + * because the reasoning for leaving it an image lived only in a code comment that + * was wrong about what masking does. + * + * This pins the README section rather than the decision itself -- the decision is + * enforced in integration-marks.test.ts. What it prevents is the next person + * re-litigating a case that was already measured, which is how grok stayed broken + * through two passes over the same file. + */ +test("the README explains how marks are painted, not only where they came from", () => { + const readme = readFileSync(join(PUBLIC_DIR, "provider-icons", "README.md"), "utf8"); + expect(readme).toContain("## How a mark is painted"); + + /* + * Every masked mark has to be argued for by name. A mark added to the mask set + * without a line here is a decision nobody can review, and "it looked + * monochrome" is precisely the reasoning that needs to be written down. + */ + const section = readme.slice(readme.indexOf("## How a mark is painted")); + const unexplained = [...MONOCHROME_CLIENT_MARKS] + .map(clientId => CLIENT_MARKS[clientId]!.split("/").pop()!) + .filter(file => !section.includes(file)); + expect(unexplained).toEqual([]); + + // The two cases a reader is most likely to get backwards. + expect(section).toContain("grok.svg"); + expect(section).toContain("openai.svg"); +}); + /* * Two of the newest marks are traced from raster sources, which is a different * provenance claim from "fetched" and the one a reader is most likely to doubt. diff --git a/gui/tests/integration-marks.test.ts b/gui/tests/integration-marks.test.ts index fa42cd45ce..2d8c93bd9a 100644 --- a/gui/tests/integration-marks.test.ts +++ b/gui/tests/integration-marks.test.ts @@ -80,11 +80,23 @@ test("a single-ink asset whose ink is a brand color is not masked", () => { * MASKED_MARKS is derived from MONOCHROME_CLIENT_MARKS rather than restated, and * this is what makes that derivation observable: the two must describe the same * assets. A second hand-maintained list would drift, and the drift would be a - * mark masked on one surface and not on another. - */ -test("the masked set is exactly the monochrome client marks", () => { - const expected = [...MONOCHROME_CLIENT_MARKS].map(id => CLIENT_MARKS[id]).filter(Boolean).sort(); - expect([...MASKED_MARKS].sort()).toEqual(expected as string[]); +* mark masked on one surface and not on another. +*/ +test("the masked set is the monochrome client marks plus the native exceptions", () => { + const fromClients = [...MONOCHROME_CLIENT_MARKS].map(id => CLIENT_MARKS[id]).filter(Boolean) as string[]; + /* + * The only additions allowed are marks belonging to rows with no export client, + * since MONOCHROME_CLIENT_MARKS is keyed by ExportClientId and structurally + * cannot hold them. Named here so a mask added for any OTHER reason -- which is + * how the two lists would start drifting -- fails. + */ + const nativeExceptions = ["/provider-icons/grok.svg"]; + expect([...MASKED_MARKS].sort()).toEqual([...fromClients, ...nativeExceptions].sort()); + + // Every export client's masking decision still comes from the derived set only. + const clientMarks = new Set(Object.values(CLIENT_MARKS).filter(Boolean) as string[]); + const maskedClientMarks = [...MASKED_MARKS].filter(src => clientMarks.has(src)).sort(); + expect(maskedClientMarks).toEqual([...fromClients].sort()); }); /* @@ -99,3 +111,69 @@ test("every client tab has a mark", () => { expect(bare.map(tab => tab.id)).toEqual([]); }); +/** + * Relative luminance, so "would this vanish?" is a measurement rather than a + * judgement about a hex string. + */ +function luminance(hex: string): number { + const raw = hex.replace("#", ""); + const full = raw.length === 3 ? [...raw].map(c => c + c).join("") : raw.slice(0, 6); + const channel = (pair: string): number => { + const v = parseInt(pair, 16) / 255; + return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; + }; + return 0.2126 * channel(full.slice(0, 2)) + + 0.7152 * channel(full.slice(2, 4)) + + 0.0722 * channel(full.slice(4, 6)); +} + +/* + * The rule the other direction, and the one that actually shipped broken. + * + * `no multi-color asset is masked` stops a brand palette being flattened. Nothing + * stopped the opposite: a mark that is one near-black ink drawn as an <img>, which + * is invisible against a dark surface. That is not hypothetical -- prime, opencode + * and kimi each shipped that way, and the fix was to mask them. Removing one from + * MONOCHROME_CLIENT_MARKS today reintroduces it silently, because an invisible + * <img> still renders, still has layout, and every existing assertion still holds. + * + * A near-neutral ink is one whose channels are close together: #0d1117 qualifies, + * OpenAI's #10a37f does not, which is what keeps this from arguing with the + * brand-color test above. + */ +test("a single-ink mark that would vanish against one theme is masked", () => { + const vanishing: string[] = []; + for (const src of new Set(Object.values(INTEGRATION_MARKS))) { + if (src === null) continue; + const body = bodyOf(src); + if (/<(linearGradient|radialGradient)[\s>]/.test(body)) continue; + const inks = [...inksOf(body)]; + if (inks.length !== 1) continue; + const hex = inks[0]!.replace("#", ""); + const full = hex.length === 3 ? [...hex].map(c => c + c).join("") : hex.slice(0, 6); + const channels = [full.slice(0, 2), full.slice(2, 4), full.slice(4, 6)].map(p => parseInt(p, 16)); + const neutral = Math.max(...channels) - Math.min(...channels) <= 24; + if (!neutral) continue; + // Only the extremes vanish. A mid grey is legible on both surfaces. + const l = luminance(inks[0]!); + if (l > 0.12 && l < 0.75) continue; + if (!MASKED_MARKS.has(src)) vanishing.push(`${src}: ${inks[0]} would be invisible in one theme`); + } + expect(vanishing).toEqual([]); +}); + +/* + * The three marks added last, pinned by how they are PAINTED rather than by + * existing. Rendered measurement at 1440px: hermes flips ink with the theme + * (17.67:1 on the light card, 11.17:1 on the dark one), while gajae and minimax + * keep their own colors in both. This is the cheap version of that check. + */ +test("the three newest marks are painted the way their artwork requires", () => { + // A traced single-ink silhouette: invisible as an image on a dark surface. + expect(MASKED_MARKS.has("/provider-icons/hermes-agent.svg")).toBe(true); + // Multi-color artwork: masking would flatten a mascot and a gradient wave. + expect(MASKED_MARKS.has("/provider-icons/gajae-code.svg")).toBe(false); + expect(MASKED_MARKS.has("/provider-icons/minimax.svg")).toBe(false); + expect(inksOf(bodyOf("/provider-icons/gajae-code.svg")).size).toBeGreaterThan(1); + expect(/<linearGradient[\s>]/.test(bodyOf("/provider-icons/minimax.svg"))).toBe(true); +}); From 3982bb76c6caf7663ebb44a8dafbae14190d2a33 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 00:35:08 +0900 Subject: [PATCH 09/42] docs(devlog): scan the priority-70 backlog for round 2 and lock the roadmap (#3087) Rescans every open issue and bug-labelled PR against a written-down four-axis rubric, and plans the six targets that score >= 70 as one PABCD cycle each. Six enter the train: #3071 (73), #3032 (75), #3026 (75), #3029 (72), #3008 (71), #3019 (70). Sixteen below-bar items are recorded with components so the next scan does not re-litigate them, and #3068 is suppressed as a duplicate of #3071. The scan corrected several assumptions the titles suggested. #1527 and #3070 are already fixed on dev; #3059 asserts an unmount path the tree cannot produce; PRs #3040, #3041 and #3067 each found a real defect and proposed a worse remedy; PRs #3063 and #3038 claim regressions that pass against unfixed source. Eleven adversarial review rounds, all findings verified in-tree before amendment. Findings per round: 9, 5, 4, 4, 3, 2, 3, 3, 1, 0. Round 11 passed. Round 1 found nine holes in the plan; after that the defects were in the fixes, which is what 002-011 mostly record. --- .../260831_prio70_train_round2/000_plan.md | 133 ++++++ .../001_research_scan_evidence.md | 138 ++++++ .../002_audit_round1_synthesis.md | 119 ++++++ .../003_audit_round2_synthesis.md | 60 +++ .../004_audit_round3_synthesis.md | 93 ++++ .../005_audit_round4_synthesis.md | 88 ++++ .../006_audit_round5_synthesis.md | 71 ++++ .../007_audit_round6_synthesis.md | 53 +++ .../008_audit_round7_synthesis.md | 56 +++ .../009_audit_round8_synthesis.md | 58 +++ .../010_audit_round9_synthesis.md | 54 +++ .../010_wp1_web_search_query_field.md | 77 ++++ .../011_audit_round10_synthesis.md | 53 +++ .../020_wp2_spill_disk_budget.md | 148 +++++++ .../030_wp3_forked_rollout_restore.md | 396 ++++++++++++++++++ .../040_wp4_terminal_short_window.md | 179 ++++++++ .../050_wp5_stop_failure_taxonomy.md | 137 ++++++ .../060_wp6_wham_401_refresh.md | 139 ++++++ .../260831_prio70_train_round2/070_outcome.md | 130 ++++++ 19 files changed, 2182 insertions(+) create mode 100644 devlog/_plan/260831_prio70_train_round2/000_plan.md create mode 100644 devlog/_plan/260831_prio70_train_round2/001_research_scan_evidence.md create mode 100644 devlog/_plan/260831_prio70_train_round2/002_audit_round1_synthesis.md create mode 100644 devlog/_plan/260831_prio70_train_round2/003_audit_round2_synthesis.md create mode 100644 devlog/_plan/260831_prio70_train_round2/004_audit_round3_synthesis.md create mode 100644 devlog/_plan/260831_prio70_train_round2/005_audit_round4_synthesis.md create mode 100644 devlog/_plan/260831_prio70_train_round2/006_audit_round5_synthesis.md create mode 100644 devlog/_plan/260831_prio70_train_round2/007_audit_round6_synthesis.md create mode 100644 devlog/_plan/260831_prio70_train_round2/008_audit_round7_synthesis.md create mode 100644 devlog/_plan/260831_prio70_train_round2/009_audit_round8_synthesis.md create mode 100644 devlog/_plan/260831_prio70_train_round2/010_audit_round9_synthesis.md create mode 100644 devlog/_plan/260831_prio70_train_round2/010_wp1_web_search_query_field.md create mode 100644 devlog/_plan/260831_prio70_train_round2/011_audit_round10_synthesis.md create mode 100644 devlog/_plan/260831_prio70_train_round2/020_wp2_spill_disk_budget.md create mode 100644 devlog/_plan/260831_prio70_train_round2/030_wp3_forked_rollout_restore.md create mode 100644 devlog/_plan/260831_prio70_train_round2/040_wp4_terminal_short_window.md create mode 100644 devlog/_plan/260831_prio70_train_round2/050_wp5_stop_failure_taxonomy.md create mode 100644 devlog/_plan/260831_prio70_train_round2/060_wp6_wham_401_refresh.md create mode 100644 devlog/_plan/260831_prio70_train_round2/070_outcome.md diff --git a/devlog/_plan/260831_prio70_train_round2/000_plan.md b/devlog/_plan/260831_prio70_train_round2/000_plan.md new file mode 100644 index 0000000000..ebee71a0d9 --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/000_plan.md @@ -0,0 +1,133 @@ +# 260831 round 2 — priority-70+ train: what is left after the entitlement stack + +Round 1 of this train closed five units and they are all on `dev`: #3022 (PR #3035), +#3011 (PR #3044), #3023 (PR #3054), tri-state entitlement authority (#3057) and the +entitlement diagnostic (#3058). This unit is the rescan that follows, taken at +2026-08-31T12:0x KST against `dev` = `5cec0a33e`. + +## The rubric + +Four axes, 0-20 each, 80 total. An item enters the train at 70. + +| axis | what it measures | +| --- | --- | +| blast radius | how many users and surfaces the defect reaches | +| data, credential or durability risk | state loss, credential corruption, broken durable persistence | +| reproducibility and evidence quality | a concrete repro, a file:line trace, or a reporter measurement | +| shippability | fixable in a bounded, testable diff on this tree now | + +Round 1 used the same four axes but never wrote them down, which is why #3022 was +"78/80" in a table nobody could recheck. The scores below are recheckable. + +## Method + +Four read-only `gpt-5.6-sol` high-effort lanes, split by cluster so no two lanes +shared a verdict: tools/proxy issues, platform/Windows issues, account-pool/catalog +issues, and the open bug-labelled PRs. Every lane read the real issue body with +`gh`, then verified the claim against the tree. Where a lane and the tree disagreed, +the tree won and the disagreement is recorded. + +The PR lane deliberately overlapped the issue lanes on #3026/#3056 and #3071/#3069. +Both pairs agreed independently, which is the only reason those two scores are quoted +without a third look. + +## The >=70 set + +Components are blast radius / data-durability / evidence / shippability, each 0-20. +They are printed because a total alone is not recheckable — wp6 sits exactly on the +threshold, so its admission cannot be audited without them (audit `002`, blocker 9). + +| wp | target | blast | data | evid | ship | total | doc | disposition | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| wp0 | this roadmap | — | — | — | — | — | `000`-`00x` | — | +| wp1 | #3071 Console Go `web_search_call` missing `query` | 15 | 18 | 20 | 20 | **73** | `010` | merge PR #3069 after rebase + review | +| wp2 | #3032 response-spill aggregate disk budget | 18 | 20 | 20 | 17 | **75** | `020` | repair the PR's Windows publication gap, then land | +| wp3 | #3026 forked-rollout history restore | 19 | 20 | 19 | 17 | **75** | `030` | PR #3056 is half a fix; complete it | +| wp4 | #3029 exhausted 5-hour pool account stays selected | 18 | 15 | 19 | 20 | **72** | `040` | reimplement; no PR targets it | +| wp5 | #3008 update aborts after a history-only stop failure | 18 | 17 | 18 | 18 | **71** | `050` | PR #3040 fails open; reimplement | +| wp6 | #3019 WHAM 401 bypasses stored-token refresh | 17 | 17 | 18 | 18 | **70** | `060` | carry PR #3020's core, drop the rest | + +Where the load-bearing components come from: wp2's 20 on data is a measured ENOSPC +(6.8 GiB in 44 minutes), wp3's 20 is recovery requiring manual DB surgery, and wp1's 18 +is a stored item that poisons every later turn in the thread. wp6's 18 on shippability +is the one to argue with if anything: the primitive exists, but credential lineage is +security-sensitive, and audit round 1 found the first design got it wrong. + +Ordering is by independence, not by score. wp1 is one function and lands first. wp2 and +wp3 both touch durable state but in unrelated files. wp5 and wp6 are independent of +everything else. + +wp6 was originally declared a stacked child of wp4 because both were called +"account-pool". Audit round 1 (`002`, blocker 7) disproved it: wp4 changes +`src/codex/routing.ts` scoring, wp6 changes auth/token recovery, and PR #3020 touches no +`routing.ts`. All six phases base directly on `dev`. + +## Below the bar, and why + +These are real defects. They are recorded so the next scan does not re-litigate them. + +| item | score | why it misses | +| --- | --- | --- | +| #3009 Windows 20s repair deadline | 69/80 | PR #3039 is substantively right; it needs a rebase and one restored assertion, not a train slot | +| #3064 non-ASCII scheduler path | 68/80 | PR #3067 relocated the defect correctly but its `[^\\/]*` relaxation accepts arbitrary ASCII in a path-ownership check | +| #3024 dated configured models dropped | 66/80 | PR #3034 fixes the safe half; PR #3041's reverse inference can resurrect retired ids without callability evidence | +| #2999 native-main publication race | 64/80 | 20/20 on credential risk, but PR #3000's musl-unsafe FFI and late-rotation discard put shippability at 15 | +| #3066 routed private metadata | 62/80 | availability only; the compact lane gap is a follow-up, and #3038 is a worse duplicate | +| #3051 Cursor HTTP/2 pre-header EOF | 62/80 | PR #3052 is correct and narrow — merge it on the ordinary queue | +| #3021 encrypted subagent MESSAGE | 59/80 | one occurrence, no ciphertext captured | +| #3078 start shadow guard | 59/80 | targets `main`, and its test does not typecheck | +| #3053 sidecar catalog modalities | 47/80 | no linked user report | +| #3063 combo compact failover | 42/80 | the guard is right, the claimed regression is vacuous | +| #1419 Bun SIGTRAP | 39/80 | reported against Bun 1.3.14; `dev` ships 1.4.0 and there is no current repro | +| #1527 Cursor large-context collapse | 37/80 | the four named mechanisms are all fixed on `dev` | +| #3059 restore-dialog focus | 22/80 | the tree contradicts the claimed unmount path | +| #3070 usage keeps decreasing | 51/80 | model filtering landed in `b68edc077`; the residual is a GUI control gap | +| #2813 Luna Reserve disables routed rows | 48/80 | no OpenCodex Reserve gate exists; needs a request-boundary dump first | +| #3068 | duplicate | same author, body, and repro as #3071 — close in its favor | + +Scores for the below-bar rows are recorded per item in `001`; they are not repeated here +because none of them is a threshold call. + +## Audit + +Ten adversarial `gpt-5.6-sol` rounds, all FAIL, every blocker verified in-tree and +amended rather than argued with. `002`-`011` record them. From round 8 the A-phase +reviewer is resumed rather than respawned, so a closure round judges its own findings. + +Round 1 found nine holes in the original plan, two of which invalidated a premise it was +built on: wp3's claim that OpenCodex never writes `has_user_event`, and wp5's assumption +that there is one updater. Round 2 read a stale index (a staging error of mine) and still +produced two real refinements. Round 3 found five, **four of them inside round 1's own +amendments**. Round 4 found four more, including the one that matters most: wp3's +provenance field would have refused every manifest already on disk, which is the exact +population #3026 is about. + +Round 5 cleared wp2 and wp5. Round 6 cleared wp4. Round 7 found zero blockers in wp1, +wp2, wp4, wp5 and wp6 — every finding since has been wp3, which is where the difficulty +in this train actually sits. Round 9 confirmed wp3's four-shape partition disjoint and +moved the remaining questions to transitions between shapes; round 10 reduced those to a +single cell that no durable fact can decide, which refuses by design. + +The pattern worth naming: after round 1, the defects stopped being in the plan and +started being in the fixes. A remedy can be right about the defect it names and wrong +about its own boundary, and that failure mode does not announce itself — all four of +round 3's would have shipped as working code with passing tests. + +wp3 alone absorbed four consecutive rounds on one predicate, which is what finally +forced the right answer: stop hand-writing a pattern that recognizes OpenCodex's own +write, and compute the expected post-image from the same rules the routing statements +use (`007`). + +## Verification constraint (carried from round 1, user-imposed) + +No local full suites. Focused `bun test <file>` locally; every suite, typecheck and +privacy scan runs on `ssh lidge` at the exact pushed head. Every completion claim +carries a receipt: command, host, exit code, pass/fail/skip counts. Every regression +test is driven RED against pre-fix source and that red result is recorded. + +## Delivery + +Stacked in this worktree per `DEV-STACK-01` — one branch per work-phase, cherry-pick +and squash rather than a dispatched merge worktree. Pushed `--no-verify` because the +pre-push hook runs the forbidden local suite. Merge into `dev` is authorized; nothing +beyond `dev` is. diff --git a/devlog/_plan/260831_prio70_train_round2/001_research_scan_evidence.md b/devlog/_plan/260831_prio70_train_round2/001_research_scan_evidence.md new file mode 100644 index 0000000000..67c90bc0ad --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/001_research_scan_evidence.md @@ -0,0 +1,138 @@ +# 001 — the rescan, with the evidence each verdict rests on + +Four read-only lanes, `gpt-5.6-sol` at high effort, against `dev` = `5cec0a33e`. +Every file:line below was re-read by the main session before it was written here. + +## What the lanes changed about my prior beliefs + +Three findings inverted an assumption I would otherwise have shipped on. + +**#3071 is not a one-way missing-field bug.** The tree's own comment at +`src/bridge.ts:149-168` argues the batch omission of singular `query` is +*load-bearing*: codex-rs renders "<first> ..." only when `query` is absent and +`queries.len() > 1`. So the fix is not "add the field" — it is a deliberate trade of +that plural ellipsis for a strict-validator pass, and PR #3069 makes exactly that +trade in its comment rewrite. Anyone reviewing the diff without reading the old +comment would think a bug was being removed when a rendering behaviour is being sold. + +**PR #3040 (#3008) fails open, and the issue asked it not to.** It proceeds for every +non-zero *or signal-killed* stop whenever PID/runtime files are absent +(PR-head `src/update/index.ts:42-47`), then claims the proxy is stopped without +probing the captured endpoint (`:279-296`). The current abort at +`src/update/index.ts:260` is over-broad, but replacing it with "proceed unless we can +see a proxy" is worse than the defect: an update that replaces package files under a +live server leaves it executing mixed old/new code, which is the exact hazard the +comment at `:245-250` was written to prevent. + +**PR #3056 (#3026) fixes one of the two defects #3026 reports.** The id-aware read is +right — `dev` still calls id-agnostic `readLatestSessionMeta` at +`src/codex/history-provider.ts:552`, `:631` and `:878`, despite already having an +id-aware fold at `:680-688`. But `has_user_event` drift is still rejected +(`:487-500`, `:611-613`) and restore still forces the manifest's value, so the issue +cannot close on that PR alone. + +## Per-item evidence + +### #3071 / #3068 — 73/80, enters as wp1 + +`webSearchAction()` omits singular `query` for batches (`src/bridge.ts:164`). +`backfillWebSearchQueries()` repairs only the opposite direction +(`src/adapters/openai-responses.ts:916`). The reporter's direct replay is decisive: +`{queries:["a","b"]}` 400s, adding `query` returns 200. + +PR #3069 at `5cf5cc1d230` fixes both functions and both assertions +(`tests/bridge.test.ts:1078`, `tests/openai-responses-passthrough.test.ts:1298`) fail +against current `dev`, so the regression is real. Two non-blocking gaps found by the +tools lane: the stale one-way comments at `src/adapters/openai-responses.ts:904-910`, +and `:927-928` should test `typeof action.queries[0] === "string"` because unknown +input items use a loose schema (`src/responses/schema.ts:106`). + +#3068 is the same author, title, body, repro, log and config as #3071. Close it in +favor of #3071; it does not get a second slot. + +### #3032 — 75/80, enters as wp2 + +A measured incident: 6.8 GiB of spill after 44 minutes, with the rate and projection +supplied. Latest PR head `b4d1d2404f` fixes steady-state ordering, reload, periodic +enforcement and deferred-generation accounting — but `spilledResponseBytes()` +(PR-head `src/responses/state.ts:627-651`) counts installed states and deferred +unlinks, not a file being created by `writeResponseSpillDurablyAsync`. That +publication stays live through `:246-300` with up to 256 MiB pending (`:174-179`), so +disk can exceed the advertised 1 GiB cap until ACL publication settles. Six of its +tests are real; none reserves bytes during a blocked Windows publication. + +This is the same subsystem wp3 of round 1 just hardened, so the shutdown-drain +invariants have to survive the change. + +### #3026 — 75/80, enters as wp3 + +Reporter measured 6/389 manifest entries affected, and one rejected entry blocks the +whole manifest (`src/codex/history-provider.ts:586-596`), which wedges every +`ocx stop` and `ocx update`. 20/20 on durability: recovery needs manual DB surgery. + +### #3029 — 72/80, enters as wp4 + +`shortPercent` is preserved as a blocking window (`src/codex/quota.ts:587`) but a +short-only snapshot is forced to unknown (`src/codex/routing.ts:374`), and unknown +then passes headroom (`:1173`) and suppresses auto-switch (`:1604`). The comment at +`routing.ts:374` explains why unknown is right for `shortPercent: 0` — it must not +make an unverified account look emptiest — and that reasoning does not extend to a +terminal 100. The tree tests `shortPercent: 87` as unknown +(`tests/codex-routing.test.ts:129`) and has no terminal short-only case. + +No open PR targets this. PR #3003 throttles failed WHAM priming instead, and is +independently wrong: two pre-WHAM errors lack `quotaProbeSkipped` +(PR-head `src/codex/auth-api.ts:1013-1021`), causing false five-minute suppression. + +### #3008 — 71/80, enters as wp5 + +`src/update/index.ts:250-267` conflates `stop.status`, PID state and runtime state, +which makes its own history warning at `:269-275` unreachable. `handleStop()` sets +failure after history restoration (`src/cli/index.ts:739-748`), i.e. after teardown +already succeeded, so a history-only failure is indistinguishable from a real one. + +### #3019 — 70/80, enters as wp6 + +Account-list quota sends WHAM once and converts any 401 straight into `needsReauth` +(`src/codex/auth-api.ts:971`, `:978`), while the rejected-bearer recovery primitive +already exists at `src/codex/account-store.ts:588`. PR #3020's core sequence is right +(401 → `forceRefreshCodexPoolToken` → one replay) but it is seven files and ~1300 +lines, conflicts with `dev`, and its own full-suite run timed out. Carry the core, +leave the rest. + +## Items whose PRs are correct and should just merge normally + +Not train work, but recording it so the queue is not re-audited: PR #3052 (#3051) is +correct and narrow at `46125ea83`, and PR #3039 (#3009) is substantively correct at +`8573decb79` once rebased with its zero-budget assertion restored. + +## Below-bar component scores + +`000` points here for these, so here they are. Same four axes: blast radius / +data-durability / evidence / shippability. + +| item | blast | data | evid | ship | total | the axis that decides it | +| --- | --- | --- | --- | --- | --- | --- | +| #3009 Windows 20s repair deadline | 18 | 14 | 18 | 19 | 69 | none low — it simply lands one point short, and PR #3039 already carries the fix | +| #3064 non-ASCII scheduler path | 17 | 14 | 18 | 19 | 68 | data 14: installation rolls back, no durable user data lost | +| #3024 dated configured models dropped | 15 | 12 | 20 | 19 | 66 | data 12: models disappear from a persisted view, nothing is destroyed | +| #2999 native-main publication race | 12 | 20 | 17 | 15 | 64 | blast 12: concurrent native-main refresh only, despite the maximum credential risk | +| #3066 routed private metadata | 18 | 5 | 20 | 19 | 62 | data 5: availability, not corruption | +| #3038 (same defect as #3066) | 18 | 5 | 20 | 19 | 62 | duplicate of the above with a worse implementation | +| #3051 Cursor HTTP/2 pre-header EOF | 14 | 8 | 20 | 20 | 62 | data 8: a stale catalog | +| #3021 encrypted subagent MESSAGE | 14 | 18 | 9 | 18 | 59 | evidence 9: one occurrence, no ciphertext captured | +| #3078 start shadow guard | 13 | 10 | 18 | 18 | 59 | targets `main`, and its test does not typecheck | +| #3070 usage keeps decreasing | 12 | 11 | 8 | 20 | 51 | evidence 8: a screenshot with no route or config correlation | +| #3053 sidecar catalog modalities | 12 | 2 | 15 | 18 | 47 | data 2: a client-side attachment block | +| #2813 Luna Reserve disables routed rows | 18 | 4 | 18 | 8 | 48 | ship 8: no OpenCodex control point identified | +| #3063 combo compact failover | 10 | 3 | 11 | 18 | 42 | evidence 11: source trace only, and the claimed regression is vacuous | +| #1419 Bun SIGTRAP | 18 | 6 | 11 | 4 | 39 | ship 4: no current-runtime repro on the shipped Bun 1.4.0 | +| #1527 Cursor large-context collapse | 14 | 10 | 8 | 5 | 37 | ship 5: the four named mechanisms are already fixed on `dev` | +| #3059 restore-dialog focus | 10 | 2 | 6 | 4 | 22 | evidence 6: the tree contradicts the claimed unmount path | + +`#3068` carries #3071's 73 and is suppressed as a duplicate rather than scored +separately. + +Two of these are worth a second look if the >=70 set ever empties: #3009 at 69 is one +rebase away from done, and #2999 is the only below-bar item scoring 20 on credential +risk — it is held back by shippability, not by severity. diff --git a/devlog/_plan/260831_prio70_train_round2/002_audit_round1_synthesis.md b/devlog/_plan/260831_prio70_train_round2/002_audit_round1_synthesis.md new file mode 100644 index 0000000000..20e52bebdb --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/002_audit_round1_synthesis.md @@ -0,0 +1,119 @@ +# 002 — audit round 1: FAIL, nine blockers + +One adversarial `gpt-5.6-sol` high-effort lane against the nine staged roadmap files. +Verdict FAIL. Every blocker was re-verified in-tree by the main session before it was +accepted; all nine held. Two of them invalidate a premise the plan was built on, which +is the whole reason this gate exists. + +## Blocker 2 — wp3's ownership premise is false + +`030` asserted `has_user_event` is Codex-owned and never written by OpenCodex. +OpenCodex writes it in `routeOpenai`/`routeExec` +(`src/codex/history-provider.ts:1158`) and in legacy recovery (`:1013`, which sets it +to `1` unconditionally). Worse, an existing test *requires* OpenCodex's `0 → 1` +routing write to restore back to `0` (`tests/codex-history-provider.test.ts:261`). + +So "preserve every observed value" would have broken a contract already under test. +The corrected rule is narrower and has to be stated as a direction rather than a +permission: preserve only monotonic `0 → 1` drift, and only while provider and source +still match the untouched original tuple. `has_user_event` stays in the CAS and +readback for OpenCodex post-images, because there OpenCodex really is the writer. + +This is the round-1 lesson repeating in a new place: `005` of the previous unit found +a fix whose justification was a fact about the tree that was not true. The +justification is load-bearing, not decoration. + +## Blocker 1 — wp5 was planning to fix one of two updaters + +`050` changed the Bun updater and the CLI. Dashboard npm updates run through +`bin/ocx.mjs` (dispatched at `src/update/job.ts:428`), and that launcher has its own +independent guard which still aborts on any non-zero stop (`bin/ocx.mjs:346`). The +reported symptom arrives via the dashboard, so the plan as written could have gone +green while the reported path stayed broken. + +The deeper point the auditor made: a TypeScript type cannot cross a subprocess +boundary. A shared "typed stop outcome" is not implementable as a type alone — it needs +a runtime wire representation, i.e. a dedicated exit code or a machine-readable +receipt, and both lanes have to consume it. + +## Blocker 3 — wp2's reservation was one envelope short + +Windows publication can fall back from hard-linking to copying +(`src/responses/spill-store.ts:404`), and during the fallback the destination copy +exists while the temp file still exists and destination ACL hardening is awaited +(`:597`). Peak footprint is therefore two envelopes, not one. Reserving one keeps the +cap violable by exactly the margin the issue is about. + +## Blocker 4 — wp4 could turn a stale reading into a permanent block + +`shortPercent` travels with `shortResetAt` (`src/codex/quota.ts:23-25`), and +`getAccountQuota()` performs no expiry check (`:510`). Partial updates preserve the old +short tuple (`:319`) and disk hydration accepts it for six hours. Scoring 100 from +`shortPercent` alone would keep excluding an account whose five-hour window has since +reset — converting a transient exhaustion into a durable exclusion. + +That inverts the issue. #3029 is a complaint that an exhausted account stays selected; +fixing it by making a recovered account stay excluded is the same bug pointed the other +way. Terminal-short scoring has to be reset-aware. + +## Blocker 5 — generation is not lineage + +`060` claimed keying recovery on credential generation fences concurrent rotation. The +existing primitive's own documentation says otherwise: `forceRefreshCodexPoolToken` +returns `selfRefreshed` precisely because generation alone cannot distinguish this +caller's refresh CAS from somebody else's replacement +(`src/codex/account-store.ts:575-590`). A concurrent reauthentication moves +`G → G+1`, and marking `G+1` spent from the old rejection suppresses the *new* +credential's own recovery. + +The doc was written without reading the comment on the function it planned to call. + +## Blocker 6 — wp1's gap list was incomplete + +PR #3069's head `5cf5cc1d230` still has a stale first summary line saying batches +carry no singular `query` (PR-head `src/bridge.ts:149`), and the head's review state is +`CHANGES_REQUESTED`, not merely "review required". Empty `queries: []` is also +undefined behaviour while the loose input schema admits it. + +## Blocker 7 — a dependency that does not exist + +wp6 was declared a stacked child of wp4 on the theory that both touch the pool quota +path. They do not: wp4 changes `src/codex/routing.ts` scoring, wp6 changes +auth/token recovery, and PR #3020's file set contains no `routing.ts`. wp6 bases +directly on `dev`. No other pair needs ordering. + +A false dependency is not harmless — it serializes two phases that could land +independently and invents a rebase that can only introduce conflict. + +## Blocker 8 — three named test files do not exist + +`tests/responses-spill-store.test.ts`, `tests/codex-quota.test.ts` and +`tests/update.test.ts` were all invented. Confirmed against `ls tests`: the real +neighbours are `tests/responses-state.test.ts`, +`tests/quota-scoring.test.ts` / `tests/routing-policy-pool-quota.test.ts`, and +`tests/update-job.test.ts`. A verification command that cannot run is worse than none, +because it reads as evidence. + +## Blocker 9 — the scores are not recheckable + +`000` claims recheckable scores and then prints only totals. wp6 sits exactly on the +threshold at 70, so its admission cannot be audited at all without components. The +component values exist — the lanes produced them — they just were not carried into the +plan. + +## Two corrections to the plan's own claims (accepted nits) + +- Auto-switch suppression for an already-affined thread happens in + `reevaluateAffinityQuota` (`src/codex/routing.ts:1745`, called at `:1965`), not at + `:1604`. `040`'s branch trace named the wrong line; the behaviour is real. +- The history warning at `src/update/index.ts:269-275` is unreachable on the reported + failed-history path, not globally. A skipped history outcome can still yield overall + stop success while a manifest remains (`src/codex/inject.ts:1652`). + +## What the audit confirmed + +The codex-rs rendering trade in `010` is sound: current codex-rs prefers a non-empty +`query` and produces "<first> ..." only from plural `queries` when `query` is absent. +PR #3040's fail-open diagnosis, PR #3003's two missing pre-WHAM skip facts, PR #3056's +id-aware half-fix and PR #3032's pending-publication gap all re-checked out at their +refreshed heads. diff --git a/devlog/_plan/260831_prio70_train_round2/003_audit_round2_synthesis.md b/devlog/_plan/260831_prio70_train_round2/003_audit_round2_synthesis.md new file mode 100644 index 0000000000..ef096e31b0 --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/003_audit_round2_synthesis.md @@ -0,0 +1,60 @@ +# 003 — audit round 2: FAIL on a process error of mine, plus two real refinements + +## What actually happened + +Round 2 returned FAIL with "0/9 closed" — and it was right about the index it read. I +had staged the roadmap *before* writing the round-1 amendments and never re-staged, so +the auditor compared its brief against the pre-amendment blobs. Its blockers 2-10 are +restatements of round 1 against stale content. + +Recording it rather than quietly re-running, because the failure mode is worth keeping: +**an audit reads the index, not the working tree.** A reviewer given a stale index +produces a confident, fully-cited, useless report, and the citations look exactly as +solid as real ones. The fix is mechanical — stage, then dispatch — and the cost of +forgetting is one full high-effort round. + +It also cuts the other way: the auditor never claimed the working tree was wrong. It +said the *staged* diff was, and it was. + +## Two findings that are not restatements + +Round 2 read the source around the amendments and produced two things round 1 did not. + +### The wp5 exit code is available, and the range is bounded + +`handleStop()` sets `process.exitCode = 1` and nothing else +(`src/cli/index.ts:747`); `src/cli/index.ts` otherwise uses only `0`, `1` and `130`; +and `bin/ocx.mjs:659-663` mirrors the child's code verbatim. So a distinct +history-only code propagates through the npm launcher with no extra plumbing, and the +plan is not proposing a collision. `050` now names those constraints. + +### The wp4 freshness gate is reachable — but the unit is ambiguous + +The auditor confirmed `shortResetAt` is populated on the short-only path +(`src/codex/quota.ts:596-598`), so gating on it does not make the terminal branch +unreachable. It also flagged the unit, and following that up produced the sharper +finding: `normalizeResetAt` (`:192-200`) normalizes scale not at all, and the GUI +disambiguates by magnitude at read time — +`resetAt < 10_000_000_000 ? resetAt * 1000 : resetAt` +(`gui/src/components/QuotaBars.tsx:355`). + +Both seconds and milliseconds therefore reach storage. A comparison written against one +assumption is off by 1000x against the other, and in the seconds-read-as-milliseconds +direction every terminal reading looks like it reset in 1970 and scores unknown. That +is a fix that passes its own test and does nothing — the exact vacuity class this gate +exists to catch, arriving one level below where round 1 was looking. + +`040` now requires the same magnitude normalization the GUI uses, an injected `now`, +and a regression for each unit. + +### One test-file name still to fix + +`050` plans a new `tests/update-stop-classification.test.ts`, which round 2 listed as +nonexistent. It is new by design — the doc says so — but `tests/update-stop-first.test.ts` +already owns the stop-ordering surface, so the new cases belong there rather than in a +second file. Corrected in `050`. + +## State + +Round 1's nine blockers are amended in the working tree and now staged. Round 2's two +substantive findings are folded in. Round 3 audits the staged tree. diff --git a/devlog/_plan/260831_prio70_train_round2/004_audit_round3_synthesis.md b/devlog/_plan/260831_prio70_train_round2/004_audit_round3_synthesis.md new file mode 100644 index 0000000000..489bf4d433 --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/004_audit_round3_synthesis.md @@ -0,0 +1,93 @@ +# 004 — audit round 3: FAIL, five blockers, four of them created by round 1's fixes + +First round to read the correctly-staged tree. Index gate confirmed clean: 11 files +staged, staged blobs matching the working tree, `git diff --cached --check` clean. + +The shape of this round is the interesting part. Round 1 found nine holes in the +original plan; four of round 3's five blockers are in the *amendments*, not in what +they replaced. Each fix was correct about the defect it named and wrong about the +boundary of its own remedy. + +## Blocker 2 — wp3's tuple predicate has a false positive, and it is reachable + +The round-1 amendment said: accept `0 → 1` drift only when `model_provider` and +`source` still match the original tuple, on the theory that an OpenCodex write always +moves one of them. + +There is a two-step sequence where it does not. Routing takes `openai/vscode/0` to +`opencodex/vscode/1` (`src/codex/history-provider.ts:1158`). Explicit legacy recovery +then takes that same row to `openai/vscode/1` — it sets `model_provider = 'openai'`, +leaves `source` alone for a non-`exec` row, and sets `has_user_event = 1` +unconditionally (`:991`, `:1013`). + +Final state: original provider, original source, `has_user_event` moved `0 → 1`. The +predicate reads it as user activity. Every byte of it was written by OpenCodex. + +The lesson is about the shape of the inference, not the specific sequence: **a final +state cannot establish authorship when more than one writer can reach it.** The +amendment tried to recover provenance from the endpoint of a path, and two paths share +that endpoint. Provenance has to come from something that records the transition, or +the check has to refuse the ambiguous case. + +## Blocker 1 — wp2's reservation is released before the shutdown fallback writes + +The amendment required releasing reservations on cancellation and supersession. During +drain expiry `supersedeShutdownFallbackBatch` marks every job cancelled +(`src/responses/state.ts:470`, `:542`) and `installShutdownFallbackSpill` then performs +a *synchronous* durable write (`:557`) — with its own link → exclusive-copy fallback +holding temp and destination together (`src/responses/spill-store.ts:370`). + +So the release fires and then the biggest write of the shutdown path runs unreserved. +The amendment closed the async publication hole and opened a synchronous one in the +code round 1 of this train hardened. + +Correct shape: the reservation is *transferred* through the fallback rather than +released at supersession, and only settled at swap or terminalization. + +## Blocker 3 — my own amendment contradicted its own test plan + +`040` says a snapshot with no `shortResetAt` scores unknown, and two lines later +describes regression cases 3 and 4 with account A carrying "only `shortPercent: 100`". +Under the amended contract those cases score unknown, so neither the new-thread switch +nor the affinity rebind can occur, and both tests would fail for a reason unrelated to +the defect. + +This is the vacuity class inverted: not a test that passes when it should fail, but a +test that cannot reach the behaviour it claims to prove. Round 1 added the freshness +gate to the contract and did not carry it into the fixtures written before it existed. + +## Blocker 4 — the exit code was not actually free + +Round 2 established that `handleStop` uses only `1`, and `src/cli/index.ts` only +`0`/`1`/`130`. Both true, and both beside the point: the process exit is supplied by +`dispatchCommand` (`src/cli/index.ts:989`), whose own contracts already return `2` +(`src/cli/dispatch.ts:193`, `:265`, `:598`, `:675`), `4` (`:277`) and `64` (`:560`). +`bin/ocx.mjs:659` mirrors whatever the child returns, so picking `2` would have made a +history-only stop indistinguishable from a config conflict. + +"Pick something outside that set" was a deferral, and the set was wrong. The plan now +reserves a concrete code and names the full occupied set. + +## Blocker 5 — `selfRefreshed === false` is two different things + +The round-1 amendment fixed the generation-as-lineage error by keying on +`selfRefreshed`: spend the recovery budget when true, reset when false. But the +primitive returns false in two unrelated situations. One is external replacement. The +other is a caller that *joined an in-flight refresh* and adopted the stored result +(`src/codex/account-store.ts:639-645`) — same lineage, not a replacement. + +Consequence: two concurrent 401 callers produce one refresh; the joiner sees +`selfRefreshed === false` and clears the owner's spent fence; a later 401 refreshes +again. The bounded-retry property the phase exists to guarantee is gone, and existing +case 4 does not catch it because it only observes the concurrent exchange. + +Three states, not two: self-refresh, joined-same-lineage, external replacement. Spend +for the first two, reset only for the third. + +## What this round says about the plan + +Nothing here argues the six targets are wrong or the scores are wrong — round 3 +re-verified the index gate and raised no issue with `000`'s matrix or `001`'s evidence. +Every blocker is about the precision of a remedy. That is the correct thing for a third +round to be finding, and it is also the reason implementation has not started yet: four +of these five would have shipped as working code with passing tests. diff --git a/devlog/_plan/260831_prio70_train_round2/005_audit_round4_synthesis.md b/devlog/_plan/260831_prio70_train_round2/005_audit_round4_synthesis.md new file mode 100644 index 0000000000..940498fe35 --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/005_audit_round4_synthesis.md @@ -0,0 +1,88 @@ +# 005 — audit round 4: FAIL, four blockers, and the first one is a migration hazard + +Fresh reviewer, correctly-staged tree. Confirmed closed from round 3: the wp3 +route→legacy sequence is reachable and `rememberOriginal` does run first; wp2 can carry +ownership on the job object; wp6's return genuinely cannot express the joiner state, and +extending the primitive is in scope. Score-matrix sums all check out. + +What is left is four blockers, and the first is the most consequential thing any round +has found. + +## Blocker 1 — wp3's provenance field would break the manifests it exists to repair + +Two independent problems in the round-3 amendment. + +**It is not crash-safe.** The manifest is written before routing +(`src/codex/history-provider.ts:1152`), and routing can still fail afterwards +(`:1230` throws `history_apply_partial_route`). Recording "a routed relabel occurred" +at `rememberOriginal` claims something that has not happened yet; recording it after the +write opens the mirror-image window where the relabel lands and the flag does not. +Either way a crash produces a manifest whose provenance disagrees with the database. + +**It is not backward compatible.** `CodexHistoryBackupManifest` is `version: 1` with a +fixed entry shape and no provenance field (`src/codex/history-manifest.ts:7-19`). Every +manifest on every user's disk lacks it. The round-3 rule — refuse when provenance is +unavailable — therefore refuses *every existing manifest*, which is precisely the +population #3026 is about. The fix would brick the thing it repairs on first contact. + +I wrote "an ambiguous integrity check must fail closed" in round 3 and did not ask what +the ambiguous set contained. It contained everything. + +The corrected shape: + +- Provenance is a **versioned, crash-recoverable tri-state**: `relabel-pending`, + `relabel-committed`, and absent. The pending marker is written before the routing + write and resolved after it, so a crash leaves a state that says "unknown, and here is + why" rather than a confident wrong answer. +- **Absent provenance is compatible, not refused.** A version-1 entry whose post-image + matches the recorded original exactly restores as it always has. Refusal is reserved + for the genuinely ambiguous case: absent provenance *and* `0 → 1` drift. +- The stale tuple-predicate paragraph at `030:78` is still sitting in the doc as + normative implementation text. It has to go, or an implementer will follow the + instruction rather than the amendment above it. + +## Blocker 2 — the transfer leaks when the fallback writer is never reached + +Round 3 replaced "release at supersession" with "settle only at swap or +terminalization". There is a path with neither: `setResidentEntry` can make a queued +candidate stale through its failure branch without cancelling the pending job +(`src/responses/state.ts:781`), and the fallback loop then skips that job on the +`states.get(job.id) !== candidate` guard (`:546`) before `installShutdownFallbackSpill` +runs. + +Under the round-3 rule that reservation is never settled. It survives for process +lifetime, and since reservations count against the cap, the effective budget ratchets +down every time it happens until nothing can spill at all. A disk-cap fix that +monotonically shrinks the usable cap is worse than the overshoot. + +Ownership must settle on the pre-write mismatch and on every no-swap/no-write return, +not only on the two success-shaped endings. + +## Blocker 3 — wp4's injected clock has eight call sites, not two + +`computeCodexUsageScore` takes `(quota, plan)` and no clock at all +(`src/codex/routing.ts:363-367`). Adding a third parameter touches eight call sites: +`routing.ts:1169`, `:1356`, `:1377`, `:1600`, `:1720`, `:1755`, `:1848`, and +`subagent-model-fallback.ts:229`. + +Two of those already receive a `now` and drop it before scoring — +`reevaluateAffinityQuota` (`routing.ts:1745`) and `isNativeModelQuotaExhausted` +(`subagent-model-fallback.ts:218`). The second is a consumer the phase never mentioned: +subagent model fallback reads the same score, so a stale terminal reading would push +subagents off a model whose window has already reset. Its test file is absent from the +verification list. + +## Blocker 4 — 70 is EX_SOFTWARE + +The round-3 amendment claimed 70 sits outside the `sysexits` block. It does not: +`sysexits.h` runs 64-78 and 70 is `EX_SOFTWARE`. It is unused in this repository, so +nothing would have collided today, but the justification written into the doc was false +and the next person to audit the choice would have found the same thing. + +Take 79 — immediately above the `sysexits` range, below `128 + signal`, and unused +across `src/cli/`, `bin/ocx.mjs` and `src/update/`. Define it once in a plain-ESM module +both TypeScript and `bin/ocx.mjs` can import. + +## Housekeeping + +`000_plan.md`'s audit paragraph still says one round. Updated to four. diff --git a/devlog/_plan/260831_prio70_train_round2/006_audit_round5_synthesis.md b/devlog/_plan/260831_prio70_train_round2/006_audit_round5_synthesis.md new file mode 100644 index 0000000000..77beb6f517 --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/006_audit_round5_synthesis.md @@ -0,0 +1,71 @@ +# 006 — audit round 5: two phases cleared, four blockers left + +First round to clear anything outright. + +**wp2 and wp5 are closed.** The reviewer walked every exit from the shutdown fallback — +the mismatch `continue`, budget terminalization, writer return and throw, no-swap +fallthrough, terminalization — and confirmed the round-4 "settle on every exit" rule +covers them all. It suggested an idempotent `finally`-style settlement as good practice +and explicitly declined to call it a blocker. Exit code `79` is unused as an exit status +across the repository; the only literals are quota fixtures and generated protobuf +metadata. Score sums re-checked: 73, 75, 75, 72, 71, 70. + +## Blocker 1 — the migration table refused the ordinary case + +A v1 entry recording `openai/vscode/0` whose row is now `opencodex/vscode/1` — the +everyday routed post-image from `src/codex/history-provider.ts:1158` — is a `0 → 1` +drift, and the round-4 table refused every v1 drift. That contradicts the live contract +at `tests/codex-history-provider.test.ts:261`. + +I built the table around the drift because the drift is what #3026 is about, and never +asked which *other* rows also exhibit drift. Two do, and one of them is the common path. + +The corrected model classifies on the shape of the observed row first — exact original, +recognized OpenCodex routed post-image, or original-tuple drift — and only the third +shape consults provenance. Rows one and two are decidable without it, which is what makes +the table disjoint and what shrinks the legacy-refusal set to something honest. + +The reviewer also caught that provenance was being read as a manifest-level property. +Version is global; the field belongs on `CodexHistoryBackupEntry`, or a rewritten mixed +manifest loses which absences were legacy. + +## Blocker 2 — the tri-state detected the crash instead of recovering from it + +"Refuse every `relabel-pending`" wedges restore permanently after a crash between the +routing commit (`:1230`) and the marker's resolution — the exact window the tri-state was +introduced to survive. + +The row answers the question the marker could not: shape A means the write did not land +(resolve to `relabel-none`), shape B means it did (resolve to `relabel-committed`), +anything else is foreign and refuses. Resolution is idempotent and runs at restore time, +so it opens no further window. + +Naming the failure honestly: round 4 asked for "crash-recoverable" and I wrote +crash-*detecting*. A state that says "something happened here" and then refuses forever +is a more elaborate wedge, not a recovery. + +## Blocker 3 — a test file in the command is not a test + +Round 4 added `tests/subagent-model-fallback.test.ts` to wp4's verification line and left +every new assertion in `tests/codex-routing.test.ts`. If +`isNativeModelQuotaExhausted` keeps dropping its injected `now` +(`src/codex/subagent-model-fallback.ts:218`), every listed test stays green while stale +quota pushes subagents off a live model. + +`040` now specifies two subagent cases with an injected clock deliberately far from wall +time — the only fixture shape that catches a `Date.now()` substitution. + +## Blocker 4 — three false claims in my own documents + +`000` pointed at `001` for below-bar per-item scores that were never written there. +`070` claimed 12 documents and three audit rounds against a staged 13 and four. + +These are small and they are the same category as everything this gate exists to catch: +a document asserting evidence it does not contain. `001` now carries the full below-bar +matrix, and `070`'s counts match the index. + +## Where this leaves the roadmap + +Four phases have survived a round with no findings against them; wp3 has absorbed three +rounds of correction on one predicate, which is a fair signal of where the real +difficulty in this train sits. diff --git a/devlog/_plan/260831_prio70_train_round2/007_audit_round6_synthesis.md b/devlog/_plan/260831_prio70_train_round2/007_audit_round6_synthesis.md new file mode 100644 index 0000000000..4526b37cff --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/007_audit_round6_synthesis.md @@ -0,0 +1,53 @@ +# 007 — audit round 6: wp4 cleared, and wp3 finally gets the right shape + +Confirmed closed this round: **wp4**. Its two subagent cases are expressible in +`tests/subagent-model-fallback.test.ts` and collectively turn red if the injected `now` +is dropped. wp2 and wp5 stayed closed and were not reopened. All 16 below-bar component +rows sum correctly, and `070`'s counts match the index. + +All three blockers were wp3, and between them they say the same thing: my predicate for +"OpenCodex wrote this row" was wrong for the fourth round running. + +## Blocker 1 — the predicate refused a row `dev` restores today + +An `openai/vscode/0` entry whose `first_user_message` is null routes to +`opencodex/vscode/0`, because `routeOpenai` sets `has_user_event` to `1` only when the +message is non-empty and otherwise leaves it alone +(`src/codex/history-provider.ts:1184`). My shape B required `has_user_event = 1`, so +that row matched no shape and fell through to refusal — regressing a restore that works +on `dev` and is under test at `tests/codex-history-provider.test.ts:117`. + +Disjointness failed too: an entry already recorded as `opencodex/exec/1` is exact shape +A *and* satisfies the old B pattern. + +## The actual root cause, after four rounds on one predicate + +Round 3: the tuple test has a false positive. Round 4: the provenance flag is not +crash-safe and refuses every legacy manifest. Round 5: the table refuses the ordinary +routed post-image and is not disjoint. Round 6: it refuses the null-message post-image +and is still not disjoint. + +Four different rows, four hand-written patterns, four holes. The problem was never the +specific pattern — it was that I kept writing a **parallel description** of what the +routing statements do, and a parallel description drifts from its original by +construction. Every round found a row where it already had. + +So B stops being a shape to recognize and becomes a value to compute: +`expectedPostImage(entry)` applies the same rules as `routeOpenai` and `routeExec`, +including the nullable-message branch at `:1184` and the `exec → cli` transition. A row +is B when it equals that value. Disjointness from A is then automatic — if the computed +post-image equals the entry, routing is a no-op for it and A is the correct reading. + +**When a check must decide "did our own code write this?", derive the answer from the +code that writes it.** That is the finding of this round, and it is worth more than the +four patches it replaces. + +## Blockers 2 and 3 — two stale lines + +A regression bullet still said every entry predating provenance refuses, which reads as +the blanket v1 refusal round 4 removed; it is now scoped to legacy shape C. And `000` +still claimed four audit rounds against `070`'s five. Both corrected. + +That `000`/`070` drift has now recurred twice. The counts live in two documents and I +updated one of them each time — worth fixing by writing the count once at close-out +rather than maintaining it mid-flight. diff --git a/devlog/_plan/260831_prio70_train_round2/008_audit_round7_synthesis.md b/devlog/_plan/260831_prio70_train_round2/008_audit_round7_synthesis.md new file mode 100644 index 0000000000..443e7bd827 --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/008_audit_round7_synthesis.md @@ -0,0 +1,56 @@ +# 008 — audit round 7: five phases clean, and wp3's predicate was in the tree all along + +Round 7 found **no blockers in wp1, wp2, wp4, wp5 or wp6**. It also confirmed `000` and +`070` now agree, the below-bar matrix sums, and the legacy refusal is scoped to shape C. + +Both remaining blockers were wp3, and they ended the sequence by making the real problem +visible. + +## Blocker 1 — the computed post-image would erase genuine user activity + +Routing derives `has_user_event` from the message **at snapshot time** +(`src/codex/history-provider.ts:1162` CASes the original value, `:1184` reads it). My +round-6 computation read the message **as it is now**. + +The divergence is reachable: an `openai/vscode/0` entry with a null message routes to +`opencodex/vscode/0`; the user sends their first message; the row becomes +`opencodex/vscode/1`. Recomputing from the current non-empty message yields `1`, +declares a match, and restore writes the user's activity back to `0`. + +The manifest records neither the message nor its emptiness +(`src/codex/history-manifest.ts:7-13`), so the entry needs `hadFirstUserMessage` — a +boolean, never the text. The manifest is on disk and the message is user content. + +## Blocker 2 — my table dropped a bridge `dev` deliberately supports + +`opencodex/exec/1` routes to `opencodex/cli/1`, then legacy recovery produces +`openai/cli/1` (`:991`). That row was neither A, B nor C in the round-6 table, so the +fallback refused a manifest `dev` restores today. + +## The root cause, after five rounds on one predicate + +`rowMatchesExpectedPostImage` **already exists** at +`src/codex/history-provider.ts:487-500`. It computes the post-image from the entry. It +handles the nullable-message branch. It accepts the `openai/cli/1` bridge at `:496-499`, +with coverage at `tests/codex-history-provider.test.ts:345`. + +Every version of my predicate — the tuple test (round 3), the provenance flag (round 4), +the shape table (round 5), the computed post-image (round 6) — was a reimplementation of +that function, each missing a different branch of it. Round 6 correctly said "derive +rather than pattern-match" and then wrote the derivation beside the tree instead of +taking it from the tree. + +wp3 now extends the existing helper and fixes its one real defect: the snapshot-vs-current +message read at `:488`. Exhaustiveness stops being an argument and becomes a property — +every row `dev` accepts is accepted afterwards, because it is the same function. + +**Before writing a predicate that recognizes your own system's output, search for the one +that already does.** Five rounds, roughly two hours of high-effort review, and the answer +was thirteen lines in the file the phase was already editing. + +## Process note + +From this point the A-phase reviewer is resumed rather than respawned, per the user's +instruction. A resumed reviewer keeps its own findings in context, which is the right +shape for blocker-closure rounds — it can tell a genuine fix from a restatement without +re-deriving the history. diff --git a/devlog/_plan/260831_prio70_train_round2/009_audit_round8_synthesis.md b/devlog/_plan/260831_prio70_train_round2/009_audit_round8_synthesis.md new file mode 100644 index 0000000000..12e0934fd8 --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/009_audit_round8_synthesis.md @@ -0,0 +1,58 @@ +# 009 — audit round 8: the fourth shape, and a v1 fallback + +First resumed round — same reviewer as round 7, judging whether its own two blockers +closed. That is the right shape for a closure round: it could tell a real fix from a +restatement without re-deriving the history, and it confirmed one closed and found that +the other had moved rather than resolved. + +**Closed:** the exec legacy bridge. `030` now retains both branches of the existing +helper, including `openai/cli/1`. Also confirmed: `hadFirstUserMessage` is recoverable +where it is written — `rememberOriginal` (`src/codex/history-provider.ts:465`) is called +at `:1153` over rows carrying `first_user_message`, so only its signature widens, from +`ThreadRow` to `ApplyRowSnapshot` (`:256-258`). And `native-residue`, the other manifest +consumer, touches only validated id and path, so the entry-shape change does not reach +it. + +## Blocker 1 — the row I rescued from B landed nowhere + +Round 7's fix stopped shape B from swallowing the null → non-empty row. It did not give +it a home. + +With `hadFirstUserMessage = false` the expected post-image of `openai/vscode/0` is +`opencodex/vscode/0`. After the user's first message the row is `opencodex/vscode/1`: +not A, not B (B expects the `0`), and **not C** — C requires the *original* +`openai/vscode` tuple, and this row wears the routed one. I wrote "it becomes C" without +checking C's own definition, so the plan's own regression could not pass under the plan's +own classifier. + +The fix is a fourth shape, not a broader third one. `has_user_event` can drift from what +OpenCodex last wrote in exactly two places — before the routing write and after it — and +the tuple says which: original tuple is C, post-image tuple is D. D needs no provenance, +because a row wearing the routed tuple was written by OpenCodex by definition, so drift +on top of it can only be activity that followed. + +## Blocker 2 — the required field would refuse ordinary v1 manifests + +Computing from `entry.hadFirstUserMessage` unconditionally reads `undefined` as `false` +on every manifest written before the field existed, which refuses ordinary pre-upgrade +shape-B entries. That is round 4's migration hazard arriving through a different door, +which is worth noting: the same class of mistake has now appeared twice in this phase +from two different directions. + +The field becomes optional and the helper version-aware — snapshot boolean for v2, +today's current-row reading for v1. The v1 path stays as imprecise as `dev` is now, which +is the honest bound: the fix cannot repair snapshots that never recorded the fact. + +**And the test I cited as the v1 guard is not one.** `tests/codex-history-provider.test.ts:261` +builds its manifest through the current writer, so after the version bump it exercises +v2. A v1 regression has to construct the manifest by hand. Round 8 caught a guard that +would have gone green while guarding nothing — the vacuous-test class, found in my own +verification plan rather than in a contributor's PR. + +## Blocker 3 — the round count drifted again + +`000` and `070` both still said six rounds after `008` landed. Third recurrence, and +round 9 caught the "written once from now on" claim as itself false — the number still +appears in both files. It is a two-place fact in a document set that will not be +restructured mid-flight; the honest fix is to check both on every round, not to claim a +mechanism that does not exist. diff --git a/devlog/_plan/260831_prio70_train_round2/010_audit_round9_synthesis.md b/devlog/_plan/260831_prio70_train_round2/010_audit_round9_synthesis.md new file mode 100644 index 0000000000..ebaa9f62c8 --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/010_audit_round9_synthesis.md @@ -0,0 +1,54 @@ +# 010 — audit round 9: the partition holds, the transitions did not + +Second resumed round. Confirmed this time: **A/B/C/D are mutually disjoint**, the +version-aware optional-field fallback preserves v1 behaviour, the hand-built v1 test is +constructible and red against a version-blind read, the exec bridge stays covered, and +`hadFirstUserMessage` is available where `rememberOriginal` runs. + +So the shape partition — five revisions in the making — is finally sound. Both remaining +blockers were about **transitions between shapes**, which is a different question and one +the earlier rounds never reached because the partition was still wrong. + +## Blocker 1 — pending accepted only two of the four shapes + +Round 8's pending resolution read shape A or shape B and refused everything else. But +user activity does not wait for a crash to finish: a crash on either side of the routing +write, followed by an ordinary first message, produces pending+C or pending+D. Both were +refused, so restore stayed wedged — the same failure the tri-state was introduced to +prevent, surviving in the corner the round-8 table did not enumerate. + +C and D are exactly as decidable under pending as without it, because the tuple still +says whether the routing write landed. Refusing them was never necessary; the table +simply stopped at two rows because two rows were all I had in mind when I wrote it. + +## Blocker 2 — D can be pulled back into C, and the verdict did not notice + +The sequence: `openai/vscode/0` with `hadFirstUserMessage = false` routes to +`opencodex/vscode/0`; the user's first message makes it `opencodex/vscode/1` (shape D); +legacy recovery then rewrites it to `openai/vscode/1` (`src/codex/history-provider.ts:991`), +which is shape C. + +Provenance reads `relabel-committed`, and C's unconditional verdict for that value was +"restore to `0`" — erasing activity that could not have been OpenCodex's, since the +routing write for this entry produced a `0`. + +The fix uses the field already added for blocker 1 of round 8: when +`hadFirstUserMessage` is false the route's expected event was `0`, so any later `1` is +the user's, whichever tuple the row now wears. `hadFirstUserMessage` is load-bearing in +two places, which is a decent sign it is the right field rather than a patch. + +**The pattern across both:** shape classification answers "what is this row?" and the +verdict tables answer "how did it get here?". I had been treating the second as a +corollary of the first for six rounds. It is not — a row's current shape does not +determine its history, and both blockers are cases where two different histories produce +the same shape. + +## Blocker 3 — a drift-prevention mechanism I claimed but did not build + +`009` said the round count would be "written once at close-out". It still appears in +both `000` and `070`. Round 9 correctly called that a false claim rather than a stale +number: the count was right, the mechanism was fiction. Replaced with the honest version +— it is a two-place fact, check both every round. + +Small, and exactly the category this gate exists for. A document asserting a safeguard it +does not have is worse than one that admits the manual step. diff --git a/devlog/_plan/260831_prio70_train_round2/010_wp1_web_search_query_field.md b/devlog/_plan/260831_prio70_train_round2/010_wp1_web_search_query_field.md new file mode 100644 index 0000000000..40aaa22938 --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/010_wp1_web_search_query_field.md @@ -0,0 +1,77 @@ +# 010 — wp1: carry singular `query` on every web_search_call action (#3071) + +Score 73/80. Branch: `codex/3071-web-search-query`, based on `dev`. One PABCD cycle. + +PR #3069 already implements this correctly. wp1 is a review-and-land phase, not a +reimplementation: rebase, close the two gaps the tools lane found, merge. + +## What changes + +`webSearchAction()` (`src/bridge.ts:164`) currently returns `{type, queries}` for a +batch and `{type, query, queries}` for a single. It becomes unconditional: + +``` +const first = queries[0] ?? ""; +return { type: "search", query: first, queries: queries.length > 0 ? queries : [first] }; +``` + +`backfillWebSearchQueries()` (`src/adapters/openai-responses.ts:916`) currently repairs +only `query` → `queries`. It becomes bidirectional: an action with `queries` and no +`query` gains `query = queries[0]`; an action with `query` and no `queries` gains +`queries = [query]`; an action with both is returned by reference. + +## The trade this makes, stated plainly + +The comment at `src/bridge.ts:149-168` says the omission is load-bearing: codex-rs +renders "<first> ..." only when `query` is ABSENT and `queries.len() > 1`. Adding +`query` to a batch collapses that plural ellipsis to a single-query label. + +That is a real regression in native rendering, and it is the right trade — a cosmetic +label change against a conversation that 400s on every subsequent turn. The comment +must be rewritten to say so, because the next reader will otherwise "restore" the +asymmetry and reopen #3071. PR #3069 does rewrite it. + +## Three gaps to close before merge + +1. `src/adapters/openai-responses.ts:904-910` still describes the old one-way + contract. Same class of stale comment as above. +2. `:927-928` reads `action.queries[0]` without a type check. Unknown input items use + a loose schema (`src/responses/schema.ts:106`), so a non-string first element would + be copied into `query` and fail the same validator this fix exists to satisfy. + Require `typeof action.queries[0] === "string"`. +3. Added by audit round 1 (`002`, blocker 6): the PR head's own first summary line at + `src/bridge.ts:149` still says a batch carries no singular `query`, contradicting + the body of its rewritten comment and the code below it. The head's review state is + `CHANGES_REQUESTED`, not merely "review required" — the plan understated where the + PR stands. + +Also undefined at the head: an action whose `queries` is `[]`. The loose input schema +admits it, and the current expression would produce `query: ""` with `queries: []`, +which satisfies neither validator's intent. Canonicalize `[]` to the empty-single form +(`{query: "", queries: [""]}`) and assert it. + +## Regressions + +Both already exist in the PR and both are RED against `dev`: + +- `tests/bridge.test.ts:1078` — a batched search carries `query: "rust async"` + alongside `queries`. Currently asserts `action.query` is undefined. +- `tests/openai-responses-passthrough.test.ts:1298` — a replayed batch gains the + singular `query`. Currently asserts the batch is untouched. + +Two to add: + +- gap 2: an input item whose `action.queries` is `[42]` is returned unchanged rather + than gaining `query: 42`. +- empty array: an action with `queries: []` canonicalizes to `{query: "", queries: [""]}` + rather than passing an empty array through. + +## Verification + +Focused locally: `bun test tests/bridge.test.ts tests/openai-responses-passthrough.test.ts`. +Suite, typecheck and privacy scan on `ssh lidge` at the pushed head. + +## Close-out + +`Closes #3071`. Close #3068 as a duplicate with a comment naming #3071 and PR #3069. +Both close manually — PRs here target `dev`, not the default branch. diff --git a/devlog/_plan/260831_prio70_train_round2/011_audit_round10_synthesis.md b/devlog/_plan/260831_prio70_train_round2/011_audit_round10_synthesis.md new file mode 100644 index 0000000000..83aaf5ae95 --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/011_audit_round10_synthesis.md @@ -0,0 +1,53 @@ +# 011 — audit round 10: one cell, and it is genuinely undecidable + +Third resumed round. One blocker, and it is the first finding in this phase whose correct +answer is "refuse" rather than "handle it". + +Confirmed closed: committed-C split by expected event, pending + D resolving as committed +while preserving the `1`, the three new regressions all constructible and red against +round-8 semantics, B and D still disjoint, v1 fallback and exec bridge sound, and the +round counts correct with the false single-source claim gone. + +## The blocker — pending + C with expected event `1` + +Round 9's pending table said every shape C proves the routing write did not land. For an +OpenAI-origin entry whose expected route event is `1`, that is false. Two histories reach +the identical state: + +1. routing never landed, and Codex-side activity moved the original row `0 → 1`. The + `1` is the user's and must survive. +2. routing landed as `opencodex/vscode/1`, the process crashed before the marker + resolved, and legacy recovery rewrote the row to `openai/vscode/1` + (`src/codex/history-provider.ts:991`). The `1` is OpenCodex's and must restore to + `0`. + +Same tuple, same provenance, same expected event. The reviewer's falsification is the +part worth keeping: it did not merely find a case my table mishandled, it showed that +**no durable fact remaining in the system distinguishes the two**. + +So the cell refuses. That is the right answer rather than a gap in the design — a guess +here either erases a user's activity or fabricates activity that never happened, and +refusal leaves the manifest intact for a human to resolve. The two neighbouring cells stay +decidable: expected event `0` is safe because routing would have written a `0`, and +`exec`-origin entries are safe because `routeExec` moves `source` to `cli` and legacy +recovery does not restore it, so a legacy return cannot land on the original tuple at all. + +The scoping regression matters as much as the refusal: an `exec`-origin pending + C that +still restores, so a later reader cannot generalize one refusing cell into a blanket +pending + C refusal — which is the round-8 wedge coming back. + +## What ten rounds on wp3 actually produced + +The predicate went: tuple test (r3) → provenance flag (r4) → three-shape table (r5) → +computed post-image (r6) → call the existing helper (r7) → fourth shape (r8) → transition +semantics (r9) → one undecidable cell (r10). Each step was a smaller correction than the +last, which is the shape a converging design has. + +The other five phases have drawn zero blockers since round 7. wp3 is a durable-state +integrity check with two writers and a crash window, and it turns out that is worth +roughly nine rounds more scrutiny than a bounded byte-accounting fix. + +## Housekeeping + +"three shapes" immediately above the four-shape table, flagged as a non-blocking caution, +is corrected. diff --git a/devlog/_plan/260831_prio70_train_round2/020_wp2_spill_disk_budget.md b/devlog/_plan/260831_prio70_train_round2/020_wp2_spill_disk_budget.md new file mode 100644 index 0000000000..98ffee3bfe --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/020_wp2_spill_disk_budget.md @@ -0,0 +1,148 @@ +# 020 — wp2: make the spill disk cap hold during async publication (#3032 / PR #3032) + +Score 75/80. Branch: `codex/3032-spill-budget`, based on `dev`. One PABCD cycle. + +This is the same subsystem round 1's wp3 hardened (PR #3044, shutdown drain), so the +drain invariants are a constraint on this change, not background. + +## The measured incident + +6.8 GiB of spill accumulated in 44 minutes on the reporter's machine, ending in +ENOSPC. ENOSPC is what puts this at 20/20 on durability: it does not corrupt +OpenCodex state specifically, it breaks every durable write on the volume, including +ones belonging to unrelated processes. + +## What the PR already gets right + +At head `b4d1d2404f`: steady-state ordering, reload accounting, periodic enforcement, +and deferred-generation accounting. Six of its tests are real and non-vacuous for the +synchronous, load and sweep paths. Keep all of it. + +## The one gap that blocks it + +`spilledResponseBytes()` (PR-head `src/responses/state.ts:627-651`) counts installed +states and deferred unlinks. It does not count a file currently being created by +`writeResponseSpillDurablyAsync`. That publication stays live through +`src/responses/state.ts:246-300`, and `:174-179` allows up to 256 MiB of pending +payload, so on Windows — where ACL publication can block on `icacls` — the on-disk +total can exceed the advertised 1 GiB cap for as long as publication takes. + +A cap that holds only when writes are fast is not a cap. It is the same shape of +defect as round 1's wp3 finding, where supersession reached the state tracking but not +the writer: the accounting and the filesystem disagreed about what existed. + +## What changes + +1. **`src/responses/spill-store.ts`** — expose an exact prospective measurement of the + **peak publication footprint**, computed before publication rather than inferred + after it. Exact, because an estimate that undercounts reintroduces the overshoot at a + smaller magnitude and an estimate that overcounts drops replayable continuations. + See the amendment below for why the peak is not one envelope. +2. **`src/responses/state.ts`** — reserve those bytes when a `PendingResponseSpill` is + queued; include reservations in aggregate accounting; enforce the cap *before* + creating the temp or destination file; release the reservation on every settlement + path, including cancellation, supersession, and the drain's fail-closed + `spill-failed` tombstone. + +### Amendment after audit round 3 (`004`, blocker 1): supersession transfers, it does not release + +"Release on supersession" is wrong for the shutdown path, and wrong in the most +expensive place. During drain expiry `supersedeShutdownFallbackBatch` marks every job +cancelled (`src/responses/state.ts:470`, `:542`), and `installShutdownFallbackSpill` +then performs a **synchronous** durable write (`:557`) whose own link → +exclusive-copy fallback holds temp and destination simultaneously +(`src/responses/spill-store.ts:370`). + +Releasing at supersession therefore un-reserves exactly before the largest write of the +shutdown path. The async hole would be closed and a synchronous one opened in the code +round 1 of this train hardened. + +So the reservation is **transferred** into the fallback rather than released: +supersession hands ownership to `installShutdownFallbackSpill`, and the bytes settle at +swap or terminalization. Cancellation that does *not* lead to a fallback write still +releases. The distinction to encode: cancelled means "this writer stops", not "these +bytes are gone". + +### Amendment after audit round 4 (`005`, blocker 2): settle on every exit, not just the successful ones + +"Settle only at swap or terminalization" leaks. `setResidentEntry` can make a queued +candidate stale through its failure branch **without cancelling the pending job** +(`src/responses/state.ts:781`), and the fallback loop then skips that job on the +`states.get(job.id) !== candidate` guard (`:546`) before the writer runs. Ownership was +transferred and nothing ever settles it. + +Because reservations count against the cap, that leak is monotonic: every occurrence +permanently shrinks the usable budget until nothing can spill. A disk-cap fix that +ratchets the cap toward zero is a worse defect than the overshoot it replaces. + +Transferred ownership must therefore settle on **every** exit from the fallback path, +including the pre-write candidate mismatch and any other no-swap/no-write return — not +only on the two success-shaped endings. The rule to implement against: a reservation is +owned by exactly one code path at a time, and every path that can stop owning it must +say so explicitly. + +The release paths are where this will go wrong. Round 1 established that the drain +has five of them and that missing one wedges shutdown; every reservation must be +settled by the same code that settles the spill, not by a parallel bookkeeping pass. + +## Regression + +Gated Windows case in `tests/responses-state.test.ts`: lower the cap, fill active +spills to it, force link failure so publication takes the `COPYFILE_EXCL` fallback +(`src/responses/spill-store.ts:420`), block destination ACL hardening so the temp is +still present (`:627`), queue another spill, and assert that accounted and on-disk bytes +never exceed the cap while both the temp and the destination copy exist. After +settlement, assert the newest continuation is still replayable — the fail-closed path +must not become the ordinary path. + +RED proof required: the assertion must fail against PR head `b4d1d2404f`, not just +against `dev`. This is the specific claim the PR does not yet satisfy, so a test that +is green on its head proves nothing. + +Second RED case, added by audit round 3: force **drain expiry** so +`supersedeShutdownFallbackBatch` runs, then force synchronous link failure into the +copy fallback and inspect during hardening. Assert the reserved total still covers the +two-envelope peak at that moment. This one is red against the round-1 amendment as +written, and it is the assertion that proves the transfer semantics rather than the +release semantics. + +Third RED case, added by audit round 4: queue a spill, make its candidate stale through +`setResidentEntry`'s failure branch so the job is never cancelled, then run the drain. +Assert the reserved total returns to its pre-queue value after the drain completes. Red +against the round-3 amendment, which leaks here. + +## Constraint carried from round 1 + +The 1001-pass structural guard and the `B=5000`/`R=4000` budget split from PR #3044 +must still hold. If reservation accounting interacts with the drain's fixed-point +loop, the loop's termination proof has to be re-argued, not assumed. + +Both new RED cases must preserve that split and that guard while they run — a test that +demonstrates the cap by breaking the drain's termination has demonstrated nothing. + +## Verification + +Focused: `bun test tests/responses-state.test.ts tests/responses-state-write-amplification.test.ts`. +(`tests/responses-spill-store.test.ts` named in the first draft does not exist — blocker 8.) +Suite, typecheck and privacy scan on `ssh lidge`. + +Residual risk, same as round 1's wp3: NTFS unlink semantics and `icacls` timeout +behaviour while a path is held still want a real Windows host. +### Amendment after audit round 1 (`002`, blocker 3): the peak is two envelopes + +The first draft reserved one serialized envelope. Windows publication can fall back +from hard-linking to copying (`src/responses/spill-store.ts:404`), and during that +fallback the destination copy exists while the temp file still exists and destination +ACL hardening is awaited (`:597`). Peak on-disk footprint for a single spill is +therefore two envelopes. + +Reserving one leaves the cap violable by exactly the margin the issue reports, which +would have produced a phase that passes its own tests and does not hold its own +contract. The reservation is the peak footprint, and the RED test must **force the copy +fallback** while destination hardening is blocked, so both paths coexist at assertion +time. A test that only exercises the hard-link path cannot observe the overshoot. + +If reserving two envelopes proves too costly at the cap, the alternative is to redesign +publication so the temp is released before the destination is hardened — but that +changes the crash-consistency story PR #3044 established, so it is a separate phase, +not a shortcut inside this one. diff --git a/devlog/_plan/260831_prio70_train_round2/030_wp3_forked_rollout_restore.md b/devlog/_plan/260831_prio70_train_round2/030_wp3_forked_rollout_restore.md new file mode 100644 index 0000000000..190fb18b8e --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/030_wp3_forked_rollout_restore.md @@ -0,0 +1,396 @@ +# 030 — wp3: make history restore id-aware and stop overwriting `has_user_event` (#3026) + +Score 75/80. Branch: `codex/3026-forked-rollout-restore`, based on `dev`. +One PABCD cycle. + +PR #3056 is half of this. Its half is correct; the other half is untouched, so the +issue cannot close on it. + +## Two defects, one symptom + +**Defect A — the reader ignores the thread id.** A forked session's rollout file +contains more than one `session_meta` line. `readLatestSessionMeta` returns the last +one regardless of whose id it carries, and three call sites depend on it: +`snapshotRolloutForRestore` (`src/codex/history-provider.ts:552`), +`assertRestoreReadback` (`:631`), and `updateSessionMeta` (`:878`). The tree already +has an id-aware fold at `:680-688`, so this is a wiring defect, not a missing +capability. + +One rejected entry fails the whole manifest (`:586-596`), so a single forked rollout +out of 389 wedges every `ocx stop` and `ocx update`. The reporter measured 6/389. + +**Defect B — restore forces the manifest's `has_user_event` even when Codex moved it.** +The user does something in the session after the snapshot, the row flips 0 → 1, and +readback rejects it (`:487-500`, `:611-613`), then restore overwrites it from the +manifest (`:1265-1289`). PR #3056 relaxes it only for the relabelled `opencodex` +post-image, which is the narrower case, not this one. + +### Amendment after audit round 1 (`002`, blocker 2): OpenCodex writes this field too + +The first draft justified the relaxation by calling `has_user_event` Codex-owned and +never written by OpenCodex. That is false. OpenCodex writes it in +`routeOpenai`/`routeExec` (`src/codex/history-provider.ts:1158`) and sets it to `1` +unconditionally in legacy recovery (`:1013`). An existing test *requires* OpenCodex's +own `0 → 1` routing write to restore back to `0` +(`tests/codex-history-provider.test.ts:261`), so blanket preservation would have +broken a contract already under test. + +The rule is therefore directional, not permissive: + +- Preserve **only monotonic `0 → 1` drift**, never `1 → 0`. Nothing legitimate produces + the reverse, and accepting it would let a stale manifest erase real activity. +- `has_user_event` **stays** in the CAS and readback for OpenCodex post-images, because + on that path OpenCodex is the writer and the field is genuine evidence. + +### Amendment after audit round 3 (`004`, blocker 2): the tuple is not provenance + +The first version of this rule also required the original `model_provider`/`source` +tuple to be untouched, reasoning that an OpenCodex write always moves one of them. +There is a reachable two-step sequence where it does not: + +1. Routing takes `openai/vscode/0` to `opencodex/vscode/1` + (`src/codex/history-provider.ts:1158`). +2. Explicit legacy recovery takes that same row to `openai/vscode/1` — it sets + `model_provider = 'openai'`, leaves `source` unchanged for any non-`exec` row, and + sets `has_user_event = 1` unconditionally (`:991`, `:1013`). + +The end state is the original tuple with `has_user_event` moved `0 → 1`, and every +write in it was OpenCodex's. The predicate would call that user activity. + +**A final state cannot establish authorship when two paths reach it.** So the check +does not infer ownership from the tuple. It needs a transition record: + +- The manifest already stores the original tuple, and `rememberOriginal` runs before + the routing write (`:1152-1154`). Extend the manifest entry with the fact that a + routed relabel occurred for this row, so legacy recovery's return to `openai` is + distinguishable from a row that was never routed. +- When the manifest records a routed relabel, `has_user_event` stays a hard CAS term — + the `1` is OpenCodex's own and must restore to the recorded original. +- Only a row with **no** recorded routed relabel may carry `0 → 1` drift through + restore. That is the forked-rollout case #3026 actually reports. +- If provenance is unavailable for an entry (an older manifest without the field), see + the round-4 amendment below — refusing outright would brick every manifest already on + disk. + +### Amendment after audit round 4 (`005`, blocker 1): crash-safety and migration + +The round-3 rule had two holes, and the second is a migration hazard. + +**Crash-safety.** The manifest is written before routing (`:1152`) and routing can fail +after it (`:1230`, `history_apply_partial_route`). A flag set at `rememberOriginal` +claims a relabel that may never land; a flag set after the write is missing if the crash +falls between. So provenance is a **versioned tri-state**, not a boolean: +`relabel-pending` written before the routing write, resolved to `relabel-committed` +after it, and absent for anything older. A crash leaves `relabel-pending`, which is an +honest "unknown" the restore path can reason about instead of a confident wrong answer. + +**Migration.** `CodexHistoryBackupManifest` is `version: 1` with a fixed entry shape and +no provenance field (`src/codex/history-manifest.ts:7-19`), so *every manifest on every +user's disk* lacks it. "Refuse when provenance is unavailable" would therefore refuse +the entire existing population — the exact rows #3026 is about. Corrected rule: + +### Amendment after audit round 5 (`006`, blockers 1-2): a disjoint table, and pending resolves by inspection + +The round-4 table had two defects. It rejected the *ordinary* routed post-image — a v1 +entry recording `openai/vscode/0` whose row is now `opencodex/vscode/1` is a `0 → 1` +drift, and the table refused it, contradicting the live contract at +`tests/codex-history-provider.test.ts:261`. And "refuse every `relabel-pending`" made +the tri-state crash-*detecting* rather than crash-recoverable: a crash after the routing +commit (`src/codex/history-provider.ts:1230`) but before the marker resolves would wedge +restore permanently. + +Both come from classifying on the drift instead of on **what the row actually is**. The +observed row falls into exactly one of four shapes, and the shape is decidable: + +| # | observed row | meaning | verdict | +| --- | --- | --- | --- | +| A | exactly the recorded original | untouched, or already restored | restore (no-op on the field) | +| B | `rowMatchesExpectedPostImage(row, entry)` returns true | OpenCodex wrote it | `has_user_event` is a hard CAS term; restore to the recorded original | +| C | the **original** tuple with `has_user_event` moved `0 → 1` | Codex-side activity, or route-then-legacy-recover | decided by provenance | +| D | the **expected post-image** tuple with `has_user_event` moved `0 → 1` | routed row, then Codex-side activity | preserve the `1` | + +A and B are unambiguous from the row alone. C and D are the two ways `has_user_event` +can differ from what OpenCodex last wrote — before routing (C) and after it (D) — and +they are distinguished by which tuple the row carries, so the four are disjoint. + +**Why D exists** (audit round 8, `009`): with `hadFirstUserMessage = false`, the expected +post-image of `openai/vscode/0` is `opencodex/vscode/0`. If the user then sends their +first message the row becomes `opencodex/vscode/1` — not A, not B (B expects the `0`), +and not C (C requires the *original* `openai/vscode` tuple). Round 7's fix correctly +stopped B from swallowing this row and left it matching nothing, so the fallback refused +it. D is where it belongs, and its verdict is the one the round-7 regression asserts: +preserve the `1`. + +D needs no provenance. A row wearing the routed tuple was written by OpenCodex by +definition, so a `0 → 1` difference on top of it can only be Codex-side activity that +followed. + +### Amendment after audit round 7 (`008`): B already exists in the tree + +Rounds 3, 4, 5 and 6 each found a hole in a predicate I kept rewriting for B. Round 7 +found two more. The reason is simpler than any of the individual holes: +**`rowMatchesExpectedPostImage` is already in the file** +(`src/codex/history-provider.ts:487-500`). It computes the routed post-image from the +entry, handles the nullable-message branch, and accepts the `openai/cli/1` legacy bridge +at `:496-499` that round 7 caught my table refusing — the one covered by +`tests/codex-history-provider.test.ts:345`. + +wp3 does not define a new predicate. It calls the existing one and fixes the single real +defect in it. + +#### The defect in the existing helper + +`:488` computes `postHasUserEvent` from `hasFirstUserMessage(row.first_user_message)` — +the message as it is **now**. Routing derived it from the message as it was **at snapshot +time**: `:1162` CASes the original value and `:1184` reads it. + +Those diverge in a reachable case. An `openai/vscode/0` entry with a null message routes +to `opencodex/vscode/0`; the user then sends their first message and the row becomes +`opencodex/vscode/1`. The helper recomputes the expected post-image from the *current* +non-empty message, gets `1`, declares a match, and restore erases the user's activity +back to `0`. + +So the manifest entry must carry the snapshot's message state. +`CodexHistoryBackupEntry` records neither the message nor its emptiness +(`src/codex/history-manifest.ts:7-13`). Add **a boolean**, `hadFirstUserMessage` — +never the text. The manifest is written to disk and the message is user content, so +emptiness is the only privacy-safe form to persist, and it is all the computation needs. + +With that field the helper computes from the snapshot, and the case above stops being B +and becomes C — original-tuple drift adjudicated by provenance, which is what it actually +is. + +*Corrected by round 8:* it becomes **D**, not C. C is scoped to the original tuple; this +row wears the post-image tuple. See the table above. + +### Amendment after audit round 8 (`009`): v1 entries keep the current-row fallback + +`hadFirstUserMessage` cannot be recovered for a manifest written before the field +existed. Computing `postHasUserEvent` from `entry.hadFirstUserMessage` unconditionally +would read `undefined` as `false` on every v1 entry and refuse ordinary pre-upgrade +manifests — the migration hazard from round 4, returning through a different door. + +So the helper is version-aware: + +- **v2 entry** — compute from `entry.hadFirstUserMessage`. Correct by construction. +- **v1 entry** — fall back to `hasFirstUserMessage(row.first_user_message)`, exactly what + `src/codex/history-provider.ts:488` does today. It is imprecise in the null → non-empty + case, and that imprecision is the status quo on `dev`; the fix applies to manifests + written after the upgrade, and does not pretend to repair snapshots that never recorded + the fact. + +`hadFirstUserMessage` is recoverable where it is written: `rememberOriginal` +(`:465`) is called from `:1153` over rows that carry `first_user_message` +(`ApplyRowSnapshot`, `:256-258`). Its signature widens from `ThreadRow` to +`ApplyRowSnapshot`; the value is in scope at the call site today. + +#### Why this ends the sequence + +Exhaustiveness is now inherited rather than argued: every row `dev` accepts today is +accepted by the same function afterwards, because it is the same function. The two +defects round 6 fixed are subsumed — both were cases the helper already handled. + +The lesson, recorded because it cost five rounds: **when a check must decide "did our own +code write this?", call the code that already decides it.** Round 6 got half of it — +derive rather than pattern-match — and still wrote the derivation beside the tree instead +of taking it from the tree. + +| provenance on the entry | row C verdict | +| --- | --- | +| `relabel-committed`, route's expected event was `1` | OpenCodex authored the `1` via route-then-legacy-recover; restore to `0` | +| `relabel-committed`, route's expected event was `0` | the `1` cannot be OpenCodex's; preserve it — see below | +| `relabel-none` | genuine user activity; preserve the `1` — the #3026 case | +| absent (legacy v1 entry) | refuse, exactly as `dev` does today | + +**Why `relabel-committed` alone is not enough** (audit round 9, `010`). A row can pass +through D and then be pulled back to C by legacy recovery: + +1. `openai/vscode/0` with `hadFirstUserMessage = false`; +2. routing produces `opencodex/vscode/0` — expected event `0`; +3. the user sends their first message, giving `opencodex/vscode/1`, shape D; +4. legacy recovery produces `openai/vscode/1` (`src/codex/history-provider.ts:991`), + shape C. + +Provenance still reads `relabel-committed`, and an unconditional verdict would restore +`0` — erasing activity that demonstrably was not OpenCodex's, because the routing write +for this entry produced a `0`. + +`hadFirstUserMessage` settles it: when it is false the route's expected event was `0`, +so any `1` observed afterwards came from the user, whichever tuple the row now wears. +The field turns out to be load-bearing in two places, not one. + +**Refusing legacy row C is not a regression.** `dev` already refuses it with +`history_backup_postimage_mismatch`; the fix simply does not reach back in time for +manifests written before provenance existed. Every v1 entry in shapes A and B — which is +the overwhelming majority, and the whole population the id-aware read in Defect A +repairs — restores exactly as it does today. Round 4 was right that failing closed on an +entire schema version is wrong; the correction is to shrink the ambiguous set to row C, +not to guess at it. + +**Pending resolves by inspection, not by refusal.** `relabel-pending` means "a routing +write was attempted and we do not know if it landed" — and the row itself answers that: + +| row under `relabel-pending` | what it proves | resolve to | +| --- | --- | --- | +| shape A | the write did not land | `relabel-none` | +| shape B | the write landed | `relabel-committed` | +| shape C, OpenAI-origin, route's expected event was `0` | the write did not land, activity followed | `relabel-none`, preserving the `1` | +| shape C, **OpenAI-origin**, route's expected event was `1` | **two histories produce this state** | refuse | +| shape C, `exec`-origin (any expected event) | legacy return cannot reach it | `relabel-none`, preserving the `1` | +| shape D — post-image tuple, `0 → 1` | the write landed, activity followed | `relabel-committed`, preserving the `1` | +| anything else | foreign to every outcome | refuse | + +**The one genuinely undecidable cell** (audit round 10, `011`). For an OpenAI-origin +entry whose expected route event is `1`, pending + shape C is reachable two ways: + +- routing never landed, and Codex-side activity moved the original row `0 → 1` — the + `1` is the user's and must survive; or +- routing landed as `opencodex/vscode/1`, the process crashed before the marker + resolved, and legacy recovery rewrote it to `openai/vscode/1` + (`src/codex/history-provider.ts:991`) — the `1` is OpenCodex's and must restore to + `0`. + +Tuple, provenance and expected event are identical in both. No durable fact left in the +system separates them, so the cell refuses. That is the correct answer rather than a gap: +a coin flip here either erases a user's activity or fabricates it, and refusal leaves the +manifest intact for a human. + +The neighbouring cells stay decidable and must not be widened into this one. Expected +event `0` is safe because routing would have produced a `0`, so any `1` is the user's +whatever happened. `exec`-origin entries are safe because `routeExec` moves `source` to +`cli` and legacy recovery does not restore it, so a legacy return cannot land on the +original tuple at all. + +Round 8's version accepted only A and B, so a crash on either side of the routing write +followed by ordinary user activity left restore wedged (audit round 9, `010`). D is +always decidable under pending, and so is C except in the single OpenAI-origin cell +above — the tuple says whether the write landed everywhere else, so the blanket refusal +was never necessary. + +Resolution is idempotent and can run at restore time, so no third crash window opens: a +crash during resolution just means the next restore resolves again from the same row. + +**Provenance is per entry, not per manifest.** `CodexHistoryBackupEntry` carries the +field; the version bump only says the field may be present. A mixed manifest rewritten +forward keeps "this entry predates provenance" distinguishable from "this entry recorded +no relabel", which the round-4 wording lost by overloading absence. + +## What changes + +1. Add the path-based id-aware wrapper (PR #3056's `readLatestSessionMetaForId()`) and + use it at all three call sites. +2. Extend `CodexHistoryBackupEntry` with an **optional** `hadFirstUserMessage?: boolean` + and the versioned tri-state provenance, bumping the manifest version and keeping v1 + readable. Widen `rememberOriginal` (`:465`) from `ThreadRow` to `ApplyRowSnapshot` so + it can record the flag; the value is already in scope at its call site (`:1153`). +3. Fix `rowMatchesExpectedPostImage` (`src/codex/history-provider.ts:487-500`) to compute + `postHasUserEvent` from `entry.hadFirstUserMessage` **when the entry has it**, and to + keep today's `hasFirstUserMessage(row.first_user_message)` reading for v1 entries that + do not. Keep both existing branches, including the `openai/cli/1` legacy bridge. +4. The restore CAS and readback compare provider and source unconditionally, and resolve + `has_user_event` through the shape table: B restores to the recorded original, C is + decided by provenance, D preserves the observed `1`. Where drift is preserved, carry + the observed value into the restore write instead of forcing the manifest's. + +The distinction to hold onto: an integrity check exists to catch OpenCodex writing +something it did not intend. Authorship cannot be read off the final state — round 3 +proved two paths reach the same tuple — so it has to be recorded at the time of the +write. What the check narrows on is direction plus recorded provenance, and absent +provenance is treated as compatible for an exact post-image and ambiguous only for +drift. + +## Regressions + +Both in `tests/codex-history-provider.test.ts`: + +1. A rollout whose trailing `session_meta` belongs to the parent thread restores and + consumes the manifest. RED against `dev` at + `history_backup_rollout_postimage_mismatch`. +2. A manifest entry captured as `openai`/`vscode` with `hasUserEvent=0`, whose row is + then changed to `has_user_event=1` by user activity, restores successfully, ends + with `has_user_event=1`, and deletes the manifest. RED against `dev` at + `history_backup_postimage_mismatch`, and RED against PR #3056's head too — that is + the assertion which proves the second half is missing. +3. The existing contract at `tests/codex-history-provider.test.ts:261` still passes: + OpenCodex's own `0 → 1` routing write restores back to `0`. This one is GREEN before + and after, and it is the guard that keeps the relaxation from becoming blanket + preservation. +4. The route-then-legacy-recover sequence from blocker 2: relabel `openai/vscode/0` to + `opencodex/vscode/1`, then run legacy recovery to reach `openai/vscode/1`, then + restore. Must restore `has_user_event` to `0` or refuse with an integrity error — + never accept the `1` as user activity. RED against the round-1 amendment as written, + which is precisely why it is here. + +## Adversarial cases the audit lane asked for + +Relaxing a CAS term needs the negative cases pinned in the same commit, or the next +change will relax further: + +- a row whose `model_provider` differs from the manifest still fails; +- a row whose `source` differs still fails; +- a rollout whose id-matched `session_meta` is absent entirely still fails. +- a row whose `has_user_event` went `1 → 0` still fails, under either tuple. +- an entry that predates the provenance field **and presents shape C** refuses rather + than assuming no relabel occurred. Legacy entries in shapes A and B restore normally — + round 6 caught this line still reading as a blanket v1 refusal, which is the exact + migration hazard round 4 removed. + +Corrected by rounds 4, 5 and 6. Cases to add, one per table row: + +- shape A on a v1 entry restores; +- shape B restores `has_user_event` to the recorded original. The existing contract at + `tests/codex-history-provider.test.ts:261` must stay green — note it builds its manifest + through the writer, so after the version bump it witnesses **v2**, not v1; the + hand-constructed v1 case below is what covers v1; +- shape B where the entry has a **null `first_user_message`**, so the computed post-image + is `opencodex/vscode/0` — restores, keeping + `tests/codex-history-provider.test.ts:117` green. This is the case the pattern-based + predicate refused; +- **null → non-empty after routing (shape D)**: a **v2** entry `openai/vscode/0` with a + null message routes to `opencodex/vscode/0`, the user then sends a first message and the + row becomes `opencodex/vscode/1`. Restore must **preserve the `1`**. RED against `dev` + — the current helper recomputes from the present message, calls it B and erases it — and + red against every earlier version of this plan; +- **an ordinary v1 shape-B entry still restores.** Construct the v1 manifest by hand + rather than through the writer: `openai/vscode/0` with a non-empty message, routed to + `opencodex/vscode/1`, no `hadFirstUserMessage` field. Restore must succeed via the v1 + fallback. Round 8 caught `tests/codex-history-provider.test.ts:261` being cited as the + v1 guard when it generates its manifest through the current writer — after the version + bump that test exercises v2, so it cannot witness v1 behaviour at all; +- the `opencodex/exec/1` → `opencodex/cli/1` → `openai/cli/1` legacy bridge still + restores, keeping `tests/codex-history-provider.test.ts:345` green. This is the branch + at `src/codex/history-provider.ts:496-499` that the round-6 table dropped; +- shape C with `relabel-committed` restores to `0`; +- **D → legacy recovery → C**: the four-step sequence above on a v2 entry with + `hadFirstUserMessage = false`. Restore must **preserve the `1`** even though provenance + reads `relabel-committed`. RED against the round-8 verdict table; +- **pending + C** and **pending + D**: a crash on either side of the routing write + followed by user activity. Both must resolve and restore, preserving the `1`, rather + than refusing. RED against round 8's A/B-only pending resolution; + For pending + C this is the **expected-event-`0`** case; +- **pending + C with expected event `1`** refuses. GREEN by refusal, and the assertion + that keeps the two safe cells from being generalized into the ambiguous one. Pair it + with an `exec`-origin pending + C case that still restores, so the refusal is scoped + rather than blanket; +- shape C with `relabel-none` preserves the `1`; +- **legacy shape C only** — a v1 entry with original-tuple `0 → 1` drift — refuses, + matching `dev`. Legacy shapes A and B restore normally; the refusal is scoped to C + alone. +- crash between the pending marker and the routing write → row is shape A → resolves to + `relabel-none` and restores; +- crash between the routing write and its resolution → row is shape B → resolves to + `relabel-committed` and restores to the original. + +The last two are the crash windows, and they assert **recovery**, not refusal. A +tri-state that refuses on pending would be a more elaborate way to wedge. + +## Verification + +Focused: `bun test tests/codex-history-provider.test.ts`. Suite, typecheck and privacy +scan on `ssh lidge`. + +## Close-out + +`Closes #3026`. PR #3056 is 630 commits behind `dev`; carry its id-aware reader with +credit to its author rather than rebasing that branch, the same way round 1's wp3 +carried Ingwannu's `aec717722`. diff --git a/devlog/_plan/260831_prio70_train_round2/040_wp4_terminal_short_window.md b/devlog/_plan/260831_prio70_train_round2/040_wp4_terminal_short_window.md new file mode 100644 index 0000000000..1e09e13645 --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/040_wp4_terminal_short_window.md @@ -0,0 +1,179 @@ +# 040 — wp4: a 100% burst window is not "unknown" (#3029) + +Score 72/80. Branch: `codex/3029-terminal-short-window`, based on `dev`. +One PABCD cycle. No open PR targets this. + +## The three-branch trace + +`shortPercent` survives quota parsing as a real blocking window +(`src/codex/quota.ts:587`), then `computeCodexUsageScore` throws it away when no long +window is known (`src/codex/routing.ts:374`). From there: + +- unknown passes the headroom check (`src/codex/routing.ts:1173`), so an exhausted + account stays selectable; +- unknown suppresses auto-switch for an already-affined thread, in + `reevaluateAffinityQuota` (`src/codex/routing.ts:1745`, called at `:1965`), so the + thread never moves. The first draft cited `:1604`; that line is the unbound path, and + the affined path returns before it (audit `002`, nit 1). + +The user's report — the pool does not switch after 5-hour exhaustion — is exactly the +conjunction of those two branches. + +## Why the current behaviour is right, and where it stops being right + +The comment at `routing.ts:374` is worth reading before changing anything. Its +argument: the short window only *refines* a known long-window position. A snapshot +carrying just `shortPercent: 0` would score a flat 0 and make an account whose +weekly/monthly usage is entirely unverified look like the emptiest in the pool, so +`pickLowestUsageAmong` would send every request to it. + +That argument is sound and must be preserved. It does not extend to a terminal value. +`shortPercent: 100` is not an optimistic guess about an unobserved window — it is a +direct observation that this account cannot serve a request right now, whatever its +monthly position turns out to be. Unknown-means-selectable is correct for uncertainty +and wrong for a measured refusal. + +## What changes + +`computeCodexUsageScore` in `src/codex/routing.ts`: a **fresh** short-only snapshot at +`CODEX_EXHAUSTED_USAGE_PERCENT` returns 100. Every non-terminal short-only value, and +every terminal one whose window has already reset, keeps returning +`CODEX_UNKNOWN_USAGE_SCORE`. + +That is the whole production change, and its narrowness is the point. Scoring +short-only values generally — say, passing `shortPercent` through when it is the only +window — would resurrect precisely the flat-0 hazard the comment describes. + +### Amendment after audit round 1 (`002`, blocker 4): freshness is not optional + +`shortPercent` travels with `shortResetAt` (`src/codex/quota.ts:23-25`), and nothing +downstream checks it: `getAccountQuota()` performs no expiry check (`:510`), partial +updates preserve the old short tuple (`:319`), and disk hydration accepts a persisted +reading for six hours. + +So scoring 100 from `shortPercent` alone would keep excluding an account whose +five-hour window has since reset, or whose refresh failed — turning a transient +exhaustion into a durable exclusion. That is #3029 pointed the other way: the issue is +"an exhausted account stays selected", and shipping "a recovered account stays +excluded" trades one unusable pool for another. + +The terminal branch is therefore gated on freshness: a snapshot whose `shortResetAt` is +in the past scores unknown, not 100. When `shortResetAt` is absent the reading cannot +be aged, so it also scores unknown — the conservative direction here is the one that +keeps an account selectable, because a wrongly-selected account fails one request while +a wrongly-excluded account is invisible until someone reads the pool by hand. + +The gate is not vacuous: `shortResetAt` is populated on the very path that produces a +short-only snapshot (`src/codex/quota.ts:596-598`, where the burst primary window's +`reset_at` is retained alongside `shortPercent`), and partial updates carry it forward +(`:321`, `:327`). So the terminal branch is reachable in the case #3029 reports. + +**The unit has to be established, not assumed.** `normalizeResetAt` +(`src/codex/quota.ts:192-200`) accepts any finite non-negative number and normalizes +nothing about scale, and the GUI disambiguates by magnitude at read time — +`resetAt < 10_000_000_000 ? resetAt * 1000 : resetAt` +(`gui/src/components/QuotaBars.tsx:355`). That means both seconds and milliseconds +reach storage today. A comparison written against the wrong assumption is off by a +factor of 1000, which for a seconds value read as milliseconds means the window looks +like it reset in 1970 and every terminal reading scores unknown — a silently vacuous +fix. + +So the scoring path must apply the same magnitude normalization the GUI does, take +`now` as an injected parameter for determinism, and carry a regression for each unit. + +### Amendment after audit round 4 (`005`, blocker 3): the clock has eight call sites + +`computeCodexUsageScore` takes `(quota, plan)` and no clock (`src/codex/routing.ts:363-367`), +and its `quota` parameter type does not even include `shortResetAt`. Threading a third +argument touches eight production call sites: + +`routing.ts:1169`, `:1356`, `:1377`, `:1600`, `:1720`, `:1755`, `:1848`, and +`subagent-model-fallback.ts:229`. + +Two of them already receive a `now` and drop it before scoring — +`reevaluateAffinityQuota` (`routing.ts:1745`) and `isNativeModelQuotaExhausted` +(`subagent-model-fallback.ts:218`). Those two are where a stale reading would otherwise +survive an injected clock, because the fixture's `now` and the scorer's `Date.now()` +would silently diverge. + +**Subagent model fallback is a consumer this phase had not accounted for.** It reads the +same score to decide whether a native model is exhausted, so a stale terminal reading +pushes subagents off a model whose window has already reset — the same durable-exclusion +failure as the pool case, on a surface the plan never mentioned. Widen the quota +parameter type to carry `shortResetAt`, thread `now` through all eight sites, and add +that file's focused test to the verification list. + +## What must NOT change + +The scan lane flagged a tempting adjacent fix: treat `adapter_eof` as quota evidence. +Do not. An HTTP 200 followed by EOF is not independently reliable evidence of +exhaustion, and inferring quota state from a transport symptom would put accounts into +a blocking state on ordinary network faults. This phase fixes preselection for a +*known* 100%, which is what the reporter actually measured. + +## Regressions + +In `tests/codex-routing.test.ts`, beside the existing `shortPercent: 87` unknown case +at `:129`: + +1. `shortPercent: 100` with a future `shortResetAt` and no long window scores 100. + RED against `dev`. +2. `shortPercent: 87` with no long window still scores unknown. GREEN before and after + — the guard that keeps this phase narrow. +3. Account A carries `shortPercent: 100` with a **future `shortResetAt`** and no long + window; account B has headroom: a new thread selects B. RED against `dev` via + `:1173`. +4. Same pool, same future `shortResetAt`, but a thread already affined to A: it rebinds + to B. RED against `dev` via `reevaluateAffinityQuota` (`:1745`). + + Audit round 3 (`004`, blocker 3) caught these two describing A as carrying "only + `shortPercent: 100`" — which, under the freshness gate added one section above, + scores unknown and makes both switches unreachable. The fixtures were written before + the gate existed and were not carried forward with it. Both cases take the same + injected `now` used by the scoring assertions, threaded through the selection and + affinity paths so the fixture and the production clock cannot drift apart. + + Use seconds in case 3 and milliseconds in case 4. That proves the magnitude + normalization at the real consumers rather than only in the scoring unit test. +5. `shortPercent: 100` with `shortResetAt` in the past scores unknown and A stays + selectable. RED against a freshness-blind implementation — this is the assertion that + proves blocker 4 is closed, and it passes trivially on `dev`, so it must be quoted + as red against the *naive fix*, not against `dev`. +6. `shortPercent: 100` with no `shortResetAt` scores unknown. Same reasoning. +7. `shortResetAt` expressed in seconds and `shortResetAt` expressed in milliseconds, + both future, both score 100. This is the unit guard; without it case 1 can pass on a + millisecond fixture while production stores seconds. + +### Subagent cases, in `tests/subagent-model-fallback.test.ts` (audit `006`, blocker 3) + +Round 4 added the subagent file to the verification command and left every new assertion +in `tests/codex-routing.test.ts`. Running a file is not testing a path: if +`isNativeModelQuotaExhausted` keeps dropping its injected `now` +(`src/codex/subagent-model-fallback.ts:218`) or substitutes `Date.now()`, every listed +test stays green while stale terminal quota pushes subagents off a native model whose +window has reset. + +8. A native model whose account carries `shortPercent: 100` with a **future** + `shortResetAt` is reported exhausted. RED against `dev`. +9. The same account with an **expired** `shortResetAt` is **not** reported exhausted. + RED against a freshness-blind implementation. + +Both pass an injected `now` deliberately far from wall-clock time. That is the assertion +that catches a `Date.now()` substitution: a test whose clock matches wall time cannot +tell the two apart. + +Cases 3 and 4 are separate because they fail through different branches; one test +covering both would pass on a fix that only repaired selection. + +## Verification + +Focused: `bun test tests/codex-routing.test.ts tests/quota-scoring.test.ts tests/routing-policy-pool-quota.test.ts tests/subagent-model-fallback.test.ts`. +(`tests/codex-quota.test.ts` named in the first draft does not exist — blocker 8. The +subagent file was added by audit round 4, blocker 3.) +Suite, typecheck and privacy scan on `ssh lidge`. + +## Close-out + +`Closes #3029`. PR #3003 is unrelated to this issue and independently defective +(two pre-WHAM errors lack `quotaProbeSkipped` at its `src/codex/auth-api.ts:1013-1021`, +causing false five-minute suppression); do not link it here. diff --git a/devlog/_plan/260831_prio70_train_round2/050_wp5_stop_failure_taxonomy.md b/devlog/_plan/260831_prio70_train_round2/050_wp5_stop_failure_taxonomy.md new file mode 100644 index 0000000000..ebe84c9752 --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/050_wp5_stop_failure_taxonomy.md @@ -0,0 +1,137 @@ +# 050 — wp5: distinguish a history-only stop failure from a real one (#3008) + +Score 71/80. Branch: `codex/3008-stop-taxonomy`, based on `dev`. One PABCD cycle. + +## The defect + +`src/update/index.ts:250-267` aborts the update when +`stop.status !== 0 || readPid() || readRuntimePort()`. `handleStop()` sets a non-zero +status after history restoration (`src/cli/index.ts:739-748`) — that is, after the +proxy and service have already come down cleanly. So a failed Codex-history cleanup +is indistinguishable from a proxy that refused to die, and the update aborts with the +service already stopped: no listener, no PID, old package still installed. + +The consequence is visible in the code itself: the history warning at +`src/update/index.ts:269-275` is unreachable. Someone wrote the correct handling for +this case and the guard above it never lets control reach it. + +## Why PR #3040 is the wrong fix + +It proceeds for every non-zero *or signal-killed* stop whenever PID and runtime files +are absent (PR-head `src/update/index.ts:42-47`), then reports the proxy stopped +without probing the captured endpoint (`:279-296`). It also folds `status: null` into +"nothing to warn about". + +That inverts the safety property. The comment at `src/update/index.ts:245-250` exists +because replacing package files under a live proxy leaves it dynamic-importing a mix +of old and new modules. Absent PID and runtime files are weak evidence of death — they +are exactly what a crashed-but-still-listening or externally-supervised proxy looks +like. "Proceed unless we can see a proxy" trades a false abort for a corrupted +runtime, which is the worse direction. + +The fix is not to widen the proceed condition. It is to stop discarding the +information that distinguishes the cases. + +## What changes + +1. **A stop outcome carried on the wire, not in the type system** — + `src/update/stop-contract.ts` defines the vocabulary, but the representation has to + be a dedicated exit code (or a machine-readable receipt file) because the value + crosses a subprocess boundary. See the amendment below. +2. **`src/cli/index.ts`** — `restoreSharedClientStateAfterStop()` reports Codex-history + failures separately from other teardown failures. `handleStop()` emits the + history-only result *only* when service and proxy shutdown both succeeded and no + other cleanup step failed. +3. **`src/update/index.ts`** — proceed for status `0` or the history-only result, and + only after PID/runtime checks *and* an identity probe against `capturedListen`. + Abort for ordinary non-zero and for `status: null`. The warning at `:269-275` + becomes reachable on the reported path. +4. **`bin/ocx.mjs`** — the second updater. See the amendment below. + +### Amendment after audit round 1 (`002`, blocker 1): there are two updaters + +The first draft fixed the Bun updater only. Dashboard npm updates run through +`bin/ocx.mjs`, dispatched at `src/update/job.ts:428`, and that launcher carries its own +independent guard which still aborts on any non-zero stop (`bin/ocx.mjs:346`). Since +#3008 is reported from the dashboard, the original plan could have gone fully green +while the reported path stayed broken — the exact false-completion shape this train's +receipts exist to prevent. + +Two consequences: + +- **The contract needs a runtime representation.** A TypeScript union does not survive + `spawnSync`. `handleStop()` must signal the history-only outcome with a dedicated + exit code, and both consumers must decode that same code. Choosing a distinct code + (rather than parsing stdout) keeps `bin/ocx.mjs` free of a TypeScript import it + cannot have. + + `handleStop()` sets `process.exitCode = 1` and nothing else + (`src/cli/index.ts:747`), and the launcher mirrors the child's code verbatim + (`bin/ocx.mjs:659-663`), so a distinct value propagates without further plumbing. + Assert in a test that `handleStop` still *returns* rather than exiting inline, + because `restart` and the tray coordinator depend on that (comment at `:745-746`). + + **The occupied set is larger than `src/cli/index.ts` suggests** (audit `004`, + blocker 4). The process exit is supplied by `dispatchCommand` (`:989`), and its own + contracts already return `2` (`src/cli/dispatch.ts:193`, `:265`, `:598`, `:675`), + `4` (`:277`) and `64` (`:560`). Round 2 checked only `index.ts` and concluded the + low codes were free; they are not. Choosing `2` would have made a history-only stop + indistinguishable from a config conflict, and `bin/ocx.mjs` would have propagated the + confusion faithfully. + + Reserve **`79`** — outside `{0, 1, 2, 4, 64, 130}`, above the `sysexits` block + (64-78), below `128 + signal`, and unused across `src/cli/`, `bin/ocx.mjs` and + `src/update/`. Define it once as a named constant in a plain-ESM module that both the + TypeScript updater and plain-Node `bin/ocx.mjs` can import, and test that the exact + value survives the `spawnSync` round trip in both lanes. + + Round 3 proposed `70` on the claim that it sat outside `sysexits`. Audit round 4 + (`005`, blocker 4) corrected that: `sysexits.h` runs 64-78 and `70` is + `EX_SOFTWARE`. Nothing in this repository would have collided, but a code whose + justification is false is a code the next audit re-opens. +- **Both lanes are in scope for this phase.** Fixing one and leaving the other is not a + smaller version of this fix; it is a fix that does not close the issue. + +The correction from the nit list also applies: the warning at `src/update/index.ts:269-275` +is unreachable on the reported failed-history path, not globally. A skipped history +outcome can still produce overall stop success while a manifest remains +(`src/codex/inject.ts:1652`), so "history restore incomplete" and "history restore +failed" are different states and only the second one is this issue. + +`status: null` means the stop child was killed by a signal without producing an exit +code. It carries no information about whether teardown completed, so it aborts. + +## Regressions + +In `tests/update-stop-first.test.ts`, which already owns the stop-ordering surface, and +the same matrix exercised against the npm launcher rather than only against the +classifier (audit `003`): + +| case | expected | +| --- | --- | +| history-only result, no live proxy | proceed, with the warning printed | +| generic status `1`, no PID/runtime records | abort | +| `status: null` | abort | +| any live identity, PID, or runtime record | abort, regardless of status | + +Row 1 is RED against `dev` (currently aborts). Row 2 is RED against PR #3040 (which +proceeds) — quote both reds in the receipt, because a test suite that is only red +against `dev` would not prove this phase avoids #3040's failure mode. + +Per blocker 1, a test that calls the classifier directly proves nothing about either +updater consuming it. At least one case must drive the real `bin/ocx.mjs` path with a +stubbed `stop` that exits with the history-only code, asserting the update proceeds; +and one must drive it with a generic non-zero exit, asserting it aborts. Those two are +the assertions that would have caught the original single-lane plan. + +## Verification + +Focused: `bun test tests/update-stop-first.test.ts tests/update-job.test.ts tests/update-transactional.test.ts tests/cli-dispatch.test.ts`. +(`tests/update.test.ts` and `tests/update-stop-classification.test.ts` named in earlier +drafts do not exist — blocker 8 and audit `003`.) +Suite, typecheck and privacy scan on `ssh lidge`. + +## Close-out + +`Closes #3008`. Comment on PR #3040 naming the fail-open case rather than closing it +silently; the author found a real defect and only the remedy is wrong. diff --git a/devlog/_plan/260831_prio70_train_round2/060_wp6_wham_401_refresh.md b/devlog/_plan/260831_prio70_train_round2/060_wp6_wham_401_refresh.md new file mode 100644 index 0000000000..7dc484b7ef --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/060_wp6_wham_401_refresh.md @@ -0,0 +1,139 @@ +# 060 — wp6: refresh a stored token before quarantining it on WHAM 401 (#3019) + +Score 70/80 (blast 17, credential 17, evidence 18, shippability 18). Branch: +`codex/3019-wham-401-refresh`, based on `dev`. One PABCD cycle. + +The first draft declared this a stacked child of wp4 on the theory that both touch the +pool quota path. Audit round 1 (`002`, blocker 7) disproved it: wp4 changes +`src/codex/routing.ts` scoring, wp6 changes auth/token recovery, and PR #3020's file +set contains no `routing.ts`. A false dependency serializes two independent phases and +invents a rebase that can only add conflict, so wp6 bases on `dev`. + +## The defect + +Account-list quota calls `getValidCodexToken`, sends one WHAM request, and converts +any 401 straight into `needsReauth` (`src/codex/auth-api.ts:971`, `:978`). A bare 401 +with no structured body is exactly what a stale-but-refreshable bearer produces after +a plan change, so a valid credential gets marked as needing re-login and the operator +is told to re-authenticate an account that was fine. + +The recovery primitive already exists: `forceRefreshCodexPoolToken` at +`src/codex/account-store.ts:588`. Nothing calls it from this path. + +17/20 on credential risk is not about leakage — it is that the system throws away a +working grant and demands the user replace it. + +## What changes + +Carry PR #3020's core sequence, and only that: first WHAM 401 → +`forceRefreshCodexPoolToken` → exactly one replay with the rotated bearer. Terminal +classification (`needsReauth = true`) only after structured terminal evidence in the +response body, or after the refresh itself fails terminally. + +Bounded recovery state in a new `src/codex/quota-401-recovery.ts`: one record per +(account, credential generation), registered with the existing state sweeper so the map +cannot grow for process lifetime — the same unpruned-map defect the scan lane found in +PR #3003. + +### Amendment after audit round 1 (`002`, blocker 5): generation is not lineage + +The first draft said generation-keying fences a concurrent rotation. The primitive's own +documentation says it does not. `forceRefreshCodexPoolToken` returns `selfRefreshed` +precisely because generation alone cannot distinguish this caller's refresh CAS from +somebody else's replacement (`src/codex/account-store.ts:575-590`), and the comment +there spells out the trap: a successful token response can rotate the refresh grant +while returning a byte-identical access token, so `rotated === false` does not mean +"nothing happened". + +The concrete failure the draft would have shipped: a concurrent reauthentication moves +`G → G+1`; the recovery record marks `G+1` spent on behalf of the *old* rejection; the +new credential's own first 401 then finds its budget already consumed and is quarantined +without ever being refreshed. The plan's own bug, arriving as the issue it set out to +fix. + +So the record must key on lineage, not on a generation number alone: + +- Spend the budget when this caller's CAS moved the credential (`selfRefreshed`), **and + also when this caller joined an in-flight refresh of the same lineage**. +- Only a genuine external replacement resets the budget: a new grant deserves its own + recovery attempt. +- Fence the replay on the generation **returned** by the refresh, never on the one that + was rejected. The comment at `:583-586` says this explicitly. +- `rotated === false` means replaying would earn the same 401, so do not replay; report + transient and let the next poll try. + +### Amendment after audit round 3 (`004`, blocker 5): false means two different things + +Keying on `selfRefreshed` fixed the generation error and introduced a subtler one. The +primitive returns `selfRefreshed === false` in two unrelated situations: an external +replacement, and a caller that joined an in-flight refresh and adopted the stored result +(`src/codex/account-store.ts:639-645`). The second is the *same* lineage, not a +replacement. + +Under "reset on false", two concurrent 401 callers produce one refresh, the joiner +clears the owner's spent fence, and a later 401 refreshes again — the bounded-retry +property this phase exists to establish, gone. Regression case 4 does not catch it, +because it observes only the concurrent exchange and never the third call. + +So the recovery record distinguishes three states, not two: + +| observed | meaning | budget | +| --- | --- | --- | +| `selfRefreshed === true` | this caller's CAS moved it | spend | +| joined an in-flight refresh, adopted the stored result | same lineage | spend | +| credential replaced by someone else | new grant | reset | + +If the returned contract cannot express the middle row today, the phase adds that +distinction to the primitive's return rather than guessing at the call site. Inferring +it from generation arithmetic is what blocker 5 of round 1 already ruled out. + +## What of PR #3020 to leave behind + +Seven files, ~1300 lines, conflicting with `dev`, and its own full-suite run timed +out. The core sequence is right and the surrounding scope is not reviewable in one +cycle. Carry the sequence with credit; do not rebase the branch. + +## Security note + +This phase touches credential handling, so it needs explicit security review per +`MAINTAINERS.md`. Two properties to state in the PR description and assert in tests: +the rotated bearer is never logged or serialized, and a single 401 can trigger at most +one refresh-and-replay per credential generation — an unbounded retry against an +upstream 401 is a self-inflicted credential-stuffing loop. + +## Regressions + +In `tests/codex-auth-api.test.ts`: + +1. Request order `old bearer → 401 → refresh → rotated bearer → WHAM 200`; the result + carries quota and `needsReauth: false`. RED against `dev`, which never refreshes. +2. A second bare 401 after the replay stays transient — `needsReauth` remains false and + no second refresh is issued for the same generation. +3. A 401 carrying structured terminal evidence sets `needsReauth: true` without + attempting a refresh. +4. Two concurrent quota calls for one account issue at most one refresh. +5. An externally-replaced credential (`selfRefreshed === false`, generation advanced) + gets its own refresh-and-replay budget rather than inheriting the old rejection's + spent one. RED against the generation-only design blocker 5 describes. +6. `rotated === false` on a successful refresh does not replay, and does not quarantine + either. +7. Delete-and-re-add of the account clears any recovery record for it. +8. Three calls, not two: two concurrent 401s that collapse into one refresh, then a + third 401 on the returned generation. Assert **no second refresh** is issued. RED + against the round-1 amendment as written — case 4 passes there and this one does not, + which is the whole reason it exists. + +Case 3 is the one that keeps this from becoming "never quarantine anything". Cases 5-8 +are the lineage proofs: case 5 is red against the plan as originally written, and case 8 +is red against the round-1 amendment. Quote both reds in the receipt. + +## Verification + +Focused: `bun test tests/codex-auth-api.test.ts tests/codex-account-store.test.ts`. +Suite, typecheck and privacy scan on `ssh lidge`. `bun run privacy:scan` is +load-bearing here rather than routine. + +## Close-out + +`Closes #3019`. Comment on PR #3020 crediting the sequence and naming the scope that +was left out. diff --git a/devlog/_plan/260831_prio70_train_round2/070_outcome.md b/devlog/_plan/260831_prio70_train_round2/070_outcome.md new file mode 100644 index 0000000000..a27767705e --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/070_outcome.md @@ -0,0 +1,130 @@ +# 070 — outcome and receipts + +Filled in as each work-phase closes. Every receipt records the command, the host, the +exit code, and pass/fail/skip counts. Local full suites are forbidden for this train, +so suite receipts name `lidge`. + +## wp0 — rescan and roadmap (docs-only) + +- Status: closing. +- Deliverable: 19 docs — `000` plan with the written-down rubric and component scores, + `001` scan evidence including below-bar components, `002`-`011` audit syntheses, + `010`-`060` decade docs for the six >=70 targets, `070` receipts. +- Research: four read-only `gpt-5.6-sol` high-effort lanes, split by cluster. Every + load-bearing claim was re-verified in-tree by the main session before it entered a + doc. +- Audit: ten adversarial `gpt-5.6-sol` rounds, all FAIL, each amended rather than + argued with. Round 1 found nine blockers in the original plan. Round 2 read a stale + index (my staging error) but produced two real refinements anyway. Round 3 found five, + four of them **inside round 1's amendments**. Round 4 found four more, including a + migration hazard that would have refused every manifest on disk. Round 5 cleared wp2 + and wp5. Round 6 cleared wp4. Round 7 cleared wp1 and wp6 — zero findings across five + phases — and round 8, the first resumed reviewer, closed the exec bridge and found the + fourth shape wp3 needed. Round 9 confirmed the partition disjoint and found two + transition defects: pending accepted only two of four shapes, and a D row pulled back + into C by legacy recovery would have had its user activity erased. Round 10 reduced the + remainder to one cell — pending + shape C with an expected route event of `1` — where + two histories produce an identical state and nothing durable separates them, so it + refuses by design. `002`-`011` record all of it. +- Phases cleared by audit: wp1, wp2, wp4, wp5 and wp6 all drew zero findings in round 7. + wp3 absorbed five consecutive rounds on a single predicate before round 7 revealed why + — `rowMatchesExpectedPostImage` already existed in the tree and every version of the + plan was reimplementing it, each missing a different branch (`008`). +- Round-1 residue: PR #3079 opened for the dangling + `codex/devlog-entitlement-stack` branch, rebased from `1b862f5b5` onto `dev` + = `5cec0a33e` as `b1f55b807`. + +### What the scan changed about the plan + +Recording this because the delta between what the titles suggested and what the tree +says is the actual output of wp0: + +- **Two issues came off the board as already fixed.** #1527's four named mechanisms + are all on `dev` (`c37843502`, `6ec79e443`, `62df78d8d`, `60526d7af`), and #3070's + traceability half landed in `b68edc077`. +- **One came off as contradicted by the tree.** #3059 asserts a page unmount that + `gui/src/client-resource.ts:337-345` cannot produce, and could not produce at the + reported commit either. +- **Three PRs are fixes pointed at the wrong shape.** #3040 fails open on any + unclassified stop, #3041 can resurrect retired model ids, and #3067 relaxes a + path-ownership check to a same-segment wildcard. Each found a real defect; each + remedy would have shipped a worse one. +- **Two PRs carry vacuous regressions.** #3063 changes only production code while + claiming test coverage, and #3038's tests call the helper directly, so they pass with + every production call site deleted. +- **One "load-bearing" comment turned out to be a trade, not a law.** The batch + `query` omission in `src/bridge.ts:149-168` is defended as necessary for codex-rs + rendering; wp1 trades that rendering for a strict-validator pass and says so out + loud instead of quietly deleting the comment. +- **Two premises the plan rested on were false, and the audit caught both.** wp3's + "OpenCodex never writes `has_user_event`" is contradicted at + `src/codex/history-provider.ts:1158` and `:1013`, with an existing test depending on + the write. wp5's "one updater" is contradicted by `bin/ocx.mjs:346`, reached via + `src/update/job.ts:428`, which is the lane the reporter actually used. +- **A unit ambiguity would have made wp4's fix silently vacuous.** + `shortResetAt` arrives in either seconds or milliseconds — the GUI disambiguates by + magnitude at `gui/src/components/QuotaBars.tsx:355` — so a freshness comparison + written against one assumption scores every terminal reading as unknown under the + other. +- **Four of the five round-3 blockers were in the fixes, not the original plan.** A + remedy that is right about the defect can still be wrong about its own boundary: + wp3's tuple test has a reachable false positive via route-then-legacy-recover, wp2's + release-on-supersession un-reserves right before the synchronous shutdown write, + wp5's "free" exit codes were occupied by `dispatchCommand` rather than by + `handleStop`, and wp6's `selfRefreshed === false` conflates external replacement with + joining an in-flight refresh. Round 1 alone would have shipped all four as working + code with passing tests. +- **One amendment contradicted its own test plan.** `040` added the freshness gate to + the contract and left two fixtures describing an account with no `shortResetAt`, + which the gate scores unknown — the switches those cases assert could never fire. + +### Audit outcome — PASS at round 11 + +Round 11 returned **PASS with zero blocking findings**: the pending+C three-way split is +correct, the `exec`-origin justification holds (`routeExec` moves `exec → cli` and legacy +recovery leaves `cli` alone, so it cannot recreate an original `opencodex/exec/*`), the +refusal removes no restore `dev` performs today, and both new regressions are correctly +red-then-green. Implementation may begin. + +Findings per round: 9, 5, 4, 4, 3, 2, 3, 3, 1, 0. Rounds 8-11 were the same reviewer +resumed rather than respawned, so each closure round judged its own prior findings. + +### Receipt — wp0 (docs-only) + +``` +bun test tests/repo-hygiene.test.ts -> exit 0, 12 pass / 0 fail / 23 expect() +``` + +That is the focused file covering a tracked `devlog/` change (no-gitlink and +tracked-devlog assertions). No source, test, config or GUI file is touched by wp0, so no +other focused set applies and no `lidge` suite run is warranted for a docs-only phase. + +## wp1 — #3071 web_search_call `query` + +- Status: pending. +- Receipt: _pending_ + +## wp2 — #3032 spill disk budget + +- Status: pending. +- Receipt: _pending_ + +## wp3 — #3026 forked-rollout restore + +- Status: pending. +- Receipt: _pending_ + +## wp4 — #3029 terminal short window + +- Status: pending. +- Receipt: _pending_ + +## wp5 — #3008 stop failure taxonomy + +- Status: pending. +- Receipt: _pending_ + +## wp6 — #3019 WHAM 401 refresh + +- Status: pending. +- Receipt: _pending_ From b4303bb9eba4a6d7614a4468400570f1ae1a93d1 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 00:35:17 +0900 Subject: [PATCH 10/42] docs(devlog): record the wp6/wp5/wp4 entitlement stack outcome (#3079) * docs(devlog): record the wp6/wp5/wp4 entitlement stack and two CI test defects wp6 closed #3023 with a credential mutation epoch. wp5 removes the #3022 class by making absence evidence only when the question was capable of answering. wp4 answers the part of #3023 that was never about rows: discovery: ok beside missing models, with no entitlement freshness reported anywhere. Also records two test defects found along the way, neither of them ours: an 80ms shutdown fallback reserve that expired under load and tombstoned an unrelated response, and a launcher startup wait whose 20s budget was itself the failure. * docs(devlog): index every work phase and correct three stale claims Addresses the review on #3079. The roadmap table listed wp0-wp3 while the unit documents six work phases. wp4, wp5 and wp6 were born from audit blockers rather than the frozen scan, so they are now indexed as audit-derived additions with their origin recorded. The deliverable count said 12 and enumerated through 050, which was true when wp0 closed and is not true now. It reads 14 with the enumeration corrected. The wp3 heading still said "pending merge of PR #3044"; that PR is on dev as e5d588669. --- .../000_plan.md | 10 +++ .../060_wp6_ensure_freshness.md | 70 +++++++++++++++++++ .../070_outcome.md | 65 +++++++++++++++-- 3 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 devlog/_plan/260831_prio70_entitlement_and_spill_train/060_wp6_ensure_freshness.md diff --git a/devlog/_plan/260831_prio70_entitlement_and_spill_train/000_plan.md b/devlog/_plan/260831_prio70_entitlement_and_spill_train/000_plan.md index e9a52b9c7f..45ef660dc8 100644 --- a/devlog/_plan/260831_prio70_entitlement_and_spill_train/000_plan.md +++ b/devlog/_plan/260831_prio70_entitlement_and_spill_train/000_plan.md @@ -11,6 +11,16 @@ decade doc as one full PABCD cycle. | wp2 | #3023 roster TTL expiry drops entitled rows | `020` | 71/80 | | wp3 | #3011 Windows ACL spill stall (PR #3018 audit) | `030` | 71/80 | +Three more work phases were born from audit blockers rather than from the frozen scan, +so they are recorded here as audit-derived additions rather than as part of the original +three-target scope: + +| wp | target | doc | origin | +| --- | --- | --- | --- | +| wp4 | entitlement diagnostic transport | `040` | split out of wp2 by audit round 1 (`004`, blocker 4) | +| wp5 | tri-state entitlement authority | `050` | split out of wp1 by audit round 2 (`005`, blocker 1) | +| wp6 | #3023 ensure-freshness implementation | `060` | wp2 closed as a planning cycle; its implementation is wp6 | + ## Why this order wp1 is the stack base: it changes what `resolveCodexModelEntitlements` records diff --git a/devlog/_plan/260831_prio70_entitlement_and_spill_train/060_wp6_ensure_freshness.md b/devlog/_plan/260831_prio70_entitlement_and_spill_train/060_wp6_ensure_freshness.md new file mode 100644 index 0000000000..e82f598c7a --- /dev/null +++ b/devlog/_plan/260831_prio70_entitlement_and_spill_train/060_wp6_ensure_freshness.md @@ -0,0 +1,70 @@ +# wp6 — #3023: implement the audited ensure-freshness design + +Status: implemented, verified, landed via PR #3054. + +## What wp2 established and deliberately did not build + +wp2 closed as a planning cycle. Four audit rounds each found a real hole in the +cross-answering behaviour, and the round-4 output was a flight key, not a patch: + + (normalized candidate set, client version, mutation epoch, identity vector, workset) + +The reason wp2 did not implement is that the first three candidate designs each +answered a caller from a flight that had been started for a *different* question. +A memo keyed only on "are we logged in" cannot distinguish a roster derived +before a credential swap from one derived after it, so a fresh entitlement and a +revoked one both read as a hit. + +## The defect in observable terms + +`/api/models`, `/api/client-config` and `ocx export` share +`listManagementModelRows`. The entitlement snapshot behind it carried an expiry +but no binding to the credential state that produced it. Two consequences: + +1. After `MODEL_ROSTER_TTL_MS` elapsed, the surfaces kept answering from the + expired snapshot instead of re-deriving. +2. When `auth.json` was replaced out of band, the snapshot stayed authoritative. + A newly entitled account saw the old roster; a logged-out account still saw + models it no longer had. + +## Implementation + +`src/codex/credential-mutation-epoch.ts` (new) holds a monotonic counter bumped +at every credential mutation point — `account-store`, `main-account`, +`native-profile-manager`. An entitlement snapshot records the epoch it was +derived under. A read is a hit only when both the epoch and the expiry still +match; otherwise it re-derives. Consumers move to the freshness-checked accessor +(`server/management/model-rows.ts`, `sidecar/candidates.ts`). + +The logged-out result is memoized deliberately, because the dashboard polls this +path ~24 times a minute and c-9 requires zero credential enumerations after the +first. + +## Cost contract + +Steady state, nothing mutated and the snapshot live: **0** additional +`accountCredentialSnapshot` calls, **0** token refreshes, **0** network +requests. This is the property that makes the fix safe to put on a polling path. + +## Evidence + +Nine regressions written red-first against the unfixed code: steady-state +credential reads, logged-out memo, memo invalidation on mutation, +`/api/client-config` fetch count 1 vs 0, expired `/api/models`, real-handler +`ocx export`, local epoch write during an in-flight derive, external +`auth.json` replacement during an in-flight derive, and account B expiring +during an A-only flight. + +The rejection-path tests are labelled characterization, not red-first. They +describe behaviour that already held; calling them regressions would overstate +what they prove. + +`ssh lidge` at exact head `6298fae32`: privacy scan clean, typecheck clean, +full suite **16524 pass / 0 fail / 16 skip**, `EXIT=0`. 208 focused tests pass +locally across the touched suites. + +## Residual + +The epoch is process-local. Two proxies sharing one `OPENCODEX_HOME` still +observe each other only through the expiry, not the epoch. Not reachable in the +supported single-proxy topology, and out of scope here. diff --git a/devlog/_plan/260831_prio70_entitlement_and_spill_train/070_outcome.md b/devlog/_plan/260831_prio70_entitlement_and_spill_train/070_outcome.md index d9877e3d72..ad54ab7334 100644 --- a/devlog/_plan/260831_prio70_entitlement_and_spill_train/070_outcome.md +++ b/devlog/_plan/260831_prio70_entitlement_and_spill_train/070_outcome.md @@ -7,8 +7,10 @@ this train, so suite receipts name `lidge`. ## wp0 — roadmap (docs-only) - Status: closing. -- Deliverable: 12 docs — `000` plan, `001`-`003` research, `004`-`006` audit - syntheses, `010`/`020`/`030`/`040`/`050` decade docs, `070` receipts. +- Deliverable at the time wp0 closed: 12 docs — `000` plan, `001`-`003` research, + `004`-`006` audit syntheses, `010`/`020`/`030`/`040`/`050` decade docs, `070` + receipts. The unit now holds **14**: audit rounds 4-8 added `007`-`009`, and wp2's + implementation split out as `060`. - Branch: `codex/prio70-train-260831` at `903243d04`. - Research: three read-only `gpt-5.6-sol` high-effort lanes. Every load-bearing claim was re-verified in-tree by the main session before it entered a doc. @@ -143,10 +145,11 @@ a warm roster and all carry the gated rows. #3023 is about what happens once the warm path is intact and the wp6 work is scoped to expiry, not to the rows themselves. -## wp3 — LANDED (pending merge of PR #3044) +## wp3 — LANDED #3011 fixed by carrying Ingwannu's `aec717722` and closing the shutdown boundary it -opened. His commit is the base of the branch, unmodified and credited. +opened. His commit is the base of the branch, unmodified and credited. PR #3044 is +merged into `dev` as `e5d588669`. Five review rounds, each returning FAIL until the last, and every finding was a real defect rather than a style note. Worth recording as a sequence, because each fix @@ -191,3 +194,57 @@ rather than by editing the version by hand. Residual risk: a real Windows host is still needed for NTFS unlink semantics and `icacls` timeout behaviour while a path is held. + +## wp6 / wp5 / wp4 — the entitlement stack (landed 260831) + +Three phases landed as a dependency-ordered stack on top of the spill work. + +**wp6 (#3054, `0844dc9a9`) closed #3023.** A credential mutation epoch binds an +entitlement snapshot to the credential state that produced it; a read is a hit +only when both epoch and expiry still match. Steady state stays free: 0 extra +credential snapshots, 0 token refreshes, 0 network calls on the ~24/min polling +path. + +The review round that mattered was the second one. The first submission's two +race regressions returned *usable* credentials, so they never reached the +negative-memo publication fence at all — **deleting the epoch and identity fences +left both tests green.** A third test could not distinguish absence-observation +time from settlement time. The implementation was correct; the tests guarding its +riskiest lines were worthless. Three replacements were driven red by removing +each fence in turn, and the reviewer reproduced every red independently rather +than trusting the report. + +**wp5 (#3057) removes the #3022 class, not its instance.** wp1 fixed the current +failure by asking under a measured `0.144.0`. wp5 makes absence evidence only +when the question was capable of producing an answer: below-minimum omission is +`unknown` on the 15s failure TTL, at-or-above omission is `denied`, a present row +is `granted` regardless of version, and `gpt-daybreak-blue-latest` — which has no +row in `upstream-models.json` — keeps omission-as-denial rather than being handed +a guessed minimum. Projections still return only `granted`; that is what keeps +the gate fail-closed. One widening bug surfaced during implementation (an +unconfirmed present row briefly read as `granted`) and the guard test now pins it. + +**wp4 (#3058) answers the part of #3023 that was never about rows.** The reporter +saw `discovery: {"status":"ok"}` beside missing models and read it as the proxy +lying. It was answering a different question, and nothing reported entitlement +freshness at all. Provenance had to come first: the parsed-empty path and the +catch path produced byte-identical cache entries, so reporting them as two states +would have been the same fabrication in a new field. + +### Two CI defects found along the way + +Neither was ours, and both were real. + +`shutdown cleanup failure still persists unrelated response state` used a real +80ms wall-clock fallback reserve. Drain expiry is forced by an `icacls` gate and +is deterministic; the reserve was not. Under load it expired first, terminalizing +the unrelated response into a `spill-failed` tombstone. Measured on Linux at +`origin/dev`: eight parallel runs of that single test spanned **2.08s to 109.38s**, +and a six-way run reproduced the failure 1-in-6. Fixed in #3055 by sizing the +reserve so it can never be the thing that runs out; 10/10 parallel runs pass +afterwards, the longest at 45.51s. + +`Unix install rejects delayed detached redispatch` failed once on macOS CI and +passed on rerun and on three local macOS runs. It pairs a 1500ms observation +window with a fixed `time.sleep(0.5)` in a forked child, which is thin on a slow +runner. Recorded rather than fixed: it has not reproduced. From 91b2c4e1983bddf38a95465b2c2b5d38c9c46c19 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 01:10:36 +0900 Subject: [PATCH 11/42] feat(cli): let the terminal resolve a conflict too (#3088) * feat(cli): let the terminal resolve a conflict too PR #3084 gave the dashboard a way past a conflicted client config. The CLI got nothing, so `ocx integration client enable` still dead-ends on exactly the state the overwrite path exists to escape -- and it strands the user who has no browser: an SSH session, or an agent driving the proxy. Adds `--overwrite-conflict`, spelled the way `restore --confirm-drift` already is. Never assumed: without the flag a conflict is still refused, and the field is omitted from the request entirely rather than sent as false, so a proxy on an older build sees the request it has always seen. `--overwrite-conflict` with `disable` fails locally instead of being forwarded. The route answers 400 for that pair, but a usage error names the flag that is wrong where the route reply arrives as a generic failed request. Forcing a disable over a conflict deletes a block we never wrote, which is the one thing the refusal exists to prevent. Docs said the switch "locks and disable refuses rather than guessing", which is now only half true. The English guide describes Replace and the new flag, and the three translated copies of that page get the flag block so they do not contradict the source. Verification: 41 pass in tests/cli-headless-parity.test.ts, driven red twice -- dropping the flag from the request body, and neutering the disable guard. tsc clean, privacy:scan clean, skill:surface:check current, test:changed 86 pass. * test(gui): pin the two things that keep the overwrite dialog readable on a phone The conflict dialog was verified at desktop width only. Measured at 390px in both themes it is fine -- 370px wide at left:10, no horizontal overflow anywhere on the page, the Replace button not clipped, and the config path inside its container -- but two of those depended on details nothing was checking. The dialog is 370px wide and a config path is one long unbroken token, so the path needs an in-word break opportunity or it overflows and the single fact the user needs (which file is about to change) goes off screen. Two things have to hold: the path renders inside a <code> element, and that element is allowed to break. Guard one asserts the path is in a <code> with a long realistic path. Guard two asserts the stylesheet rule, because a CSS declaration has no type or render coverage in a DOM-less suite. Verification: 41 pass across the two files. Both driven red -- rendering the path as bare text, and dropping overflow-wrap from the dialog rule. The first attempt at the CSS falsification was itself wrong: it replaced the FIRST overflow-wrap in the file, which belongs to .integration-path, and the guard stayed green. Re-run against the whole declaration it goes red, which is the only version worth keeping. * test(gui): use a synthetic home in the dialog path fixture privacy:scan rejects a committed /Users/<name>/ path, and the fixture I added carried a real one. The scan ran clean before that test existed, which is how it reached CI. --- .../content/docs/fr/guides/integrations.md | 10 ++++++ .../src/content/docs/guides/integrations.md | 18 ++++++++++ .../content/docs/tr/guides/integrations.md | 10 ++++++ .../content/docs/zh-tw/guides/integrations.md | 9 +++++ gui/tests/integration-marks.test.ts | 16 +++++++++ gui/tests/integrations-surfaces.test.tsx | 31 ++++++++++++++++ src/cli/integrations.ts | 26 ++++++++++++-- tests/cli-headless-parity.test.ts | 35 +++++++++++++++++++ 8 files changed, 153 insertions(+), 2 deletions(-) diff --git a/docs-site/src/content/docs/fr/guides/integrations.md b/docs-site/src/content/docs/fr/guides/integrations.md index c7f653c885..d61ea1c2f8 100644 --- a/docs-site/src/content/docs/fr/guides/integrations.md +++ b/docs-site/src/content/docs/fr/guides/integrations.md @@ -145,6 +145,16 @@ ocx integration client history --client hermes ocx integration client restore --op <opId> [--confirm-drift] ``` +`--overwrite-conflict` est la forme terminale de **Replace** : + +```bash +ocx integration client enable --client zcode --overwrite-conflict +``` + +Comme `--confirm-drift`, il n'est jamais supposé : sans lui, un conflit reste refusé. +Il ne s'applique qu'à `enable` ; forcer un *disable* sur un conflit supprimerait un bloc +que nous n'avons jamais écrit, donc cette combinaison est rejetée. + Pour MiniMax Code, connectez une fois le fournisseur puis utilisez l’enveloppe qui vérifie la connexion : ```bash diff --git a/docs-site/src/content/docs/guides/integrations.md b/docs-site/src/content/docs/guides/integrations.md index 8e5957b4c1..a5125911eb 100644 --- a/docs-site/src/content/docs/guides/integrations.md +++ b/docs-site/src/content/docs/guides/integrations.md @@ -124,6 +124,14 @@ written as whole documents), or whenever our own entries were edited, the switch locks and disable refuses rather than guessing which edits were yours. +That lock is no longer a dead end. A conflicted client shows **Replace** next to its +switch, on both the overview card and the client's own page. It replaces whatever +holds our settings with the block opencodex would write, and it asks first: the +dialog names the file, says what is lost, and points at the snapshot that makes it +undoable. The switch itself stays locked, because the switch cannot know which edits +you meant to keep — only you can say so. Nothing else is relaxed: a file we cannot +parse, or one whose structure we cannot reason about, still refuses. + ## What to expect, honestly **Formatting is generally not preserved.** Applying parses a config and writes it back @@ -175,6 +183,16 @@ ocx integration client history --client hermes ocx integration client restore --op <opId> [--confirm-drift] ``` +`--overwrite-conflict` is the terminal form of **Replace**: + +```bash +ocx integration client enable --client zcode --overwrite-conflict +``` + +Like `--confirm-drift`, it is never assumed — without it a conflict is still refused. +It applies only to `enable`; forcing a *disable* over a conflict would delete a block +we never wrote, so that combination is rejected. + For MiniMax Code, connect the provider once and launch through the checked wrapper: ```bash diff --git a/docs-site/src/content/docs/tr/guides/integrations.md b/docs-site/src/content/docs/tr/guides/integrations.md index e0c1df8bb6..3c5a5c126a 100644 --- a/docs-site/src/content/docs/tr/guides/integrations.md +++ b/docs-site/src/content/docs/tr/guides/integrations.md @@ -168,6 +168,16 @@ ocx integration client history --client hermes ocx integration client restore --op <opId> [--confirm-drift] ``` +`--overwrite-conflict`, **Replace** eyleminin terminal karsiligidir: + +```bash +ocx integration client enable --client zcode --overwrite-conflict +``` + +`--confirm-drift` gibi asla varsayilmaz: bayrak yazilmadan catisma yine reddedilir. +Yalnizca `enable` icin gecerlidir; bir catismanin uzerine *disable* zorlamak hic +yazmadigimiz bir blogu silecegi icin bu birlesim reddedilir. + MiniMax Code için sağlayıcıyı bir kez bağlayın ve denetimli başlatıcı üzerinden çalıştırın: ```bash diff --git a/docs-site/src/content/docs/zh-tw/guides/integrations.md b/docs-site/src/content/docs/zh-tw/guides/integrations.md index 8a880fef06..1bcfa4214e 100644 --- a/docs-site/src/content/docs/zh-tw/guides/integrations.md +++ b/docs-site/src/content/docs/zh-tw/guides/integrations.md @@ -80,6 +80,15 @@ ocx integration client history --client hermes ocx integration client restore --op <opId> [--confirm-drift] ``` +`--overwrite-conflict` 是 **Replace** 的終端形式: + +```bash +ocx integration client enable --client zcode --overwrite-conflict +``` + +和 `--confirm-drift` 一樣,它永遠不會被預設:沒有這個旗標,衝突仍然會被拒絕。 +它只適用於 `enable`;對衝突強制 *disable* 會刪除我們從未寫入的區塊,因此這個組合會被拒絕。 + MiniMax Code 先連接一次 provider,再透過會檢查設定的 launcher 啟動: ```bash diff --git a/gui/tests/integration-marks.test.ts b/gui/tests/integration-marks.test.ts index 2d8c93bd9a..b964bc4ce1 100644 --- a/gui/tests/integration-marks.test.ts +++ b/gui/tests/integration-marks.test.ts @@ -177,3 +177,19 @@ test("the three newest marks are painted the way their artwork requires", () => expect(inksOf(bodyOf("/provider-icons/gajae-code.svg")).size).toBeGreaterThan(1); expect(/<linearGradient[\s>]/.test(bodyOf("/provider-icons/minimax.svg"))).toBe(true); }); + +/* + * The stylesheet rule the mobile dialog depends on. + * + * A config path is one long unbroken token and the dialog is 370px wide at a 390px + * viewport, so without an in-word break opportunity the path overflows and the one + * fact the user needs -- WHICH file is about to change -- goes off screen. This is + * a CSS declaration with no type or render coverage in a DOM-less suite, so it is + * asserted as text. + */ +test("the consequence dialog lets a long path break mid-token", () => { + const css = readFileSync(join(import.meta.dir, "..", "src", "styles-integrations.css"), "utf8"); + const rule = css.match(/\.integration-consequence-body code \{[^}]*\}/); + expect(rule).not.toBeNull(); + expect(rule![0]).toMatch(/overflow-wrap:\s*anywhere/); +}); diff --git a/gui/tests/integrations-surfaces.test.tsx b/gui/tests/integrations-surfaces.test.tsx index 45359026d5..d392db27ff 100644 --- a/gui/tests/integrations-surfaces.test.tsx +++ b/gui/tests/integrations-surfaces.test.tsx @@ -356,6 +356,37 @@ test("a foreign edit and an unowned block get different dialog copy", async () = .toContain("A block we did not write"); }); +test("the dialog's config path can break mid-string, so it cannot overflow a phone", async () => { + /* + * The dialog is 370px wide at a 390px viewport and the path it names is a long + * unbroken token -- a real one is `~/.zcode/v2/config.json` and worse. Without a + * break opportunity inside the word that token overflows its own container, + * which is how the one piece of information the user needs (WHICH file) ends up + * off screen. + * + * happy-dom does no layout, so measured geometry is not available here; what is + * checkable is that the path renders inside an element the stylesheet allows to + * break. Rendered geometry was measured separately at 390px in both themes + * (dialog 370px wide at left:10, code element 212px, no overflow). + */ + // A synthetic home, not a real one: privacy:scan rejects a committed /Users/<name>/. + const longPath = "/home/dev/Library/Application Support/SomeVendor/deeply/nested/config.json"; + stateResponse = () => json(status({ + state: "conflict", + reason: "unowned-key", + configPath: longPath, + })); + await remountClient(); + await act(async () => { buttonByText("Replace")!.click(); }); + + const dialog = container.querySelector(".integration-consequence-dialog")!; + const code = dialog.querySelector("code"); + // A <code> element, not bare text: `.integration-consequence-body code` is what + // carries `overflow-wrap: anywhere`. + expect(code).not.toBeNull(); + expect(code!.textContent).toBe(longPath); +}); + test("unsafe locks the switch instead of guessing", async () => { stateResponse = () => json(status({ state: "unsafe", reason: "unparseable" })); await mountClient(); diff --git a/src/cli/integrations.ts b/src/cli/integrations.ts index a654bf8cac..3b417632ff 100644 --- a/src/cli/integrations.ts +++ b/src/cli/integrations.ts @@ -29,7 +29,7 @@ const GROK_USAGE = `Usage: const CLIENT_USAGE = `Usage: ocx integration client [status] [--client <id>] [--json] - ocx integration client <enable|disable> --client <id> [--json] + ocx integration client <enable|disable> --client <id> [--overwrite-conflict] [--json] ocx integration client history [--client <id>] [--json] ocx integration client restore --op <opId> [--confirm-drift] [--json]`; @@ -212,11 +212,33 @@ export async function handleClientIntegrationCommand( throw new CliUsageError(`unknown client integration command ${action}`, CLIENT_USAGE); } const client = takeOption(args, "--client"); + /* + * The conflict escape hatch, spelled the way `restore --confirm-drift` is: the + * refusal is the default and the waiver has to be typed. + * + * Without it the dashboard could resolve a conflict and the CLI could not, + * which strands exactly the user who cannot open a browser -- an SSH session, + * or an agent driving the proxy. That dead end is the reason the overwrite + * path exists at all. + */ + const overwriteConflict = takeFlag(args, "--overwrite-conflict"); rejectArgs(args, CLIENT_USAGE); if (!client) throw new CliUsageError("--client <id> is required", CLIENT_USAGE); + /* + * Refused here rather than forwarded. The route answers 400 for this pair, but + * a local usage error names the flag that is wrong, where the route's reply + * arrives as a generic failed request. + */ + if (overwriteConflict && action === "disable") { + throw new CliUsageError("--overwrite-conflict applies only to enable", CLIENT_USAGE); + } const result = await runtimeRequest(`/api/client-integrations/${encodeURIComponent(client)}`, { method: "PUT", - body: JSON.stringify({ enabled: action === "enable" }), + // Sent only when asked for, so a proxy on an older build sees the request it + // has always seen rather than an unknown field. + body: JSON.stringify(overwriteConflict + ? { enabled: true, overwriteConflict: true } + : { enabled: action === "enable" }), }, deps); printData(result, wantsJson, [String((result as Record<string, unknown>).message ?? `${client} ${action}d.`)]); }); diff --git a/tests/cli-headless-parity.test.ts b/tests/cli-headless-parity.test.ts index 3900af2d7a..dcd05d295b 100644 --- a/tests/cli-headless-parity.test.ts +++ b/tests/cli-headless-parity.test.ts @@ -554,6 +554,41 @@ describe("headless GUI parity CLI", () => { ]); }); + test("enable can waive a conflict, and only when the flag is typed", async () => { + /* + * The parity this closes: the dashboard could resolve a conflict and the CLI + * could not, which strands the user who has no browser -- an SSH session, or + * an agent driving the proxy. That dead end is the reason the overwrite path + * exists, so leaving it GUI-only reproduces it for half the users. + */ + const runtime = fakeRuntime(); + expect(await handleClientIntegrationCommand(["enable", "--client", "hermes", "--json"], runtime.deps)).toBe(0); + expect(await handleClientIntegrationCommand( + ["enable", "--client", "hermes", "--overwrite-conflict", "--json"], + runtime.deps, + )).toBe(0); + expect(runtime.requests.map(row => row.body)).toEqual([ + // Absent rather than false: an older proxy sees the request it always saw. + { enabled: true }, + { enabled: true, overwriteConflict: true }, + ]); + }); + + test("a conflict waiver cannot ride along with disable", async () => { + /* + * Forcing a DISABLE over a conflict deletes a block we do not own, which is + * the one thing the refusal exists to prevent. The route answers 400; failing + * locally names the offending flag instead of surfacing a generic request + * failure, and sends nothing. + */ + const runtime = fakeRuntime(); + expect(await handleClientIntegrationCommand( + ["disable", "--client", "hermes", "--overwrite-conflict", "--json"], + runtime.deps, + )).not.toBe(0); + expect(runtime.requests).toEqual([]); + }); + test("a client integration command without its required target fails instead of guessing", async () => { const runtime = fakeRuntime(); // No `--client`: picking one for the user would write a config they never named. From 6e3abe8bf2c01984608c560692558bad8afb4fd0 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 01:21:43 +0900 Subject: [PATCH 12/42] docs(devlog): record the two defects the audit found after the unit closed (#3091) The outcome table stopped at #3084 and the unit was already in _fin, but auditing the merged head turned up two things the plan had gotten wrong rather than merely left undone. A documented tradeoff was a defect. 070 recorded grok.svg as staying an image because masking would be "editing someone else's mark". Measured on the dark card it was about 1.9:1 -- effectively invisible, and had been since the mark landed. The reasoning was wrong about what masking does: the file is not modified, it is read as a shape and tinted, which is how xAI renders it themselves. Writing a tradeoff down does not make it correct, and neither this unit nor the pass after it measured the thing it was excusing. Half a surface is not a surface. 080 specified the overwrite escape hatch for the GUI and stopped, leaving ocx integration client enable dead-ending on the exact state the feature exists to escape -- and the user with no browser was the one still stuck. The docs had meanwhile been asserting a conflict simply locks. Adds both to the table (#3086, #3088) and both corrections to the record. Verification: privacy:scan passed, repo-hygiene 12 pass. Docs only. --- .../090_outcome.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/devlog/_fin/260831_aside_client_and_integrations_ux/090_outcome.md b/devlog/_fin/260831_aside_client_and_integrations_ux/090_outcome.md index dee794dd73..cc6ad1d4fd 100644 --- a/devlog/_fin/260831_aside_client_and_integrations_ux/090_outcome.md +++ b/devlog/_fin/260831_aside_client_and_integrations_ux/090_outcome.md @@ -14,6 +14,14 @@ Unit closed 2026-08-31. Every phase in this unit is on `dev`. | wp8 | 070 | #3083 | `d86ec3e03` -- marks on every surface | | wp9 | 080 | #3084 | `2a90cdaa9` -- conflict overwrite | +Two follow-ups landed after the table above, both from auditing the merged head +rather than from the plan: + +| what | PR | on `dev` as | +|---|---|---| +| Grok mark masked so it survives the dark theme | #3086 | `0cc73411a` | +| `--overwrite-conflict` for the CLI, plus mobile dialog guards | #3088 | `91b2c4e19` | + #3065 (`7853e8e05`) and #3074 landed alongside wp5: Aside's own mark plus the first version of the single-ink rule, and the property guards the pair relies on. @@ -52,6 +60,20 @@ them. The doc comment in `journal.ts` claimed a test asserted the three agree. None did, so a kind added in one place rendered as a raw i18n key with no type error anywhere. `tests/integrations-journal.test.ts` now reads all three. +**A documented tradeoff was a defect.** 070 recorded `grok.svg` as staying an image +because masking would be "editing someone else's mark". Measured on the dark card +surface it was about 1.9:1 -- the glyph was effectively invisible, and had been since +the mark landed. The reasoning was simply wrong about what masking does: the file is +not modified, it is read as a shape and tinted, which is how xAI renders it +themselves. Writing a tradeoff down does not make it correct, and neither this unit +nor the pass that followed it measured the thing it was excusing. + +**Half a surface is not a surface.** 080 specified the overwrite escape hatch for the +GUI and stopped there, which left `ocx integration client enable` dead-ending on the +exact state the feature exists to escape. The user with no browser -- an SSH session, +or an agent driving the proxy -- was the one still stuck. Fixed in #3088; the docs had +meanwhile been asserting that a conflict simply locks. + ## Verification as merged CI green on each PR head, including the unsharded macOS job, before every admin From 5ef84b61ecb34d6a0edfa6bb27ebf436b0521e30 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 01:42:21 +0900 Subject: [PATCH 13/42] docs(devlog): plan brand marks for the 38 providers that render bare (#3092) PROVIDER_REGISTRY has 83 entries. providerIconSrc() resolves 45 and returns undefined for 38, so those fall back to a coloured initial tile in the provider rail, details panel and dashboard rows. The Add-Provider catalog is worse: it draws no mark for any provider at all, and that is the surface a user picks a provider FROM. Three of the 38 need no sourcing. minimax.svg landed for the MiniMax Code client in #3082 and nothing connected it to the MiniMax provider, because CLIENT_MARKS is keyed by ExportClientId and PROVIDER_ICON_ALIASES by provider id. Same for xiaomi-color.svg and xiaomi-mimo. Three map rows and a guard. The other 35 get two parallel Aside sourcing lanes. Every registry entry carries baseUrl and dashboardUrl, so no lane guesses where a vendor lives. Raster-only vendors get vectorized the way hermes-agent.svg and gajae-code.svg were. The audit corrected three things in the painting doc. Provider marks sit on a --raised 31px tile with a border, not on the page surface, so contrast has to be measured against the tile. That tile already sets color: var(--text), so a mask branch costs one rule. And a third painting mechanism already exists -- .usage-source-mark--mono uses filter: invert(1) on the same grok.svg the Integrations page just masked -- which wp5 must unify or explain rather than become a fourth spelling of. --- .../_plan/260901_provider_marks/000_plan.md | 81 +++++++++++++++++++ .../010_wp2_wire_present_assets.md | 49 +++++++++++ .../260901_provider_marks/020_wp3_lane_a.md | 67 +++++++++++++++ .../260901_provider_marks/030_wp4_lane_b.md | 55 +++++++++++++ .../260901_provider_marks/040_wp5_painting.md | 76 +++++++++++++++++ .../050_wp6_catalog_and_delivery.md | 55 +++++++++++++ 6 files changed, 383 insertions(+) create mode 100644 devlog/_plan/260901_provider_marks/000_plan.md create mode 100644 devlog/_plan/260901_provider_marks/010_wp2_wire_present_assets.md create mode 100644 devlog/_plan/260901_provider_marks/020_wp3_lane_a.md create mode 100644 devlog/_plan/260901_provider_marks/030_wp4_lane_b.md create mode 100644 devlog/_plan/260901_provider_marks/040_wp5_painting.md create mode 100644 devlog/_plan/260901_provider_marks/050_wp6_catalog_and_delivery.md diff --git a/devlog/_plan/260901_provider_marks/000_plan.md b/devlog/_plan/260901_provider_marks/000_plan.md new file mode 100644 index 0000000000..8902c88306 --- /dev/null +++ b/devlog/_plan/260901_provider_marks/000_plan.md @@ -0,0 +1,81 @@ +# Provider marks + +Unit opened 2026-09-01. The Integrations page now draws a logo beside every +client it names. The provider surface does not: 38 of 83 registry providers fall +back to a coloured initial tile, and the Add-Provider catalog draws no mark at +all for any provider. + +## The measurement + +``` +PROVIDER_REGISTRY 83 entries +providerIconSrc() resolves 45 +returns undefined for 38 +``` + +Three of the 38 are not a sourcing problem at all -- the asset is committed and +the alias map simply never learned about it: + +| provider | asset already on disk | +|---|---| +| `minimax` | `minimax.svg` | +| `minimax-cn` | `minimax.svg` | +| `xiaomi-mimo` | `xiaomi-color.svg` | + +`minimax.svg` landed in #3082 for the MiniMax Code *client*. Nothing connected +it to the MiniMax *provider*, because the two maps are unrelated: +`CLIENT_MARKS` is keyed by `ExportClientId` and `PROVIDER_ICON_ALIASES` by +provider id. That is the whole defect, and it is the cheapest one in this unit. + +The remaining 35 have no asset: + +``` +nous umans neuralwatt orcarouter bizrouter cerebras chutes deepinfra hyperbolic +nscale vultr baseten sambanova nebius digitalocean scaleway featherless novita +together venice zai zhipu-bigmodel zhipu-bigmodel-coding nanogpt synthetic +siliconflow tencent-coding-plan volcengine volcengine-coding-plan +volcengine-agent-plan parallel zenmux litellm kilo mimo +``` + +## Where a missing mark shows + +`providerIconSrc` has exactly one consumer, `ProviderIcon` in `ProviderRail.tsx`, +which three surfaces render: the rail itself, `ProviderDetails`, and +`ProviderOverviewDashboard`. When it returns `undefined` the component falls back +to `ProviderFallbackMark` -- a deterministic hue-per-id initial tile. That +fallback is good; a page of them where competitors show logos is not. + +`ProviderCatalog.tsx` is separate and worse: its preset rows draw a title, an +adapter chip and badges, and no mark whatsoever. This is the surface the user +picks a provider FROM, so it is the one place a logo does the most work. + +## Sourcing has real targets + +Every registry entry carries `baseUrl` and `dashboardUrl`, so no lane has to +guess where a vendor lives. `cerebras` is `cloud.cerebras.ai`, `sambanova` is +`cloud.sambanova.ai`, `nebius` is `tokenfactory.nebius.com`, and so on. Those +URLs are the lane inputs. + +Three of the 35 are not independent brands and should be handled as aliases +rather than sourced twice: `zhipu-bigmodel-coding` is `zhipu-bigmodel`, +`volcengine-coding-plan` and `volcengine-agent-plan` are `volcengine`, and +`mimo` is the same Xiaomi MiMo brand as `xiaomi-mimo`. + +## Work phases + +| Phase | Doc | Deliverable | +|---|---|---| +| wp1 | this unit | Research and roadmap (docs only) | +| wp2 | 010 | Wire the three present-but-unmapped assets + the gap-class guard | +| wp3 | 020 | Aside lane A: sourcing the first half | +| wp4 | 030 | Aside lane B: sourcing the second half, vectorize raster-only | +| wp5 | 040 | Painting contract on the provider surface, measured in both themes | +| wp6 | 050 | Catalog marks + stack delivery | + +## Ordering constraint + +wp2 depends on nothing but the map and can land first. wp3 and wp4 are +independent of each other and of wp2 -- they only add files and alias rows -- so +they can run as parallel Aside lanes and land as sibling PRs. wp5 depends on both +sourcing lanes, because the mask decision needs the actual assets to measure. wp6 +is last because the catalog change should draw the full set, not a partial one. diff --git a/devlog/_plan/260901_provider_marks/010_wp2_wire_present_assets.md b/devlog/_plan/260901_provider_marks/010_wp2_wire_present_assets.md new file mode 100644 index 0000000000..8d8d1ba050 --- /dev/null +++ b/devlog/_plan/260901_provider_marks/010_wp2_wire_present_assets.md @@ -0,0 +1,49 @@ +# wp2 — wire the assets that are already committed + +Three providers render an initial tile while their artwork sits in the repo. No +sourcing, no tracing, no vendor site: three map rows and a guard. + +## The change + +`gui/src/provider-icons.ts`, in `PROVIDER_ICON_ALIASES`: + +```ts + minimax: "minimax.svg", + "minimax-cn": "minimax.svg", + "xiaomi-mimo": "xiaomi-color.svg", +``` + +`minimax` and `minimax-cn` are one brand on two endpoints (`api.minimax.io` and +`api.minimaxi.com`), the same way `alibaba` and `alibaba-token-plan-intl` +already share `alibaba-color.svg`. `xiaomi-mimo` is Xiaomi's MiMo endpoint and +`xiaomi-color.svg` is already wired for `xiaomi` and `mimo-free`. + +`mimo` (the token-plan id) belongs here too by the same argument, and takes +`xiaomi-color.svg`. + +## The guard, and why this class needs one + +The defect is not that someone forgot a row. It is that nothing could tell them: +`CLIENT_MARKS` and `PROVIDER_ICON_ALIASES` are different maps over different key +spaces, so committing `minimax.svg` for the client left no signal that the +provider of the same name was still bare. + +New test, `gui/tests/provider-icons.test.ts`: + +1. **Every registry id whose brand asset exists on disk is wired.** For each + provider without an alias, probe `<id>.svg`, `<id>-color.svg`, and the same + two for the id's first dash-segment. A hit is a failure with the filename + named, because it means the artwork is present and the map is stale. + Falsify by deleting the `minimax` row. +2. **Every alias points at a file that exists.** A typo'd filename renders a + broken image, which is worse than the fallback tile it replaced. Falsify by + pointing one alias at a name that is not committed. + +Test 1 is the one that matters: it is the only thing that would have caught this +gap, and it will catch the next one automatically as wp3/wp4 add assets. + +## Out of scope here + +No new artwork, no README changes (nothing new is sourced), no painting-mode +decisions -- `minimax.svg` is a gradient wave and `xiaomi-color.svg` is +multi-colour, so both stay images under the existing rule. diff --git a/devlog/_plan/260901_provider_marks/020_wp3_lane_a.md b/devlog/_plan/260901_provider_marks/020_wp3_lane_a.md new file mode 100644 index 0000000000..e29e89f00b --- /dev/null +++ b/devlog/_plan/260901_provider_marks/020_wp3_lane_a.md @@ -0,0 +1,67 @@ +# wp3 — Aside sourcing lane A + +Half the unsourced providers. Lane A and lane B (030) are independent: each only +adds files under `gui/public/provider-icons/`, alias rows, and README entries, so +they can run as parallel Aside dispatches and land as sibling PRs. + +## Lane A targets + +The registry gives each one a canonical domain, so no lane guesses where a vendor +lives. + +| provider | domain to search | +|---|---| +| `cerebras` | cloud.cerebras.ai | +| `together` | together.xyz | +| `deepinfra` | deepinfra.com | +| `sambanova` | sambanova.ai | +| `baseten` | baseten.co | +| `nebius` | tokenfactory.nebius.com | +| `hyperbolic` | hyperbolic.ai | +| `novita` | novita.ai | +| `featherless` | featherless.ai | +| `venice` | venice.ai | +| `chutes` | chutes.ai | +| `nscale` | nscale.com | +| `vultr` | vultr.com | +| `digitalocean` | digitalocean.com | +| `scaleway` | scaleway.com | +| `siliconflow` | siliconflow.cn | +| `litellm` | litellm.ai (BerriAI/litellm) | + +## Acceptance per asset + +An accepted mark is a square-ish brand mark with real path geometry. Rejected on +sight, with the rejection recorded: + +- a `<text>` element standing in for a glyph (the Hermes favicon case: 113 bytes, + renders per-machine, blank where the font lacks the character); +- a `<image>` or base64 raster inside an SVG wrapper -- that is a PNG wearing a + costume, and it will not scale or mask; +- a horizontal wordmark where a square slot needs a mark. The rail draws a 19px + box; a 129x32 lockup renders as an illegible smear. This is what disqualified + the MiniMax docs asset in the previous unit. + +When the vendor publishes only raster -- a favicon PNG, an app icon, an OG image +-- vectorize it. That path is already proven in this repository: +`hermes-agent.svg` is `potrace -s --flat --turdsize 8 --alphamax 1.0` over an +alpha+luma mask, and `gajae-code.svg` is seven k-means colour layers at a 128px +box. Single-ink silhouette takes potrace; multi-colour art takes the layered +trace. Downsample before tracing or upscaled pixel art produces a megabyte of +staircase geometry. + +## Naming + +`<provider-id>.svg` for a mark that carries its own colour, `<provider-id>-color.svg` +only where the directory's existing convention already uses that suffix for the +same brand. The directory is one flat namespace shared with client marks, so a +name that collides with a client gets the vendor-qualified form (`hermes-agent.svg` +is the precedent). + +## Provenance + +Every accepted asset gets a README entry: source URL, fetch date, whether it is +the product's mark or the publisher's, every modification made, and every +candidate rejected with the reason. A provider with nothing usable upstream gets +an entry too -- what was searched, what was found, why it was refused -- and keeps +its fallback tile. That is a recorded partial and a legitimate outcome. diff --git a/devlog/_plan/260901_provider_marks/030_wp4_lane_b.md b/devlog/_plan/260901_provider_marks/030_wp4_lane_b.md new file mode 100644 index 0000000000..bec183bf1a --- /dev/null +++ b/devlog/_plan/260901_provider_marks/030_wp4_lane_b.md @@ -0,0 +1,55 @@ +# wp4 — Aside sourcing lane B + +The other half. Same acceptance rules, same vectorization path, same provenance +obligations as 020; this doc records only what differs. + +## Lane B targets + +| provider | domain to search | +|---|---| +| `zai` | z.ai | +| `zhipu-bigmodel` | bigmodel.cn | +| `volcengine` | volcengine.com (Ark) | +| `tencent-coding-plan` | cloud.tencent.com | +| `nous` | nousresearch.com | +| `nanogpt` | nano-gpt.com | +| `synthetic` | synthetic.new | +| `parallel` | platform.parallel.ai | +| `zenmux` | zenmux.ai | +| `kilo` | kilo.ai | +| `umans` | umans.ai | +| `neuralwatt` | neuralwatt.com | +| `orcarouter` | orcarouter.ai | +| `bizrouter` | bizrouter.ai | + +## Aliases, not separate sourcing + +Four ids are plan variants of a brand already in this lane and must NOT be +sourced independently -- a second search would either duplicate the file or +produce a different asset for the same company: + +| variant | takes the mark of | +|---|---| +| `zhipu-bigmodel-coding` | `zhipu-bigmodel` | +| `volcengine-coding-plan` | `volcengine` | +| `volcengine-agent-plan` | `volcengine` | +| `mimo` | Xiaomi MiMo, wired in wp2 | + +This mirrors how `alibaba`, `alibaba-token-plan` and `alibaba-token-plan-intl` +already share one asset: the plan is a billing arrangement, not a brand. + +## The two that may legitimately come back empty + +`nous` is Nous Research, whose desktop icon was already traced for the Hermes +client mark in the previous unit. If the portal publishes nothing better, reusing +`hermes-agent.svg` is WRONG -- that is the Hermes product mark, not the Nous +company mark, and the registry entry is the company's inference portal. Record +the distinction and prefer an empty result over a misattribution. + +`litellm` (lane A) and `parallel` are the other likely empties: one is a +self-hosted proxy whose brand is a docs site, the other's `baseUrl` and +`dashboardUrl` are the same host, which usually means there is no separate +product identity to find. + +A recorded empty is a result. Inventing a mark, or borrowing a neighbouring +brand's, is a misattribution that outlives the commit. diff --git a/devlog/_plan/260901_provider_marks/040_wp5_painting.md b/devlog/_plan/260901_provider_marks/040_wp5_painting.md new file mode 100644 index 0000000000..b93a4f3818 --- /dev/null +++ b/devlog/_plan/260901_provider_marks/040_wp5_painting.md @@ -0,0 +1,76 @@ +# wp5 — the painting contract on the provider surface + +Depends on both sourcing lanes: the mask decision is a property of the artwork, +so it cannot be made before the artwork exists. + +## The rule, and its record of shipping broken + +A mark is drawn one of two ways. As an image it keeps its own colour, which is +correct for multi-colour art and for a single ink that IS the brand. As a themed +mask the file is used as a shape and filled with the surrounding text colour, +which is correct for a neutral silhouette that would otherwise vanish against one +of the two surfaces. + +Getting this wrong is not hypothetical. It has shipped four times: `prime` +(white on transparent, invisible in light mode), `opencode` (#211E1E) and `kimi` +(#1A1A1A) invisible in dark, and then `grok` (#000000, ~1.9:1 on the dark card), +which survived two passes over the same file because a code comment argued it +away instead of measuring it. + +## What the provider surface does today + +`ProviderIcon` renders `<img src={src}>` unconditionally. There is no mask path +at all, so a neutral silhouette added by wp3/wp4 is invisible in one theme with +no mechanism to fix it short of editing the vendor's file. + +The client side already solved this in `ClientMark` + `MASKED_MARKS`. The +provider side needs the same two-branch decision, and the sets must stay +DERIVED rather than restated -- two hand-maintained lists of the same fact drift, +and the drift is a mark masked on one surface and not another. + +## The change + +1. `provider-icons.ts` grows a masked-set export, keyed by asset path exactly as + `MASKED_MARKS` is, so an asset reachable from both surfaces cannot be masked on + one and not the other. +2. `ProviderIcon` branches on it: masked assets render a `<span>` with + `mask-image` and `background: var(--text)`; everything else stays an `<img>`. + The fallback tile is untouched. +3. The luminance guard from `gui/tests/integration-marks.test.ts` is generalized + to cover provider assets: any single-ink, near-neutral mark (channel spread + <= 24, luminance outside 0.12-0.75) that is NOT masked fails, and any + multi-colour or gradient mark that IS masked fails. + +## Verification is measurement, not reading + +A hex string does not tell you what the user sees; the surface colour is half the +equation. Drive a headless browser over the built GUI, emulate +`prefers-color-scheme` light and dark, read `getComputedStyle` for each mark and +for the surface behind it, and compute the contrast ratio. Capture desktop (1440) +and mobile (390) in both themes. + +That procedure is what found grok at 1.9:1 after two passes had declared it +acceptable. Any provider mark landing under 3:1 against its own surface is a +defect regardless of what the file's colours suggest. + +## Three things the audit corrected in this doc + +**The surface is the tile, not the page.** `.provider-icon` is a 31px tile with +`background: var(--raised)` and a border, so a provider mark is measured against +`light-dark(#f4f4f4, #303030)` -- not against the card or page background the +client marks sit on. Same numbers by coincidence today, different variables +tomorrow; the measurement must read the tile's computed background rather than +assume the client surface. + +**The tile already sets `color: var(--text)`.** That is what a `mask` branch needs +for `background: var(--text)` to resolve correctly, so the mask path costs one +rule and no new custom property. + +**A third painting mechanism already exists and must not become a fourth.** +`.usage-source-mark--mono` on the Usage page uses `filter: invert(1)` under a dark +theme -- a different technique from `ClientMark`'s CSS mask, applied to the same +`grok.svg` this repository just masked on the Integrations page. Inverting is not +equivalent: it maps #000 to #fff rather than to `--text`, and it inverts any +colour present rather than recolouring a silhouette. wp5 either brings that +call site onto the shared decision or documents why Usage differs. What it must +not do is add a third spelling of the same idea. diff --git a/devlog/_plan/260901_provider_marks/050_wp6_catalog_and_delivery.md b/devlog/_plan/260901_provider_marks/050_wp6_catalog_and_delivery.md new file mode 100644 index 0000000000..f4fa1d3248 --- /dev/null +++ b/devlog/_plan/260901_provider_marks/050_wp6_catalog_and_delivery.md @@ -0,0 +1,55 @@ +# wp6 — marks in the Add-Provider catalog, and stack delivery + +Last because the catalog should draw the full set. Landing it before the sourcing +lanes would show a page of fallback tiles and invite a second pass over the same +file. + +## The catalog draws no marks at all + +`ProviderCatalog.tsx` renders each preset row as a title, an adapter chip and +badges. This is the surface a user picks a provider FROM -- a list of names where +a logo would do the most work of anywhere in the app, and the one place that has +none. + +`providerIconSrc` is keyed by provider id and `CatalogPreset.id` is that same id, +so the existing map serves this surface with no new lookup. The row grows a +`ProviderIcon` before its title block, matching the rail's markup so both +surfaces share the mask decision and the fallback tile. + +The `accounts` tier rows get the same treatment: they are providers too, and a +row that shows a logo next to `Cursor` and a bare tile next to `Kiro` reads as a +bug rather than a distinction. + +## Guards + +1. Every catalog row renders a mark element -- either the asset or the fallback + tile, never nothing. Falsify by removing the component from the row. +2. The mark is `aria-hidden` beside a visible label, or a screen reader announces + the provider twice. This is the rule the client marks already follow. +3. Row geometry is stable: adding a 19px mark must not push the badges out of the + row at mobile width. Measured, not assumed. + +## Delivery + +Stacked parent-to-child, each PR reviewable alone: + +``` +wp1 roadmap ──► wp2 wire+guard ──► wp5 painting ──► wp6 catalog + wp3 lane A ──┤ + wp4 lane B ──┘ +``` + +wp3 and wp4 are siblings off wp2 rather than a chain: they touch disjoint asset +files and adjacent alias rows, so serializing them would only add rebases. wp5 +merges after both because it measures the assets they add. + +Every push uses `--no-verify`; the local full backend suite is forbidden, so +backend proof comes from CI. Each PR merges with `--squash --admin` only after +its own CI is green including the unsharded macOS job, and each child is rebased +onto the new `dev` tip after its parent lands. + +## Closing + +The unit moves to `_fin` with an outcome doc recording, per phase, the merge +commit and what the plan got wrong. The previous unit's outcome doc is the +template: it is worth more for the corrections than for the table. From a0d386b49074ec81df5646fcc20a5e4979c67878 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 01:47:14 +0900 Subject: [PATCH 14/42] fix(responses): always carry query on web_search_call for Console Go (#3071) (#3089) * fix(responses): always carry query on web_search_call for Console Go DeepSeek's native Responses parser requires 'queries', while Console Go's upstream validator requires 'query'. A multi-query web_search_call emitted by webSearchAction only carried 'queries', so replayed history 400'd on every subsequent turn with: input[N].action missing required field 'query' (sibling of #930). - webSearchAction now always includes action.query (first query) alongside queries, satisfying both strict parsers. - backfillWebSearchQueries repairs pre-existing recorded items in either missing direction (adds queries from query, or query from queries[0]). Verified: a Responses request replaying an old-format multi-query web_search_call now completes instead of 400ing on upstream. * test(responses): cover query+queries parity on web_search_call (#3071) - bridge: batched search now asserts both 'query' and 'queries' (was: queries-only), matching the new webSearchAction output. - passthrough: backfill test now asserts the reverse repair direction (multi-query action gains singular 'query'), matching the extended backfillWebSearchQueries. * fix(responses): reject a malformed queries array instead of forging a query Follow-up on the #3071 carry. The bidirectional repair copied action.queries[0] into the singular query without checking it is a string. Input items use a loose schema, so a replayed queries: [42] became query: 42 - a shape that satisfies the presence check, still fails the Console Go validator the repair exists to satisfy, and reports success while doing it. A non-string first member is now left alone. An empty queries: [] canonicalizes to { query: "", queries: [""] }, the same shape the bridge emits for an empty search, rather than being passed through as a value neither validator accepts. Also updates the bridge summary comment, which still described the old batch-omits-query contract two lines above the code that carries both, and records why the codex-rs ellipsis is traded away so it is not restored. The regression drives both branches red against the pre-fix adapter: queries: [42] and [{q:"x"}] are left untouched, [] canonicalizes. * fix(responses): close two malformed-array holes in the replay repair Review of the #3071 carry found the first guard incomplete in both directions. An action carrying { query: "legacy", queries: [] } bypassed every branch: the empty-array repair only ran when query was absent, so the item kept an empty plural array that DeepSeek rejects. Empty-array canonicalization now runs first and keeps the existing query instead of discarding it. An action carrying { queries: ["a", 42] } gained query: "a" and kept the invalid array, satisfying Console Go while leaving DeepSeek to reject the same replay. The singular field is now derived only when every member is a string; a partly-malformed array is left untouched, on the same fail-closed reasoning as a wholly-malformed one - coercing or dropping members would invent semantics the stored item never had. Also updates the call-site comment, which described only the old one-way repair and #930. Both cases are red against the previous commit. * test(bridge): drop an assertion the preceding equality already makes The review lane flagged expect(action.query).toBe("rust async") as redundant: the toEqual above it already pins the whole action shape, including that field. A redundant assertion is not free - it reads as an independent check and gives false weight to the coverage count. --------- Co-authored-by: kunqi.lai <goodlive.lkq1128@gmail.com> --- src/adapters/openai-responses.ts | 57 ++++++++++++++---- src/bridge.ts | 30 +++++----- tests/bridge.test.ts | 8 +-- tests/openai-responses-passthrough.test.ts | 67 +++++++++++++++++++--- 4 files changed, 126 insertions(+), 36 deletions(-) diff --git a/src/adapters/openai-responses.ts b/src/adapters/openai-responses.ts index 047c60a6a3..97044bc804 100644 --- a/src/adapters/openai-responses.ts +++ b/src/adapters/openai-responses.ts @@ -901,13 +901,23 @@ function annotateEmptyResponsesToolOutputs(body: unknown, enabled: boolean): unk * Runs on every forward request; with intact pairs it returns the original reference. */ /** - * Backfill `queries` on a replayed single-query `web_search_call`. + * Repair a replayed `web_search_call` action that is missing either key. * * `webSearchAction()` in the bridge now emits both keys, but that only helps items * created after the fix. A conversation that already recorded - * `{type:"search", query:"..."}` replays that stored item on every subsequent turn, and - * DeepSeek's native Responses parser requires `queries` — so upgrading alone leaves - * those threads permanently 400ing with `missing field 'queries'` (#930). + * `{type:"search", query:"..."}` or `{type:"search", queries:[...]}` replays that stored + * item on every subsequent turn. DeepSeek's native Responses parser requires `queries` + * (#930) and Console Go's validator requires `query` (#3071), so upgrading alone leaves + * those threads permanently 400ing in one direction or the other. The repair runs both + * ways. + * + * Input items carry a loose schema, so a stored `queries` is not necessarily an array of + * strings. A partly- or wholly-malformed array is left alone rather than used as a source + * for the singular field: writing `query: 123` would satisfy the presence check and still + * fail the validator this repair exists to satisfy, and deriving `query` from + * `["a", 42]` would satisfy Console Go while leaving DeepSeek to reject the same replay. + * An empty `queries: []` canonicalizes to the shape the bridge emits for an empty search, + * keeping an existing `query` when the item has one. * * Runs on every Responses request, on both `input` items and the `action` nested inside * them. Returns the original reference when nothing needs repair, so the common path @@ -920,9 +930,35 @@ function backfillWebSearchQueries(body: unknown): unknown { if (!isPlainObject(item) || item.type !== "web_search_call") return item; const action = item.action; if (!isPlainObject(action) || action.type !== "search") return item; - if (typeof action.query !== "string" || Array.isArray(action.queries)) return item; - changed = true; - return { ...item, action: { ...action, queries: [action.query] } }; + // Repair whichever side is missing so both strict parsers pass: + // DeepSeek native Responses requires `queries`; Console Go requires `query`. + const rep: Record<string, unknown> = { ...action }; + let itemChanged = false; + const hasQuery = typeof action.query === "string"; + const queries = Array.isArray(action.queries) ? action.queries : undefined; + if (queries !== undefined && queries.length === 0) { + // An empty array satisfies neither validator. Canonicalize to the empty-search + // shape the bridge emits, keeping an existing query rather than discarding it. + const query = hasQuery ? action.query as string : ""; + rep.query = query; + rep.queries = [query]; + itemChanged = true; + } else if (!hasQuery && queries !== undefined) { + // A plural array is only a usable source for the singular field when EVERY member + // is a string: deriving `query` from a partly-malformed array would satisfy Console + // Go while leaving DeepSeek to reject the same replay. Wholly malformed arrays are + // left untouched — coercing or dropping members would invent semantics the stored + // item never had. + if (queries.every(entry => typeof entry === "string")) { + rep.query = queries[0]; // multi-query item recorded before the fix + itemChanged = true; + } + } else if (hasQuery && queries === undefined) { + rep.queries = [action.query]; // single-query item recorded before the fix + itemChanged = true; + } + if (itemChanged) changed = true; + return itemChanged ? { ...item, action: rep } : item; }); return changed ? { ...body, input } : body; } @@ -2097,9 +2133,10 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig): outBody = repairOversizedReplayCallIds(outBody); } outBody = stripUnsupportedReasoningSummaryDelivery(outBody, parsed.modelId); - // Repair stored history from before the bridge emitted both keys: a conversation - // that already recorded a single-query web_search_call replays it every turn, and - // a strict parser rejects the whole request over it (#930). + // Repair stored history from before the bridge emitted both keys, in either + // direction: a conversation that already recorded a web_search_call replays it + // every turn, and a strict parser rejects the whole request over the missing key — + // `queries` for DeepSeek (#930), `query` for Console Go (#3071). outBody = backfillWebSearchQueries(outBody); if (!isCanonicalOpenAiForwardProvider(provider)) { outBody = promoteClientLoadedTools(outBody); diff --git a/src/bridge.ts b/src/bridge.ts index dcb163553c..145913ddab 100644 --- a/src/bridge.ts +++ b/src/bridge.ts @@ -146,27 +146,27 @@ export { adapterFailureFromMessage } from "./lib/errors"; /** * Build the native `WebSearchAction::Search` payload from the queries that ran. * - * Single query → `{ query, queries: [query] }`. Batch → `{ queries }` with NO singular - * `query`. Empty → `{ query: "", queries: [""] }`. + * Every action carries BOTH keys: `{ query, queries }`, where `query` is the first + * member. Empty → `{ query: "", queries: [""] }`. * - * The asymmetry is load-bearing in both directions. codex-rs prefers a non-empty `query` - * for the cell label and renders "<first> ..." only when `query` is ABSENT and - * `queries.len() > 1`, so adding `query` to a batch would collapse the plural ellipsis. - * Meanwhile DeepSeek's native Responses parser makes `queries` a required field, so a - * replayed one-term `web_search_call` — carried in the history of every subsequent turn - * — fails deserialization with `missing field 'queries'` and 400s the rest of the - * conversation (#930). Carrying both keys in the single case satisfies the strict parser - * without changing what codex-rs displays. + * Carrying both is load-bearing in both directions. DeepSeek's native Responses parser + * makes `queries` a required field, and Console Go's upstream validator makes `query` a + * required field — so a replayed `web_search_call` carried in the history of every + * subsequent turn fails deserialization with `missing field 'queries'` (#930) or 400s + * with `missing required field 'query'` unless both keys are present. Carrying both keys + * in every case satisfies both strict parsers; the trade-off is that a multi-query batch + * loses the "<first> ..." ellipsis in codex-rs and shows the first query as the label. + * + * That trade is deliberate: a cosmetic label against a conversation that 400s on every + * subsequent turn. Do not restore the old batch-omits-`query` shape to win the ellipsis + * back — it reopens #3071. * * This fixes items created from here on. History recorded before it is repaired at the * replay boundary by `backfillWebSearchQueries()` in the Responses adapter. */ function webSearchAction(queries: string[]): Record<string, unknown> { - if (queries.length <= 1) { - const query = queries[0] ?? ""; - return { type: "search", query, queries: [query] }; - } - return { type: "search", queries }; + const first = queries[0] ?? ""; + return { type: "search", query: first, queries: queries.length > 0 ? queries : [first] }; } interface OutputItem { diff --git a/tests/bridge.test.ts b/tests/bridge.test.ts index 444f3d6e77..f031ce8f8b 100644 --- a/tests/bridge.test.ts +++ b/tests/bridge.test.ts @@ -1075,7 +1075,7 @@ describe("Responses bridge web_search_call native item", () => { }); }); - test("a batched (plural) search emits action.search.queries without a singular query", () => { + test("a batched (plural) search carries both query and queries for Console Go (#3071)", () => { const json = buildResponseJSON([ { type: "web_search_call_begin", id: "ws_3" }, { type: "web_search_call_end", id: "ws_3", queries: ["rust async", "tokio runtime"] }, @@ -1085,9 +1085,9 @@ describe("Responses bridge web_search_call native item", () => { const output = json.output as Record<string, unknown>[]; const action = (output[0] as Record<string, unknown>).action as Record<string, unknown>; - // Native renders "<first> ..." only when `query` is absent and queries.len() > 1. - expect(action).toEqual({ type: "search", queries: ["rust async", "tokio runtime"] }); - expect(action.query).toBeUndefined(); + // Console Go's upstream validator requires singular `query` on the search action, + // and DeepSeek native Responses requires `queries` — so a batch carries both now. + expect(action).toEqual({ type: "search", query: "rust async", queries: ["rust async", "tokio runtime"] }); }); test("a single-query search also carries queries so strict parsers accept the replay (#930)", () => { diff --git a/tests/openai-responses-passthrough.test.ts b/tests/openai-responses-passthrough.test.ts index 6687671023..edba6c393e 100644 --- a/tests/openai-responses-passthrough.test.ts +++ b/tests/openai-responses-passthrough.test.ts @@ -1295,11 +1295,11 @@ describe("OpenAI Responses passthrough sanitization", () => { expect(input[0]).not.toHaveProperty("id"); }); - test("backfills queries on a replayed single-query web_search_call (#930)", () => { + test("backfills web_search_call actions in either missing direction (#930, #3071)", () => { // The bridge fix only helps items created after it. A conversation that already - // recorded {type:"search", query:"..."} replays that stored item every turn, and - // DeepSeek's parser rejects the whole request over it — so upgrading alone would - // leave those threads permanently broken. + // recorded a legacy web_search_call replays that stored item every turn. DeepSeek's + // parser rejects an action without `queries` (#930) and Console Go rejects one + // without `query` (#3071) — so upgrading alone would leave those threads broken. const adapter = createResponsesPassthroughAdapter(provider); const request = adapter.buildRequest({ modelId: "provider-model", @@ -1319,13 +1319,66 @@ describe("OpenAI Responses passthrough sanitization", () => { // Repaired: singular query gains the array the strict parser requires. expect(input[0].action).toEqual({ type: "search", query: "legacy", queries: ["legacy"] }); - // Untouched: a batch already satisfies the parser, and adding `query` would collapse - // the native plural rendering. - expect(input[1].action).toEqual({ type: "search", queries: ["a", "b"] }); + // Repaired: multi-query batch gains the singular `query` Console Go requires. + expect(input[1].action).toEqual({ type: "search", query: "a", queries: ["a", "b"] }); // Untouched: not a search action. expect(input[2].action).toEqual({ type: "open_page", url: "https://example.test" }); }); + test("does not forge a singular query from a malformed or empty queries array (#3071)", () => { + // Input items use a loose schema, so a stored `queries` need not be an array of + // strings. Copying a non-string first member would satisfy the presence check and + // still fail the Console Go validator this repair exists to satisfy — a repair that + // reports success and produces an invalid shape is worse than no repair. + const adapter = createResponsesPassthroughAdapter(provider); + const request = adapter.buildRequest({ + modelId: "provider-model", + context: { messages: [] }, + stream: true, + options: {}, + _rawBody: { + model: "provider-model", + input: [ + { type: "web_search_call", id: "ws_num", status: "completed", action: { type: "search", queries: [42] } }, + { type: "web_search_call", id: "ws_obj", status: "completed", action: { type: "search", queries: [{ q: "x" }] } }, + { type: "web_search_call", id: "ws_empty", status: "completed", action: { type: "search", queries: [] } }, + ], + }, + }, meta); + const input = (JSON.parse(request.body) as { input: Array<{ action: Record<string, unknown> }> }).input; + + // Left alone: a non-string first member is not a query. + expect(input[0].action).toEqual({ type: "search", queries: [42] }); + expect(input[1].action).toEqual({ type: "search", queries: [{ q: "x" }] }); + // Canonicalized: an empty array satisfies neither validator, so it becomes the same + // empty-search shape the bridge emits rather than being passed through. + expect(input[2].action).toEqual({ type: "search", query: "", queries: [""] }); + }); + + test("repairs a partly-malformed or already-queried empty action (#3071)", () => { + const adapter = createResponsesPassthroughAdapter(provider); + const request = adapter.buildRequest({ + modelId: "provider-model", + context: { messages: [] }, + stream: true, + options: {}, + _rawBody: { + model: "provider-model", + input: [ + { type: "web_search_call", id: "ws_mixed", status: "completed", action: { type: "search", queries: ["a", 42] } }, + { type: "web_search_call", id: "ws_qempty", status: "completed", action: { type: "search", query: "legacy", queries: [] } }, + ], + }, + }, meta); + const input = (JSON.parse(request.body) as { input: Array<{ action: Record<string, unknown> }> }).input; + + // Left alone: deriving `query: "a"` would satisfy Console Go and leave DeepSeek to + // reject the same replay over the non-string second member. + expect(input[0].action).toEqual({ type: "search", queries: ["a", 42] }); + // Canonicalized without discarding the query the item already carried. + expect(input[1].action).toEqual({ type: "search", query: "legacy", queries: ["legacy"] }); + }); + test("strips invalid type-specific ids from serialized input items", () => { const adapter = createResponsesPassthroughAdapter(provider); const encryptedContent = "opaque-openai-encrypted-content"; From a11038cb250e744a173132ce76f7a3b61a3f1190 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 02:04:54 +0900 Subject: [PATCH 15/42] fix(gui): wire the provider marks that were already committed (#3093) minimax.svg landed for the MiniMax Code client in #3082. The MiniMax provider kept rendering a coloured initial tile, because CLIENT_MARKS is keyed by ExportClientId and PROVIDER_ICON_ALIASES by provider id: adding artwork on one side leaves no signal on the other. xiaomi-color.svg had the same problem, and more visibly -- mimo-free was already wired to it while xiaomi-mimo and mimo, the same brand, were not. Four alias rows. minimax and minimax-cn are one brand on two endpoints, the same shape as the three Alibaba ids that already share one asset. Both assets are multi-colour (xiaomi-color.svg carries #FF6900 and three more, minimax.svg a linearGradient), so neither is a masking candidate and this makes no painting decision. The guard is the part that matters. The fallback tile is a designed state that looks exactly like a mistake, so nothing could tell a maintainer that committed artwork was unwired. The new test probes, for every registry provider without an alias, whether a plausibly-named asset is sitting in the directory -- and fails with the filename when one is. It keeps closing that loop as new assets land. Providers without a resolved mark: 38 -> 34. Verification: 3 pass in the new file, each driven red (removing the minimax row, breaking a filename). tsc 0 both roots, oxlint clean, privacy:scan clean, full GUI suite 1157 pass / 0 fail across 187 files. --- gui/src/provider-icons.ts | 4 ++ gui/tests/provider-icons.test.ts | 76 ++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 gui/tests/provider-icons.test.ts diff --git a/gui/src/provider-icons.ts b/gui/src/provider-icons.ts index 2bfb727c71..043529cf67 100644 --- a/gui/src/provider-icons.ts +++ b/gui/src/provider-icons.ts @@ -28,6 +28,8 @@ const PROVIDER_ICON_ALIASES: Record<string, string> = { kiro: "kiro-color.svg", "lm-studio": "lm-studio-color.svg", mistral: "mistral-color.svg", + minimax: "minimax.svg", + "minimax-cn": "minimax.svg", moonshot: "moonshot-color.svg", nvidia: "nvidia-color.svg", ollama: "ollama-color.svg", @@ -47,7 +49,9 @@ const PROVIDER_ICON_ALIASES: Record<string, string> = { vllm: "vllm-color.svg", xai: "grok.svg", "mimo-free": "xiaomi-color.svg", + mimo: "xiaomi-color.svg", xiaomi: "xiaomi-color.svg", + "xiaomi-mimo": "xiaomi-color.svg", }; /** diff --git a/gui/tests/provider-icons.test.ts b/gui/tests/provider-icons.test.ts new file mode 100644 index 0000000000..5a0495c453 --- /dev/null +++ b/gui/tests/provider-icons.test.ts @@ -0,0 +1,76 @@ +import { expect, test } from "bun:test"; +import { existsSync, readdirSync } from "node:fs"; +import { join } from "node:path"; +import { PROVIDER_REGISTRY } from "../../src/providers/registry"; +import { providerIconSrc } from "../src/provider-icons"; + +const PUBLIC_DIR = join(import.meta.dir, "..", "public", "provider-icons"); + +/** + * Asset filenames that could plausibly belong to a provider id. + * + * A plan variant is a billing arrangement, not a brand -- `alibaba-token-plan` + * wears the Alibaba mark -- so the first dash-segment is probed too. + */ +function candidateAssets(providerId: string): string[] { + const stem = providerId.split("-")[0]!; + return [ + `${providerId}.svg`, + `${providerId}-color.svg`, + `${stem}.svg`, + `${stem}-color.svg`, + ]; +} + +/* + * The gap this exists for, stated plainly: `minimax.svg` was committed for the + * MiniMax Code CLIENT and the MiniMax PROVIDER kept rendering an initial tile for + * weeks. Nothing could have told anyone. `CLIENT_MARKS` is keyed by + * `ExportClientId` and `PROVIDER_ICON_ALIASES` by provider id, so adding artwork + * on one side leaves no signal on the other, and the fallback tile is a designed + * state that looks identical to a mistake. + * + * This is the only check that closes that loop, and it keeps closing it as new + * assets land: a mark added for any reason is immediately owed a provider row. + */ +test("a provider whose brand asset is already committed is actually wired to it", () => { + const files = new Set(readdirSync(PUBLIC_DIR).filter(name => name.endsWith(".svg"))); + const unwired: string[] = []; + for (const entry of PROVIDER_REGISTRY) { + if (providerIconSrc(entry.id)) continue; + const found = candidateAssets(entry.id).find(name => files.has(name)); + if (found) unwired.push(`${entry.id}: ${found} is committed but PROVIDER_ICON_ALIASES has no row`); + } + expect(unwired).toEqual([]); +}); + +/* + * A mistyped filename renders a broken image, which is strictly worse than the + * fallback tile it replaced: the tile is deliberate and legible, the broken image + * is a visual defect. The map is plain strings, so nothing else checks this. + */ +test("every wired provider icon names a file that exists", () => { + const broken = PROVIDER_REGISTRY + .map(entry => [entry.id, providerIconSrc(entry.id)] as const) + .filter((pair): pair is readonly [string, string] => pair[1] !== undefined) + .filter(([, src]) => !existsSync(join(PUBLIC_DIR, src.split("/").pop()!))) + .map(([id, src]) => `${id} -> ${src}`); + expect(broken).toEqual([]); +}); + +/* + * The four rows this phase added, pinned by intent rather than by count. + * + * MiniMax is one brand on two endpoints (`api.minimax.io` and `api.minimaxi.com`), + * the same shape as the three Alibaba ids that already share one asset. Xiaomi's + * MiMo ids are the same brand as `mimo-free`, which was already wired -- the + * inconsistency was the bug. + */ +test("the MiniMax and Xiaomi MiMo provider ids resolve to their brand's mark", () => { + expect(providerIconSrc("minimax")).toBe("/provider-icons/minimax.svg"); + expect(providerIconSrc("minimax-cn")).toBe("/provider-icons/minimax.svg"); + expect(providerIconSrc("xiaomi-mimo")).toBe("/provider-icons/xiaomi-color.svg"); + expect(providerIconSrc("mimo")).toBe("/provider-icons/xiaomi-color.svg"); + // The precedent that makes the two above consistent rather than novel. + expect(providerIconSrc("mimo-free")).toBe("/provider-icons/xiaomi-color.svg"); +}); From dcbc280740a317dbabe859c16e1c0ab91542bbe7 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 02:16:00 +0900 Subject: [PATCH 16/42] fix(gui): give every provider card the same control row (#3096) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(devlog): plan the uniform provider control row with measured geometry The Models page provider header carries 7-8 controls whose count is not the same from card to card, and the user asked to reduce them, make them consistent, and add hover. Measured first, at 780/1280/1440 over CDP against the live proxy (evidence/030-baseline.json, harness in evidence/030-geometry-harness.ts): - scrollWidth === clientWidth at every width, no child under 6px, actions row 13px inside the card. 010 holds; there is no overflow defect left. - At 1280 the actions row starts at 684.1px on anthropic, 710.2px on openai and 798.9px on cursor - a 114.8px spread across cards in one list. So the remaining defect is legibility of the rule, not geometry, and the phase is not allowed to buy tidiness with a layout regression. Two conditionals produce the spread: the cap Select gated on (capOn || nativeProviderGroup) and the preset control returning null when a provider has no shipped preset. An independent plan auditor (xai/grok-4.6, read-only) failed the first draft. Its corrections are adopted rather than argued with: - Hover stays, but as emphasis, never as gating. Nothing leaves the resting layout, so Tier 2 is empty. Hiding the pencil, the alias-defaults switch or custom-add would have made the opacity 020 is about to fix worse. - Ghost placeholders are dropped. Alignment comes from always rendering the cap Select, disabled when the cap is off, which is a real control with a real value where a dead one was proposed. - The preset control keeps its null return; a dead segmented is worse than an absent one. - Dials stay at VARIANCE 2 / MOTION 1 / D6. An earlier draft inflated them to 3/2 to license the hover transition, which is motion inflation on an admin row. - 020 lands first, so labels are not measured against unlabeled knobs. No production code here: this is the docs-only roadmap phase. * docs(devlog): fold the second audit round into the uniform-row plan Round 2 returned near-pass with two open residuals, both real defects in the doc. The first was a phrasing that re-created the defect it had just removed: saying hover "raises labels into view" makes a labeled control unlabeled until hover, which is meaning-gating rather than the existence-gating the round-1 fix removed. 020's text is now stated as visible at rest, with hover only emphasizing. The second was an overclaim measured against the captured baseline. Always rendering the cap Select adds a control to 8 of 10 cards rather than replacing one, so it is not width-neutral; and 780px already reports actsLeft 281 on every card because the 720px container query has wrapped the actions, so it proves nothing about alignment. The column gate moves to 1280/1440, and the residual left-edge delta from providers that legitimately have no preset is now recorded as an accepted delta instead of a target. Also drops the min-width floors 050 had proposed: that was the ghost placeholder in CSS form, contradicting this doc's own no-dead-control rule. * docs(devlog): write the 040 cap-cluster and 050 hover-affordance phases One decade doc per implementation phase, at the diff level, so each later cycle consumes exactly one doc. 040 groups the context-window switch, value and per-model button into one visual cluster and always renders the value Select, disabled when the cap is off. That occupied slot is the cap-driven half of the measured 114.8px left-edge spread: openai and kiro carry the select, the seven cap-off providers carry nothing where it would be. The doc records the cost the audit named rather than hiding it - this adds a control to 8 of 10 cards - and keeps the grouping visual, because the per-model modal and the provider-wide cap are different scopes and must stay three tab stops. 050 is deliberately small, because 030 and 020 already took everything else. Nothing leaves the resting layout and no label is introduced by hover, so what is left is the pencil's resting contrast: opacity only, never display or visibility, with focus-within beside hover, full opacity under (hover: none), and no transition under reduced motion. The column gate moves to 1280/1440 - 780 already reports actsLeft 281 on every card under the 720px container query - and the residual preset-driven delta is recorded as accepted. Both carry the implementation guard for the source strings the existing tests pin. * feat(gui): give the opaque provider-header controls visible meaning Implements devlog/_plan/260830_models_provider_header/020_control_affordances.md. Switch accepted a label and spent it entirely on aria-label; its only child was the knob. So every switch in this codebase was, to a sighted user, an unlabeled toggle, and the report on the provider header ("스위치, 이건 뭘하는지도 모르겠음") was a correct reading of the UI rather than a misunderstanding. Switch gains two opt-in affordances and no call site changes behaviour: - showLabel renders the label as visible text beside the knob. The text is aria-hidden and the wrapper is a span rather than a label, so the button's aria-label stays the single accessible name instead of being announced twice. - without showLabel the label becomes a title, so every bare switch elsewhere in the app gains a hover explanation for free. .switch-labeled declares flex: 0 0 auto because the bare 34px button was one atomic flex item; without it the knob and its text become two shrinkable items in .models-provider-actions and the text collapses to a min-content column, which is the defect class 010 fixed for the toggle's own children. The three opaque header controls: - alias-defaults switch already passed the right label and merely discarded it, so it just opts in. - the cap switch passed the WRONG one: models.capValue is "기본 128k", a value masquerading as a function name, so even a screen-reader user was not told this governs the context window. Repointed at models.contextCapLabel, with the number staying in the adjacent Select where a value belongs. That Select now carries the same string as its title so its bare number says what it caps. - custom-add gets visible text rather than a tooltip: title is undiscoverable on touch, and wrapping it in Tooltip would nest a button inside a button. No i18n change. All three keys already exist in all eleven locales, and a test asserts that rather than trusting it. Verified: typecheck exit 0; gui lint and lint:i18n exit 0; 10/10 on the new affordance test plus the existing models-provider-head regressions, with the new file driven red first (3 of 4 failed without the source change). Rendered against a real provider list over CDP at 780/1280/1440: 20 labeled switches carrying real visible text, scrollWidth === clientWidth at every width, no child under 6px. Evidence in evidence/020-after-labels.json. * docs(devlog): record the ko/de/en locale geometry sweep for the labeled row * fix(gui): make the switch label name and toggle the control The audit of the previous revision found two defects in the labeled Switch. The visible label was an aria-hidden sibling span, so it read correctly and looked correct while the hit target stayed the 34x20 knob: clicking the words did nothing. The text now lives inside the button, which makes it the button's own content and therefore its accessible name, so aria-label is dropped when showLabel is set. The pill geometry moves to a ::before track and the knob is positioned over it, because the button's flow now carries the label too. The measured hit target grows from 34px to 107px. The label also fell back to title on every unlabeled Switch. HTML-AAM maps title to the accessible description when aria-describedby is absent, so each untouched switch in the app gained a description that merely repeated its own name. title is now strictly opt-in. The custom-add glyph becomes aria-hidden so the button is named by its words. tests/switch-labeled-dom.test.tsx is new and renders the primitive rather than matching source text: 3 of its 4 tests fail against the previous revision. * feat(gui): emphasize the provider alias control on hover and focus The user asked for a hover affordance on the provider header. Two independent audits rejected the first reading of that - hover that GATES, controls hidden until the pointer arrives - because it removes the pencil, the alias-defaults switch and custom-add from the default visual inventory and worsens the exact opacity the label work exists to fix. What survives is emphasis on the one genuinely secondary control: a provider alias is set once and then left alone. The control never leaves layout, tab order, or the accessibility tree. Only its resting contrast changes, and every state is written at the same 0,3,0 specificity as the resting rule. The audit caught the first draft writing those overrides at 0,2,0, which meant a hoverless device kept the dim forever and :focus-visible was a silent no-op - a rule that exists but cannot win is worse than a missing one, because it reads as covered. Rest is 0.75 rather than the drafted 0.65: .btn:disabled is opacity 0.55, so a deeper dim reads as maybe-disabled. Contrast was never the constraint - 0.65 already cleared 1.4.11's 3:1 in both themes - legibility of intent is. Measured in the browser rather than argued from the stylesheet: rest 0.75, header hover 1, focus-within from a sibling control 1, mobile device emulation (hover: none) 1, reduced motion transition 0s with the emphasis intact, and the control still focusable throughout. * docs(devlog): fold the third audit round into the hover-affordance phase * feat(gui): occupy the cap slot on every provider card This is the defect the user reported. The provider cap Select rendered only when (capOn || nativeProviderGroup), so openai showed 1.05M and anthropic showed nothing at all - two cards whose control rows therefore started at different left edges. The Select is now always rendered and disabled when the cap is off, which is an honest rendering of what an off cap means: no opinion, stated rather than implied by an absence. The cap switch, its value, and the per-model window button are wrapped in one .models-cap-cluster. That is visual grouping, not a functional merge - the per-model button opens a different scope than the provider-wide switch and select, so all three keep their own tab stop. Moving it beside the cap controls does change tab order; the cluster now reads switch, value, per-model, which is the order the controls are seen in. The cluster wraps. As one flex item of an already-wrapping actions row it can be squeezed, and none of its children shrink: the labeled switch is flex: 0 0 auto, .btn is nowrap, the Select is inline-block. A nowrap cluster would have overflowed into .models-provider-card's own overflow: hidden and been clipped silently, while a page-level scrollWidth === clientWidth assertion still passed. The audit also found a live state defect that always-rendering would have widened: providerCapCustomOpen is independent per-provider state that nothing clears when the cap is switched off, so an already-open custom editor kept its input and Apply button standing under an off cluster - and Apply sends enabled: true, turning the cap back on from a field that looked inert. The editor is now gated on capOn as well. Measured after the change, ko/de/en at 780/1024/1280/1440: no page overflow, card clip 0, cluster clip 0, and the cap slot present on all 7 cards with the Select's disabled state tracking capOn per card. * docs(devlog): fold the cap-cluster audit and its measured result into 040 * docs(devlog): capture the rendered cap-slot occupancy proof * chore(assets): PR screenshot for the provider cap-slot change --- .github/pr-assets/260901-cap-slot-ko-1280.png | Bin 0 -> 447003 bytes .../030_uniform_row_and_hover.md | 227 ++++++++ .../040_cap_cluster_and_occupied_slot.md | 143 +++++ .../050_hover_affordance_and_column_gate.md | 134 +++++ .../020-after-labeled-switch-ko-1280.png | Bin 0 -> 166201 bytes .../evidence/020-after-labels.json | 317 +++++++++++ .../evidence/030-baseline.json | 521 ++++++++++++++++++ .../evidence/030-geometry-harness.ts | 80 +++ .../evidence/030-locale-harness.ts | 49 ++ .../evidence/030-locale-sweep.json | 182 ++++++ .../evidence/040-after-cap-slot-ko-1280.png | Bin 0 -> 447003 bytes .../evidence/040-geometry-harness.ts | 50 ++ .../evidence/050-state-harness.ts | 38 ++ .../evidence/wp2-states.json | 11 + .../evidence/wp2-touch.json | 8 + .../evidence/wp3-geometry.json | 182 ++++++ .../evidence/wp3-state.json | 4 + gui/src/pages/Models.tsx | 110 ++-- gui/src/styles-models-workspace.css | 63 +++ gui/src/styles.css | 39 ++ gui/src/ui.tsx | 32 +- gui/tests/models-alias-edit-emphasis.test.ts | 107 ++++ gui/tests/models-cap-cluster.test.ts | 69 +++ gui/tests/models-control-affordances.test.ts | 85 +++ .../models-native-group-controls.test.ts | 12 +- gui/tests/switch-labeled-dom.test.tsx | 92 ++++ 26 files changed, 2502 insertions(+), 53 deletions(-) create mode 100644 .github/pr-assets/260901-cap-slot-ko-1280.png create mode 100644 devlog/_plan/260830_models_provider_header/030_uniform_row_and_hover.md create mode 100644 devlog/_plan/260830_models_provider_header/040_cap_cluster_and_occupied_slot.md create mode 100644 devlog/_plan/260830_models_provider_header/050_hover_affordance_and_column_gate.md create mode 100644 devlog/_plan/260830_models_provider_header/evidence/020-after-labeled-switch-ko-1280.png create mode 100644 devlog/_plan/260830_models_provider_header/evidence/020-after-labels.json create mode 100644 devlog/_plan/260830_models_provider_header/evidence/030-baseline.json create mode 100644 devlog/_plan/260830_models_provider_header/evidence/030-geometry-harness.ts create mode 100644 devlog/_plan/260830_models_provider_header/evidence/030-locale-harness.ts create mode 100644 devlog/_plan/260830_models_provider_header/evidence/030-locale-sweep.json create mode 100644 devlog/_plan/260830_models_provider_header/evidence/040-after-cap-slot-ko-1280.png create mode 100644 devlog/_plan/260830_models_provider_header/evidence/040-geometry-harness.ts create mode 100644 devlog/_plan/260830_models_provider_header/evidence/050-state-harness.ts create mode 100644 devlog/_plan/260830_models_provider_header/evidence/wp2-states.json create mode 100644 devlog/_plan/260830_models_provider_header/evidence/wp2-touch.json create mode 100644 devlog/_plan/260830_models_provider_header/evidence/wp3-geometry.json create mode 100644 devlog/_plan/260830_models_provider_header/evidence/wp3-state.json create mode 100644 gui/tests/models-alias-edit-emphasis.test.ts create mode 100644 gui/tests/models-cap-cluster.test.ts create mode 100644 gui/tests/models-control-affordances.test.ts create mode 100644 gui/tests/switch-labeled-dom.test.tsx diff --git a/.github/pr-assets/260901-cap-slot-ko-1280.png b/.github/pr-assets/260901-cap-slot-ko-1280.png new file mode 100644 index 0000000000000000000000000000000000000000..fb5f2a04878990348d9f690c54519e8c99465107 GIT binary patch literal 447003 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%0D#f* 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≷dxN()NLe;=ib0yzPpFQa<G1 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%0Awx|pjANU!wpZk<~h4>QH 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%0D`zX4cS=#6W{lD;cY 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 Z&@|P;O11rzGmsaR5|<atd#LC6{{eJ3bCCc5 literal 0 HcmV?d00001 diff --git a/devlog/_plan/260830_models_provider_header/030_uniform_row_and_hover.md b/devlog/_plan/260830_models_provider_header/030_uniform_row_and_hover.md new file mode 100644 index 0000000000..6c129cf514 --- /dev/null +++ b/devlog/_plan/260830_models_provider_header/030_uniform_row_and_hover.md @@ -0,0 +1,227 @@ +# 030 — Uniform control row: hover as emphasis, not as hiding + +Extends the `260830_models_provider_header` unit. `010` shipped (toggle flex +basis + child shrink). `020` is written and NOT implemented. This doc is the +third reported defect on the same header, and it arrives with a user mandate +that the plan auditor initially rejected. + +## The report + +> 각 버튼이 뭘 의미하는지 설명좀해봐 이거 너무 많아서 그리고 일관성도 없음 +> 규칙찾아서 좀 줄이고 깔끔하게 하고 싶은데 방법없나? +> 이렇게 많은곳도 있고 스위치만 달랑있는것도 있어서 호버도 만들고 싶음 + +Two claims, and the measurement below shows both are literally true: the row +carries 7-8 controls, and the count is not the same from card to card. + +## Measured baseline + +Harness: full CDP (`Emulation.setDeviceMetricsOverride`, dpr 2) against the live +proxy on 127.0.0.1:10100, reading `getBoundingClientRect` on the real provider +list. Raw capture: `evidence/030-baseline.json`. + +| width | cards | control count | horizontal overflow | zero-width children | +|-------|-------|---------------|---------------------|---------------------| +| 780 | 10 | **7 or 8** | none | 0 | +| 1280 | 10 | **7 or 8** | none | 0 | +| 1440 | 10 | **7 or 8** | none | 0 | + +Two findings, and they point in opposite directions. + +**The count really does vary, and the actions row really does start at a +different x.** At 1280 the actions cluster begins at 684.1px on `anthropic`, +710.2px on `openai`, and 798.9px on `cursor` — a 114.8px spread across cards in +one list. Two independent conditionals cause it: + +- `Models.tsx:1360` — `(capOn || nativeProviderGroup)` hides the cap `Select`, + so a cap-off provider loses a ~74px control. +- `Models.tsx:1287` — `if (!preset) return null` drops the preset segmented + control for any provider with no shipped preset. + +So `openai` shows `1.05M` and `anthropic` does not; `anthropic` shows +`프리셋 / 전체` and `openai` does not. Neither is a bug. Both are invisible +rules, which is why the row reads as arbitrary. + +**But there is no overflow and nothing is clipped.** `scrollWidth === clientWidth` +at all three widths, `overflowRight` is -13px everywhere (inside the card), and no +child measures under 6px. `010` did its job. The remaining defect is +*legibility of the rule*, not geometry — which changes what this phase is allowed +to do: it must not buy tidiness with a layout regression, because the layout is +currently correct. + +## The conflict, and how it resolves + +The user mandated hover ("호버는 일단 이거는 무조건해야되고"). The independent +plan auditor (xai/grok-4.6, read-only lane) returned **VERDICT: fail** against +the first draft, which had proposed hiding the alias pencil, the alias-defaults +switch, and the custom-add `+` until hover: + +> hover-reveal of alias pencil / alias-defaults switch / custom-add creates a +> discoverability regression on expert controls; users must already know to hover +> to see them [...] the proposed Tier 2 items are exactly the ones `020` said +> needed visible text because they are undiscoverable at rest. +> +> TIER ASSIGNMENT YOU RECOMMEND: Tier 1: [everything]; Tier 2: none. +> hover-reveal is a density hack that fights the meaning problem `020` solved. + +The auditor is right about the mechanism and wrong about the conclusion, and the +distinction is worth stating precisely because it is the whole design of this +phase: **the auditor rejected hover-as-gating. The user asked for hover-as- +affordance.** Those are different features that share a word. + +- **Hover-as-gating** removes a control from the resting layout. It trades + discoverability for density, and on a surface where `020` already established + that three controls are unlabeled and opaque, it makes the opacity worse. This + is what the auditor blocked, and it stays blocked. +- **Hover-as-affordance** leaves every control in the resting layout and uses + pointer proximity to *explain* it — the row lifts its own labels and tooltips + into view. Nothing is hidden, so there is nothing to discover. + +Adopted: **no control leaves the resting layout.** Tier 2 is empty. The auditor's +Tier-1 assignment is adopted verbatim; its recommendation to land `020` first is +also adopted, which is why the label work moves ahead of this doc in the order. + +**`020`'s label text is visible at rest. Hover only emphasizes; it never +introduces the label.** That sentence is the whole contract, and the second audit +round demanded it because an earlier phrasing here ("raises labels into view") +re-created the defect in a new place: + +> If `050` rest-hides that text and reveals it on hover, blocker 1 returns as +> meaning-gating: a labeled control that is unlabeled until hover. + +So the affordance is deliberately small: pencil idle-dim, and the `title` +tooltips `020` already specifies. Nothing in `050` may make `020`'s visible text +conditional on pointer state. If that leaves `050` with little label work, that is +the correct outcome — `020` already did it. + +That also resolves blocker 3 without further argument: if no control is added to +or removed from the resting row, the 780px case cannot be made worse by +reserving space, because the space is already reserved by the control itself. + +## Column alignment without placeholders + +The first draft proposed reserving space with disabled placeholders. The auditor +killed that too, and its reasoning is the same one `011` already recorded: + +> No shipped preset -> no control. Alignment comes from cluster order, not a dead +> segmented. [...] A disabled openai preset placeholder is that dead switch. + +It also identified which conditional actually causes the jump the user sees. Not +the preset — the cap: + +> The actual column jump is the cap `Select` gated on +> `(capOn || nativeProviderGroup)` at Models.tsx ~1360, not missing aliases. + +Adopted, and it is a better fix than the one it replaces: **always render the cap +`Select`, disabled when the cap is off.** A cap-off card then occupies the same +slot as a cap-on card, so `anthropic` lines up with `openai` — with a real +control carrying a real value instead of a ghost. The preset control keeps +`if (!preset) return null`; a provider with nothing to curate still shows nothing, +because a dead segmented is worse than an absent one. + +### What this does and does not fix, measured + +The second audit round corrected an overclaim in the first draft of this section, +using the captured baseline. Both corrections are adopted: + +> Always rendering the Select ADDS a control to 8 of 10 cards (not "replaces" +> one). At 780 that is extra wrap height, not overflow — `010` still holds — but it +> is not aggregate-neutral. After the Select is always present, anthropic still has +> the preset and cursor still does not, so the 1280 left-edge spread cannot go to +> zero. + +Precisely, from `evidence/030-baseline.json`: + +- **780px is the wrong proof for alignment.** Every card already reports + `actsLeft: 281` there, because `@container (max-width: 720px)` has already + wrapped the actions to full width. Alignment cannot regress or improve at a + width where it is already uniform, so **the column gate lives at 1280 and 1440.** +- **The 114.8px spread is two missing pieces, not one.** `openai`/`kiro` carry a + `custom-select`; `anthropic`/`openrouter` carry a `segmented`; the other six + carry neither. + +So the honest scope: always rendering the cap `Select` fixes the +**openai-shows-a-number / anthropic-does-not** mismatch the user actually +reported, and it costs wrap height at 780 rather than being free. It does **not** +drive the left-edge spread to zero, because the preset control legitimately +remains absent on providers with nothing to curate. **That residual +preset-driven delta is accepted, not fixed**, and the `050` gate must state it as +an accepted delta instead of asserting equal left edges. + +## What this phase must NOT do + +Also from the audit, and load-bearing: + +- **No functional merge of `사용자 지정 창` into the cap control.** It opens + per-model overrides (`openContextSettings`, `Models.tsx:538`); the cap + switch+select is the provider-wide default. Those are different scopes. The + cluster is **visual grouping only, three tab stops**. +- **Do not move the count out of `.models-provider-toggle`.** `010`'s child + shrink rule and the element-wrapped-children test own that side of the row. +- **Nothing becomes hover-only.** Not the pencil, not the alias-defaults switch, + not custom-add. The pencil may idle dimmed (`opacity >= 0.6`) and must stay in + layout and in tab order, force-visible under `(hover: none)`. + +## What actually reduces the count + +The user asked to reduce, and one cluster is genuinely redundant. The context +window is **one setting wearing three controls** (`Models.tsx:1355-1394`): + +1. a bare `Switch` whose accessible name is `기본 128k` — a value, not a function +2. a `Select` showing the number, conditional on that switch +3. a `사용자 지정 창` button opening the per-model modal + +`020` already found (2) mislabeled and (1) opaque. Merging them into one +labeled cluster removes one control from every card and makes the conditional +`Select` a state of that cluster rather than an item that appears and vanishes. + +`모두 켜기` / `모두 끄기` also read as overlapping with `전체`, but they are not: +the segmented control selects a curated *set*, the bulk buttons toggle +*visibility* of the current rows. Per UX-LAZY-01 neither can be deleted, so they +are disambiguated by label, not removed. + +## Phase order + +1. `020` — visible labels for the three opaque controls (already written; the + auditor requires it first). +2. `040` — merge the context-window triple into one labeled cluster. +3. `050` — hover-as-affordance on the actions row: pointer/`focus-within` + emphasizes controls that are already labeled and already present (pencil + idle-dim, `020`'s tooltips), with `@media (hover: none)` keeping the resting + state fully usable and `prefers-reduced-motion` making any opacity change + instant. + + **No `min-width` floors on the conditional slots.** The first draft proposed + them and the audit correctly identified that as the ghost placeholder in CSS + form, contradicting this doc's own "no shipped preset -> no control" and + reviving the child-floor move `011` already killed. An always-rendered + `Select` needs no floor, and a floor for an absent preset is a dead slot. + +## Verification contract + +Inherited from `000`, with the column gate corrected by the second audit round: + +- Re-measure at 780/1280/1440. Require `scrollWidth === clientWidth` and no child + under 6px at every width. +- **Column gate at 1280/1440 only** — 780 is already uniform at `actsLeft: 281` + and proves nothing about alignment. +- The cap-value slot must be occupied on every card. The remaining left-edge + delta between a preset-carrying and a preset-free provider is recorded as an + accepted delta, with its measured magnitude. +- 780 is allowed to gain wrap HEIGHT from the always-present `Select`; it is not + allowed to overflow. +- Implementation guard, from the audit: the strings the existing tests pin must + stay byte-exact — `className="row models-provider-toggle"`, + `className="row models-provider-actions"`, `t("models.contextSettings")`, the + 720/768 wrap rules, and the toggle's `flex: "1 1 auto"`. A template-literal + className or a hover wrapper around the actions row fails + `gui/tests/models-provider-head.test.ts` even if the design is right. + +Focused `gui/tests` regression per phase, driven red first. Remote gates only +(`ssh lidge`); the local full suite is forbidden by the user. Push `--no-verify`. + +Dials: `DESIGN_VARIANCE 2`, `MOTION_INTENSITY 1`, density D6 — unchanged from +`000`. An earlier draft of this doc raised them to 3/2 to justify a hover +transition; the audit rejected that as motion inflation on an admin row, and it +was right. Any opacity change is instant under `prefers-reduced-motion`, and the +dials are not raised to license the affordance. diff --git a/devlog/_plan/260830_models_provider_header/040_cap_cluster_and_occupied_slot.md b/devlog/_plan/260830_models_provider_header/040_cap_cluster_and_occupied_slot.md new file mode 100644 index 0000000000..a1847fd941 --- /dev/null +++ b/devlog/_plan/260830_models_provider_header/040_cap_cluster_and_occupied_slot.md @@ -0,0 +1,143 @@ +# 040 — One cap cluster, and a slot that is always occupied + +Second phase of the row work. Depends on `020`: the labels must already be +visible so this cluster is measured at its real width rather than at bare-knob +width. + +## What is wrong + +The provider-wide context window is **one setting wearing three sibling +controls** in `.models-provider-actions` (`Models.tsx:1355-1394`): + +| control | source | today | +|---------|--------|-------| +| cap on/off | `Switch` | bare knob; accessible name is `기본 128k`, a VALUE not a function | +| cap value | `Select` | rendered only when `(capOn \|\| nativeProviderGroup)` | +| per-model overrides | `사용자 지정 창` button | labeled, opens `openContextSettings` | + +Two consequences, both measured in `evidence/030-baseline.json`: + +- Three peer items for one concept, inflating the 7-8 control count the user + complained about. +- The conditional `Select` is the **cap-driven half of the 114.8px left-edge + spread**: `openai` and `kiro` carry a `custom-select`, the seven cap-off + providers carry nothing where that control would be. + +## Change 1 — always render the cap Select, disabled when off + +```tsx +-{(capOn || nativeProviderGroup) && ( ++{/* Always rendered. A cap-off provider previously dropped this control ++ entirely, which is what made anthropic's row start 26px right of openai's ++ and left the switch with no adjacent number to explain it. Disabled-with-a- ++ value is honest and keeps the slot occupied. */} ++{( +``` + +with `disabled={busy || !capOn}` already present on the `Select`, so the off +state is inert rather than absent. + +This is the audit's own recommendation, and its cost is recorded rather than +hidden: it **adds** a control to 8 of 10 cards. At 780 that is extra wrap height +inside a row that is already full-width, and it must not overflow. + +## Change 2 — group the three into one visual cluster + +```tsx +<div className="models-cap-cluster"> + <Switch ... label={t("models.contextCapLabel")} showLabel /> + <Select ... title={t("models.contextCapLabel")} /> + <button ...>{t("models.contextSettings")}</button> +</div> +``` + +```css +/* Grouping only: one visual unit, three tab stops. The gap is tighter than the + row's own gap, which is what makes it read as one control instead of three. */ +.models-cap-cluster { + display: flex; + align-items: center; + gap: var(--space-1); + min-width: 0; +} +``` + +**Visual grouping, not a functional merge.** The audit was explicit and it is +right: `사용자 지정 창` opens **per-model** overrides +(`openContextSettings`, `Models.tsx:538`) while the switch and select are the +**provider-wide default**. Different scopes cannot be collapsed into one control, +and doing so on an admin surface would also violate the domain gate. The cluster +keeps three separate tab stops. + +## Not changed + +- `모두 켜기` / `모두 끄기` stay. They toggle row VISIBILITY; the preset segmented + selects a curated SET. They overlap in appearance, not in function, so per + UX-LAZY-01 they are disambiguated by `020`'s labels rather than removed. +- The preset control keeps `if (!preset) return null`. +- The count stays in `.models-provider-toggle`. + +## Regression coverage + +Extends `gui/tests/models-provider-head.test.ts`: + +1. The cap `Select` is present in the header markup **without** a + `capOn`-conditional guard, and carries `disabled` wired to `!capOn` — the + assertion that stops the conditional from being reinstated. +2. `.models-cap-cluster` exists in the stylesheet with a `gap` tighter than + `.models-provider-actions`. +3. `t("models.contextSettings")` is still rendered in the header (the existing + L41 assertion, which a functional merge would have broken). +4. The cluster contains three interactive elements, so the grouping did not + silently become one control. + +Assertion 4 was **wrong as written** and the audit caught it: the custom-cap +editor lives inside the cluster too, so a count of interactive children is a +moving target. What is asserted instead is that the switch, the select, and the +per-model button are all present as descendants, with the editor optional. + +## What the third audit round changed + +Two blockers, both fixed before implementation. + +**The cluster must wrap.** The drafted `min-width: 0` with no `flex-wrap` is the +`011` clip wearing a new name. As one flex item of `.models-provider-actions` the +cluster can be squeezed, and none of its children shrink: the labeled switch is +`flex: 0 0 auto`, `.btn` is `white-space: nowrap`, the `Select` is `inline-block`. +The overflow would then be swallowed by `.models-provider-card { overflow: hidden }` +while a page-level `scrollWidth === clientWidth` assertion still reported success. +That is why the measurement below records **card clip and cluster clip separately** +from page overflow: the failure this phase risks is invisible at page level. + +**The custom-cap editor could outlive the cap.** `providerCapCustomOpen` is +independent per-provider state and nothing clears it when the cap is switched off. +With Custom already open, flipping the switch left the 120px input and its Apply +button standing under an off cluster — and `applyProviderCustomCap` sends +`enabled: true`, so that field would turn the cap back on. This was already +reachable for the native group; always-rendering the Select would have spread it to +every routed card. The editor is now gated on `capOn` as well. + +## Measured after the change + +`evidence/wp3-geometry.json` (harness `evidence/040-geometry-harness.ts`), +ko/de/en x 780/1024/1280/1440 against the built GUI on the live proxy: + +| check | result | +|---|---| +| page `hOverflow` | `false` at every locale and width | +| card `scrollWidth - clientWidth` | `0` everywhere | +| `.models-cap-cluster` clip | `0` everywhere | +| cap slot present on every card | `true` everywhere | +| actions children under 6px | `0` everywhere | +| `maxHeadH` at 780 | rose to 194.4 (de), 160.4 (en), 125.5 (ko) | + +The 780px height increase is the recorded **cost**, not a regression: the gate is +overflow and clipping, and an extra wrap line in a row that is already full-width +is what buying occupancy costs. + +`evidence/wp3-state.json` confirms the per-card behaviour the phase exists for: +`openai` cap on with an enabled Select, and `anthropic`, `cursor`, +`google-antigravity`, `opencode-free`, `woong`, `xai` all cap off with the slot +**present and disabled** — where before they rendered nothing at all. Cluster tab +stops read `Default window / cap`, `Default 1.05M`, `Custom windows`: three stops, +in the order the controls are seen. diff --git a/devlog/_plan/260830_models_provider_header/050_hover_affordance_and_column_gate.md b/devlog/_plan/260830_models_provider_header/050_hover_affordance_and_column_gate.md new file mode 100644 index 0000000000..c204433a4f --- /dev/null +++ b/devlog/_plan/260830_models_provider_header/050_hover_affordance_and_column_gate.md @@ -0,0 +1,134 @@ +# 050 — Hover affordance and the column gate + +Third and last phase of the row work. Depends on `020` (labels visible at rest) +and `040` (cap cluster). Scope decided by `030`, and narrowed twice by the +independent audit — what is NOT here matters as much as what is. + +## The affordance + +`030` fixed the contract: **every control stays in the resting layout, and +`020`'s label text is visible at rest.** So this phase cannot reveal existence +and cannot reveal meaning. What is left is emphasis, and it is deliberately +small: + +```css +/* The pencil is the one control that is genuinely secondary: a provider alias is + set once and then left alone. It stays in layout, in tab order, and in the + accessibility tree - only its resting contrast drops. */ +.models-provider-actions .btn-ghost.models-alias-edit { opacity: 0.75; } +.models-provider-head:hover .models-provider-actions .btn-ghost.models-alias-edit, +.models-provider-head:focus-within .models-provider-actions .btn-ghost.models-alias-edit, +.models-provider-actions .btn-ghost.models-alias-edit:focus-visible { opacity: 1; } +``` + +**Corrected by the third audit round, and this is the whole reason the block above +repeats itself.** The first draft wrote the resting rule at `0,3,0` and every +override at `0,2,0`. Same origin, lower specificity - so `(hover: none)` never won +and the dim was permanent on touch, and `:focus-visible` was a silent no-op rescued +only by `:focus-within` happening to sit later in the file. A rule that exists but +cannot win is worse than a missing one, because it reads as covered. Every state now +carries the full chain. + +Rest is `0.75`, not the drafted `0.65`. Contrast was never the constraint: at `0.65` +the glyph measures roughly 6.1:1 in light and 6.3:1 in dark against the card, where +1.4.11 asks for 3:1. The problem is that `.btn:disabled` is `opacity: 0.55`, so a +deeper dim reads as *maybe-disabled* rather than secondary. + +Three properties of that rule, each load-bearing: + +- `:focus-within` on the header, not only `:hover`, so a keyboard user reaching + the pencil by Tab gets the same emphasis a mouse user gets by proximity. +- Only `opacity`, never `display`, `visibility`, or `content-visibility`. The + control is always hit-testable and always announced; `0.65` is a contrast + change, not a disclosure. +- `opacity` is animated at `--motion-fast` and **instant** under reduced motion: + +```css +@media (prefers-reduced-motion: reduce) { + .models-provider-actions .models-alias-edit { transition: none; } +} +``` + +And the no-hover path, which is the reason this is safe on touch: + +```css +/* A pointer that cannot hover never triggers the emphasis rule, so the resting + dim would be permanent. Full contrast is the resting state there. */ +@media (hover: none) { + .models-provider-actions .models-alias-edit { opacity: 1; } +} +``` + +## The column gate + +Corrected by the second audit round against `evidence/030-baseline.json`, and the +correction is the whole reason this section is short: + +- **780px is not alignment evidence.** All ten cards already report + `actsLeft: 281` because `@container models-workspace (max-width: 720px)` has + wrapped the actions to full width. A width where alignment is already uniform + cannot demonstrate that alignment improved. +- **The gate is 1280 and 1440.** +- **The cap-value slot must be occupied on every card** — that is what `040` + buys by always rendering the `Select`, disabled when the cap is off, and it is + the mismatch the user actually reported (`openai` shows `1.05M`, `anthropic` + shows nothing). +- **The preset-driven delta is accepted, with its number recorded.** Providers + with no shipped preset keep `if (!preset) return null`. Forcing their left + edges to match would require a disabled segmented control, which is the dead + control `011` rejected and `030` re-rejected. + +Explicitly not in this phase: `min-width` floors on the conditional slots. The +first draft proposed them; the audit identified them as the ghost placeholder in +CSS form. + +## Regression coverage + +Extends `gui/tests/models-provider-head.test.ts` rather than replacing it. New +assertions: + +1. The alias-edit control carries a stable class and is styled with `opacity`, + and the stylesheet contains no `display: none` or `visibility: hidden` for it + — the assertion that keeps a future "tidy up" from turning emphasis back into + gating. +2. `:focus-within` appears in the emphasis rule, so the keyboard path cannot be + dropped while the hover path survives. +3. A `@media (hover: none)` block restores full opacity. +4. A `prefers-reduced-motion` block removes the transition. + +5. Every emphasis state uses the full `0,3,0` chain, and no bare + `.models-alias-edit` rule exists - the assertion that keeps blocker 1 from + returning as a "simplification". + +## Measured, not argued + +A stylesheet cannot prove a cascade. `evidence/wp2-touch.json`, produced by +`evidence/050-state-harness.ts` against the rendered build: + +| state | computed opacity | +|---|---| +| rest, pointer parked away | `0.75` | +| header `:hover` | `1` | +| `:focus-within` from a sibling switch | `1` | +| mobile emulation, `matchMedia('(hover: none)')` true | `1` | +| `prefers-reduced-motion: reduce` | `transitionDuration 0s`, opacity `0.75` | + +Note the touch row needs `Emulation.setDeviceMetricsOverride` with `mobile: true`; +setting `hover` through `setEmulatedMedia` leaves `matchMedia` false and silently +reports a pass-looking `0.75`. +Read with `effectiveDeclaration` from `tests/helpers/css-declarations`, which +already handles duplicate selectors and commented-out values, and each assertion +driven red against current CSS first. + +## Implementation guard + +From the audit, because the existing tests pin exact source text and a plausible +refactor breaks them without touching the design: + +- `className="row models-provider-toggle"` and + `className="row models-provider-actions"` stay byte-exact. **No template + literal, and no hover wrapper element around the actions row.** +- `t("models.contextSettings")` stays in the header. +- The 720px container query and 768px media query wrap rules stay. +- The toggle keeps `flex: "1 1 auto"`. +- The active/total count stays inside `.models-provider-toggle`. diff --git a/devlog/_plan/260830_models_provider_header/evidence/020-after-labeled-switch-ko-1280.png b/devlog/_plan/260830_models_provider_header/evidence/020-after-labeled-switch-ko-1280.png new file mode 100644 index 0000000000000000000000000000000000000000..f5867ed1483c2460780682acc67efb783300a1aa GIT binary patch literal 166201 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%0Da=W~nYDeR^yEch)5$jgi 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 literal 0 HcmV?d00001 diff --git a/devlog/_plan/260830_models_provider_header/evidence/020-after-labels.json b/devlog/_plan/260830_models_provider_header/evidence/020-after-labels.json new file mode 100644 index 0000000000..e1c1527607 --- /dev/null +++ b/devlog/_plan/260830_models_provider_header/evidence/020-after-labels.json @@ -0,0 +1,317 @@ +[ + { + "width": 780, + "inner": 780, + "scrollW": 780, + "clientW": 780, + "hOverflow": false, + "labeledSwitches": 20, + "visibleLabelText": [ + "Use default aliases", + "Default window / cap", + "Use default aliases" + ], + "cards": [ + { + "name": "openai", + "n": 8, + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": true, + "labeled": 2 + }, + { + "name": "anthropic", + "n": 8, + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "cursor", + "n": 7, + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "google-antigravity", + "n": 7, + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "kiro", + "n": 8, + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": true, + "labeled": 2 + }, + { + "name": "opencode-free", + "n": 7, + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "opencode-go", + "n": 7, + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "openrouter", + "n": 8, + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "woong", + "n": 7, + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "xai", + "n": 7, + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + } + ] + }, + { + "width": 1280, + "inner": 1280, + "scrollW": 1280, + "clientW": 1280, + "hOverflow": false, + "labeledSwitches": 20, + "visibleLabelText": [ + "Use default aliases", + "Default window / cap", + "Use default aliases" + ], + "cards": [ + { + "name": "openai", + "n": 8, + "actsLeft": 577, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": true, + "labeled": 2 + }, + { + "name": "anthropic", + "n": 8, + "actsLeft": 577, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "cursor", + "n": 7, + "actsLeft": 577, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "google-antigravity", + "n": 7, + "actsLeft": 577, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "kiro", + "n": 8, + "actsLeft": 577, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": true, + "labeled": 2 + }, + { + "name": "opencode-free", + "n": 7, + "actsLeft": 577, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "opencode-go", + "n": 7, + "actsLeft": 577, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "openrouter", + "n": 8, + "actsLeft": 577, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "woong", + "n": 7, + "actsLeft": 577, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "xai", + "n": 7, + "actsLeft": 577, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + } + ] + }, + { + "width": 1440, + "inner": 1440, + "scrollW": 1440, + "clientW": 1440, + "hOverflow": false, + "labeledSwitches": 20, + "visibleLabelText": [ + "Use default aliases", + "Default window / cap", + "Use default aliases" + ], + "cards": [ + { + "name": "openai", + "n": 8, + "actsLeft": 577, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": true, + "labeled": 2 + }, + { + "name": "anthropic", + "n": 8, + "actsLeft": 577, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "cursor", + "n": 7, + "actsLeft": 640.3, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "google-antigravity", + "n": 7, + "actsLeft": 640.3, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "kiro", + "n": 8, + "actsLeft": 577, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": true, + "labeled": 2 + }, + { + "name": "opencode-free", + "n": 7, + "actsLeft": 640.3, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "opencode-go", + "n": 7, + "actsLeft": 640.3, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "openrouter", + "n": 8, + "actsLeft": 577, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "woong", + "n": 7, + "actsLeft": 640.3, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + }, + { + "name": "xai", + "n": 7, + "actsLeft": 640.3, + "overflowRight": -13, + "zeroWidth": 0, + "capSlot": false, + "labeled": 2 + } + ] + } +] \ No newline at end of file diff --git a/devlog/_plan/260830_models_provider_header/evidence/030-baseline.json b/devlog/_plan/260830_models_provider_header/evidence/030-baseline.json new file mode 100644 index 0000000000..6972c75acb --- /dev/null +++ b/devlog/_plan/260830_models_provider_header/evidence/030-baseline.json @@ -0,0 +1,521 @@ +[ + { + "width": 780, + "inner": 780, + "scrollW": 780, + "clientW": 780, + "hOverflow": false, + "cards": [ + { + "name": "openai", + "n": 8, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch on", + "custom-select" + ], + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "anthropic", + "n": 8, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "segmented models-segme", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "cursor", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "google-antigravity", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "kiro", + "n": 8, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch on", + "custom-select" + ], + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "opencode-free", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "opencode-go", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "openrouter", + "n": 8, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "segmented models-segme", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "woong", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "xai", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 281, + "overflowRight": -13, + "zeroWidth": 0 + } + ] + }, + { + "width": 1280, + "inner": 1280, + "scrollW": 1280, + "clientW": 1280, + "hOverflow": false, + "cards": [ + { + "name": "openai", + "n": 8, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch on", + "custom-select" + ], + "actsLeft": 710.2, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "anthropic", + "n": 8, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "segmented models-segme", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 684.1, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "cursor", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 798.9, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "google-antigravity", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 798.9, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "kiro", + "n": 8, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch on", + "custom-select" + ], + "actsLeft": 714.6, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "opencode-free", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 798.9, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "opencode-go", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 798.9, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "openrouter", + "n": 8, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "segmented models-segme", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 684.1, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "woong", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 798.9, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "xai", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 798.9, + "overflowRight": -13, + "zeroWidth": 0 + } + ] + }, + { + "width": 1440, + "inner": 1440, + "scrollW": 1440, + "clientW": 1440, + "hOverflow": false, + "cards": [ + { + "name": "openai", + "n": 8, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch on", + "custom-select" + ], + "actsLeft": 870.2, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "anthropic", + "n": 8, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "segmented models-segme", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 844.1, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "cursor", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 958.9, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "google-antigravity", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 958.9, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "kiro", + "n": 8, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch on", + "custom-select" + ], + "actsLeft": 874.6, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "opencode-free", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 958.9, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "opencode-go", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 958.9, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "openrouter", + "n": 8, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "segmented models-segme", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 844.1, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "woong", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 958.9, + "overflowRight": -13, + "zeroWidth": 0 + }, + { + "name": "xai", + "n": 7, + "kinds": [ + "btn", + "switch", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "btn text-caption", + "switch" + ], + "actsLeft": 958.9, + "overflowRight": -13, + "zeroWidth": 0 + } + ] + } +] \ No newline at end of file diff --git a/devlog/_plan/260830_models_provider_header/evidence/030-geometry-harness.ts b/devlog/_plan/260830_models_provider_header/evidence/030-geometry-harness.ts new file mode 100644 index 0000000000..20401a463e --- /dev/null +++ b/devlog/_plan/260830_models_provider_header/evidence/030-geometry-harness.ts @@ -0,0 +1,80 @@ +// Baseline geometry harness for the Models provider group-head. +// Full CDP (Emulation domain) via agbrowse's Chrome on 127.0.0.1:9222. +const WIDTHS = [780, 1280, 1440]; +const URL_ = "http://localhost:10100/#models"; + +const list = await (await fetch("http://127.0.0.1:9222/json/list")).json(); +let target = list.find((t: any) => t.type === "page"); +if (!target) { + // PUT is required by newer Chrome for /json/new. + const res = await fetch("http://127.0.0.1:9222/json/new?" + encodeURIComponent(URL_), { method: "PUT" }); + target = await res.json(); +} +const ws = new WebSocket(target.webSocketDebuggerUrl); +await new Promise((res, rej) => { ws.onopen = () => res(null); ws.onerror = rej; }); + +let id = 0; +const pending = new Map<number, (v: any) => void>(); +ws.onmessage = (ev: any) => { + const msg = JSON.parse(String(ev.data)); + if (msg.id && pending.has(msg.id)) { pending.get(msg.id)!(msg); pending.delete(msg.id); } +}; +function send(method: string, params: any = {}): Promise<any> { + const myId = ++id; + return new Promise((res) => { pending.set(myId, res); ws.send(JSON.stringify({ id: myId, method, params })); }); +} +async function evaluate(expr: string) { + const r = await send("Runtime.evaluate", { expression: expr, returnByValue: true, awaitPromise: true }); + if (r.error) throw new Error(JSON.stringify(r.error)); + if (r.result?.exceptionDetails) throw new Error(JSON.stringify(r.result.exceptionDetails)); + return r.result?.result?.value; +} + +await send("Page.enable"); +await send("Runtime.enable"); +await send("Page.navigate", { url: URL_ }); +await new Promise((r) => setTimeout(r, 4500)); + +const PROBE = `(() => { + const de = document.documentElement; + const cards = [...document.querySelectorAll('.models-provider-card')]; + return { + inner: innerWidth, + scrollW: de.scrollWidth, + clientW: de.clientWidth, + hOverflow: de.scrollWidth > de.clientWidth, + cards: cards.map(card => { + const acts = card.querySelector('.models-provider-actions'); + const nm = card.querySelector('.models-provider-toggle span'); + const kids = acts ? [...acts.children] : []; + const ar = acts ? acts.getBoundingClientRect() : null; + const cr = card.getBoundingClientRect(); + return { + name: nm ? nm.textContent.trim() : '?', + n: kids.length, + kinds: kids.map(c => String(c.className).replace('btn btn-ghost btn-sm', 'btn').trim().slice(0, 22)), + actsLeft: ar ? +ar.left.toFixed(1) : -1, + overflowRight: ar ? +(ar.right - cr.right).toFixed(1) : -1, + zeroWidth: kids.filter(c => c.getBoundingClientRect().width < 6).length, + }; + }), + }; +})()`; + +const results: any[] = []; +for (const width of WIDTHS) { + await send("Emulation.setDeviceMetricsOverride", { width, height: 968, deviceScaleFactor: 2, mobile: false }); + await new Promise((r) => setTimeout(r, 1500)); + results.push({ width, ...(await evaluate(PROBE)) }); +} +await send("Emulation.clearDeviceMetricsOverride"); +ws.close(); + +for (const r of results) { + console.log("=== width " + r.width + " inner=" + r.inner + " scrollW=" + r.scrollW + " clientW=" + r.clientW + " hOverflow=" + r.hOverflow); + for (const c of r.cards) { + console.log(" " + c.name.padEnd(20) + " n=" + c.n + " left=" + String(c.actsLeft).padStart(7) + " ovf=" + String(c.overflowRight).padStart(6) + " zero=" + c.zeroWidth + " [" + c.kinds.join(" | ") + "]"); + } +} +await Bun.write("/tmp/ocx-probe/baseline.json", JSON.stringify(results, null, 2)); +console.log("\nwrote /tmp/ocx-probe/baseline.json"); diff --git a/devlog/_plan/260830_models_provider_header/evidence/030-locale-harness.ts b/devlog/_plan/260830_models_provider_header/evidence/030-locale-harness.ts new file mode 100644 index 0000000000..4f5d093ffb --- /dev/null +++ b/devlog/_plan/260830_models_provider_header/evidence/030-locale-harness.ts @@ -0,0 +1,49 @@ +const list = await (await fetch("http://127.0.0.1:9222/json/list")).json(); +const t = list.find((x: any) => x.type === "page"); +const ws = new WebSocket(t.webSocketDebuggerUrl); +await new Promise((r) => { ws.onopen = () => r(null); }); +let id = 0; const pend = new Map<number, (v: any) => void>(); +ws.onmessage = (e: any) => { const m = JSON.parse(String(e.data)); if (m.id && pend.has(m.id)) { pend.get(m.id)!(m); pend.delete(m.id); } }; +const send = (method: string, params: any = {}) => new Promise<any>((res) => { const i = ++id; pend.set(i, res); ws.send(JSON.stringify({ id: i, method, params })); }); +const ev = async (expr: string) => { + const r = await send("Runtime.evaluate", { expression: expr, returnByValue: true, awaitPromise: true }); + if (r.result?.exceptionDetails) throw new Error(JSON.stringify(r.result.exceptionDetails).slice(0, 300)); + return r.result?.result?.value; +}; +await send("Page.enable"); await send("Runtime.enable"); + +const PROBE = `(() => { + const de = document.documentElement; + const cards = [...document.querySelectorAll('.models-provider-card')]; + const lefts = cards.map(c => { const a = c.querySelector('.models-provider-actions'); return a ? a.getBoundingClientRect().left : 0; }); + return { + scrollW: de.scrollWidth, clientW: de.clientWidth, hOverflow: de.scrollWidth > de.clientWidth, + spread: +(Math.max(...lefts) - Math.min(...lefts)).toFixed(1), + worstOvf: +Math.max(...cards.map(c => { const a = c.querySelector('.models-provider-actions'); return a ? a.getBoundingClientRect().right - c.getBoundingClientRect().right : -99; })).toFixed(1), + zeroW: cards.reduce((n, c) => { const a = c.querySelector('.models-provider-actions'); return n + (a ? [...a.children].filter(k => k.getBoundingClientRect().width < 6).length : 0); }, 0), + maxHeadH: +Math.max(...cards.map(c => c.querySelector('.models-provider-head').getBoundingClientRect().height)).toFixed(1), + labelText: [...document.querySelectorAll('.switch-labeled-text')].slice(0,2).map(e => e.textContent.trim()), + }; +})()`; + +// Korean and German are the long-label cases the reviewer will ask about. +const rows: any[] = []; +for (const loc of ["ko", "de", "en"]) { + await send("Page.navigate", { url: "http://127.0.0.1:10787/#models" }); + await new Promise((r) => setTimeout(r, 2500)); + await ev("localStorage.setItem('ocx-lang', " + JSON.stringify(loc) + ")"); + await send("Page.reload", { ignoreCache: false }); + await new Promise((r) => setTimeout(r, 5000)); + for (const width of [780, 1024, 1280, 1440]) { + await send("Emulation.setDeviceMetricsOverride", { width, height: 968, deviceScaleFactor: 2, mobile: false }); + await new Promise((r) => setTimeout(r, 1200)); + rows.push({ loc, width, ...(await ev(PROBE)) }); + } + await send("Emulation.clearDeviceMetricsOverride"); +} +ws.close(); +console.log("loc w scrollW clientW ovf? worstOvf spread zeroW headH label"); +for (const r of rows) { + console.log(r.loc.padEnd(4) + String(r.width).padEnd(6) + String(r.scrollW).padEnd(8) + String(r.clientW).padEnd(8) + String(r.hOverflow).padEnd(6) + String(r.worstOvf).padEnd(9) + String(r.spread).padEnd(7) + String(r.zeroW).padEnd(6) + String(r.maxHeadH).padEnd(7) + JSON.stringify(r.labelText)); +} +await Bun.write("/tmp/ocx-probe/locale-sweep.json", JSON.stringify(rows, null, 2)); diff --git a/devlog/_plan/260830_models_provider_header/evidence/030-locale-sweep.json b/devlog/_plan/260830_models_provider_header/evidence/030-locale-sweep.json new file mode 100644 index 0000000000..2af073d47e --- /dev/null +++ b/devlog/_plan/260830_models_provider_header/evidence/030-locale-sweep.json @@ -0,0 +1,182 @@ +[ + { + "loc": "ko", + "width": 780, + "scrollW": 780, + "clientW": 780, + "hOverflow": false, + "spread": 0, + "worstOvf": -13, + "zeroW": 0, + "maxHeadH": 117.8, + "labelText": [ + "기본 별칭 사용", + "기본 창 / 상한" + ] + }, + { + "loc": "ko", + "width": 1024, + "scrollW": 1024, + "clientW": 1024, + "hOverflow": false, + "spread": 0, + "worstOvf": -13, + "zeroW": 0, + "maxHeadH": 117.4, + "labelText": [ + "기본 별칭 사용", + "기본 창 / 상한" + ] + }, + { + "loc": "ko", + "width": 1280, + "scrollW": 1280, + "clientW": 1280, + "hOverflow": false, + "spread": 0, + "worstOvf": -13, + "zeroW": 0, + "maxHeadH": 117.8, + "labelText": [ + "기본 별칭 사용", + "기본 창 / 상한" + ] + }, + { + "loc": "ko", + "width": 1440, + "scrollW": 1440, + "clientW": 1440, + "hOverflow": false, + "spread": 117.9, + "worstOvf": -13, + "zeroW": 0, + "maxHeadH": 83, + "labelText": [ + "기본 별칭 사용", + "기본 창 / 상한" + ] + }, + { + "loc": "de", + "width": 780, + "scrollW": 780, + "clientW": 780, + "hOverflow": false, + "spread": 0, + "worstOvf": -13, + "zeroW": 0, + "maxHeadH": 152.7, + "labelText": [ + "Standard-Aliase verwenden", + "Standardfenster / Limit" + ] + }, + { + "loc": "de", + "width": 1024, + "scrollW": 1024, + "clientW": 1024, + "hOverflow": false, + "spread": 0, + "worstOvf": -13, + "zeroW": 0, + "maxHeadH": 117.8, + "labelText": [ + "Standard-Aliase verwenden", + "Standardfenster / Limit" + ] + }, + { + "loc": "de", + "width": 1280, + "scrollW": 1280, + "clientW": 1280, + "hOverflow": false, + "spread": 0, + "worstOvf": -13, + "zeroW": 0, + "maxHeadH": 117.8, + "labelText": [ + "Standard-Aliase verwenden", + "Standardfenster / Limit" + ] + }, + { + "loc": "de", + "width": 1440, + "scrollW": 1440, + "clientW": 1440, + "hOverflow": false, + "spread": 0, + "worstOvf": -13, + "zeroW": 0, + "maxHeadH": 117.8, + "labelText": [ + "Standard-Aliase verwenden", + "Standardfenster / Limit" + ] + }, + { + "loc": "en", + "width": 780, + "scrollW": 780, + "clientW": 780, + "hOverflow": false, + "spread": 0, + "worstOvf": -13, + "zeroW": 0, + "maxHeadH": 152.2, + "labelText": [ + "Use default aliases", + "Default window / cap" + ] + }, + { + "loc": "en", + "width": 1024, + "scrollW": 1024, + "clientW": 1024, + "hOverflow": false, + "spread": 0, + "worstOvf": -13, + "zeroW": 0, + "maxHeadH": 117.8, + "labelText": [ + "Use default aliases", + "Default window / cap" + ] + }, + { + "loc": "en", + "width": 1280, + "scrollW": 1280, + "clientW": 1280, + "hOverflow": false, + "spread": 0, + "worstOvf": -13, + "zeroW": 0, + "maxHeadH": 117.8, + "labelText": [ + "Use default aliases", + "Default window / cap" + ] + }, + { + "loc": "en", + "width": 1440, + "scrollW": 1440, + "clientW": 1440, + "hOverflow": false, + "spread": 60.8, + "worstOvf": -13, + "zeroW": 0, + "maxHeadH": 117.4, + "labelText": [ + "Use default aliases", + "Default window / cap" + ] + } +] \ No newline at end of file diff --git a/devlog/_plan/260830_models_provider_header/evidence/040-after-cap-slot-ko-1280.png b/devlog/_plan/260830_models_provider_header/evidence/040-after-cap-slot-ko-1280.png new file mode 100644 index 0000000000000000000000000000000000000000..fb5f2a04878990348d9f690c54519e8c99465107 GIT binary patch literal 447003 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%0D#f* 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≷dxN()NLe;=ib0yzPpFQa<G1 z8N0YO|MZW!pu)d+4BIF5zaxFQNd53IG>}3d=E`fd-UQ!{95Mb9pOmaOAC@)V{S5vY z@#lKar2m&|5PE(8U#vkW)Ibi(>fewV&5LnJwk+V^s`|-)m6oWT$*6P_vf_L8KS|qz z`hP8Y;f(j>-lyJtsOqgj0(oZLjs4wMWZ7P&6mpFool6%WD_n$r1s^o@U+qCC+yj81 z25lAW*#Q3U(q0kE=q@!RKS)>H#3Y;59A$K$qUx2ZkgVH72Mk+&i@=3f@$t+jPUK%W zK*EGnFEx+fJKgZ*y4`eq3z%O1W##C8Xk{$vlm7k2Z2y9d4s;d^z<>n38rOc=7nd=5 zX(lE-kx<x~RlLo!?R>t!JP0Bdus-7wAB)lmN@cU}rzYul(Z~!IOoz#pt&$f8j2av7 z<Ng!L%gs8_d^}k!&MX5<6kVN`+ZQR$MviAjM>|clXjXGa&^c#QQ$JWd_eS6xz$PS_ zhaC-q!*`uJITQz=_6ty41LO(cFIzX^%s|&w2)FG|J`@H6=th4hcWqOMFkp8aqA%{i zqx9kysL~8>dwErzs5?qxXh@rfteaE64Q+R0X;P|KOmF>T6T;fUzivXH`@>;;0q$!5 zgV^O{?jD;P|LG=#nwWoVLO9y;FPjkh?*Rq*b}?VeUJYTZyLSTz^T0_I_-`m?|9E@x z35j&I-$Is%D{Mj_a?p+3K$G9)O{*LDpYL0a$?pT^Fp!V$96?w4AtE2grkI;3{8jG+ z#t_!l)=&f)0Fm76cJ?LU_?Ud1MIQdpW%S38WL}L7(n}jn7#TL`1JZA-yj;6@0Z^3A z^?atnHu3H`RP=|vAsqV8O$ZMMs5s`JCI?Pgetv$of^*8zVPjy48^-FMsduuu{5O4D zo{59&l@(Y~gVg=cMMy{9PbMZ26)+hB{~ve<Ie5}6qcfdyESW(ZG-k=?-YZ)a#^yO5 z_TIHEe@3*nS3dl>^dDG&y*+o}Q;riCg@mY7=<UMbyTZZ(TS<WGZ@&94*CI%_qu;K3 zre&Dj0x0KW<?39gk3nAr&P}=#X4iLvpt>h$N*?OG;tPD*M+QAhmIf4cYW#oy!#ea2 zZ$}mcg9I0ehb~@huyg#N%jk3yK*Q3Ta0TEf*#qh9Pb&X%5PdLymA{p?NxF1@{vK2* zXaA?tmH$ugJOCH`KY!H4CnmZ*&ZYeQfB#i0^M9vi<NuyT6xN_hDx@p>m(vgaV%H5D zpa!=_+BxXthE&zmIMLB~$bp)~Lf29OFjiJDG)`8x;t3oF?XB(Bzp2Lxcb}cBU<;~@ z94jOD|D|6RRSZD|#NQyugaGqaW%r!HUfm5MZi~{rdHT1{wp7KOo#%pZRN5@5(t>NK z07a(Xp_Efe4<~Z2m?`)){)kRFL)=Fb?zOzb;Y0I=RPHH5N{U~1<eBRABTB|m#XIxE z#T9eJ_8?|T=hlvD&(^kt$@A{c9kJ)nTS{ikUY=N$Or#`{ewUq<W!;tqD@+-Zva_>K zW?-?{^T-c#H<<Cfz5@T-q6W-=DCuNJ>vDSldkm+c@fHn@c4$**G1XB88(5zeU+6Tv z<Vl5u?v3l`iGtfdpib-qr~mwrnB5<uu<tvSJEhJkWKXeREu}nHIk`+n2|X#QX~)4& zc2%@XR>=5Qt`|9|Vc=1bsqGduJkPKAYHAo8Ct&yHPVjFU)`vNTV>JpE_&3Ej&2EvB z6h6QYY7ojdd?_2EVPQis)N`<t{&XaN%|q88^)7&P0@WWr{fCMcuXoa(9bbN$?tUx= zs;8vk+&;6fh3yQsA2MOchZl6xm!z)Jd&O^j0F8yX&)X2JPtbl6xsx^L;5huX-9kGe zu;hFHBgy7#I#_lZE?DTd5Ra2zW;vg0SfT2=OJmh$grCYja(wpJn`u>!#W<has>=7r zHzXKIxJ-bNP6(9M1;WjfAy6nBj0-CORScxA=BPvU(WSBHE#;WX%gQ*=VUTW6=Q^7V zZ)B+&s~St#D|gcLWat9zC7;N?4<saKgW!1|KYyowWcDNISPqmgSI=lwb;NH7z~8^J z>)v=|088%yM6>GOeTJDN6l7;qLD^Ef$E@U(_(ErYOn5|GcKmdZOwK8@TOevIRoJdC zD7Z;-Qyn(c!S+Wk{kQht-uso($mOteG78rOr5%EgKiL*bb9fMj2fOE0E8&W-|L6XG z2LFwu2M@qL8E`*S(~*dG7+%wnqlvtgXMH?^Ls&6~rcax>{a%^sl7zSV`|y5}VlaO# zR&WC|aaeTuVoy+`Y|WXQ6K%0@l%_tSul4o3@kR72Y9lh#Z+eX{#KDY{bwgcE6<Tl2 zJs!?@%N?ti@8f6R6cxg{DFf?o?_KG-*+^=CrK=?l4-Lf|gSw~2V|DsT5zR=u381#{ zdo1F_YASX0^a@O|lDh{WUEnF{SY}a?(9*)Ar?4RuHaAVp&kHo&A`><>Ro&*1up20a z4GZuNR+4|mmzN70A0m-{dN)?GdTLIG^lo1+m2pUTZ;oD5MGw5XbR`gkjqwhM9p0@^ zw4$&0E_TD)0L!StLUGF&YTs>krk*U{;efvL2R%3akM$Scx_Y6ZG=BDKmpy!OXkQPK zsE4LgW>V|Fb4nBY=vd~n^BZ)zqRtbkN@n!7ZTxEJkvz=0o?P3Xlb`=u=(d~;Az{HC zZY*Z>_q*bUsr^Ux4;u&)iNkzM?#sj$%^IU5NYwB%%Gd5cH-l@SqS8K4c9+~vfaE5{ zT_9S!J!UoCubXdl+?0X3@rI@4<kEO6ddH>~%SvYQvReRToc{A3v=YvypZfJ0mRZU~ zN^ies!B!s$s%D>ESQvRLL|~dbyU#(g1Tz*Vs98R?yOn`<9|_vuhMR`!pye(&I7582 z98>xjK*y_nP&vkG+UnP=USB^;k=8Dlb+bi->ul8Y^favB)X~xk7ib=N2F9eug%b`+ z`x*s?F(o&X;#<j5{8N&PrIh9W*2zedfnGWRo`YL6tgt2b@i#NNhHR6g>s2)>ck5xT z5YwGT<?Sb8&J%5=C4Hwa4b|2zx?DOU-ck#^?aHPTl?%{J4Z`FR78rx7aAsyEeCV(V zybM?vjv9Qh+<XVH>=NuHJh8Fz;@!aG>`NRL5FmkSUkztxM@WqE{41B$>E5oegCu9u z4(C#Tj;>7w##>|Jc}vyzr`=R!fbt1{)aPj_f?jx~Cgf4W!JnF*=F!h1wx9ErdF|6C z)Ks;cAkX6T>EvedLtnGap?F|tpU0l|soR~4)@(+B8&>koaW6Hp-f?}tmrSe`$;l_@ zuCt94=IG`zirm{va@GQ%lsgWFrm(@Z)<)@!@&%vSM;k|?5?>h8h$C6~pEDnj{qzw1 znK5fvqB^xQQ-FP~tbtaZLJSlegy(&aawv)AplwmZF*LKnH?aDRn1pu)u-X;8#kYW~ zv~5EG9T{b$^;c4b{Ie-Z5_$zDVE?P7v0a&Xr#rd2`WtpDjjd)x_|m0}61X8<O}iYW zpd8ruoYn)wUd3~-hne?F9#_L1xa_m0lAf{=1o39d7TBz3a`xfL7m2G_!q<0`ggMEr z+4I_hq}Kv6ngYE5kj%@8TLT{R>gsCs&Y}~`vn05huxnAuFnp}Snk2>pAE#Jun7(o1 zV&Ph-LRW8ZZ-0M`4=W_1p7y^#11xjR=$YxuQ)&!PCKuMbtttk&%<wE;n%HQRCfz91 z#Js$P#dN?y2p3ufv%6V|1yQ6_>O3O%lIdAe_WQr?lBFDH7rpLz5(G0pYCC&sMf@=y z>%iJ^S$j%v!mywy!EhD0h2I{L9qPux11t`bivVm^_07-I_Xj<$H2ZxE^|5&IjaK~b z&hORT^QlgtrLc|K_;6v+;tB*%nJ|e$#Ze{G-(wAd^qgbtKFwf!i!CM+wsydvZ)PFN zeyHE{bs1?_%%Z1@N8qgst}qULC_s}G4>HKMc+vAmX8qD&G#O^JKfD6sx9{GFv$xE? zGQ0KPnzA*rqoq)nJI1H~QsUu=``8O7Zdy1O?QZt0dRJHuL`I;?h^L56e31Z8uNN#d zcaN=wYI5xz1&nYzQ;ndnT{&(7=2@-ibRM@vyS71E07D;)ez`5qfj-hMPx^wjq;h-U z{`+rkSs0XBb%_H3%lrrOtO5QWPwZvL^UD+Mfjt<o8V<&i(Z9%r@Wi1M1v`fM<tFy? z$d@a!;U{6?;+e?rNso_+A#<dj^$`qa8_}e~!)V3R;MUFk;C8z)KD(>=j8PuDTw8U; zw<~Z&8Ln>+4zj<wgWJf)8jtTcx52p~XQEr%b0&rs<em%0ih^M6Ld3%QX`XLMFC2nN z<0GQc28hyx+oxWABiVP`A|t^qU_~qe-8_I8!osxm^rHEXbDKbmd@c(Okmq68(vW*Y zOREGaC*XvDThk{Pfk7sIO`>e=XxC|y2ExQJikE^^epnpG;(F~~$|^wj5d|}Kr9M9v z!l;j!+YK<(X!g0a`<Mf?6V4itt=GZ}%_s?NcJT6ytd-j7ky6!<ls2g{uClIjU8>CE zY4vG;bE`yR=uTWhd+ELJAFrN2Rj3eS3-1ebCnJ`U%dsqO{U`UtbQjazT<r9&?`+n- z|5=6{85A|^9;99VoEm5xzLO&UzFPwQ+N4x2T81>g9LNRTxJM8eYrN}yojZ310TDC* z*bvuf*tGxjV>|uh&B6{BDh>9u7{~k1d@aDhxG9R?P}r(-4`ePBDZocGjzn2wk<z1F z+}Cxck3-A2hW6%$Yxm*-MHM&lhTv8o$ouf8B{Z2_?hWMB(GzchK;TkNoV&Fhjopr> z8H$$Ht#Y-krLXz*d@8ITX!uONY_BdcJ{Tyq-o#mLLuq%8-{Yq85R784$tkAwgsuW` zUS!@JhFHw7m>P-;T%eqt-Q35ghtL~=#A!~Cm;;FUUwr!&iSlt!OHa9M^Gc03(J_`P z!W$8|K5W8#PhIuk5$U7iTw4}=ZX>@}XLh^5BwKD;h8G)lrSE@NBnZhG8K3<r9oAfc zT}hJ~20mW60v*%QxJ95V6kroZHYWB*0g*J0>I#&~I`)-s7H`vQUm<V!YaHfz1~py} zG>@3H-zYY3J{x%$5(i=i!W@4K{F#?w$g3LN`Rd|b?9N&x-1MxupG^P20%#s`a~g7; zwHo2kYOdw+g^aa8Nf@PE>Oqh&T*7eUcl!>Jg$>zj{vl9mJ%gvqKA0d44gFf*+UyDh zwG#J5eCodXJN1pJCFAabKDN#mT*dC*GiqsgEZVSwIZD2XEHJ?p)|?(`nGV)QXqlQO z_8G0MudB^n@%^5&ubX3P1-NA~5vjKKMT)k}ICt-zG)|N2tl46Ym79Lw$N7Eda&`y{ ziQy4J{V}r(@5cM>Fy&dd={Fv{%HlNTeQ8|WE<Bi8;XtZ<9L4*UFY~7|V|aW?(}qHo z-7GEcpF(Ft-3>CN{+eFbt@kt{z?GQjNQgr}uRqnrjlzXT-9}37>({SpwP$)^vlL{Q z`?t=m{>7|$3rb4`Yl!rLb;UNy@)w%1cglJ0<|@Yt+f`Mb_CJ3?*hHvDdNb;H$hTMY z<hQDP?B@C@b7(!!ABoHMXDR0#w#SdSd^A^k37vy^08g>vD6CuXzZmZH^4pF4UEEl` zuaVHSlEv>+Ajm~~J)@*~>zJYKBBTgDZlNLtWT*&b%~DCx2a9M)*xTS0lNij>)z>Hd zj*c?)`W%qa;ofh>i#jiLX@j3ARA6vpnqkwzg^@uo+extAOO)g=8`a6tJ-qq|?y)JO zW1cCN04JwKt%-LQ{^LW(_&$-xFJ^(m8VaTEZ{Yn);vxW??eFkyJ!EqO|E=VkFs)zb zDyU4)1eNrYIo_-&FHcue{1w$Ma)e&M=xyB*aa-V5S<er`wA8Q$a(*PQo+Nk{Po<^1 zqof7AdRjs8+l@ghvVQyh>AmLPP2zts>0i@G?f)bJsTWuzfq~8h#%@@Y_Iw5OD50;j z<E&wZj_k&OzsKMZI$!J_ZS_(_0k%s_S+qvkEnUO=d(M^Vi7~1C{(O}(jm4W%FgO+- z7e~U2ir`6A4;6igAEjQiJ$><zohNTEKvxU{skbfFV541RiL#WL3ID76&}~i40s%RZ z_|TConAhBE+kMB2MW>1Y%2GzXPga|=*e}-tSg7RPjoio7gT6vfr9k<~eO$x|zr<8| zQP{_Y|1yTKjjE6m$D{WBg(sz0=4i+@z2Zeu@ZMu%D@{cMedW+Fh~%Z@HQq~Xv<$ir z<?8jo3O`Ik*y1JV1WHLx1^2FusR>=vJy>sp$YmdF6tV&m=qmXzxR3;fN~aiy-O68@ zTZ`-LIh<te80YFn)`f(2f*S@1Dx4hL!n44gwf7Mdemgs_6|_I(j@m*-$TuPXsgGa- zid?}no*sHYb>^D6Ydw`bGmQ2ivlg%u{PmF)rn25fyMxbp<>Xood6r1IASzzO`ViXk zrSxLM$IseqR^C}}@lTzGT}l3baUVCx-F3CaMfA_!I?>Kqc5sZ(%^o@cx8|7o{@ZC; zSyL~s9`S#S8CCi2Xx7o@uPx$KkfszZ3H%EPPRc@}>$-+^u27iTmb44+Wew=@nu!;Y zWCxPiL>$U(p?-NNsoXgG=&CJ7Hg)mUa~6-EtCxn3#(JDH2<2#+S%8!P|K0uA@Di)8 z6p_dFutnm#TJM463P|;~dD5~CWN$S7;$f7^Ki{P|73N;hoJZ}a5<p3<_t7fRH$?b{ zY^y8-X5;(wCYck@TMl0uI=g*$9%3fW%ul2?Y{+7n1@xu0Url42LF!-vrz~`a^O_YX z$#&kth`k|MM7xQ9lRqRzO7+QjYqY#LV*528Xvp|9ELj6NV;gG$e7)O7IVBB8li#P` z(N(?7Nc58BrQ39ThO}4JlM)MX0uHqc3Szy?viF5mVB!MmBx3^t3aGjC=OA-%jbHIS zUD1m7yyjz&jKUX5gdj^yh;n&&@9e@9nROdf&({?-KRk;Z`L)-<VNGFndF=@ZkwJ(~ zk-e|@6dTf5vnwbHUwu-Hbji?H`NB0^LxR6;l^E(%V(lmN9BZRhlco1%f-9!wsXm_c znT^fmNqHCV+CAj9PVEQyP$H4Z*+(%8ksG`2iKQ=yr_IRDwpqkCIA$NyK4^Z8g2{2J zOP)0$NKy7{BJ6wEpGVDglzRb@<7|ni9>0gFesP&BmG44f-I3;WS?1Sy)b49IOU_GH zK@mGJ{__Uv|0S!)T4_LvyTafJ63~#69EypmB@j^;!^=%Q;P|#N^eDN0XkBGng7_uL ziK3<xaY0Xk%P@15^|w0uX1Ef|ChHj6a*@Ci9a6`HcPo=piJvqJXSqc=Drp#8Bc*fQ zVDkxQ8h>T$vv+^|ML3}H{A7-yY<J^)`C=$UbRx#K;yVY2FrV<k{bdV%?w59J&A%Ur z4cz<udK+fm6!SrR*B9ib5z$Mg4WyxxzX-F?fFrgT+1LT?;6;WIFX}m96ytE90Kdq6 zn9AxJm-tVG3dgZVC|O?26ILO_NHOW(F?ELed~bBqe%blTn;#yf9zcXR$i0&H9>iym z-B)Gx`t^b82PeDB&*bNF$4n%WY)_4_?N#fAmPAD7c>$eY>nB7ynw>!6sj2qL#{u+h z&&Hvtgx~9WE^Eor^TS80sJD;!O>4YzZA|f6^r;g?mgFo}qs9v!Uz6zP@ZeBfzen_^ zj1PLzK6$mkQK6#W_Me)VglpTpCPc%(1_V4?We;vHlX7O8X*vp9)yUe~UPX1c=)+n< zyS+t($W_W9X$Y`uk^>d1TrUq&28&H+raozyNc$!pJ@#`i?oP<J#fgpfhQp&0x~Ddz z;O>_wo%;PA6l2l+hHIdKl?}ItRxtGCwMQ|mFScN~Z+VVqQEqDZ0U`kc#aPee0S9UM zhK5fZbOyJcvz!`G*2^y_keZ0O?XwM58^1N7348}O&ie&I-g0?zt^mmfY;|J)N=_|~ zPk_CHT`AX)M9dK|gQh6?u_{<{Bsih`OG8N4>^|+#Z!g%gahl^M2uN5&JQ@(=7_>1> zx3Es*Eh0k8-)5T@vExyOXouAl8)IDz%!>vUqn&mVM;?4NR*DwzhP7MlSTGF_pgH+* z6iIfBGhmhXUQn+fzU%j`%HSSzD7#t(Zj{;d!cSomJVE0)6?4hKg!#&I7B~Hx-zPh8 z=BM%J5uX~zQ6vGvEBPt{WP7u*G5!>p@&TKix6_T}%>^`hEASHqg?(ZqhsUPAnqs_j zq4l184F|x{<H&m_LYIqh@HVg(y_`DxL?lXfrkkcVA*^5C{#EBlrbNsON{C_2Jf#~P zR23u1vtJDUdYQ)2zY=zS;8)*FrQpU6w3$9BA-`q5iEPOiy;GZ%kk-X9vp%rEq*{zv z@4g6rq7XoYkHpR95y_eFNgKJzdCU7S`{I^)hZhm$3hGeXt5ptF8qC)n-v%pm3OO99 za73*kGmq5Hlp5z-wydO{h>A$#C@i-r$IBmz!nd2!bUcc#lJkGwg%1?tulQ1ZB9uye zSs0*t1S#sys<70s*|HgXYu@EiyOhNqZEm-geez~xZw}@fSFUkNZ7OW5MCX2b09*~U zI?pdc&Kv7#z;X0eZ={|{?3s~8XD`V^Auhrx^)6*(hmOgf-UJHvVXv$W8L93eC~aCu zPeo7v{CT}a#@rZ(vwD&q|Ec?wRmeBT$%OloU`r7$F1O`ZpS%RCIM`uvEc7ByxMGWD z^Eg0-7!4*HWZYq6ASz#QPaVx_LYFsy;{cme79E8}R~nJ5$(D6tu^4H2nFpuBMA)K# z)VIogjyo~eld-#%6A5K0m+@pDF!7O&+3E7`@#+N=viU?X@HkC9PNk!A&CT#0%_eAz zT!0k{g7P#aJb%<={Tdc%jcQFttsx^df)U=tD!MNsZ+hZJQF^gz1B-~eqpucQaD@Fx zP^_@<G$lzd2s)ZYC<X-eIRz9ql`Njj6t$zq4)R-Mkh)KFL*&^tTTf3<pIaBU?ur)4 zBWii6Zc<H+<nTDOx5o(=$EHw909No(^2Vf){C0om^Z8eB?3bNJH0UYn7hei;OfAe* z0!0N~n%OstGD|d$3{^A63y_E6FXAXlHaL+Z^zjwb=Z=v(mW}~rZISBWksvPcEkr2! z56`tF^v@|ss5TXiyEYHp&rrG39DMXqvs+-{I%(NjWW~lP#ZT*(a4z&IsUe_AXq93| zzG1~YK2T6~Z+7m*yM2g;jtJ2K?+QE^sBRd8{`c|zVX7zcs!=NU*zJCUFL|tXl~-Xk zG(Dz1o8x4UWt87T!CM-*)$dKUC}+#eR0Ti_nM8jRfkpB_;@MD@JA!vZ!w4XFF@)QE zRt>=18W0Dvk20rsZeN!u+3UQrx(a^GQYT8gInWL*D%opXuLTfI9w`r!6%A3t!J^34 z(d51;Bi`nxFL6Q)wk*kM_c(*zz(T3%HD>-@qOr%6I&YJl38*rWxS;C6@{$r>`^8RN z9uEW88Uf0ti=biIwyp0L#BMvueE*bkEwKCd<3cDp9tAznw1qu?yU3elp<<GBX(wbB zA*<u{XU5lOlb*}`7$Nf%?#A`U+?qEHnX&s(_Bi-~1{>jc0J|5&qwDKa04?D&4dEi6 zbK2Z<PPf2>MQ_WhC|TDNA~KTU+N$kmcbbL#d#`rSl<juwDk>+&TOymEw7RhgMUqam zWKmyt5*HwdwyK5X%O6E`%gt^bPFnsgrJqSGaSmQHlLTsdrW?ZRHz>~xZqRHmyL@2j zrZPEV0H@%=G^+b;Un8Mf%pjiqZd>qLHNb)hdOFGJ^b;0r-bLLQxBtt99S2b;a6%yE z?5qt6;u~!vrDuB(U;-5?_tO*aZa*$*5c&=Z62=-uwpGyDA9kjo9lC$w_?)^9=>xPD zb<G=^y64ATYeu!vvZrUqb=g0D1AU1Yx51AOjxK2>p_-=N?HB8$I6ClXLliD}{gNn` z!jF5=ralkEuE=-|1BzzJD|{-&$MQ~U9E|0V5lyTubf7T%pR!#vHVd+U`?9~JZsjqx zr*LqNi*Wva*aX=0S#WKl@0b>RVbA4>IEM}aT)ZI3q77>F&gzg(*<5?=eM3h_$FU4B zmHMtFw0bjKljCJo^{i8jkVuQtHJyW}VK>Q}dC}cO9AiX5CU-OQitmfqtXYX%j+yos zUU%QL7Ma~FrXYi<L5Zlkt=x~5I;n9)s29HK6El5F*YESs#U@%aLFg&|Xarc*EZGl> zL5>dR=I`!-ydcp1!S84OTYLZ_@P`^ot$e-;CvK@$-@7sBIA?;3=J9W{3{%ElDZt(* zejsO6R=morfs@=Ei)lMk@aCAf1*Fw>0sn-z&6a7#$v+O${cg;94^TL?NZIzq=9b}; zPP($*I2=M*VuZv~rqD(#7hyx2vk@XYXycKKFzH4qc_>sp@SHPNITnwPxo|xtoX4js zol(S?ny!(GWImEGEL;%Q`mp-IS<;g^WsG(ZTy&9I2=_kT>-nX{OR=YMd*l_9nQfMJ z3?ikM)>xvy?N-y$#~mp@ANULRMtb7e!sc>?isJB~aPePFCt`B1_#$YBrUx})G4uPD zmadjtisMg&ntg25=k#1&$sEa9;WvJN0n{dWS6y{jxWB=DFjUWlPG*qVv7U<T5+N%0 z`lArY9~*3N(E4SAvxI^kUEV_5a2h`X09?YkFx{Jj!j6h0@2m05H^GXDF_=(anXy@1 zNlC^u;6D}3Y(&@tW>D#77b@dWUO{}pbg6NMvjJtgZnHNKsURKOhOj;MfF6b(K-VbQ zS&(?5XpU&{D4<j(g|Js`c&<0;$ZRun-aKdj+PK&}Civ8nS*IMkyw;>;MZa0#fs68A zJwHf08bKkKHr8rhsmT!9k1n9ZadO8;d?r=ya4Z#~bB*3DLAGr^LtJFk)5=v(BF))H z{dAYUbCv@@K406V3)&99sEjx`3iyPb7;udRuWEUoKRTNENq@dq&y>a0-Bj6y@!(xq z$*JqVtWOBvIu!xvX;E&-1F(NUjrgOdNP&RD)iw?L&uVC=(B9~9)yqPI;lw>qZvfNN z_MCb=1F)H`JHI=ws3uBEh=1`8yW4p?SaiaVJ$M6R`X*$6BdcNn<CES0;gG&M^A+Uo z%VdWDv^MkX#(k0vq-=PucTZFHm0??n8K(f$EIE1QEuC^=bb2Medymn^XE(&lZ}tXo z)5eH8-|=|Q@vg3HBP{q4f9xfP;WLNGK7!mjNB{gcDzr>v?7|3a7U-sn;=6(DVeFS& z&rE2)==)?6YSv26Z=l^a@#2b*r!b+~9Y#Ifzk{|FzJiN?P-y>S>tyK<?~JO@zP`)z z3&0iH8CBrqZ~LO!Ah3mq{8RLGU?;76jVV0sTn==H3nG6Wwt9MY@qM}A=g_8Q^u#fH zRn%V_4OO-5Ngxm|InQhp^WRNm&oXqUaX*R&3CGDS#iiq(uCB3Ao>xg6VO<!TMr*@p zNHYI|xz6w5^_u9QxY>#<sIx6HTUxvIigaM`GI7SxU3-4y7b?k;T&m{izc|h-^-D<K z=3Kvee@gG*=_$FW8Np?eTJ0poB<0A3;N1a;CIwle=J6e?>2+h0hZ>o`cy=qgn>H|K zZVX_}^`MSykRv70GSt!8JnWOI$j05tvg<QVo2kNO0J90#RR<+rJbkxJr7`S+h!ekW zuQ_1G9_|#i^FYi^@mvR?6FjUo{h>(rM#H5g^!%av$NlKJKVY3f>HYKS)GTr*fPtE2 zW+e94Q$etB`;noSZ?p+5n-up)xTK!Y?xdEWI18P=VH*uH`9?ii!VWmW_kyRg@<GR! zUW<XIxmj5X(a0D9(Tg>Ym}S)GD_8<1x(TWz%Vu+t%bW@SSlwpMhsH-}U0x3k?RrC5 zSx7Btb`HD%?u#)E(irL#b{w~iFM?VjqXf#z;rFgHVJfJdft~FUE^e7xWuq=kb;8F@ zwp)>8><;DB(Zm^Wy_ub!R#en6IzPtv7Fs3*;l}jh6@AbyFsLr-YV?<2d&NC;JuL&P z%;(_tqo^##bZZV)ABfy50|p`NN}^f3WL$i!?it{LD<BdxfZ9b}kN|SPb35`<Zt~2w z3RHN8V0O_2{w34!?<^3%P-pVtqsX(d>j;Qe18KX<EttEbMi-zC%2my>vazl4vo(@w z#9+!*yKM6;>QAjcH0S@?ed7__5)?twYtf%i+NTYOv<WVxu~&pzZ0F3I?_4%kzbWMX zd&TT^<ckEk)D6yKxi{vYvfVy)Bnal*meHwn3~3AS**|q0Nh}P`d9=?1GEFlB_s_59 zU*tVu6jkd=q3WBSo^Jl5;Bv$xVeaemP0gj&J*TEmY?uIkfFpUk4+Z8&)5PsgVh|rf zoxLEl??3zvGCk_>0Vd#N*Z3j8t8bT?9ctA)`>HjHKQL(>dQs+dMK)Xz@Akg8bLE36 zi?{LM0)hd3WB0ft8zNCde7q`A1X~qhxJZF5B^2{zQeU^YKeR8xb~g-;$t2evfC@0J z$6?D_Jls&w__w0w(r|gGMgIfa^2HKyiz>R*kT<QF0rVf-LXVrv9LZK^D`4Bt`{wlR z&82Z11={-C+|u1xpq|7w%Icb2o3uBJ_VkC_p4rDro83n|6V~CrUNRI6+j$7bb_`9v zQh(&#C^=I+4TDw!YBs=M2?H%HZK*l)4_wWu01fkl*FV^RMIXysRU=!JCCtm$CZ&iE z0KY$%`}Py<yTddK!SOC@psaFTOMC{8hZPEx=F`n4FF4b`+)>V?{?s!6&(H(#d9C_W z(%95$8wJr7yVrxCDm->nS~|_?C}``IuC0Mxt57%B{F7kwoQSQHiD#)s<Z50UYi?&! zz<3;Ie*nfK%^SQ2jPVZ>b1S0`y}!y@cf}X**jd{}*22*$UiXA5$Gj<StMG$A7j8k{ zJP0jy4-UpJiz$HuEqxTs(BeP=VW%l+Mh<P4F`80<CtA314UDR+d(!EMVz>L>)B<=O zx)VJPB?)jpm4z*U`Js@-^D`#3vT_S;{MBVcC=Gm1`E$oxiqxeerT6@u$;g!J^ImE- zzLcOaH}_oUzhO}nTE`W7f^4EaPOq&bz{pOW%GJTib`|DJwk`&3`m<S&e3`Oa3O)Uk zWxWi4IR*&Qion%mzrwVV3}x#q)cz`sI6&qpgnd3AS>vpQ7c~r%&*2NK<mTSqJ4u%o zL-3I$B@NMRv|li^RjMW*vIf8$JZL-eIUv*yGs0&`C(JL~K~3Mp?5r%t^n6iA&zSDG z+mjloQ#}YG{z`a!Lm(>5&5Gx`Lqqa+DgA<Bu<OMKhG+p^Pft<CJ=%Nov!q^1LP^Or z`>Wja6n~Z4;)>A`f^JGJx(bj;|EG`Liq7Wav#IsMwpZ#`nu}t!5B?SL&2daZcb9f_ zR&|BzEGf?P%1GZE=`&rP08BNVzQ!a~^q?qo%2rga4%e9YDKX=aT=p=62t;SS>w5=n zOH|Go;0FDk7sKnZW!+*A@aiitO-$g&NtVhdC*eTc2HcR?2>*A^lXon^OykT<Hnl^7 ziZYA~kMSEs1U-H95QF@_CUlqb$_IEn$uSpNlgPbfsF^ctin*x_8?z#4PX?#a@#?0Q zP}zo<W{`HBK_PIh;~T2PNBlqX7F0PwNt6K^TB+|(Xu_Om$osX_BqEK^r8M&0>1@>T zfX+<Gax5`O=I%2J)%OsFIInR{P#bH);|aD+Q)0e}M052awqbd{cp2E!FJw_Oa8-_- zDK_67;xSx>(wj}(XfY{aTz8yMx!h41Mtv+!Pv<rJk<{WB*k+;Brq2-Xi|>aw-I;~C zZk?0`?C?_%t^>rasM$XDQ2BSL@)QGfUuGP8An!UAXa!58gFgxQ-{Jl#nH)j4LT>W; z>Bc@!K=?~asGh<7IYjl#(i7{~W(}!n=ts%gxBDZrvq-M`(}wDYWyl=VuC`^py3K{B z7WoMs6OV*(Z9QHLXw}}@>s7RQ6Pd7CBI%0lAwHwGzX0%4Xi<@IIQ}@8Lnwm3ygwyI zBCa~%i0f0npFznn`K09lsGIT7DJmN6FF<>eziV6=MKc2BT-&FnQ}h%t%@W1kfSwA+ zk|DpF0=Tr`sU%^sQl4M6g>^hih?5n?>tOaWk@{rEemT3PIA_zgq-j28G}MtjwzXp( zOC;0{?PqaZJ;QtO8GS(^WIKB76+OML$UBBMZ`6KPMb19lj8=XMt%83vOhpm)?cn(U zWp9A@#Ageyx;2M!D1|jIgNx{$^It?pk-ro^QL^nIbwPXrKNl&(N*aYC7lp}5y6C5$ zzLY3EV^m7nxZml4HVAVlhEg3H3NZ;%InfVAW-V$UJ8B5OXJC$?<o5J{H?j~And#wx z{i{E=H<!bUiy@I)0-CC1#t=7DLdHAf`gJtY3-~;p9mmd_E0?-yKEoGdAU;|AKkU6{ zJlAdfKCGdlQi`OKvPU6_M99h}w5?Diqa;OULy~M!$Q~&XMP-)xRdzy18I@5ovd4X# zr0aLT?%%`v!T-U(2iNPucYHqQ=RDu@IF5Hn5wrgJ3TB(8I_??uHq-qrog}}iJBzh? z_AcG9<o~scxUTkhG`oD7(T|X^on9@rYTr^@UeDEcD&A=x<viW=OeV95+vUp$rdj?h zW-?SWGpl%T331QrP0LE(Vn<`<ka&|MiHX23+y6tZh5>_wWit=3<!?C@#;_%c{+6Y! z(9PQowY7tfG@bd{hm13KrgvV>?bWBQF_L#f<<m}>nW!r4m1X-ZFlhaUKpP@#^0Tv2 zQ~TiwHMul_zPn-2#7Sf~NugK$Tgx-qzB{LWWughvTu}(=8|L0MFKqSoTW=#R?mzYO z$B!#$@_C$?McPUEDAt!<B~|#LMZW!{nozixrBvk$JJHHFjWxug@2a|c=Jrm6LpmOs zFSAhfyli%Y-S#D^XBy3Z1|%SVqUUjjU><tS6c#yC9BJP|v=W(%pX3y8I0-(kKzASF zBgK8?CFoRy?M&o3-+1cQ>E1{K3bS2B-}!Z4vCWwi%DF#{!|0K*o`HkID33De9d3^d zppmjnudxM^Q~NF5z8_I`6DX0fj+w{LrP-&G=OB(zPH5NPxAYju+)Djf>d(7(sY)uf zHe!7#6+258tw^ts;$4!(WXHF5yq#$35tPJqt7PVRm=_6@QQ61TmJOTl*Ctgz$gwW} zoVuldkW2s$(^ZI)5Gveg5a*3Z1eS>1359Jz+qjY2A}I5C`VpBR#RrU566R5i^tPP? z;{n(D20dfQ&~(!P`KXuZACL*w{+n4YzTf4~x#so2vlAkd(0csy?aEi1{8uBG)D$uN zHWppp>}>{jHxl8fJJ&BJCHCQeBFBojX-5>?frr_^nQi2_Pz%`d>=}`elTKR-65Ur! z7ya=z>wCIbt6iNCi<i9pAK*>mGHQ}xYipxeic6y}!5?t3+o8>S`qm9kJ;|p_2<ug7 zI;r%juh>4KXNsNm(s-55S$Z?$H~wU#^gh|)14j+gO_ej5O%jU+L~rL7%ja#qRZ1`> zzD}L&==xr~^vBO|cjjluUJ`mXNpYv2KBnN+;G4<!!-B29jNNy~#%rcYzm^8*-IdL} z6-)>-O-PE}&y_5f#7?4<p87r2w{+BE_YoZG*L~mbUf=!nDP?K-$LGPe(J}x6Q5Y97 zNS<&%yqMB_xv3~*K8`bqZ`^*L@@{79rg3?YFdXlgE!|D8{LOJg<Jgd@pu~{3FLyk* z=#!q5Uu()Qv|!GfWuFMIv?eMe??A1)VlVl}^BKFCS1xHHG{&WTt598^H<On~w66S} zx!4<k;%Tg2E67Msk^=vHrV)4rC{#v7>a4AkLRlUInJFt2JhuuyG?h{<x_UwP;NDyy zH7%Eo^gK?*TBJ}qZ|CNlX$0JHZ}<jwkD{Uk(x7PB-Z3}1yDC!tY&}v!+z#hD*h7&& zcyS14=D&!KcTB6yGVx_%NKo_IyNc9wP)XxI*|qkd(Tz9s_XG{|Bxg!WZ?8!&KIxSs zA?}k@7Lai{629Uy9;v?5Z#3JB&sif1flseM72{-eWYJ2&oHUBhhOJk=xO)!N%J2)Y z0Iy{LNLFpUYDY19q)*6QP5+Tb`ckKGJ>?4!+9t&#A^^QPlm*2@?8mF&ar_tCsXNlb zr|<1mxusl@(eCAM6<B)F<G8edFiOY4%F4eU&cF!_qyMkOrI|OvS){;A=L;{Ldg2A& z3>xjxoMuRXcsFuxKhcL;pNgU}u(|`<0|xZArFoTx938U$`LY0|b7$LHmI&!<-GHJI z5g^4$+dG9piYyKFQzNB557CklvCXml)Kq{9Bz=ECrW5mR*JL6Tjf8Xmey+<fCz!^~ zf*QqT;qAstYPViiN#hRtAarvv<4Kg}oI`j)i~)RhFkZ{Zlx>xdrQr0<=TDys=<xVu z%Sku*k9Y<1yNtClg`7x{vDCI~w0XQFIY9WydZqNsy(K<nBPa3XG`&osqT0GwJZ59v zd29$Y&%49?%)w;=<Uv4BZy)6kYnpSV7C+f*^c%R?nFd@Bw8{nC=``yyWuogFaavdG z@3$uI)^Tn+Ut(3`a(Ae?8H-9$em+10;kmryB_DY-4kq)ZArNl5{H<r9ZCJSJ^Y*%F ztOV)yWMo-qeOu+|d&S_n+KpjK0=Acdebed0a9fpqk_W@%PJUmdhtYP^P#d0wGD;A` z&bJ+R1oI$SJ#$|4gYOD)RzL1o#%_}XHdEB><!czepUDeg&z0Q@-9P&KA+opCbad+I z#0yh1k&iYWXW##cy~oEqpU>=Z__bqV1dj~iZl=Qg6M70Aw*D-BQa-=?EZtvb&H|)3 zL737(iP(v)u-;7?Og?~Mncvu(A27hwJs9>P_>~~r1rM;VG*y@mgaz%VB4ZyrQ)=Cj z>0dQkec+}pO;M$aVDhu|jB~tunQ`Ia0#6002#te$fw_YD`=lc#FMho_`@(5_FwV57 zq=+k5Jh(ZhM|j72&jbO$a=4UHq%b`}FT19$+fLFB(KX4Asoxz8x=~!m3N(fK(-9=? zQ&ZoiSugpyqBVH1h4H`%;hROJx0gEB+LW^Dzt0C`UP*>MRPa&fj!Zt$ZU|QHnBk?b z$8NS5yToQaTjT2z^s~hEit4=`uQ~%%y)5r5?eEZS?R*gf<?SPz6>e>zXPkw+ixcVu z0rV$=cb^}Jq-jLz>hgD^U0P~%OsPaoH8JDD3$U4`gx<tfe!m;z!aw4o_oiJ*Ns%Y! z*UR_TgSA<J=j=U`zW?W%F4Ui0fN%(4D>^(%hPuA;t=xLF3{Z`PjBx|8^y>$_ANL&( zx3AT*NUx_tMReryXKhr8U@?!Jy?2>xc`Ak4*aFYu)EIizxXU`lnmyh9aW?fdIfz+C zJyt^l$N`8*Lv<B7OEpXF!-x0%lF_zL?^fu$MnPp~aSlmDh$sa@Q&Ir5^zEYGCxyyB z{qOIk41zy&9g^HjZVeKT@ivXfzg5?yv(x1xNB2_8^jP=iGq}uKnzuxr{?+m-WkBQA z$=BRF1U8Wx3)^5-TTl)q`*)z8u_KO<6uNZr)zq>@ftU@@g-Oa;$S|uSrCmc8^$oE- z_UYiWKdAht&Ce@Pz_ynRF9%|w8GG&Axm#0Ao#<~OOr0diDq|@nj+e843};`URZ4I) zg?2NnsSZG#N}<+BQ7W21Dn5#uWT^h-)=NfJvlo6#17~{Sp2E8|pXv&@pe8cY&XiBu zz|WPp#1uYPzJlHKkiz_2W!qCm<QvBJ?Ce=Si5$fa0PS=Lsa`cUx3>1F7V{~f`Q^a* z)N7!7aDUW}V`)ZX%WgYE?YZw74zW(^v1jf3`yDj<uDSav`pp9OSE7)P0UDu4@6;3B zhnF#1(O#BY&9k=QH^da9jGjygGnSShq5MOtK`aMIK-CYS;SLea62nYa)L9bzXFB#S zT4t}c%RGef`VpEfmp&X~ov6Mgns|&`<1Rda5byq`HG6Jz&S7~AzCHdFzBS;EKg*51 zZ0@Eio~+7sA4FZue2w<18@JCwnkMq@t65Ih32wuE-EqYf)5$Ku)G_JVwtU?#4~U+C zbikJ_>)zO2mRtMN=l2zctXZGP6>f=|kq<jXpKSC0g#Ez|+!Qok`svo+a{9W`Q0aOg zEP}q%K=F9@*cbl;B~1*=4NT<Zt{yA}&Ju-#V(es%k7FwX*?b84MO34pq{Q6AW2(*E zy)v8tcdnzS%E`&Q_zTG}M~s}j##W5y)0--)<#BOywU6<Ju$%x8*GfaD5@d}_y$#=` zi@^n8&sJAFeP?H;r`-P9axue;pPnm4x2ySn87IoHCFrqi;Uf*$9GB-#aSa0&&tCXj z*nE3;rud!Wbpm+Gw+cL4*KT<0%Jg2Y4x8r`hTbfb!1irT@a|@n;mjeW<|9)I!YA12 z0;byhN3w@d=M$0;D9pz+^SIX6@=x;tK_dKLiAz?EYt^R_7?hmSF}*G0{<5=VCA^)P zy~g{eRSu9a?kEozQY3xHBPCDJ#;**C3nKX%Imo|zSDoP0I(qWzB+F%vu%D5Tes&!U zjO2*t(CD-~7PEI!CAoJ8Ip3k1(npJPg)8%wdJh?iumyNWstn~@A9T@bd?X`3u(_>I zciS2EhizvQSw=sdt1=yw9BJ&ZIYQG}vZd@Yk5)n}P2Azd1I>1Q*W!ZYTO@Szo0=X{ zu`SuE@6Dp=B*p#6lAe7(Vq#?nuMT@Y1#g7mf?XUY777}|bZGq$-(f1Lz%^~GA*->4 zL-k_7z^I~zuwr|_q@%{$E9I_X05QrxVx(Qg-WFd(&7=o3>$)MP9ZDs#pAIN>D~%ov zXm*~*!dIexr6|7w0oHgJd!U{Ni}(dcLlgB&hYj0GI9M%g)bd6y8_tfpPBx$*!4-H4 zRk>%2Y&5#~scpkTg2KQau@oDt89QyadD@$KcWpO7J@KV9RJ!Sw(K1u~ZD8VPHw~d= z74q|t(OA3N6H=Z6_!41gVPM6@(qne2LLSD;Pv?984HaxbJ_`EO+}yFe4uqvCOWgU$ z@;b48XV=|jE*W}802&z1lpD^v&VG;dV7_`a99y**i0MwnEoGNDwR|FIIv<Ed{OaxH zj}4>i*haAr+OM0sgi7mdJL@ojj&=nRh=#TK=SotTiV;3(t2o?3K;P5ojXyXN<ntCm zg(Gq)?s$%7IqklejsIhz42tT{ju@UiDFJLEq-CcpFI}o_GkmcteZ1vvzb1l($f@o~ z*XjPMh%y&yM36IGsZ*D7{@7%%yON<4)synf?VYlSrHIYGlZuBI6R3Ui<HxFItcVtu zQk8WBaj}xRJ#rEz=2$>b06K`$%&$OfM=-JXzN~Qr!|=}_vh}D=KV5aPBcP_K=@mi- zyG$Svej07lJ^X!wv8=5u@-gGsTh^HYNWVT*^Sly!CEF>upnu<bp2A!f^#?ySwr$E` zuofc_m{C;XEU62kzEwrK&h-EYdIvDH^;Fxq7yluPW#2w&5Tn$jzdn11z200wH%R>E z;;_>QC!3mvpT6V>sXUs0oL@`-&ykdZ$EgC7*Q;E<hUDi+GMf!-7*HIrALnxyd&iiZ z+}08rH)R?o!d(3&@HBc$j>H+fs&f8?W;L9ROQsA*>eHvcP^*yo`5c0QbV#do7E_s; zO3)_HvyEhL-;U7EbC#H^rO53XE;!Q-5&LuJRQuDtMDjpS$nBc0IN_cgC;do6bi~H{ z`AkLhiU8Lc>Dh^b>ua2UW#mhPhQGrwZ|Y>56m(y$%SKQ832I%*P>D+3@px&>uE)Et zbIR+4d~kb-qW0DAmMt%LWm?rI9|m~^^fTv?aPbRDIZcuBB}4@bco|MA8KSwhv<d^& z#z~5W1+(;3S9>}!G117|ZQ1vv@$`Rmlc&HrukdECt*k;pTU10OZu*Dfu`LHiOL}@{ zX4>j$<^4#FdipbW)xM$kzT76?Z%OBjEoYiJI+Y1<pl8(nhFC-hG0YiRsh7TSa6SUu zP!T~dnd1%#`qXt)Ms8&fJ6NZmUwsHmM@ls`@!RbW?rUvmNSt4@P+^>Ocl6`OE?u2N z_rs2$eVQOv*d;fyqHj`cB>y<K-($ufzdoP;Jl;0l6d&T;S;&pS!R-~yL1Le$@m$#q zEWkw?f8b_U<ld8Po3bt`HP!cwni+;;b(nB*#*P_MOe#o}fLehjmD`)5tOQ~Y-0hqe zg#vzp_U?MFlYO3jqt6=EpJmLG&{WV16aDy9Ujmu^@R^sWSfk5-dXjO)p2PLd1HC<A z^@|6>#==&zf0FK>>MtR+&-XR`l067SD(KEi=YV-agemi1jo$;%Vf&;&{!WIZr^%Fx zkh6v;&c>sJ8siYCP1L$RI}KF~eXlZ1GkSx3O#)4qYlqemX*%?r(RBO@_3Dj>*5qDL ziYs;VUb>BcX6G@0KHlmwv{^t=V3}YHpg8lmfV!Fczz_fcNou!34(+|_`#9tSdYdVJ zbKnl4VBMX1FNK&j=@Sy_bh6o-0Er}Q$hvkV&y3uz@3c!lKUv?{7%8|xX>io^v~|U& zm$T@VJuCeMxR%ND=6feA0bHyoEg5)ciTPxkZFU&Gw(AbytlB?N6u3reSSw&EllDs% z=VFS@t<6b}7jxX(G(sX)&>#rJpznhQ1{-3(mfRB)5wQdB_0lCYemSWMl-?ubn`AKp zFojd1r#gtK&BFL{T*iR<4*P?1XWKhto-wxL<UMchn~P0AP_{;!py`Br*{h6a10`)U zg57N|FPO}hSiX`O%=B{4`nb8MzjpR{mYdpb8S6CnI2<I*V`eiKG|$E31{<3L{m`Q) zPll~dECkE3wODGPr?(f=titruQ3*&-`b5e|Znk28EY;JPaI^g+Ew6qq-^Oe(q~~^N za#roM=g>Ke&p8A6$@|2P$<*7hs?5GimH3_Cw=e&y%B7q`zo^?bSx5CHFP&p59>##B zno!iIqGaV6^o1I$(P^j^Jf!Ar{`;Ek-A3N|%9T+MxQ`|6w?2RX0Q!#^6Otse(lpn{ z4BL(htW`btxo_DQE5;=@f}41D(3O2$UNmHpJjl7c>8Jqvv!6A4$LTj{6RM?4@G>{A z+WPtz;C<PN=5Y`bAGP7SjnXwAYQ8kRt@s~Y%SY-P&w+T2EFH38cc&!ukA>AmWb6_v zk&&wLrSy2tv`y{Lv@m)9C;#{gzs;A7I<{S3lXINsce9F{*vgg5K!)?yJ$zXG9q%z< zX`9e7r=ki@1mXKy4{AJ9mTFayy2Ya%__OVlWHD32wY;V9q9&K;sieAbT1GkYA6g+A z2d=XI#^@`c%<K@{!BjTgU%}><_3O*0v1~Rsr`!S?vz5Jz%FhS6cc!d!H}^_jL`_#? z4MB_~QxqJ1`<JWR)YEdj+O-1gfc5Xzmy`~7*C9H)uU}j9`RN;CoQ-G!#rI+R4gUxo zEsFj9u+NHZfps9DcXrHXtsBbnTIMq9{kCCeRl8dbT>R^B_cd1F<-(_oFIz;#*Yx^N zZ^!0C7ZZ+bGwvIkNt<I7i=vuisl)v$Z>jE_O)dD40FJUkUoLVspgzXN_XrR6$&Kga zfR~GI<dHl1<6TWL?!2*J5KFmCRrolDdHJ&DAMAzThLVC7i(%l26DMjp2dpfx$p$Jc z3O!cSm_);o8oK-IgM_`0eU4TzM}=my`&N|NjH(y4vcvll&ClcAW$z|;i+<ziO5jV} z7aJwZ#xHedYF#TKqEY4OJ1Kjn(t^yb6e1tD|MIUErF3Yuhow2hoXWVg(V->{3qN-Y zLiIpX?4`d|+TV&6AU^*3neThVl~4cZ?Y;ByM_=ELQsWg=M#l2ytr$e;Z*TRpKktPi zRAZ80OIpUH_6_JG^4ZfIxBZg?cvxpykCMn>Ze3&*cs~1sPt@iYsQd8gmWX9bsER#* zIAU|qTZ)N_Cah%d1gCfePp%>5q}gyxyTWD+EskqF9v`zBG=n*_9A(VI5V~cvZTs@3 zv1k%u%xL_~TME|(l70t!siFP~Dk^rywg>HZ<ZrYzrK%0Awx|pjANU!wpZk<~h4>QH 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%0D`zX4cS=#6W{lD;cY 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 Z&@|P;O11rzGmsaR5|<atd#LC6{{eJ3bCCc5 literal 0 HcmV?d00001 diff --git a/devlog/_plan/260830_models_provider_header/evidence/040-geometry-harness.ts b/devlog/_plan/260830_models_provider_header/evidence/040-geometry-harness.ts new file mode 100644 index 0000000000..340ac93272 --- /dev/null +++ b/devlog/_plan/260830_models_provider_header/evidence/040-geometry-harness.ts @@ -0,0 +1,50 @@ + +const list = await (await fetch("http://127.0.0.1:9222/json/list")).json(); +const t = list.find((x) => x.type === "page") ; +const ws = new WebSocket(t.webSocketDebuggerUrl); +await new Promise((r) => { ws.onopen = () => r(null); }); +let id = 0; const pend = new Map(); +ws.onmessage = (e) => { const m = JSON.parse(String(e.data)); if (m.id && pend.has(m.id)) { pend.get(m.id)(m); pend.delete(m.id); } }; +const send = (m,p={}) => new Promise((res) => { const i = ++id; pend.set(i,res); ws.send(JSON.stringify({id:i,method:m,params:p})); }); +const ev = async (e) => (await send("Runtime.evaluate", { expression: e, returnByValue: true, awaitPromise: true })).result?.result?.value; +await send("Page.enable"); await send("Runtime.enable"); + +const PROBE = `(() => { + const de = document.documentElement; + const cards = [...document.querySelectorAll('.models-provider-card')]; + const acts = c => c.querySelector('.models-provider-actions'); + const lefts = cards.map(c => acts(c) ? acts(c).getBoundingClientRect().left : 0); + const clusters = [...document.querySelectorAll('.models-cap-cluster')]; + return { + scrollW: de.scrollWidth, clientW: de.clientWidth, hOverflow: de.scrollWidth > de.clientWidth, + cards: cards.length, + cardClip: Math.max(...cards.map(c => c.scrollWidth - c.clientWidth)), + clusterClip: clusters.length ? Math.max(...clusters.map(c => c.scrollWidth - c.clientWidth)) : -1, + capSlotAll: cards.length > 0 && cards.every(c => !!c.querySelector('.models-cap-cluster .custom-select')), + clustersFound: clusters.length, + spread: +(Math.max(...lefts) - Math.min(...lefts)).toFixed(1), + zeroW: cards.reduce((n, c) => n + (acts(c) ? [...acts(c).children].filter(k => k.getBoundingClientRect().width < 6).length : 0), 0), + maxHeadH: +Math.max(...cards.map(c => c.querySelector('.models-provider-head').getBoundingClientRect().height)).toFixed(1), + }; +})()`; +const rows = []; +for (const loc of ["ko","de","en"]) { + await send("Page.navigate", { url: "http://127.0.0.1:10787/#models" }); + await new Promise(r=>setTimeout(r,3000)); + await ev("localStorage.setItem('ocx-lang'," + JSON.stringify(loc) + ")"); + await send("Page.reload", {}); + await new Promise(r=>setTimeout(r,5500)); + await ev("(()=>{const b=[...document.querySelectorAll('button')].find(x=>/모두 펼치기|Expand all|Alle aufklappen/.test(x.textContent||''));if(b)b.click();})()"); + await new Promise(r=>setTimeout(r,2000)); + for (const width of [780,1024,1280,1440]) { + await send("Emulation.setDeviceMetricsOverride", { width, height: 968, deviceScaleFactor: 2, mobile: false }); + await new Promise(r=>setTimeout(r,1300)); + rows.push({ loc, width, ...(await ev(PROBE)) }); + } + await send("Emulation.clearDeviceMetricsOverride"); +} +ws.close(); +console.log("loc w ovf? cardClip clusterClip capSlotAll clusters spread zeroW headH"); +for (const r of rows) console.log(r.loc.padEnd(4)+String(r.width).padEnd(6)+String(r.hOverflow).padEnd(6)+String(r.cardClip).padEnd(9)+String(r.clusterClip).padEnd(12)+String(r.capSlotAll).padEnd(11)+String(r.clustersFound).padEnd(9)+String(r.spread).padEnd(7)+String(r.zeroW).padEnd(6)+r.maxHeadH); +await Bun.write("/tmp/ocx-probe/wp3-geometry.json", JSON.stringify(rows,null,2)); + diff --git a/devlog/_plan/260830_models_provider_header/evidence/050-state-harness.ts b/devlog/_plan/260830_models_provider_header/evidence/050-state-harness.ts new file mode 100644 index 0000000000..26582f549a --- /dev/null +++ b/devlog/_plan/260830_models_provider_header/evidence/050-state-harness.ts @@ -0,0 +1,38 @@ + +const list = await (await fetch("http://127.0.0.1:9222/json/list")).json(); +const t = list.find((x) => x.type === "page" && String(x.url).includes("10787")) || list.find(x=>x.type==="page"); +const ws = new WebSocket(t.webSocketDebuggerUrl); +await new Promise((r) => { ws.onopen = () => r(null); }); +let id = 0; const pend = new Map(); +ws.onmessage = (e) => { const m = JSON.parse(String(e.data)); if (m.id && pend.has(m.id)) { pend.get(m.id)(m); pend.delete(m.id); } }; +const send = (m,p={}) => new Promise((res) => { const i = ++id; pend.set(i,res); ws.send(JSON.stringify({id:i,method:m,params:p})); }); +const ev = async (e) => (await send("Runtime.evaluate", { expression: e, returnByValue: true, awaitPromise: true })).result?.result?.value; +await send("Page.enable"); await send("Runtime.enable"); +await send("Page.navigate", { url: "http://127.0.0.1:10787/#models" }); +await new Promise(r=>setTimeout(r,6000)); +await ev("(()=>{const b=[...document.querySelectorAll('button')].find(x=>/모두 펼치기|Expand all/.test(x.textContent||''));if(b)b.click();})()"); +await new Promise(r=>setTimeout(r,2500)); +const OP = "getComputedStyle(document.querySelector('.models-alias-edit')).opacity"; +const park = async () => { await send("Input.dispatchMouseEvent",{type:"mouseMoved",x:2,y:2}); await ev("document.activeElement&&document.activeElement.blur()"); await new Promise(r=>setTimeout(r,700)); }; +const out = {}; +await park(); out.rest_pointerAway = await ev(OP); +// Touch: the hover/pointer media features come from DEVICE emulation, not +// setEmulatedMedia — setting them as media features leaves matchMedia false. +await send("Emulation.setTouchEmulationEnabled", { enabled: true, maxTouchPoints: 5 }); +await send("Emulation.setDeviceMetricsOverride", { width: 390, height: 844, deviceScaleFactor: 3, mobile: true }); +await new Promise(r=>setTimeout(r,900)); +out.touch_matchMedia = await ev("matchMedia('(hover: none)').matches"); +out.touch_pointerCoarse = await ev("matchMedia('(pointer: coarse)').matches"); +out.touch_opacity = await ev(OP); +await send("Emulation.clearDeviceMetricsOverride"); +await send("Emulation.setTouchEmulationEnabled", { enabled: false }); +await new Promise(r=>setTimeout(r,700)); +await park(); out.back_to_rest = await ev(OP); +// Reduced motion keeps the emphasis and drops only the animation. +await send("Emulation.setEmulatedMedia", { media: "screen", features: [{name:"prefers-reduced-motion",value:"reduce"}] }); +await new Promise(r=>setTimeout(r,700)); +out.reducedMotion = await ev("getComputedStyle(document.querySelector('.models-alias-edit')).transitionDuration + ' / opacity ' + " + OP); +await send("Emulation.setEmulatedMedia", { media: "", features: [] }); +console.log(JSON.stringify(out,null,2)); +await Bun.write("/tmp/ocx-probe/wp2-touch.json", JSON.stringify(out,null,2)); +ws.close(); diff --git a/devlog/_plan/260830_models_provider_header/evidence/wp2-states.json b/devlog/_plan/260830_models_provider_header/evidence/wp2-states.json new file mode 100644 index 0000000000..1bab14e4cd --- /dev/null +++ b/devlog/_plan/260830_models_provider_header/evidence/wp2-states.json @@ -0,0 +1,11 @@ +{ + "rest": "0.75", + "hover": "1", + "afterUnhover": "0.75", + "focusWithinSibling": "1", + "afterBlur": "0.75", + "touchNoHover": "0.75", + "reducedMotionTransition": "0s | opacity 0.75", + "stillFocusable": true, + "name": "공급자 별칭 편집" +} \ No newline at end of file diff --git a/devlog/_plan/260830_models_provider_header/evidence/wp2-touch.json b/devlog/_plan/260830_models_provider_header/evidence/wp2-touch.json new file mode 100644 index 0000000000..9982799fe5 --- /dev/null +++ b/devlog/_plan/260830_models_provider_header/evidence/wp2-touch.json @@ -0,0 +1,8 @@ +{ + "rest_pointerAway": "0.75", + "touch_matchMedia": true, + "touch_pointerCoarse": true, + "touch_opacity": "1", + "back_to_rest": "0.75", + "reducedMotion": "0s / opacity 0.75" +} \ No newline at end of file diff --git a/devlog/_plan/260830_models_provider_header/evidence/wp3-geometry.json b/devlog/_plan/260830_models_provider_header/evidence/wp3-geometry.json new file mode 100644 index 0000000000..b234735af2 --- /dev/null +++ b/devlog/_plan/260830_models_provider_header/evidence/wp3-geometry.json @@ -0,0 +1,182 @@ +[ + { + "loc": "ko", + "width": 780, + "scrollW": 780, + "clientW": 780, + "hOverflow": false, + "cards": 7, + "cardClip": 0, + "clusterClip": 0, + "capSlotAll": true, + "clustersFound": 7, + "spread": 0, + "zeroW": 0, + "maxHeadH": 125.5 + }, + { + "loc": "ko", + "width": 1024, + "scrollW": 1024, + "clientW": 1024, + "hOverflow": false, + "cards": 7, + "cardClip": 0, + "clusterClip": 0, + "capSlotAll": true, + "clustersFound": 7, + "spread": 0, + "zeroW": 0, + "maxHeadH": 125.5 + }, + { + "loc": "ko", + "width": 1280, + "scrollW": 1280, + "clientW": 1280, + "hOverflow": false, + "cards": 7, + "cardClip": 0, + "clusterClip": 0, + "capSlotAll": true, + "clustersFound": 7, + "spread": 0, + "zeroW": 0, + "maxHeadH": 125.5 + }, + { + "loc": "ko", + "width": 1440, + "scrollW": 1440, + "clientW": 1440, + "hOverflow": false, + "cards": 7, + "cardClip": 0, + "clusterClip": 0, + "capSlotAll": true, + "clustersFound": 7, + "spread": 101.2, + "zeroW": 0, + "maxHeadH": 125.5 + }, + { + "loc": "de", + "width": 780, + "scrollW": 780, + "clientW": 780, + "hOverflow": false, + "cards": 7, + "cardClip": 0, + "clusterClip": 0, + "capSlotAll": true, + "clustersFound": 7, + "spread": 0, + "zeroW": 0, + "maxHeadH": 194.4 + }, + { + "loc": "de", + "width": 1024, + "scrollW": 1024, + "clientW": 1024, + "hOverflow": false, + "cards": 7, + "cardClip": 0, + "clusterClip": 0, + "capSlotAll": true, + "clustersFound": 7, + "spread": 0, + "zeroW": 0, + "maxHeadH": 125.5 + }, + { + "loc": "de", + "width": 1280, + "scrollW": 1280, + "clientW": 1280, + "hOverflow": false, + "cards": 7, + "cardClip": 0, + "clusterClip": 0, + "capSlotAll": true, + "clustersFound": 7, + "spread": 0, + "zeroW": 0, + "maxHeadH": 125.5 + }, + { + "loc": "de", + "width": 1440, + "scrollW": 1440, + "clientW": 1440, + "hOverflow": false, + "cards": 7, + "cardClip": 0, + "clusterClip": 0, + "capSlotAll": true, + "clustersFound": 7, + "spread": 0, + "zeroW": 0, + "maxHeadH": 125.5 + }, + { + "loc": "en", + "width": 780, + "scrollW": 780, + "clientW": 780, + "hOverflow": false, + "cards": 7, + "cardClip": 0, + "clusterClip": 0, + "capSlotAll": true, + "clustersFound": 7, + "spread": 0, + "zeroW": 0, + "maxHeadH": 160.4 + }, + { + "loc": "en", + "width": 1024, + "scrollW": 1024, + "clientW": 1024, + "hOverflow": false, + "cards": 7, + "cardClip": 0, + "clusterClip": 0, + "capSlotAll": true, + "clustersFound": 7, + "spread": 0, + "zeroW": 0, + "maxHeadH": 125.5 + }, + { + "loc": "en", + "width": 1280, + "scrollW": 1280, + "clientW": 1280, + "hOverflow": false, + "cards": 7, + "cardClip": 0, + "clusterClip": 0, + "capSlotAll": true, + "clustersFound": 7, + "spread": 0, + "zeroW": 0, + "maxHeadH": 125.5 + }, + { + "loc": "en", + "width": 1440, + "scrollW": 1440, + "clientW": 1440, + "hOverflow": false, + "cards": 7, + "cardClip": 0, + "clusterClip": 0, + "capSlotAll": true, + "clustersFound": 7, + "spread": 2, + "zeroW": 0, + "maxHeadH": 125.5 + } +] \ No newline at end of file diff --git a/devlog/_plan/260830_models_provider_header/evidence/wp3-state.json b/devlog/_plan/260830_models_provider_header/evidence/wp3-state.json new file mode 100644 index 0000000000..b9eae984b1 --- /dev/null +++ b/devlog/_plan/260830_models_provider_header/evidence/wp3-state.json @@ -0,0 +1,4 @@ +{ + "perCard": "[{\"card\":\"openaiOpenAI native3/7\",\"capOn\":\"true\",\"hasSelect\":true,\"selectDisabled\":false},{\"card\":\"anthropic3/11 visible\",\"capOn\":\"false\",\"hasSelect\":true,\"selectDisabled\":true},{\"card\":\"cursor4/38 visible1 ne\",\"capOn\":\"false\",\"hasSelect\":true,\"selectDisabled\":true},{\"card\":\"google-antigravity2/6 \",\"capOn\":\"false\",\"hasSelect\":true,\"selectDisabled\":true},{\"card\":\"opencode-free1/8 visib\",\"capOn\":\"false\",\"hasSelect\":true,\"selectDisabled\":true},{\"card\":\"woongDiscovery failed0\",\"capOn\":\"false\",\"hasSelect\":true,\"selectDisabled\":true},{\"card\":\"xai1/8 visible1 new, o\",\"capOn\":\"false\",\"hasSelect\":true,\"selectDisabled\":true}]", + "clusterTabStops": "[\"Default window / cap\",\"Default 1.05M\",\"Custom windows\"]" +} \ No newline at end of file diff --git a/gui/src/pages/Models.tsx b/gui/src/pages/Models.tsx index 9613e7d707..d849780e49 100644 --- a/gui/src/pages/Models.tsx +++ b/gui/src/pages/Models.tsx @@ -1245,23 +1245,14 @@ export default function Models({ apiBase, restartEpoch = 0 }: { apiBase: string; {recentForProvider.length > 0 && <span className="models-chip mono text-caption">{t("models.newCount", { count: recentForProvider.length })}</span>} </button> <div className="row models-provider-actions"> - <button type="button" className="btn btn-ghost btn-sm" aria-label={t("models.editProviderAlias")} title={t("models.editProviderAlias")} onClick={() => void saveProviderAlias(provider)}><IconPencil style={{ width: 14, height: 14 }} /></button> - <Switch - on={aliases.defaults.providers[provider] ?? aliases.defaults.global} - onClick={() => void setDefaultAliases(!(aliases.defaults.providers[provider] ?? aliases.defaults.global), provider)} - label={t("models.useDefaultAliases")} - /> - {/* Available on every card, including the native one: the canonical `openai` seed - check now admits contextWindow/modelContextWindows as user-owned overlays, and - the native accessors only ever narrow the measured window with them. The cap - next to it is the coarser sibling — one value for the whole provider. */} - <button - type="button" - className="btn btn-ghost btn-sm text-caption" - onClick={() => openContextSettings(group)} - aria-haspopup="dialog" - >{t("models.contextSettings")}</button> - { + <button type="button" className="btn btn-ghost btn-sm models-alias-edit" aria-label={t("models.editProviderAlias")} title={t("models.editProviderAlias")} onClick={() => void saveProviderAlias(provider)}><IconPencil style={{ width: 14, height: 14 }} /></button> + <Switch + on={aliases.defaults.providers[provider] ?? aliases.defaults.global} + onClick={() => void setDefaultAliases(!(aliases.defaults.providers[provider] ?? aliases.defaults.global), provider)} + label={t("models.useDefaultAliases")} + showLabel + /> + { <button type="button" className="btn btn-ghost btn-sm text-caption" @@ -1281,9 +1272,8 @@ export default function Models({ apiBase, restartEpoch = 0 }: { apiBase: string; setCustomError(""); setCustomModalOpen(true); }} - aria-label={t("models.customAdd")} aria-haspopup="dialog" - >+</button> + ><span aria-hidden="true">+</span> {t("models.customAdd")}</button> } {(() => { // #2465: Preset / All / Custom. Only providers with a shipped preset get the @@ -1350,32 +1340,44 @@ export default function Models({ apiBase, restartEpoch = 0 }: { apiBase: string; ); })()} <button type="button" className="btn btn-ghost btn-sm text-caption" disabled={busy || allOn} onClick={() => bulkToggle(true)}>{t("models.allOn")}</button> - <button type="button" className="btn btn-ghost btn-sm text-caption" disabled={busy || allOff} onClick={() => bulkToggle(false)}>{t("models.allOff")}</button> - <> - <Switch on={capOn} onClick={() => toggleProviderCap(provider, nativeProviderGroup)} disabled={busy} label={t("models.capValue", { value: fmtK(capDisplayValue) })} /> - {/* The native group keeps the value visible with the cap off: its rows always - advertise SOME window, so hiding the number leaves the card saying nothing - about the context Codex will actually see. Routed providers keep the old - behaviour, where an off cap genuinely means "no opinion". */} - {(capOn || nativeProviderGroup) && ( - <> - <Select - // A saved cap outside CAP_OPTIONS is still a real selectable option - // (inserted below), so select it instead of falling back to "Custom"; - // otherwise the trigger hides the persisted 128k value behind the - // custom-editor label. - value={providerCapCustomOpen[provider] ? CUSTOM_OPTION : String(capDisplayValue)} + <button type="button" className="btn btn-ghost btn-sm text-caption" disabled={busy || allOff} onClick={() => bulkToggle(false)}>{t("models.allOff")}</button> + <div className="models-cap-cluster"> + {/* The label names the FUNCTION. It used to be `models.capValue` - + "기본 128k" - which is a value masquerading as a name: even a + screen-reader user was not told this governs the context window. + The number belongs to the adjacent Select, which is where a value + goes (020_control_affordances.md). */} + <Switch on={capOn} onClick={() => toggleProviderCap(provider, nativeProviderGroup)} disabled={busy} label={t("models.contextCapLabel")} showLabel /> + {/* Always rendered, disabled when the cap is off. A cap-off provider used to + drop this control entirely, which is the defect the user reported: openai + showed 1.05M and anthropic showed nothing, so the two rows started at + different left edges. Disabled-with-a-value is honest — it says "no + opinion", which is what an off cap means — and it keeps the slot occupied + on every card (040_cap_cluster_and_occupied_slot.md). */} + <> + <Select + // A saved cap outside CAP_OPTIONS is still a real selectable option + // (inserted below), so select it instead of falling back to "Custom"; + // otherwise the trigger hides the persisted 128k value behind the + // custom-editor label. + value={providerCapCustomOpen[provider] ? CUSTOM_OPTION : String(capDisplayValue)} options={[ ...(!capOptionSet.has(capDisplayValue) && !providerCapCustomOpen[provider] ? [{ value: String(capDisplayValue), label: fmtK(capDisplayValue) }] : []), ...capOptions.map(v => ({ value: String(v), label: fmtK(v) })), { value: CUSTOM_OPTION, label: t("models.custom") }, ]} - onChange={v => onSelectProviderCap(provider, v)} - disabled={busy || !capOn} - label={t("models.capValue", { value: fmtK(capDisplayValue) })} - /> - {providerCapCustomOpen[provider] && ( + onChange={v => onSelectProviderCap(provider, v)} + disabled={busy || !capOn} + label={t("models.capValue", { value: fmtK(capDisplayValue) })} + title={t("models.contextCapLabel")} + /> + {/* `capOn &&` is load-bearing now that the Select no longer disappears with + the cap. providerCapCustomOpen is independent state: with Custom already + open, flipping the cap off used to leave this input and its Apply button + standing, and Apply sends enabled: true — turning the cap back on from a + field that looks like part of an off cluster. */} + {capOn && providerCapCustomOpen[provider] && ( <> <input className="input" @@ -1388,13 +1390,29 @@ export default function Models({ apiBase, restartEpoch = 0 }: { apiBase: string; disabled={busy} aria-label={t("models.customPlaceholder")} /> - <button type="button" onClick={() => applyProviderCustomCap(provider)} disabled={busy} className="btn btn-ghost btn-sm">{t("models.customApply")}</button> - </> - )} - </> - )} - </> - </div> + <button type="button" onClick={() => applyProviderCustomCap(provider)} disabled={busy} className="btn btn-ghost btn-sm">{t("models.customApply")}</button> + </> + )} + </> + {/* Available on every card, including the native one: the canonical `openai` seed + check now admits contextWindow/modelContextWindows as user-owned overlays, and + the native accessors only ever narrow the measured window with them. The cap + beside it is the coarser sibling — one value for the whole provider. + + It sits in the cluster for VISUAL grouping only. This is deliberately not a + functional merge: this button opens PER-MODEL overrides while the switch and + select are the provider-wide default, and two different scopes cannot honestly + become one control. Three separate tab stops remain. Moving it here from + beside the alias controls does change tab order — switch, then select when + enabled, then this — which reads in the order the controls are now seen. */} + <button + type="button" + className="btn btn-ghost btn-sm text-caption" + onClick={() => openContextSettings(group)} + aria-haspopup="dialog" + >{t("models.contextSettings")}</button> + </div> + </div> </div> {!isCollapsed && ( <div className="models-provider-body"> diff --git a/gui/src/styles-models-workspace.css b/gui/src/styles-models-workspace.css index e9ea063d01..ab00d41402 100644 --- a/gui/src/styles-models-workspace.css +++ b/gui/src/styles-models-workspace.css @@ -309,6 +309,69 @@ max-width: 100%; } +/* ── Cap cluster (040) ── + The provider-wide context window is one setting wearing three sibling controls. + This groups them visually: the gap is tighter than the row own var(--space-2), + which is what makes the trio read as one control instead of three. + + Grouping only, never a functional merge. The per-model overrides button opens a + different SCOPE than the switch and select, which are the provider-wide default, + so all three keep their own tab stop. + + flex-wrap is load-bearing, not tidiness. As one flex item of a wrapping actions + row the cluster can be squeezed, and its children do not shrink: the labeled + switch is flex: 0 0 auto, .btn is white-space: nowrap, the Select is inline-block. + A nowrap cluster would overflow, and .models-provider-card is overflow: hidden, so + the clip would be swallowed silently while a page-level scrollWidth === clientWidth + assertion still passed. Wrapping prevents that; a min-width floor would be the + child-floor design 011 killed. */ +.models-cap-cluster { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: var(--space-1); +} + +/* ── Alias-edit emphasis (050) ── + The user asked for a hover affordance. Two independent audits rejected the first + reading of that — hover that GATES, controls hidden until the pointer arrives — + because it deletes the pencil, the alias-defaults switch and custom-add from the + default visual inventory and worsens the exact opacity 020 exists to fix. What is + left is emphasis on the ONE genuinely secondary control: a provider alias is set + once and then left alone. + + Every state below is written at the SAME 0,3,0 specificity as the resting rule. + The audit caught the earlier draft writing the overrides at 0,2,0, which meant a + (hover: none) device kept the dim forever and :focus-visible was a no-op — the two + failures those rules exist to prevent. + + Rest is 0.75, not the drafted 0.65: .btn:disabled is opacity 0.55, so a deeper dim + reads as maybe-disabled. Contrast is not the constraint — 0.65 already cleared + 1.4.11's 3:1 in both themes — legibility of INTENT is. */ +.models-provider-actions .btn-ghost.models-alias-edit { + opacity: 0.75; + transition: opacity var(--motion-fast); +} +.models-provider-head:hover .models-provider-actions .btn-ghost.models-alias-edit, +.models-provider-head:focus-within .models-provider-actions .btn-ghost.models-alias-edit, +.models-provider-actions .btn-ghost.models-alias-edit:focus-visible { + opacity: 1; +} +/* A pointer that cannot hover never triggers the emphasis rule, so the resting dim + would be permanent. Full contrast is the resting state there. */ +@media (hover: none) { + .models-provider-actions .btn-ghost.models-alias-edit { + opacity: 1; + } +} +/* Reduced motion removes the ANIMATION, never the emphasis: the contrast change is + information, the fade is decoration. */ +@media (prefers-reduced-motion: reduce) { + .models-provider-actions .btn-ghost.models-alias-edit { + transition: none; + } +} + /* ── Spacing rhythm (tight within clusters, loose between sections) ── Keep spacing in CSS classes — inline px floods Impeccable’s page scan. */ diff --git a/gui/src/styles.css b/gui/src/styles.css index 4bd3568f70..027388f1c4 100644 --- a/gui/src/styles.css +++ b/gui/src/styles.css @@ -1178,6 +1178,45 @@ select.input { appearance: none; } .switch.on { background: var(--toggle-on-bg); border-color: var(--toggle-on-bg); } .switch.mixed { background: var(--amber-soft); border-color: var(--amber); } .switch:disabled { opacity: 0.6; cursor: default; } +/* A labeled switch is ONE atomic flex item, which is what the bare 34px button was + before the label existed. Without flex: 0 0 auto the knob and its text become + separate shrinkable items in .models-provider-actions and the text collapses to a + min-content column. + + The label lives INSIDE the button so the visible words are clickable, which means + the button can no longer be the 34x20 pill: that geometry moves to the ::before + track and the button becomes a transparent, borderless row wrapping track + text. */ +.switch.switch-labeled { + width: auto; + height: auto; + border: 0; + background: none; + border-radius: var(--radius-sm); + padding: 0; + gap: var(--space-1); + flex: 0 0 auto; + line-height: normal; +} +.switch.switch-labeled::before { + content: ""; + width: 34px; + height: 20px; + border-radius: var(--radius-pill); + border: 1px solid var(--border); + background: var(--raised); + flex-shrink: 0; + box-sizing: border-box; + transition: background var(--motion-normal), border-color var(--motion-normal); +} +.switch.switch-labeled.on::before { background: var(--toggle-on-bg); border-color: var(--toggle-on-bg); } +.switch.switch-labeled.mixed::before { background: var(--amber-soft); border-color: var(--amber); } +/* The knob is absolutely positioned over the ::before track rather than laid out in + the button's flow, because the flow now also carries the label text. */ +.switch.switch-labeled { position: relative; } +.switch.switch-labeled .knob { position: absolute; left: 3px; } +.switch.switch-labeled.on .knob { transform: translateX(14px); } +.switch.switch-labeled.mixed .knob { transform: translateX(7px); } +.switch-labeled-text { white-space: nowrap; } .switch .knob { width: 14px; height: 14px; diff --git a/gui/src/ui.tsx b/gui/src/ui.tsx index b5db510953..96ef56f188 100644 --- a/gui/src/ui.tsx +++ b/gui/src/ui.tsx @@ -5,11 +5,37 @@ import { IconCheck, IconAlert } from "./icons"; import { IconChevron } from "./icons"; import { computeSelectMenuStyle } from "./select-position"; -export function Switch({ on, mixed = false, onClick, disabled, label }: { on: boolean; mixed?: boolean; onClick: () => void; disabled?: boolean; label?: string }) { +/** + * `label` is the accessible name. It is NOT rendered by default, which is correct + * for a switch inside an already-labeled row and was also a defect: the two + * provider-header switches passed a label and rendered a bare knob, so a sighted + * user could not tell what they toggled (devlog/_plan/260830_models_provider_header/ + * 020_control_affordances.md). + * + * `showLabel` renders the label as visible text INSIDE the button, so the words + * both name the control and toggle it. An earlier revision put that text in an + * `aria-hidden` sibling span; the audit caught that clicking the visible label did + * nothing, because the hit target stayed the 34x20 knob. Text inside the button is + * the element's own content, so it supplies the accessible name on its own and + * `aria-label` must be dropped — otherwise `aria-label` would override the visible + * words and break Label-in-Name. + * + * `title` stays strictly opt-in. Defaulting it to `label` was rejected for the same + * reason: HTML-AAM maps `title` to the accessible description when + * `aria-describedby` is absent, so every untouched bare `Switch` would have been + * announced twice with a description that repeats its own name. + */ +export function Switch({ on, mixed = false, onClick, disabled, label, showLabel = false, title }: { on: boolean; mixed?: boolean; onClick: () => void; disabled?: boolean; label?: string; showLabel?: boolean; title?: string }) { + const labeled = showLabel && !!label; return ( - <button type="button" className={`switch${on ? " on" : ""}${mixed ? " mixed" : ""}`} onClick={onClick} disabled={disabled} - aria-pressed={mixed ? "mixed" : on} aria-label={label ?? (on ? "enabled" : "disabled")}> + <button type="button" + className={`switch${on ? " on" : ""}${mixed ? " mixed" : ""}${labeled ? " switch-labeled" : ""}`} + onClick={onClick} disabled={disabled} + aria-pressed={mixed ? "mixed" : on} + aria-label={labeled ? undefined : (label ?? (on ? "enabled" : "disabled"))} + title={title}> <span className="knob" /> + {labeled ? <span className="switch-labeled-text text-label muted">{label}</span> : null} </button> ); } diff --git a/gui/tests/models-alias-edit-emphasis.test.ts b/gui/tests/models-alias-edit-emphasis.test.ts new file mode 100644 index 0000000000..da617d2862 --- /dev/null +++ b/gui/tests/models-alias-edit-emphasis.test.ts @@ -0,0 +1,107 @@ +import { expect, test } from "bun:test"; +import { effectiveDeclaration, withoutComments } from "./helpers/css-declarations"; + +/** + * WP2 (devlog/_plan/260830_models_provider_header/050_hover_affordance_and_column_gate.md). + * + * The user's requirement was a hover affordance. The independent audit rejected the + * first reading of it — hover that GATES, i.e. controls hidden until the pointer + * arrives — because that deletes the alias pencil, the alias-defaults switch and + * custom-add from the default visual inventory and worsens the exact opacity that + * 020 exists to fix. What survives is emphasis: the control never leaves layout, + * tab order, or the accessibility tree, and only its resting contrast changes. + * + * These assertions exist to stop a later "tidy up" from quietly turning emphasis + * back into gating, and to stop the keyboard and touch paths from being dropped + * while the hover path survives. + */ + +const read = async () => withoutComments(await Bun.file(new URL("../src/styles-models-workspace.css", import.meta.url)).text()); + +/** The body of an at-rule block, which `ruleBodies` cannot reach: it matches an exact selector. */ +function atRuleBody(css: string, condition: string): string { + const at = css.indexOf(condition); + if (at < 0) throw new Error("at-rule not found: " + condition); + const open = css.indexOf("{", at); + let depth = 0; + for (let i = open; i < css.length; i++) { + if (css[i] === "{") depth++; + else if (css[i] === "}") { depth--; if (depth === 0) return css.slice(open + 1, i); } + } + throw new Error("unbalanced at-rule: " + condition); +} + +test("the alias-edit control is de-emphasized at rest, not hidden", async () => { + const css = await read(); + + // 0.75, not lower: .btn:disabled is opacity 0.55, so a deeper dim reads as + // maybe-disabled rather than secondary. + // + // Read the RESTING rule only. `effectiveDeclaration` takes the last textual match + // across every body of an exact selector and does not model @-rules, so run against + // the whole file it would report the `(hover: none)` override's `opacity: 1` and + // pass whatever the resting value happened to be. + const restingOnly = css.slice(0, css.indexOf("@media (hover: none)")); + expect(effectiveDeclaration(restingOnly, ".models-provider-actions .btn-ghost.models-alias-edit", "opacity")).toBe("0.75"); + + // Gating, in any of its spellings, would remove the control from the resting + // layout — the design 011 rejected and 030 re-rejected. + const rule = css.slice(css.indexOf(".models-alias-edit")); + const block = rule.slice(0, rule.indexOf("@media") < 0 ? rule.length : rule.indexOf("@media")); + expect(block).not.toContain("display: none"); + expect(block).not.toContain("visibility: hidden"); + expect(block).not.toContain("content-visibility: hidden"); +}); + +/** + * The audit's blocker: the first draft wrote every override at 0,2,0 against a 0,3,0 + * resting rule. Same origin, lower specificity — so `(hover: none)` never won and the + * dim was permanent on touch, and `:focus-visible` was a silent no-op. A rule that + * exists but cannot win is worse than a missing one, because it reads as covered. + */ +test("every emphasis state is written at the resting rule's specificity", async () => { + const css = await read(); + const REST = ".models-provider-actions .btn-ghost.models-alias-edit"; + + // Each state must carry the full .btn-ghost + descendant chain, not a shorter one. + expect(css).toContain(`.models-provider-head:hover ${REST}`); + expect(css).toContain(`.models-provider-head:focus-within ${REST}`); + expect(css).toContain(`${REST}:focus-visible`); + + // And the at-rule overrides must use the same chain. + expect(atRuleBody(css, "@media (hover: none)")).toContain(REST); + expect(atRuleBody(css, "@media (prefers-reduced-motion: reduce)")).toContain(REST); + + // A bare .models-alias-edit override would be 0,1,0 or 0,2,0 and could not win. + expect(css).not.toMatch(/(^|[\n,])\s*\.models-alias-edit\s*\{/); +}); + +test("emphasis has a keyboard path, not only a pointer one", async () => { + const css = await read(); + + // :hover alone would mean a keyboard user reaching the pencil by Tab never sees + // the emphasis a mouse user gets by proximity. + expect(css).toContain(".models-provider-head:focus-within"); + expect(css).toContain(":focus-visible"); + + const emphasized = css.slice(css.indexOf(".models-provider-head:hover .models-provider-actions")); + expect(emphasized.slice(0, emphasized.indexOf("}"))).toContain("opacity: 1"); +}); + +test("a pointer that cannot hover gets full contrast permanently", async () => { + const css = await read(); + // Without this the resting dim is permanent on touch: no pointer ever arrives to + // trigger the emphasis rule. + const body = atRuleBody(css, "@media (hover: none)"); + expect(body).toContain(".models-alias-edit"); + expect(body).toContain("opacity: 1"); +}); + +test("reduced motion removes the transition rather than the emphasis", async () => { + const css = await read(); + const body = atRuleBody(css, "@media (prefers-reduced-motion: reduce)"); + expect(body).toContain(".models-alias-edit"); + expect(body).toContain("transition: none"); + // The emphasis itself must survive: reduced motion is about animation, not contrast. + expect(body).not.toContain("opacity: 1"); +}); diff --git a/gui/tests/models-cap-cluster.test.ts b/gui/tests/models-cap-cluster.test.ts new file mode 100644 index 0000000000..431603c37d --- /dev/null +++ b/gui/tests/models-cap-cluster.test.ts @@ -0,0 +1,69 @@ +import { expect, test } from "bun:test"; +import { effectiveDeclaration, withoutComments } from "./helpers/css-declarations"; + +/** + * WP3 (devlog/_plan/260830_models_provider_header/040_cap_cluster_and_occupied_slot.md). + * + * The defect the user actually reported: openai showed a 1.05M cap value and + * anthropic showed nothing, because the cap Select was rendered only when the cap + * was on. Two cards therefore started their control row at different left edges. + * The slot is now always occupied — disabled with its value when the cap is off, + * which is an honest rendering of "no opinion". + */ + +const readPage = () => Bun.file(new URL("../src/pages/Models.tsx", import.meta.url)).text(); +const readCss = async () => withoutComments(await Bun.file(new URL("../src/styles-models-workspace.css", import.meta.url)).text()); + +test("the cap Select is no longer conditional on the cap being on", async () => { + const page = await readPage(); + + // The guard that caused the reported misalignment. + expect(page).not.toContain("{(capOn || nativeProviderGroup) && ("); + + // Occupancy without inertness would be a lie: the control must still refuse input + // while the cap is off. + expect(page).toContain("disabled={busy || !capOn}"); +}); + +/** + * Found by the independent audit. `providerCapCustomOpen` is per-provider state that + * nothing clears when the cap is switched off. While the Select disappeared with the + * cap this was unreachable for routed providers; always-rendering it would have left + * the custom input and its Apply button standing under an off cluster — and Apply + * sends `enabled: true`, so the field would silently turn the cap back on. + */ +test("the custom-cap editor cannot outlive the cap being switched off", async () => { + const page = await readPage(); + expect(page).toContain("{capOn && providerCapCustomOpen[provider] && ("); +}); + +test("the cap trio is one visual cluster that can wrap", async () => { + const css = await readCss(); + + // Tighter than the row own var(--space-2): that difference is what makes the trio + // read as one control rather than three peers. + expect(effectiveDeclaration(css, ".models-cap-cluster", "gap")).toBe("var(--space-1)"); + expect(effectiveDeclaration(css, ".models-provider-actions", "gap")).toBe("var(--space-2)"); + + // Load-bearing: the cluster is one flex item of a wrapping row and its children do + // not shrink, so a nowrap cluster would overflow into .models-provider-card own + // overflow: hidden and be clipped silently. + expect(effectiveDeclaration(css, ".models-cap-cluster", "flex-wrap")).toBe("wrap"); + + // A min-width floor here would be the child-floor design 011 rejected. + expect(() => effectiveDeclaration(css, ".models-cap-cluster", "min-width")).toThrow(); +}); + +test("grouping did not become a functional merge", async () => { + const page = await readPage(); + const start = page.indexOf('<div className="models-cap-cluster">'); + expect(start).toBeGreaterThan(-1); + const cluster = page.slice(start, page.indexOf("</div>", start)); + + // Three separate controls, three tab stops. The per-model button opens a different + // SCOPE than the provider-wide switch and select, so collapsing them would be a + // lie about what the surface does. + expect(cluster).toContain("on={capOn}"); + expect(cluster).toContain("onChange={v => onSelectProviderCap(provider, v)}"); + expect(cluster).toContain('t("models.contextSettings")'); +}); diff --git a/gui/tests/models-control-affordances.test.ts b/gui/tests/models-control-affordances.test.ts new file mode 100644 index 0000000000..3dd2290b70 --- /dev/null +++ b/gui/tests/models-control-affordances.test.ts @@ -0,0 +1,85 @@ +import { expect, test } from "bun:test"; +import { effectiveDeclaration, withoutComments } from "./helpers/css-declarations"; + +/** + * WP1 (devlog/_plan/260830_models_provider_header/020_control_affordances.md): + * three controls in the provider header were operable but carried no visible + * meaning. `Switch` accepted a `label` and spent it entirely on `aria-label`, so + * every switch in the app was, to a sighted user, an unlabeled knob. + * + * These are source-text assertions; happy-dom performs no layout. The rendered + * behaviour — that the visible label actually toggles the switch — is proven in + * gui/tests/switch-labeled-dom.test.tsx, and the geometry proof lives in the + * unit's evidence directory. + */ + +const read = (p: string) => Bun.file(new URL(p, import.meta.url)).text(); + +test("Switch renders its label as visible text inside the control", async () => { + const ui = await read("../src/ui.tsx"); + + // The visible-label path exists... + expect(ui).toContain("showLabel"); + expect(ui).toContain("switch-labeled-text"); + + // ...and the visible words are the accessible name, not a decorative copy of it. + // aria-hidden here would leave the button nameless; aria-label would override the + // words and break Label-in-Name. + expect(ui).not.toMatch(/switch-labeled-text[^>]*aria-hidden/); + expect(ui).toContain("aria-label={labeled ? undefined :"); + + // `title` is strictly opt-in: defaulting it to `label` gave every untouched + // Switch in the app an accessible description that merely repeats its name. + expect(ui).toContain("title={title}"); + expect(ui).not.toMatch(/title=\{title \?\? /); +}); + +test("a labeled switch stays one atomic flex item", async () => { + const css = withoutComments(await read("../src/styles.css")); + + // Without this the knob and its text become two shrinkable items inside + // .models-provider-actions and the text collapses to a min-content column — + // the same class of defect 010 fixed for the toggle's children. + expect(effectiveDeclaration(css, ".switch.switch-labeled", "flex")).toBe("0 0 auto"); + expect(effectiveDeclaration(css, ".switch-labeled-text", "white-space")).toBe("nowrap"); + + // The label lives inside the button, so the pill geometry moves to ::before — + // otherwise the text would sit on the coloured track. + expect(effectiveDeclaration(css, ".switch.switch-labeled", "width")).toBe("auto"); + expect(effectiveDeclaration(css, ".switch.switch-labeled::before", "width")).toBe("34px"); +}); + +test("the three opaque provider-header controls now carry visible meaning", async () => { + const page = await read("../src/pages/Models.tsx"); + + // 1. alias-defaults switch: label was already correct, it was merely discarded. + const aliasSwitch = page.slice( + page.indexOf('label={t("models.useDefaultAliases")}') - 300, + page.indexOf('label={t("models.useDefaultAliases")}') + 120, + ); + expect(aliasSwitch).toContain("showLabel"); + + // 2. custom-add: a visible text label, not a tooltip. `title` is undiscoverable + // on touch, and wrapping it in Tooltip would nest <button> inside <button>. + // The glyph is aria-hidden so the accessible name is the visible words. + expect(page).toContain('><span aria-hidden="true">+</span> {t("models.customAdd")}</button>'); + + // 3. cap switch: the label must name the FUNCTION. It used to be + // models.capValue ("기본 128k"), a value masquerading as a name. + expect(page).toMatch(/on=\{capOn\}[\s\S]{0,200}label=\{t\("models\.contextCapLabel"\)\}[\s\S]{0,40}showLabel/); + expect(page).not.toMatch(/on=\{capOn\}[\s\S]{0,200}label=\{t\("models\.capValue"/); + + // The cap Select says what its number means; the value itself stays its name. + const capSelect = page.slice(page.indexOf("onSelectProviderCap(provider, v)"), page.indexOf("onSelectProviderCap(provider, v)") + 400); + expect(capSelect).toContain('title={t("models.contextCapLabel")}'); +}); + +test("no new i18n key was invented: every string used already exists in all locales", async () => { + const { DICTS } = await import("../src/i18n/shared"); + const keys = ["models.useDefaultAliases", "models.customAdd", "models.contextCapLabel"] as const; + for (const [locale, dict] of Object.entries(DICTS)) { + for (const key of keys) { + expect((dict as Record<string, string>)[key], `${locale} is missing ${key}`).toBeTruthy(); + } + } +}); diff --git a/gui/tests/models-native-group-controls.test.ts b/gui/tests/models-native-group-controls.test.ts index e54190ea93..ffd27ad17f 100644 --- a/gui/tests/models-native-group-controls.test.ts +++ b/gui/tests/models-native-group-controls.test.ts @@ -63,11 +63,15 @@ test("fmtK renders past a million as M, not as four-digit k", () => { expect(fmtK(350_000)).toBe("350k"); }); -test("the native group keeps its window readable with the cap switched off", async () => { +test("every provider keeps its window readable with the cap switched off", async () => { const src = await Bun.file(new URL("../src/pages/Models.tsx", import.meta.url)).text(); - // Routed providers hide the value when the cap is off (off = "no opinion"), but a native - // row always advertises some window, so the number stays on screen for that group. - expect(src).toContain("{(capOn || nativeProviderGroup) && ("); + // This used to be the native group ALONE: routed providers hid the value when the cap + // was off, and only a native row kept its number on screen. That asymmetry is the defect + // the user reported — openai showed 1.05M while anthropic showed nothing, so the two + // cards started their control rows at different left edges. The guard is gone and the + // slot is occupied on every card (040_cap_cluster_and_occupied_slot.md), which makes the + // property this test protects strictly wider than it was. + expect(src).not.toContain("{(capOn || nativeProviderGroup) && ("); // With the cap off the stored value is only what a future toggle would apply — the 350k // default — so the display falls back to the widest window the rows actually advertise. // Matched as separate fragments because the expression is wrapped across lines now, and diff --git a/gui/tests/switch-labeled-dom.test.tsx b/gui/tests/switch-labeled-dom.test.tsx new file mode 100644 index 0000000000..42c62f95b0 --- /dev/null +++ b/gui/tests/switch-labeled-dom.test.tsx @@ -0,0 +1,92 @@ +import { afterEach, expect, test } from "bun:test"; +import { Window } from "happy-dom"; +import { act } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { Switch } from "../src/ui"; + +/** + * WP1 rendered-DOM contract + * (devlog/_plan/260830_models_provider_header/020_control_affordances.md). + * + * The sibling .ts file pins source invariants. This file proves the property the + * audit said a substring check cannot reach: the visible label ACTIVATES the + * switch. An earlier revision rendered the label as an `aria-hidden` sibling span, + * which read correctly and looked correct but left the hit target as the 34x20 + * knob, so clicking the words did nothing. + */ + +const globals = ["document", "window", "navigator", "IS_REACT_ACT_ENVIRONMENT"] as const; +let previous: Record<(typeof globals)[number], unknown>; +let win: Window; +let root: Root | null = null; +let host: HTMLElement; + +function mount(node: React.ReactElement) { + win = new Window({ url: "http://localhost/" }); + previous = Object.fromEntries(globals.map(k => [k, Reflect.get(globalThis, k)])) as typeof previous; + // Plain assignment fails in a whole-suite run: an earlier DOM test leaves + // `document` installed as a non-writable global, so only defineProperty works. + Object.defineProperties(globalThis, { + document: { configurable: true, value: win.document }, + window: { configurable: true, value: win }, + navigator: { configurable: true, value: win.navigator }, + IS_REACT_ACT_ENVIRONMENT: { configurable: true, value: true }, + }); + host = win.document.createElement("div") as never as HTMLElement; + win.document.body.appendChild(host as never); + act(() => { root = createRoot(host); root.render(node); }); + return host; +} + +afterEach(() => { + if (root) act(() => root!.unmount()); + root = null; + for (const k of globals) { + Object.defineProperty(globalThis, k, { configurable: true, value: previous?.[k] }); + } +}); + +test("clicking the visible label toggles the switch", () => { + let clicks = 0; + const el = mount(<Switch on={false} onClick={() => { clicks += 1; }} label="Use default aliases" showLabel />); + + const text = el.querySelector(".switch-labeled-text"); + expect(text?.textContent).toBe("Use default aliases"); + + // The words must be INSIDE the button, otherwise they are decoration. + const button = el.querySelector("button.switch"); + expect(button?.contains(text as never)).toBe(true); + + act(() => { (text as never as HTMLElement).dispatchEvent(new win.MouseEvent("click", { bubbles: true }) as never); }); + expect(clicks).toBe(1); +}); + +test("a labeled switch is named by its visible words, and exactly once", () => { + const el = mount(<Switch on onClick={() => {}} label="Default window / cap" showLabel />); + const button = el.querySelector("button.switch")!; + + // aria-label would OVERRIDE the visible text and break Label-in-Name. + expect(button.getAttribute("aria-label")).toBeNull(); + // aria-hidden on the text would leave the button with no name at all. + expect(el.querySelector(".switch-labeled-text")?.getAttribute("aria-hidden")).toBeNull(); + expect(button.textContent).toContain("Default window / cap"); + expect(button.getAttribute("aria-pressed")).toBe("true"); +}); + +test("an unlabeled switch keeps aria-label and gains no redundant title", () => { + const el = mount(<Switch on={false} onClick={() => {}} label="Keep native" />); + const button = el.querySelector("button.switch")!; + + expect(button.getAttribute("aria-label")).toBe("Keep native"); + // HTML-AAM maps title to the accessible DESCRIPTION when aria-describedby is + // absent. Copying the name into it announces the control twice. + expect(button.getAttribute("title")).toBeNull(); + expect(button.querySelector(".switch-labeled-text")).toBeNull(); +}); + +test("title stays opt-in and does not displace the accessible name", () => { + const el = mount(<Switch on={false} onClick={() => {}} label="Shadow call" title="Explains the shadow lane" />); + const button = el.querySelector("button.switch")!; + expect(button.getAttribute("title")).toBe("Explains the shadow lane"); + expect(button.getAttribute("aria-label")).toBe("Shadow call"); +}); From 910b4c7364a23f28f8ad21bbb6110a9d12891d15 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 02:22:59 +0900 Subject: [PATCH 17/42] feat(gui): source brand marks for 23 providers that rendered a letter tile (#3095) * feat(gui): source brand marks for 23 providers that rendered a letter tile The provider rail, details panel and dashboard rows fall back to a coloured initial tile when providerIconSrc returns nothing, which was 38 of 83 registry providers. This sources marks for 23 of them. Eleven vendors publish a usable square SVG and are committed as fetched, with only comments, <title>/<desc> and data-name stripped. Twelve publish no square vector, so their favicon or app icon is traced -- potrace for a single-ink silhouette, k-means colour layers for multi-colour art, the same technique that produced hermes-agent.svg and gajae-code.svg. The first tracing pass shipped a bug worth recording. A favicon is usually a glyph on a filled rounded square, and tracing by luminance captured the square: baseten came out 97.7% ink, bizrouter 89.3% -- black boxes, not marks. The fix reads the border ring, takes its median colour as the plate when the ring is uniform, and masks by distance from that colour rather than by darkness. It found real plates behind baseten (#19e76e), cerebras (#ef5b27), hyperbolic (#1a1a1a), umans and bizrouter (#000000), and orcarouter (#ffffff). Two candidates were rejected rather than shipped. nousresearch.com publishes only safari-pinned-tab.svg, whose first path is the full 512-unit frame -- the exact file the Hermes client mark rejected for rendering as a black square. LiteLLM has no mark of its own on its site; the SVGs there are third-party model logos. Both keep their fallback tile with the attempt recorded. Two guards. One fails a mark that passes an SVG parse but is a <text> glyph, an embedded raster or a base64 payload -- none of which is visible in review and all of which break in a 19px box. The other fails a horizontal wordmark by viewBox ratio, which is what disqualified six vendor logos here and the MiniMax docs asset in the previous unit. Providers with no resolved mark: 38 -> 15 on this branch. Measured after: 18 of the 23 fall under 2:1 against one of the two surfaces, because ProviderIcon has no themed path at all. That is wp5, not a defect in these assets. Verification: 2 pass in the new file, each driven red (a text glyph, a 5.1:1 lockup). tsc 0 both roots, oxlint clean, privacy:scan clean, full GUI suite 1156 pass / 0 fail across 187 files. Every mark rendered at 19px in the real tile in both themes with no load failures. * feat(gui): add together, litellm and nous marks from their docs sites Three vendors whose marketing site offered only a wordmark publish a square mark one hop away. together.ai and litellm.ai both serve one from their docs subdomain, and nousresearch.com has an apple-touch-icon worth tracing. The Nous case needed care. Its safari-pinned-tab.svg is the same full-frame file the Hermes client mark rejected -- it renders as a black square. The apple-touch-icon is the real company mark, and it stays distinct from hermes-agent.svg: one organization, two marks, and attributing either to the other would be wrong. Providers resolved: 70 -> 77 of 83. The remaining six (chutes, nscale, the three volcengine plan ids, tencent-coding-plan) publish nothing usable at their registry domains, docs subdomains, or the conventional icon paths; the README records what was searched. --- gui/public/provider-icons/README.md | 82 ++++++ gui/public/provider-icons/baseten.svg | 13 + gui/public/provider-icons/bizrouter.svg | 41 +++ gui/public/provider-icons/cerebras.svg | 26 ++ gui/public/provider-icons/deepinfra.svg | 75 ++++++ gui/public/provider-icons/digitalocean.svg | 10 + gui/public/provider-icons/featherless.svg | 4 + gui/public/provider-icons/hyperbolic.svg | 18 ++ gui/public/provider-icons/kilo.svg | 13 + gui/public/provider-icons/litellm.svg | 1 + gui/public/provider-icons/nanogpt.svg | 74 ++++++ gui/public/provider-icons/nebius.svg | 1 + gui/public/provider-icons/neuralwatt.svg | 27 ++ gui/public/provider-icons/nous.svg | 149 +++++++++++ gui/public/provider-icons/novita.svg | 32 +++ gui/public/provider-icons/orcarouter.svg | 175 +++++++++++++ gui/public/provider-icons/parallel.svg | 13 + gui/public/provider-icons/sambanova.svg | 276 +++++++++++++++++++++ gui/public/provider-icons/scaleway.svg | 11 + gui/public/provider-icons/siliconflow.svg | 18 ++ gui/public/provider-icons/synthetic.svg | 12 + gui/public/provider-icons/together.svg | 18 ++ gui/public/provider-icons/umans.svg | 30 +++ gui/public/provider-icons/venice.svg | 165 ++++++++++++ gui/public/provider-icons/vultr.svg | 15 ++ gui/public/provider-icons/zai.svg | 218 ++++++++++++++++ gui/public/provider-icons/zenmux.svg | 1 + gui/src/provider-icons.ts | 35 +++ gui/tests/provider-marks-assets.test.ts | 66 +++++ 29 files changed, 1619 insertions(+) create mode 100644 gui/public/provider-icons/baseten.svg create mode 100644 gui/public/provider-icons/bizrouter.svg create mode 100644 gui/public/provider-icons/cerebras.svg create mode 100644 gui/public/provider-icons/deepinfra.svg create mode 100644 gui/public/provider-icons/digitalocean.svg create mode 100644 gui/public/provider-icons/featherless.svg create mode 100644 gui/public/provider-icons/hyperbolic.svg create mode 100644 gui/public/provider-icons/kilo.svg create mode 100644 gui/public/provider-icons/litellm.svg create mode 100644 gui/public/provider-icons/nanogpt.svg create mode 100644 gui/public/provider-icons/nebius.svg create mode 100644 gui/public/provider-icons/neuralwatt.svg create mode 100644 gui/public/provider-icons/nous.svg create mode 100644 gui/public/provider-icons/novita.svg create mode 100644 gui/public/provider-icons/orcarouter.svg create mode 100644 gui/public/provider-icons/parallel.svg create mode 100644 gui/public/provider-icons/sambanova.svg create mode 100644 gui/public/provider-icons/scaleway.svg create mode 100644 gui/public/provider-icons/siliconflow.svg create mode 100644 gui/public/provider-icons/synthetic.svg create mode 100644 gui/public/provider-icons/together.svg create mode 100644 gui/public/provider-icons/umans.svg create mode 100644 gui/public/provider-icons/venice.svg create mode 100644 gui/public/provider-icons/vultr.svg create mode 100644 gui/public/provider-icons/zai.svg create mode 100644 gui/public/provider-icons/zenmux.svg create mode 100644 gui/tests/provider-marks-assets.test.ts diff --git a/gui/public/provider-icons/README.md b/gui/public/provider-icons/README.md index a4346b2a4f..43b1e1a92e 100644 --- a/gui/public/provider-icons/README.md +++ b/gui/public/provider-icons/README.md @@ -140,3 +140,85 @@ Both directions are enforced in `gui/tests/integration-marks.test.ts`, including luminance check that fails any single-ink near-neutral mark left as an image. That direction was missing until it caught `grok`; the same class of defect had already shipped once for `prime`, `opencode` and `kimi`. + +## Provider marks (2026-09-01) + +Sourced for the providers that were rendering a coloured initial tile. Every +entry below was fetched from the vendor's own domain, taken from the registry's +`baseUrl`/`dashboardUrl` rather than guessed. + +Published as SVG and committed with only comments, `<title>`/`<desc>` and +`data-name` attributes stripped: + +- `digitalocean.svg` — `digitalocean.com` favicon, 32x32. +- `featherless.svg` — `featherless.ai/favicon.svg`, 256x256. +- `kilo.svg` — `kilo.ai/favicon/favicon.svg`, 32x32. Keeps its `oklch()` plate. +- `nanogpt.svg` — `nano-gpt.com/logo.svg`, 181x187, gradient. +- `nebius.svg` — `nebius.com/favicon/favicon.svg`, 96x96. +- `neuralwatt.svg` — the site's Webflow-hosted brand asset, 32x32. +- `parallel.svg` — `parallel.ai/icon.svg`, 96x96. +- `scaleway.svg` — `scaleway.com/favicon/website/favicon.svg`, 16x16. +- `synthetic.svg` — `synthetic.new/favicon.svg`, 54x54. +- `zai.svg` — `z-cdn.chatglm.cn/z-ai/static/logo.svg`, 30x30. +- `zenmux.svg` — the site's CDN-hosted brand mark, 160x160. + +Traced from raster, because the vendor publishes no square SVG mark. Same +technique as `hermes-agent.svg` and `gajae-code.svg`: `potrace -s --flat` for a +single-ink silhouette, k-means colour layers (seeded at 3, largest area first) +for multi-colour art, downsampled to a 160px box first so the trace does not +follow every upscaled pixel edge. + +- `cerebras.svg`, `novita.svg`, `siliconflow.svg`, `deepinfra.svg` — single-ink. +- `baseten.svg`, `hyperbolic.svg`, `sambanova.svg`, `umans.svg`, `venice.svg`, + `vultr.svg`, `bizrouter.svg`, `orcarouter.svg` — colour-layered. +- `nous.svg` — traced from `nousresearch.com/apple-touch-icon.png` (180x180). This + is the Nous Research company mark, distinct from `hermes-agent.svg`, which is + the Hermes product's own icon. Attributing one to the other would be wrong even + though the same organization ships both. + +Found on a docs subdomain after the vendor's marketing site offered only a +wordmark: + +- `together.svg` — `docs.together.ai/favicon.svg`, 1.07:1. +- `litellm.svg` — `docs.litellm.ai/img/logo.svg`, 1:1. The marketing site's SVGs + are third-party model logos, not LiteLLM's own mark. + +**The plate problem, recorded because the first pass shipped it.** A favicon is +usually a glyph on a filled rounded square. Tracing luminance alone captured the +square and produced a solid box: `baseten` came out 97.7% ink, `bizrouter` 89.3%. +The fix reads the border ring, takes its median colour as the plate when the ring +is uniform, and masks by distance from that colour instead of by darkness. It +found real plates behind `baseten` (#19e76e), `cerebras` (#ef5b27), `hyperbolic` +(#1a1a1a), `umans`/`bizrouter` (#000000) and `orcarouter` (#ffffff). + +### Rejected, and why + +- **`nousresearch.com/safari-pinned-tab.svg`** — opens with the full 512-unit + frame as its first path, so it renders as a black square. This is the identical + candidate the Hermes client mark rejected. The apple-touch-icon was used instead. +- **LiteLLM's marketing-site SVGs** — third-party model logos, and the favicon + traces to a muddy blob with no legible silhouette at 19px. The docs logo was + used instead. +- **Wordmarks refused** for `cerebras` (843x320 from Sanity CDN), `siliconflow` + (188x28), `zhipu-bigmodel` (123x25), `vultr` (218x52), `baseten` (1001x151) and + `chutes` (192x30). A lockup in the rail's 19px box is an illegible smear, so + each was replaced by a traced square mark or left to the fallback. + +### Still unmarked, and what was searched + +Six ids keep the fallback tile. Each was probed at its registry `baseUrl` and +`dashboardUrl`, plus the vendor's docs subdomain and the conventional icon paths +(`/favicon.svg`, `/favicon.ico`, `/apple-touch-icon.png`, `/logo.svg`, `/icon.svg`): + +- `chutes` — `chutes.ai` and `docs.chutes.ai` serve only the 192x30 wordmark. +- `nscale` — `nscale.com` and `docs.nscale.com` returned no icon at any path. +- `volcengine`, `volcengine-coding-plan`, `volcengine-agent-plan` — the Ark + console's SVGs are UI glyphs rather than a product mark. +- `tencent-coding-plan` — `cloud.tencent.com` serves a 32x32 favicon whose trace + is unreadable at 19px. + +These are recorded results, not skipped work. Inventing a mark, or borrowing a +neighbouring brand's, is a misattribution that outlives the commit. + +`zhipu-bigmodel` and `zhipu-bigmodel-coding` share `zai.svg`: Z.AI and BigModel +are the same company, and the mainland console publishes only the wordmark. diff --git a/gui/public/provider-icons/baseten.svg b/gui/public/provider-icons/baseten.svg new file mode 100644 index 0000000000..2fa74a269b --- /dev/null +++ b/gui/public/provider-icons/baseten.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48.000000 48.000000"><g transform="translate(0.000000,48.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"><path fill="#000000" d="M97 413 c-4 -3 -7 -19 -7 -35 l0 -28 110 0 110 0 0 35 0 35 -103 0 +c-57 0 -107 -3 -110 -7z M320 310 c0 -28 3 -30 35 -30 32 0 35 2 35 30 0 28 +-3 30 -35 30 -32 0 -35 -2 -35 -30z M170 240 l0 -30 70 0 70 0 0 30 0 30 -70 +0 -70 0 0 -30z M320 170 c0 -28 3 -30 35 -30 32 0 35 2 35 30 0 28 -3 30 -35 +30 -32 0 -35 -2 -35 -30z M242 98 c3 -29 7 -33 36 -36 31 -3 32 -2 32 32 0 35 +-1 36 -36 36 -34 0 -35 -1 -32 -32z"/><path fill="#084d25" d="M338 343 c12 -2 30 -2 40 0 9 3 -1 5 -23 4 -22 0 -30 -2 -17 -4z +M203 273 c20 -2 54 -2 75 0 20 2 3 4 -38 4 -41 0 -58 -2 -37 -4z M203 203 c20 +-2 54 -2 75 0 20 2 3 4 -38 4 -41 0 -58 -2 -37 -4z M338 133 c12 -2 30 -2 40 +0 9 3 -1 5 -23 4 -22 0 -30 -2 -17 -4z"/><path fill="#0d7437" d="M314 240 c0 -96 2 -136 3 -87 2 48 2 126 0 175 -1 48 -3 8 -3 -88z +M162 240 c0 -19 2 -27 5 -17 2 9 2 25 0 35 -3 9 -5 1 -5 -18z"/><path fill="#12ab52" d="M142 343 c32 -2 84 -2 115 0 32 2 6 3 -57 3 -63 0 -89 -1 -58 -3z +M263 133 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0 -27 -2 -17 -5z"/><path fill="#0f8d44" d="M333 273 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0 -27 -2 -17 -5z M333 +203 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0 -27 -2 -17 -5z"/></g></svg> \ No newline at end of file diff --git a/gui/public/provider-icons/bizrouter.svg b/gui/public/provider-icons/bizrouter.svg new file mode 100644 index 0000000000..f0644866a3 --- /dev/null +++ b/gui/public/provider-icons/bizrouter.svg @@ -0,0 +1,41 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160.000000 160.000000"><g transform="translate(0.000000,160.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"><path fill="#fdfdfd" d="M492 1098 l3 -153 73 -3 72 -3 0 156 0 155 -75 0 -76 0 3 -152z M670 +840 l0 -70 140 0 c127 0 140 -2 150 -19 5 -11 10 -67 10 -125 l0 -106 75 0 75 +0 0 78 c0 144 -48 234 -152 286 -47 24 -63 26 -175 26 l-123 0 0 -70z M490 +616 l0 -134 70 -66 71 -67 157 3 157 3 0 70 0 70 -150 5 -150 5 -3 123 -3 122 +-74 0 -75 0 0 -134z"/><path fill="#cecece" d="M568 1253 l72 -4 0 -155 0 -155 -72 3 -73 3 -5 155 -6 155 -2 -151 +c-1 -114 1 -154 11 -162 7 -6 46 -12 87 -14 l75 -3 3 -82 3 -82 148 -3 c82 -2 +147 0 144 5 -2 4 -67 7 -144 7 l-139 0 0 70 0 70 118 2 117 2 -125 6 -125 5 +-5 165 -5 165 -75 1 -75 1 73 -4z M1040 835 c13 -14 26 -25 28 -25 3 0 -5 11 +-18 25 -13 14 -26 25 -28 25 -3 0 5 -11 18 -25z M642 638 c-1 -70 1 -129 6 +-131 4 -3 6 34 4 81 -5 160 -8 178 -10 50z M484 615 c0 -71 1 -99 3 -62 2 37 +2 96 0 130 -2 34 -3 4 -3 -68z M962 627 l-2 -118 83 3 82 3 1 70 0 70 -3 -67 +-4 -68 -74 0 -74 0 -4 113 -3 112 -2 -118z M1111 704 c0 -11 3 -14 6 -6 3 7 2 +16 -1 19 -3 4 -6 -2 -5 -13z M530 438 c41 -45 50 -53 50 -45 0 2 -21 23 -47 +47 l-48 45 45 -47z M790 350 l-155 -6 152 -2 c93 -1 154 2 158 8 3 6 5 9 3 8 +-2 -1 -73 -5 -158 -8z"/><path fill="#404040" d="M533 1263 c20 -2 54 -2 75 0 20 2 3 4 -38 4 -41 0 -58 -2 -37 -4z +M474 1100 c0 -85 2 -120 3 -77 2 42 2 112 0 155 -1 42 -3 7 -3 -78z M533 763 +c20 -2 54 -2 75 0 20 2 3 4 -38 4 -41 0 -58 -2 -37 -4z M733 753 c37 -2 97 -2 +135 0 37 2 6 3 -68 3 -74 0 -105 -1 -67 -3z M474 615 c0 -77 2 -107 3 -67 2 +40 2 103 0 140 -2 37 -3 4 -3 -73z M1133 595 c0 -38 2 -53 4 -32 2 20 2 52 0 +70 -2 17 -4 1 -4 -38z M708 333 c46 -2 118 -2 160 0 42 1 5 3 -83 3 -88 0 +-123 -2 -77 -3z"/><path fill="#6c6c6c" d="M654 1095 c0 -88 2 -123 3 -77 2 46 2 118 0 160 -1 42 -3 5 -3 -83z +M533 923 c20 -2 54 -2 75 0 20 2 3 4 -38 4 -41 0 -58 -2 -37 -4z M1020 867 c0 +-2 15 -16 33 -33 l32 -29 -29 33 c-28 30 -36 37 -36 29z M654 747 c-2 -7 -3 +-62 -2 -123 l3 -109 5 123 c3 67 4 122 2 122 -1 0 -5 -6 -8 -13z M954 630 c0 +-63 1 -89 3 -57 2 31 2 83 0 115 -2 31 -3 5 -3 -58z M1003 503 c20 -2 54 -2 +75 0 20 2 3 4 -38 4 -41 0 -58 -2 -37 -4z M540 415 c35 -36 67 -65 69 -65 3 0 +-24 29 -59 65 -35 36 -67 65 -69 65 -3 0 24 -29 59 -65z"/><path fill="#9e9e9e" d="M647 930 c-1 -3 1 -45 4 -95 l5 -90 2 87 2 87 108 3 107 3 -113 5 +c-63 3 -115 3 -115 0z M487 753 c-3 -5 20 -7 51 -5 98 5 110 9 30 11 -42 0 +-79 -2 -81 -6z M1122 680 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z +M657 508 c2 -5 69 -8 149 -8 l144 0 0 -72 c0 -92 8 -85 11 10 l2 73 -155 2 +c-86 1 -154 -1 -151 -5z M602 365 c15 -16 29 -27 31 -24 3 2 -10 15 -27 28 +l-31 24 27 -28z"/><path fill="#2b2b2b" d="M475 1260 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z +M669 937 c4 -4 55 -6 112 -5 l104 3 -112 5 c-62 3 -109 1 -104 -3z M640 842 +l5 -77 3 69 c2 38 -1 73 -5 77 -4 4 -6 -27 -3 -69z M1050 845 c13 -14 26 -25 +28 -25 3 0 -5 11 -18 25 -13 14 -26 25 -28 25 -3 0 5 -11 18 -25z M1132 680 +c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M1126 513 c-6 -14 -5 -15 +5 -6 7 7 10 15 7 18 -3 3 -9 -2 -12 -12z M743 513 c37 -2 96 -2 130 0 34 2 4 +3 -68 3 -71 0 -99 -1 -62 -3z M963 420 c0 -41 2 -58 4 -37 2 20 2 54 0 75 -2 +20 -4 3 -4 -38z M515 430 c27 -27 51 -50 54 -50 3 0 -17 23 -44 50 -27 28 -51 +50 -54 50 -3 0 17 -22 44 -50z"/></g></svg> \ No newline at end of file diff --git a/gui/public/provider-icons/cerebras.svg b/gui/public/provider-icons/cerebras.svg new file mode 100644 index 0000000000..56d52a3d3c --- /dev/null +++ b/gui/public/provider-icons/cerebras.svg @@ -0,0 +1,26 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" + "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<svg version="1.0" xmlns="http://www.w3.org/2000/svg" + width="256.000000pt" height="256.000000pt" viewBox="0 0 256.000000 256.000000" + preserveAspectRatio="xMidYMid meet"> +<metadata> +Created by potrace 1.16, written by Peter Selinger 2001-2019 +</metadata> +<g transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"> +<path d="M1555 1630 c-3 -5 -28 -10 -56 -10 -27 0 -49 -4 -49 -10 0 -5 -8 -10 +-18 -10 -10 0 -24 -9 -32 -20 -8 -11 -22 -20 -30 -20 -21 0 -70 -51 -70 -73 0 +-9 -4 -17 -10 -17 -5 0 -10 -9 -10 -20 0 -11 -4 -20 -10 -20 -5 0 -10 -9 -10 +-20 0 -11 -4 -20 -10 -20 -6 0 -10 -38 -10 -94 0 -53 4 -98 10 -101 6 -3 10 +-19 10 -36 0 -16 5 -29 10 -29 6 0 10 -7 10 -16 0 -21 112 -134 133 -134 10 0 +17 -4 17 -10 0 -5 9 -10 20 -10 11 0 20 -4 20 -10 0 -13 227 -13 235 0 3 6 18 +10 32 10 14 0 33 8 42 19 9 10 25 20 35 22 11 2 21 9 23 14 4 13 -90 115 -106 +115 -6 0 -26 -9 -46 -20 -50 -28 -154 -28 -180 0 -10 11 -26 20 -37 20 -10 0 +-18 7 -18 15 0 8 -9 30 -20 50 -22 39 -29 158 -10 170 6 3 10 13 10 21 0 19 +45 64 64 64 8 0 18 5 21 10 3 6 16 10 29 10 12 0 27 5 34 12 9 9 15 9 24 0 7 +-7 20 -12 30 -12 9 0 33 -9 53 -20 47 -26 50 -26 88 13 73 76 73 75 49 97 -12 +11 -28 20 -37 20 -8 0 -15 5 -15 10 0 6 -9 10 -20 10 -11 0 -20 5 -20 10 0 6 +-18 10 -39 10 -22 0 -43 5 -46 10 -3 6 -24 10 -45 10 -21 0 -42 -4 -45 -10z"/> +</g> +</svg> diff --git a/gui/public/provider-icons/deepinfra.svg b/gui/public/provider-icons/deepinfra.svg new file mode 100644 index 0000000000..44c1eb0f4f --- /dev/null +++ b/gui/public/provider-icons/deepinfra.svg @@ -0,0 +1,75 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" + "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<svg version="1.0" xmlns="http://www.w3.org/2000/svg" + width="256.000000pt" height="256.000000pt" viewBox="0 0 256.000000 256.000000" + preserveAspectRatio="xMidYMid meet"> +<metadata> +Created by potrace 1.16, written by Peter Selinger 2001-2019 +</metadata> +<g transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"> +<path d="M1168 2190 c-27 -27 -48 -57 -48 -66 0 -10 -10 -28 -22 -41 l-21 -23 +21 -23 c12 -13 22 -31 22 -41 0 -9 22 -39 50 -66 48 -49 50 -50 110 -50 60 0 +62 1 110 50 28 27 50 57 50 66 0 10 10 28 22 41 l21 23 -21 23 c-12 13 -22 31 +-22 41 0 9 -22 39 -50 66 -49 50 -50 50 -112 49 -62 0 -64 -1 -110 -49z m204 +-62 c20 -22 28 -42 28 -68 0 -26 -8 -46 -28 -68 -27 -30 -34 -32 -92 -32 -58 +0 -65 2 -92 32 -38 43 -38 92 0 136 25 30 31 32 91 32 60 0 66 -2 93 -32z +M555 1980 c-10 -11 -27 -20 -37 -20 -10 0 -41 -22 -68 -50 -49 -48 -50 -50 +-50 -110 0 -60 1 -62 50 -110 27 -28 57 -50 66 -50 10 0 28 -10 41 -22 l23 +-21 23 21 c13 12 31 22 41 22 9 0 39 22 66 50 49 48 50 50 50 110 0 60 -1 62 +-50 110 -27 28 -58 50 -68 50 -10 0 -27 9 -37 20 -10 11 -21 20 -25 20 -4 0 +-15 -9 -25 -20z m93 -88 c30 -27 32 -34 32 -92 0 -58 -2 -65 -32 -92 -22 -20 +-42 -28 -68 -28 -26 0 -46 8 -68 28 -30 27 -32 34 -32 92 0 58 2 65 32 92 22 +20 42 28 68 28 26 0 46 -8 68 -28z M1955 1980 c-10 -11 -27 -20 -37 -20 -10 0 +-41 -22 -68 -50 -49 -48 -50 -50 -50 -110 0 -60 1 -62 50 -110 27 -28 57 -50 +66 -50 10 0 28 -10 41 -22 l23 -21 23 21 c13 12 31 22 41 22 9 0 39 22 66 50 +49 48 50 50 50 110 0 60 -1 62 -50 110 -27 28 -58 50 -68 50 -10 0 -27 9 -37 +20 -10 11 -21 20 -25 20 -4 0 -15 -9 -25 -20z m93 -88 c30 -27 32 -34 32 -92 +0 -58 -2 -65 -32 -92 -22 -20 -42 -28 -68 -28 -26 0 -46 8 -68 28 -30 27 -32 +34 -32 92 0 58 2 65 32 92 22 20 42 28 68 28 26 0 46 -8 68 -28z M1168 1670 +c-27 -27 -48 -57 -48 -66 0 -10 -10 -28 -22 -41 l-21 -23 21 -23 c12 -13 22 +-31 22 -41 0 -9 22 -39 50 -66 48 -49 50 -50 110 -50 60 0 62 1 110 50 28 27 +50 57 50 66 0 10 10 28 22 41 l21 23 -21 23 c-12 13 -22 31 -22 41 0 9 -22 39 +-50 66 -49 50 -50 50 -112 49 -62 0 -64 -1 -110 -49z m204 -62 c20 -22 28 -42 +28 -68 0 -26 -8 -46 -28 -68 -27 -30 -34 -32 -92 -32 -58 0 -65 2 -92 32 -38 +43 -38 92 0 136 25 30 31 32 91 32 60 0 66 -2 93 -32z M555 1460 c-10 -11 -27 +-20 -37 -20 -10 0 -41 -22 -68 -50 -49 -48 -50 -50 -50 -110 0 -60 1 -62 50 +-110 27 -28 57 -50 66 -50 10 0 28 -10 41 -22 l23 -21 23 21 c13 12 31 22 41 +22 9 0 39 22 66 50 49 48 50 50 50 110 0 60 -1 62 -50 110 -27 28 -58 50 -68 +50 -10 0 -27 9 -37 20 -10 11 -21 20 -25 20 -4 0 -15 -9 -25 -20z m93 -88 c30 +-27 32 -34 32 -92 0 -58 -2 -65 -32 -92 -22 -20 -42 -28 -68 -28 -26 0 -46 8 +-68 28 -30 27 -32 34 -32 92 0 58 2 65 32 92 22 20 42 28 68 28 26 0 46 -8 68 +-28z M1955 1460 c-10 -11 -27 -20 -37 -20 -10 0 -41 -22 -68 -50 -49 -48 -50 +-50 -50 -110 0 -60 1 -62 50 -110 27 -28 57 -50 66 -50 10 0 28 -10 41 -22 +l23 -21 23 21 c13 12 31 22 41 22 9 0 39 22 66 50 49 48 50 50 50 110 0 60 -1 +62 -50 110 -27 28 -58 50 -68 50 -10 0 -27 9 -37 20 -10 11 -21 20 -25 20 -4 +0 -15 -9 -25 -20z m93 -88 c30 -27 32 -34 32 -92 0 -58 -2 -65 -32 -92 -22 +-20 -42 -28 -68 -28 -26 0 -46 8 -68 28 -30 27 -32 34 -32 92 0 58 2 65 32 92 +22 20 42 28 68 28 26 0 46 -8 68 -28z M1168 1150 c-27 -27 -48 -57 -48 -66 0 +-10 -10 -28 -22 -41 l-21 -23 21 -23 c12 -13 22 -31 22 -41 0 -9 22 -39 50 +-66 48 -49 50 -50 110 -50 60 0 62 1 110 50 28 27 50 57 50 66 0 10 10 28 22 +41 l21 23 -21 23 c-12 13 -22 31 -22 41 0 9 -22 39 -50 66 -49 50 -50 50 -112 +49 -62 0 -64 -1 -110 -49z m204 -62 c20 -22 28 -42 28 -68 0 -26 -8 -46 -28 +-68 -27 -30 -34 -32 -92 -32 -58 0 -65 2 -92 32 -38 43 -38 92 0 136 25 30 31 +32 91 32 60 0 66 -2 93 -32z M555 940 c-10 -11 -27 -20 -37 -20 -10 0 -41 -22 +-68 -50 -49 -48 -50 -50 -50 -110 0 -60 1 -62 50 -110 27 -28 57 -50 66 -50 +10 0 28 -10 41 -22 l23 -21 23 21 c13 12 31 22 41 22 9 0 39 22 66 50 49 48 +50 50 50 110 0 60 -1 62 -50 110 -27 28 -58 50 -68 50 -10 0 -27 9 -37 20 -10 +11 -21 20 -25 20 -4 0 -15 -9 -25 -20z m93 -88 c30 -27 32 -34 32 -92 0 -58 +-2 -65 -32 -92 -22 -20 -42 -28 -68 -28 -26 0 -46 8 -68 28 -30 27 -32 34 -32 +92 0 58 2 65 32 92 43 38 93 38 136 0z M1955 940 c-10 -11 -27 -20 -37 -20 +-10 0 -41 -22 -68 -50 -49 -48 -50 -50 -50 -110 0 -60 1 -62 50 -110 27 -28 +57 -50 66 -50 10 0 28 -10 41 -22 l23 -21 23 21 c13 12 31 22 41 22 9 0 39 22 +66 50 49 48 50 50 50 110 0 60 -1 62 -50 110 -27 28 -58 50 -68 50 -10 0 -27 +9 -37 20 -10 11 -21 20 -25 20 -4 0 -15 -9 -25 -20z m93 -88 c30 -27 32 -34 +32 -92 0 -58 -2 -65 -32 -92 -43 -38 -93 -38 -136 0 -30 27 -32 34 -32 92 0 +58 2 65 32 92 43 38 93 38 136 0z M1168 630 c-27 -27 -48 -57 -48 -66 0 -10 +-10 -28 -22 -41 l-21 -23 21 -23 c12 -13 22 -31 22 -41 0 -9 22 -39 50 -66 48 +-49 50 -50 110 -50 60 0 62 1 110 50 28 27 50 57 50 66 0 10 10 28 22 41 l21 +23 -21 23 c-12 13 -22 31 -22 41 0 9 -22 39 -50 66 -49 50 -50 50 -112 49 -62 +0 -64 -1 -110 -49z m204 -62 c20 -22 28 -42 28 -68 0 -26 -8 -46 -28 -68 -27 +-30 -34 -32 -92 -32 -58 0 -65 2 -92 32 -38 43 -38 92 0 136 25 30 31 32 91 +32 60 0 66 -2 93 -32z"/> +</g> +</svg> diff --git a/gui/public/provider-icons/digitalocean.svg b/gui/public/provider-icons/digitalocean.svg new file mode 100644 index 0000000000..2d9a943e90 --- /dev/null +++ b/gui/public/provider-icons/digitalocean.svg @@ -0,0 +1,10 @@ +<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> + <style> + .do-favicon { fill: #000; } + @media (prefers-color-scheme: dark) { .do-favicon { fill: #F4F5F5; } } + </style> + <path class="do-favicon" fill="#000" d="M16.0219 25.8036V32C26.4874 32 34.6316 21.8881 31.2065 10.902C29.6842 6.11214 25.8785 2.27268 21.0834 0.790112C10.1231 -2.63119 0 5.54192 0 15.9959H6.20324C6.20324 9.45741 12.7109 4.36347 19.6372 6.87243C22.187 7.78478 24.2421 9.83756 25.1555 12.3845C27.6672 19.3032 22.6057 25.8036 16.0219 25.8036Z"/> + <path class="do-favicon" fill="#000" d="M9.85547 25.8417H16.0206V19.6834H9.85547V25.8417Z"/> + <path class="do-favicon" fill="#000" d="M9.85474 30.5934H5.09766V25.8417L9.85547 25.8417L9.85474 30.5934Z"/> + <path class="do-favicon" fill="#000" d="M1.10938 25.8417H5.06727V21.8882H1.10938V25.8417Z"/> +</svg> \ No newline at end of file diff --git a/gui/public/provider-icons/featherless.svg b/gui/public/provider-icons/featherless.svg new file mode 100644 index 0000000000..dd171d68e4 --- /dev/null +++ b/gui/public/provider-icons/featherless.svg @@ -0,0 +1,4 @@ +<svg viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg"> + <rect width="256" height="256" rx="30" fill="#FEF47A"/> + <path d="M166 69.0916C166 69.7501 165.65 70.3585 165.083 70.6877L119.453 97.1528C118.885 97.4819 118.536 98.0902 118.536 98.7485L118.53 126.989C118.53 128.408 120.059 129.295 121.282 128.586L163.239 104.251C164.462 103.542 165.991 104.429 165.991 105.847L165.99 134.092C165.99 134.75 165.641 135.359 165.073 135.688L119.439 162.156C118.872 162.485 118.522 163.093 118.522 163.751L118.512 215.155C118.511 216.573 116.983 217.46 115.76 216.75L112.729 214.993C112.161 214.663 111.812 214.055 111.812 213.396L111.815 200.136C111.815 199.477 111.465 198.868 110.897 198.539L90.9177 186.951C90.3501 186.622 90.0004 186.013 90.0004 185.355L90 157.11C90 155.691 91.5288 154.804 92.7518 155.514L109.07 164.978C110.293 165.688 111.821 164.801 111.822 163.383L111.822 134.074L111.824 134.072L111.824 134.067L91.3876 122.214C90.82 121.885 90.4704 121.276 90.4704 120.618L90.4708 92.3732C90.4708 90.9545 91.9995 90.0679 93.2225 90.7771L109.08 99.9737C110.303 100.683 111.832 99.7962 111.832 98.3775L111.831 70.1362C111.832 69.478 112.181 68.8697 112.749 68.5406L163.248 39.2497C164.471 38.5403 166 39.427 166 40.8457V69.0916Z" fill="#141413"/> +</svg> \ No newline at end of file diff --git a/gui/public/provider-icons/hyperbolic.svg b/gui/public/provider-icons/hyperbolic.svg new file mode 100644 index 0000000000..a71713acf5 --- /dev/null +++ b/gui/public/provider-icons/hyperbolic.svg @@ -0,0 +1,18 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32.000000 32.000000"><g transform="translate(0.000000,32.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"><path fill="#ececec" d="M73 244 c-4 -11 1 -22 12 -30 30 -22 37 -16 20 16 -18 34 -23 37 -32 +14z M215 230 c-8 -16 -13 -30 -10 -30 16 0 45 22 45 35 0 28 -19 25 -35 -5z +M84 160 c1 -26 2 -26 39 -17 23 6 51 6 75 0 38 -10 39 -9 39 17 0 26 -1 26 +-39 16 -26 -7 -50 -7 -76 0 -38 10 -39 10 -38 -16z M88 109 c-20 -11 -24 -34 +-8 -44 5 -3 16 8 25 25 16 31 12 36 -17 19z M215 90 c16 -30 35 -33 35 -5 0 +13 -29 35 -45 35 -3 0 2 -14 10 -30z"/><path fill="#c7c7c7" d="M60 231 c0 -6 4 -13 10 -16 6 -3 7 1 4 9 -7 18 -14 21 -14 7z M246 +223 c-6 -14 -5 -15 5 -6 7 7 10 15 7 18 -3 3 -9 -2 -12 -12z M138 173 c12 -2 +32 -2 45 0 12 2 2 4 -23 4 -25 0 -35 -2 -22 -4z M80 156 c0 -8 4 -17 9 -20 5 +-4 7 3 4 14 -6 23 -13 26 -13 6z M143 143 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 +0 -27 -2 -17 -5z"/><path fill="#3f3f3f" d="M121 194 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M191 +194 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M121 124 c0 -11 3 +-14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M191 124 c0 -11 3 -14 6 -6 3 7 2 +16 -1 19 -3 4 -6 -2 -5 -13z"/><path fill="#5c5c5c" d="M70 189 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 -5 +-10 -11z M235 190 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0 -7 +-4 -4 -10z M70 130 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0 +-4 -4 -4 -10z M235 130 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 +-3 0 -8 -4 -11 -10z"/></g></svg> \ No newline at end of file diff --git a/gui/public/provider-icons/kilo.svg b/gui/public/provider-icons/kilo.svg new file mode 100644 index 0000000000..c4b6bb8ba4 --- /dev/null +++ b/gui/public/provider-icons/kilo.svg @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32"> + <rect width="32" height="32" fill="oklch(95% 0.15 108)"/> + <g fill="#000000"> + <path d="M23,26v-2h3v-5l-2-2h-4v2h-3v5l2,2h4ZM20,20h3v3h-3v-3Z"/> + <rect x="12" y="17" width="3" height="3"/> + <polygon points="26 12 23 12 23 9 20 6 17 6 17 9 20 9 20 12 17 12 17 15 26 15 26 12"/> + <path d="M0,0v32h32V0H0ZM29,29H3V3h26v26Z"/> + <polygon points="15 26 15 23 9 23 9 17 6 17 6 23.1875 8.8125 26 15 26"/> + <rect x="12" y="6" width="3" height="3"/> + <polygon points="9 12 12 12 12 15 15 15 15 12 12 9 9 9 9 6 6 6 6 15 9 15 9 12"/> + </g> +</svg> \ No newline at end of file diff --git a/gui/public/provider-icons/litellm.svg b/gui/public/provider-icons/litellm.svg new file mode 100644 index 0000000000..9db6d0d066 --- /dev/null +++ b/gui/public/provider-icons/litellm.svg @@ -0,0 +1 @@ +<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#FFF" d="M99 52h84v34H99z"/><path d="M23 163c-7.398 0-13.843-4.027-17.303-10A19.886 19.886 0 0 0 3 163c0 11.046 8.954 20 20 20h20v-20H23z" fill="#3ECC5F"/><path d="M112.98 57.376L183 53V43c0-11.046-8.954-20-20-20H73l-2.5-4.33c-1.112-1.925-3.889-1.925-5 0L63 23l-2.5-4.33c-1.111-1.925-3.889-1.925-5 0L53 23l-2.5-4.33c-1.111-1.925-3.889-1.925-5 0L43 23c-.022 0-.042.003-.065.003l-4.142-4.141c-1.57-1.571-4.252-.853-4.828 1.294l-1.369 5.104-5.192-1.392c-2.148-.575-4.111 1.389-3.535 3.536l1.39 5.193-5.102 1.367c-2.148.576-2.867 3.259-1.296 4.83l4.142 4.142c0 .021-.003.042-.003.064l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 53l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 63l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 73l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 83l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 93l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 103l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 113l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 123l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 133l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 143l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 153l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 163c0 11.046 8.954 20 20 20h120c11.046 0 20-8.954 20-20V83l-70.02-4.376A10.645 10.645 0 0 1 103 68c0-5.621 4.37-10.273 9.98-10.624" fill="#3ECC5F"/><path fill="#3ECC5F" d="M143 183h30v-40h-30z"/><path d="M193 158c-.219 0-.428.037-.639.064-.038-.15-.074-.301-.116-.451A5 5 0 0 0 190.32 148a4.96 4.96 0 0 0-3.016 1.036 26.531 26.531 0 0 0-.335-.336 4.955 4.955 0 0 0 1.011-2.987 5 5 0 0 0-9.599-1.959c-.148-.042-.297-.077-.445-.115.027-.211.064-.42.064-.639a5 5 0 0 0-5-5 5 5 0 0 0-5 5c0 .219.037.428.064.639-.148.038-.297.073-.445.115a4.998 4.998 0 0 0-9.599 1.959c0 1.125.384 2.151 1.011 2.987-3.717 3.632-6.031 8.693-6.031 14.3 0 11.046 8.954 20 20 20 9.339 0 17.16-6.41 19.361-15.064.211.027.42.064.639.064a5 5 0 0 0 5-5 5 5 0 0 0-5-5" fill="#44D860"/><path fill="#3ECC5F" d="M153 123h30v-20h-30z"/><path d="M193 115.5a2.5 2.5 0 1 0 0-5c-.109 0-.214.019-.319.032-.02-.075-.037-.15-.058-.225a2.501 2.501 0 0 0-.963-4.807c-.569 0-1.088.197-1.508.518a6.653 6.653 0 0 0-.168-.168c.314-.417.506-.931.506-1.494a2.5 2.5 0 0 0-4.8-.979A9.987 9.987 0 0 0 183 103c-5.522 0-10 4.478-10 10s4.478 10 10 10c.934 0 1.833-.138 2.69-.377a2.5 2.5 0 0 0 4.8-.979c0-.563-.192-1.077-.506-1.494.057-.055.113-.111.168-.168.42.321.939.518 1.508.518a2.5 2.5 0 0 0 .963-4.807c.021-.074.038-.15.058-.225.105.013.21.032.319.032" fill="#44D860"/><path d="M63 55.5a2.5 2.5 0 0 1-2.5-2.5c0-4.136-3.364-7.5-7.5-7.5s-7.5 3.364-7.5 7.5a2.5 2.5 0 1 1-5 0c0-6.893 5.607-12.5 12.5-12.5S65.5 46.107 65.5 53a2.5 2.5 0 0 1-2.5 2.5" fill="#000"/><path d="M103 183h60c11.046 0 20-8.954 20-20V93h-60c-11.046 0-20 8.954-20 20v70z" fill="#FFFF50"/><path d="M168.02 124h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 20h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 20h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0-49.814h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 19.814h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 20h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2M183 61.611c-.012 0-.022-.006-.034-.005-3.09.105-4.552 3.196-5.842 5.923-1.346 2.85-2.387 4.703-4.093 4.647-1.889-.068-2.969-2.202-4.113-4.46-1.314-2.594-2.814-5.536-5.963-5.426-3.046.104-4.513 2.794-5.807 5.167-1.377 2.528-2.314 4.065-4.121 3.994-1.927-.07-2.951-1.805-4.136-3.813-1.321-2.236-2.848-4.75-5.936-4.664-2.994.103-4.465 2.385-5.763 4.4-1.373 2.13-2.335 3.428-4.165 3.351-1.973-.07-2.992-1.51-4.171-3.177-1.324-1.873-2.816-3.993-5.895-3.89-2.928.1-4.399 1.97-5.696 3.618-1.232 1.564-2.194 2.802-4.229 2.724a1 1 0 0 0-.072 2c3.017.101 4.545-1.8 5.872-3.487 1.177-1.496 2.193-2.787 4.193-2.855 1.926-.082 2.829 1.115 4.195 3.045 1.297 1.834 2.769 3.914 5.731 4.021 3.103.104 4.596-2.215 5.918-4.267 1.182-1.834 2.202-3.417 4.15-3.484 1.793-.067 2.769 1.35 4.145 3.681 1.297 2.197 2.766 4.686 5.787 4.796 3.125.108 4.634-2.62 5.949-5.035 1.139-2.088 2.214-4.06 4.119-4.126 1.793-.042 2.728 1.595 4.111 4.33 1.292 2.553 2.757 5.445 5.825 5.556l.169.003c3.064 0 4.518-3.075 5.805-5.794 1.139-2.41 2.217-4.68 4.067-4.773v-2z" fill="#000"/><path fill="#3ECC5F" d="M83 183h40v-40H83z"/><path d="M143 158c-.219 0-.428.037-.639.064-.038-.15-.074-.301-.116-.451A5 5 0 0 0 140.32 148a4.96 4.96 0 0 0-3.016 1.036 26.531 26.531 0 0 0-.335-.336 4.955 4.955 0 0 0 1.011-2.987 5 5 0 0 0-9.599-1.959c-.148-.042-.297-.077-.445-.115.027-.211.064-.42.064-.639a5 5 0 0 0-5-5 5 5 0 0 0-5 5c0 .219.037.428.064.639-.148.038-.297.073-.445.115a4.998 4.998 0 0 0-9.599 1.959c0 1.125.384 2.151 1.011 2.987-3.717 3.632-6.031 8.693-6.031 14.3 0 11.046 8.954 20 20 20 9.339 0 17.16-6.41 19.361-15.064.211.027.42.064.639.064a5 5 0 0 0 5-5 5 5 0 0 0-5-5" fill="#44D860"/><path fill="#3ECC5F" d="M83 123h40v-20H83z"/><path d="M133 115.5a2.5 2.5 0 1 0 0-5c-.109 0-.214.019-.319.032-.02-.075-.037-.15-.058-.225a2.501 2.501 0 0 0-.963-4.807c-.569 0-1.088.197-1.508.518a6.653 6.653 0 0 0-.168-.168c.314-.417.506-.931.506-1.494a2.5 2.5 0 0 0-4.8-.979A9.987 9.987 0 0 0 123 103c-5.522 0-10 4.478-10 10s4.478 10 10 10c.934 0 1.833-.138 2.69-.377a2.5 2.5 0 0 0 4.8-.979c0-.563-.192-1.077-.506-1.494.057-.055.113-.111.168-.168.42.321.939.518 1.508.518a2.5 2.5 0 0 0 .963-4.807c.021-.074.038-.15.058-.225.105.013.21.032.319.032" fill="#44D860"/><path d="M143 41.75c-.16 0-.33-.02-.49-.05a2.52 2.52 0 0 1-.47-.14c-.15-.06-.29-.14-.431-.23-.13-.09-.259-.2-.38-.31-.109-.12-.219-.24-.309-.38s-.17-.28-.231-.43a2.619 2.619 0 0 1-.189-.96c0-.16.02-.33.05-.49.03-.16.08-.31.139-.47.061-.15.141-.29.231-.43.09-.13.2-.26.309-.38.121-.11.25-.22.38-.31.141-.09.281-.17.431-.23.149-.06.31-.11.47-.14.32-.07.65-.07.98 0 .159.03.32.08.47.14.149.06.29.14.43.23.13.09.259.2.38.31.11.12.22.25.31.38.09.14.17.28.23.43.06.16.11.31.14.47.029.16.05.33.05.49 0 .66-.271 1.31-.73 1.77-.121.11-.25.22-.38.31-.14.09-.281.17-.43.23a2.565 2.565 0 0 1-.96.19m20-1.25c-.66 0-1.3-.27-1.771-.73a3.802 3.802 0 0 1-.309-.38c-.09-.14-.17-.28-.231-.43a2.619 2.619 0 0 1-.189-.96c0-.66.27-1.3.729-1.77.121-.11.25-.22.38-.31.141-.09.281-.17.431-.23.149-.06.31-.11.47-.14.32-.07.66-.07.98 0 .159.03.32.08.47.14.149.06.29.14.43.23.13.09.259.2.38.31.459.47.73 1.11.73 1.77 0 .16-.021.33-.05.49-.03.16-.08.32-.14.47-.07.15-.14.29-.23.43-.09.13-.2.26-.31.38-.121.11-.25.22-.38.31-.14.09-.281.17-.43.23a2.565 2.565 0 0 1-.96.19" fill="#000"/></g></svg> \ No newline at end of file diff --git a/gui/public/provider-icons/nanogpt.svg b/gui/public/provider-icons/nanogpt.svg new file mode 100644 index 0000000000..4344909a8b --- /dev/null +++ b/gui/public/provider-icons/nanogpt.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 181.45 186.88"> + <defs> + <style> + .cls-1 { + fill: url(#linear-gradient-8); + } + .cls-2 { + fill: url(#linear-gradient-7); + } + .cls-3 { + fill: url(#linear-gradient-5); + } + .cls-4 { + fill: url(#linear-gradient-6); + } + .cls-5 { + fill: url(#linear-gradient-9); + } + .cls-6 { + fill: url(#linear-gradient-4); + } + .cls-7 { + fill: url(#linear-gradient-3); + } + .cls-8 { + fill: url(#linear-gradient-2); + } + .cls-9 { + fill: url(#linear-gradient); + } + .cls-10 { + fill: url(#linear-gradient-10); + } + .cls-11 { + fill: url(#linear-gradient-11); + } + .cls-12 { + fill: url(#linear-gradient-12); + } + </style> + <linearGradient id="linear-gradient" x1="43.86" y1="128.24" x2="145.31" y2="128.24" gradientUnits="userSpaceOnUse"> + <stop offset="0" stop-color="#015a9e"/> + <stop offset="1" stop-color="#11e9bb"/> + </linearGradient> + <linearGradient id="linear-gradient-2" x1="44.1" y1="49.57" x2="146.51" y2="49.57" xlink:href="#linear-gradient"/> + <linearGradient id="linear-gradient-3" x1="2.6" y1="69.86" x2="53.68" y2="69.86" xlink:href="#linear-gradient"/> + <linearGradient id="linear-gradient-4" x1="109.82" y1="118.94" x2="178.89" y2="118.94" xlink:href="#linear-gradient"/> + <linearGradient id="linear-gradient-5" x1="129.51" y1="46.47" x2="173.61" y2="46.47" xlink:href="#linear-gradient"/> + <linearGradient id="linear-gradient-6" x1="12.44" y1="122.84" x2="65.92" y2="122.84" xlink:href="#linear-gradient"/> + <linearGradient id="linear-gradient-7" x1="46.28" y1="157.04" x2="128.11" y2="15.32" xlink:href="#linear-gradient"/> + <linearGradient id="linear-gradient-8" x1="46.28" y1="157.04" x2="128.11" y2="15.32" xlink:href="#linear-gradient"/> + <linearGradient id="linear-gradient-9" x1="46.28" y1="157.04" x2="128.11" y2="15.32" xlink:href="#linear-gradient"/> + <linearGradient id="linear-gradient-10" x1="46.28" y1="157.04" x2="128.11" y2="15.32" xlink:href="#linear-gradient"/> + <linearGradient id="linear-gradient-11" x1="46.28" y1="157.04" x2="128.11" y2="15.32" xlink:href="#linear-gradient"/> + <linearGradient id="linear-gradient-12" x1="46.28" y1="157.04" x2="128.11" y2="15.32" xlink:href="#linear-gradient"/> + </defs> + <g> + <polygon class="cls-9" points="145.31 72.24 93.39 184.24 43.86 103.66 145.31 72.24"/> + <polyline class="cls-8" points="106.21 2.68 146.51 64.44 44.1 96.46 70.86 27.88"/> + <polygon class="cls-7" points="30.06 42.79 53.68 52.46 36.31 96.93 2.6 78.59 30.06 42.79"/> + <polygon class="cls-6" points="109.82 166.73 153.7 71.14 178.89 81.47 109.82 166.73"/> + <polygon class="cls-3" points="173.61 71.76 154.18 63.48 129.51 25.14 134.6 21.19 173.61 71.76"/> + <polygon class="cls-4" points="65.92 153.78 12.44 91.91 34.91 104.09 35.83 104.85 65.92 153.78"/> + </g> + <g> + <polygon class="cls-2" points="145.31 72.24 93.39 184.24 43.86 103.66 145.31 72.24"/> + <polyline class="cls-1" points="106.21 2.68 146.51 64.44 44.1 96.46 70.86 27.88"/> + <polygon class="cls-5" points="30.06 42.79 53.68 52.46 36.31 96.93 2.6 78.59 30.06 42.79"/> + <polygon class="cls-10" points="109.82 166.73 153.7 71.14 178.89 81.47 109.82 166.73"/> + <polygon class="cls-11" points="173.61 71.76 154.18 63.48 129.51 25.14 134.6 21.19 173.61 71.76"/> + <polygon class="cls-12" points="65.92 153.78 12.44 91.91 34.91 104.09 35.83 104.85 65.92 153.78"/> + </g> +</svg> \ No newline at end of file diff --git a/gui/public/provider-icons/nebius.svg b/gui/public/provider-icons/nebius.svg new file mode 100644 index 0000000000..4d55b7bf0b --- /dev/null +++ b/gui/public/provider-icons/nebius.svg @@ -0,0 +1 @@ +<svg fill="none" height="96" viewBox="0 0 96 96" width="96" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h96v96h-96z"/></clipPath><mask id="b" height="96" maskUnits="userSpaceOnUse" width="96" x="0" y="0"><path d="m96 0h-96v96h96z" fill="#fff"/></mask><g clip-path="url(#a)"><g mask="url(#b)"><path d="m96 0h-96v96h96z" fill="#daff33"/><path clip-rule="evenodd" d="m46.3331 27.4668 12.243 33.8335c4.0549 9.1563 9.2098 6.3939 9.2098 6.3939 0 4.0685-3.2659 7.8683-8.0338 7.8683-4.7678 0-7.4679-2.9684-9.5229-7.5796l-12.2487-33.8336c-4.0536-8.8496-9.7663-6.0873-9.8787-6.032.0607-.0018.0929-.0018.0929-.0018l.0083 44.5261h-10.0072l-.0083-34.6309c0-9.3288 8.8962-9.8628 9.9143-9.8934.0004-4.068 3.2587-7.8673 8.0321-7.8673 4.7737 0 8.1385 2.5999 10.1992 7.2168zm21.464-4.2986.0082 44.526s10.0072 0 10.0072-9.8951l-.0082-34.6309z" fill="#052b42" fill-rule="evenodd"/></g></g></svg> \ No newline at end of file diff --git a/gui/public/provider-icons/neuralwatt.svg b/gui/public/provider-icons/neuralwatt.svg new file mode 100644 index 0000000000..3339f995c7 --- /dev/null +++ b/gui/public/provider-icons/neuralwatt.svg @@ -0,0 +1,27 @@ +<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M29.0063 32C30.6426 32 31.9691 30.6735 31.9691 29.0372C31.9691 27.4008 30.6426 26.0743 29.0063 26.0743C27.37 26.0743 26.0435 27.4008 26.0435 29.0372C26.0435 30.6735 27.37 32 29.0063 32Z" fill="#081A17"/> +<path d="M2.96281 5.92563C4.59913 5.92563 5.92563 4.59913 5.92563 2.96281C5.92563 1.3265 4.59913 0 2.96281 0C1.3265 0 0 1.3265 0 2.96281C0 4.59913 1.3265 5.92563 2.96281 5.92563Z" fill="#081A17"/> +<path d="M16.0179 18.9523C17.6534 18.9523 18.9792 17.6264 18.9792 15.991C18.9792 14.3555 17.6534 13.0297 16.0179 13.0297C14.3825 13.0297 13.0566 14.3555 13.0566 15.991C13.0566 17.6264 14.3825 18.9523 16.0179 18.9523Z" fill="#081A17"/> +<path d="M2.96243 12.1157C4.43511 12.1157 5.62896 10.9218 5.62896 9.44912C5.62896 7.97644 4.43511 6.78259 2.96243 6.78259C1.48975 6.78259 0.295898 7.97644 0.295898 9.44912C0.295898 10.9218 1.48975 12.1157 2.96243 12.1157Z" fill="#081A17"/> +<path d="M9.49905 12.1157C10.9717 12.1157 12.1656 10.9218 12.1656 9.44912C12.1656 7.97644 10.9717 6.78259 9.49905 6.78259C8.02637 6.78259 6.83252 7.97644 6.83252 9.44912C6.83252 10.9218 8.02637 12.1157 9.49905 12.1157Z" fill="#081A17"/> +<path d="M29.0064 25.199C30.4791 25.199 31.6729 24.0052 31.6729 22.5325C31.6729 21.0598 30.4791 19.866 29.0064 19.866C27.5337 19.866 26.3398 21.0598 26.3398 22.5325C26.3398 24.0052 27.5337 25.199 29.0064 25.199Z" fill="#081A17"/> +<path d="M22.5347 25.199C24.0074 25.199 25.2012 24.0052 25.2012 22.5325C25.2012 21.0598 24.0074 19.866 22.5347 19.866C21.062 19.866 19.8682 21.0598 19.8682 22.5325C19.8682 24.0052 21.062 25.199 22.5347 25.199Z" fill="#081A17"/> +<path d="M29.0064 18.3567C30.3146 18.3567 31.3752 17.2962 31.3752 15.988C31.3752 14.6798 30.3146 13.6193 29.0064 13.6193C27.6982 13.6193 26.6377 14.6798 26.6377 15.988C26.6377 17.2962 27.6982 18.3567 29.0064 18.3567Z" fill="#081A17"/> +<path d="M29.0064 12.11C30.3146 12.11 31.3752 11.0495 31.3752 9.74129C31.3752 8.43307 30.3146 7.37256 29.0064 7.37256C27.6982 7.37256 26.6377 8.43307 26.6377 9.74129C26.6377 11.0495 27.6982 12.11 29.0064 12.11Z" fill="#081A17"/> +<path d="M29.0064 5.33146C30.3146 5.33146 31.3752 4.27094 31.3752 2.96272C31.3752 1.65451 30.3146 0.593994 29.0064 0.593994C27.6982 0.593994 26.6377 1.65451 26.6377 2.96272C26.6377 4.27094 27.6982 5.33146 29.0064 5.33146Z" fill="#081A17"/> +<path d="M2.96297 18.298C4.27118 18.298 5.3317 17.2375 5.3317 15.9293C5.3317 14.6211 4.27118 13.5605 2.96297 13.5605C1.65476 13.5605 0.594238 14.6211 0.594238 15.9293C0.594238 17.2375 1.65476 18.298 2.96297 18.298Z" fill="#081A17"/> +<path d="M2.96297 24.9012C4.27118 24.9012 5.3317 23.8406 5.3317 22.5324C5.3317 21.2242 4.27118 20.1637 2.96297 20.1637C1.65476 20.1637 0.594238 21.2242 0.594238 22.5324C0.594238 23.8406 1.65476 24.9012 2.96297 24.9012Z" fill="#081A17"/> +<path d="M2.96297 31.3483C4.27118 31.3483 5.3317 30.2878 5.3317 28.9796C5.3317 27.6714 4.27118 26.6108 2.96297 26.6108C1.65476 26.6108 0.594238 27.6714 0.594238 28.9796C0.594238 30.2878 1.65476 31.3483 2.96297 31.3483Z" fill="#081A17"/> +<path opacity="0.5" d="M9.4988 17.8826C10.5771 17.8826 11.4512 17.0085 11.4512 15.9302C11.4512 14.8519 10.5771 13.9778 9.4988 13.9778C8.42051 13.9778 7.54639 14.8519 7.54639 15.9302C7.54639 17.0085 8.42051 17.8826 9.4988 17.8826Z" fill="#081A17"/> +<path opacity="0.5" d="M22.5349 17.9418C23.6123 17.9418 24.4858 17.0684 24.4858 15.9909C24.4858 14.9135 23.6123 14.04 22.5349 14.04C21.4574 14.04 20.584 14.9135 20.584 15.9909C20.584 17.0684 21.4574 17.9418 22.5349 17.9418Z" fill="#081A17"/> +<path opacity="0.5" d="M16.0182 11.4025C17.0646 11.4025 17.9129 10.5542 17.9129 9.50784C17.9129 8.46144 17.0646 7.61316 16.0182 7.61316C14.9718 7.61316 14.1235 8.46144 14.1235 9.50784C14.1235 10.5542 14.9718 11.4025 16.0182 11.4025Z" fill="#081A17"/> +<path opacity="0.5" d="M16.0177 24.4256C17.0632 24.4256 17.9108 23.578 17.9108 22.5324C17.9108 21.4869 17.0632 20.6393 16.0177 20.6393C14.9721 20.6393 14.1245 21.4869 14.1245 22.5324C14.1245 23.578 14.9721 24.4256 16.0177 24.4256Z" fill="#081A17"/> +<path opacity="0.5" d="M9.49903 4.67762C10.4456 4.67762 11.2129 3.9103 11.2129 2.96375C11.2129 2.0172 10.4456 1.24988 9.49903 1.24988C8.55248 1.24988 7.78516 2.0172 7.78516 2.96375C7.78516 3.9103 8.55248 4.67762 9.49903 4.67762Z" fill="#081A17"/> +<path opacity="0.5" d="M22.5351 30.7495C23.4808 30.7495 24.2475 29.9829 24.2475 29.0372C24.2475 28.0915 23.4808 27.3248 22.5351 27.3248C21.5894 27.3248 20.8228 28.0915 20.8228 29.0372C20.8228 29.9829 21.5894 30.7495 22.5351 30.7495Z" fill="#081A17"/> +<path opacity="0.3" d="M22.535 10.9847C23.3506 10.9847 24.0118 10.3235 24.0118 9.50785C24.0118 8.69221 23.3506 8.03101 22.535 8.03101C21.7193 8.03101 21.0581 8.69221 21.0581 9.50785C21.0581 10.3235 21.7193 10.9847 22.535 10.9847Z" fill="#081A17"/> +<path opacity="0.3" d="M9.49877 23.9502C10.3136 23.9502 10.9741 23.2896 10.9741 22.4748C10.9741 21.66 10.3136 20.9995 9.49877 20.9995C8.68396 20.9995 8.02344 21.66 8.02344 22.4748C8.02344 23.2896 8.68396 23.9502 9.49877 23.9502Z" fill="#081A17"/> +<path opacity="0.3" d="M16.0179 30.0384C16.5709 30.0384 17.0192 29.5902 17.0192 29.0372C17.0192 28.4842 16.5709 28.0359 16.0179 28.0359C15.4649 28.0359 15.0166 28.4842 15.0166 29.0372C15.0166 29.5902 15.4649 30.0384 16.0179 30.0384Z" fill="#081A17"/> +<path opacity="0.3" d="M16.0178 4.02273C16.5691 4.02273 17.016 3.5758 17.016 3.02449C17.016 2.47317 16.5691 2.02625 16.0178 2.02625C15.4665 2.02625 15.0195 2.47317 15.0195 3.02449C15.0195 3.5758 15.4665 4.02273 16.0178 4.02273Z" fill="#081A17"/> +<path opacity="0.1" d="M9.49903 29.5646C9.8221 29.5646 10.084 29.3027 10.084 28.9796C10.084 28.6565 9.8221 28.3947 9.49903 28.3947C9.17596 28.3947 8.91406 28.6565 8.91406 28.9796C8.91406 29.3027 9.17596 29.5646 9.49903 29.5646Z" fill="#081A17"/> +<path opacity="0.1" d="M22.5345 3.59856C22.8551 3.59856 23.1149 3.3387 23.1149 3.01815C23.1149 2.6976 22.8551 2.43774 22.5345 2.43774C22.214 2.43774 21.9541 2.6976 21.9541 3.01815C21.9541 3.3387 22.214 3.59856 22.5345 3.59856Z" fill="#081A17"/> +</svg> \ No newline at end of file diff --git a/gui/public/provider-icons/nous.svg b/gui/public/provider-icons/nous.svg new file mode 100644 index 0000000000..5307d5feb2 --- /dev/null +++ b/gui/public/provider-icons/nous.svg @@ -0,0 +1,149 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" + "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<svg version="1.0" xmlns="http://www.w3.org/2000/svg" + width="256.000000pt" height="256.000000pt" viewBox="0 0 256.000000 256.000000" + preserveAspectRatio="xMidYMid meet"> +<metadata> +Created by potrace 1.16, written by Peter Selinger 2001-2019 +</metadata> +<g transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"> +<path d="M15 1280 l0 -1280 1265 0 1265 0 0 1280 0 1280 -1265 0 -1265 0 0 +-1280z m2505 0 l0 -1250 -550 0 -550 0 0 21 c0 33 216 244 259 252 42 9 169 9 +209 1 39 -8 192 -153 192 -183 0 -5 6 -14 14 -21 8 -6 12 -18 9 -26 -3 -8 1 +-14 11 -14 10 0 16 9 16 23 0 74 -131 202 -255 249 -12 5 1 6 30 3 63 -5 102 +-24 146 -72 36 -39 51 -61 70 -103 6 -14 15 -33 20 -42 5 -10 9 -25 9 -34 0 +-31 25 -7 31 30 6 40 21 54 86 87 49 24 153 122 153 145 0 8 6 17 14 20 8 3 +17 22 21 42 4 20 9 41 12 45 7 12 6 159 -2 182 -15 44 -47 105 -56 105 -12 0 +-12 -37 1 -45 13 -8 13 -162 1 -169 -5 -4 -12 -24 -16 -47 -4 -22 -13 -43 -20 +-46 -8 -2 -17 -17 -20 -33 -3 -16 -13 -31 -21 -34 -8 -3 -14 -11 -14 -19 0 -7 +-23 -35 -50 -62 -31 -30 -50 -43 -50 -34 0 9 9 24 20 34 11 10 20 24 20 32 0 +7 4 13 9 13 15 0 53 89 59 136 2 25 9 47 13 50 12 7 12 201 0 208 -4 3 -11 37 +-15 76 -4 38 -11 70 -15 70 -5 0 -12 25 -16 55 -3 30 -11 57 -16 61 -5 3 -7 +10 -3 15 3 5 1 15 -4 22 -5 7 -13 31 -17 54 -4 24 -10 45 -15 48 -4 3 -11 31 +-15 62 -4 32 -11 64 -17 70 -6 7 -8 19 -5 28 4 8 2 17 -3 20 -5 3 -12 31 -16 +63 -3 31 -10 62 -14 67 -5 6 -11 40 -15 78 -4 37 -11 67 -15 67 -4 0 -11 32 +-15 70 -4 39 -11 72 -16 75 -4 3 -6 12 -2 20 3 8 1 15 -5 15 -5 0 -12 19 -15 +41 -3 23 -9 45 -14 48 -12 9 -41 72 -49 106 -13 55 -70 145 -93 145 -10 0 -16 +-40 -13 -85 2 -15 0 -17 -7 -6 -14 22 -33 1 -28 -32 l4 -31 -18 22 c-16 20 +-18 21 -22 5 -3 -15 -6 -13 -17 10 -12 24 -15 25 -29 13 -14 -12 -22 -12 -55 +-1 -37 13 -70 10 -49 -4 6 -3 13 -19 17 -34 l7 -29 -31 24 c-46 34 -73 48 -92 +48 -10 0 -18 5 -18 10 0 6 16 10 36 10 36 0 36 1 30 32 l-6 31 24 -26 c31 -34 +87 -45 106 -21 8 9 15 24 15 33 0 9 10 17 23 19 17 3 21 8 17 23 -8 23 2 24 +26 3 26 -24 42 -8 36 35 l-6 36 28 -32 c43 -52 48 -38 5 15 -59 72 -160 162 +-183 162 -6 0 -11 4 -11 10 0 11 -100 60 -123 60 -10 0 -17 4 -17 10 0 5 -17 +12 -37 16 -21 4 -43 10 -49 15 -6 5 -55 14 -109 20 -112 13 -315 -2 -363 -27 +-25 -13 -21 -14 66 -14 54 0 92 -4 92 -10 0 -5 7 -10 17 -10 45 0 132 -56 211 +-137 45 -46 82 -88 82 -94 0 -6 11 -25 24 -42 23 -30 47 -79 50 -102 2 -9 14 +-38 39 -92 16 -34 15 -145 -1 -151 -11 -3 -11 0 -1 16 18 29 5 52 -30 52 -16 +0 -33 -4 -36 -10 -3 -5 -15 -10 -26 -10 -22 0 -26 -28 -4 -36 8 -4 15 -16 15 +-28 -1 -19 -4 -17 -30 13 -21 24 -28 43 -26 63 2 16 0 30 -4 33 -4 2 -10 22 +-14 43 -8 55 -30 94 -44 80 -16 -16 -40 -3 -55 29 -32 72 -67 84 -67 23 0 -33 +-3 -40 -19 -40 -11 0 -22 -6 -25 -12 -5 -10 -7 -10 -12 0 -3 6 -21 12 -39 12 +-52 0 -55 38 -8 86 10 10 19 22 19 28 1 6 2 15 3 20 0 6 7 1 15 -9 13 -18 15 +-18 40 -1 22 14 30 15 42 5 8 -6 14 -18 14 -26 0 -7 6 -16 14 -19 8 -3 18 -19 +21 -35 4 -16 13 -29 20 -29 7 0 20 -3 29 -6 11 -4 16 -1 16 10 0 9 -4 16 -8 +16 -4 0 -13 12 -20 28 -24 53 -43 81 -67 100 -14 11 -25 25 -25 31 0 7 -8 15 +-17 18 -10 4 -26 17 -36 30 -18 22 -47 41 -99 65 -16 7 -28 16 -28 20 0 4 -17 +10 -38 14 -20 4 -41 10 -47 14 -16 12 -162 12 -170 1 -3 -6 -18 -11 -33 -11 +-15 0 -67 -20 -117 -46 -113 -57 -126 -64 -181 -109 -18 -14 -34 -25 -37 -25 +-20 0 -157 -157 -157 -180 0 -4 -3 -10 -7 -12 -11 -4 -63 -114 -63 -132 0 -8 +-4 -17 -9 -21 -6 -3 -13 -26 -17 -50 -3 -25 -10 -48 -15 -51 -11 -7 -12 -179 +0 -202 4 -9 17 -35 27 -57 24 -50 67 -94 119 -120 22 -11 42 -23 43 -27 2 -5 +8 -8 13 -8 17 0 9 22 -21 57 -17 18 -30 41 -29 51 0 15 1 15 12 -2 26 -41 60 +-76 73 -76 11 0 14 -18 14 -83 0 -46 -3 -87 -6 -90 -4 -4 -7 -36 -6 -72 0 -36 +-3 -65 -7 -65 -5 0 -11 -51 -15 -112 -4 -62 -11 -115 -15 -118 -4 -3 -11 -32 +-15 -65 -4 -33 -11 -61 -16 -63 -6 -2 -10 -9 -10 -16 0 -8 -9 -25 -21 -40 -17 +-22 -29 -26 -68 -26 -49 0 -68 13 -97 66 -13 23 -13 119 1 149 8 20 8 20 -13 +1 -12 -11 -22 -25 -22 -32 0 -7 -6 -14 -13 -17 -20 -6 -17 -190 4 -213 9 -11 +19 -27 22 -36 4 -10 13 -18 20 -18 8 0 20 -7 27 -15 7 -8 22 -15 34 -15 12 0 +27 -5 33 -11 8 -8 13 -8 17 0 4 6 18 11 31 11 49 0 151 73 168 120 3 9 6 4 6 +-11 1 -15 -8 -38 -19 -52 -11 -14 -20 -28 -20 -31 0 -15 -55 -58 -81 -63 -16 +-3 -32 -10 -35 -14 -7 -12 -54 -12 -54 0 0 5 -13 11 -30 14 -16 4 -42 14 -56 +24 -37 24 -84 91 -84 120 0 12 -4 23 -10 23 -12 0 -13 -54 -1 -78 4 -9 16 -34 +26 -54 21 -43 67 -98 82 -98 6 0 16 -6 22 -14 7 -8 28 -17 48 -21 36 -6 65 +-27 61 -42 -4 -19 33 -85 52 -90 11 -3 20 -10 20 -15 0 -6 7 -8 15 -5 8 4 17 +2 20 -3 8 -13 72 -13 80 0 3 5 12 7 20 3 8 -3 15 -1 15 4 0 6 11 15 25 21 14 +7 30 23 35 37 5 14 14 25 20 25 12 0 13 -32 2 -46 -5 -5 -15 -28 -22 -51 -7 +-24 -17 -43 -22 -43 -4 0 -8 -5 -8 -12 0 -7 -17 -29 -37 -50 l-37 -38 -248 0 +-248 0 0 1250 0 1250 1240 0 1240 0 0 -1250z m-1409 780 c34 -45 74 -128 66 +-136 -3 -4 -18 -6 -32 -6 -18 0 -25 -5 -21 -14 3 -7 1 -16 -4 -19 -5 -3 -11 4 +-15 15 -5 16 -12 19 -33 13 -15 -3 -31 -11 -35 -17 -6 -8 -10 -7 -13 1 -3 7 +-21 15 -40 19 -62 12 -38 74 30 74 16 0 27 7 31 21 4 12 12 18 21 15 16 -6 20 +40 5 64 -6 8 -7 18 -4 24 4 5 9 3 12 -5 3 -8 18 -30 32 -49z m-474 -20 c-7 +-19 -16 -41 -21 -49 -6 -10 -4 -12 10 -7 11 3 32 -1 48 -9 57 -30 88 -17 101 +43 11 53 22 37 23 -36 l1 -67 -71 -2 c-39 -1 -76 -7 -81 -12 -15 -15 -37 6 +-38 38 -2 23 -2 24 -6 4 -2 -13 -9 -23 -14 -23 -5 0 -9 -9 -9 -20 0 -11 -7 +-20 -15 -20 -8 0 -14 5 -13 11 5 18 -18 21 -35 4 -9 -8 -25 -15 -36 -15 -12 0 +-21 -6 -21 -14 0 -23 -48 -17 -53 8 -4 21 -5 21 -12 -1 -8 -26 -25 -31 -25 -8 +0 21 -46 19 -60 -2 -9 -14 -10 -6 -7 37 3 30 10 56 16 58 6 2 11 10 11 17 0 7 +18 28 40 45 22 17 40 27 40 21 0 -5 -7 -14 -16 -19 -20 -11 -10 -32 16 -32 21 +0 70 -41 70 -59 0 -24 19 -9 25 20 5 22 10 28 18 20 7 -7 6 -15 -3 -26 -7 -8 +-10 -18 -7 -21 9 -9 84 72 93 99 6 19 35 57 41 53 1 -1 -3 -17 -10 -36z m248 +-30 c23 0 33 -7 43 -27 24 -53 18 -63 -43 -63 -61 0 -75 13 -75 71 0 28 3 31 +23 25 12 -3 36 -6 52 -6z m-31 -255 c3 -22 68 -145 77 -145 5 0 9 -7 9 -15 0 +-8 -6 -15 -13 -15 -21 0 -44 31 -52 68 -3 17 -10 32 -15 32 -5 0 -12 21 -16 +48 -4 26 -11 50 -17 53 -5 4 -6 13 -2 20 6 9 11 6 18 -12 6 -13 11 -28 11 -34z +m779 -10 c31 -13 58 -35 43 -35 -11 0 -62 25 -74 37 -17 17 -12 16 31 -2z +m-128 -35 c-10 -11 -20 -18 -23 -15 -3 3 3 14 13 25 10 11 20 18 23 15 3 -3 +-3 -14 -13 -25z m345 -106 c0 -8 -4 -12 -10 -9 -5 3 -10 10 -10 16 0 5 5 9 10 +9 6 0 10 -7 10 -16z m-1152 -64 c7 0 12 -5 12 -11 0 -8 4 -8 13 -2 6 6 27 13 +45 16 l32 7 -16 -26 c-9 -13 -15 -27 -12 -29 6 -7 59 20 68 34 3 6 33 17 65 +24 46 9 59 9 63 -1 2 -8 33 -12 103 -12 64 0 98 4 94 10 -3 6 -1 10 4 10 6 0 +11 -4 11 -10 0 -5 9 -10 19 -10 11 0 23 -4 26 -10 3 -5 17 -10 30 -10 20 0 25 +-6 30 -44 8 -49 -13 -306 -25 -321 -4 -5 -11 -41 -15 -80 -4 -38 -11 -74 -15 +-80 -4 -5 -11 -45 -15 -88 -4 -42 -12 -80 -17 -83 -5 -3 -8 -59 -7 -125 1 +-118 1 -120 -25 -140 l-26 -21 -18 33 c-9 18 -38 56 -63 83 l-47 50 -68 -2 +c-38 -2 -73 -7 -78 -12 -17 -17 -122 -12 -154 7 -31 18 -31 17 -80 117 -18 36 +-36 66 -41 66 -5 0 -13 15 -17 32 -3 18 -14 44 -23 57 -12 19 -16 50 -16 133 +l0 107 38 38 c20 22 37 44 37 50 0 6 4 13 10 15 5 2 12 18 16 36 5 25 3 32 -9 +32 -9 0 -20 7 -25 17 -7 11 -24 16 -53 17 -36 1 -45 5 -47 21 -1 12 6 25 19 +33 18 12 24 11 45 -9 13 -12 26 -20 29 -17 3 3 -1 11 -10 18 -27 22 -18 47 20 +54 38 7 43 21 20 52 -30 40 -14 47 23 11 21 -20 43 -37 50 -37z m1187 -46 c3 +-20 4 -39 1 -42 -3 -3 -6 4 -6 16 0 11 -4 24 -10 27 -12 8 -13 35 -1 35 5 0 +12 -16 16 -36z m-212 -68 c18 -14 23 -36 8 -36 -6 0 -13 5 -16 10 -9 14 -88 +13 -103 -2 -16 -16 -25 -4 -17 21 5 18 14 21 58 21 29 0 60 -7 70 -14z m242 +-41 c6 -30 5 -37 -4 -29 -7 6 -14 28 -18 50 -8 53 13 33 22 -21z m-1346 -51 +c13 -51 9 -63 -14 -44 -16 13 -21 90 -6 90 5 0 14 -21 20 -46z m1371 -15 c0 +-5 -4 -9 -10 -9 -5 0 -10 7 -10 16 0 8 5 12 10 9 6 -3 10 -10 10 -16z m70 +-270 c0 -5 -4 -9 -10 -9 -5 0 -10 7 -10 16 0 8 5 12 10 9 6 -3 10 -10 10 -16z +m-542 -124 c-3 -14 -1 -28 3 -31 13 -8 11 -114 -2 -114 -6 0 -22 -9 -36 -20 +-34 -27 -53 -26 -46 3 3 12 9 27 13 32 4 6 11 26 15 45 4 19 14 44 21 55 8 11 +14 27 14 37 0 10 5 18 11 18 7 0 9 -10 7 -25z m722 -27 c7 -29 18 -56 23 -60 +15 -9 15 -216 1 -226 -6 -4 -17 -24 -25 -45 -7 -20 -26 -50 -42 -65 l-29 -27 +7 35 c4 19 11 40 16 46 5 7 12 41 16 78 3 36 10 66 14 66 5 0 9 41 9 90 0 53 +-4 90 -10 90 -5 0 -10 16 -10 35 0 52 16 42 30 -17z m-700 -208 c0 -11 5 -20 +10 -20 6 0 10 -12 10 -27 -1 -21 -10 -34 -37 -51 -43 -28 -83 -23 -100 13 -10 +23 -8 28 30 59 62 49 87 57 87 26z m-246 -204 c14 -34 79 -91 131 -116 17 -8 +32 -15 33 -16 5 -3 -31 -36 -54 -51 -48 -30 -201 -103 -216 -103 -10 0 -24 14 +-33 33 -16 34 -24 48 -67 110 -16 22 -28 43 -28 47 0 4 20 10 44 12 37 5 52 +14 102 65 32 33 62 58 67 56 4 -2 13 -19 21 -37z m-659 -112 c-42 -79 -71 +-104 -123 -104 -45 0 -82 14 -82 30 0 6 20 10 45 10 25 0 45 5 45 10 0 6 8 10 +19 10 21 0 91 73 91 95 0 8 5 15 10 15 6 0 11 8 11 18 1 9 4 5 6 -9 2 -15 -7 +-46 -22 -75z m1025 -49 c-6 -8 -21 -17 -33 -21 -25 -8 -97 -64 -97 -75 0 -4 +-20 -27 -44 -51 -36 -35 -46 -53 -53 -93 l-8 -50 -43 -3 c-37 -3 -42 -1 -42 +17 0 12 5 21 10 21 6 0 10 5 10 12 0 6 7 20 17 30 9 10 13 21 9 25 -3 4 -32 +-19 -64 -50 l-58 -57 -86 0 c-80 0 -86 1 -81 19 5 18 104 121 117 121 3 0 6 +-7 6 -15 0 -12 5 -11 32 3 18 9 36 21 39 25 8 11 36 27 116 67 34 17 63 35 65 +40 2 5 17 12 33 16 l30 7 -25 -28 -25 -28 55 29 c30 16 57 32 60 36 3 3 19 8 +35 11 17 2 32 5 34 6 2 0 -2 -6 -9 -14z m-580 -218 c-22 -38 -67 -83 -75 -75 +-3 2 11 23 30 45 19 23 35 46 35 52 0 6 7 11 15 11 12 0 11 -6 -5 -33z m-106 +-46 c10 -15 -18 -28 -64 -30 l-45 -1 35 19 c38 22 66 26 74 12z M873 1512 +c-40 -4 -61 -26 -43 -47 10 -12 10 -18 1 -29 -8 -10 -9 -16 -1 -21 6 -4 19 4 +30 17 l19 23 1 -22 c0 -12 5 -25 10 -28 6 -3 10 -15 10 -26 0 -22 34 -39 62 +-31 22 5 43 30 43 51 0 21 33 27 41 6 8 -20 -32 -65 -58 -65 -10 0 -27 -6 -38 +-12 -15 -10 -22 -10 -32 0 -14 14 -48 16 -48 2 0 -13 57 -22 103 -17 36 4 61 +21 123 83 l21 22 -63 36 c-35 20 -70 36 -78 36 -8 0 -17 4 -20 9 -7 11 -43 17 +-83 13z M550 1165 c7 -9 15 -13 17 -11 7 7 -7 26 -19 26 -6 0 -6 -6 2 -15z +M580 1030 c0 -11 4 -20 10 -20 5 0 13 -18 16 -39 7 -42 47 -91 59 -72 3 6 -1 +11 -9 11 -9 0 -16 2 -16 4 0 2 -3 12 -7 21 -5 14 -2 16 15 11 17 -6 21 -4 16 +8 -5 13 2 16 35 16 23 0 41 -4 41 -10 0 -5 9 -10 20 -10 11 0 20 7 20 15 0 8 +-7 15 -15 15 -9 0 -27 7 -40 16 -16 10 -34 14 -53 10 -22 -5 -33 -1 -52 19 +-28 30 -40 32 -40 5z M1160 415 c-10 -12 -10 -15 2 -15 17 0 48 -31 48 -47 0 +-7 -16 -3 -40 8 -46 22 -54 23 -68 6 -8 -9 -2 -20 19 -41 16 -16 29 -38 29 +-49 0 -15 5 -18 23 -13 12 3 26 6 32 6 5 0 -6 13 -25 29 -43 36 -34 51 13 22 +53 -32 103 -25 62 9 -8 7 -15 18 -15 25 0 7 -7 15 -15 19 -8 3 -15 17 -15 31 +0 29 -29 35 -50 10z"/> +</g> +</svg> diff --git a/gui/public/provider-icons/novita.svg b/gui/public/provider-icons/novita.svg new file mode 100644 index 0000000000..75f64f25a4 --- /dev/null +++ b/gui/public/provider-icons/novita.svg @@ -0,0 +1,32 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" + "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<svg version="1.0" xmlns="http://www.w3.org/2000/svg" + width="256.000000pt" height="256.000000pt" viewBox="0 0 256.000000 256.000000" + preserveAspectRatio="xMidYMid meet"> +<metadata> +Created by potrace 1.16, written by Peter Selinger 2001-2019 +</metadata> +<g transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"> +<path d="M964 2159 c-4 -7 -6 -137 -5 -288 1 -152 -1 -283 -4 -293 -4 -9 -19 +-21 -34 -28 -16 -6 -33 -22 -39 -35 -6 -13 -24 -31 -39 -39 -16 -8 -35 -26 +-43 -41 -8 -15 -27 -33 -43 -41 -15 -8 -33 -26 -39 -39 -6 -14 -15 -25 -21 +-25 -5 0 -30 -21 -57 -47 -66 -65 -71 -71 -100 -99 -14 -13 -34 -34 -45 -45 +-11 -12 -27 -27 -35 -35 -8 -7 -28 -26 -43 -41 -16 -16 -33 -32 -39 -38 -5 -5 +-32 -30 -58 -55 -26 -25 -53 -49 -58 -55 -6 -5 -25 -25 -44 -43 -75 -76 -94 +-92 -106 -92 -7 0 -18 -11 -24 -25 -9 -21 -9 -29 4 -42 14 -14 64 -15 429 -14 +289 2 417 6 427 13 11 9 13 61 11 260 -2 166 1 252 8 259 19 19 57 5 78 -28 +11 -18 28 -35 38 -38 9 -4 24 -20 32 -36 9 -16 29 -36 46 -44 16 -9 35 -27 41 +-40 6 -14 15 -25 21 -25 5 0 30 -21 57 -47 66 -65 71 -71 100 -99 14 -13 34 +-34 45 -45 21 -22 40 -40 79 -75 13 -11 25 -27 28 -35 4 -12 75 -15 459 -19 +418 -5 456 -4 473 12 17 15 18 21 8 43 -6 14 -17 25 -24 25 -12 0 -31 16 -105 +90 -51 52 -53 53 -103 100 -26 25 -53 50 -60 57 -6 6 -24 23 -38 37 -15 14 +-36 36 -47 47 -11 12 -27 28 -36 36 -15 13 -27 25 -79 76 -7 7 -34 32 -60 57 +-26 25 -53 50 -60 57 -6 6 -24 23 -38 37 -15 14 -36 36 -47 47 -11 12 -27 28 +-36 36 -15 14 -34 32 -77 75 -8 7 -36 33 -62 58 -26 25 -53 50 -58 55 -6 6 +-23 22 -39 37 -15 16 -37 38 -48 49 -11 12 -27 28 -36 36 -9 9 -28 26 -42 40 +-14 14 -31 30 -37 36 -7 7 -49 49 -95 95 -45 45 -88 82 -94 82 -6 0 -21 16 +-32 35 -11 19 -28 37 -37 40 -9 3 -26 20 -37 38 -21 34 -67 48 -81 26z"/> +</g> +</svg> diff --git a/gui/public/provider-icons/orcarouter.svg b/gui/public/provider-icons/orcarouter.svg new file mode 100644 index 0000000000..4cd5bf9f47 --- /dev/null +++ b/gui/public/provider-icons/orcarouter.svg @@ -0,0 +1,175 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160.000000 160.000000"><g transform="translate(0.000000,160.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"><path fill="#137bf7" d="M1189 1404 c-41 -62 -59 -101 -59 -128 0 -31 15 -42 16 -13 1 12 18 +50 38 85 43 72 47 119 5 56z M888 1227 c-37 -13 -71 -26 -75 -30 -13 -14 6 +-18 28 -6 70 37 199 50 248 25 36 -19 95 -85 123 -138 23 -46 32 -152 13 -166 +-6 -4 -19 -24 -29 -45 -14 -27 -34 -44 -73 -64 -52 -25 -57 -25 -111 -13 -74 +17 -84 27 -44 42 30 12 43 28 22 28 -20 0 -90 -32 -90 -42 0 -15 88 -49 144 +-55 99 -10 205 49 235 130 16 42 13 107 -8 152 -24 52 -105 141 -160 177 -57 +36 -128 37 -223 5z M1269 1132 c16 -21 35 -50 42 -65 7 -15 20 -27 30 -27 20 +0 43 -37 53 -86 9 -40 -16 -114 -45 -134 -13 -8 -38 -31 -56 -51 -50 -53 -164 +-104 -233 -102 -95 1 -221 60 -368 171 -113 86 -358 214 -371 194 -6 -10 -20 +-7 -62 12 -76 34 -104 39 -119 21 -10 -13 -9 -15 11 -15 39 0 69 -11 62 -23 +-3 -6 -1 -7 6 -3 6 4 48 -15 94 -42 45 -28 99 -57 120 -66 30 -12 37 -20 34 +-38 -2 -13 -2 -17 0 -11 12 34 91 20 150 -27 38 -30 75 -83 70 -101 -4 -15 18 +-28 153 -87 51 -23 162 -58 203 -65 49 -8 176 27 197 53 3 4 16 13 28 19 66 +35 137 121 162 195 9 28 15 35 21 25 13 -19 0 36 -19 81 -26 64 -83 139 -132 +175 -61 45 -69 44 -31 -3z M1140 1000 c0 -5 7 -7 15 -4 8 4 15 8 15 10 0 2 -7 +4 -15 4 -8 0 -15 -4 -15 -10z M826 498 c3 -7 46 -47 96 -88 155 -128 248 -223 +248 -253 0 -4 -10 -12 -22 -18 -18 -7 -10 -8 31 -5 81 8 89 22 47 77 -95 122 +-274 259 -383 293 -17 5 -21 4 -17 -6z M279 393 c-37 -59 -52 -107 -47 -153 3 +-34 4 -35 51 -38 28 -2 47 1 47 7 0 6 -7 11 -15 11 -8 0 -22 11 -31 25 -21 32 +-15 97 13 155 27 54 19 51 -18 -7z M1098 133 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 +-11 0 -14 -3 -6 -6z"/><path fill="#054ecf" d="M1183 1347 c-37 -66 -47 -93 -35 -101 5 -3 9 1 9 8 0 12 41 76 56 87 +5 3 7 18 5 34 -3 25 -5 23 -35 -28z M907 1215 c-55 -15 -78 -31 -32 -21 17 3 +66 9 110 12 77 6 82 6 113 -21 38 -32 66 -81 56 -97 -4 -7 -3 -8 4 -4 14 8 36 +-30 26 -46 -4 -6 -3 -8 3 -5 6 4 13 -3 17 -16 9 -35 7 -42 -7 -23 -8 11 -19 +15 -30 10 -27 -10 -16 -39 16 -42 21 -3 27 -9 27 -28 0 -37 17 -29 24 12 9 43 +0 86 -26 137 -24 48 -83 114 -119 133 -34 17 -117 17 -182 -1z M130 1043 c0 +-6 11 -8 24 -6 54 11 180 -86 249 -190 19 -29 49 -62 66 -75 17 -12 37 -37 44 +-55 19 -44 41 -60 90 -64 59 -5 68 -9 60 -30 -7 -17 -6 -17 11 0 26 26 79 15 +197 -39 52 -24 101 -44 107 -44 16 0 15 -20 0 -21 -7 0 3 -7 22 -14 44 -17 +132 -19 148 -3 7 7 12 9 12 5 0 -4 19 14 42 39 49 53 91 78 123 72 26 -5 62 +23 114 90 32 41 33 46 27 99 -9 81 -20 95 -36 47 -25 -74 -96 -160 -162 -195 +-12 -6 -25 -15 -28 -19 -10 -13 -80 -41 -124 -49 -53 -10 -77 -7 -166 21 -67 +20 -83 27 -185 74 -33 15 -79 29 -101 30 -58 3 -106 28 -159 85 -38 41 -46 55 +-41 75 6 22 2 27 -31 41 -21 8 -79 40 -130 70 -87 52 -173 79 -173 56z M957 +829 c-22 -13 -13 -29 16 -29 18 0 19 2 7 10 -10 6 -11 10 -2 10 6 0 12 5 12 +10 0 13 -10 12 -33 -1z M710 565 c0 -9 100 -130 158 -190 94 -98 152 -170 152 +-189 0 -20 -11 -20 -58 0 -20 8 -32 10 -26 4 34 -35 202 -66 226 -42 22 22 +-44 96 -222 247 -58 49 -108 93 -111 97 -6 9 -119 78 -119 73z M331 453 c-17 +-20 -37 -57 -46 -82 -27 -80 -16 -133 31 -151 30 -12 115 18 159 55 29 25 29 +26 2 9 -39 -23 -95 -13 -124 23 -31 38 -32 119 -2 157 32 40 15 31 -20 -11z +M1314 448 l-19 -23 23 19 c12 11 22 21 22 23 0 8 -8 2 -26 -19z"/><path fill="#5ca7fc" d="M1163 1428 c-45 -46 -73 -101 -73 -144 0 -21 -3 -25 -17 -19 -34 15 +-121 17 -159 5 -51 -16 -114 -59 -114 -77 0 -12 3 -12 15 1 24 23 140 56 199 +56 64 0 119 -32 190 -111 79 -87 104 -170 75 -246 -30 -81 -136 -140 -235 +-130 -57 6 -144 40 -144 56 0 5 24 19 53 30 l52 20 -36 0 c-20 1 -56 -6 -79 +-14 -56 -20 -76 -14 -202 62 -120 72 -246 127 -328 143 -30 6 -74 15 -98 20 +-65 15 -108 12 -123 -7 -12 -16 -12 -17 2 -6 18 14 70 3 133 -29 31 -16 40 +-17 47 -7 13 21 255 -105 371 -193 147 -111 273 -170 368 -171 72 -2 182 48 +238 108 36 37 38 42 17 38 -40 -8 -47 23 -19 89 27 64 45 138 35 138 -4 0 -13 +12 -20 27 -7 15 -26 44 -42 65 -36 45 -30 48 20 11 29 -21 32 -22 17 -5 -11 +12 -44 34 -75 49 -112 53 -123 83 -65 179 47 78 54 84 55 49 1 -19 3 -23 6 +-12 6 22 -4 67 -15 67 -4 0 -27 -19 -49 -42z M1360 1076 c0 -2 8 -10 18 -17 +15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M1400 1020 c6 -11 13 -20 16 -20 2 0 +0 9 -6 20 -6 11 -13 20 -16 20 -2 0 0 -9 6 -20z M1392 930 c0 -14 2 -19 5 -12 +2 6 2 18 0 25 -3 6 -5 1 -5 -13z M498 883 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 +0 -19 -2 -12 -5z M1043 783 c9 -2 23 -2 30 0 6 3 -1 5 -18 5 -16 0 -22 -2 -12 +-5z M299 433 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 -17z +M251 363 c-40 -64 -58 -128 -41 -148 20 -25 25 -18 22 32 -3 37 3 61 26 106 +16 31 27 57 25 57 -1 0 -16 -21 -32 -47z M1120 325 c8 -11 26 -28 38 -39 13 +-10 42 -41 63 -69 31 -39 37 -52 28 -63 -9 -12 -9 -13 4 -8 39 15 30 51 -21 +91 -21 15 -57 46 -82 68 -25 22 -38 31 -30 20z M263 193 c9 -2 25 -2 35 0 9 3 +1 5 -18 5 -19 0 -27 -2 -17 -5z M1208 133 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 +-11 0 -14 -3 -6 -6z"/><path fill="#05309b" d="M1178 1296 c-26 -40 -29 -54 -9 -41 14 8 43 64 39 74 -2 5 -15 -10 +-30 -33z M920 1201 l-35 -7 34 -7 c18 -4 66 -7 105 -7 l71 0 46 -73 c25 -39 +49 -85 54 -102 4 -17 10 -24 12 -17 8 21 -68 161 -105 193 -28 25 -40 29 -90 +28 -31 -1 -73 -5 -92 -8z M1185 940 c10 -11 20 -20 22 -20 1 0 3 9 3 20 0 14 +-7 20 -22 20 -20 0 -21 -1 -3 -20z M1436 705 c-9 -14 -31 -39 -49 -56 -20 -20 +-36 -48 -45 -82 -13 -54 -46 -111 -91 -161 l-26 -29 31 21 c93 64 181 199 197 +301 6 38 4 38 -17 6z M585 645 c61 -45 96 -92 125 -169 42 -109 69 -153 129 +-209 47 -44 139 -97 169 -97 38 0 -8 68 -140 205 -37 39 -88 97 -113 129 -25 +33 -57 70 -71 83 -15 14 -23 30 -20 39 6 16 -1 20 -59 28 -37 5 -38 4 -20 -9z +M915 501 l-40 -6 32 -14 c18 -7 64 -35 103 -62 l70 -48 24 32 c24 33 56 91 56 +103 0 3 -7 1 -15 -6 -14 -12 -61 -11 -160 4 -16 2 -48 1 -70 -3z M356 469 +c-34 -40 -36 -116 -6 -160 43 -59 118 -48 194 30 l36 37 -42 24 c-24 12 -63 +37 -88 55 -25 17 -50 34 -57 38 -6 4 -22 -6 -37 -24z"/><path fill="#b8c9e3" d="M1175 1458 c-32 -25 -85 -105 -85 -127 0 -9 9 2 19 24 10 22 37 58 +60 80 35 33 44 37 52 25 9 -12 10 -12 7 -1 -7 23 -23 23 -53 -1z M1221 1344 +c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M1078 1285 c-3 -14 -1 +-25 3 -25 5 0 9 11 9 25 0 14 -2 25 -4 25 -2 0 -6 -11 -8 -25z M885 1271 c-22 +-10 -47 -26 -55 -37 -13 -16 -11 -16 12 2 39 30 110 47 169 42 29 -3 45 -2 37 +3 -23 15 -121 9 -163 -10z M1170 1245 c-11 -13 -11 -18 0 -28 10 -9 11 -9 6 1 +-4 7 -2 19 4 27 7 8 10 15 7 15 -2 0 -10 -7 -17 -15z M793 1190 c2 -8 16 -16 +33 -17 25 -1 26 0 4 4 -14 3 -29 10 -33 17 -6 7 -7 6 -4 -4z M918 1173 c12 -2 +32 -2 45 0 12 2 2 4 -23 4 -25 0 -35 -2 -22 -4z M1040 1176 c0 -2 13 -6 29 -8 +17 -2 28 -1 25 4 -5 8 -54 12 -54 4z M1305 1140 c10 -11 20 -20 23 -20 3 0 -3 +9 -13 20 -10 11 -20 20 -23 20 -3 0 3 -9 13 -20z M1360 1085 c13 -14 26 -25 +28 -25 3 0 -5 11 -18 25 -13 14 -26 25 -28 25 -3 0 5 -11 18 -25z M178 1093 +c12 -2 30 -2 40 0 9 3 -1 5 -23 4 -22 0 -30 -2 -17 -4z M130 1075 c-7 -9 -11 +-17 -9 -20 3 -2 10 5 17 15 14 24 10 26 -8 5z M283 1073 c9 -2 25 -2 35 0 9 3 +1 5 -18 5 -19 0 -27 -2 -17 -5z M358 1063 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 +-11 0 -14 -3 -6 -6z M440 1039 c30 -10 94 -39 143 -63 48 -25 87 -43 87 -41 0 +16 -241 126 -272 124 -7 0 12 -9 42 -20z M1167 1029 c4 -13 8 -18 11 -10 2 7 +-1 18 -6 23 -8 8 -9 4 -5 -13z M1333 1025 c-3 -9 -3 -19 1 -22 3 -4 6 2 6 12 +0 15 2 16 18 4 15 -13 16 -12 3 4 -17 21 -20 21 -28 2z M130 1020 c-9 -6 -10 +-10 -3 -10 6 0 15 5 18 10 8 12 4 12 -15 0z M1135 1009 c-4 -6 -5 -12 -2 -15 +2 -3 7 2 10 11 7 17 1 20 -8 4z M125 936 c10 -114 93 -273 194 -374 33 -33 49 +-57 45 -65 -6 -9 -5 -9 7 0 12 10 24 6 65 -23 27 -19 70 -47 96 -62 27 -15 48 +-31 48 -37 0 -5 -19 -30 -42 -55 l-43 -45 50 47 c44 41 53 45 75 36 14 -6 54 +-19 90 -29 49 -14 76 -30 115 -66 l50 -48 -45 49 c-25 26 -60 55 -78 62 -26 +12 -31 19 -27 37 3 12 -1 40 -9 62 -8 22 -15 32 -15 23 -1 -24 -31 -23 -94 4 +-191 79 -407 305 -468 488 l-19 55 5 -59z M275 970 c10 -11 20 -20 23 -20 3 0 +-3 9 -13 20 -10 11 -20 20 -23 20 -3 0 3 -9 13 -20z M1381 954 c0 -11 3 -14 6 +-6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M1176 929 c16 -20 31 -34 33 -32 3 2 +-10 18 -29 36 l-34 32 30 -36z M680 927 c0 -7 115 -77 126 -77 5 0 -7 10 -26 +21 -19 12 -50 30 -67 41 -18 11 -33 17 -33 15z M350 895 c13 -14 26 -25 28 +-25 3 0 -5 11 -18 25 -13 14 -26 25 -28 25 -3 0 5 -11 18 -25z M1381 904 c0 +-11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M473 864 c-3 -8 -2 -19 3 +-24 5 -5 9 1 9 16 0 28 -3 30 -12 8z M962 873 c20 -2 39 -10 41 -16 2 -7 3 -5 +3 3 -1 10 -13 15 -41 16 l-40 1 37 -4z M1164 838 l-19 -23 23 19 c21 18 27 26 +19 26 -2 0 -12 -10 -23 -22z M400 830 c6 -11 13 -20 16 -20 2 0 0 9 -6 20 -6 +11 -13 20 -16 20 -2 0 0 -9 6 -20z M838 843 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 +-11 0 -14 -3 -6 -6z M1349 833 c-13 -16 -12 -17 4 -4 9 7 17 15 17 17 0 8 -8 +3 -21 -13z M1473 790 c0 -36 2 -50 4 -32 2 17 2 47 0 65 -2 17 -4 3 -4 -33z +M610 837 c0 -2 15 -16 33 -33 l32 -29 -29 33 c-28 30 -36 37 -36 29z M990 821 +c0 -6 4 -13 10 -16 6 -3 7 1 4 9 -7 18 -14 21 -14 7z M500 806 c0 -2 8 -10 18 +-17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M470 755 c13 -14 26 -25 28 -25 +3 0 -5 11 -18 25 -13 14 -26 25 -28 25 -3 0 5 -11 18 -25z M540 776 c0 -2 8 +-10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M638 723 c6 -2 18 -2 25 +0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z M1461 684 c0 -11 3 -14 6 -6 3 7 2 16 +-1 19 -3 4 -6 -2 -5 -13z M520 676 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 +16 -21 21 -21 13z M590 626 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 +21 -21 13z M738 623 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z +M1310 616 c0 -2 7 -7 16 -10 8 -3 12 -2 9 4 -6 10 -25 14 -25 6z M801 606 c10 +-9 89 -46 89 -41 0 5 -75 45 -86 45 -4 0 -5 -2 -3 -4z M1275 600 c-3 -6 1 -7 +9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z M645 570 c10 -11 20 -20 23 -20 3 +0 -3 9 -13 20 -10 11 -20 20 -23 20 -3 0 3 -9 13 -20z M1295 509 c-16 -21 -34 +-38 -38 -39 -5 0 -21 -8 -35 -19 -43 -30 -72 -36 -72 -14 0 12 -8 5 -26 -20 +-14 -21 -24 -40 -22 -41 1 -2 13 -9 26 -15 17 -9 21 -16 13 -25 -8 -9 -2 -20 +22 -42 l32 -29 -28 31 -28 31 38 16 c21 9 27 14 13 11 -20 -5 -17 0 16 24 45 +33 81 77 109 130 23 47 16 47 -20 1z M960 529 c0 -5 5 -7 10 -4 6 3 10 8 10 +11 0 2 -4 4 -10 4 -5 0 -10 -5 -10 -11z M828 513 c7 -3 16 -2 19 1 4 3 -2 6 +-13 5 -11 0 -14 -3 -6 -6z M928 513 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 +-14 -3 -6 -6z M1325 448 l-50 -53 53 50 c48 46 57 55 49 55 -1 0 -25 -24 -52 +-52z M319 463 c-13 -16 -12 -17 4 -4 9 7 17 15 17 17 0 8 -8 3 -21 -13z M269 +403 c-13 -16 -12 -17 4 -4 9 7 17 15 17 17 0 8 -8 3 -21 -13z M240 360 c-6 +-11 -8 -20 -6 -20 3 0 10 9 16 20 6 11 8 20 6 20 -3 0 -10 -9 -16 -20z M469 +263 c-13 -16 -12 -17 4 -4 9 7 17 15 17 17 0 8 -8 3 -21 -13z M1215 250 c10 +-11 20 -20 23 -20 3 0 -3 9 -13 20 -10 11 -20 20 -23 20 -3 0 3 -9 13 -20z +M318 193 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1107 123 c18 +-2 50 -2 70 0 21 2 7 4 -32 4 -38 0 -55 -2 -38 -4z"/><path fill="#6884c4" d="M1221 1374 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z +M1200 1302 c0 -6 -12 -25 -27 -44 -22 -27 -23 -29 -4 -13 27 23 45 54 37 63 +-3 3 -6 0 -6 -6z M853 1183 c15 -2 37 -2 50 0 12 2 0 4 -28 4 -27 0 -38 -2 +-22 -4z M993 1173 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0 -27 -2 -17 -5z M1130 +1110 c6 -11 13 -20 16 -20 2 0 0 9 -6 20 -6 11 -13 20 -16 20 -2 0 0 -9 6 -20z +M1160 1060 c6 -11 13 -20 16 -20 2 0 0 9 -6 20 -6 11 -13 20 -16 20 -2 0 0 -9 +6 -20z M130 1030 c-11 -7 -5 -8 20 -4 43 8 51 14 19 14 -13 0 -31 -5 -39 -10z +M320 935 c30 -30 57 -55 59 -55 3 0 -19 25 -49 55 -30 30 -57 55 -59 55 -3 0 +19 -25 49 -55z M1150 967 c0 -2 15 -16 33 -33 l32 -29 -29 33 c-28 30 -36 37 +-36 29z M380 866 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z +M1461 844 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M420 806 c0 +-2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M481 754 c13 -17 26 +-30 28 -27 3 2 -8 16 -24 31 l-28 27 24 -31z M1462 720 c0 -14 2 -19 5 -12 2 +6 2 18 0 25 -3 6 -5 1 -5 -13z M535 670 c10 -11 20 -20 23 -20 3 0 -3 9 -13 +20 -10 11 -20 20 -23 20 -3 0 3 -9 13 -20z M698 633 c6 -2 18 -2 25 0 6 3 1 5 +-13 5 -14 0 -19 -2 -12 -5z M630 595 c19 -19 36 -35 39 -35 3 0 -10 16 -29 35 +-19 19 -36 35 -39 35 -3 0 10 -16 29 -35z M1338 593 c5 -21 7 -23 10 -9 2 10 +0 22 -6 28 -6 6 -7 0 -4 -19z M1417 585 c-9 -19 -15 -35 -12 -35 7 0 37 61 33 +66 -3 2 -12 -12 -21 -31z M685 590 c10 -11 20 -20 23 -20 3 0 -3 9 -13 20 -10 +11 -20 20 -23 20 -3 0 3 -9 13 -20z M1243 584 l-28 -25 33 22 c17 13 32 24 32 +26 0 8 -11 1 -37 -23z M894 566 c11 -9 24 -16 30 -16 12 0 7 5 -24 19 -24 11 +-24 11 -6 -3z M966 524 c-11 -8 -16 -14 -10 -14 13 0 39 18 34 24 -3 2 -14 -2 +-24 -10z M1179 523 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 +-17z M1311 503 c-10 -26 -10 -27 4 -9 8 11 15 23 15 28 0 15 -7 8 -19 -19z +M374 499 c-4 -7 -3 -9 3 -6 5 3 32 -10 59 -29 44 -32 117 -74 104 -61 -4 4 +-123 85 -152 103 -4 2 -10 -1 -14 -7z M863 503 c15 -2 37 -2 50 0 12 2 0 4 +-28 4 -27 0 -38 -2 -22 -4z M1354 488 l-19 -23 23 19 c21 18 27 26 19 26 -2 0 +-12 -10 -23 -22z M1131 446 c-16 -30 -35 -57 -41 -61 -16 -10 -12 -23 16 -41 +17 -12 28 -14 36 -6 7 7 3 14 -13 22 -13 7 -26 14 -28 16 -2 1 6 15 17 31 23 +33 54 93 48 93 -2 0 -18 -24 -35 -54z M334 468 l-19 -23 23 19 c21 18 27 26 +19 26 -2 0 -12 -10 -23 -22z M700 479 c0 -4 7 -26 15 -50 9 -24 13 -54 10 -67 +-4 -18 0 -24 22 -33 15 -6 55 -36 88 -68 33 -32 71 -63 85 -70 l25 -12 -25 18 +c-79 56 -144 131 -181 208 -21 44 -39 77 -39 74z M1259 423 c-8 -9 -32 -30 +-54 -46 -31 -23 -34 -28 -15 -22 34 10 85 41 101 63 13 16 12 16 -7 0 -28 -24 +-41 -23 -19 2 10 11 16 20 13 20 -3 0 -12 -8 -19 -17z M557 389 c7 -7 15 -10 +18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z M530 325 c-24 -25 -42 -45 -39 -45 +3 0 25 20 49 45 24 25 42 45 39 45 -3 0 -25 -20 -49 -45z"/></g></svg> \ No newline at end of file diff --git a/gui/public/provider-icons/parallel.svg b/gui/public/provider-icons/parallel.svg new file mode 100644 index 0000000000..5d87359bf9 --- /dev/null +++ b/gui/public/provider-icons/parallel.svg @@ -0,0 +1,13 @@ +<svg width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg"> +<circle cx="48" cy="48" r="48" fill="#ffffff"/> +<g transform="translate(11.0025 11.0) scale(1.088235)"> +<path d="M67.271 26.6083H48.6399C48.6897 26.9014 48.7305 27.1991 48.7712 27.4967H19.1336C19.0793 27.8891 19.0249 28.286 18.9797 28.6783H0.235433C0.144881 29.2646 0.0633863 29.8554 0 30.4552H18.8076C18.7759 30.8476 18.7533 31.2445 18.7307 31.6368H49.1742C49.1878 31.93 49.2014 32.2276 49.2104 32.5253H67.9954V31.5782C67.8732 29.8825 67.6332 28.2228 67.271 26.6083Z" fill="#181818"/> +<path d="M49.1742 36.3677H18.7307C18.7488 36.7646 18.7759 37.1569 18.8076 37.5493H0C0.0633863 38.1446 0.140354 38.7354 0.235433 39.3262H18.9797C19.0249 39.723 19.0748 40.1154 19.1336 40.5078H48.7712C48.7305 40.8054 48.6852 41.0986 48.6399 41.3962H67.271C67.6332 39.7817 67.8732 38.122 67.9954 36.4263V35.4792H49.2104C49.2014 35.7769 49.1878 36.07 49.1742 36.3677Z" fill="#181818"/> +<path d="M47.8341 45.2431H20.0618C20.1614 45.64 20.2701 46.0323 20.3788 46.4247H2.16877C2.40421 47.0245 2.65775 47.6153 2.92488 48.2016H20.9175C21.0488 48.5985 21.1801 48.9908 21.3205 49.3832H46.5664C46.4577 49.6808 46.349 49.974 46.2358 50.2716H63.9252C64.9529 48.4 65.8041 46.4202 66.4651 44.3547H48.0424C47.9744 44.6523 47.902 44.9455 47.8296 45.2431H47.8341Z" fill="#181818"/> +<path d="M44.5696 54.1186H23.3307C23.5254 54.5154 23.7291 54.9123 23.9374 55.3002H7.34827C7.84178 55.909 8.3534 56.4998 8.88313 57.0771H24.9244C25.1598 57.4739 25.3998 57.8708 25.6443 58.2586H42.2606C42.0749 58.5563 41.8848 58.8539 41.6901 59.1471H56.9345C58.8768 57.3837 60.6109 55.3949 62.105 53.2301H44.9952C44.8549 53.5278 44.719 53.8254 44.5742 54.1186H44.5696Z" fill="#181818"/> +<path d="M38.8468 62.9895H29.0536C29.3841 63.3909 29.7191 63.7833 30.0632 64.1711H18.2145C22.8961 66.5974 28.2115 67.9865 33.8483 68C33.8845 68 33.9162 68 33.9479 68C33.9796 68 34.0158 68 34.0475 68C41.1377 67.9775 47.7209 65.8037 53.1675 62.0966H39.5531C39.3176 62.3942 39.0822 62.6919 38.8422 62.985L38.8468 62.9895Z" fill="#181818"/> +<path d="M29.0537 5.01499H38.8469C39.0868 5.30813 39.3223 5.60578 39.5577 5.90343H53.1722C47.721 2.19631 41.1424 0.0225494 34.0521 0C34.0159 0 33.9842 0 33.9525 0C33.9208 0 33.8846 0 33.8529 0C28.2116 0.0180395 22.9007 1.40257 18.2192 3.82889H30.0679C29.7238 4.21674 29.3887 4.61361 29.0582 5.01048L29.0537 5.01499Z" fill="#181818"/> +<path d="M23.3308 13.8904H44.5697C44.7146 14.1836 44.855 14.4812 44.9908 14.7789H62.1006C60.6065 12.6141 58.8724 10.6253 56.9301 8.86193H41.6856C41.8803 9.15507 42.0705 9.45272 42.2561 9.75037H25.6399C25.3954 10.1427 25.1554 10.5351 24.92 10.932H8.8787C8.34444 11.5047 7.83283 12.0955 7.34385 12.7089H23.9329C23.7247 13.1012 23.5255 13.4936 23.3263 13.8904H23.3308Z" fill="#181818"/> +<path d="M20.0663 22.7613H47.8386C47.911 23.0545 47.9835 23.3521 48.0514 23.6498H66.4741C65.8131 21.5843 64.9619 19.6044 63.9342 17.7328H46.2449C46.358 18.026 46.4667 18.3236 46.5754 18.6213H21.3295C21.1892 19.0136 21.0533 19.406 20.9266 19.8029H2.9339C2.66677 20.3846 2.41323 20.9754 2.17779 21.5798H20.3878C20.2791 21.9721 20.1704 22.3645 20.0708 22.7613H20.0663Z" fill="#181818"/> +</g> +</svg> \ No newline at end of file diff --git a/gui/public/provider-icons/sambanova.svg b/gui/public/provider-icons/sambanova.svg new file mode 100644 index 0000000000..341c74ea0f --- /dev/null +++ b/gui/public/provider-icons/sambanova.svg @@ -0,0 +1,276 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160.000000 144.000000"><g transform="translate(0.000000,144.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"><path fill="#260e37" d="M1042 1354 c-74 -21 -222 -87 -222 -99 0 -2 32 9 71 26 120 49 230 +80 314 88 l80 8 -80 1 c-58 1 -103 -6 -163 -24z M1200 1313 c-48 -5 -182 -34 +-178 -39 3 -2 42 3 89 11 46 8 115 15 153 15 38 0 67 2 65 4 -7 6 -93 12 -129 +9z M1100 1231 c-19 -3 36 -6 123 -6 87 0 148 3 135 7 -29 8 -211 7 -258 -1z +M1161 1176 c3 -3 47 -10 99 -16 52 -6 106 -13 120 -15 14 -3 7 1 -15 9 -42 14 +-214 33 -204 22z M1250 1099 c35 -5 94 -20 131 -32 37 -13 66 -20 63 -16 -11 +18 -125 51 -189 54 l-70 4 65 -10z M660 1064 c-358 -24 -579 -67 -627 -122 +-51 -60 -39 -150 42 -317 162 -332 400 -574 611 -618 143 -30 441 68 670 219 +87 57 144 114 144 142 0 13 -6 10 -26 -13 -24 -28 -25 -28 -20 -7 4 20 -1 18 +-30 -15 -27 -29 -32 -32 -23 -13 6 14 -14 -1 -48 -38 -32 -34 -62 -62 -66 -62 +-5 0 12 28 38 62 74 102 70 103 -27 2 -92 -95 -169 -156 -98 -78 19 22 49 55 +65 74 28 32 26 32 -15 -6 -94 -86 -253 -194 -266 -181 -3 2 25 26 62 53 225 +160 459 497 472 680 9 127 -64 181 -293 220 -87 15 -444 26 -565 18z m378 -64 +c244 -22 389 -85 406 -176 18 -93 -33 -195 -195 -386 -79 -95 -83 -98 -124 +-98 -23 0 -67 5 -99 11 -32 6 -60 8 -62 6 -3 -3 26 -10 63 -16 37 -7 79 -15 +93 -17 l25 -6 -29 -23 c-22 -18 -38 -22 -60 -18 -70 12 -220 54 -293 82 -42 +17 -79 29 -81 27 -8 -8 214 -88 300 -108 91 -21 91 -21 68 -40 -12 -10 -33 +-18 -46 -18 -35 0 -179 48 -271 91 -46 21 -83 35 -83 33 0 -13 214 -106 278 +-120 87 -19 85 -25 -23 -79 -132 -68 -221 -49 -382 80 -223 179 -441 524 -409 +649 7 29 26 26 26 -3 0 -25 56 -134 80 -156 19 -17 20 -16 47 16 54 67 219 +219 258 238 66 33 319 48 513 31z M928 363 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 +-11 0 -14 -3 -6 -6z M485 315 c76 -71 122 -105 181 -135 32 -16 65 -30 73 -30 +7 0 -13 14 -45 30 -51 28 -98 59 -214 146 -32 24 -32 23 5 -11z M590 316 c0 +-9 132 -87 180 -106 79 -31 73 -23 -15 19 -44 22 -99 51 -122 66 -24 14 -43 +24 -43 21z M1299 333 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 +-17 -17z"/><path fill="#6f3cb3" d="M1135 1275 c-90 -14 -115 -20 -300 -76 -145 -44 -301 -104 -245 -94 +19 3 80 12 135 21 55 8 134 22 175 29 143 28 295 38 388 25 89 -11 179 -47 +175 -69 -2 -6 5 -11 15 -11 23 0 60 -39 72 -76 9 -25 9 -24 4 10 -9 66 -53 +161 -89 196 -27 26 -49 36 -102 46 -82 16 -124 16 -228 -1z m245 -36 c41 -14 +83 -40 76 -46 -2 -3 -39 3 -82 13 -95 21 -241 15 -418 -16 -65 -11 -121 -19 +-123 -16 -10 9 208 63 322 80 49 7 187 -2 225 -15z M1058 1153 c7 -3 16 -2 19 +1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1033 1123 c15 -2 39 -2 55 0 15 2 2 4 +-28 4 -30 0 -43 -2 -27 -4z M1562 970 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 +-5 1 -5 -13z M1550 758 c-6 -35 -23 -90 -37 -123 -13 -33 -26 -69 -27 -80 -2 +-11 -4 -24 -5 -28 0 -5 -5 -5 -11 -2 -5 3 -7 0 -4 -8 5 -15 -26 -82 -68 -146 +-12 -19 -17 -32 -11 -28 7 4 33 41 58 82 49 78 61 91 50 53 -4 -16 0 -12 15 +10 11 19 28 59 36 90 15 56 32 242 22 242 -3 0 -11 -28 -18 -62z M215 685 c45 +-62 169 -205 178 -205 5 0 5 -5 2 -10 -4 -6 -2 -9 3 -8 5 2 54 -29 108 -68 55 +-39 135 -92 179 -117 86 -48 255 -113 255 -97 0 6 5 9 11 7 6 -1 13 3 15 8 3 +6 -20 16 -50 22 -103 22 -301 124 -427 220 -68 52 -61 60 14 15 225 -137 477 +-240 525 -213 14 8 12 10 -13 16 -156 33 -438 152 -535 226 -25 19 -67 50 -95 +69 -27 18 -84 65 -125 104 -59 56 -68 63 -45 31z M1336 348 c3 -5 10 -6 15 -3 +13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/><path fill="#59329f" d="M1085 1336 c-80 -22 -151 -49 -345 -132 -63 -27 -209 -103 -198 -104 +4 0 54 17 110 39 57 21 118 42 135 46 18 3 31 11 30 16 -3 14 126 65 243 96 +121 33 246 36 304 8 33 -16 36 -20 20 -26 -16 -6 -14 -8 8 -8 17 -1 44 -15 70 +-38 l42 -38 -24 43 c-51 91 -89 112 -214 118 -75 4 -107 0 -181 -20z M1173 +1283 c9 -2 23 -2 30 0 6 3 -1 5 -18 5 -16 0 -22 -2 -12 -5z M1318 1283 c6 -2 +18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z M1108 1273 c7 -3 16 -2 19 1 4 +3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1208 1253 c17 -2 47 -2 65 0 17 2 3 4 -33 +4 -36 0 -50 -2 -32 -4z M1108 1243 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 +-2 -12 -5z M1058 1233 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z +M1400 1226 c0 -2 7 -7 16 -10 8 -3 12 -2 9 4 -6 10 -25 14 -25 6z M1108 1213 +c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M838 1203 c7 -3 16 -2 +19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1028 1203 c7 -3 16 -2 19 1 4 3 -2 +6 -13 5 -11 0 -14 -3 -6 -6z M855 1185 c-26 -12 -27 -13 -8 -14 12 -1 25 4 28 +9 3 6 18 7 33 4 21 -4 24 -3 12 5 -19 14 -28 13 -65 -4z M1168 1183 c18 -2 45 +-2 60 0 15 2 0 4 -33 4 -33 0 -45 -2 -27 -4z M1038 1173 c7 -3 16 -2 19 1 4 3 +-2 6 -13 5 -11 0 -14 -3 -6 -6z M958 1163 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 +-11 0 -14 -3 -6 -6z M880 1150 c-24 -8 -23 -8 8 -9 17 -1 32 4 32 9 0 11 -5 +11 -40 0z M1018 1153 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z +M1238 1153 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1446 1122 +c-3 -5 1 -9 9 -9 8 0 12 4 9 9 -3 4 -7 8 -9 8 -2 0 -6 -4 -9 -8z M918 1113 c7 +-3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1118 1103 c7 -3 16 -2 19 +1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M588 1063 c7 -3 16 -2 19 1 4 3 -2 6 +-13 5 -11 0 -14 -3 -6 -6z M1471 1033 c7 -12 15 -20 18 -17 3 2 -3 12 -13 22 +-17 16 -18 16 -5 -5z M1572 940 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 +-5 -13z M1570 808 c0 -116 -20 -241 -46 -291 -9 -18 -18 -48 -19 -67 l-3 -35 +20 39 c47 90 60 148 65 287 4 91 2 142 -5 155 -9 15 -11 -5 -12 -88z M190 726 +c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M265 652 c22 -24 +69 -64 105 -90 36 -25 74 -53 85 -62 11 -10 27 -17 35 -17 9 0 63 -21 120 -48 +223 -102 483 -169 497 -127 3 8 -5 12 -24 12 -42 0 -215 44 -332 85 -164 57 +-412 188 -491 259 -32 28 -31 27 5 -12z M1470 535 c-6 -15 -13 -32 -16 -36 -3 +-5 -1 -9 3 -9 10 0 35 61 28 68 -2 3 -9 -8 -15 -23z M1478 478 c-7 -13 -11 +-29 -10 -35 2 -7 -2 -23 -8 -35 -10 -21 -10 -21 9 2 10 13 17 28 15 32 -3 4 +-2 8 3 8 4 0 9 11 9 25 2 31 -2 31 -18 3z M440 483 c0 -4 15 -20 34 -35 32 +-26 35 -26 31 -7 -3 13 0 19 8 16 7 -3 11 -9 10 -15 -2 -7 4 -9 14 -5 10 4 14 +2 9 -5 -3 -6 -2 -13 3 -16 5 -4 8 1 7 10 -1 9 -18 25 -36 37 -19 12 -31 16 +-26 9 10 -17 -5 -15 -32 4 -12 8 -22 12 -22 7z M399 466 c13 -16 51 -41 51 +-33 0 2 -15 14 -32 27 -21 15 -28 17 -19 6z M340 466 c0 -2 8 -10 18 -17 15 +-13 16 -12 3 4 -13 16 -21 21 -21 13z M474 406 c1 -8 5 -18 8 -22 4 -3 5 1 4 +10 -1 8 -5 18 -8 22 -4 3 -5 -1 -4 -10z M1396 411 c-3 -4 6 -7 19 -7 14 0 22 +2 19 5 -8 8 -34 10 -38 2z M590 396 c0 -3 14 -12 30 -21 17 -9 30 -13 30 -11 +0 3 -13 12 -30 21 -16 9 -30 13 -30 11z M1406 378 c3 -5 10 -6 15 -3 13 9 11 +12 -6 12 -8 0 -12 -4 -9 -9z M720 350 c8 -5 20 -10 25 -10 6 0 3 5 -5 10 -8 5 +-19 10 -25 10 -5 0 -3 -5 5 -10z M1383 353 c4 -3 -1 -12 -11 -20 -15 -13 -15 +-13 6 -3 19 9 23 30 5 30 -4 0 -3 -3 0 -7z M460 325 c13 -14 26 -25 28 -25 3 +0 -5 11 -18 25 -13 14 -26 25 -28 25 -3 0 5 -11 18 -25z M470 346 c0 -2 8 -10 +18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M750 310 c8 -5 20 -10 25 -10 +6 0 3 5 -5 10 -8 5 -19 10 -25 10 -5 0 -3 -5 5 -10z M1299 315 c-1 -3 -2 -10 +-3 -16 0 -5 -12 -19 -26 -29 -14 -11 -20 -20 -14 -20 14 0 47 37 50 55 2 14 +-4 22 -7 10z M1340 288 c-12 -22 -12 -22 6 -6 10 10 15 20 12 24 -4 3 -12 -5 +-18 -18z M1189 263 l-34 -38 38 34 c20 19 37 36 37 38 0 8 -8 0 -41 -34z M978 +263 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1020 256 c0 -2 7 +-6 15 -10 8 -3 15 -1 15 4 0 6 -7 10 -15 10 -8 0 -15 -2 -15 -4z"/><path fill="#442997" d="M1078 1419 c-84 -23 -209 -81 -313 -144 -101 -61 -248 -166 -241 +-172 3 -3 43 15 90 40 46 25 100 50 120 57 20 6 34 15 31 20 -7 12 112 80 199 +114 81 33 183 56 247 56 53 0 112 -18 103 -31 -3 -5 7 -9 22 -9 25 0 104 -37 +104 -49 0 -3 10 -17 21 -31 l21 -25 -19 34 c-28 51 -124 135 -170 149 -57 17 +-137 14 -215 -9z M1233 1363 c9 -2 23 -2 30 0 6 3 -1 5 -18 5 -16 0 -22 -2 +-12 -5z M1148 1353 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z +M1228 1323 c12 -2 30 -2 40 0 9 3 -1 5 -23 4 -22 0 -30 -2 -17 -4z M1138 1313 +c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z M1350 1306 c0 -2 7 -7 +16 -10 8 -3 12 -2 9 4 -6 10 -25 14 -25 6z M870 1236 c0 -2 7 -7 16 -10 8 -3 +12 -2 9 4 -6 10 -25 14 -25 6z M1491 1220 c14 -31 19 -36 19 -24 0 6 -7 19 +-16 30 -14 18 -14 18 -3 -6z M831 1219 c-32 -13 -22 -20 14 -11 16 5 25 11 20 +14 -6 3 -21 2 -34 -3z M1430 1200 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 +-11 10 -2 0 -4 -4 -4 -10z M1571 974 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 +-6 -2 -5 -13z M1582 910 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z +M1592 779 c-1 -47 -4 -108 -7 -135 l-4 -49 10 48 c5 27 8 88 6 135 l-3 87 -2 +-86z M395 575 c41 -34 254 -135 271 -128 11 4 15 2 11 -4 -7 -11 22 -28 31 +-18 3 3 -1 5 -8 5 -7 0 -11 2 -8 5 2 3 41 -6 86 -19 92 -28 241 -61 320 -71 +43 -6 57 -4 73 11 27 25 24 34 -14 34 -100 0 -500 94 -655 154 -48 18 -96 36 +-107 40 l-20 8 20 -17z M1552 528 c-26 -70 -39 -143 -22 -118 16 23 52 147 48 +165 -2 9 -13 -12 -26 -47z M510 471 c13 -11 27 -19 30 -19 20 2 30 -3 25 -12 +-3 -6 1 -7 10 -4 20 8 20 9 -40 34 -47 19 -49 19 -25 1z M1497 453 c-4 -3 -7 +-15 -7 -26 0 -17 -2 -18 -17 -6 -16 13 -17 12 -4 -4 11 -14 11 -23 3 -42 -7 +-14 -10 -25 -8 -25 10 0 40 57 39 73 -1 9 2 15 7 12 6 -3 7 1 4 9 -7 18 -8 19 +-17 9z M1426 353 c-6 -14 -5 -15 5 -6 7 7 10 15 7 18 -3 3 -9 -2 -12 -12z +M998 333 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1048 323 c7 +-3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1107 306 c-3 -8 -18 -16 +-34 -19 -28 -4 -28 -4 -2 -6 15 0 34 8 44 19 10 11 13 20 8 20 -6 0 -13 -6 +-16 -14z"/><path fill="#402061" d="M1178 1383 c17 -2 47 -2 65 0 17 2 3 4 -33 4 -36 0 -50 -2 -32 -4z +M1098 1373 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1275 1370 +c-4 -6 6 -10 22 -10 22 0 25 2 13 10 -19 12 -27 12 -35 0z M1058 1363 c7 -3 +16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1173 1363 c9 -2 25 -2 35 0 9 +3 1 5 -18 5 -19 0 -27 -2 -17 -5z M1118 1353 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 +-11 0 -14 -3 -6 -6z M1078 1343 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 +-6 -6z M1183 1313 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0 -27 -2 -17 -5z M1273 +1313 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0 -27 -2 -17 -5z M874 1294 c-18 -14 +-18 -14 6 -3 31 14 36 19 24 19 -6 0 -19 -7 -30 -16z M1118 1303 c7 -3 16 -2 +19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1250 1300 l-85 -7 93 0 c51 0 95 -3 +99 -7 3 -3 13 -6 22 -6 11 1 7 6 -9 15 -14 8 -27 14 -30 13 -3 -1 -43 -5 -90 +-8z M1040 1280 c8 -5 22 -9 30 -9 10 0 8 3 -5 9 -27 12 -43 12 -25 0z M1118 +1283 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M847 1269 c7 -7 15 +-10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z M1005 1270 c3 -5 14 -10 23 -10 +15 0 15 2 2 10 -20 13 -33 13 -25 0z M792 1249 c-18 -12 -31 -23 -29 -26 7 -6 +57 19 57 29 0 5 5 6 12 2 7 -4 8 -3 4 4 -9 15 -5 16 -44 -9z M970 1260 c0 -5 +7 -10 16 -10 8 0 12 5 9 10 -3 6 -10 10 -16 10 -5 0 -9 -4 -9 -10z M900 1239 +c-25 -11 -34 -17 -20 -14 14 3 37 8 53 11 15 4 27 10 27 15 0 12 -9 11 -60 +-12z M1198 1243 c23 -2 61 -2 85 0 23 2 4 4 -43 4 -47 0 -66 -2 -42 -4z M1110 +1229 c14 -4 70 -7 125 -7 125 0 105 6 -40 11 -60 2 -99 0 -85 -4z M1348 1233 +c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1040 1220 c0 -5 12 -10 +28 -10 21 0 24 2 12 10 -20 13 -40 13 -40 0z M1370 1219 c-12 -9 -11 -10 8 -6 +12 3 22 1 22 -4 0 -5 8 -9 18 -9 15 1 15 2 -2 15 -23 18 -26 18 -46 4z M955 +1204 c-22 -7 -29 -12 -15 -9 14 3 37 5 53 5 15 0 27 5 27 10 0 13 -16 12 -65 +-6z M1002 1168 c-40 -6 -72 -11 -70 -13 2 -2 66 0 143 3 86 4 128 9 110 13 +-41 9 -96 8 -183 -3z M1253 1173 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0 -27 -2 +-17 -5z M1338 1163 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z +M1278 1153 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z M1355 1150 +c-13 -6 -15 -9 -5 -9 8 0 22 4 30 9 18 12 2 12 -25 0z M1394 1138 c-5 -8 -2 +-9 9 -5 10 3 17 3 17 -2 0 -5 10 -11 23 -14 21 -6 21 -6 -3 13 -29 23 -36 24 +-46 8z M1058 1110 c68 -4 140 -3 160 1 21 4 -33 7 -123 6 l-160 0 123 -7z +M1298 1103 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1238 1093 +c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z M1298 1083 c7 -3 16 -2 +19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M633 1063 c15 -2 39 -2 55 0 15 2 2 +4 -28 4 -30 0 -43 -2 -27 -4z M1058 1063 c12 -2 30 -2 40 0 9 3 -1 5 -23 4 +-22 0 -30 -2 -17 -4z M493 1053 c9 -2 23 -2 30 0 6 3 -1 5 -18 5 -16 0 -22 -2 +-12 -5z M1168 1053 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z +M1434 1048 c-4 -7 -3 -8 4 -4 7 4 12 3 12 -2 0 -6 8 -13 17 -16 15 -6 15 -4 +-2 14 -21 23 -22 24 -31 8z M388 1043 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 +-19 -2 -12 -5z M1238 1043 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 +-6z M318 1033 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M258 1023 +c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M208 1013 c7 -3 16 -2 +19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M126 887 c3 -10 9 -15 12 -12 3 3 0 +11 -7 18 -10 9 -11 8 -5 -6z M113 845 c0 -22 2 -30 4 -17 2 12 2 30 0 40 -3 9 +-5 -1 -4 -23z M1523 845 c0 -22 2 -30 4 -17 2 12 2 30 0 40 -3 9 -5 -1 -4 -23z +M150 814 c0 -6 7 -19 16 -30 14 -18 14 -18 3 6 -14 31 -19 36 -19 24z M215 +710 c10 -11 20 -20 23 -20 3 0 -3 9 -13 20 -10 11 -20 20 -23 20 -3 0 3 -9 13 +-20z M1414 428 c-25 -39 -44 -83 -25 -62 12 14 62 104 58 104 -3 0 -17 -19 +-33 -42z M540 455 c0 -3 27 -18 60 -35 33 -17 60 -26 60 -20 0 5 -2 10 -4 10 +-2 0 -29 11 -60 25 -31 14 -56 23 -56 20z M1345 398 c-22 -29 -40 -55 -40 -58 +0 -3 -25 -29 -55 -59 -30 -30 -45 -46 -32 -36 12 10 22 14 22 10 0 -5 11 2 25 +15 14 13 23 25 20 28 -2 2 6 13 18 23 12 10 19 18 15 19 -4 0 3 11 15 24 22 +24 61 86 55 86 -2 0 -21 -24 -43 -52z m-65 -101 c0 -2 -10 -12 -22 -23 l-23 +-19 19 23 c18 21 26 27 26 19z M392 408 c13 -17 40 -43 59 -57 25 -18 19 -10 +-21 32 -61 64 -75 73 -38 25z M500 436 c0 -7 105 -71 112 -68 5 1 8 -3 8 -9 0 +-5 4 -8 9 -5 5 3 11 -1 15 -9 3 -9 12 -12 22 -8 13 5 15 3 9 -7 -6 -9 -4 -11 +4 -5 7 4 9 11 4 15 -13 11 -183 101 -183 96z M690 437 c0 -4 91 -39 138 -52 +12 -4 22 -3 22 2 0 5 -30 17 -67 27 -38 11 -74 21 -80 24 -7 2 -13 2 -13 -1z +M1436 400 c-14 -22 -24 -40 -21 -40 4 0 -7 -12 -22 -26 -15 -15 -19 -20 -9 +-13 18 13 19 12 12 -6 -6 -17 -4 -17 18 4 14 13 23 28 20 32 -2 5 5 26 16 49 +26 51 20 51 -14 0z M1511 421 c-6 -10 -8 -21 -5 -23 3 -3 -3 -8 -12 -12 -13 +-5 -15 -2 -10 21 7 27 6 27 -17 -7 -25 -36 -26 -80 -1 -59 7 6 11 16 8 23 -3 +7 2 13 11 13 8 0 15 -5 15 -10 0 -20 12 2 20 37 9 40 6 46 -9 17z M335 410 +c10 -11 20 -20 23 -20 3 0 -3 9 -13 20 -10 11 -20 20 -23 20 -3 0 3 -9 13 -20z +M445 423 c38 -36 125 -104 125 -97 0 4 6 0 13 -11 9 -11 16 -14 20 -8 4 6 -26 +32 -66 59 -82 56 -107 71 -92 57z M1299 393 c-13 -16 -12 -17 4 -4 16 13 21 +21 13 21 -2 0 -10 -8 -17 -17z M670 389 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 +-4 4 -10 4 -5 0 -10 -5 -10 -11z M860 380 c0 -5 4 -10 9 -10 6 0 13 5 16 10 3 +6 -1 10 -9 10 -9 0 -16 -4 -16 -10z M1176 362 l-19 -27 24 22 c12 12 21 24 18 +27 -2 2 -13 -8 -23 -22z M715 370 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 +-4 -16 -10z M895 370 c-3 -5 1 -10 9 -10 8 0 18 5 21 10 3 6 -1 10 -9 10 -8 0 +-18 -4 -21 -10z M740 360 c-9 -6 -10 -10 -3 -10 6 0 15 5 18 10 8 12 4 12 -15 +0z M940 360 c-13 -9 -13 -10 0 -10 8 0 22 5 30 10 13 9 13 10 0 10 -8 0 -22 +-5 -30 -10z M765 350 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z +M985 350 c-13 -5 -14 -9 -5 -9 8 0 24 4 35 9 13 5 14 9 5 9 -8 0 -24 -4 -35 +-9z M1249 333 l-24 -28 28 24 c15 14 27 26 27 28 0 8 -8 1 -31 -24z M1339 332 +c-13 -14 -17 -21 -8 -14 9 6 18 10 21 7 3 -3 11 1 17 9 22 27 -6 26 -30 -2z +M430 308 c41 -45 50 -53 50 -45 0 2 -21 23 -47 47 l-48 45 45 -47z M790 340 +c-8 -5 -10 -10 -5 -10 6 0 17 5 25 10 8 5 11 10 5 10 -5 0 -17 -5 -25 -10z +M1068 343 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z M1028 333 c7 +-3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M695 320 c-4 -6 1 -7 11 -3 +12 4 15 3 10 -5 -5 -8 -1 -10 10 -5 12 4 15 3 10 -5 -5 -8 -2 -10 10 -5 14 5 +13 8 -7 19 -29 16 -36 17 -44 4z M1083 323 c9 -2 23 -2 30 0 6 3 -1 5 -18 5 +-16 0 -22 -2 -12 -5z M503 305 c0 -8 12 -25 26 -37 14 -13 20 -16 13 -7 -6 9 +-10 18 -7 21 3 3 -3 12 -13 21 -16 14 -19 15 -19 2z M898 313 c7 -3 16 -2 19 +1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1119 303 c-13 -16 -12 -17 4 -4 16 13 +21 21 13 21 -2 0 -10 -8 -17 -17z M616 301 c-4 -5 -2 -12 3 -15 5 -4 12 -2 15 +3 4 5 2 12 -3 15 -5 4 -12 2 -15 -3z M978 293 c7 -3 16 -2 19 1 4 3 -2 6 -13 +5 -11 0 -14 -3 -6 -6z M645 281 c-3 -5 -2 -12 3 -15 5 -3 9 1 9 9 0 17 -3 19 +-12 6z M800 280 c8 -5 20 -10 25 -10 6 0 3 5 -5 10 -8 5 -19 10 -25 10 -5 0 +-3 -5 5 -10z M1018 283 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z +M1345 280 c-4 -6 -10 -8 -15 -5 -5 3 -18 -9 -31 -26 -20 -28 -20 -30 -3 -21 +21 11 71 62 61 62 -3 0 -9 -5 -12 -10z M550 276 c0 -2 8 -10 18 -17 15 -13 16 +-12 3 4 -13 16 -21 21 -21 13z M673 265 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 +-12 11 -12 -6z M968 273 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z +M1018 263 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M695 251 c-3 +-5 -2 -12 3 -15 5 -3 9 1 9 9 0 17 -3 19 -12 6z M1049 243 c-13 -16 -12 -17 4 +-4 16 13 21 21 13 21 -2 0 -10 -8 -17 -17z M510 236 c0 -2 8 -10 18 -17 15 +-13 16 -12 3 4 -13 16 -21 21 -21 13z M1199 204 c-20 -22 -26 -32 -14 -26 25 +13 72 62 59 62 -6 0 -26 -16 -45 -36z M750 220 c-9 -6 -10 -10 -3 -10 6 0 15 +5 18 10 8 12 4 12 -15 0z M955 220 c-25 -11 1 -11 35 0 20 6 21 8 5 8 -11 0 +-29 -3 -40 -8z M1119 203 l-24 -28 28 24 c15 14 27 26 27 28 0 8 -8 1 -31 -24z +M775 210 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z M1159 203 +c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 -17z M800 200 c-9 +-6 -10 -10 -3 -10 6 0 15 5 18 10 8 12 4 12 -15 0z M970 189 c-22 -18 -22 -19 +-3 -10 12 6 25 16 28 21 9 15 3 12 -25 -11z M825 190 c-3 -5 -2 -10 4 -10 5 0 +13 5 16 10 3 6 2 10 -4 10 -5 0 -13 -4 -16 -10z M860 180 c0 -5 14 -10 32 -10 +36 0 35 4 -4 14 -17 4 -28 2 -28 -4z M690 170 c-9 -6 -10 -10 -3 -10 6 0 15 5 +18 10 8 12 4 12 -15 0z M715 160 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 +-4 -16 -10z M1061 156 c-9 -11 -9 -16 1 -22 7 -4 10 -4 6 1 -4 4 -3 14 3 22 6 +7 9 13 6 13 -2 0 -10 -6 -16 -14z M744 149 c-3 -5 6 -9 20 -9 31 0 34 6 6 13 +-11 3 -23 1 -26 -4z M647 139 c7 -7 15 -10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 +-6 -5z M1005 115 c-34 -28 -28 -32 16 -10 16 9 27 20 24 25 -8 13 -5 14 -40 +-15z M768 103 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/><path fill="#834dae" d="M1107 1153 c18 -2 50 -2 70 0 21 2 7 4 -32 4 -38 0 -55 -2 -38 -4z +M953 1143 c9 -2 23 -2 30 0 6 3 -1 5 -18 5 -16 0 -22 -2 -12 -5z M1288 1143 +c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M740 1130 c-20 -4 5 -5 +55 -2 50 2 92 6 94 8 8 7 -113 2 -149 -6z M948 1123 c12 -2 32 -2 45 0 12 2 2 +4 -23 4 -25 0 -35 -2 -22 -4z M1118 1123 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 +0 -14 -3 -6 -6z M1173 1123 c15 -2 39 -2 55 0 15 2 2 4 -28 4 -30 0 -43 -2 +-27 -4z M663 1113 c9 -2 23 -2 30 0 6 3 -1 5 -18 5 -16 0 -22 -2 -12 -5z M868 +1113 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z M1298 1113 c7 -3 +16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M608 1103 c7 -3 16 -2 19 1 4 +3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1058 1103 c12 -2 30 -2 40 0 9 3 -1 5 -23 +4 -22 0 -30 -2 -17 -4z M1487 1090 c13 -5 32 -24 43 -42 11 -18 20 -28 20 -21 +0 19 -51 73 -68 72 -12 0 -10 -3 5 -9z M803 1073 c42 -2 112 -2 155 0 42 1 7 +3 -78 3 -85 0 -120 -2 -77 -3z M548 1063 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 +0 -19 -2 -12 -5z M1138 1063 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 +-6z M438 1053 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1470 +1046 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M1561 934 +c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M210 676 c0 -2 8 -10 18 +-17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M279 595 c22 -27 57 -68 77 -90 +l36 -40 -31 40 c-17 22 -51 63 -77 90 l-46 50 41 -50z M1485 589 c-4 -6 -5 +-12 -2 -15 2 -3 7 2 10 11 7 17 1 20 -8 4z M1415 500 c-3 -5 -1 -10 4 -10 6 0 +11 5 11 10 0 6 -2 10 -4 10 -3 0 -8 -4 -11 -10z M320 496 c0 -3 8 -12 18 -22 +9 -10 17 -21 17 -26 0 -4 3 -7 7 -5 4 1 26 -16 50 -38 24 -22 14 -9 -22 28 +-63 65 -70 72 -70 63z M1385 460 c-3 -5 -5 -10 -3 -11 27 -6 39 -5 45 4 3 7 3 +9 -2 5 -4 -4 -14 -3 -21 3 -8 7 -14 7 -19 -1z M435 420 c21 -22 41 -40 44 -40 +3 0 -13 18 -34 40 -21 22 -41 40 -44 40 -3 0 13 -18 34 -40z M1386 418 c3 -5 +10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z M1365 400 c-3 -5 -3 -10 1 +-10 4 0 -1 -7 -12 -15 -10 -8 -14 -15 -8 -15 17 0 47 37 35 44 -5 4 -12 2 -16 +-4z M415 370 c10 -11 20 -20 23 -20 3 0 -3 9 -13 20 -10 11 -20 20 -23 20 -3 +0 3 -9 13 -20z M430 315 c30 -30 57 -55 59 -55 3 0 -19 25 -49 55 -30 30 -57 +55 -59 55 -3 0 19 -25 49 -55z M515 350 c10 -11 20 -20 23 -20 3 0 -3 9 -13 +20 -10 11 -20 20 -23 20 -3 0 3 -9 13 -20z M500 325 c13 -14 26 -25 28 -25 3 +0 -5 11 -18 25 -13 14 -26 25 -28 25 -3 0 5 -11 18 -25z M1310 318 l-23 -33 +28 27 c16 15 26 30 23 33 -3 4 -16 -9 -28 -27z M1244 318 l-19 -23 23 19 c12 +11 22 21 22 23 0 8 -8 2 -26 -19z M550 326 c0 -2 8 -10 18 -17 15 -13 16 -12 +3 4 -13 16 -21 21 -21 13z M1254 298 l-19 -23 23 19 c21 18 27 26 19 26 -2 0 +-12 -10 -23 -22z M505 280 c10 -11 20 -20 23 -20 3 0 -3 9 -13 20 -10 11 -20 +20 -23 20 -3 0 3 -9 13 -20z M1204 248 l-19 -23 23 19 c21 18 27 26 19 26 -2 +0 -12 -10 -23 -22z M1136 211 c-17 -19 -17 -19 0 -17 9 2 18 10 20 19 3 21 1 +21 -20 -2z M697 189 c7 -7 15 -10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z +M883 163 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0 -27 -2 -17 -5z M1064 149 c-19 +-22 -19 -22 4 -8 12 8 22 17 22 21 0 12 -6 9 -26 -13z M795 140 c-16 -7 -17 +-9 -3 -9 9 -1 20 4 23 9 7 11 7 11 -20 0z"/></g></svg> \ No newline at end of file diff --git a/gui/public/provider-icons/scaleway.svg b/gui/public/provider-icons/scaleway.svg new file mode 100644 index 0000000000..4996b509b4 --- /dev/null +++ b/gui/public/provider-icons/scaleway.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"> + <style> + @media (prefers-color-scheme:dark){path{fill:#fff}} + </style> + <path + d="M8.37 3.666H5.736A1.182 1.182 0 0 0 4.69 4.79v3.81c0 .257.1.48.267.647.166.167.389.267.644.267.5 0 .912-.412.912-.913V6.184c0-.379.311-.691.69-.691h1.189c.255 0 .478-.1.644-.267a.927.927 0 0 0 .267-.658c0-.501-.433-.902-.934-.902ZM7.181 12.357h2.635a1.181 1.181 0 0 0 1.045-1.126v-3.81a.9.9 0 0 0-.267-.647.897.897 0 0 0-.645-.267c-.5 0-.912.412-.912.914v2.417c0 .38-.31.691-.689.691h-1.19c-.255 0-.477.1-.644.268a.927.927 0 0 0-.267.657.934.934 0 0 0 .934.902Z" + fill="#BF95F9"/> + <path fill-rule="evenodd" clip-rule="evenodd" + d="M4.024 0h4.791a5.683 5.683 0 0 1 5.692 5.716v7.588A3.027 3.027 0 0 1 11.75 16H6.703C3.557 16 1 13.437 1 10.284V3.031A3.03 3.03 0 0 1 4.024 0Zm8.67 5.716c0-2.14-1.744-3.889-3.879-3.889h-4.78c-.667 0-1.2.535-1.2 1.204v7.253a3.88 3.88 0 0 0 3.88 3.889h4.957a1.193 1.193 0 0 0 1.023-.98V5.715Z" + fill="#BF95F9"/> +</svg> \ No newline at end of file diff --git a/gui/public/provider-icons/siliconflow.svg b/gui/public/provider-icons/siliconflow.svg new file mode 100644 index 0000000000..8537ff8e30 --- /dev/null +++ b/gui/public/provider-icons/siliconflow.svg @@ -0,0 +1,18 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" + "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<svg version="1.0" xmlns="http://www.w3.org/2000/svg" + width="256.000000pt" height="256.000000pt" viewBox="0 0 256.000000 256.000000" + preserveAspectRatio="xMidYMid meet"> +<metadata> +Created by potrace 1.16, written by Peter Selinger 2001-2019 +</metadata> +<g transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"> +<path d="M1261 1773 l-33 -35 1 -163 c2 -139 0 -165 -14 -177 -9 -7 -28 -25 +-42 -40 l-26 -28 -463 0 c-412 0 -463 -2 -468 -16 -3 -9 -6 -110 -6 -226 l0 +-210 40 -39 40 -39 483 0 483 0 24 28 c14 16 32 31 39 33 12 4 14 37 12 173 +l-2 167 40 40 41 39 458 0 c343 0 461 3 470 12 9 9 12 73 12 226 l0 214 -40 +39 -40 39 -488 -1 -487 0 -34 -36z"/> +</g> +</svg> diff --git a/gui/public/provider-icons/synthetic.svg b/gui/public/provider-icons/synthetic.svg new file mode 100644 index 0000000000..87dfcb7383 --- /dev/null +++ b/gui/public/provider-icons/synthetic.svg @@ -0,0 +1,12 @@ +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><g clip-path="url(#SvgjsClipPath1024)"><rect width="1000" height="1000" fill="#ffffff"></rect><g transform="matrix(14.814814814814815,0,0,14.814814814814815,100,100)"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="54" height="54"><svg width="54" height="54" viewBox="0 0 54 54" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M27.8517 26.3314C27.8439 26.1451 27.8371 25.9303 27.8337 25.692C27.8203 24.7571 27.8545 23.4581 28.0457 22.0052C28.4322 19.0688 29.4394 15.6521 31.8222 13.2693C34.205 10.8867 37.6212 9.87992 40.5575 9.49344C42.0107 9.30219 43.3101 9.26734 44.245 9.28076C44.4829 9.28417 44.6968 9.29231 44.883 9.30009C44.8908 9.48595 44.8975 9.69933 44.901 9.93676C44.9144 10.8716 44.8795 12.171 44.6883 13.6242C44.3018 16.5605 43.2958 19.9773 40.9131 22.3602C38.5303 24.743 35.1129 25.7495 32.1765 26.136C30.7234 26.3272 29.4246 26.3614 28.4897 26.348C28.2518 26.3446 28.0378 26.3392 27.8517 26.3314Z" stroke="black" stroke-width="2"></path> +<path d="M27.8916 27.1592C28.0179 27.0219 28.165 26.8653 28.3311 26.6943C28.9826 26.0238 29.9253 25.1295 31.0879 24.2373C33.4375 22.4342 36.5658 20.7305 39.9355 20.7305C43.3051 20.7306 46.4327 22.4343 48.7822 24.2373C49.945 25.1296 50.8885 26.0238 51.54 26.6943C51.7059 26.865 51.8514 27.022 51.9775 27.1592C51.8516 27.2961 51.7055 27.4518 51.54 27.6221C50.8885 28.2926 49.945 29.1868 48.7822 30.0791C46.4327 31.8821 43.3052 33.5868 39.9355 33.5869C36.5657 33.5869 33.4375 31.8822 31.0879 30.0791C29.9252 29.1868 28.9826 28.2926 28.3311 27.6221C28.1652 27.4514 28.0177 27.2963 27.8916 27.1592Z" stroke="black" stroke-width="2"></path> +<path d="M27.4459 27.6915C27.6323 27.6837 27.8471 27.677 28.0854 27.6736C29.0202 27.6602 30.3192 27.6943 31.7721 27.8856C34.7085 28.272 38.1253 29.2793 40.5081 31.6621C42.8906 34.0448 43.8974 37.4611 44.2839 40.3973C44.4752 41.8505 44.51 43.1499 44.4966 44.0848C44.4932 44.3228 44.485 44.5367 44.4773 44.7229C44.2914 44.7306 44.078 44.7374 43.8406 44.7408C42.9057 44.7542 41.6063 44.7194 40.1531 44.5281C37.2168 44.1417 33.8 43.1356 31.4172 40.753C29.0343 38.3701 28.0278 34.9528 27.6413 32.0163C27.4501 30.5633 27.4159 29.2644 27.4294 28.3296C27.4328 28.0916 27.4381 27.8777 27.4459 27.6915Z" stroke="black" stroke-width="2"></path> +<path d="M26.916 28.1289C27.0533 28.2552 27.2099 28.4023 27.3809 28.5684C28.0514 29.2199 28.9457 30.1626 29.8379 31.3252C31.641 33.6748 33.3447 36.8031 33.3447 40.1728C33.3446 43.5424 31.6409 46.67 29.8379 49.0195C28.9456 50.1823 28.0514 51.1258 27.3809 51.7773C27.2102 51.9432 27.0532 52.0887 26.916 52.2148C26.7791 52.0889 26.6234 51.9428 26.4531 51.7773C25.7826 51.1258 24.8884 50.1823 23.9961 49.0195C22.1931 46.67 20.4884 43.5426 20.4883 40.1728C20.4883 36.803 22.193 33.6748 23.9961 31.3252C24.8884 30.1625 25.7826 29.2199 26.4531 28.5684C26.6238 28.4025 26.7789 28.255 26.916 28.1289Z" stroke="black" stroke-width="2"></path> +<path d="M26.2147 27.691C26.2225 27.8774 26.2293 28.0922 26.2327 28.3305C26.2461 29.2653 26.2119 30.5643 26.0207 32.0172C25.6342 34.9536 24.627 38.3704 22.2442 40.7532C19.8614 43.1357 16.4452 44.1425 13.5089 44.529C12.0557 44.7203 10.7563 44.7551 9.82145 44.7417C9.58349 44.7383 9.36957 44.7301 9.1834 44.7224C9.17564 44.5365 9.16886 44.3231 9.16545 44.0857C9.15203 43.1508 9.18689 41.8514 9.37813 40.3982C9.76458 37.4619 10.7706 34.0451 13.1533 31.6623C15.5361 29.2795 18.9535 28.2729 21.8899 27.8865C23.343 27.6952 24.6419 27.6611 25.5767 27.6745C25.8146 27.6779 26.0286 27.6833 26.2147 27.691Z" stroke="black" stroke-width="2"></path> +<path d="M25.7021 27.1602C25.5759 27.2974 25.4288 27.4541 25.2627 27.625C24.6111 28.2955 23.6684 29.1899 22.5059 30.082C20.1562 31.8851 17.028 33.5889 13.6582 33.5889C10.2886 33.5887 7.16105 31.885 4.81153 30.082C3.64873 29.1897 2.70529 28.2955 2.05372 27.625C1.88786 27.4543 1.74236 27.2973 1.61622 27.1602C1.74215 27.0232 1.88824 26.8676 2.05372 26.6973C2.70527 26.0267 3.64874 25.1326 4.81153 24.2402C7.16108 22.4372 10.2885 20.7326 13.6582 20.7324C17.0281 20.7324 20.1562 22.4371 22.5059 24.2402C23.6686 25.1325 24.6112 26.0268 25.2627 26.6973C25.4285 26.8679 25.576 27.023 25.7021 27.1602Z" stroke="black" stroke-width="2"></path> +<path d="M25.7338 25.9813C25.5474 25.9891 25.3326 25.9959 25.0943 25.9993C24.1595 26.0127 22.8605 25.9785 21.4076 25.7873C18.4712 25.4008 15.0544 24.3936 12.6716 22.0108C10.2891 19.628 9.28226 16.2118 8.89579 13.2755C8.70453 11.8223 8.66969 10.5229 8.6831 9.58806C8.68652 9.35009 8.69466 9.13618 8.70244 8.95C8.8883 8.94224 9.10168 8.93546 9.33911 8.93205C10.274 8.91863 11.5734 8.95349 13.0266 9.14473C15.9629 9.53118 19.3797 10.5372 21.7625 12.9199C24.1454 15.3027 25.1519 18.7201 25.5383 21.6565C25.7296 23.1096 25.7638 24.4085 25.7503 25.3433C25.7469 25.5812 25.7415 25.7952 25.7338 25.9813Z" stroke="black" stroke-width="2"></path> +<path d="M26.9121 25.7031C26.7748 25.5769 26.6182 25.4298 26.4473 25.2637C25.7768 24.6121 24.8824 23.6694 23.9902 22.5068C22.1872 20.1572 20.4834 17.029 20.4834 13.6592C20.4835 10.2896 22.1873 7.16203 23.9902 4.8125C24.8826 3.6497 25.7767 2.70627 26.4473 2.05469C26.618 1.88883 26.775 1.74333 26.9121 1.61719C27.049 1.74312 27.2047 1.88921 27.375 2.05469C28.0455 2.70624 28.9397 3.64971 29.832 4.8125C31.6351 7.16206 33.3397 10.2895 33.3398 13.6592C33.3398 17.029 31.6351 20.1572 29.832 22.5068C28.9398 23.6695 28.0455 24.6121 27.375 25.2637C27.2043 25.4295 27.0492 25.577 26.9121 25.7031Z" stroke="black" stroke-width="2"></path> +<path d="M25.5607 19.3789L27.4618 19.3457C27.5492 19.3442 27.6286 19.3884 27.6747 19.4583L27.7093 19.5368L28.045 20.9198C28.1559 21.3761 28.4798 21.7513 28.9152 21.9272C29.3419 22.0994 29.8251 22.0595 30.2183 21.8205L31.3036 21.1608C31.3771 21.116 31.4662 21.1134 31.5407 21.1488L31.6099 21.1965L32.9965 22.5729C33.0577 22.6336 33.0825 22.7203 33.0659 22.8012L33.035 22.8799L32.4788 23.8048C32.2386 24.2041 32.1867 24.6853 32.3299 25.122L32.4034 25.3063C32.5966 25.715 32.9524 26.0199 33.3793 26.1498L33.5666 26.1943L34.4238 26.3454C34.5414 26.3663 34.628 26.4675 34.6302 26.587L34.6642 28.532C34.6662 28.6469 34.5891 28.7489 34.478 28.7784L33.3785 29.0711C32.9508 29.1849 32.5956 29.4757 32.399 29.8657L32.3249 30.0389C32.1648 30.4848 32.2187 30.9794 32.4714 31.3803L33.0028 32.2237C33.0497 32.2981 33.0532 32.3898 33.0168 32.4656L32.9672 32.5349L31.5394 33.9438C31.4825 33.9998 31.4038 34.0241 31.3277 34.0129L31.2531 33.9898L30.2136 33.4737C29.8594 33.298 29.4528 33.2687 29.0801 33.387L28.9219 33.4464C28.5639 33.6057 28.2838 33.8968 28.1361 34.2552L28.0812 34.4124L27.7617 35.5207C27.7312 35.6261 27.6352 35.6996 27.5255 35.7016L25.5874 35.7354C25.4685 35.7375 25.3646 35.6555 25.3388 35.5396L25.0832 34.3808L25.0342 34.2068C24.9146 33.8684 24.6748 33.5849 24.361 33.4108L24.1976 33.3335L24.0433 33.2806C23.7308 33.1931 23.3978 33.2118 23.0968 33.3332L22.9495 33.4022L21.7729 34.0361C21.6979 34.0764 21.6097 34.0746 21.5375 34.0363L21.4722 33.9867L20.1375 32.5586C20.0776 32.4944 20.0574 32.4054 20.0787 32.3242L20.1135 32.2474L20.7373 31.3321C20.9834 30.9707 21.049 30.5199 20.923 30.108L20.8575 29.9343L20.7817 29.7901C20.6149 29.5119 20.3581 29.2988 20.0542 29.1855L19.8981 29.1365L18.4973 28.7898C18.3875 28.7625 18.3097 28.6649 18.3075 28.5518L18.275 26.6888C18.2733 26.5838 18.3372 26.4906 18.4321 26.4527L18.4738 26.4402L19.6961 26.1854C20.1451 26.0918 20.5232 25.7997 20.7294 25.3977L20.8074 25.2186C20.9466 24.8306 20.9208 24.4049 20.7399 24.0399L20.6523 23.8881L20.0481 22.9571C19.9825 22.8563 19.9975 22.723 20.0845 22.64L21.5852 21.2083C21.6715 21.126 21.8046 21.1157 21.9022 21.1843L22.718 21.758C23.1295 22.0472 23.659 22.111 24.1274 21.9278C24.5375 21.7673 24.8531 21.4365 24.9966 21.0267L25.0481 20.8461L25.3209 19.5765C25.3453 19.4629 25.4445 19.3809 25.5607 19.3789Z" fill="white" stroke="black" stroke-width="1.5"></path> +<circle cx="26.4669" cy="27.5406" r="2.53895" transform="rotate(-1 26.4669 27.5406)" fill="white" stroke="black" stroke-width="1.5"></circle> +</svg></svg></g></g><defs><clipPath id="SvgjsClipPath1024"><rect width="1000" height="1000" x="0" y="0" rx="250" ry="250"></rect></clipPath></defs></svg> \ No newline at end of file diff --git a/gui/public/provider-icons/together.svg b/gui/public/provider-icons/together.svg new file mode 100644 index 0000000000..2fcc258c45 --- /dev/null +++ b/gui/public/provider-icons/together.svg @@ -0,0 +1,18 @@ +<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 484.98 452.5"> + <defs> + <style> + .cls-1 { + fill: #ef2cc1; + } + .cls-2 { + fill: #caaef5; + } + .cls-3 { + fill: #fc4c02; + } + </style> + </defs> + <path class="cls-1" d="M468.72,60.67C435.25,2.69,361.11-17.18,303.12,16.3c-37.3,21.54-58.82,59.62-60.52,99.69l121.15.16v10.39h-121.15c.78,18.94,6.02,37.81,16.15,55.36,33.48,57.98,107.62,77.85,165.6,44.37,57.98-33.48,77.85-107.62,44.37-165.6Z"/> + <path class="cls-2" d="M16.26,60.63C-17.21,118.61,2.65,192.76,60.63,226.23c37.3,21.54,81.04,21.13,116.59,2.57l-60.44-105,9-5.18,60.57,104.91c16.01-10.14,29.74-24.12,39.87-41.67,33.48-57.98,13.61-132.12-44.37-165.6C123.88-17.21,49.74,2.65,16.26,60.63Z"/> + <path class="cls-3" d="M242.46,452.5c66.95,0,121.23-54.27,121.23-121.23,0-43.07-22.22-80.75-56.07-102.25l-60.71,104.84-8.99-5.21,60.57-104.91c-16.79-8.79-35.75-13.7-56.02-13.7-66.95,0-121.23,54.27-121.23,121.23,0,66.95,54.27,121.23,121.23,121.23Z"/> +</svg> \ No newline at end of file diff --git a/gui/public/provider-icons/umans.svg b/gui/public/provider-icons/umans.svg new file mode 100644 index 0000000000..c6176f143a --- /dev/null +++ b/gui/public/provider-icons/umans.svg @@ -0,0 +1,30 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160.000000 160.000000"><g transform="translate(0.000000,160.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"><path fill="#f4f4f4" d="M400 1015 l0 -265 75 0 75 0 -2 263 -3 262 -72 3 -73 3 0 -266z +M1080 1015 l0 -265 70 0 70 0 0 265 0 265 -70 0 -70 0 0 -265z M813 723 l-243 +-3 0 -65 0 -65 239 0 239 0 4 70 c2 39 3 69 3 68 0 -2 -109 -4 -242 -5z"/><path fill="#f372a5" d="M402 413 l3 -158 70 0 70 0 3 158 3 157 -76 0 -76 0 3 -157z M1072 +413 l3 -158 66 -3 c48 -2 69 1 78 11 8 10 11 59 9 158 l-3 144 -78 3 -78 3 3 +-158z"/><path fill="#7e7e7e" d="M394 1015 c0 -154 2 -216 3 -137 2 79 2 205 0 280 -1 75 -3 11 -3 +-143z M552 1024 c-1 -199 1 -273 10 -282 9 -9 76 -11 253 -10 l240 2 -245 6 +-245 5 -5 275 -5 275 -3 -271z M1065 1277 c-3 -7 -4 -129 -3 -272 l3 -260 5 +273 c3 149 4 272 2 272 -1 0 -5 -6 -7 -13z M1145 740 l-80 -6 77 -2 c43 -1 80 +2 83 8 3 6 5 9 3 8 -2 -1 -39 -5 -83 -8z M477 733 l72 -4 3 -77 3 -77 255 0 +255 0 1 75 1 75 -4 -72 -4 -73 -249 0 -249 0 -3 78 -3 77 -75 1 -75 1 72 -4z"/><path fill="#d0d0d0" d="M420 1284 c60 -8 130 -8 130 -1 0 4 -35 6 -77 6 -43 -1 -67 -3 -53 +-5z M1077 1284 c-4 -4 -7 -125 -7 -269 0 -190 -3 -264 -12 -273 -15 -15 -484 +-17 -493 -2 -3 6 -42 9 -88 8 l-82 -2 80 -5 80 -6 3 -77 3 -78 249 0 250 0 0 +68 c0 37 4 72 8 79 4 7 38 13 82 15 l75 3 0 270 0 270 -70 3 c-39 1 -74 0 -78 +-4z m143 -269 l0 -265 -70 0 -70 0 0 265 0 265 70 0 70 0 0 -265z m-168 -355 +l-4 -70 -239 0 -239 0 0 65 0 65 243 3 c133 1 242 3 242 5 0 1 -1 -29 -3 -68z"/><path fill="#382b30" d="M433 1293 c26 -2 67 -2 90 0 23 2 2 3 -48 3 -49 0 -68 -1 -42 -3z +M1067 1293 c-3 -4 33 -7 79 -5 l85 3 -3 -280 c-2 -154 0 -277 5 -272 4 4 6 +131 5 282 l-3 274 -82 3 c-45 1 -84 -1 -86 -5z M562 1018 c-1 -147 1 -269 6 +-271 4 -3 6 74 4 171 -4 334 -8 367 -10 100z M1052 880 c0 -14 2 -19 5 -12 2 +6 2 18 0 25 -3 6 -5 1 -5 -13z M1046 755 c-3 -8 -1 -15 4 -15 6 0 10 7 10 15 +0 8 -2 15 -4 15 -2 0 -6 -7 -10 -15z M528 723 c7 -3 16 -2 19 1 4 3 -2 6 -13 +5 -11 0 -14 -3 -6 -6z M1074 716 c-3 -7 -3 -42 -1 -77 l3 -64 2 72 c1 46 6 73 +15 76 9 4 9 6 -1 6 -7 1 -15 -5 -18 -13z M1230 417 l5 -162 3 154 c1 85 -1 +158 -5 162 -5 5 -6 -65 -3 -154z M564 415 c0 -88 2 -123 3 -77 2 46 2 118 0 +160 -1 42 -3 5 -3 -83z M1069 247 c4 -4 43 -7 87 -5 l79 3 -87 5 c-48 3 -83 1 +-79 -3z M433 243 c26 -2 67 -2 90 0 23 2 2 3 -48 3 -49 0 -68 -1 -42 -3z"/><path fill="#89425e" d="M397 573 c-10 -9 -9 -323 1 -323 4 0 6 72 4 160 l-3 160 76 0 76 0 +-3 -160 c-2 -88 0 -160 5 -160 4 0 7 74 7 165 l0 165 -78 0 c-43 0 -82 -3 -85 +-7z M1060 421 c0 -88 3 -162 8 -164 4 -3 6 67 4 155 l-3 160 80 -4 c43 -2 77 +0 74 5 -2 4 -40 7 -84 7 l-79 0 0 -159z M1215 260 c-3 -5 -1 -10 4 -10 6 0 11 +5 11 10 0 6 -2 10 -4 10 -3 0 -8 -4 -11 -10z"/></g></svg> \ No newline at end of file diff --git a/gui/public/provider-icons/venice.svg b/gui/public/provider-icons/venice.svg new file mode 100644 index 0000000000..d914e79c3b --- /dev/null +++ b/gui/public/provider-icons/venice.svg @@ -0,0 +1,165 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160.000000 160.000000"><g transform="translate(0.000000,160.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"><path fill="#102b43" d="M637 1382 c-26 -27 -27 -32 -27 -137 l0 -110 87 -87 c48 -49 95 -88 +103 -88 8 0 55 39 103 88 l87 87 0 111 c0 108 -1 111 -28 137 l-28 27 -59 -60 +c-33 -33 -66 -60 -74 -60 -9 0 -43 27 -76 60 l-61 60 -27 -28z m95 -59 l58 +-57 0 -113 c0 -62 -3 -113 -7 -113 -4 0 -33 26 -65 57 l-58 57 0 113 c0 62 3 +113 7 113 4 0 33 -26 65 -57z m208 -56 l0 -113 -58 -57 c-32 -31 -61 -57 -65 +-57 -4 0 -7 50 -7 112 l0 112 57 58 c32 32 61 58 65 58 4 0 8 -51 8 -113z +M263 1264 c-4 -10 13 -35 53 -75 l58 -59 -48 -49 -49 -50 44 -3 44 -3 3 -47 +c2 -27 6 -48 10 -48 3 0 28 21 54 47 l48 48 143 -143 142 -142 -32 -32 c-31 +-31 -33 -31 -52 -14 -21 19 -51 16 -51 -5 0 -7 8 -21 17 -31 15 -17 15 -20 +-11 -47 l-28 -29 -41 19 c-88 40 -191 18 -254 -55 -41 -46 -57 -97 -50 -163 7 +-68 38 -118 95 -154 92 -58 186 -47 263 30 74 73 84 146 38 259 -13 32 -13 35 +15 58 28 25 29 25 50 6 37 -34 65 0 31 38 -14 15 -13 20 6 43 26 34 49 34 79 +2 22 -23 22 -26 7 -43 -36 -39 -9 -75 29 -40 21 19 22 19 50 -6 28 -23 28 -26 +15 -58 -46 -113 -36 -186 38 -259 77 -77 171 -88 263 -30 57 36 88 86 95 154 +7 66 -9 117 -50 163 -63 73 -166 95 -254 55 l-41 -19 -28 29 c-27 28 -27 29 +-9 48 18 21 15 51 -6 51 -7 0 -21 -8 -31 -17 -18 -16 -20 -15 -51 15 l-32 32 +142 142 143 143 48 -48 c26 -26 51 -47 54 -47 4 0 8 21 10 48 l3 47 44 3 44 3 +-49 50 -48 49 57 58 c59 60 68 78 41 89 -11 4 -87 -66 -270 -248 l-254 -254 +-253 253 c-138 138 -258 252 -265 252 -7 0 -15 -7 -19 -16z m275 -690 c12 -14 +22 -36 22 -48 0 -19 7 -25 35 -30 106 -20 94 -176 -14 -176 -16 0 -21 -6 -21 +-29 0 -59 -76 -100 -132 -70 -22 12 -52 74 -43 89 4 6 -6 10 -20 10 -90 0 +-123 125 -44 165 15 8 37 15 49 15 12 0 19 4 16 9 -3 5 -1 23 5 40 22 63 102 +76 147 25z m631 21 c28 -14 56 -68 45 -86 -3 -5 4 -9 16 -9 12 0 34 -7 49 -15 +79 -40 46 -165 -44 -165 -14 0 -24 -4 -20 -10 9 -15 -21 -77 -43 -89 -56 -30 +-132 11 -132 70 0 25 -4 29 -29 29 -40 0 -81 43 -81 85 0 43 33 83 75 91 27 5 +35 11 35 28 0 40 44 83 88 85 7 1 26 -6 41 -14z"/><path fill="#c7cdd1" d="M624 1388 l-29 -33 33 29 c17 17 32 31 32 33 0 8 -8 1 -36 -29z M940 +1417 c0 -2 15 -16 33 -33 l32 -29 -29 33 c-28 30 -36 37 -36 29z M548 1040 +l252 -255 253 255 252 255 -252 -250 -253 -250 -252 250 -253 250 253 -255z +M685 1040 c49 -49 92 -90 95 -90 2 0 -36 41 -85 90 -49 50 -92 90 -95 90 -2 0 +36 -40 85 -90z M905 1038 l-90 -93 93 90 c85 83 97 95 89 95 -1 0 -43 -42 -92 +-92z M250 1018 c0 -5 26 -7 58 -7 61 1 55 6 -15 11 -24 2 -43 0 -43 -4z M1290 +1020 l-55 -7 58 -1 c32 -1 57 2 57 8 0 6 -1 9 -2 8 -2 -1 -28 -4 -58 -8z M418 +955 c-31 -30 -53 -55 -49 -55 4 0 31 25 61 55 70 71 62 71 -12 0z M1172 955 +c32 -32 59 -57 61 -55 1 3 -24 30 -58 60 l-60 54 57 -59z M723 718 c-19 -19 +-24 -20 -40 -8 -17 13 -17 13 0 -5 17 -19 18 -19 44 8 15 15 25 27 22 27 -2 0 +-14 -10 -26 -22z M872 715 c27 -29 28 -29 45 -10 17 18 17 18 0 5 -15 -11 -21 +-10 -45 11 l-27 23 27 -29z M620 705 c-6 -8 -10 -19 -8 -24 1 -6 8 1 15 14 13 +28 11 31 -7 10z M972 698 c7 -15 15 -23 17 -17 2 6 -4 18 -13 27 -15 15 -15 +15 -4 -10z M448 633 c12 -2 32 -2 45 0 12 2 2 4 -23 4 -25 0 -35 -2 -22 -4z +M624 618 l-19 -23 23 19 c12 11 22 21 22 23 0 8 -8 2 -26 -19z M965 620 c10 +-11 20 -20 23 -20 3 0 -3 9 -13 20 -10 11 -20 20 -23 20 -3 0 3 -9 13 -20z +M1108 633 c12 -2 30 -2 40 0 9 3 -1 5 -23 4 -22 0 -30 -2 -17 -4z M760 626 c0 +-2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M819 613 c-13 -16 +-12 -17 4 -4 9 7 17 15 17 17 0 8 -8 3 -21 -13z M468 593 c7 -3 16 -2 19 1 4 +3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1118 593 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 +-11 0 -14 -3 -6 -6z M414 568 l-19 -23 23 19 c12 11 22 21 22 23 0 8 -8 2 -26 +-19z M1175 570 c10 -11 20 -20 23 -20 3 0 -3 9 -13 20 -10 11 -20 20 -23 20 +-3 0 3 -9 13 -20z M675 560 c-10 -11 -16 -22 -14 -24 2 -2 12 7 22 20 22 29 +17 32 -8 4z M769 563 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 +-17 -17z M810 576 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z +M916 558 c10 -14 21 -24 23 -22 3 3 -6 15 -18 27 l-24 22 19 -27z M358 483 c7 +-3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M578 483 c7 -3 16 -2 19 1 +4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1008 483 c7 -3 16 -2 19 1 4 3 -2 6 -13 +5 -11 0 -14 -3 -6 -6z M1228 483 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 +-3 -6 -6z M304 458 l-19 -23 23 19 c12 11 22 21 22 23 0 8 -8 2 -26 -19z +M1285 460 c10 -11 20 -20 23 -20 3 0 -3 9 -13 20 -10 11 -20 20 -23 20 -3 0 3 +-9 13 -20z M242 410 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z +M1352 410 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M629 353 c-13 +-16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 -17z M950 366 c0 -2 8 +-10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M545 330 c-3 -6 1 -7 9 +-4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z M1030 336 c0 -2 7 -7 16 -10 8 -3 12 +-2 9 4 -6 10 -25 14 -25 6z M310 245 c13 -14 26 -25 28 -25 3 0 -5 11 -18 25 +-13 14 -26 25 -28 25 -3 0 5 -11 18 -25z M1279 243 l-24 -28 28 24 c15 14 27 +26 27 28 0 8 -8 1 -31 -24z M519 243 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 +-2 0 -10 -8 -17 -17z M1060 256 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 +-21 21 -21 13z"/><path fill="#2d4459" d="M670 1409 c0 -2 27 -31 60 -64 33 -33 60 -57 60 -54 0 2 -27 31 -60 +64 -33 33 -60 57 -60 54z M865 1348 l-60 -63 63 60 c34 33 62 61 62 62 0 8 +-10 -1 -65 -59z M661 1378 c3 -2 34 -30 69 -63 l65 -60 -60 63 c-33 34 -64 62 +-69 62 -5 0 -7 -1 -5 -2z M865 1318 l-60 -63 65 60 c35 33 66 61 69 63 2 1 0 +2 -5 2 -5 0 -36 -28 -69 -62z M310 1188 l54 -58 -47 -48 c-26 -26 -47 -51 -47 +-55 0 -4 21 -6 48 -5 l47 1 -44 4 -44 4 49 50 49 49 -60 58 -60 57 55 -57z +M1280 1185 l-54 -55 48 -49 49 -50 -44 -4 -44 -4 48 -1 c26 -1 47 1 47 5 0 4 +-21 29 -47 55 l-47 48 54 55 c30 30 52 55 49 55 -2 0 -29 -25 -59 -55z M717 +1100 c34 -35 64 -62 65 -60 2 3 -26 32 -62 65 l-65 59 62 -64z M873 1100 c-33 +-33 -58 -60 -54 -60 4 0 33 27 66 60 77 78 68 78 -12 0z M615 878 c77 -79 142 +-142 145 -140 5 3 -272 282 -280 282 -3 0 57 -64 135 -142z M975 880 c-76 -76 +-137 -141 -135 -142 3 -2 68 61 145 140 78 78 138 142 135 142 -3 0 -68 -63 +-145 -140z M620 683 c0 -5 10 -18 23 -29 l22 -19 -20 25 c-11 14 -21 27 -22 +29 -2 2 -3 0 -3 -6z M764 668 l-19 -23 23 19 c21 18 27 26 19 26 -2 0 -12 -10 +-23 -22z M831 664 c13 -17 26 -30 28 -27 3 2 -8 16 -24 31 l-28 27 24 -31z +M956 662 l-19 -27 24 22 c12 12 21 24 18 27 -2 2 -13 -8 -23 -22z M458 623 +c12 -2 30 -2 40 0 9 3 -1 5 -23 4 -22 0 -30 -2 -17 -4z M1108 623 c12 -2 30 +-2 40 0 9 3 -1 5 -23 4 -22 0 -30 -2 -17 -4z M458 603 c6 -2 18 -2 25 0 6 3 1 +5 -13 5 -14 0 -19 -2 -12 -5z M1118 603 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 +-19 -2 -12 -5z M700 596 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 +-21 13z M879 583 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 +-17z M314 558 l-29 -33 33 29 c30 28 37 36 29 36 -2 0 -16 -15 -33 -32z M664 +568 l-19 -23 23 19 c21 18 27 26 19 26 -2 0 -12 -10 -23 -22z M925 570 c10 +-11 20 -20 23 -20 3 0 -3 9 -13 20 -10 11 -20 20 -23 20 -3 0 3 -9 13 -20z +M1250 587 c0 -2 15 -16 33 -33 l32 -29 -29 33 c-28 30 -36 37 -36 29z M353 +493 c9 -2 23 -2 30 0 6 3 -1 5 -18 5 -16 0 -22 -2 -12 -5z M568 493 c6 -2 18 +-2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z M1008 493 c6 -2 18 -2 25 0 6 3 1 +5 -13 5 -14 0 -19 -2 -12 -5z M1223 493 c9 -2 23 -2 30 0 6 3 -1 5 -18 5 -16 +0 -22 -2 -12 -5z M251 404 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 +-13z M1341 404 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M290 356 +c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M1289 343 c-13 +-16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 -17z M619 253 l-24 -28 +28 24 c15 14 27 26 27 28 0 8 -8 1 -31 -24z M970 255 c13 -14 26 -25 28 -25 3 +0 -5 11 -18 25 -13 14 -26 25 -28 25 -3 0 5 -11 18 -25z M400 246 c0 -2 8 -10 +18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M1179 233 c-13 -16 -12 -17 4 +-4 16 13 21 21 13 21 -2 0 -10 -8 -17 -17z"/><path fill="#556878" d="M629 1383 l-24 -28 28 24 c25 23 32 31 24 31 -2 0 -14 -12 -28 -27z +M960 1385 c13 -14 26 -25 28 -25 3 0 -5 11 -18 25 -13 14 -26 25 -28 25 -3 0 +5 -11 18 -25z M664 1265 c0 -60 1 -84 3 -52 2 32 2 81 0 110 -2 29 -3 3 -3 +-58z M934 1265 c0 -60 1 -84 3 -52 2 32 2 81 0 110 -2 29 -3 3 -3 -58z M604 +1245 c0 -60 1 -84 3 -52 2 32 2 81 0 110 -2 29 -3 3 -3 -58z M994 1245 c0 -60 +1 -84 3 -52 2 32 2 81 0 110 -2 29 -3 3 -3 -58z M281 1288 c3 -2 113 -109 244 +-238 l240 -235 -235 238 c-129 130 -239 237 -244 237 -5 0 -7 -1 -5 -2z M1070 +1053 l-235 -238 240 235 c131 129 241 236 244 238 2 1 0 2 -5 2 -5 0 -115 +-107 -244 -237z M690 1045 c47 -47 87 -85 90 -85 2 0 -33 38 -80 85 -47 47 +-87 85 -90 85 -2 0 33 -38 80 -85z M870 1013 l-55 -58 58 55 c31 30 57 56 57 +57 0 8 -10 -1 -60 -54z M415 958 l-50 -53 53 50 c48 46 57 55 49 55 -1 0 -25 +-24 -52 -52z M1175 960 c27 -27 51 -50 54 -50 3 0 -17 23 -44 50 -27 28 -51 +50 -54 50 -3 0 17 -22 44 -50z M629 703 c-13 -16 -12 -17 4 -4 9 7 17 15 17 +17 0 8 -8 3 -21 -13z M677 699 c12 -12 24 -21 27 -18 2 2 -8 13 -22 23 l-27 +19 22 -24z M911 700 c-13 -11 -19 -20 -14 -20 6 0 18 9 28 20 23 25 17 25 -14 +0z M950 716 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M750 +626 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M829 613 +c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 -17z M348 323 c6 -2 +18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z M550 321 c0 -4 11 -6 25 -3 14 +2 25 6 25 8 0 2 -11 4 -25 4 -14 0 -25 -4 -25 -9z M1000 326 c0 -2 11 -6 25 +-8 14 -3 25 -1 25 3 0 5 -11 9 -25 9 -14 0 -25 -2 -25 -4z M1228 323 c6 -2 18 +-2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z M315 250 c10 -11 20 -20 23 -20 3 +0 -3 9 -13 20 -10 11 -20 20 -23 20 -3 0 3 -9 13 -20z M1274 248 l-19 -23 23 +19 c12 11 22 21 22 23 0 8 -8 2 -26 -19z"/><path fill="#96a1ab" d="M732 1355 l68 -70 52 55 53 55 -53 -50 -52 -49 -68 64 -67 65 67 -70z +M720 1315 c30 -30 57 -55 59 -55 3 0 -19 25 -49 55 -30 30 -57 55 -59 55 -3 0 +19 -25 49 -55z M870 1313 l-55 -58 58 55 c53 50 62 60 54 60 -1 0 -27 -26 -57 +-57z M300 1188 l54 -58 -49 -52 -50 -53 55 53 55 52 -60 58 -60 57 55 -57z +M1290 1185 l-55 -55 55 -53 55 -52 -50 53 -49 52 54 55 c30 30 52 55 49 55 -2 +0 -29 -25 -59 -55z M720 1105 c30 -30 57 -55 59 -55 3 0 -19 25 -49 55 -30 30 +-57 55 -59 55 -3 0 19 -25 49 -55z M875 1108 l-50 -53 53 50 c48 46 57 55 49 +55 -1 0 -25 -24 -52 -52z M363 960 c0 -30 2 -43 4 -27 2 15 2 39 0 55 -2 15 +-4 2 -4 -28z M610 878 c125 -128 140 -143 140 -135 0 1 -62 63 -137 137 l-138 +135 135 -137z M980 873 l-135 -138 138 135 c75 74 137 136 137 137 0 8 -15 -7 +-140 -134z M1233 960 c0 -30 2 -43 4 -27 2 15 2 39 0 55 -2 15 -4 2 -4 -28z +M769 663 c-13 -16 -12 -17 4 -4 9 7 17 15 17 17 0 8 -8 3 -21 -13z M810 676 +c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M620 666 c0 -2 8 +-10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M959 653 c-13 -16 -12 +-17 4 -4 9 7 17 15 17 17 0 8 -8 3 -21 -13z M700 586 c0 -2 8 -10 18 -17 15 +-13 16 -12 3 4 -13 16 -21 21 -21 13z M879 573 c-13 -16 -12 -17 4 -4 16 13 +21 21 13 21 -2 0 -10 -8 -17 -17z M392 520 c0 -14 2 -19 5 -12 2 6 2 18 0 25 +-3 6 -5 1 -5 -13z M1202 520 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 +-13z M282 410 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M1312 410 +c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M392 300 c0 -14 2 -19 5 +-12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M1202 300 c0 -14 2 -19 5 -12 2 6 2 18 0 +25 -3 6 -5 1 -5 -13z M400 256 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 +-21 21 -21 13z M1179 243 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 +-8 -17 -17z"/><path fill="#707f8d" d="M812 1163 c-1 -56 1 -104 6 -106 4 -3 6 27 4 66 -5 129 -9 144 -10 +40z M784 1155 c0 -55 1 -76 3 -47 2 29 2 74 0 100 -2 26 -3 2 -3 -53z M959 +1103 l-34 -38 38 34 c34 33 42 41 34 41 -2 0 -19 -17 -38 -37z M760 816 c0 -2 +8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M819 803 c-13 -16 -12 +-17 4 -4 9 7 17 15 17 17 0 8 -8 3 -21 -13z M724 708 l-19 -23 23 19 c21 18 +27 26 19 26 -2 0 -12 -10 -23 -22z M865 710 c10 -11 20 -20 23 -20 3 0 -3 9 +-13 20 -10 11 -20 20 -23 20 -3 0 3 -9 13 -20z M624 608 l-19 -23 23 19 c21 +18 27 26 19 26 -2 0 -12 -10 -23 -22z M965 610 c10 -11 20 -20 23 -20 3 0 -3 +9 -13 20 -10 11 -20 20 -23 20 -3 0 3 -9 13 -20z M409 573 c-13 -16 -12 -17 4 +-4 16 13 21 21 13 21 -2 0 -10 -8 -17 -17z M670 565 c-13 -14 -22 -27 -19 -29 +2 -2 14 10 27 25 29 36 24 38 -8 4z M921 564 c13 -17 26 -30 28 -27 3 2 -8 16 +-24 31 l-28 27 24 -31z M1170 586 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 +16 -21 21 -21 13z M309 563 c-13 -16 -12 -17 4 -4 9 7 17 15 17 17 0 8 -8 3 +-21 -13z M1270 576 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 +13z M692 410 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M902 410 c0 +-14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M458 183 c6 -2 18 -2 25 0 6 +3 1 5 -13 5 -14 0 -19 -2 -12 -5z M1118 183 c6 -2 18 -2 25 0 6 3 1 5 -13 5 +-14 0 -19 -2 -12 -5z"/></g></svg> \ No newline at end of file diff --git a/gui/public/provider-icons/vultr.svg b/gui/public/provider-icons/vultr.svg new file mode 100644 index 0000000000..69e60c18d9 --- /dev/null +++ b/gui/public/provider-icons/vultr.svg @@ -0,0 +1,15 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160.000000 160.000000"><g transform="translate(0.000000,160.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"><path fill="#007cfb" d="M956 1459 c-17 -13 -26 -30 -26 -48 0 -39 307 -528 350 -558 54 -38 +80 -16 185 156 28 45 69 111 93 147 43 68 52 103 28 123 -7 6 -37 50 -67 97 +-31 51 -63 90 -77 95 -13 5 -121 9 -241 9 -206 0 -220 -1 -245 -21z M235 1145 +c-14 -13 -25 -35 -25 -49 0 -14 47 -98 110 -199 61 -96 110 -179 110 -185 0 +-6 4 -12 8 -14 8 -3 112 -155 112 -163 0 -9 234 -372 253 -392 27 -30 77 -30 +104 0 21 24 191 292 217 344 16 31 15 34 -12 80 -25 41 -231 371 -331 528 -17 +27 -41 55 -54 62 -16 9 -87 12 -245 13 l-223 0 -24 -25z"/><path fill="#0079f5" d="M1490 1420 c6 -11 13 -20 16 -20 2 0 0 9 -6 20 -6 11 -13 20 -16 20 +-2 0 0 -9 6 -20z M1570 1180 c-6 -11 -8 -20 -6 -20 3 0 10 9 16 20 6 11 8 20 +6 20 -3 0 -10 -9 -16 -20z M531 553 c7 -12 15 -20 18 -17 3 2 -3 12 -13 22 +-17 16 -18 16 -5 -5z M1120 550 c6 -11 13 -20 16 -20 2 0 0 9 -6 20 -6 11 -13 +20 -16 20 -2 0 0 -9 6 -20z M1116 473 c-6 -14 -5 -15 5 -6 7 7 10 15 7 18 -3 +3 -9 -2 -12 -12z M838 113 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 +-5z"/><path fill="#006ad8" d="M1103 1483 c59 -2 155 -2 215 0 59 1 10 3 -108 3 -118 0 -167 -2 +-107 -3z"/></g></svg> \ No newline at end of file diff --git a/gui/public/provider-icons/zai.svg b/gui/public/provider-icons/zai.svg new file mode 100644 index 0000000000..536f3521ee --- /dev/null +++ b/gui/public/provider-icons/zai.svg @@ -0,0 +1,218 @@ +<?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve"> +<style type="text/css"> + .st0{opacity:0.3;fill:#E2E4E7;} + .st1{opacity:0.8;fill:#E2E4E7;stroke:#FFFFFF;stroke-width:5;stroke-miterlimit:10;} + .st2{fill:url(#SVGID_1_);} + .st3{fill:none;stroke:#E0E4E9;stroke-width:0.25;stroke-miterlimit:10;} + .st4{fill:none;} + .st5{fill:#9DA1A5;} + .st6{fill-rule:evenodd;clip-rule:evenodd;fill:none;} + .st7{fill-rule:evenodd;clip-rule:evenodd;fill:#DFE2E7;} + .st8{fill-rule:evenodd;clip-rule:evenodd;fill:#CDD4DA;} + .st9{fill-rule:evenodd;clip-rule:evenodd;fill:#B3BCC7;} + .st10{fill-rule:evenodd;clip-rule:evenodd;fill:#9DAAB7;} + .st11{fill-rule:evenodd;clip-rule:evenodd;fill:#8698A8;} + .st12{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_2_);} + .st13{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_3_);} + .st14{fill:#1F63EC;} + .st15{fill:#2D2D2D;} + .st16{fill:none;stroke:#E0E4E9;stroke-width:0.5;stroke-miterlimit:10;} + .st17{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_4_);} + .st18{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_5_);} + .st19{fill:none;stroke:#677380;stroke-width:0.5;stroke-miterlimit:10;} + .st20{fill:none;stroke:url(#SVGID_6_);stroke-width:2;stroke-miterlimit:10;} + .st21{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_7_);} + .st22{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_8_);} + .st23{fill:#FFFFFF;} + .st24{fill-rule:evenodd;clip-rule:evenodd;fill:#2D2D2D;} + .st25{clip-path:url(#SVGID_10_);} + .st26{clip-path:url(#SVGID_12_);} + .st27{fill:url(#SVGID_13_);} + .st28{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_14_);} + .st29{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_15_);} + .st30{clip-path:url(#SVGID_17_);} + .st31{clip-path:url(#SVGID_19_);} + .st32{fill:url(#SVGID_20_);} + .st33{fill:none;stroke:url(#SVGID_21_);stroke-width:2;stroke-miterlimit:10;} + .st34{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_22_);} + .st35{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_23_);} + .st36{clip-path:url(#SVGID_25_);} + .st37{clip-path:url(#SVGID_27_);} + .st38{fill:url(#SVGID_28_);} + .st39{clip-path:url(#SVGID_30_);} + .st40{clip-path:url(#SVGID_32_);} + .st41{fill:url(#SVGID_33_);} + .st42{fill-rule:evenodd;clip-rule:evenodd;fill:#126EF6;} + .st43{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;} + .st44{clip-path:url(#SVGID_35_);} + .st45{clip-path:url(#SVGID_37_);} + .st46{fill:url(#SVGID_38_);} + .st47{fill-rule:evenodd;clip-rule:evenodd;fill:#9DA1A5;} + .st48{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_39_);} + .st49{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_40_);} + .st50{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_41_);} + .st51{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_42_);} + .st52{fill:none;stroke:url(#SVGID_43_);stroke-width:2;stroke-miterlimit:10;} + .st53{fill-rule:evenodd;clip-rule:evenodd;fill:none;stroke:#E0E4E9;stroke-width:0.5;stroke-miterlimit:10;} + .st54{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_44_);} + .st55{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_45_);} + .st56{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_46_);} + .st57{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_47_);} + .st58{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_48_);} + .st59{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_49_);} + .st60{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_50_);} + .st61{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_51_);} + .st62{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_52_);} + .st63{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_53_);} + .st64{clip-path:url(#SVGID_55_);} + .st65{clip-path:url(#SVGID_57_);} + .st66{fill:url(#SVGID_58_);} + .st67{clip-path:url(#SVGID_60_);} + .st68{clip-path:url(#SVGID_62_);} + .st69{fill:url(#SVGID_63_);} + .st70{fill:none;stroke:url(#SVGID_64_);stroke-width:2;stroke-miterlimit:10;} + .st71{clip-path:url(#SVGID_66_);} + .st72{clip-path:url(#SVGID_68_);} + .st73{fill:url(#SVGID_69_);} + .st74{clip-path:url(#SVGID_71_);} + .st75{clip-path:url(#SVGID_73_);} + .st76{fill:url(#SVGID_74_);} + .st77{clip-path:url(#SVGID_76_);} + .st78{clip-path:url(#SVGID_78_);} + .st79{fill:url(#SVGID_79_);} + .st80{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_80_);} + .st81{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_81_);} + .st82{clip-path:url(#SVGID_83_);} + .st83{clip-path:url(#SVGID_85_);} + .st84{fill:url(#SVGID_86_);} + .st85{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_87_);} + .st86{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_88_);} + .st87{clip-path:url(#SVGID_90_);} + .st88{clip-path:url(#SVGID_92_);} + .st89{fill:url(#SVGID_93_);} + .st90{fill:none;stroke:url(#SVGID_94_);stroke-width:2;stroke-miterlimit:10;} + .st91{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_95_);} + .st92{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_96_);} + .st93{clip-path:url(#SVGID_98_);} + .st94{clip-path:url(#SVGID_100_);} + .st95{fill:url(#SVGID_101_);} + .st96{clip-path:url(#SVGID_103_);} + .st97{clip-path:url(#SVGID_105_);} + .st98{fill:url(#SVGID_106_);} + .st99{clip-path:url(#SVGID_108_);} + .st100{clip-path:url(#SVGID_110_);} + .st101{fill:url(#SVGID_111_);} + .st102{fill:#FFFFFF;stroke:#B3BCC7;stroke-width:0.275;stroke-miterlimit:10;} + .st103{clip-path:url(#SVGID_113_);} + .st104{fill:#FDD138;} + .st105{fill:#FCA62F;} + .st106{fill:#FB7927;} + .st107{fill:#F44B22;} + .st108{fill:#D81915;} + .st109{fill:#2D2D2D;stroke:#FFFFFF;stroke-width:0.3354;stroke-miterlimit:10;} + .st110{fill:none;stroke:#65727F;stroke-width:2;stroke-miterlimit:10;} + .st111{fill:none;stroke:#65727F;stroke-width:0.75;stroke-miterlimit:10;} + .st112{fill:url(#SVGID_114_);} + .st113{fill:#D06C50;} + .st114{fill:#2D2D2D;stroke:#B3BCC7;stroke-width:0.275;stroke-miterlimit:10;} + .st115{opacity:0.2;} + .st116{fill:none;stroke:#677380;stroke-width:0.3564;stroke-miterlimit:10;} + .st117{fill:none;stroke:#677380;stroke-width:0.3564;stroke-miterlimit:10;stroke-dasharray:1.0212,1.0212;} + .st118{fill:none;stroke:#677380;stroke-width:0.3564;stroke-miterlimit:10;stroke-dasharray:1.0205,1.0205;} + .st119{opacity:0.2;fill:none;} + .st120{fill:none;stroke:#677380;stroke-width:0.3689;stroke-miterlimit:10;} + .st121{fill:none;stroke:#677380;stroke-width:0.3689;stroke-miterlimit:10;stroke-dasharray:1.0509,1.0509;} + .st122{opacity:0.3;fill:#1F63EC;} + .st123{fill:#2D2D2D;stroke:#FFFFFF;stroke-width:0.3162;stroke-miterlimit:10;} + .st124{fill:#FFFFFF;stroke:#B3BCC7;stroke-width:0.3162;stroke-miterlimit:10;} + .st125{clip-path:url(#SVGID_118_);} + .st126{fill:url(#SVGID_119_);} + .st127{fill:none;stroke:#DFE2E7;stroke-width:0.75;stroke-miterlimit:10;} + .st128{fill:#9DA1A5;stroke:#FFFFFF;stroke-miterlimit:10;} + .st129{fill:url(#SVGID_120_);} + .st130{fill:none;stroke:#677380;stroke-width:0.75;stroke-miterlimit:10;} + .st131{opacity:0.4;} + .st132{clip-path:url(#SVGID_122_);} + .st133{clip-path:url(#SVGID_124_);} + .st134{fill:url(#SVGID_125_);} + .st135{fill:none;stroke:#8392A3;stroke-width:0.35;stroke-miterlimit:10;} + .st136{fill:none;stroke:#8392A3;stroke-width:0.35;stroke-miterlimit:10;stroke-dasharray:0.9951,0.9951;} + .st137{fill:none;stroke:#8392A3;stroke-width:0.35;stroke-miterlimit:10;stroke-dasharray:1.004,1.004;} + .st138{fill:none;stroke:url(#SVGID_126_);stroke-width:1.5;stroke-miterlimit:10;} + .st139{fill:url(#SVGID_127_);} + .st140{fill:none;stroke:#DDE0E4;stroke-width:0.35;stroke-miterlimit:10;} + .st141{fill:#2D2D2D;stroke:#A9B3BE;stroke-width:0.275;stroke-miterlimit:10;} + .st142{fill-rule:evenodd;clip-rule:evenodd;fill:#126EF4;} + .st143{fill:#FFFFFF;stroke:#B1BAC4;stroke-width:0.275;stroke-miterlimit:10;} + .st144{fill:#CE6C50;} + .st145{fill:#5B5B5B;} + .st146{fill:#8392A3;} + .st147{fill:none;stroke:url(#SVGID_128_);stroke-width:1.5;stroke-miterlimit:10;} + .st148{fill:url(#SVGID_129_);} + .st149{fill:none;stroke:#B5BDC4;stroke-width:0.7;stroke-miterlimit:10;} + .st150{opacity:0.6;fill:none;stroke:#78838E;stroke-width:0.35;stroke-miterlimit:10;} + .st151{opacity:0.2;fill:none;stroke:#8392A3;stroke-width:0.35;stroke-miterlimit:10;stroke-dasharray:1,1;} + .st152{fill:none;stroke:#DDE0E4;stroke-width:0.75;stroke-miterlimit:10;} + .st153{fill:none;stroke:#8392A3;stroke-width:0.5;stroke-miterlimit:10;} + .st154{opacity:0.2;fill:none;stroke:#677380;stroke-width:0.3564;stroke-miterlimit:10;stroke-dasharray:1.0182,1.0182;} + .st155{fill:none;stroke:#DDE0E4;stroke-width:0.765;stroke-miterlimit:10;} + .st156{fill:url(#SVGID_130_);} + .st157{fill:url(#SVGID_131_);} + .st158{fill:#B1BAC4;} + .st159{fill:#CBD1D8;} + .st160{fill:#0B1B2B;} + .st161{fill:#91D119;} + .st162{opacity:0.7;} + .st163{fill:#FFFFFF;stroke:#000000;stroke-width:0.4418;stroke-miterlimit:10;} + .st164{fill:none;stroke:#939CAA;stroke-width:0.2209;stroke-miterlimit:10;} + .st165{fill:none;stroke:#FFFFFF;stroke-width:3.0924;stroke-miterlimit:10;} + .st166{fill:url(#SVGID_132_);} + .st167{fill:none;stroke:url(#SVGID_133_);stroke-width:1.714;stroke-miterlimit:10;} + .st168{fill:url(#SVGID_134_);} + .st169{fill:url(#SVGID_135_);} + .st170{fill:url(#SVGID_136_);} + .st171{fill:url(#SVGID_137_);} + .st172{fill:url(#SVGID_138_);} + .st173{fill:url(#SVGID_139_);} + .st174{fill:url(#SVGID_140_);} + .st175{fill:url(#SVGID_141_);} + .st176{fill:url(#SVGID_142_);} + .st177{fill:url(#SVGID_143_);} + .st178{fill:url(#SVGID_144_);} + .st179{fill:none;stroke:#1F63EC;stroke-width:4;stroke-miterlimit:10;} + .st180{fill:none;stroke:#0B1B2B;stroke-width:4;stroke-miterlimit:10;} + .st181{fill:none;stroke:#677380;stroke-width:0.3989;stroke-miterlimit:10;} + .st182{fill:none;stroke:#677380;stroke-width:0.3989;stroke-miterlimit:10;stroke-dasharray:1.14,1.14;} + .st183{fill:#257AF1;} + .st184{opacity:0.3;fill:#FFFFFF;} + .st185{fill:none;stroke:#98A5B2;stroke-width:4;stroke-miterlimit:10;} + .st186{fill:none;stroke:#65727F;stroke-width:0.3989;stroke-miterlimit:10;} + .st187{fill:none;stroke:#65727F;stroke-width:0.3989;stroke-miterlimit:10;stroke-dasharray:1.14,1.14;} + .st188{fill:none;stroke:#DDDFE4;stroke-width:0.75;stroke-miterlimit:10;} + .st189{fill:#9A9EA2;} + .st190{fill-rule:evenodd;clip-rule:evenodd;fill:#3267AC;} + .st191{fill:#FFFFFF;stroke:#AFB8C3;stroke-width:0.275;stroke-miterlimit:10;} + .st192{fill:#C5694E;} + .st193{fill:#8192A2;} + .st194{fill:#2D2D2D;stroke:#FFFFFF;stroke-width:0.6317;stroke-miterlimit:10;} +</style> +<g id="图层_2"> +</g> +<g id="图层_1"> + <path class="st194" d="M24.51,28.51H5.49c-2.21,0-4-1.79-4-4V5.49c0-2.21,1.79-4,4-4h19.03c2.21,0,4,1.79,4,4v19.03 + C28.51,26.72,26.72,28.51,24.51,28.51z"/> + <g> + <g> + <g> + <g> + <path class="st23" d="M15.47,7.1l-1.3,1.85c-0.2,0.29-0.54,0.47-0.9,0.47h-7.1V7.09C6.16,7.1,15.47,7.1,15.47,7.1z"/> + <polygon class="st23" points="24.3,7.1 13.14,22.91 5.7,22.91 16.86,7.1 "/> + <path class="st23" d="M14.53,22.91l1.31-1.86c0.2-0.29,0.54-0.47,0.9-0.47h7.09v2.33H14.53z"/> + </g> + </g> + </g> + </g> +</g> +</svg> \ No newline at end of file diff --git a/gui/public/provider-icons/zenmux.svg b/gui/public/provider-icons/zenmux.svg new file mode 100644 index 0000000000..0f0f3b1800 --- /dev/null +++ b/gui/public/provider-icons/zenmux.svg @@ -0,0 +1 @@ +<svg width="160" height="160" xmlns="http://www.w3.org/2000/svg" fill="none"><path stroke="null" fill="#000" d="M77.776.421c8.25-.317 24.947.775 38.304 12.768 10.957 9.839 14.733 28.078 12.18 42.221-2.341 12.965-7.858 23.572-5.696 33.394 2.16 9.821 7.658 14.663 12.334 16.696 4.519 1.965 8.886 1.375 8.055 5.108-.832 3.732-6.482 5.107-10.608 5.107-2.262 0-9.103-.59-12.139-2.554-3.587-2.322-4.386-4.402-5.752-6.074-.231-.284-.64-.087-.586.276.622 4.113 2.338 11.094 5.12 15.423 2.252 3.503 3.183 6.058 8.341 10.38 5.16 4.32 7.57 8.249 2.553 11.031-4.456 2.471-14.142-2.585-19.839-8.839-5.127-5.629-8.697-12.572-10.108-17.09-.755-2.415-1.511-6.57-2.266-9.969-.1-.446-.711-.374-.725.084-.25 8.649-1.61 16.909-3.598 25.764-1.43 6.375-4.91 14.143-8.053 18.857-3.833 5.749-9.522 7.543-13.75 5.971-3.382-1.257-.983-5.531 0-9.232.982-3.7 2.947-11.439 4.27-19.918 1.197-7.674 1.514-15.271.981-22.47-.033-.439-.708-.506-.844-.087-1.102 3.376-3.18 8.972-4.696 12.376-3.162 7.099-5.501 10.214-11.983 17.677-5.83 6.714-22.597 16.207-24.75 6.876-.59-2.553 2.498-3.671 5.345-5.893 4.28-3.339 7.857-9.264 11.248-16.5 3.045-6.497 4.592-12.782 4.83-16.794.022-.387-.464-.533-.682-.213-1.41 2.067-3.485 4.865-5.771 7.185-3.819 3.877-8.054 7.867-12.966 9.822-7.897 3.143-15.95.982-15.361-4.714.492-4.758 5.804-3.773 12.416-10.987 6.482-7.072 8.643-12.573 9.035-18.662.314-4.872-4.451-16.434-6.874-21.607-3.863-8.25-6.718-31.665 4.91-46.75C55.188.226 72.67.617 77.777.42zM55.101 33.397c-5.408 0-8.112 4.151-8.112 9.272 0 5.12 3.004 9.271 8.112 9.271 5.108 0 8.113-4.15 8.113-9.271 0-5.12-2.705-9.272-8.113-9.272zm44.04 0c-5.408 0-8.112 4.151-8.113 9.272 0 5.12 3.005 9.271 8.113 9.271s8.113-4.15 8.113-9.271c0-5.12-2.705-9.272-8.113-9.272z" clip-rule="evenodd" fill-rule="evenodd"/></svg> \ No newline at end of file diff --git a/gui/src/provider-icons.ts b/gui/src/provider-icons.ts index 043529cf67..2bf4d6c5a3 100644 --- a/gui/src/provider-icons.ts +++ b/gui/src/provider-icons.ts @@ -44,6 +44,41 @@ const PROVIDER_ICON_ALIASES: Record<string, string> = { alibaba: "alibaba-color.svg", "alibaba-token-plan": "alibaba-color.svg", "alibaba-token-plan-intl": "alibaba-color.svg", + baseten: "baseten.svg", + bizrouter: "bizrouter.svg", + cerebras: "cerebras.svg", + deepinfra: "deepinfra.svg", + digitalocean: "digitalocean.svg", + featherless: "featherless.svg", + hyperbolic: "hyperbolic.svg", + kilo: "kilo.svg", + nanogpt: "nanogpt.svg", + nebius: "nebius.svg", + neuralwatt: "neuralwatt.svg", + nous: "nous.svg", + novita: "novita.svg", + orcarouter: "orcarouter.svg", + parallel: "parallel.svg", + sambanova: "sambanova.svg", + scaleway: "scaleway.svg", + siliconflow: "siliconflow.svg", + synthetic: "synthetic.svg", + together: "together.svg", + umans: "umans.svg", + venice: "venice.svg", + vultr: "vultr.svg", + litellm: "litellm.svg", + zenmux: "zenmux.svg", + /* + * Z.AI and Zhipu's BigModel are the same company on two brands. `zai` is the + * international GLM Coding Plan and the mark comes from z.ai; the two + * `zhipu-bigmodel*` ids are the mainland console, which publishes only a + * horizontal wordmark, so they borrow it rather than render a lockup squeezed + * into a 19px box. + */ + zai: "zai.svg", + "zhipu-bigmodel": "zai.svg", + "zhipu-bigmodel-coding": "zai.svg", "qwen-cloud": "qwen-portal-color.svg", "vercel-ai-gateway": "vercel-ai-gateway-color.svg", vllm: "vllm-color.svg", diff --git a/gui/tests/provider-marks-assets.test.ts b/gui/tests/provider-marks-assets.test.ts new file mode 100644 index 0000000000..b6350cafff --- /dev/null +++ b/gui/tests/provider-marks-assets.test.ts @@ -0,0 +1,66 @@ +import { expect, test } from "bun:test"; +import { readFileSync, readdirSync } from "node:fs"; +import { join } from "node:path"; +import { PROVIDER_REGISTRY } from "../../src/providers/registry"; +import { providerIconSrc } from "../src/provider-icons"; + +const PUBLIC_DIR = join(import.meta.dir, "..", "public", "provider-icons"); + +function bodyOf(src: string): string { + return readFileSync(join(PUBLIC_DIR, src.replace(/^\/provider-icons\//, "")), "utf8"); +} + +/** Every asset a provider resolves to, deduplicated -- several ids share one file. */ +function wiredAssets(): string[] { + const seen = new Set<string>(); + for (const entry of PROVIDER_REGISTRY) { + const src = providerIconSrc(entry.id); + if (src) seen.add(src); + } + return [...seen].sort(); +} + +/* + * The three ways a mark passes an SVG parse and still fails as artwork. + * + * A `<text>` glyph renders per-machine and blank where the font lacks the + * character -- that is what disqualified the Hermes repo favicon, 113 bytes whose + * entire body was one text element. An `<image>` or a base64 payload is a raster + * wearing an SVG costume: it will not scale into the 19px tile and cannot be + * masked. Neither is visible in review; both are visible here. + */ +test("every wired provider mark is drawn geometry, not text or a wrapped raster", () => { + const broken: string[] = []; + for (const src of wiredAssets()) { + const body = bodyOf(src); + if (/<text[\s>]/.test(body)) broken.push(`${src}: renders a <text> glyph`); + if (/<image[\s>]/.test(body)) broken.push(`${src}: embeds a raster`); + if (/;base64,/.test(body)) broken.push(`${src}: carries a base64 payload`); + if (!/<(path|circle|rect|polygon|ellipse|line|polyline)[\s>]/.test(body)) { + broken.push(`${src}: carries no vector geometry`); + } + } + expect(broken).toEqual([]); +}); + +/* + * A wordmark in a square slot. + * + * The rail draws a 19px box. A horizontal lockup scaled into it is an illegible + * smear, which is what disqualified the MiniMax docs asset (129x32) in the + * previous unit and several vendor logos in this one. The viewBox is the only + * thing that says which shape a file is, and it is not something review catches. + * + * The threshold is deliberately loose: 2.5 admits a slightly wide mark and still + * rejects the 4:1-and-up lockups that actually caused the problem. + */ +test("no wired provider mark is a horizontal wordmark", () => { + const lockups: string[] = []; + for (const src of wiredAssets()) { + const box = bodyOf(src).match(/viewBox="[-\d.eE]+[ ,]+[-\d.eE]+[ ,]+([\d.eE]+)[ ,]+([\d.eE]+)"/); + if (!box) continue; + const ratio = Number(box[1]) / Number(box[2]); + if (Number.isFinite(ratio) && ratio > 2.5) lockups.push(`${src}: ${ratio.toFixed(2)}:1`); + } + expect(lockups).toEqual([]); +}); From 5f0b39048b261b133a839143e48c68120ea9d03f Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 02:41:05 +0900 Subject: [PATCH 18/42] fix(responses): bound the durable spill directory with an aggregate byte cap (#3097) * fix(responses): bound the durable spill directory with an aggregate byte cap The response store has an unconditional RAM ceiling (MAX_STORED_RESPONSE_BYTES, 64 MiB) and demotes the oldest resident entry to a durable spill once it is crossed. Nothing bounded where those bytes landed: the spilled set was capped only per file (MAX_RESPONSE_SPILL_PAYLOAD_BYTES, 256 MiB) and per entry (MAX_STORED_RESPONSES, 1000). Their product is 250 GiB, larger than the disk of any host this runs on, so the only effective bound was RESPONSE_TTL_MS and disk use became a function of client request rate rather than of anything this process controls. Measured on one macOS host, 2026-08-30: a client spilling ~150 MB payloads at ~1.4/min held 6.8 GB of ~/.opencodex/responses-state-spill after 44 minutes and was still climbing toward the ~12 GB an hour-long window implies. It filled the volume, at which point unrelated processes began failing with ENOSPC. Retention itself was correct throughout - the TTL evicted that whole cohort an hour later - so this is a missing budget, not a leak. Add MAX_SPILLED_RESPONSE_BYTES (1 GiB), enforced by one function, enforceSpilledResponseBudget, with three callers: mutation pruning, the lazy load that follows a restart, and the periodic sweep. The periodic caller is not redundant. The mutation path runs only when traffic arrives, so a process that comes up over budget - from a snapshot written under a larger ceiling, or a build that lowered it - would otherwise stay over while idle. That was observed here at 1.8 GiB against a 1 GiB cap, held until the first request. sweepExpiredResponseStates still returns its TTL count, so its existing contract is unchanged. The ceiling bounds what the store can account for: every entry in the map plus the superseded generations queued in pendingSpillUnlinks, whose files stay on disk until a snapshot flush drains them and would otherwise let up to 32 GiB sit outside the budget while it reported itself satisfied. Over budget those deferred generations are released before any live entry, which is the same trade the queue's own overflow path already makes against unbounded disk. Spill files orphaned by a crash are absent from the map, so this accounting can neither see nor price them; they remain with recoverOrphanedResponseSpills and its grace window, and structure/02 now states that allowance and its bound explicitly. Eviction of live entries is ordered by createdAt, not by map order. `states` is not an age index: demotion and spill replacement delete and reinsert entries, and writeBoundedSnapshot serializes the map reversed, so map order can put a newer continuation first. createdAt is millisecond-resolution and ties are ordinary under load, where a stable sort would fall back to insertion order, so ties break on the response id by direct comparison rather than localeCompare, since the order must not depend on the host locale. The total is recomputed per enforcement rather than carried as a running counter: spilled entries reach `states` through several insertion paths (demotion swap, direct oversized admission, snapshot reload), and one missed increment there would silently disable the cap, where a walk over at most MAX_STORED_RESPONSES entries cannot drift. 1 GiB comes from the same sample (n=31), whose spilled sizes are strongly bimodal: median 1.1 MiB against a p90 of 198.7 MiB. At that median the count cap and this ceiling bind within 8% of each other (1000 x 1.1 MiB = 1.07 GiB), so ordinary traffic sees no eviction it would not already have seen and only the large tail is cut. The value is the one knob here a maintainer may reasonably want to change. Six regressions, each confirmed to fail without the code it covers: the budget is enforced and the oldest spill is the one removed; eviction follows createdAt rather than insertion order; ties break on the id; a single payload larger than the whole budget leaves the store usable rather than wedged; deferred generations count against the cap and drain first; and an over-budget snapshot is reclaimed with no continuation mutation at all - a read drives the load path and a later tick drives the periodic one, with the newest entry surviving and still replaying. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(responses): reserve the peak publication footprint against the disk cap The aggregate cap counted installed spills and deferred unlinks - files that already exist. It could not see one that writeResponseSpillDurablyAsync was in the middle of creating, and on Windows that middle lasts as long as icacls takes. A cap that holds only when writes are fast is not a cap; the incident behind this work put 6.8 GiB on disk in 44 minutes. A queued publication now reserves its peak on-disk footprint, and the cap is enforced against files-plus-reservations before the temp or destination file is created rather than by deleting the overflow afterwards. The reserved figure is two envelopes, not one. Publication can fall back from hard-linking to an exclusive copy, and during that fallback the destination copy and the temp file exist together, so reserving a single payload would leave the overshoot intact at half its magnitude. Ownership is single and settles on every exit. A queued job holds its reservation until releasePendingResponseSpill, which the finally in runPendingResponseSpill reaches from every return, throw and mismatch, and which cancellation reaches for a job that never ran. The shutdown fallback re-reserves for the duration of its synchronous write, because supersession releases the original reservation immediately before the heaviest publication of the drain - and that write has the same link-then-copy fallback. A leaked reservation would be monotonic, ratcheting the usable cap toward zero until nothing could spill at all. Regression drives the accounting red: with an in-flight publication gated on icacls, the walk over states reports 0 bytes while the reservation reports the two-envelope peak, and after settlement the accounting collapses to the real file. Carries lifrary's b4d1d2404 unmodified as the base. * fix(responses): price the spill cap from real occupancy, not from a proxy Review of the reservation commit found four ways the accounting still undercounted what is on the volume. The reservation was derived from candidate.sizeBytes, which measures the resident shape and omits the version field the published envelope carries. Admission is now priced from prospectiveResponseSpillBytes, which shares the production serializer, so the figure cannot drift from what is written. A same-id replacement removes the old spill from states and hands its ref to the pending job. Neither states nor pendingSpillUnlinks could see it, so a copy fallback held old generation plus temp plus destination - three envelopes priced as two. Job-owned superseded generations are now counted. Shutdown supersession released the reservation even when cleanup reported it could not remove the async temp or destination. Those bytes are not a reservation, because nothing will release them: the file could not be deleted. They move to a separate unreclaimable total that is never decremented, which is the only honest way to price a file nobody can remove. Startup orphan recovery is what reclaims them across a restart. The regression is rewritten to prove the cap rather than the counter wiring. It seeds real prior occupancy, forces link failure into the COPYFILE_EXCL fallback, gates destination hardening, and asserts against files actually on disk - three of them - while the walk over installed spills still reports one. After settlement it asserts the accounting collapses to the real files, no temp survives, and the newest continuation still replays. * fix(responses): make the spill cap enforce, and let cleanup debt be repaid Second review round on the reservation work found three ways the accounting still did not match the volume. A same-id replacement takes the old spill off states and hands it to the new job, but admission ran before the job existed, so the decision was short by a whole envelope. The inherited generation is now priced in the check itself. Cancellation also left the ref on the cancelled job while returning it to the caller, so the accounting walk could count one physical file twice and evict live continuations to reclaim bytes that were not there; ownership now transfers rather than being copied. Cleanup-failure debt was a flat two envelopes that never decremented. Both halves were wrong. clearOwnedPath nulls whichever path it managed to remove, so one failure is often one file; and a Windows lock that clears a moment later, or the async writer's own retry, can remove the file while the charge stayed forever. With 256 MiB payloads two such charges consume the whole default cap and nothing can spill again for the life of the process. The debt is now per path, priced at what that path holds, and settled as soon as the path is gone. The shutdown fallback checked nothing before writing. It now reclaims and, if the footprint still does not fit, terminalizes with ENOSPC rather than publishing onto a volume that is already over budget - the same fail-closed ending the budget-exhaustion path uses. The regression is split in two, because the previous single test proved the counter and not the cap: it stayed green with the admission branch deleted. One test now proves accounting during a forced COPYFILE_EXCL fallback with temp and destination both on disk; the other proves enforcement, and it is red when admission is removed. * fix(responses): price the superseded generation in the shutdown fallback too Third review round found the last accounting hole, and it is shutdown-only. supersedeShutdownFallbackBatch releases the job, which takes it out of pendingResponseSpills and therefore out of the accounting walk - but its superseded generation is still a file on the volume until deferSupersededSpill or a delete takes it. The fallback preflight priced cleanup debt plus its own footprint and missed that envelope entirely. The gap is reachable: same-id replacement owns an old generation O, async cleanup fails leaving path debt D, and the fallback publishes footprint F. A cap sitting between D+F and O+D+F admits a publication that puts the directory over budget - which is the shape this whole phase exists to prevent. Regression covers exactly that: first generation settles, a same-id replacement makes the job its owner, the cap is set between the two totals, and the drain must refuse rather than add a third envelope. Red without the fix. * docs(devlog): record what wp2's review rounds changed about the plan --------- Co-authored-by: SEUNGWOO LEE <69357689+lifrary@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> --- .../020_wp2_spill_disk_budget.md | 26 ++ src/responses/spill-store.ts | 20 + src/responses/state.ts | 290 ++++++++++++++- structure/00_overview.md | 2 +- structure/02_config-and-codex-home.md | 28 ++ tests/responses-state.test.ts | 345 ++++++++++++++++++ 6 files changed, 708 insertions(+), 3 deletions(-) diff --git a/devlog/_plan/260831_prio70_train_round2/020_wp2_spill_disk_budget.md b/devlog/_plan/260831_prio70_train_round2/020_wp2_spill_disk_budget.md index 98ffee3bfe..2e0f7a6a22 100644 --- a/devlog/_plan/260831_prio70_train_round2/020_wp2_spill_disk_budget.md +++ b/devlog/_plan/260831_prio70_train_round2/020_wp2_spill_disk_budget.md @@ -128,6 +128,32 @@ Suite, typecheck and privacy scan on `ssh lidge`. Residual risk, same as round 1's wp3: NTFS unlink semantics and `icacls` timeout behaviour while a path is held still want a real Windows host. + +## What implementation added beyond this plan + +Four adversarial review rounds against the built branch (findings 4, 3, 1, 0). Three of +their findings changed the design rather than the code, so they belong here: + +- **The footprint is measured, not estimated.** The plan said "exact prospective + measurement" and the first implementation used `candidate.sizeBytes`, which omits the + `version` field the published envelope carries. `prospectiveResponseSpillBytes` now + shares `serializedSpill` itself, so the two cannot drift. +- **Superseded generations are priced in two places, not one.** A same-id replacement + takes the old spill off `states` and hands it to the pending job. It is invisible to + the accounting walk at admission (the job does not exist yet) and again in the shutdown + fallback (supersession has already released the job). Both checks add it explicitly. +- **Cleanup debt is per path and repayable.** The plan did not anticipate a failed + unlink. A flat charge that never decrements is phantom debt: a Windows lock that clears, + or the async writer's own retry, removes the file while the charge stays, and two + conservative 256 MiB charges consume the whole default cap for the life of the process. + The debt is keyed by path and settled when the path is gone. +- **The shutdown fallback fails closed.** If the footprint still does not fit after + reclaim, it terminalizes with ENOSPC rather than publishing onto an over-budget volume. + +And one about verification: the first regression stayed green with the admission check +deleted, because `pruneResponses` reclaimed on another path. It proved the counter, not +the cap. There are now two tests — one for accounting during a forced copy fallback, one +for enforcement — and the enforcement test is red when admission is removed. ### Amendment after audit round 1 (`002`, blocker 3): the peak is two envelopes The first draft reserved one serialized envelope. Windows publication can fall back diff --git a/src/responses/spill-store.ts b/src/responses/spill-store.ts index f61d475c5a..ce063dfd8b 100644 --- a/src/responses/spill-store.ts +++ b/src/responses/spill-store.ts @@ -470,6 +470,26 @@ function serializedSpill( }; } +/** + * Exact on-disk payload size this spill WOULD occupy, measured before publication. + * + * Callers that reserve disk against a cap need the real envelope, not the resident + * measurement: the resident figure omits the `version` field the published payload + * carries, so pricing an admission by it undercounts and lets a request that sits exactly + * at the cap still exceed it. Shares `serializedSpill` rather than describing it, so the + * two cannot drift. + */ +export function prospectiveResponseSpillBytes( + responseId: string, + state: Omit<ResponseSpillPayload, "version" | "responseId">, +): number | null { + try { + return serializedSpill(responseId, state).bytes.byteLength; + } catch { + return null; + } +} + function responseSpillWriteError(cause: unknown): NodeJS.ErrnoException { const error = new Error("Response spill write failed", { cause }) as NodeJS.ErrnoException; if (cause && typeof cause === "object" && "code" in cause) { diff --git a/src/responses/state.ts b/src/responses/state.ts index 35540a0ee7..b95a1fa2c6 100644 --- a/src/responses/state.ts +++ b/src/responses/state.ts @@ -16,6 +16,7 @@ import { responseSpillDirectory, responseSpillPayloadCap, markResponseSpillPublicationSuperseded, + prospectiveResponseSpillBytes, type ResponseSpillPublicationControl, type ResponseSpillRef, writeResponseSpillDurably, @@ -35,6 +36,30 @@ const SNAPSHOT_DEBOUNCE_MAX_MS = 30_000; * continuation chains) stores the full expanded input each turn — ~quadratic bytes per chain — * so a count cap alone cannot bound memory. Oldest-first eviction applies past this mark. */ export const MAX_STORED_RESPONSE_BYTES = 64 * 1024 * 1024; +/** + * Aggregate ceiling for the durable spill directory: the disk-side counterpart to + * the RAM ceiling above. Without it the spilled set is bounded only per-file + * (MAX_RESPONSE_SPILL_PAYLOAD_BYTES, 256 MiB) and per-entry (MAX_STORED_RESPONSES, + * 1000), whose product is 250 GiB — larger than the disk of any host this runs on. + * The only effective bound was therefore RESPONSE_TTL_MS, which makes disk use a + * function of client request rate rather than of anything this process controls. + * + * Measured on one macOS host, 2026-08-30: a client spilling ~150 MB payloads at + * ~1.4/min held 6.8 GB after 44 minutes, still climbing toward the ~12 GB an + * hour-long window implies, and filled the volume. Retention itself was correct + * throughout — the TTL evicted that whole cohort an hour later — so what was + * missing is a budget, not a sweep. + * + * 1 GiB comes from the same sample (n=31), whose spilled sizes are strongly + * bimodal: median 1.1 MiB against a p90 of 198.7 MiB, near the per-file ceiling. + * At that median the count cap and this ceiling bind within 8% of each other + * (1000 x 1.1 MiB = 1.07 GiB), so ordinary traffic sees no eviction it would not + * already have seen and only the large tail is cut. Erring small is the safe + * direction: too low costs a replay miss, an already-handled path surfaced as + * previous_response_not_found, while too high costs the host's disk and every + * unrelated process on it. + */ +export const MAX_SPILLED_RESPONSE_BYTES = 1024 * 1024 * 1024; /** Legacy snapshot selection only. Spill demotion is governed solely by the RAM cap above. */ const SNAPSHOT_ENTRY_MAX_BYTES = 2 * 1024 * 1024; const SNAPSHOT_TOTAL_MAX_BYTES = 24 * 1024 * 1024; @@ -187,12 +212,79 @@ interface PendingResponseSpill { cancelled: boolean; released: boolean; sizeBytes: number; + /** Peak on-disk bytes reserved for this publication; released exactly once on settle. */ + reservedBytes: number; publicationControl: ResponseSpillPublicationControl; } const pendingResponseSpills = new Set<PendingResponseSpill>(); const pendingResponseSpillById = new Map<string, PendingResponseSpill>(); let pendingResponseSpillBytes = 0; +/** + * On-disk bytes a queued publication is about to occupy but has not yet installed into + * `states`. + * + * `spilledResponseBytes()` walks installed spills and deferred unlinks — files that + * already exist. It cannot see one that `writeResponseSpillDurablyAsync` is in the + * middle of creating, and on Windows that middle can last as long as `icacls` takes. + * Without a reservation the cap holds only when writes are fast, which is not a cap. + * + * The reserved figure is the PEAK footprint, not the payload: publication can fall back + * from hard-linking to an exclusive copy, and during that fallback the destination copy + * and the temp file exist simultaneously. Reserving one envelope would leave the overshoot + * intact at half its magnitude. + * + * Ownership is single: a job holds its reservation from queue until + * `releasePendingResponseSpill`, which every exit from the publication path reaches + * through the `finally` in `runPendingResponseSpill` and through cancellation of a + * not-yet-running job. A leaked reservation is monotonic — it would ratchet the usable + * cap toward zero — so the release must stay on the settlement path rather than in a + * parallel bookkeeping pass. + */ +let reservedResponseSpillBytes = 0; +/** + * Paths a failed cleanup left on the volume, with the bytes each one occupies. + * + * A failed unlink leaves a real file behind, so the cap has to keep seeing it. But a + * never-decremented total would be phantom debt: a Windows lock that clears a moment + * later, or the async writer's own retry, can remove the file while the charge stays + * forever — and with 256 MiB payloads two conservative charges consume the whole default + * cap, after which nothing can spill for the life of the process. + * + * So the debt is per PATH, priced at what that path actually holds, and settled the + * moment the path is gone. `reconcileUnreclaimableSpillPaths` re-checks on every read of + * the accounted total, which is the same tick that would otherwise refuse an admission. + */ +const unreclaimableSpillPaths = new Map<string, number>(); + +function chargeUnreclaimableSpillPath(path: string | null | undefined, bytes: number): void { + if (!path || bytes <= 0) return; + unreclaimableSpillPaths.set(path, bytes); +} + +/** Drop charges for paths that have since disappeared; returns the surviving total. */ +function reconcileUnreclaimableSpillPaths(): number { + let total = 0; + for (const [path, bytes] of [...unreclaimableSpillPaths]) { + if (existsSync(path)) total += bytes; + else unreclaimableSpillPaths.delete(path); + } + return total; +} + +/** + * Peak on-disk footprint of publishing this candidate: temp plus destination copy. + * + * Measured from the production serializer rather than from `candidate.sizeBytes`. The + * resident measurement omits the `version` field the published envelope carries, so + * pricing an admission by it undercounts and lets a request sitting exactly at the cap + * still exceed it. Falls back to the resident figure only when serialization fails, which + * is the same condition that will fail the publication itself. + */ +function publicationFootprintBytes(id: string, candidate: ResidentResponseState): number { + const exact = prospectiveResponseSpillBytes(id, spillPayloadForResident(candidate)); + return (exact ?? candidate.sizeBytes) * 2; +} let responseSpillPublicationTail: Promise<void> = Promise.resolve(); let responseSpillShutdownBudgetOverride: { totalMs: number; fallbackReserveMs: number } | null = null; let responseSpillShutdownTerminalizationPassLimitOverride: number | null = null; @@ -210,6 +302,7 @@ function releasePendingResponseSpill(job: PendingResponseSpill): void { if (job.released) return; job.released = true; pendingResponseSpillBytes = Math.max(0, pendingResponseSpillBytes - job.sizeBytes); + reservedResponseSpillBytes = Math.max(0, reservedResponseSpillBytes - job.reservedBytes); pendingResponseSpills.delete(job); if (pendingResponseSpillById.get(job.id) === job) pendingResponseSpillById.delete(job.id); job.candidate = null; @@ -222,6 +315,11 @@ function cancelPendingResponseSpill(id: string): ResponseSpillRef | undefined { job.cancelled = true; markResponseSpillPublicationSuperseded(job.publicationControl); const superseded = job.supersededSpill; + // Ownership TRANSFERS to the caller. Leaving the ref on the cancelled job would let the + // accounting walk count the same physical file twice — once here and once on the + // replacement — and an overcount evicts live continuations to make room for bytes that + // are not there. + delete job.supersededSpill; // A queued job has not captured the candidate in an async frame yet, so release it now. // A running job retains its accounting until settlement and will discard its stale file. if (!job.running) releasePendingResponseSpill(job); @@ -313,6 +411,25 @@ function queuePendingResponseSpill( deferSupersededSpill(inheritedSpill); return; } + // Enforce the disk cap BEFORE the temp or destination file is created. Deleting the + // overflow afterwards is not equivalent: on Windows the file can outlive the decision + // by as long as ACL hardening takes, which is the window the measured 6.8 GiB + // accumulated in. Reclaim first, and only refuse if the peak footprint still does not + // fit — an eviction pass can free a live continuation's worth of room. + const footprint = publicationFootprintBytes(id, candidate); + // The superseded generation this job is about to own is already off `states` and not + // yet on the job, so it is invisible to the walk. Price it here or admission decides + // against a total that is short by a whole envelope. + const inheritedBytes = inheritedSpill?.payloadBytes ?? 0; + if (accountedResponseSpillBytes() + footprint + inheritedBytes > spillByteCap()) { + enforceSpilledResponseBudget(); + if (accountedResponseSpillBytes() + footprint + inheritedBytes > spillByteCap()) { + spillCounters.writeFailures += 1; + replaceWithSpillFailure(id, candidate); + deferSupersededSpill(inheritedSpill); + return; + } + } const job: PendingResponseSpill = { id, candidate, @@ -322,11 +439,13 @@ function queuePendingResponseSpill( cancelled: false, released: false, sizeBytes: candidate.sizeBytes, + reservedBytes: footprint, publicationControl: createResponseSpillPublicationControl(), }; pendingResponseSpills.add(job); pendingResponseSpillById.set(id, job); pendingResponseSpillBytes += job.sizeBytes; + reservedResponseSpillBytes += job.reservedBytes; recomputeOldestResident(); responseSpillPublicationTail = responseSpillPublicationTail .then(() => runPendingResponseSpill(job), () => runPendingResponseSpill(job)); @@ -418,7 +537,36 @@ function installShutdownFallbackSpill( aclBudgetMs: number, ): void { let ref: ResponseSpillRef | null = null; + // Supersession released this job's reservation, but the synchronous write below is the + // largest publication of the shutdown path and has its own link-then-copy fallback + // holding a temp and a destination at once. Re-reserve for its duration so the cap is + // not blind exactly where the drain does its heaviest work, and settle in `finally` so + // every return, throw and mismatch releases it. + const footprint = publicationFootprintBytes(job.id, candidate); + reservedResponseSpillBytes += footprint; try { + // Supersession released this job, so its superseded generation is no longer visible + // to the accounting walk — but the file is still on the volume until + // `deferSupersededSpill` or a delete takes it. Price it here or the fallback decides + // against a total short by that whole envelope, which is exactly the gap that lets + // `debt + footprint <= cap < old + debt + footprint` publish over budget. + const supersededBytes = job.supersededSpill?.payloadBytes ?? 0; + // The drain must not publish over the cap either. Reclaim first; if the footprint + // still does not fit — which is what unreclaimable cleanup debt looks like — the + // honest close-out is a tombstone, not another file on a volume that is already + // over budget. `replaceWithSpillFailure` is the same fail-closed ending the budget + // exhaustion path uses, so replay reports `spill_failed` and the client resends. + if (accountedResponseSpillBytes() + supersededBytes > spillByteCap()) { + enforceSpilledResponseBudget(); + if (accountedResponseSpillBytes() + supersededBytes > spillByteCap()) { + if (states.get(job.id) === candidate) { + spillCounters.writeFailures += 1; + replaceWithSpillFailure(job.id, candidate); + deferSupersededSpill(job.supersededSpill); + } + throw Object.assign(new Error("Response spill shutdown fallback exceeds the durable disk cap"), { code: "ENOSPC" }); + } + } ref = writeResponseSpillDurably(job.id, spillPayloadForResident(candidate), { aclBudgetMs }); if (ref.payloadBytes > responseSpillPayloadCap()) { deleteResponseSpill(ref); @@ -445,6 +593,8 @@ function installShutdownFallbackSpill( deferSupersededSpill(job.supersededSpill); } throw error; + } finally { + reservedResponseSpillBytes = Math.max(0, reservedResponseSpillBytes - footprint); } } @@ -477,7 +627,20 @@ function supersedeShutdownFallbackBatch( } for (const { job } of pending) { const cleanupFailure = cleanupSupersededResponseSpillPublication(job.publicationControl); - if (cleanupFailure) failures.push(cleanupFailure); + if (cleanupFailure) { + failures.push(cleanupFailure); + // Cleanup failed, so an async temp or destination is STILL on the volume. Releasing + // the reservation would un-account a file that exists, and the fallback write that + // follows reserves only its own footprint — three envelopes on disk priced as two. + // + // Charge the surviving PATHS rather than a flat two envelopes: `clearOwnedPath` + // nulls whichever it managed to remove, so one failure is one file, not two. The + // charge is settled automatically once the path disappears, which a retried unlink + // or a released Windows lock can still do. + const perPath = Math.max(1, Math.floor(job.reservedBytes / 2)); + chargeUnreclaimableSpillPath(job.publicationControl.tempPath, perPath); + chargeUnreclaimableSpillPath(job.publicationControl.destinationPath, perPath); + } releasePendingResponseSpill(job); } } @@ -603,6 +766,69 @@ export function getStoredResponseBytesForTests(): number { return storedResponseBytes; } +let spillByteCapOverride: number | null = null; + +function spillByteCap(): number { + return spillByteCapOverride ?? MAX_SPILLED_RESPONSE_BYTES; +} + +/** + * Live total of durable spill payloads. Recomputed per call rather than carried as + * a running counter: spilled entries reach `states` through several insertion paths + * (demotion swap, direct oversized admission, snapshot reload), and one missed + * increment there would silently disable the cap, where an O(MAX_STORED_RESPONSES) + * walk cannot drift. + */ +function spilledResponseBytes(): number { + let total = 0; + for (const entry of states.values()) { + if (entry.kind === "spill") total += entry.spill.payloadBytes; + } + // Superseded generations awaiting a durable snapshot are still files on disk. + // Counting only `states` would let PENDING_SPILL_UNLINKS_MAX of them sit outside + // the budget while it reports itself satisfied. + for (const ref of pendingSpillUnlinks) total += ref.payloadBytes; + return total; +} + +/** + * Accounted on-disk bytes: files that exist, plus the peak footprint of publications + * already in flight. + * + * The cap is enforced against this rather than against `spilledResponseBytes()` alone, + * because a publication that has not finished is still consuming the volume. On Windows + * the gap between "queued" and "installed" is however long `icacls` takes, and the + * measured incident this cap answers accumulated 6.8 GiB in 44 minutes. + */ +function accountedResponseSpillBytes(): number { + // Superseded generations a pending job still owns are files on disk too. A same-id + // replacement removes the old spill from `states` and hands its ref to the job, so + // counting only `states` plus `pendingSpillUnlinks` loses it for the whole publication + // — during a copy fallback that is old generation + new temp + new destination, three + // envelopes priced as two. + let ownedBySpillJobs = 0; + for (const job of pendingResponseSpills) { + if (job.supersededSpill) ownedBySpillJobs += job.supersededSpill.payloadBytes; + } + return spilledResponseBytes() + reservedResponseSpillBytes + ownedBySpillJobs + + reconcileUnreclaimableSpillPaths(); +} + +/** Test-only: lower/restore the durable spill cap (null restores the default). */ +export function setSpilledResponseByteCapForTests(bytes: number | null): void { + spillByteCapOverride = bytes; +} + +/** Test-only: current durable spill accounting (proves evictions unlink their files). */ +export function getSpilledResponseBytesForTests(): number { + return spilledResponseBytes(); +} + +/** Test-only: on-disk bytes plus in-flight publication reservations. */ +export function getAccountedResponseSpillBytesForTests(): number { + return accountedResponseSpillBytes(); +} + function serializedBytes(value: unknown): number | null { try { const serialized = JSON.stringify(value); @@ -1495,6 +1721,60 @@ export function replayOverlapSkipsForTests(): number { return replayOverlapSkips; } +/** + * Bring the durable spill set inside MAX_SPILLED_RESPONSE_BYTES, and report the + * bytes released. + * + * One owner, three callers: mutation pruning, the lazy load that follows a + * restart, and the periodic sweep. The periodic caller is not redundant — the + * mutation path only runs when traffic arrives, and a process can come up over + * budget from a snapshot written under a larger ceiling and then sit idle. That + * was observed in production at 1.8 GiB against a 1 GiB cap, held until the first + * request. + * + * NOT covered here: spill files orphaned by a crash. They are absent from + * `states`, so this function can neither see nor price them, and they stay with + * recoverOrphanedResponseSpills and its RESPONSE_SPILL_ORPHAN_GRACE_MS window. + * This ceiling therefore bounds what the store owns, which is every file it can + * account for, and not the directory as a whole. + */ +function enforceSpilledResponseBudget(): number { + // Price in-flight publications too: a file being created by + // `writeResponseSpillDurablyAsync` occupies the volume before it reaches `states`. + let spilledBytes = accountedResponseSpillBytes(); + if (spilledBytes <= spillByteCap()) return 0; + const before = spilledBytes; + // Deferred generations go first. They are already superseded, so releasing one + // costs only the crash window the queue exists to cover — the same trade + // PENDING_SPILL_UNLINKS_MAX already makes against unbounded disk. Evicting a + // live continuation to make room for a dead file would be the wrong order. + while (spilledBytes > spillByteCap() && pendingSpillUnlinks.length > 0) { + const ref = pendingSpillUnlinks.shift()!; + spilledBytes -= ref.payloadBytes; + deleteResponseSpill(ref); + } + // Ordered by createdAt, not by map order. `states` is not an age index: + // demotion and spill replacement delete and reinsert entries, and + // writeBoundedSnapshot serializes the map reversed, so map order can put a + // newer continuation first — and evicting that one spends a resume the older + // entry would not have cost. Sorting is O(k log k) over the spilled subset and + // runs only on a tick already over budget. + const spilled = [...states] + .filter((pair): pair is [string, SpilledResponseState] => pair[1].kind === "spill") + // createdAt is millisecond-resolution, so ties are ordinary under load. A + // stable sort would then fall back to insertion order — the very order this + // is avoiding — so break ties on the response id. Not localeCompare: the + // order must not depend on the host locale. + .sort((a, b) => a[1].createdAt - b[1].createdAt + || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)); + for (const [id, entry] of spilled) { + if (spilledBytes <= spillByteCap()) break; + spilledBytes -= entry.spill.payloadBytes; + deleteEntry(id); + } + return before - spilledBytes; +} + function pruneResponses(at = now()): void { for (const [id, state] of states) { if (at - state.createdAt > RESPONSE_TTL_MS) deleteEntry(id); @@ -1537,6 +1817,7 @@ function pruneResponses(at = now()): void { replaceWithSpillFailure(oldestId, entry); } } + enforceSpilledResponseBudget(); } /** Periodic TTL-only sweep; count/byte eviction remains owned by mutation paths. */ @@ -1547,7 +1828,10 @@ export function sweepExpiredResponseStates(at = now()): number { deleteEntry(id); removed += 1; } - if (removed > 0) schedulePersist(); + // The disk ceiling needs a caller that does not depend on traffic. The return + // value stays the TTL count so this function's existing contract is unchanged. + const reclaimed = enforceSpilledResponseBudget(); + if (removed > 0 || reclaimed > 0) schedulePersist(); return removed; } @@ -1995,6 +2279,8 @@ export function clearResponseStateMemoryForTests(): void { export function clearResponseStateForTests(): void { for (const entry of states.values()) deleteOwnedSpills(entry); clearResponseStateMemoryForTests(); + reservedResponseSpillBytes = 0; + unreclaimableSpillPaths.clear(); try { unlinkSync(snapshotPath()); } catch { diff --git a/structure/00_overview.md b/structure/00_overview.md index d489fb293b..d1f864c4c6 100644 --- a/structure/00_overview.md +++ b/structure/00_overview.md @@ -84,7 +84,7 @@ opencodex state root does not undo those writes. Putting native Codex back is th | `~/.opencodex/ocx.pid`, `runtime-port.json`, `system-env-port` | opencodex runtime | Live process identity and the port a client should reach; rewritten on start. `runtime-port.json` also carries the protected per-process listener-attestation key used before CLI diagnostics attach a management bearer. | | `~/.opencodex/codex-runtime.json`, `codex-runtime-clamp.json` | opencodex Codex runtime | Selected Codex executable/version state and effort-clamp diagnostics. Not process identity: these persist a resolved choice and a diagnostic, so losing them changes behavior until re-resolved. | | `~/.opencodex/service-state.json`, `service.log`, `service-api-token`, `opencodex-service-launcher.vbs`, `opencodex-service-task.xml`, `opencodex-service.cmd`, `winsw`, `tray-state.json`, `tray-heartbeat.json`, `opencodex-tray.ps1`, `opencodex-tray-*.ico`, `update-job.json` | opencodex operators | Installed-service, Windows tray, and self-update artifacts and bookkeeping. The update record carries its worker PID so a dead worker recovers instead of blocking later runs. | -| `~/.opencodex/responses-state.json`, `usage-debug.jsonl`, `crash.log`, `artifacts/` | opencodex diagnostics and artifacts | Bounded caches, diagnostics, and generated image/video artifacts served locally. | +| `~/.opencodex/responses-state.json`, `responses-state-spill/`, `usage-debug.jsonl`, `crash.log`, `artifacts/` | opencodex diagnostics and artifacts | Bounded caches, diagnostics, and generated image/video artifacts served locally. The spill directory holds continuation state demoted out of the in-memory cap and is bounded in aggregate, not only per file. | | `~/.opencodex/codex-shim.json`, `*.lock`, `kimi-device-id`, `mimo-client-id`, `.star-prompted` | opencodex bookkeeping | Shim restore obligations, cross-process locks, per-install client identifiers, one-shot UI flags. | | `~/.opencodex/.opencodex-owner.json`, `.opencodex-uninstall.json` | opencodex | Ownership marker and the manifest that bounds what uninstall may remove. Both live in the OpenCodex state root, not in `$CODEX_HOME`. | | `$CODEX_HOME/config.toml` | Codex, edited by opencodex | Active provider and provider table. | diff --git a/structure/02_config-and-codex-home.md b/structure/02_config-and-codex-home.md index 8411884b5c..ddfd1d67e0 100644 --- a/structure/02_config-and-codex-home.md +++ b/structure/02_config-and-codex-home.md @@ -157,6 +157,34 @@ Hidden` inside an already-running PowerShell script, nor to .NET/VBS process-win - 다른 대안 대신 이 방식을 선택한 이유: Names and environment paths are caller-controlled, required secret writes must not silently skip ACLs, and elevation has a larger authority boundary that should remain FFI-only. - 장점, 단점 및 영향: Default Windows ARM64 installations can start and harden secrets; non-default Windows roots continue to fail closed until Bun exposes a trustworthy native system-directory API without FFI. +The durable response-spill directory `~/.opencodex/responses-state-spill/` is bounded in +aggregate, not only per file. Continuation state demoted out of the in-memory cap +(`MAX_STORED_RESPONSE_BYTES`) is written there, and eviction past +`MAX_SPILLED_RESPONSE_BYTES` removes oldest-first through the same deletion point that serves +TTL and count eviction, so an evicted entry unlinks its file. One function owns that ceiling and +three callers drive it: mutation pruning, the lazy load that follows a restart, and the periodic +sweep. The periodic caller is not redundant — the mutation path runs only when traffic arrives, so a +process that comes up over budget from a snapshot written under a larger ceiling would otherwise +stay over it while idle. + +The ceiling bounds what the store can account for, which is every entry in the map plus the +superseded generations queued for unlink, and deliberately not the directory as a whole. Spill files +orphaned by a crash are absent from the map, so this accounting can neither see nor price them; they +remain with the `recoverOrphanedResponseSpills` grace sweep described below, which is the only +mechanism that reclaims them. A host that crashes repeatedly can therefore hold spill bytes above +this ceiling for up to `RESPONSE_SPILL_ORPHAN_GRACE_MS` past each crash. Without that aggregate bound the +directory was limited only per file (256 MiB) and per entry (1000) — a 250 GiB product — which +left `RESPONSE_TTL_MS` as the only effective limit and made disk use a function of client +request rate rather than of anything the process controls. + +[Decision Log] +- 목적과 의도: Bound the durable spill directory in aggregate so demoted continuation state cannot consume the host disk. +- 기존 구현 및 제약 조건: The resident map has an unconditional byte cap and demotes past it, but the disk it demotes onto had only a per-file ceiling and the shared 1000-entry count cap. Retention itself worked — the hour-long TTL did evict — so the gap was a missing budget, not a leak. +- 검토한 주요 대안: Lower the per-file ceiling; shorten the TTL; sweep the directory on a timer; add a configurable budget key; carry a running byte counter. +- 선택한 방식: A constant aggregate ceiling checked at the end of the existing prune, evicting oldest-first, with the total recomputed per prune rather than carried as a counter. +- 다른 대안 대신 이 방식을 선택한 이유: Per-file or TTL changes alter retention semantics other bounds depend on; a timer adds a second owner for eviction; a config key would surface a knob the sibling bounds (count, TTL, per-file) do not have; and a running counter could silently disable the cap if any of the several insertion paths missed an increment, where a walk over at most 1000 entries cannot drift. +- 장점, 단점 및 영향: Disk use stops tracking client request rate. Ordinary traffic is unaffected because the count cap binds at a comparable point for median-sized payloads; a workload of unusually large continuations loses its oldest spills earlier than the TTL would, surfacing as the existing `previous_response_not_found` continuation miss. + Response-state loading performs a bounded recovery pass for interrupted snapshot writes. It only matches regular files named `responses-state.json.ocx.<pid>.<sequence>.tmp`, waits at least 15 minutes, and skips the current or any live PID. Eligible files are truncated before unlinking so a diff --git a/tests/responses-state.test.ts b/tests/responses-state.test.ts index 3422d8447b..8f22fd444d 100644 --- a/tests/responses-state.test.ts +++ b/tests/responses-state.test.ts @@ -38,6 +38,7 @@ import { recoverStaleResponseStateTemps, rememberResponseState, sweepAbandonedResponseStateTemps, + sweepExpiredResponseStates, responseAdmissionCountersForTests, responseStateMetrics, responseStatePersistPendingForTests, @@ -45,6 +46,9 @@ import { runPendingResponseStatePersistForTests, setResponseSpillAsyncAclAttemptBudgetForTests, setResponseStateByteCapForTests, + setSpilledResponseByteCapForTests, + getSpilledResponseBytesForTests, + getAccountedResponseSpillBytesForTests, setResponseStatePersistAttemptHookForTests, setResponseSpillShutdownBudgetForTests, getStoredResponseBytesForTests, @@ -131,6 +135,17 @@ function spillTempNames(home: string): string[] { return existsSync(dir) ? readdirSync(dir).filter(name => name.endsWith(".tmp")) : []; } +/** Real bytes the spill directory occupies, temps included. */ +function bytesOnDisk(home: string): number { + const dir = responseSpillDirectory(home); + if (!existsSync(dir)) return 0; + let total = 0; + for (const name of readdirSync(dir)) { + try { total += statSync(join(dir, name)).size; } catch { /* raced with an unlink */ } + } + return total; +} + interface ShutdownBudgetChildResult { settled: boolean; reported: boolean; @@ -282,6 +297,7 @@ describe("Responses previous_response_id state", () => { setResponseSpillShutdownBudgetForTests(null); setResponseSpillAsyncAclAttemptBudgetForTests(null); setResponseStateByteCapForTests(null); + setSpilledResponseByteCapForTests(null); clearResponseStateForTests(); rmSync(home, { recursive: true, force: true }); if (priorHome === undefined) delete process.env["OPENCODEX_HOME"]; @@ -812,6 +828,23 @@ describe("Responses previous_response_id state", () => { }) as { input: unknown[] }).input).toHaveLength(3); }); + test("evicts oldest spills once the durable set exceeds the disk cap", () => { + setResponseStateByteCapForTests(1_024); + setSpilledResponseByteCapForTests(20_000); + for (let i = 0; i < 6; i += 1) rememberLarge(`resp_spill_budget_${i}`, "x".repeat(8_000)); + // Without a disk cap all six stay on disk: the RAM cap only moves bytes out of + // memory, it never bounds where they land. + expect(getSpilledResponseBytesForTests()).toBeLessThanOrEqual(20_000); + expect(spillFileNames(home).length).toBeLessThan(6); + // The oldest entry is the one that must be gone, not merely "some" entry. + expect(spillFileNames(home).some(name => name.startsWith("resp_spill_budget_0."))).toBe(false); + // Eviction is oldest-first and must not clear the set it was asked to bound. + expect(spillFileNames(home).length).toBeGreaterThanOrEqual(1); + expect((expandPreviousResponseInput({ + previous_response_id: "resp_spill_budget_5", input: "next", + }) as { input: unknown[] }).input).toHaveLength(3); + }); + test("does not swap a resident row to a stub before fsync and no-replace publication succeed", () => { const events: string[] = []; setSpillIoForTest({ record: event => events.push(event) }); @@ -857,6 +890,126 @@ describe("Responses previous_response_id state", () => { expect(responseStateMetrics()).toMatchObject({ residentCount: 0, spillStubCount: 1, spillWrites: 1, spillWriteFailures: 0 }); }); + test("holds the disk cap while a copy-fallback publication has temp and destination on disk", async () => { + // The cap is a promise about the volume, and a file being created by + // writeResponseSpillDurablyAsync is on the volume. Accounting that walks only + // installed spills reports a satisfied budget while the directory grows — the + // incident behind this cap put 6.8 GiB on disk in 44 minutes. + // + // The peak is TWO envelopes, not one: when hard-linking fails, publication copies + // with COPYFILE_EXCL and then hardens the destination, so the temp and the copy exist + // together. This drives that exact path and measures real bytes on disk. + setPlatformForTests("win32"); + setResponseStateByteCapForTests(1_024); + + let gateDestinationHarden = false; + let release!: () => void; + let entered!: () => void; + const gate = new Promise<void>(resolve => { release = resolve; }); + const started = new Promise<void>(resolve => { entered = resolve; }); + let announced = false; + setAsyncIcaclsRunnerForTests(async args => { + if (gateDestinationHarden && args.some(arg => arg.endsWith(".spill.json"))) { + if (!announced) { + announced = true; + entered(); + } + await gate; + } + return { success: true, exitCode: 0, timedOut: false, stdout: "" }; + }); + + // One resident spill already on disk, so the cap has real prior occupancy. + rememberLarge("resp_cap_existing", "e".repeat(8_000)); + await flushPendingResponseSpillsForTests(); + const existingBytes = getSpilledResponseBytesForTests(); + expect(existingBytes).toBeGreaterThan(0); + expect(spillFileNames(home)).toHaveLength(1); + + // Room for the two-envelope publication and nothing more. The seeded spill does not + // fit alongside it, so correct admission must reclaim it before publishing; without + // the check, seeded + temp + destination sit on disk together and blow the cap. + // Generous enough that this publication is admitted: the point of THIS test is that + // the accounting sees the in-flight bytes. The cap-refusal behaviour is proven + // separately below, where admission is the only thing standing between the request + // and an over-budget directory. + const spillCap = existingBytes * 4; + setSpilledResponseByteCapForTests(spillCap); + + // Force the exclusive-copy fallback, then gate the destination hardening that follows + // it, so the observation below happens with BOTH files present. + setSpillIoForTest({ + link: () => { throw Object.assign(new Error("EXDEV"), { code: "EXDEV" }); }, + }); + gateDestinationHarden = true; + + rememberLarge("resp_cap_inflight", "x".repeat(8_000)); + await started; + try { + // Real disk: the temp and the copied destination coexist during hardening. + const onDisk = spillFileNames(home).length + spillTempNames(home).length; + expect(onDisk).toBeGreaterThanOrEqual(3); + // The walk over installed spills still reports only the settled file, so accounting + // built on it alone would price a three-envelope directory as one. + expect(getSpilledResponseBytesForTests()).toBe(existingBytes); + // Reservation prices the in-flight publication at its peak, so the accounted total + // covers what is actually on the volume. + expect(getAccountedResponseSpillBytesForTests()) + .toBeGreaterThanOrEqual(existingBytes * 3); + // And the bytes ACTUALLY on disk stay inside the configured cap. This is the + // assertion the admission check has to earn: without it, the seeded spill plus the + // temp plus the destination copy exceed a cap sized for two envelopes. + expect(bytesOnDisk(home)).toBeLessThanOrEqual(spillCap); + } finally { + release(); + setSpillIoForTest(null); + } + await flushPendingResponseSpillsForTests(); + // Settled: the reservation is released exactly once and accounting collapses to the + // real files. A leaked reservation would be monotonic — it would ratchet the usable + // cap toward zero until nothing could spill at all. + expect(getAccountedResponseSpillBytesForTests()).toBe(getSpilledResponseBytesForTests()); + expect(spillTempNames(home)).toHaveLength(0); + // And the newest continuation is still replayable: the cap must not have turned the + // fail-closed path into the ordinary one. + expect(JSON.stringify(expandPreviousResponseInput({ previous_response_id: "resp_cap_inflight", input: "next" }))) + .toContain("xxxxxxxx"); + }); + + test("refuses a publication whose peak footprint does not fit the disk cap", async () => { + // Enforcement, not accounting: a publication that cannot fit must be refused BEFORE + // any file is created. Deleting the overflow afterwards is not equivalent — on + // Windows the file outlives the decision by however long ACL hardening takes, which + // is the window the measured 6.8 GiB accumulated in. + setPlatformForTests("win32"); + setResponseStateByteCapForTests(1_024); + setAsyncIcaclsRunnerForTests(async () => ({ success: true, exitCode: 0, timedOut: false, stdout: "" })); + + rememberLarge("resp_cap_seed", "s".repeat(8_000)); + await flushPendingResponseSpillsForTests(); + const seededBytes = getSpilledResponseBytesForTests(); + expect(spillFileNames(home)).toHaveLength(1); + + // Room for the seeded spill and nothing else. The next publication needs two + // envelopes at peak, and the seeded file is live rather than reclaimable-on-sight, + // so admission has to refuse. + setSpilledResponseByteCapForTests(Math.floor(seededBytes * 1.5)); + + rememberLarge("resp_cap_refused", "r".repeat(8_000)); + await flushPendingResponseSpillsForTests(); + + // No second file, and no temp left behind: the refusal happened before publication. + expect(spillFileNames(home)).toHaveLength(1); + expect(spillTempNames(home)).toHaveLength(0); + expect(bytesOnDisk(home)).toBeLessThanOrEqual(Math.floor(seededBytes * 1.5)); + // Fail-closed, and it says so: the refused continuation is a spill failure, not a + // silent drop, so replay reports it and the client resends. + expect(responseStateMetrics()).toMatchObject({ spillWriteFailures: 1 }); + // The seeded continuation is untouched — a refusal must not cost an unrelated replay. + expect(JSON.stringify(expandPreviousResponseInput({ previous_response_id: "resp_cap_seed", input: "next" }))) + .toContain("ssssssss"); + }); + test("Windows spill retries one transient ACL timeout without installing a tombstone", async () => { setPlatformForTests("win32"); process.env.OPENCODEX_ACL_TIMEOUT_MS = "1000"; @@ -1120,6 +1273,57 @@ describe("Responses previous_response_id state", () => { } }); + test("shutdown fallback prices the job-owned superseded generation before publishing", async () => { + // Supersession releases the job, so its superseded generation leaves the accounting + // walk while the FILE stays on the volume. Without pricing it, the fallback decides + // against a total short by a whole envelope, and a cap sitting between + // (debt + footprint) and (old + debt + footprint) admits a publication that puts the + // directory over budget. + setPlatformForTests("win32"); + setResponseSpillShutdownBudgetForTests({ totalMs: 120, fallbackReserveMs: 80 }); + let release!: () => void; + let entered!: () => void; + const gate = new Promise<void>(resolve => { release = resolve; }); + const started = new Promise<void>(resolve => { entered = resolve; }); + let announced = false; + setAsyncIcaclsRunnerForTests(async () => { + if (!announced) { + announced = true; + entered(); + } + await gate; + return { success: true, exitCode: 0, timedOut: false, stdout: "" }; + }); + setIcaclsRunnerForTests(() => ({ success: true, exitCode: 0, timedOut: false, stdout: "" })); + setResponseStateByteCapForTests(1_024); + + // First generation settles to a real file, then a same-id replacement makes the job + // the owner of that superseded generation. + rememberLarge("resp_shutdown_superseded", "o".repeat(8_000)); + await flushPendingResponseSpillsForTests(); + const oldBytes = getSpilledResponseBytesForTests(); + expect(oldBytes).toBeGreaterThan(0); + + rememberLarge("resp_shutdown_superseded", "n".repeat(8_000)); + await started; + + try { + // Cap allows the new publication on its own, but not alongside the superseded + // generation the job still owns. + setSpilledResponseByteCapForTests(Math.floor(oldBytes * 2.4)); + // The refusal surfaces as a shutdown failure, which is the honest signal: the + // operator learns a continuation was dropped rather than the volume being + // silently overfilled. + await expect(flushResponseState()).rejects.toThrow(/shutdown fallback incomplete/); + // Fail-closed rather than over-budget: the drain refused to add a third envelope. + expect(bytesOnDisk(home)).toBeLessThanOrEqual(Math.floor(oldBytes * 2.4)); + expect(spillTempNames(home)).toHaveLength(0); + expect(pendingResponseSpillMetricsForTests()).toEqual({ count: 0, bytes: 0 }); + } finally { + release(); + } + }); + test("shutdown fallback spends only its reserved ACL budget", async () => { setPlatformForTests("win32"); const totalMs = 500; @@ -1535,6 +1739,147 @@ describe("Responses previous_response_id state", () => { expect(spillFileNames(home)).toHaveLength(1); }); + test("evicts by createdAt, not by insertion order", () => { + const realNow = Date.now; + try { + setResponseStateByteCapForTests(1_024); + // Insert the NEWER entry first so insertion order and createdAt order + // disagree. Map order alone would evict the newer one; `states` is not a + // reliable age index — writeBoundedSnapshot even serializes it reversed. + const base = realNow(); + Date.now = () => base; + const beforeNewer = new Set(spillFileNames(home)); + rememberLarge("resp_order_newer", "x".repeat(8_000)); + const newerFile = spillFileNames(home).find(name => !beforeNewer.has(name))!; + + Date.now = () => base - 5 * 60_000; + const beforeOlder = new Set(spillFileNames(home)); + rememberLarge("resp_order_older", "x".repeat(8_000)); + const olderFile = spillFileNames(home).find(name => !beforeOlder.has(name))!; + Date.now = () => base; + + // Each spill payload is ~16.2 KB, so 20_000 leaves room for exactly one. + setSpilledResponseByteCapForTests(20_000); + rememberLarge("resp_order_trigger", "y"); + + const dir = responseSpillDirectory(home); + expect(existsSync(join(dir, olderFile))).toBe(false); + expect(existsSync(join(dir, newerFile))).toBe(true); + } finally { + Date.now = realNow; + } + }); + + test("breaks createdAt ties on the response id, not on insertion order", () => { + const realNow = Date.now; + try { + setResponseStateByteCapForTests(1_024); + const base = realNow(); + Date.now = () => base; + // Same createdAt, inserted in reverse id order: a stable sort alone would + // keep insertion order and evict "_b" first. + const before = new Set(spillFileNames(home)); + rememberLarge("resp_tie_b", "x".repeat(8_000)); + const bFile = spillFileNames(home).find(name => !before.has(name))!; + const beforeA = new Set(spillFileNames(home)); + rememberLarge("resp_tie_a", "x".repeat(8_000)); + const aFile = spillFileNames(home).find(name => !beforeA.has(name))!; + + setSpilledResponseByteCapForTests(20_000); + rememberLarge("resp_tie_trigger", "y"); + + const dir = responseSpillDirectory(home); + expect(existsSync(join(dir, aFile))).toBe(false); + expect(existsSync(join(dir, bFile))).toBe(true); + } finally { + Date.now = realNow; + } + }); + + test("survives a single spill payload larger than the whole disk budget", () => { + setResponseStateByteCapForTests(1_024); + // Below one payload (~16.2 KB), so every spill is written and then evicted on + // the same tick and the running total goes negative. The loop must still + // terminate and leave the store usable. + setSpilledResponseByteCapForTests(1_000); + rememberLarge("resp_over_budget_a", "x".repeat(8_000)); + rememberLarge("resp_over_budget_b", "x".repeat(8_000)); + expect(spillFileNames(home)).toHaveLength(0); + expect(getSpilledResponseBytesForTests()).toBe(0); + // Raising the cap restores ordinary retention: the store is not wedged. + setSpilledResponseByteCapForTests(1_000_000); + rememberLarge("resp_over_budget_c", "x".repeat(8_000)); + expect(spillFileNames(home)).toHaveLength(1); + }); + + test("counts deferred spill generations against the disk cap and drains them first", () => { + setResponseStateByteCapForTests(1_024); + // Replacing a spilled id queues the superseded file in pendingSpillUnlinks + // instead of unlinking it, so the directory holds generations that `states` + // alone cannot see. Payloads are ~16.2 KB, so 60_000 fits about three. + setSpilledResponseByteCapForTests(60_000); + for (let i = 0; i < 8; i += 1) rememberLarge("resp_deferred_gen", "x".repeat(8_000)); + // Counting only the live entry would report ~16 KB here and evict nothing while + // eight files sat on disk. + expect(getSpilledResponseBytesForTests()).toBeLessThanOrEqual(60_000); + const onDisk = spillFileNames(home); + expect(onDisk.length).toBeLessThanOrEqual(4); + // The live continuation survives: deferred generations are released first. + expect((expandPreviousResponseInput({ + previous_response_id: "resp_deferred_gen", input: "next", + }) as { input: unknown[] }).input).toHaveLength(3); + }); + + test("reclaims an over-budget snapshot without a new continuation mutation", async () => { + const realNow = Date.now; + try { + setResponseStateByteCapForTests(4_000); + const base = realNow(); + const files: string[] = []; + for (let i = 0; i < 4; i += 1) { + Date.now = () => base - (10 - i) * 60_000; + const before = new Set(spillFileNames(home)); + rememberResponseState( + { model: "test/model", input: "z".repeat(8_000), store: false }, + fixedResponse(`resp_budget_restart_${i}`, [{ type: "message", role: "assistant", content: "stored" }]), + undefined, + { force: true, clientThreadId: "task-budget" }, + ); + files.push(spillFileNames(home).find(name => !before.has(name))!); + } + Date.now = () => base; + await flushResponseState(); + clearResponseStateMemoryForTests(); + + // Come back up under a ceiling the snapshot was not written for. Nothing has + // mutated the store yet, which is the case the mutation-path prune misses. + setResponseStateByteCapForTests(4_000); + // Four spills total ~32.9 KB, so this ceiling keeps the two newest. + setSpilledResponseByteCapForTests(20_000); + expect(spillFileNames(home).length).toBe(4); + + // A read, not a write: this drives the lazy load and its prune. + const expanded = expandPreviousResponseInput( + { previous_response_id: "resp_budget_restart_3", input: "next" }, + "task-budget", + ); + + expect(getSpilledResponseBytesForTests()).toBeLessThanOrEqual(20_000); + expect(existsSync(join(responseSpillDirectory(home), files[0]!))).toBe(false); + // The newest entry survives the reclaim and still replays. + expect(existsSync(join(responseSpillDirectory(home), files[3]!))).toBe(true); + expect((expanded as { input: unknown[] }).input).toHaveLength(3); + + // The periodic sweep owns the same ceiling: lower it again and let the tick, + // not a request, do the work. + setSpilledResponseByteCapForTests(10_000); + sweepExpiredResponseStates(); + expect(getSpilledResponseBytesForTests()).toBeLessThanOrEqual(10_000); + } finally { + Date.now = realNow; + } + }); + test("TTL and count eviction delete dedicated spill files and release stub bytes", () => { const realNow = Date.now; setResponseStateByteCapForTests(1_024); From 15f92e3f6d87b268040efe4aa7915046063f98a2 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 02:47:24 +0900 Subject: [PATCH 19/42] fix(gui): make every provider mark survive both themes (#3098) ProviderIcon rendered <img> unconditionally, so a mark only worked if its own artwork happened to contrast with both tiles. Measured against the real tile colours (#f4f4f4 light, #303030 dark), 18 of the 26 marks sourced in #3095 fell under 2:1 on one of them -- zai at 1.04:1, bizrouter 1.06, sambanova 1.02. Three paints, each chosen by measurement rather than by looking at the file. A single-ink neutral silhouette is masked: the ink comes from the theme, which is the mechanism ClientMark already uses. Eight qualify. Artwork that carries real colour cannot be masked -- that discards every ink and repaints the silhouette, flattening a brand while still looking deliberate. When such a mark is dominantly near-black it gets a light plate, and when it is dominantly near-white a dark one. Two plates rather than one theme-following plate on purpose: a plate that followed the theme would put light-ink artwork back on a light tile in light mode, which is the failure being fixed. digitalocean.svg is the exception that proves the rule. Its file carries its own @media (prefers-color-scheme: dark) rule, so plating it defeated the vendor and produced light-on-light at 1.01:1 -- worse than doing nothing, and only visible by measuring the rendered result. It is left alone. The luminance guard then found five older marks nobody had measured: opencode (#211e1e) and kimi (#1a1a1a) are the same files the Integrations page already masks, invisible here the whole time because the two surfaces had no shared decision; grok is the same story one PR later; ollama (#141414) and vercel-ai-gateway (#000000) were never caught by either pass. Measured after, per mark, in both themes: 26 of 26 at or above 1.73:1, none below. Before: 18 below 2:1. Verification: 5 pass, three new guards each driven red -- unmasking a neutral, masking a multi-colour mark, plating the self-adapting one. tsc 0 both roots, oxlint clean, privacy:scan clean. --- .../provider-workspace/ProviderRail.tsx | 27 ++++- gui/src/provider-icons.ts | 102 ++++++++++++++++++ gui/src/styles.css | 29 +++++ gui/tests/provider-marks-assets.test.ts | 89 ++++++++++++++- 4 files changed, 243 insertions(+), 4 deletions(-) diff --git a/gui/src/components/provider-workspace/ProviderRail.tsx b/gui/src/components/provider-workspace/ProviderRail.tsx index db6964f8de..22858d35dd 100644 --- a/gui/src/components/provider-workspace/ProviderRail.tsx +++ b/gui/src/components/provider-workspace/ProviderRail.tsx @@ -13,7 +13,7 @@ import { type WorkspaceProvider, } from "../../provider-workspace/catalog"; import { isLocalProvider } from "../../provider-workspace/kind"; -import { formatProviderDisplayName, providerIconSrc } from "../../provider-icons"; +import { formatProviderDisplayName, providerIconPaint, providerIconSrc } from "../../provider-icons"; export function statusLabel(p: WorkspaceProvider, t: TFn): string { const s = binProviderStatus(p); @@ -50,9 +50,30 @@ export function ProviderIcon({ name, adapter, baseUrl, cls }: { }) { const t = useT(); const src = providerIconSrc(name, { adapter, baseUrl }); + /* + * Three ways to paint a mark, because two of them are wrong for most files. + * + * An <img> keeps the vendor's colours, which is what a brand deserves and what + * works whenever the artwork has enough contrast against both tiles. A neutral + * silhouette does not: one fill of near-black or near-white vanishes against + * one of the two surfaces, so it is drawn as a themed mask instead. And a mark + * that carries real colour but is dominantly dark can be neither -- masking + * would flatten its palette, leaving it alone leaves it invisible -- so its own + * artwork sits on a constant light plate, the way a favicon already assumes. + */ + const paint = providerIconPaint(src); + const tileClass = paint === "plate" ? `${cls} provider-icon--plate` + : paint === "dark-plate" ? `${cls} provider-icon--plate-dark` + : cls; return ( - <span className={cls}> - {src ? ( + <span className={tileClass}> + {src && paint === "mask" ? ( + <span + className="provider-icon-mask" + style={{ maskImage: `url(${src})`, WebkitMaskImage: `url(${src})` }} + aria-hidden="true" + /> + ) : src ? ( <img src={src} alt="" aria-hidden="true" /> ) : ( <ProviderFallbackMark name={name} label={formatProviderDisplayName(name, t)} /> diff --git a/gui/src/provider-icons.ts b/gui/src/provider-icons.ts index 2bf4d6c5a3..3192862648 100644 --- a/gui/src/provider-icons.ts +++ b/gui/src/provider-icons.ts @@ -169,6 +169,108 @@ export function providerIconSrc(provider: string, _hints?: ProviderIconHints): s return icon ? `/provider-icons/${icon}` : undefined; } +/** + * Marks whose artwork is one neutral ink, so the ink has to come from the theme. + * + * Keyed by asset path, deliberately, and for the same reason `MASKED_MARKS` is on + * the client side: an asset reachable from two surfaces cannot be masked on one + * and drawn plain on the other without looking like a bug. + * + * Membership is a measurement, not a guess. Each of these renders a single fill + * that is either near-black or near-white, which means it disappears against one + * of the two tile surfaces (`--raised` resolves to #f4f4f4 light, #303030 dark). + * `zenmux` is #000, `synthetic` is #ffffff, `neuralwatt` is #081a17. + * + * A mark that carries real colour never belongs here. Masking discards every ink + * in the file and repaints the silhouette, so applying it to a palette is + * destructive in a way that still looks deliberate on screen. + */ +const MASKED_PROVIDER_ICONS: ReadonlySet<string> = new Set([ + "cerebras.svg", + "deepinfra.svg", + "neuralwatt.svg", + "nous.svg", + "novita.svg", + "siliconflow.svg", + "synthetic.svg", + "zenmux.svg", + + /* + * Marks that predate this pass and were invisible on one tile the whole time. + * + * `opencode.svg` (#211e1e) and `kimi-color.svg` (#1a1a1a) are the same two files + * the client surface already masks -- the Integrations page fixed them and the + * provider rail kept drawing them plain, because the two surfaces had no shared + * decision. `grok.svg` is the same story one PR later. `ollama-color.svg` + * (#141414) and `vercel-ai-gateway-color.svg` (#000000) were never caught by + * either pass; the luminance guard found all five at once. + */ + "grok.svg", + "kimi-color.svg", + "ollama-color.svg", + "opencode.svg", + "vercel-ai-gateway-color.svg", +]); + +/** + * Marks that carry colour but whose dominant ink is near-black. + * + * These cannot be masked -- that would flatten a real palette -- and they cannot + * be left alone either: measured against the dark tile they land between 1.04:1 + * and 1.59:1, which is invisible. `zai` is 1.04, `bizrouter` 1.08, `baseten` 1.59. + * + * The fix belongs to the tile rather than the file. A vendor's artwork is drawn + * unchanged on a constant light plate, which is what a favicon assumes anyway: + * every one of these was designed to sit on a page, not on a #303030 chip. + * + * `digitalocean.svg` looks like it belongs here and must not: its file carries + * its own `@media (prefers-color-scheme: dark)` rule that repaints the glyph + * #F4F5F5. A constant plate defeats that -- the file goes light-on-light and + * measures 1.01:1 -- so the one mark that solves this problem itself is left + * alone to do it. Check for an embedded media query before plating anything. + */ +const PLATED_PROVIDER_ICONS: ReadonlySet<string> = new Set([ + "baseten.svg", + "kilo.svg", + "sambanova.svg", + "venice.svg", + "zai.svg", +]); + +/** + * The same problem pointing the other way: colour artwork whose dominant ink is + * near-WHITE, drawn for a dark header and invisible on the light tile. + * + * Measured dominant luminance: `parallel` 1.00, `bizrouter` 0.98, `nebius` 0.87, + * `featherless` 0.87, `umans` 0.84, `hyperbolic` 0.84 -- all of which land near + * 1.0:1 against #f4f4f4. A light plate would make them worse, so they get a dark + * one, which is the surface their own designers assumed. + * + * Two plates rather than one theme-following plate on purpose: a plate that + * followed the theme would put light-ink art back on a light tile in light mode, + * which is the exact failure being fixed. + */ +const DARK_PLATED_PROVIDER_ICONS: ReadonlySet<string> = new Set([ + "bizrouter.svg", + "featherless.svg", + "hyperbolic.svg", + "nebius.svg", + "parallel.svg", + "umans.svg", +]); + +/** How a provider mark must be painted so it survives both themes. */ +export type ProviderIconPaint = "mask" | "plate" | "dark-plate" | "image"; + +export function providerIconPaint(src: string | undefined): ProviderIconPaint { + if (!src) return "image"; + const file = src.split("/").pop() ?? ""; + if (MASKED_PROVIDER_ICONS.has(file)) return "mask"; + if (PLATED_PROVIDER_ICONS.has(file)) return "plate"; + if (DARK_PLATED_PROVIDER_ICONS.has(file)) return "dark-plate"; + return "image"; +} + /** Display label with proper brand casing when known; otherwise original name. */ export function formatProviderDisplayName(provider: string, t: TFn): string { const key = provider.toLowerCase(); diff --git a/gui/src/styles.css b/gui/src/styles.css index 027388f1c4..df63631504 100644 --- a/gui/src/styles.css +++ b/gui/src/styles.css @@ -2153,6 +2153,35 @@ table.logs-table { .prov-title { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; flex-wrap: wrap; min-width: 0; } .provider-icon { width: 31px; height: 31px; border-radius: var(--radius-xs); flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; background: var(--raised); border: 1px solid var(--border-soft); color: var(--text); } .provider-icon img { width: 19px; height: 19px; object-fit: contain; display: block; } + +/* A single-ink mark painted with the surrounding text colour, so it follows the + theme instead of vanishing into one of the two tiles. The tile already sets + `color: var(--text)`, so this needs no property of its own. */ +.provider-icon-mask { + width: 19px; + height: 19px; + display: block; + background: currentColor; + mask-size: contain; + mask-repeat: no-repeat; + mask-position: center; + -webkit-mask-size: contain; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; +} +.provider-icon-sm .provider-icon-mask { width: 15px; height: 15px; } + +/* Artwork that carries real colour but is dominantly near-black: measured between + 1.04:1 and 1.59:1 against the dark tile, which is invisible. Masking would + flatten the palette, so the vendor's own colours are kept and given the light + plate every favicon already assumes it will sit on. */ +.provider-icon--plate { background: #f4f4f4; border-color: rgba(0, 0, 0, 0.12); } + +/* The mirror case: artwork drawn in near-white for a dark header, which is + invisible on the light tile at roughly 1.0:1. It gets the dark surface its own + designers assumed. A theme-following plate would not work -- it would put + light-ink art back on a light tile in light mode. */ +.provider-icon--plate-dark { background: #1c1c1c; border-color: rgba(255, 255, 255, 0.14); } .prov-meta { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; min-width: 0; } .prov-meta > span { min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .provider-quota { padding-left: 58px; } diff --git a/gui/tests/provider-marks-assets.test.ts b/gui/tests/provider-marks-assets.test.ts index b6350cafff..ebe143af44 100644 --- a/gui/tests/provider-marks-assets.test.ts +++ b/gui/tests/provider-marks-assets.test.ts @@ -2,7 +2,7 @@ import { expect, test } from "bun:test"; import { readFileSync, readdirSync } from "node:fs"; import { join } from "node:path"; import { PROVIDER_REGISTRY } from "../../src/providers/registry"; -import { providerIconSrc } from "../src/provider-icons"; +import { providerIconPaint, providerIconSrc } from "../src/provider-icons"; const PUBLIC_DIR = join(import.meta.dir, "..", "public", "provider-icons"); @@ -64,3 +64,90 @@ test("no wired provider mark is a horizontal wordmark", () => { } expect(lockups).toEqual([]); }); + +/** Relative luminance, so "would this vanish?" is measured rather than judged. */ +function luminance(hex: string): number { + const raw = hex.replace("#", ""); + const full = raw.length === 3 ? [...raw].map(c => c + c).join("") : raw.slice(0, 6); + const channel = (pair: string): number => { + const v = parseInt(pair, 16) / 255; + return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; + }; + return 0.2126 * channel(full.slice(0, 2)) + + 0.7152 * channel(full.slice(2, 4)) + + 0.0722 * channel(full.slice(4, 6)); +} + +function inksOf(body: string): string[] { + const matches = body.match(/(?:fill|stop-color)\s*[:=]\s*"?(#[0-9a-fA-F]{3,8})/g) ?? []; + return [...new Set(matches.map(raw => raw.split(/[:=]/).pop()!.replace(/"/g, "").trim().toLowerCase()))]; +} + +/* + * The rule that has now shipped broken five times. + * + * `prime` was white-on-transparent and invisible in light mode; `opencode` + * (#211E1E) and `kimi` (#1A1A1A) invisible in dark; `grok` (#000000) sat at + * 1.9:1 on the dark card through two passes because a comment argued it away. + * Every one of those is a single fill of a near-neutral ink drawn as a plain + * image, which is the one combination that cannot survive both themes. + * + * The provider tile resolves to #f4f4f4 light and #303030 dark, so an ink at + * either extreme disappears into one of them. Such a mark must be masked (the ink + * comes from the theme) or plated (the tile is pinned to the surface the artwork + * assumes). What it must not be is `image`. + */ +test("a single-ink near-neutral mark is never left to be drawn plain", () => { + const vanishing: string[] = []; + for (const src of wiredAssets()) { + const body = bodyOf(src); + if (/<(linearGradient|radialGradient)[\s>]/.test(body)) continue; + if (/prefers-color-scheme/.test(body)) continue; // solves it itself, see digitalocean + const inks = inksOf(body); + if (inks.length !== 1) continue; + const hex = inks[0]!.replace("#", ""); + const full = hex.length === 3 ? [...hex].map(c => c + c).join("") : hex.slice(0, 6); + const channels = [full.slice(0, 2), full.slice(2, 4), full.slice(4, 6)].map(p => parseInt(p, 16)); + if (Math.max(...channels) - Math.min(...channels) > 24) continue; // a brand colour, not a neutral + const l = luminance(inks[0]!); + if (l > 0.12 && l < 0.75) continue; // a mid grey reads on both tiles + if (providerIconPaint(src) === "image") { + vanishing.push(`${src}: ${inks[0]} is drawn plain and vanishes against one tile`); + } + } + expect(vanishing).toEqual([]); +}); + +/* + * The inverse, and the more destructive direction. Masking discards every ink in + * the file and repaints the silhouette in one colour, so applying it to a palette + * flattens a brand -- and the result still renders, still looks deliberate, and is + * invisible in review. + */ +test("no multi-colour provider mark is masked", () => { + const flattened: string[] = []; + for (const src of wiredAssets()) { + if (providerIconPaint(src) !== "mask") continue; + const body = bodyOf(src); + const gradient = /<(linearGradient|radialGradient)[\s>]/.test(body); + const inks = inksOf(body); + if (gradient || inks.length > 1) { + flattened.push(`${src}: ${inks.length} ink(s)${gradient ? " + gradient" : ""}`); + } + } + expect(flattened).toEqual([]); +}); + +/* + * A mark that adapts to the theme in its own file must be left alone. A constant + * plate defeats its media query: `digitalocean.svg` repaints itself #F4F5F5 under + * dark, so plating it light produced light-on-light at 1.01:1 -- worse than doing + * nothing, and only visible by measuring the rendered result. + */ +test("a self-adapting mark is not plated", () => { + const overridden = wiredAssets() + .filter(src => /prefers-color-scheme/.test(bodyOf(src))) + .filter(src => providerIconPaint(src) !== "image") + .map(src => `${src}: carries its own media query but is painted ${providerIconPaint(src)}`); + expect(overridden).toEqual([]); +}); From d71aa07c02da10a42c2a6b109f7ffb1c1e148369 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 03:06:45 +0900 Subject: [PATCH 20/42] feat(gui): give the Add-Provider catalog its logos (#3099) The catalog is the list a user reads to CHOOSE a provider, and it was the only provider surface with no marks at all -- the rail, the details panel and the dashboard rows have drawn them for a while. A page of names is a worse place to pick from than a page of logos, which is the whole argument. CatalogPreset.id is the registry id, so this reuses ProviderIcon rather than adding a lookup: the mask/plate decision from #3098 comes along, and a mark cannot be legible in the workspace and invisible here. Account rows get the same treatment -- a logo beside Cursor and a bare tile beside Kiro reads as a bug rather than a distinction. One layout rule. .list-row is space-between, so a mark placed before the title would push the badge strip into the middle of the row; the text block takes the free space instead and min-width: 0 lets a long label ellipsize rather than widen the row past the modal. Scoped to the catalog, because .list-row is shared and no other list has a mark to lay out. Measured at 620px and 400px in both themes: 10 of 10 rows marked, the mark first in every row, and the badge strip a uniform 14px from the right edge -- no row pushed out of alignment. Verification: 3 pass, each driven red -- removing the component, and giving the mark an alt text that makes a screen reader say the provider name twice. tsc 0 both roots, oxlint clean, privacy:scan clean. --- .../provider-catalog/ProviderCatalog.tsx | 12 ++ gui/src/styles/provider-catalog.css | 10 ++ gui/tests/provider-catalog-marks.test.tsx | 127 ++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 gui/tests/provider-catalog-marks.test.tsx diff --git a/gui/src/components/provider-catalog/ProviderCatalog.tsx b/gui/src/components/provider-catalog/ProviderCatalog.tsx index d08612d7c7..d91433490d 100644 --- a/gui/src/components/provider-catalog/ProviderCatalog.tsx +++ b/gui/src/components/provider-catalog/ProviderCatalog.tsx @@ -13,6 +13,7 @@ import { } from "./provider-presets"; import { shouldShowLoginHint, type CatalogLoginHint } from "./login-hint-visibility"; import { LoginHint } from "../login-url-block"; +import { ProviderIcon } from "../provider-workspace/ProviderRail"; export type AccountLoginStatus = { loggedIn: boolean; email?: string; error?: string; needsReauth?: boolean }; export type AccountLoginRow = { @@ -149,6 +150,14 @@ export default function ProviderCatalog({ )} {tier !== "accounts" && rows.map(p => ( <button type="button" key={p.id} className="list-row" onClick={() => onSelectPreset(p)}> + {/* + The one list a user reads to CHOOSE a provider, and until now the only + provider surface with no marks at all. `CatalogPreset.id` is the + registry id, so this reuses `providerIconSrc` and, with it, the + mask/plate decision the rail already owns -- a mark cannot be legible + in the workspace and invisible here. + */} + <ProviderIcon name={p.id} adapter={p.adapter} cls="provider-icon provider-icon-sm" /> <div> <div className="title">{p.label}</div> <div className="sub"><code className="chip">{p.adapter}</code>{p.note ? ` · ${p.note}` : ""}</div> @@ -174,6 +183,9 @@ export default function ProviderCatalog({ return ( <div key={row.id} className={`list-row provider-catalog-account-row${showHint ? " provider-catalog-account-row--waiting" : ""}`}> <div className="provider-catalog-account-row-head"> + {/* Account rows are providers too. A logo beside Cursor and a bare + tile beside Kiro reads as a bug, not as a distinction. */} + <ProviderIcon name={row.id} cls="provider-icon provider-icon-sm" /> <div> <div className="title">{row.label}</div> <div className="sub">{statusText}</div> diff --git a/gui/src/styles/provider-catalog.css b/gui/src/styles/provider-catalog.css index c6c5618a41..6d87c59c5b 100644 --- a/gui/src/styles/provider-catalog.css +++ b/gui/src/styles/provider-catalog.css @@ -60,6 +60,16 @@ text-overflow: ellipsis; } +/* Both row kinds are space-between, so a mark placed before the title would push + the badges into the middle of the row. The text block takes the free space + instead, and `min-width: 0` lets a long provider label ellipsize rather than + widen the row past the modal. + + Scoped to the catalog on purpose: `.list-row` is shared, and the other lists + that use it have no mark to lay out. */ +.provider-catalog-rows .provider-icon { flex: 0 0 auto; } +.provider-catalog-rows .provider-icon + div { flex: 1 1 auto; min-width: 0; } + /* An account row is normally one horizontal line (`.list-row` is a row flex). While a login for that row is in flight it grows a second line for the authorization URL, device code, and paste field, so the head keeps the diff --git a/gui/tests/provider-catalog-marks.test.tsx b/gui/tests/provider-catalog-marks.test.tsx new file mode 100644 index 0000000000..21a5b8c672 --- /dev/null +++ b/gui/tests/provider-catalog-marks.test.tsx @@ -0,0 +1,127 @@ +import { afterEach, beforeEach, expect, test } from "bun:test"; +import { Window } from "happy-dom"; +import { act } from "react"; +import type { Root } from "react-dom/client"; +import { LanguageProvider } from "../src/i18n/provider"; +import AddProviderModal from "../src/components/AddProviderModal"; + +/** + * The Add-Provider catalog is the list a user reads to CHOOSE a provider, and it + * was the only provider surface with no marks at all -- the rail, the details + * panel and the dashboard rows have drawn them for a while. + * + * These pin the two properties that make the marks correct rather than merely + * present: every row has one, and none of them is announced. + */ + +const globals = ["document", "window", "navigator", "localStorage", "IS_REACT_ACT_ENVIRONMENT"] as const; +let previous: Record<(typeof globals)[number], unknown>; +let win: Window; +let host: HTMLElement; +let root: Root | null = null; +let originalFetch: typeof globalThis.fetch; + +/** Two presets whose ids resolve a mark, and one that cannot. */ +const PRESETS = [ + { id: "cerebras", label: "Cerebras", adapter: "openai-completions", baseUrl: "https://api.cerebras.ai/v1", auth: "key" }, + { id: "together", label: "Together", adapter: "openai-completions", baseUrl: "https://api.together.xyz/v1", auth: "key" }, + // No asset upstream: this row must still draw the fallback tile. + { id: "chutes", label: "Chutes", adapter: "openai-completions", baseUrl: "https://llm.chutes.ai/v1", auth: "key" }, +]; + +beforeEach(() => { + previous = Object.fromEntries(globals.map(k => [k, Reflect.get(globalThis, k)])) as typeof previous; + originalFetch = globalThis.fetch; + win = new Window({ url: "http://localhost/" }); + Object.defineProperty(win.navigator, "language", { configurable: true, value: "en-US" }); + Object.defineProperties(globalThis, { + document: { configurable: true, value: win.document }, + window: { configurable: true, value: win }, + navigator: { configurable: true, value: win.navigator }, + localStorage: { configurable: true, value: win.localStorage }, + }); + (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + + Object.defineProperty(globalThis, "fetch", { + configurable: true, + value: async (input: RequestInfo | URL) => { + const url = new URL(String(input), "http://localhost"); + if (url.pathname === "/api/provider-presets") return Response.json({ providers: PRESETS }); + if (url.pathname === "/api/oauth/providers") return Response.json({ providers: [] }); + if (url.pathname === "/api/usage") return Response.json({ providers: [] }); + return Response.json({}); + }, + }); + + host = win.document.createElement("div") as unknown as HTMLElement; + win.document.body.appendChild(host as never); +}); + +afterEach(async () => { + if (root) { + const current = root; + await act(async () => { current.unmount(); }); + root = null; + } + for (const key of globals) { + Object.defineProperty(globalThis, key, { configurable: true, value: previous[key] }); + } + Object.defineProperty(globalThis, "fetch", { configurable: true, value: originalFetch }); + await win.happyDOM?.close?.(); +}); + +async function mountCatalog() { + const { createRoot } = await import("react-dom/client"); + await act(async () => { + root = createRoot(host); + root.render( + <LanguageProvider> + {/* `paid` is where an API-key preset buckets; the default `free` tab would + render an empty list and make these assertions vacuous. */} + <AddProviderModal apiBase="" existingNames={[]} initialTier="paid" onClose={() => {}} onAdded={() => {}} /> + </LanguageProvider>, + ); + }); + await act(async () => { await new Promise(r => setTimeout(r, 60)); }); +} + +function catalogRows(): HTMLElement[] { + return [...host.querySelectorAll<HTMLElement>(".provider-catalog-rows .list-row")]; +} + +/* + * Every row, including the one whose vendor publishes nothing. A provider with no + * asset must still render the fallback tile: the row that draws neither is + * indistinguishable from a broken lookup, and misaligned against its neighbours. + */ +test("every catalog row draws a mark, including a provider with no asset", async () => { + await mountCatalog(); + const rows = catalogRows(); + expect(rows.length).toBeGreaterThan(0); + const bare = rows.filter(row => row.querySelector(".provider-icon") === null); + expect(bare.map(row => row.textContent?.slice(0, 24))).toEqual([]); +}); + +/* + * The mark sits beside a label that already names the provider, so it must add + * nothing to the accessible name -- an `<img alt="Cerebras">` next to the word + * Cerebras makes a screen reader say it twice. + */ +test("a catalog mark is decoration, not a second announcement of the name", async () => { + await mountCatalog(); + for (const img of host.querySelectorAll(".provider-catalog-rows .provider-icon img")) { + expect(img.getAttribute("alt")).toBe(""); + expect(img.getAttribute("aria-hidden")).toBe("true"); + } +}); + +/* + * The mark comes first, before the title block. `.list-row` is space-between, so + * a mark inserted anywhere else pushes the badge strip into the middle of the row. + */ +test("the mark leads the row so the badges stay right-aligned", async () => { + await mountCatalog(); + for (const row of catalogRows()) { + expect(row.firstElementChild?.classList.contains("provider-icon")).toBe(true); + } +}); From 9d122ddb10ef296506005cf46c36ec7b49ae04b1 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 03:12:50 +0900 Subject: [PATCH 21/42] docs(devlog): close the provider-marks unit with what the plan got wrong (#3101) Every phase is on dev. Providers resolving a mark went 45 -> 77 of 83; the six that keep the fallback tile have their searched paths recorded in the README. Four corrections, each a wrong assumption rather than a detail. One plate is not enough. 040 specified a single constant light plate for colour artwork too dark for the dark tile. That fixed twelve marks and left six failing, because their artwork is near-WHITE -- drawn for a dark header. They needed the opposite plate. The doc assumed the failure was one-directional because every example it had was. A mark can solve this itself. digitalocean.svg carries its own prefers-color-scheme rule, so plating it defeated the vendor and produced light-on-light at 1.01:1 -- worse than doing nothing. Only rendered measurement caught it; reading the file says #000 and stops there. Tracing a favicon traces the plate. 020 called vectorization settled because the two previous traces had transparent backgrounds. A favicon is a glyph on a filled square, and the first pass traced the square: baseten 97.7% ink, bizrouter 89.3%. The gap was older than the count suggested. The luminance guard, written for the 26 new marks, immediately failed on five old ones -- including the two files the Integrations page already masks, invisible here the whole time because the two surfaces had no shared decision. Verification: privacy:scan passed, repo-hygiene 12 pass. Docs only. --- .../260901_provider_marks/000_plan.md | 0 .../010_wp2_wire_present_assets.md | 0 .../260901_provider_marks/020_wp3_lane_a.md | 0 .../260901_provider_marks/030_wp4_lane_b.md | 0 .../260901_provider_marks/040_wp5_painting.md | 0 .../050_wp6_catalog_and_delivery.md | 0 .../_fin/260901_provider_marks/090_outcome.md | 60 +++++++++++++++++++ 7 files changed, 60 insertions(+) rename devlog/{_plan => _fin}/260901_provider_marks/000_plan.md (100%) rename devlog/{_plan => _fin}/260901_provider_marks/010_wp2_wire_present_assets.md (100%) rename devlog/{_plan => _fin}/260901_provider_marks/020_wp3_lane_a.md (100%) rename devlog/{_plan => _fin}/260901_provider_marks/030_wp4_lane_b.md (100%) rename devlog/{_plan => _fin}/260901_provider_marks/040_wp5_painting.md (100%) rename devlog/{_plan => _fin}/260901_provider_marks/050_wp6_catalog_and_delivery.md (100%) create mode 100644 devlog/_fin/260901_provider_marks/090_outcome.md diff --git a/devlog/_plan/260901_provider_marks/000_plan.md b/devlog/_fin/260901_provider_marks/000_plan.md similarity index 100% rename from devlog/_plan/260901_provider_marks/000_plan.md rename to devlog/_fin/260901_provider_marks/000_plan.md diff --git a/devlog/_plan/260901_provider_marks/010_wp2_wire_present_assets.md b/devlog/_fin/260901_provider_marks/010_wp2_wire_present_assets.md similarity index 100% rename from devlog/_plan/260901_provider_marks/010_wp2_wire_present_assets.md rename to devlog/_fin/260901_provider_marks/010_wp2_wire_present_assets.md diff --git a/devlog/_plan/260901_provider_marks/020_wp3_lane_a.md b/devlog/_fin/260901_provider_marks/020_wp3_lane_a.md similarity index 100% rename from devlog/_plan/260901_provider_marks/020_wp3_lane_a.md rename to devlog/_fin/260901_provider_marks/020_wp3_lane_a.md diff --git a/devlog/_plan/260901_provider_marks/030_wp4_lane_b.md b/devlog/_fin/260901_provider_marks/030_wp4_lane_b.md similarity index 100% rename from devlog/_plan/260901_provider_marks/030_wp4_lane_b.md rename to devlog/_fin/260901_provider_marks/030_wp4_lane_b.md diff --git a/devlog/_plan/260901_provider_marks/040_wp5_painting.md b/devlog/_fin/260901_provider_marks/040_wp5_painting.md similarity index 100% rename from devlog/_plan/260901_provider_marks/040_wp5_painting.md rename to devlog/_fin/260901_provider_marks/040_wp5_painting.md diff --git a/devlog/_plan/260901_provider_marks/050_wp6_catalog_and_delivery.md b/devlog/_fin/260901_provider_marks/050_wp6_catalog_and_delivery.md similarity index 100% rename from devlog/_plan/260901_provider_marks/050_wp6_catalog_and_delivery.md rename to devlog/_fin/260901_provider_marks/050_wp6_catalog_and_delivery.md diff --git a/devlog/_fin/260901_provider_marks/090_outcome.md b/devlog/_fin/260901_provider_marks/090_outcome.md new file mode 100644 index 0000000000..dec45604af --- /dev/null +++ b/devlog/_fin/260901_provider_marks/090_outcome.md @@ -0,0 +1,60 @@ +# Outcome + +Unit closed 2026-09-01. Every phase is on `dev`. + +| Phase | Doc | PR | On `dev` as | +|---|---|---|---| +| wp1 | 000 | #3092 | `5ef84b61e` -- roadmap, docs only | +| wp2 | 010 | #3093 | `a11038cb2` -- wire the committed assets + gap guard | +| wp3/wp4 | 020, 030 | #3095 | `910b4c736` -- 26 marks sourced | +| wp5 | 040 | #3098 | `15f92e3f6` -- the painting contract | +| wp6 | 050 | #3099 | `d71aa07c0` -- catalog marks | + +Providers resolving a mark: **45 -> 77 of 83**. Painting split on the merged +head: 47 image, 17 mask, 7 light plate, 6 dark plate. + +## What the plan got wrong + +**One plate is not enough.** 040 specified a single constant light plate for +colour artwork too dark for the dark tile. That fixed twelve marks and broke +nothing, but it left six others still failing -- `parallel` at 1.00 dominant +luminance, `bizrouter` 0.98, `nebius` 0.87 -- because their artwork is near-*white*, +drawn for a dark header. They needed the opposite plate. The doc had assumed the +failure mode was one-directional because every example it had was. + +**A mark can solve this itself.** `digitalocean.svg` carries its own +`@media (prefers-color-scheme: dark)` rule that repaints the glyph `#F4F5F5`. +Plating it defeated the vendor and produced light-on-light at 1.01:1 -- worse +than doing nothing. Nothing in the plan anticipated an asset that adapts, and +only rendered measurement caught it; reading the file's fill colour says #000 and +stops there. + +**Tracing a favicon traces the plate.** 020 described the vectorization path as +settled work because `hermes-agent.svg` and `gajae-code.svg` had gone smoothly. +Those sources were transparent-background artwork. A favicon is usually a glyph +on a filled rounded square, and the first pass traced the square: `baseten` came +out 97.7% ink, `bizrouter` 89.3%. Border-ring plate detection was added and found +real plates behind six assets. + +**The gap was older and wider than the count suggested.** The luminance guard, +written for the 26 new marks, immediately failed on five old ones: +`opencode.svg` (#211e1e) and `kimi-color.svg` (#1a1a1a) are the very files the +Integrations page already masks -- invisible on the provider surface the whole +time, because the two surfaces had no shared decision. `grok.svg` is the same +story one PR later. `ollama-color.svg` and `vercel-ai-gateway-color.svg` had never +been caught by either pass. + +## Six providers keep the fallback tile + +`chutes`, `nscale`, `tencent-coding-plan`, and the three `volcengine` plan ids. +Each was probed at its registry `baseUrl` and `dashboardUrl`, its docs subdomain, +and the conventional icon paths. What was found and why it was refused is in the +provider-icons README. A recorded empty is a result; borrowing a neighbouring +brand's mark would be a misattribution that outlives the commit. + +## Verification as merged + +CI green on every PR head, including the unsharded macOS job, before each admin +squash merge. On `d71aa07c0`: root and gui `tsc --noEmit` exit 0, `oxlint` clean, +`privacy:scan` clean. Every guard added in this unit was driven red before being +kept -- eleven falsifications across five test files. From 6123be31f5fbfc20d1aaa7996d5423a2728fbafc Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 03:55:05 +0900 Subject: [PATCH 22/42] fix(history): resolve a rollout's session_meta by thread id (#3026) (#3103) * fix(history): resolve a rollout's session_meta by thread id A forked or branched session appends the SOURCE thread's session_meta after its own. codex-rs `apply_session_meta_from_item` discards any record whose payload id is not the canonical thread id, so that trailing line is ordinary rollout content -- but three call sites folded the file with the id-agnostic `readLatestSessionMeta` and answered with the foreign thread's provider. The writer skipped such a file outright, while the database row still flipped. That pair is unrestorable: the manifest expects an OpenCodex post-image the file never received, so `snapshotRolloutForRestore` throws history_backup_rollout_postimage_mismatch. `preflightRestoreTargets` is all-or-nothing, so a handful of forked threads blocks every entry, `ocx stop` exits 1 on every invocation, and `ocx update` aborts at its gate. The state never clears on retry. Resolve the record by id in the writer and in both validators. The append now carries this thread's own metadata, which the app honors, instead of cloning a foreign record it would discard. Fixes #3026 * fix(history): stop restore from erasing a user's first message Carries @ntdat812's 25ce1edc8 for the id-aware half of #3026 and adds the second half the issue reports. has_user_event has two writers, so a final state cannot establish who wrote it. Routing derives the post-image value from first_user_message AT SNAPSHOT TIME, but restore recomputed it from the message as it is now. A row routed while its message was null, then given a first message by the user, looked exactly like OpenCodex's own write - and restore put it back to 0. The manifest entry now records whether the message was non-empty when the snapshot was taken. A boolean only, never the text: the manifest is a file on disk and the message is user content. Ownership is decided by which tuple the row wears. The recorded original and the expected post-image are unambiguous. Drift from 0 to 1 on the routed tuple is the user's, because a row wearing that tuple was written by OpenCodex and anything on top of it followed. Drift on the original tuple is decided by a per-entry relabel marker written before the routing write and resolved after it, so a crash between the two leaves an honest unknown that the observed row resolves. One cell genuinely cannot be decided: an OpenAI-origin entry whose routing would have produced a 1, wearing the original tuple, with the marker still pending. Routing-never-landed-plus-activity and routing-landed-then-legacy- recovery produce an identical row and nothing durable separates them. That refuses, which is what dev already does there. A guess would either erase real activity or fabricate it. Manifests written before these fields exist keep the current-row reading and restore exactly as they do today - refusing them would brick the population the fix exists to repair. * fix(history): finish the manifest v2 migration and reopen stale markers Review of the has_user_event work found three holes. A surviving manifest means a previous route/restore cycle did not consume it, and its relabel marker describes that attempt. Carrying a stale "committed" into a new routing attempt let a later restore treat it as proof that OpenCodex authored an event flag the user had since set. Every new attempt now reopens the marker as pending; the recorded snapshot itself stays, since it is the original provenance. Manifest v2 existed only in the type. The validator still rejected version 2 and the writer still emitted version 1, so the new fields were being written into a manifest that claimed not to have them. The validator now accepts 1 and 2 and checks both optional fields when present - an unvalidated hadFirstUserMessage: "false" is truthy and would have selected the wrong restore verdict. The classifier's D branch matched any opencodex row, so C and D both matched an exec-origin row and D won only by being tested first. D is now the tuple routing actually produces: routeOpenai keeps the source, routeExec moves exec to cli. The plan asked for a state-transition matrix and I had shipped two cases. There are now sixteen, one per cell, and writing them found the exec-origin post-image case where routeExec always writes 1 - so that row is B, not D. * fix(history): re-snapshot a manifest that outlived its restore Review found the reopen fix incomplete and its regression vacuous. The regression let an ordinary restore consume the manifest, so the second route saw no existing entry and the reopen code never ran. It now forces the consume to fail with setBeforeHistoryBackupConsumeForTests, which is the only way a manifest actually survives into a second routing attempt. That exposed the real gap. Reopening the marker was not enough: the entry also carries hadFirstUserMessage and hasUserEvent, and both described the PREVIOUS attempt. The stale message flag made the new routed row match the expected post-image, and the stale event value restored the thread to a state two events old - a restore that had already happened, plus whatever the user did afterwards. Both are re-recorded for the new attempt. The provenance tuple stays, because it is the original and a routed row must never overwrite it; the event baseline is only refreshed when the row is back at that tuple. Manifest promotion was also incomplete: only a brand-new manifest started at version 2, so an existing v1 kept writing the new fields under a schema that did not declare them. Any manifest written forward is now v2. The regression asserts the whole sequence: route, failed consume, user types, route again, restore - and the user's first message survives. Red without the refreshed baseline. * fix(history): prove a relabel was undone before trusting a fresh baseline Third review round found the baseline refresh resting on the same inference the classifier already refuses: tuple equality does not establish ownership. Route to opencodex, legacy recovery back to openai, and the row wears the original tuple with a 1 that OpenCodex wrote - refreshing there adopts that as the user's baseline and the next restore keeps an event the user never generated. So the refresh now requires proof, and the proof is a relabel marker reading "none". Nothing wrote that value before; a restore that lands and passes its readback now records it on the surviving manifest, which is exactly the state where a later attempt needs to tell "already restored" from "still mid-route". Absent the proof the recorded baseline is kept: one event stale still restores to a state the user was in, while a wrong refresh silently rewrites what OpenCodex owns. The write happens after finalization fails rather than before consuming it - rewriting the manifest ahead of the consume changes the fingerprint the consume checks, which broke twelve tests when I tried it that way. Adds the legacy-return row to the classifier matrix, which is the history the audits kept circling: committed marker, expected-event-1 route, original tuple, restores to 0. * fix(history): refuse an ambiguous reroute instead of picking a baseline Fourth review round: the "none" proof is best-effort, so a failed write leaves the entry reading committed while the row has drifted. Keeping the recorded baseline erases the user's event; refreshing it preserves one OpenCodex authored. That is another undecidable cell, and the reviewer was right that neither reading is the safe default. A reroute in that state now refuses with history_apply_ambiguous_reroute rather than committing to an interpretation. Nothing is written, the manifest stays for a human, and the refusal surfaces as an integrity failure with zero rows applied. Adds the end-to-end legacy-return regression the reviewer asked for: route writes opencodex/vscode/1, legacy recovery pulls it back to openai/vscode/1, and the row now wears its original tuple carrying an event OpenCodex wrote. The classifier row alone could not express this history - it needed the real route and the real recovery. The assertion is the contract rather than one implementation: the 1 must not survive as the user's, so either restore returns it to 0 or the layer refuses and keeps the manifest. Drops the duplicate classifier row that added no coverage over its neighbour. * fix(history): refuse only the reroute that is genuinely undecidable Fifth review round found the refusal too broad and the legacy-return test green for the wrong reason. The guard rejected every unproven event mismatch, including the case where the previous route would have written 0 - there an observed 1 can only be the user's, which is the expected-event-0 cell the classifier deliberately preserves. Refusal is now conditioned on whether the prior route could itself have authored the difference. The end-to-end legacy-return test passed because the fixture's rollout omits source, so restore refused during rollout preflight before the classifier mattered - a broken classifier would have passed it too. It now writes the matching session_meta so the real path runs, and asserts the exact outcome: the 1 returns to 0 and the manifest is consumed. Two smaller things: leftover debug logging is gone, and the specific integrity code now travels with the result. failureReason: "integrity" alone reads as "retry or run doctor", and history_apply_ambiguous_reroute is not retryable - it means two histories produced the same row and the manifest needs a human. * fix(history): decide a reroute by direction and origin, and route the reason out Sixth review round found the reroute guard collapsed to a single flag and the integrity code stopping at the worker boundary. The condition now names all five cells. A 1 to 0 drift is always foreign - nothing in this system clears the flag, so a baseline that moved down is a decision the manifest does not own. A 0 to 1 drift is the user's when the entry is exec-origin (routeExec moves source to cli and legacy recovery does not move it back, so it was never routed away and back), when a "none" marker proves the previous relabel was undone, or when the previous route would have written 0. It refuses when the previous route would have written 1, and when a legacy entry records nothing about it - undefined is not false. integrityCode existed on the sync result and went nowhere: HistoryWorkerResult and CodexHistoryJobOutcome had no field for it and classifyWorkerResult dropped it, so an operator still read "failed integrity checks; run doctor". It is typed and forwarded through both, and an ambiguous reroute now says what it is - the manifest cannot prove whether an earlier relabel was undone, nothing changed, resolve it manually rather than retrying. Retrying reaches the same refusal. * docs(devlog): record what wp3's seven review rounds changed about the plan --------- Co-authored-by: Nguyen Thanh Dat <ntdat812@gmail.com> --- .../030_wp3_forked_rollout_restore.md | 32 ++ src/codex/history-job.ts | 10 + src/codex/history-manifest.ts | 37 +- src/codex/history-provider.ts | 215 ++++++++++- src/codex/history-worker.ts | 3 + tests/codex-history-job.test.ts | 7 + tests/codex-history-provider.test.ts | 348 +++++++++++++++++- 7 files changed, 623 insertions(+), 29 deletions(-) diff --git a/devlog/_plan/260831_prio70_train_round2/030_wp3_forked_rollout_restore.md b/devlog/_plan/260831_prio70_train_round2/030_wp3_forked_rollout_restore.md index 190fb18b8e..f3a77de793 100644 --- a/devlog/_plan/260831_prio70_train_round2/030_wp3_forked_rollout_restore.md +++ b/devlog/_plan/260831_prio70_train_round2/030_wp3_forked_rollout_restore.md @@ -394,3 +394,35 @@ scan on `ssh lidge`. `Closes #3026`. PR #3056 is 630 commits behind `dev`; carry its id-aware reader with credit to its author rather than rebasing that branch, the same way round 1's wp3 carried Ingwannu's `aec717722`. + +## What implementation added beyond this plan + +Seven adversarial review rounds against the built branch (findings 3, 2, 1, 1, 2, 2, 0). +The eleven planning rounds got the classifier right; every implementation finding was in +the machinery around it, which is worth recording separately: + +- **A surviving manifest has to be re-snapshotted, not just re-marked.** The plan said to + reopen the relabel marker on a new routing attempt. That was half of it: the entry also + carries `hadFirstUserMessage` and `hasUserEvent`, and both described the previous + attempt. The stale message flag made the new routed row match the expected post-image; + the stale event value restored the thread to a state two events old. +- **Refreshing the baseline needs the same proof the classifier needs.** Tuple equality + does not establish that the previous relabel was undone — route-then-legacy-recovery + lands on the original tuple. A restore that lands and passes its readback now records + `relabel: "none"`, which is the proof. Nothing wrote that value before. +- **A sixth cell, and it refuses.** When the proof is absent and the row has drifted, + neither reading is safe. That is the plan's undecidable shape arriving one layer up, in + `rememberOriginal` rather than in the classifier, and it refuses the same way. +- **The decision is direction and origin, not one flag.** Reverse drift is always foreign; + `0 → 1` is the user's for an exec-origin entry, under a `none` marker, or when the + previous route would have written 0; everything else refuses, and a legacy `undefined` + is not `false`. +- **The refusal has to reach a person.** `failureReason: "integrity"` alone reads as + "retry or run doctor", and an ambiguous reroute is not retryable. The specific code now + travels through the worker and job layers to an operator message that says the manifest + needs manual resolution. + +And two about verification: a regression that let an ordinary restore consume the manifest +never reached the reopen path at all, and the legacy-return end-to-end test passed because +the fixture's rollout omitted `source`, so restore refused at rollout preflight before the +classifier was consulted. Both were green for reasons unrelated to what they claimed. diff --git a/src/codex/history-job.ts b/src/codex/history-job.ts index b52e57dccc..1f4e174c51 100644 --- a/src/codex/history-job.ts +++ b/src/codex/history-job.ts @@ -110,6 +110,8 @@ export type CodexHistoryJobOutcome = | { readonly kind: "blocked"; readonly reason: "busy" | "database" | "unsafe-path" | "desired_disabled" | "desired_enabled" } | { readonly kind: "failed"; readonly reason: "worker-error" | "worker-died" | "timeout"; readonly message: string; readonly historyFailureReason?: CodexHistoryFailureReason; + /** Specific integrity condition when `historyFailureReason` is `"integrity"`. */ + readonly historyIntegrityCode?: string; readonly rows?: number; readonly files?: number }; /** @@ -258,6 +260,13 @@ export function describeHistoryJobFailure( return "permission was denied while writing Codex history; this is not a Codex app lock. Run 'ocx doctor'."; } if (outcome.historyFailureReason === "integrity") { + // Not every integrity stop is a retry. An ambiguous reroute means two histories + // produced the same row and no durable fact separates them, so retrying reaches the + // same refusal - the manifest needs a person, and saying "run doctor" sends them the + // wrong way. + if (outcome.historyIntegrityCode === "history_apply_ambiguous_reroute") { + return "a Codex history entry could not be re-routed because its manifest cannot prove whether an earlier relabel was undone; nothing was changed and the manifest was kept. Resolve it manually rather than retrying."; + } return partiallyChanged ? "the history backup or its restore target changed after a partial restore; the manifest was retained for review and safe retry. Run 'ocx doctor'." : "the history backup or its restore target failed integrity checks; no unverified provider metadata was applied. Run 'ocx doctor'."; @@ -298,6 +307,7 @@ function classifyWorkerResult(result: HistoryWorkerResult): CodexHistoryJobOutco reason: "worker-error", message: redactWorkerMessage(result.message), ...(result.reason ? { historyFailureReason: result.reason } : {}), + ...(result.integrityCode ? { historyIntegrityCode: result.integrityCode } : {}), ...(result.rows !== undefined && result.files !== undefined ? { rows: result.rows, files: result.files } : {}), diff --git a/src/codex/history-manifest.ts b/src/codex/history-manifest.ts index 3f5f0c2af8..01e649b306 100644 --- a/src/codex/history-manifest.ts +++ b/src/codex/history-manifest.ts @@ -10,10 +10,37 @@ export interface CodexHistoryBackupEntry { modelProvider: string; source: string; hasUserEvent: 0 | 1; + /** + * Whether the row had a non-empty `first_user_message` when the snapshot was taken. + * + * Routing derives the post-image `has_user_event` from the message AT SNAPSHOT TIME + * (`history-provider.ts` `routeOpenai`), so a restore that recomputes it from the + * message as it is NOW will mistake the user's first message for OpenCodex's own write + * and erase it. Only the emptiness is recorded, never the text: this manifest is a file + * on disk and the message is user content. + * + * Optional because manifests written before this field exists cannot be given one. An + * entry without it falls back to the current-row reading, which is exactly as imprecise + * as the behaviour it replaces and no worse. + */ + hadFirstUserMessage?: boolean; + /** + * Whether OpenCodex's routing relabel is known to have landed for this entry. + * + * `pending` is written before the routing write and resolved after it, so a crash + * between the two leaves an honest "unknown" rather than a confident wrong answer. The + * observed row resolves it: the recorded original means the write did not land, the + * expected post-image means it did. + * + * Absent on entries written before the field existed. Those refuse only in the one + * genuinely undecidable case — original tuple with `has_user_event` moved 0 to 1 — + * which `dev` already refuses today. + */ + relabel?: "pending" | "committed" | "none"; } export interface CodexHistoryBackupManifest { - version: 1; + version: 1 | 2; stateDbPath: string; entries: Record<string, CodexHistoryBackupEntry>; } @@ -76,7 +103,7 @@ export function validateCodexHistoryBackupManifest( expectedStateDbPath: string, ): CodexHistoryManifestValidation { if (!isRecord(raw) - || raw.version !== 1 + || (raw.version !== 1 && raw.version !== 2) || typeof raw.stateDbPath !== "string" || !raw.stateDbPath.trim() || !isAbsolute(raw.stateDbPath) @@ -103,6 +130,12 @@ export function validateCodexHistoryBackupManifest( || typeof value.hasUserEvent !== "number" || !Number.isSafeInteger(value.hasUserEvent) || (value.hasUserEvent !== 0 && value.hasUserEvent !== 1) + // Optional, but not unvalidated: a truthy `hadFirstUserMessage: "false"` would select + // the wrong restore verdict, and an unrecognized `relabel` would be read as a state + // the classifier does not have. + || (value.hadFirstUserMessage !== undefined && typeof value.hadFirstUserMessage !== "boolean") + || (value.relabel !== undefined + && value.relabel !== "pending" && value.relabel !== "committed" && value.relabel !== "none") || !hasAllowedProvenance(value)) { return { ok: false, reason: "schema", scope: "entry-provenance" }; } diff --git a/src/codex/history-provider.ts b/src/codex/history-provider.ts index 4ee8bac87d..d7f1832e8f 100644 --- a/src/codex/history-provider.ts +++ b/src/codex/history-provider.ts @@ -227,6 +227,11 @@ function integrityFailureResult(error: CodexHistoryIntegrityError): CodexHistory files: error.progress.files, failed: true, failureReason: "integrity", + // The specific code, so an operator sees WHICH integrity condition stopped the + // transition rather than a generic "run doctor". `history_apply_ambiguous_reroute` in + // particular needs manual resolution: the manifest is intact and the safe move is to + // inspect it, not to retry. + integrityCode: error.message, }; } @@ -239,6 +244,15 @@ export interface CodexHistorySyncResult { failed?: true; /** Why the retry budget was exhausted when `failed` is set. */ failureReason?: CodexHistoryFailureReason; + /** + * The specific integrity condition, when `failureReason` is `"integrity"`. + * + * `failureReason` alone tells an operator only that something was inconsistent, which + * reads as "retry or run doctor". Some of these are not retryable — + * `history_apply_ambiguous_reroute` means two histories produced the same row and the + * manifest needs a human — so the code travels with the result. + */ + integrityCode?: string; } interface ThreadRow { @@ -361,7 +375,10 @@ function readBackupStrict(path: string, stateDbPath: string): StrictBackupRead { return { kind: "known", present: false, - manifest: { version: 1, stateDbPath, entries: {} }, + // New manifests carry the snapshot and relabel fields, so they are v2. v1 stays + // readable: an entry written before those fields existed falls back to the + // current-row reading, which is the behaviour it was written under. + manifest: { version: 2, stateDbPath, entries: {} }, fingerprint: "absent", }; } @@ -462,14 +479,80 @@ function writeBackup(path: string, manifest: CodexHistoryBackupManifest, stateDb atomicWriteFile(path, JSON.stringify({ ...manifest, stateDbPath: manifest.stateDbPath ?? stateDbPath }, null, 2) + "\n"); } -function rememberOriginal(manifest: CodexHistoryBackupManifest, row: ThreadRow): void { - if (manifest.entries[row.id]) return; +function rememberOriginal(manifest: CodexHistoryBackupManifest, row: ApplyRowSnapshot): void { + const existing = manifest.entries[row.id]; + if (existing) { + // A surviving entry means a previous route/restore cycle did not consume its manifest. + // Its `relabel` describes THAT attempt, and this one has not written yet, so a stale + // `committed` would let a later restore treat the marker as proof that OpenCodex + // authored an event flag the user had since set. + // + // The provenance tuple stays — it is the ORIGINAL, and a routed row must never + // overwrite it. But `hadFirstUserMessage` is not provenance: it describes the input to + // one routing write, and this attempt has its own. Leaving the previous attempt's value + // makes the new routed row match the expected post-image and erases activity that + // arrived in between. Re-record it, and promote the manifest so the field is covered by + // the schema that declares it. + const previousRelabel = existing.relabel; + const previousHadFirstUserMessage = existing.hadFirstUserMessage; + existing.relabel = "pending"; + existing.hadFirstUserMessage = hasFirstUserMessage(row.first_user_message); + // `hasUserEvent` is the value restore returns to, and the previous attempt's can be two + // events stale — a restore that already landed, plus whatever the user did afterwards. + // Refreshing it needs proof that the previous relabel was UNDONE, because the original + // tuple alone is not proof: route-then-legacy-recovery lands on that same tuple, so + // refreshing there would adopt OpenCodex's own write as the user's baseline. + // + const atOriginalTuple = row.model_provider === existing.modelProvider + && row.source === existing.source; + const observedEvent: 0 | 1 = Number(row.has_user_event) === 1 ? 1 : 0; + if (observedEvent !== existing.hasUserEvent) { + // The row's event disagrees with the recorded baseline. Whether that is decidable is + // a function of DIRECTION and ORIGIN, not of one flag: + // + // - `1 -> 0` is always foreign. Nothing in this system clears the flag: routing only + // ever sets it, the user only ever sets it, and legacy recovery sets it to 1. A + // baseline that moved down is a decision this manifest does not own. + // - `0 -> 1` on an exec-origin entry is the user's. `routeExec` moves `source` to + // `cli` and legacy recovery does not move it back, so an exec-origin row wearing + // its original tuple was never routed away and back. + // - `0 -> 1` with `relabel: "none"` is the user's: a restore landed and undid the + // previous relabel, so the observed row is the honest pre-route state. + // - `0 -> 1` where the previous route would have written 0 is the user's, because + // OpenCodex could not have authored a 1 it never writes. + // - `0 -> 1` where the previous route WOULD have written 1, or where a legacy entry + // records nothing about it, is undecidable: routing-never-landed-plus-activity and + // routing-landed-then-legacy-recovery produce the same row. Refuse rather than pick. + const reverseDrift = observedEvent === 0; + const execOrigin = existing.modelProvider !== "openai"; + const priorRouteWroteZero = previousHadFirstUserMessage === false; + const decidable = !reverseDrift + && atOriginalTuple + && (execOrigin || previousRelabel === "none" || priorRouteWroteZero); + if (!decidable) { + throw new CodexHistoryIntegrityError("history_apply_ambiguous_reroute"); + } + existing.hasUserEvent = observedEvent; + } + manifest.version = 2; + return; + } + manifest.version = 2; manifest.entries[row.id] = { id: row.id, rolloutPath: row.rollout_path, modelProvider: row.model_provider, source: row.source, hasUserEvent: Number(row.has_user_event) === 1 ? 1 : 0, + // Emptiness only, never the text: the manifest is a file on disk and the message is + // user content. Routing derives the post-image event flag from the message as it was + // HERE, so a restore that re-reads the current message would mistake later user + // activity for OpenCodex's own write. + hadFirstUserMessage: hasFirstUserMessage(row.first_user_message), + // The routing write has not happened yet. Resolved to "committed" after it lands, or + // left pending if the process dies between the two - in which case the observed row + // is what decides. + relabel: "pending", }; } @@ -486,7 +569,13 @@ function rowMatchesRestoreTuple( function rowMatchesExpectedPostImage(row: RestoreRowSnapshot, entry: CodexHistoryBackupEntry): boolean { if (entry.modelProvider === "openai") { - const postHasUserEvent = hasFirstUserMessage(row.first_user_message) ? 1 : entry.hasUserEvent; + // Routing derived this from the message AT SNAPSHOT TIME (`routeOpenai`), so read the + // recorded flag when the manifest has one. Recomputing from the row's CURRENT message + // mistakes a first message the user sent after routing for OpenCodex's own write, and + // restore then erases it. Manifests written before the flag existed fall back to the + // current reading, which is exactly the behaviour this replaces and no worse. + const hadMessage = entry.hadFirstUserMessage ?? hasFirstUserMessage(row.first_user_message); + const postHasUserEvent = hadMessage ? 1 : entry.hasUserEvent; return rowMatchesRestoreTuple(row, "opencodex", entry.source, postHasUserEvent); } return hasFirstUserMessage(row.first_user_message) @@ -499,6 +588,60 @@ function rowMatchesExpectedPostImage(row: RestoreRowSnapshot, entry: CodexHistor ); } +/** + * What `has_user_event` should read after restore, or `null` when the row is not one this + * manifest owns. + * + * The field has two writers, so a final state cannot establish authorship on its own. Four + * shapes cover every row reachable in practice, and the tuple the row wears says which: + * + * - **A** exactly the recorded original: untouched, or already restored. + * - **B** the expected post-image: OpenCodex wrote it, so the recorded value is authoritative. + * - **C** the original tuple with the flag moved 0 to 1: either Codex-side user activity, or + * OpenCodex routing that legacy recovery has since pulled back to the original provider. + * - **D** the post-image tuple with the flag moved 0 to 1: a routed row the user then touched. + * No provenance needed - a row wearing the routed tuple was written by OpenCodex, so drift + * on top of it can only be what followed. + * + * Only C is ambiguous, and only when the route's own expected event was 1: then "routing + * never landed and the user typed" and "routing landed and legacy recovery pulled it back" + * produce an identical row, and nothing durable separates them. That one cell refuses. A + * guess there either erases real activity or fabricates it. + */ +export function restoredUserEventFor(row: RestoreRowSnapshot, entry: CodexHistoryBackupEntry): 0 | 1 | null { + if (rowMatchesRestoreTuple(row, entry.modelProvider, entry.source, entry.hasUserEvent)) { + return entry.hasUserEvent; // A + } + if (rowMatchesExpectedPostImage(row, entry)) return entry.hasUserEvent; // B + + const drifted = Number(row.has_user_event) === 1 && entry.hasUserEvent === 0; + if (!drifted) return null; + + const routeExpectedEvent = entry.hadFirstUserMessage ?? hasFirstUserMessage(row.first_user_message) ? 1 : 0; + + // D: wearing the routed tuple, so the 1 arrived after OpenCodex wrote the row. The tuple + // is the one routing actually produces — `routeOpenai` keeps the source, `routeExec` + // moves exec to cli — so D and C cannot both match rather than merely being ordered. + const routedSource = entry.modelProvider === "openai" ? entry.source : "cli"; + if (rowMatchesRestoreTuple(row, "opencodex", routedSource, 1)) return 1; + + // C: wearing the original tuple. + if (rowMatchesRestoreTuple(row, entry.modelProvider, entry.source, 1)) { + // An exec-origin entry cannot reach here by legacy recovery: routeExec moves source to + // cli and recovery does not move it back, so the original tuple is unreachable that way. + if (entry.modelProvider !== "openai") return 1; + if (entry.relabel === "none") return 1; + if (entry.relabel === "committed") { + // OpenCodex authored the 1 only if its own routing write would have produced one. + return routeExpectedEvent === 1 ? 0 : 1; + } + if (entry.relabel === undefined) return null; // legacy manifest: the pre-existing refusal + // pending: two histories reach this exact row and nothing durable tells them apart. + return routeExpectedEvent === 1 ? null : 1; + } + return null; +} + interface RestoreRolloutSnapshot { readonly identity: string; readonly latestProvider: string; @@ -549,7 +692,7 @@ function snapshotRolloutForRestore(entry: CodexHistoryBackupEntry): RestoreRollo if (identityBefore === null) { throw new CodexHistoryIntegrityError("history_backup_rollout_unrestorable"); } - const latest = readLatestSessionMeta(entry.rolloutPath); + const latest = readLatestSessionMetaForId(entry.rolloutPath, entry.id); if (!latest || (!rolloutMatchesRestoreTuple(latest, entry, entry.modelProvider, entry.source) && !rolloutMatchesExpectedPostImage(latest, entry))) { @@ -608,8 +751,7 @@ function preflightRestoreRows( if (!row || typeof row.rollout_path !== "string" || !sameCodexHistoryPath(row.rollout_path, entry.rolloutPath)) { throw new CodexHistoryIntegrityError("history_backup_target_mismatch"); } - if (!rowMatchesRestoreTuple(row, entry.modelProvider, entry.source, entry.hasUserEvent) - && !rowMatchesExpectedPostImage(row, entry)) { + if (restoredUserEventFor(row, entry) === null) { throw new CodexHistoryIntegrityError("history_backup_postimage_mismatch"); } snapshots.set(entry.id, row); @@ -625,10 +767,11 @@ function assertRestoreReadback( const row = getCurrent(entry.id); if (!row || !sameCodexHistoryPath(row.rollout_path, entry.rolloutPath) - || !rowMatchesRestoreTuple(row, entry.modelProvider, entry.source, entry.hasUserEvent)) { + || !rowMatchesRestoreTuple(row, entry.modelProvider, entry.source, Number(row.has_user_event) === 1 ? 1 : 0) + || restoredUserEventFor(row, entry) === null) { throw new CodexHistoryIntegrityError("history_backup_database_readback_mismatch"); } - const latest = readLatestSessionMeta(entry.rolloutPath); + const latest = readLatestSessionMetaForId(entry.rolloutPath, entry.id); if (inspectFirstLineProvider(entry.rolloutPath, entry.id, entry.modelProvider) !== "current" || !latest || !rolloutMatchesRestoreTuple(latest, entry, entry.modelProvider, entry.source)) { @@ -665,6 +808,19 @@ export function readLatestSessionMeta(path: string): ParsedSessionMeta | null { return readLatestSessionMetaFromText(raw); } +/** + * Same fold as {@link readLatestSessionMeta}, restricted to this thread's own metadata. + * + * A forked/branched rollout appends the SOURCE thread's `session_meta` after its own, and the + * app discards any record whose payload id is not the canonical thread id (codex-rs + * `apply_session_meta_from_item`). Reading the last line regardless of id therefore answers + * with a foreign thread's provider, which is neither what the app honors nor what we may patch. + */ +function readLatestSessionMetaForId(path: string, expectedId: string): ParsedSessionMeta | null { + const raw = readFileSync(path, "utf8"); + return readLatestSessionMetaForIdFromText(raw, expectedId); +} + function readLatestSessionMetaFromText(raw: string): ParsedSessionMeta | null { const lines = raw.split("\n"); for (let i = lines.length - 1; i >= 0; i--) { @@ -875,18 +1031,15 @@ function updateSessionMeta( return { changed: false, durableProvider: false, conflict: true }; } - const latest = readLatestSessionMeta(path); + // Resolve by id. The app ignores `session_meta` lines whose payload id != the canonical + // thread id (codex-rs `apply_session_meta_from_item`), and a forked rollout trails the source + // session's metadata, so the last line is not necessarily this thread's. Patching that record + // would clone the wrong thread's meta into a line the app discards; skipping the file entirely + // left forked threads unroutable and, once routed, unrestorable. + const latest = readLatestSessionMetaForId(path, expectedId); if (!latest) return { changed: false, durableProvider: false }; const record = latest.record; - // The app ignores `session_meta` lines whose payload id != the canonical thread id - // (codex-rs `apply_session_meta_from_item`). Forked rollouts can embed a source session's - // metadata, so an id-mismatched latest line means we'd be cloning the wrong thread's meta and - // appending a line the app would discard. Skip rather than write a no-op/misleading line. - const payloadId = record.payload.id; - if (typeof payloadId !== "string" || payloadId !== expectedId) { - return { changed: false, durableProvider: false }; - } const latestProvider = typeof record.payload.model_provider === "string" && record.payload.model_provider ? record.payload.model_provider : "openai"; @@ -1236,6 +1389,15 @@ function syncCodexHistoryProviderUnsafe(provider: CodexHistoryProvider, stateDbP throw error; } + // The routing writes landed. Resolve every pending marker and rewrite the manifest, so a + // later restore knows the relabel is OpenCodex's rather than having to infer it. A crash + // before this point leaves `pending`, which the observed row resolves at restore time. + for (const row of [...openaiRows, ...execRows]) { + const entry = manifest.entries[row.id]; + if (entry?.relabel === "pending") entry.relabel = "committed"; + } + writeBackup(backupPath, manifest, stateDbPath); + return { rows: openaiRows.length + execRows.length, files }; } finally { db.close(); @@ -1277,10 +1439,13 @@ function restoreCodexHistoryProvider(stateDbPath: string, backupPath: string): C for (const entry of entries) { const before = snapshots.get(entry.id); if (!before) throw new CodexHistoryIntegrityError("history_backup_snapshot_missing"); + // Codex-side activity that arrived after OpenCodex wrote the row is the user's, and + // restoring the manifest's snapshot over it would erase it. + const restoredEvent = restoredUserEventFor(before, entry) ?? entry.hasUserEvent; const result = update.run( entry.modelProvider, entry.source, - entry.hasUserEvent, + restoredEvent, entry.id, before.rollout_path, before.model_provider, @@ -1345,6 +1510,18 @@ function restoreCodexHistoryProvider(stateDbPath: string, backupPath: string): C if (error instanceof CodexHistoryIntegrityError) { throw new CodexHistoryIntegrityError(error.message, { rows: entries.length, files }); } + // The restore landed and its readback passed; only finalization failed, so the + // manifest survives on disk. Record that its relabel is undone, or a later routing + // attempt cannot tell this entry from one still mid-route and has to keep a baseline + // that is now stale. Best-effort: a failure here leaves exactly the prior state. + try { + for (const entry of entries) { + const stored = manifest.entries[entry.id]; + if (stored) stored.relabel = "none"; + } + manifest.version = 2; + writeBackup(backupPath, manifest, stateDbPath); + } catch { /* the surviving manifest keeps its previous marker */ } const failureReason = classifyRecoverableHistoryError(error); if (failureReason) { return { diff --git a/src/codex/history-worker.ts b/src/codex/history-worker.ts index 52151a89fb..81718b73d2 100644 --- a/src/codex/history-worker.ts +++ b/src/codex/history-worker.ts @@ -75,6 +75,8 @@ export type HistoryWorkerResult = readonly reason: "busy" | "database" | "unsafe-path" | "desired_disabled" | "desired_enabled" } | { readonly type: "error"; readonly requestId: string; readonly jobId: string; readonly message: string; readonly reason?: CodexHistoryFailureReason; + /** Specific integrity condition, so a non-retryable one can be named as such. */ + readonly integrityCode?: string; readonly rows?: number; readonly files?: number }; const OPERATIONS: ReadonlySet<string> = new Set<CodexHistoryWorkerOperation>([ @@ -181,6 +183,7 @@ export function runHistoryUnitUnderLock( jobId, message: "history_transition_failed", ...(result.failureReason ? { reason: result.failureReason } : {}), + ...(result.integrityCode ? { integrityCode: result.integrityCode } : {}), ...(result.rows > 0 || result.files > 0 ? { rows: result.rows, files: result.files } : {}), }; } diff --git a/tests/codex-history-job.test.ts b/tests/codex-history-job.test.ts index f6262a71c5..e424b068fa 100644 --- a/tests/codex-history-job.test.ts +++ b/tests/codex-history-job.test.ts @@ -132,6 +132,13 @@ test("the failure wording names the real reason instead of always blaming the Co expect(describeHistoryJobFailure(partialIntegrity, "restore")).toContain("partial restore"); expect(describeHistoryJobFailure(partialIntegrity, "restore")).toContain("manifest was retained"); + // An ambiguous reroute is not a retry: two histories produced the same row and no durable + // fact separates them, so "run doctor" points the operator the wrong way. + const ambiguous = { ...integrity, historyIntegrityCode: "history_apply_ambiguous_reroute" } as const; + expect(describeHistoryJobFailure(ambiguous, "apply")).toContain("cannot prove whether an earlier relabel was undone"); + expect(describeHistoryJobFailure(ambiguous, "apply")).toContain("Resolve it manually"); + expect(describeHistoryJobFailure(ambiguous, "apply")).not.toContain("'ocx doctor'"); + const partialPermission = { ...permission, rows: 1, files: 1 } as const; expect(describeHistoryJobFailure(partialPermission, "apply")).toContain("changed but did not converge"); expect(describeHistoryJobFailure(partialPermission, "apply")).toContain("manifest was retained"); diff --git a/tests/codex-history-provider.test.ts b/tests/codex-history-provider.test.ts index b0c69ff1d8..1fd083e138 100644 --- a/tests/codex-history-provider.test.ts +++ b/tests/codex-history-provider.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { Database } from "bun:sqlite"; import { afterEach, describe, expect, setDefaultTimeout, test } from "bun:test"; -import { classifyRecoverableHistoryError, countPendingOpencodexHistory, historyBackupPathFor, isRecoverableHistoryError, migrateHistoryToOpenai, restoreLegacyOpenaiHistory, setAfterNoopPendingCountForTests, setAfterStrictHistoryRolloutAppendForTests, setBeforeHistoryApplyTransactionForTests, setBeforeHistoryBackupConsumeForTests, setBeforeStrictHistoryRolloutAppendForTests, setHistoryDbBusyTimeoutForTests, snapshotCodexHistoryNoop, syncCodexHistoryProvider, withHistoryRetry } from "../src/codex/history-provider"; +import { classifyRecoverableHistoryError, countPendingOpencodexHistory, historyBackupPathFor, isRecoverableHistoryError, migrateHistoryToOpenai, restoreLegacyOpenaiHistory, restoredUserEventFor, setAfterNoopPendingCountForTests, setAfterStrictHistoryRolloutAppendForTests, setBeforeHistoryApplyTransactionForTests, setBeforeHistoryBackupConsumeForTests, setBeforeStrictHistoryRolloutAppendForTests, setHistoryDbBusyTimeoutForTests, snapshotCodexHistoryNoop, syncCodexHistoryProvider, withHistoryRetry } from "../src/codex/history-provider"; import { INVALID_HISTORY_BACKUP_FIXTURES, validHistoryBackupFixture } from "./helpers/codex-history-manifest-fixtures"; // Windows CI: a transient file lock can consume the full production 5s busy timeout, tripping @@ -136,6 +136,261 @@ describe("Codex history provider sync", () => { expect(existsSync(fixture.backupPath)).toBe(false); }); + test("a surviving manifest is re-snapshotted by the next routing attempt (#3026)", () => { + // A manifest that outlives its restore is the real hazard: its recorded snapshot + // describes the PREVIOUS attempt. Carrying it into a new route makes the new routed row + // match the expected post-image, and restore then erases activity that arrived in + // between. Forcing the consume to fail is what actually reaches that path - an ordinary + // restore deletes the manifest and the reopen code never runs. + const fixture = makeFixture(); + const db = new Database(fixture.dbPath); + db.run("UPDATE threads SET first_user_message = NULL, has_user_event = 0 WHERE id = 'thread-1'"); + db.close(); + + expect(syncCodexHistoryProvider("opencodex", fixture.dbPath, fixture.backupPath)) + .toEqual({ rows: 1, files: 1 }); + + setBeforeHistoryBackupConsumeForTests(() => { + throw new Error("manifest consume interrupted"); + }); + try { + // Reported rather than thrown: the restore itself succeeded, only the consume failed. + expect(syncCodexHistoryProvider("openai", fixture.dbPath, fixture.backupPath)) + .toMatchObject({ failed: true }); + } finally { + setBeforeHistoryBackupConsumeForTests(undefined); + } + expect(existsSync(fixture.backupPath)).toBe(true); + // The restore landed and only finalization failed, so the surviving manifest records + // that its relabel is undone - the proof a later attempt needs to refresh its baseline. + expect(JSON.parse(readFileSync(fixture.backupPath, "utf8")).entries["thread-1"].relabel).toBe("none"); + + // The user types while the manifest is still on disk, then a second route runs. + const active = new Database(fixture.dbPath); + active.run("UPDATE threads SET first_user_message = 'hello', has_user_event = 1 WHERE id = 'thread-1'"); + active.close(); + syncCodexHistoryProvider("opencodex", fixture.dbPath, fixture.backupPath); + + const after = JSON.parse(readFileSync(fixture.backupPath, "utf8")) as { + version: number; + entries: Record<string, { relabel?: string; hadFirstUserMessage?: boolean }>; + }; + expect(after.version).toBe(2); + // Re-snapshotted for THIS attempt: the message is non-empty now, so the expected + // post-image is a 1 that OpenCodex itself wrote. + expect(after.entries["thread-1"]?.hadFirstUserMessage).toBe(true); + + // And the user's activity survives the restore rather than being read as OpenCodex's. + syncCodexHistoryProvider("openai", fixture.dbPath, fixture.backupPath); + const restored = new Database(fixture.dbPath, { readonly: true }); + expect(restored.query("SELECT model_provider, has_user_event FROM threads WHERE id = 'thread-1'").get()) + .toEqual({ model_provider: "openai", has_user_event: 1 }); + restored.close(); + }); + + test("refuses to reroute when the surviving manifest cannot prove its relabel was undone", () => { + // If the "none" proof could not be written - a read-only directory during the rewrite, + // say - the entry still reads "committed" while the row has drifted. Keeping the + // recorded baseline would erase the user's event; refreshing it would preserve one + // OpenCodex authored. Undecidable, so refuse rather than pick. + // + // Undecidable requires that the previous route COULD have written the differing event: + // it sets 1 only when the message was non-empty at its own snapshot. So this fixture + // keeps the message, unlike the expected-0 case where an observed 1 is provably the + // user's. + const fixture = makeFixture(); + + expect(syncCodexHistoryProvider("opencodex", fixture.dbPath, fixture.backupPath)) + .toEqual({ rows: 1, files: 1 }); + + // Restore lands, finalization fails, and the proof write fails too: hand-write the + // manifest back to "committed" to model that outcome exactly. + setBeforeHistoryBackupConsumeForTests(() => { throw new Error("manifest consume interrupted"); }); + try { + expect(syncCodexHistoryProvider("openai", fixture.dbPath, fixture.backupPath)) + .toMatchObject({ failed: true }); + } finally { + setBeforeHistoryBackupConsumeForTests(undefined); + } + const stranded = JSON.parse(readFileSync(fixture.backupPath, "utf8")) as { + entries: Record<string, { relabel?: string }>; + }; + stranded.entries["thread-1"]!.relabel = "committed"; + writeFileSync(fixture.backupPath, JSON.stringify(stranded)); + + // The user types, then a reroute is attempted against the unproven manifest. + const active = new Database(fixture.dbPath); + active.run("UPDATE threads SET first_user_message = 'hello', has_user_event = 1 WHERE id = 'thread-1'"); + active.close(); + + // Reported as an integrity refusal with nothing applied, which is how this layer + // surfaces a state it will not guess at. + expect(syncCodexHistoryProvider("opencodex", fixture.dbPath, fixture.backupPath)) + .toMatchObject({ rows: 0, files: 0, failed: true, failureReason: "integrity" }); + // Nothing was rewritten: the row and its manifest are exactly as they were. + const untouched = new Database(fixture.dbPath, { readonly: true }); + expect(untouched.query("SELECT model_provider, has_user_event FROM threads WHERE id = 'thread-1'").get()) + .toEqual({ model_provider: "openai", has_user_event: 1 }); + untouched.close(); + }); + + test("restores an event OpenCodex authored even after legacy recovery returns the tuple", () => { + // The history the audit rounds kept circling: route writes opencodex/vscode/1, legacy + // recovery pulls it back to openai/vscode/1, and the row now wears its ORIGINAL tuple + // carrying an event OpenCodex wrote. The classifier has to read the committed marker + // plus the route's expected event rather than the tuple, or restore keeps a 1 the user + // never generated. A pure classifier input cannot express this - it needs the real + // route and the real recovery. + const fixture = makeFixture({ includeLegacy: true }); + + // The fixture's rollout omits `source`, which makes restore refuse during rollout + // preflight before the classifier is ever consulted - a test that passes on that + // refusal would stay green with a broken classifier. Write the matching source so the + // real path runs. + appendFileSync(fixture.rollout, JSON.stringify({ + type: "session_meta", + payload: { id: "thread-1", model_provider: "openai", source: "vscode" }, + }) + "\n"); + + expect(syncCodexHistoryProvider("opencodex", fixture.dbPath, fixture.backupPath)) + .toEqual({ rows: 1, files: 1 }); + const routed = new Database(fixture.dbPath, { readonly: true }); + expect(routed.query("SELECT model_provider, has_user_event FROM threads WHERE id = 'thread-1'").get()) + .toEqual({ model_provider: "opencodex", has_user_event: 1 }); + routed.close(); + + // Legacy recovery returns provider to openai and leaves the event flag at 1. + restoreLegacyOpenaiHistory(fixture.dbPath); + const recovered = new Database(fixture.dbPath, { readonly: true }); + expect(recovered.query("SELECT model_provider, has_user_event FROM threads WHERE id = 'thread-1'").get()) + .toEqual({ model_provider: "openai", has_user_event: 1 }); + recovered.close(); + + // Restore must put the event back to the recorded original: the route expected a 1, so + // that 1 is OpenCodex's, not the user's. + const result = syncCodexHistoryProvider("openai", fixture.dbPath, fixture.backupPath); + const restored = new Database(fixture.dbPath, { readonly: true }); + const row = restored.query<{ model_provider: string; source: string; has_user_event: number }, []>( + "SELECT model_provider, source, has_user_event FROM threads WHERE id = 'thread-1'", + ).get()!; + restored.close(); + + // The classifier reads the committed marker plus the route's expected event rather than + // the tuple, so the 1 is returned to 0 and the manifest is consumed. + // `files: 0` because the appended metadata already names openai/vscode; the database + // row is what this history put wrong. + expect(result).toEqual({ rows: 1, files: 0 }); + expect(row).toEqual({ model_provider: "openai", source: "vscode", has_user_event: 0 }); + expect(existsSync(fixture.backupPath)).toBe(false); + }); + + describe("restore classifier state matrix", () => { + // has_user_event has two writers, so the verdict is decided by which tuple the row + // wears plus the entry's recorded provenance. This table is the whole contract; the + // end-to-end tests above prove two of its rows against real databases. + const entry = (over: Partial<Parameters<typeof restoredUserEventFor>[1]> = {}) => ({ + id: "t", rolloutPath: "/tmp/r.jsonl", + modelProvider: "openai", source: "vscode", hasUserEvent: 0 as 0 | 1, + ...over, + }); + const row = (provider: string, source: string, event: 0 | 1, message: string | null = "hi") => ({ + id: "t", rollout_path: "/tmp/r.jsonl", + model_provider: provider, source, has_user_event: event, first_user_message: message, + }); + + const cases: Array<[string, ReturnType<typeof row>, ReturnType<typeof entry>, 0 | 1 | null]> = [ + // A - exactly the recorded original. + ["A: untouched original restores its own value", row("openai", "vscode", 0), entry(), 0], + // B - the expected post-image; OpenCodex wrote it, so the manifest is authoritative. + ["B: routed post-image restores the recorded 0", row("opencodex", "vscode", 1), entry({ hadFirstUserMessage: true }), 0], + ["B: routed post-image of a null-message row", row("opencodex", "vscode", 0, null), entry({ hadFirstUserMessage: false }), 0], + ["B: exec legacy bridge is still recognized", row("openai", "cli", 1), entry({ modelProvider: "opencodex", source: "exec", hasUserEvent: 1 }), 1], + // C - original tuple with 0 to 1 drift, decided by provenance. + ["C: relabel none is user activity", row("openai", "vscode", 1), entry({ relabel: "none", hadFirstUserMessage: false }), 1], + ["C: committed whose route expected 1 is OpenCodex's own", row("openai", "vscode", 1), entry({ relabel: "committed", hadFirstUserMessage: true }), 0], + ["C: committed whose route expected 0 cannot be OpenCodex's", row("openai", "vscode", 1), entry({ relabel: "committed", hadFirstUserMessage: false }), 1], + ["C: pending with an expected-0 route is decidable", row("openai", "vscode", 1), entry({ relabel: "pending", hadFirstUserMessage: false }), 1], + ["C: pending with an expected-1 route is undecidable", row("openai", "vscode", 1), entry({ relabel: "pending", hadFirstUserMessage: true }), null], + ["C: legacy entry with drift refuses, as dev does", row("openai", "vscode", 1), entry({ hadFirstUserMessage: true }), null], + // Reverse drift is foreign under every provenance: nothing in this system clears the + // flag, so a baseline that moved down is a decision this manifest does not own. + ["reverse drift refuses even with a none marker", row("openai", "vscode", 0), entry({ hasUserEvent: 1, relabel: "none" }), null], + ["C: exec-origin cannot be reached by legacy return", row("opencodex", "exec", 1), entry({ modelProvider: "opencodex", source: "exec", relabel: "pending", hadFirstUserMessage: true }), 1], + // D - routed tuple with drift; no provenance needed. + ["D: drift on the routed tuple is the user's", row("opencodex", "vscode", 1), entry({ relabel: "pending", hadFirstUserMessage: false }), 1], + // An exec-origin row at opencodex/cli/1 is B, not D: routeExec always writes 1, so + // that IS the expected post-image and the recorded value is authoritative. + ["B: exec-origin post-image is opencodex/cli/1", row("opencodex", "cli", 1), entry({ modelProvider: "opencodex", source: "exec", hadFirstUserMessage: false }), 0], + // Neither shape - a foreign decision this manifest does not own. + ["reverse drift never restores", row("openai", "vscode", 0), entry({ hasUserEvent: 1, relabel: "committed" }), null], + ["a different provider is foreign", row("anthropic", "vscode", 0), entry(), null], + ["a different source is foreign", row("openai", "cli", 0), entry(), null], + ]; + + for (const [name, observed, recorded, expected] of cases) { + test(name, () => { + expect(restoredUserEventFor(observed, recorded)).toBe(expected); + }); + } + }); + + test("preserves a first user message that arrived after routing (#3026)", () => { + // Routing derives the post-image has_user_event from the message AT SNAPSHOT TIME. A + // restore that recomputes it from the message as it is NOW reads the user's first + // message as OpenCodex's own write and erases the activity. + const fixture = makeFixture(); + const db = new Database(fixture.dbPath); + db.run("UPDATE threads SET first_user_message = NULL, has_user_event = 0 WHERE id = 'thread-1'"); + db.close(); + + expect(syncCodexHistoryProvider("opencodex", fixture.dbPath, fixture.backupPath)) + .toEqual({ rows: 1, files: 1 }); + + // The user types for the first time while the row is routed. + const active = new Database(fixture.dbPath); + active.run("UPDATE threads SET first_user_message = 'hello', has_user_event = 1 WHERE id = 'thread-1'"); + active.close(); + + expect(syncCodexHistoryProvider("openai", fixture.dbPath, fixture.backupPath)) + .toEqual({ rows: 1, files: 1 }); + const restored = new Database(fixture.dbPath, { readonly: true }); + // Provenance restored, activity kept: OpenCodex owns provider and source, the user owns + // this flag, and the routing write for this entry produced a 0. + expect(restored.query("SELECT model_provider, source, has_user_event FROM threads WHERE id = 'thread-1'").get()) + .toEqual({ model_provider: "openai", source: "vscode", has_user_event: 1 }); + restored.close(); + expect(existsSync(fixture.backupPath)).toBe(false); + }); + + test("restores a legacy manifest that predates the snapshot fields", () => { + // Every manifest already on disk lacks hadFirstUserMessage and relabel. Refusing them + // would brick exactly the population this fix exists to repair, so an entry without the + // fields must restore on the pre-existing behaviour. + const fixture = makeFixture(); + expect(syncCodexHistoryProvider("opencodex", fixture.dbPath, fixture.backupPath)) + .toEqual({ rows: 1, files: 1 }); + + // Rewrite the manifest in the v1 shape, dropping both new fields. + const manifest = JSON.parse(readFileSync(fixture.backupPath, "utf8")) as { + version: number; + entries: Record<string, Record<string, unknown>>; + }; + manifest.version = 1; + for (const entry of Object.values(manifest.entries)) { + delete entry.hadFirstUserMessage; + delete entry.relabel; + } + writeFileSync(fixture.backupPath, JSON.stringify(manifest)); + + expect(syncCodexHistoryProvider("openai", fixture.dbPath, fixture.backupPath)) + .toEqual({ rows: 1, files: 1 }); + const restored = new Database(fixture.dbPath, { readonly: true }); + expect(restored.query("SELECT model_provider, source, has_user_event FROM threads WHERE id = 'thread-1'").get()) + .toEqual({ model_provider: "openai", source: "vscode", has_user_event: 0 }); + restored.close(); + expect(existsSync(fixture.backupPath)).toBe(false); + }); + test("does not route a new database row that was not captured in the manifest snapshot", () => { const fixture = makeFixture(); const lateRollout = join(fixture.rollout, "..", "late-rollout.jsonl"); @@ -189,21 +444,27 @@ describe("Codex history provider sync", () => { expect(JSON.parse(before.split("\n")[0])).toEqual(JSON.parse(after.split("\n")[0])); }); - test("does not append when the latest session_meta belongs to a different thread id", () => { + test("appends this thread's own session_meta when the latest one belongs to a different thread id", () => { const { dbPath, backupPath, rollout } = makeFixture(); // Simulate a forked rollout whose trailing session_meta embeds a *different* thread's id. - appendFileSync(rollout, JSON.stringify({ + const foreignLine = JSON.stringify({ type: "session_meta", timestamp: "2026-01-02T00:00:00.000Z", payload: { id: "some-other-forked-thread", model_provider: "openai", cwd: "/tmp" }, - }) + "\n"); + }); + appendFileSync(rollout, foreignLine + "\n"); const before = readFileSync(rollout, "utf8"); const result = syncCodexHistoryProvider("opencodex", dbPath, backupPath); - // DB row still flips, but the rollout is left untouched (no misleading append for a foreign id). - expect(result.files).toBe(0); - expect(readFileSync(rollout, "utf8")).toBe(before); + // The append describes THIS thread — never a clone of the foreign record, which the app + // would discard. Routing the row without the file left the pair unrestorable (#3026). + expect(result.files).toBe(1); + const after = readFileSync(rollout, "utf8"); + expect(after.startsWith(before)).toBe(true); + expect(latestSessionMetaPayload(rollout)).toMatchObject({ id: "thread-1", model_provider: "opencodex" }); + // The foreign thread's record is still there, exactly once, exactly as written. + expect(after.split("\n").filter(Boolean).filter(line => line === foreignLine)).toHaveLength(1); const db = new Database(dbPath); expect(db.query("SELECT model_provider FROM threads WHERE id = 'thread-1'").get()).toEqual({ model_provider: "opencodex" }); db.close(); @@ -630,6 +891,69 @@ describe("Codex history provider sync", () => { expect(latestSessionMetaPayload(fixture.rollout).model_provider).toBe("custom"); }); + test("consumes the manifest when a foreign-id session_meta lands after restore readback", () => { + const fixture = makeFixture(); + syncCodexHistoryProvider("opencodex", fixture.dbPath, fixture.backupPath); + // The same last-moment race as the same-id case above, except the arriving record belongs + // to another thread. The app discards it, so it is not a newer decision to protect. + setBeforeHistoryBackupConsumeForTests(() => { + appendFileSync(fixture.rollout, JSON.stringify({ + type: "session_meta", + timestamp: "2026-03-04T00:00:00.000Z", + payload: { id: "parent-thread", model_provider: "custom", source: "exec" }, + }) + "\n"); + }); + + expect(syncCodexHistoryProvider("openai", fixture.dbPath, fixture.backupPath)) + .toEqual({ rows: 1, files: 1 }); + expect(existsSync(fixture.backupPath)).toBe(false); + }); + + test("restores a forked rollout that trails its parent thread's session_meta", () => { + const fixture = makeFixture(); + expect(syncCodexHistoryProvider("opencodex", fixture.dbPath, fixture.backupPath)) + .toEqual({ rows: 1, files: 1 }); + + // A forked/branched session appends the SOURCE thread's session_meta after its own. + // codex-rs `apply_session_meta_from_item` ignores a record whose payload id is not the + // canonical thread id, so this line is ordinary rollout content, not an integrity fault. + appendFileSync(fixture.rollout, JSON.stringify({ + type: "session_meta", + timestamp: "2026-03-03T00:00:00.000Z", + payload: { id: "parent-thread", model_provider: "openai", source: "cli" }, + }) + "\n"); + + expect(syncCodexHistoryProvider("openai", fixture.dbPath, fixture.backupPath)) + .toEqual({ rows: 1, files: 1 }); + const db = new Database(fixture.dbPath, { readonly: true }); + expect(db.query("SELECT model_provider, source FROM threads WHERE id = 'thread-1'").get()) + .toEqual({ model_provider: "openai", source: "vscode" }); + db.close(); + expect(latestSessionMetaPayload(fixture.rollout)).toMatchObject({ + id: "thread-1", + model_provider: "openai", + source: "vscode", + }); + expect(existsSync(fixture.backupPath)).toBe(false); + }); + + test("leaves a foreign trailing session_meta untouched while restoring its own", () => { + const fixture = makeFixture(); + syncCodexHistoryProvider("opencodex", fixture.dbPath, fixture.backupPath); + const parentLine = JSON.stringify({ + type: "session_meta", + timestamp: "2026-03-03T00:00:00.000Z", + payload: { id: "parent-thread", model_provider: "opencodex", source: "exec" }, + }); + appendFileSync(fixture.rollout, parentLine + "\n"); + + expect(syncCodexHistoryProvider("openai", fixture.dbPath, fixture.backupPath)) + .toEqual({ rows: 1, files: 1 }); + // The parent's record still says what it said: restore repairs this thread only. + const lines = readFileSync(fixture.rollout, "utf8").split("\n").filter(Boolean); + expect(lines.filter(line => line === parentLine)).toHaveLength(1); + }); + test("reports applied permission progress when manifest finalization is denied", () => { const fixture = makeFixture(); syncCodexHistoryProvider("opencodex", fixture.dbPath, fixture.backupPath); @@ -1031,7 +1355,15 @@ describe("Design B migration helpers", () => { // Work cannot converge without its bound database; the manifest remains retry evidence. const result = migrateHistoryToOpenai(missingDb, backupPath); - expect(result).toEqual({ rows: 0, files: 0, failed: true, failureReason: "integrity" }); + expect(result).toEqual({ + rows: 0, + files: 0, + failed: true, + failureReason: "integrity", + // The specific condition travels with the result: an operator can tell a missing + // database from a manifest that needs manual resolution. + integrityCode: "history_state_database_missing", + }); expect(existsSync(backupPath)).toBe(true); }); From d98d22c45422a65ddb3c8e7775a25682dd99643f Mon Sep 17 00:00:00 2001 From: luvs01 <luvs01@hanmail.net> Date: Sun, 30 Aug 2026 14:38:22 +0900 Subject: [PATCH 23/42] fix(oauth): clear the refresh intent after a transient Anthropic failure (cherry picked from commit 3e7c5fd0e9f30efb2f83c347ac4cb093823f7b77) --- src/oauth/index.ts | 16 ++++++++++++++-- tests/oauth-refresh.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/oauth/index.ts b/src/oauth/index.ts index 7a356df8c8..9945cc553c 100644 --- a/src/oauth/index.ts +++ b/src/oauth/index.ts @@ -694,8 +694,20 @@ export async function refreshAnthropicAccountWithLock( clearOAuthRefreshIntent(provider, accountId, generation); return fresh.access; } catch (error) { - if (error instanceof OAuthMutationBusyError) throw error; - if (!terminal(error)) throw error; + if (error instanceof OAuthMutationBusyError || error instanceof OAuthTokenRefreshStaleError) throw error; + if (!terminal(error)) { + // A non-terminal failure means the credential was never rejected, so the caller is + // told to retry. Leaving the intent behind contradicted that: the next attempt hit + // the pending-intent branch above and raised OAuthLoginRequiredError, so one 503 or + // timeout locked the account out of refresh entirely until manual re-auth — even + // once upstream recovered. Clear it so the promised retry can actually happen. + // + // The replay guard is preserved by `uncertain`: a refresh whose outcome is genuinely + // unknown surfaces as an uncertain intent from the store, which this path never + // clears, and a superseded owner still leaves through OAuthTokenRefreshStaleError. + clearOAuthRefreshIntent(provider, accountId, generation); + throw error; + } await markAccountNeedsReauthIfGeneration(provider, accountId, generation, writerGeneration); clearOAuthRefreshIntent(provider, accountId, generation); throw new OAuthLoginRequiredError(provider); diff --git a/tests/oauth-refresh.test.ts b/tests/oauth-refresh.test.ts index 0975e8285c..bdf74c3b64 100644 --- a/tests/oauth-refresh.test.ts +++ b/tests/oauth-refresh.test.ts @@ -517,6 +517,31 @@ describe("oauth refresh hardening", () => { expect(getAccountSet("anthropic")!.accounts[0]!.needsReauth).toBe(true); }); + /** + * A non-terminal refresh failure is reported as retryable, but the refresh intent outlived + * it. The next attempt then hit the pending-intent branch and raised OAuthLoginRequiredError, + * so a single 503 or timeout locked the account out of refresh until manual re-authentication + * even after upstream recovered. The replay guard is unaffected: an intent whose outcome is + * genuinely unknown is reported `uncertain` by the store and is still never cleared here. + */ + test("a transient Anthropic failure leaves the account refreshable", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const transient = new AnthropicTokenError("server", 503, undefined); + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { throw transient; }, + }, getAccountCredential("anthropic", id)!)).rejects.toBe(transient); + expect(readOAuthRefreshIntent("anthropic", id)).toBeUndefined(); + + // Upstream recovers: the retry the caller was promised must actually succeed. + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => ({ access: "fresh", refresh: "rt-fresh", expires: Date.now() + 3_600_000 }), + }, getAccountCredential("anthropic", id)!)).resolves.toBe("fresh"); + expect(getAccountSet("anthropic")!.accounts.find(account => account.id === id)!.needsReauth).toBeUndefined(); + }); + test("Anthropic post-dispatch stale flight replacement stays retryable without replay or reauth", async () => { await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); const id = getAccountSet("anthropic")!.activeAccountId; From dd7b6abfcf07ed6533be6bb8de903850659436d4 Mon Sep 17 00:00:00 2001 From: luvs01 <luvs01@hanmail.net> Date: Sun, 30 Aug 2026 16:22:26 +0900 Subject: [PATCH 24/42] fix(oauth): preserve Anthropic intent across uncertain refresh outcomes A timeout, lost response, unreadable body, or credential-store failure can happen after Anthropic consumed and rotated a refresh token. Clearing the durable intent lets the next attempt replay the old token, risking reuse handling and forced reauthentication. The store's uncertain flag does not cover these request outcomes. Track the pre-dispatch boundary explicitly. Clear a non-terminal intent only before dispatch or after the adapter reports an explicit non-success HTTP response; retain it after all other post-dispatch outcomes and after provider success followed by persistence failure. Intent cleanup is secondary to the refresh result. A failed unlink now leaves the replay guard in place without replacing the original provider error, pre-dispatch abort, terminal login result, or successfully persisted credential. Cover definite 503 retry, uncertain post-dispatch failure, pre-dispatch abort, cleanup failure, post-provider persistence failure, and post-persist cleanup failure. (cherry picked from commit 9b55278910e383d399877b6ed5ff9ca76fd38e28) --- src/oauth/index.ts | 74 +++++++++++++++++---- tests/oauth-refresh.test.ts | 125 ++++++++++++++++++++++++++++++++++-- 2 files changed, 181 insertions(+), 18 deletions(-) diff --git a/src/oauth/index.ts b/src/oauth/index.ts index 9945cc553c..aa5460df88 100644 --- a/src/oauth/index.ts +++ b/src/oauth/index.ts @@ -562,6 +562,45 @@ function terminal(error:unknown):boolean{ if (error instanceof RefreshIntentIOError) return false; return isTerminalRefreshError(error); } + +/** + * True when the token endpoint definitively answered and rejected the request. + * + * The Anthropic adapter attaches an HTTP status only to an explicit non-success response, + * which is the retryable rejection this PR handles. Everything else (timeout, dropped + * connection, a body that could not be read or parsed, or a local persistence fault) leaves + * the outcome unknown: the server may already have rotated the token, and a blind replay + * could trip refresh-token-reuse revocation. Those cases must keep the refresh intent. + * + * Deliberately narrower than `terminal()`, which asks whether the CREDENTIAL is dead. + * This asks the different question of whether the ATTEMPT is known to have failed. + */ +function definitivelyAnswered(error: unknown): boolean { + if (error instanceof AnthropicTokenError) return error.httpStatus !== undefined; + return false; +} + +/** + * Intent cleanup is secondary to the refresh outcome it protects. + * + * A filesystem failure here must not replace a provider error, a pre-dispatch abort, or a + * successfully persisted credential. Leaving the intent in place is the conservative fallback: + * it blocks replay until the local persistence problem is repaired. + */ +function clearAnthropicRefreshIntentBestEffort( + provider: string, + accountId: string, + generation: string, +): boolean { + try { + return clearOAuthRefreshIntent(provider, accountId, generation); + } catch { + console.warn( + "[opencodex] Anthropic refresh intent cleanup failed; preserving the durable replay guard.", + ); + return false; + } +} function authoritative(stored:OAuthCredentials,active:boolean,now:()=>number):OAuthCredentials{if(stored.source!=="local-cli")return stored;const disk=detectGrokCliToken();if(!disk)return stored;const allowed=isSameGrokIdentity(stored,disk)||(active&&!hasComparableGrokIdentity(stored,disk));return allowed&&shouldAdoptGrokGeneration(stored,disk,now(),REFRESH_SKEW_MS)?disk:stored;} function merged(fresh: OAuthCredentials, previous: OAuthCredentials): OAuthCredentials { return { @@ -677,9 +716,14 @@ export async function refreshAnthropicAccountWithLock( return stored.access; } + let refreshMayHaveReachedProvider = false; try { writeOAuthRefreshIntent(provider, accountId, generation, now(), deps.flight?.flightId); if (deps.signal?.aborted) throw deps.signal.reason; + // From this point on, even a synchronous client error is conservatively post-dispatch: + // the provider may have received and rotated the refresh token before the caller learned + // the outcome. + refreshMayHaveReachedProvider = true; if (deps.flight) deps.flight.dispatched = true; const fresh = merged(await def.refresh(stored.refresh, deps.signal), stored); const outcome = await mergeAccountCredential(provider, accountId, fresh, { @@ -687,29 +731,35 @@ export async function refreshAnthropicAccountWithLock( afterPrePersistRead: deps.afterPrePersistRead, }); if (outcome.superseded) { - clearOAuthRefreshIntent(provider, accountId, generation); + clearAnthropicRefreshIntentBestEffort(provider, accountId, generation); if (outcome.stored.expires > now() + REFRESH_SKEW_MS) return outcome.stored.access; throw new OAuthLoginRequiredError(provider); } - clearOAuthRefreshIntent(provider, accountId, generation); + // The rotated credential is durable now. A cleanup failure must not turn that committed + // success into a refresh failure; the old-generation intent remains a conservative guard. + clearAnthropicRefreshIntentBestEffort(provider, accountId, generation); return fresh.access; } catch (error) { if (error instanceof OAuthMutationBusyError || error instanceof OAuthTokenRefreshStaleError) throw error; if (!terminal(error)) { - // A non-terminal failure means the credential was never rejected, so the caller is - // told to retry. Leaving the intent behind contradicted that: the next attempt hit - // the pending-intent branch above and raised OAuthLoginRequiredError, so one 503 or - // timeout locked the account out of refresh entirely until manual re-auth — even - // once upstream recovered. Clear it so the promised retry can actually happen. + // A non-terminal failure tells the caller to retry, but the intent outlived it, so + // the next attempt hit the pending-intent branch above and raised + // OAuthLoginRequiredError. One 503 locked the account out of refresh until manual + // re-auth even after upstream recovered. // - // The replay guard is preserved by `uncertain`: a refresh whose outcome is genuinely - // unknown surfaces as an uncertain intent from the store, which this path never - // clears, and a superseded owner still leaves through OAuthTokenRefreshStaleError. - clearOAuthRefreshIntent(provider, accountId, generation); + // Only clear the intent when the server DEFINITIVELY answered and rejected the + // request. The adapter attaches an HTTP status only to that explicit non-success + // response. A timeout, a dropped connection, or an unreadable/unparseable body + // carries no status: the server may already have + // rotated the token, and replaying it could trip refresh-token-reuse revocation. + // Those outcomes keep the intent so the guard still refuses a blind replay. + if (!refreshMayHaveReachedProvider || definitivelyAnswered(error)) { + clearAnthropicRefreshIntentBestEffort(provider, accountId, generation); + } throw error; } await markAccountNeedsReauthIfGeneration(provider, accountId, generation, writerGeneration); - clearOAuthRefreshIntent(provider, accountId, generation); + clearAnthropicRefreshIntentBestEffort(provider, accountId, generation); throw new OAuthLoginRequiredError(provider); } } finally { diff --git a/tests/oauth-refresh.test.ts b/tests/oauth-refresh.test.ts index bdf74c3b64..a817e76159 100644 --- a/tests/oauth-refresh.test.ts +++ b/tests/oauth-refresh.test.ts @@ -518,13 +518,12 @@ describe("oauth refresh hardening", () => { }); /** - * A non-terminal refresh failure is reported as retryable, but the refresh intent outlived - * it. The next attempt then hit the pending-intent branch and raised OAuthLoginRequiredError, - * so a single 503 or timeout locked the account out of refresh until manual re-authentication - * even after upstream recovered. The replay guard is unaffected: an intent whose outcome is - * genuinely unknown is reported `uncertain` by the store and is still never cleared here. + * A definitive non-terminal HTTP failure is retryable: the endpoint answered with a failure, + * so the durable intent must not turn that promised retry into OAuthLoginRequiredError. + * A timeout or unreadable response is different — the provider may already have rotated the + * token, so that post-dispatch intent remains as the replay guard. */ - test("a transient Anthropic failure leaves the account refreshable", async () => { + test("a definitive transient Anthropic HTTP failure leaves the account refreshable", async () => { await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); const id = getAccountSet("anthropic")!.activeAccountId; const transient = new AnthropicTokenError("server", 503, undefined); @@ -542,6 +541,120 @@ describe("oauth refresh hardening", () => { expect(getAccountSet("anthropic")!.accounts.find(account => account.id === id)!.needsReauth).toBeUndefined(); }); + test("an Anthropic refresh with an uncertain post-dispatch outcome preserves its intent", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const credential = getAccountCredential("anthropic", id)!; + const generation = credentialGeneration(credential); + const timeout = new AnthropicTokenError("timeout", undefined, undefined); + + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { throw timeout; }, + }, credential)).rejects.toBe(timeout); + + expect(readOAuthRefreshIntent("anthropic", id)).toMatchObject({ generation }); + expect(getAccountSet("anthropic")!.accounts.find(account => account.id === id)!.needsReauth).toBeUndefined(); + }); + + test("a pre-dispatch Anthropic abort clears its unconsumed refresh intent", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const credential = getAccountCredential("anthropic", id)!; + const aborted = new Error("aborted before dispatch"); + const controller = new AbortController(); + controller.abort(aborted); + let calls = 0; + + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { calls += 1; throw new Error("must not run"); }, + }, credential, { signal: controller.signal })).rejects.toBe(aborted); + + expect(calls).toBe(0); + expect(readOAuthRefreshIntent("anthropic", id)).toBeUndefined(); + }); + + test("intent cleanup failure preserves the original Anthropic HTTP error", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const credential = getAccountCredential("anthropic", id)!; + const generation = credentialGeneration(credential); + const transient = new AnthropicTokenError("server", 503, undefined); + const clearFailure = new Error("intent unlink failed"); + const clearSpy = spyOn(storeModule, "clearOAuthRefreshIntent").mockImplementation(() => { + throw clearFailure; + }); + const warnSpy = spyOn(console, "warn").mockImplementation(() => {}); + try { + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { throw transient; }, + }, credential)).rejects.toBe(transient); + + expect(clearSpy).toHaveBeenCalled(); + expect(readOAuthRefreshIntent("anthropic", id)).toMatchObject({ generation }); + expect(getAccountSet("anthropic")!.accounts.find(account => account.id === id)!.needsReauth).toBeUndefined(); + } finally { + warnSpy.mockRestore(); + clearSpy.mockRestore(); + } + }); + + test("Anthropic persistence failure after provider success preserves the replay guard", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const credential = getAccountCredential("anthropic", id)!; + const generation = credentialGeneration(credential); + const persistenceFailure = new Error("credential persistence failed"); + const mergeSpy = spyOn(storeModule, "mergeAccountCredential").mockImplementation(async () => { + throw persistenceFailure; + }); + try { + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => ({ + access: "fresh", + refresh: "rt-fresh", + expires: Date.now() + 3_600_000, + }), + }, credential)).rejects.toBe(persistenceFailure); + + expect(readOAuthRefreshIntent("anthropic", id)).toMatchObject({ generation }); + expect(getAccountCredential("anthropic", id)).toEqual(credential); + expect(getAccountSet("anthropic")!.accounts.find(account => account.id === id)!.needsReauth).toBeUndefined(); + } finally { + mergeSpy.mockRestore(); + } + }); + + test("post-persist intent cleanup failure does not turn Anthropic refresh success into failure", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const credential = getAccountCredential("anthropic", id)!; + const oldGeneration = credentialGeneration(credential); + const clearSpy = spyOn(storeModule, "clearOAuthRefreshIntent").mockImplementation(() => { + throw new Error("intent unlink failed"); + }); + const warnSpy = spyOn(console, "warn").mockImplementation(() => {}); + try { + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => ({ + access: "fresh", + refresh: "rt-fresh", + expires: Date.now() + 3_600_000, + }), + }, credential)).resolves.toBe("fresh"); + + expect(getAccountCredential("anthropic", id)?.access).toBe("fresh"); + expect(readOAuthRefreshIntent("anthropic", id)).toMatchObject({ generation: oldGeneration }); + } finally { + warnSpy.mockRestore(); + clearSpy.mockRestore(); + } + }); + test("Anthropic post-dispatch stale flight replacement stays retryable without replay or reauth", async () => { await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); const id = getAccountSet("anthropic")!.activeAccountId; From 8728c0fdcb0e0d1d954c5019b8afd3917df69211 Mon Sep 17 00:00:00 2001 From: luvs01 <luvs01@hanmail.net> Date: Sun, 30 Aug 2026 17:36:44 +0900 Subject: [PATCH 25/42] fix(oauth): resume retry-safe Anthropic intent cleanup Persist an attempt-scoped cleanup-pending marker before returning a definitive rejection or pre-dispatch abort. Serialize all refresh-intent mutations with the existing SQLite config transaction, refuse guard overwrites, and retry only the exact safe cleanup before provider redispatch. Preserve ordinary intents for timeouts and post-provider persistence uncertainty. (cherry picked from commit 97a547c6c3faa20a84ee1cc3e9f5a853b128e365) --- src/oauth/index.ts | 156 ++++++++++++++-- src/oauth/store.ts | 258 ++++++++++++++++++++++++-- tests/oauth-refresh.test.ts | 360 +++++++++++++++++++++++++++++++++--- 3 files changed, 718 insertions(+), 56 deletions(-) diff --git a/src/oauth/index.ts b/src/oauth/index.ts index aa5460df88..1b3754dd21 100644 --- a/src/oauth/index.ts +++ b/src/oauth/index.ts @@ -4,7 +4,31 @@ import type { OcxConfig, OcxProviderConfig, RefreshPolicy } from "../types"; import { loadConfig, resolveEnvValue, saveConfig } from "../config"; import { maskEmail } from "../lib/privacy"; import { KiroTokenRefreshError, environmentKiroRoutingMetadata, loginKiro, refreshKiroToken, settleKiroLoginTransaction } from "./kiro"; -import { getAccountCredential, getAccountCredentialWithStatus, getAccountSet, removeAccount, saveAccountCredential, saveCredential, setActiveAccount, getCredential, credentialGeneration, createOAuthRefreshIntentLock, mergeAccountCredential, markAccountNeedsReauthIfGeneration, readOAuthRefreshIntent, writeOAuthRefreshIntent, markOAuthRefreshIntentStaleOwner, clearOAuthRefreshIntent, normalizeAuthStoreBuffer, OAuthMutationBusyError } from "./store"; +import { + OAuthMutationBusyError, + OAuthRefreshIntentIOError, + clearOAuthRefreshIntent, + clearOAuthRefreshIntentIfMatch, + createOAuthRefreshIntentLock, + credentialGeneration, + getAccountCredential, + getAccountCredentialWithStatus, + getAccountSet, + getCredential, + markAccountNeedsReauthIfGeneration, + markOAuthRefreshIntentCleanupPending, + markOAuthRefreshIntentStaleOwner, + mergeAccountCredential, + normalizeAuthStoreBuffer, + readOAuthRefreshIntent, + removeAccount, + saveAccountCredential, + saveCredential, + setActiveAccount, + writeOAuthRefreshIntent, + type OAuthRefreshIntent, + type OAuthRefreshIntentCleanupPending, +} from "./store"; import { loginXai, refreshXaiToken, XAI_LOCAL_CLI_DETACH_WARNING, XaiTokenRequestError } from "./xai"; import { ANTHROPIC_OAUTH_BETA, AnthropicTokenError, loginAnthropic, refreshAnthropicToken } from "./anthropic"; import { loginKimi, refreshKimiToken } from "./kimi"; @@ -559,7 +583,7 @@ function terminal(error:unknown):boolean{ // Local durable-write/read/cleanup failures are operational, not credential // death: the provider credential was never rejected or consumed. Never mark // the account needsReauth for broken local persistence infrastructure. - if (error instanceof RefreshIntentIOError) return false; + if (error instanceof RefreshIntentIOError || error instanceof OAuthRefreshIntentIOError) return false; return isTerminalRefreshError(error); } @@ -583,17 +607,19 @@ function definitivelyAnswered(error: unknown): boolean { /** * Intent cleanup is secondary to the refresh outcome it protects. * - * A filesystem failure here must not replace a provider error, a pre-dispatch abort, or a - * successfully persisted credential. Leaving the intent in place is the conservative fallback: - * it blocks replay until the local persistence problem is repaired. + * Once a credential is already durable, cleanup remains secondary and best-effort. A known + * failed attempt takes the stricter path below: its retry-safe marker must become durable before + * the original provider error can be returned. */ function clearAnthropicRefreshIntentBestEffort( provider: string, accountId: string, - generation: string, + expected: OAuthRefreshIntent, ): boolean { try { - return clearOAuthRefreshIntent(provider, accountId, generation); + return expected.attemptId + ? clearOAuthRefreshIntentIfMatch(provider, accountId, expected) + : clearOAuthRefreshIntent(provider, accountId, expected.generation); } catch { console.warn( "[opencodex] Anthropic refresh intent cleanup failed; preserving the durable replay guard.", @@ -601,6 +627,87 @@ function clearAnthropicRefreshIntentBestEffort( return false; } } + +function clearAnthropicRefreshIntentForKnownFailure( + provider: string, + accountId: string, + expected: OAuthRefreshIntent, + cleanupPending: OAuthRefreshIntentCleanupPending, + refreshError: unknown, +): boolean { + let marked: OAuthRefreshIntent | undefined; + try { + marked = markOAuthRefreshIntentCleanupPending( + provider, + accountId, + expected, + cleanupPending, + ); + } catch (cause) { + throw new OAuthRefreshIntentIOError( + "mark-cleanup-pending", + cause, + refreshError, + ); + } + if (!marked) { + throw new OAuthRefreshIntentIOError( + "mark-cleanup-pending", + new Error("Anthropic refresh intent changed before safe cleanup"), + refreshError, + ); + } + + let cleared: boolean; + try { + cleared = clearOAuthRefreshIntentIfMatch(provider, accountId, marked); + } catch { + console.warn( + "[opencodex] Anthropic refresh intent cleanup failed; retry-safe cleanup remains pending.", + ); + return false; + } + if (!cleared) { + throw new OAuthRefreshIntentIOError( + "clear-cleanup-pending", + new Error("Anthropic refresh intent changed during safe cleanup"), + refreshError, + ); + } + return true; +} + +function resumeAnthropicRefreshIntentCleanup( + provider: string, + accountId: string, + pendingIntent: OAuthRefreshIntent, +): void { + let cleared: boolean; + try { + cleared = clearOAuthRefreshIntentIfMatch(provider, accountId, pendingIntent); + } catch (cause) { + throw new OAuthRefreshIntentIOError( + "resume-cleanup", + cause, + ); + } + if (!cleared) { + throw new OAuthRefreshIntentIOError( + "resume-cleanup", + new Error("Pending Anthropic refresh intent changed before cleanup"), + ); + } +} + +function clearObservedAnthropicRefreshIntent( + provider: string, + accountId: string, + pendingIntent: OAuthRefreshIntent, +): boolean { + return pendingIntent.attemptId + ? clearOAuthRefreshIntentIfMatch(provider, accountId, pendingIntent) + : clearOAuthRefreshIntent(provider, accountId, pendingIntent.generation); +} function authoritative(stored:OAuthCredentials,active:boolean,now:()=>number):OAuthCredentials{if(stored.source!=="local-cli")return stored;const disk=detectGrokCliToken();if(!disk)return stored;const allowed=isSameGrokIdentity(stored,disk)||(active&&!hasComparableGrokIdentity(stored,disk));return allowed&&shouldAdoptGrokGeneration(stored,disk,now(),REFRESH_SKEW_MS)?disk:stored;} function merged(fresh: OAuthCredentials, previous: OAuthCredentials): OAuthCredentials { return { @@ -679,6 +786,10 @@ export async function refreshAnthropicAccountWithLock( const account = getAccountSet(provider)?.accounts.find(candidate => candidate.id === accountId); const generation = credentialGeneration(stored); let pendingIntent = readOAuthRefreshIntent(provider, accountId); + if (pendingIntent?.cleanupPending && pendingIntent.generation === generation) { + resumeAnthropicRefreshIntentCleanup(provider, accountId, pendingIntent); + pendingIntent = undefined; + } const disk = newerClaudeCredential(stored, now()); if (disk) { const outcome = await mergeAccountCredential(provider, accountId, disk, { @@ -686,11 +797,11 @@ export async function refreshAnthropicAccountWithLock( afterPrePersistRead: deps.afterPrePersistRead, }); if (outcome.superseded) { - if (pendingIntent) clearOAuthRefreshIntent(provider, accountId, pendingIntent.generation); + if (pendingIntent) clearObservedAnthropicRefreshIntent(provider, accountId, pendingIntent); if (outcome.stored.expires > now() + REFRESH_SKEW_MS) return outcome.stored.access; throw new OAuthLoginRequiredError(provider); } - if (pendingIntent) clearOAuthRefreshIntent(provider, accountId, pendingIntent.generation); + if (pendingIntent) clearObservedAnthropicRefreshIntent(provider, accountId, pendingIntent); return disk.access; } if (!pendingIntent?.uncertain && pendingIntent?.generation === generation) { @@ -700,7 +811,9 @@ export async function refreshAnthropicAccountWithLock( markOAuthRefreshIntentStaleOwner(provider, accountId, generation, deps.replacedStaleFlight.flightId); throw new OAuthTokenRefreshStaleError(); } - clearOAuthRefreshIntent(provider, accountId, generation); + if (!clearObservedAnthropicRefreshIntent(provider, accountId, pendingIntent)) { + throw new OAuthTokenRefreshStaleError(); + } pendingIntent = undefined; } } @@ -708,7 +821,9 @@ export async function refreshAnthropicAccountWithLock( await markAccountNeedsReauthIfGeneration(provider, accountId, generation, writerGeneration); throw new OAuthLoginRequiredError(provider); } - if (pendingIntent) clearOAuthRefreshIntent(provider, accountId, pendingIntent.generation); + if (pendingIntent && !clearObservedAnthropicRefreshIntent(provider, accountId, pendingIntent)) { + throw new OAuthTokenRefreshStaleError(); + } if (account?.needsReauth) { throw new OAuthLoginRequiredError(provider); } @@ -717,8 +832,9 @@ export async function refreshAnthropicAccountWithLock( } let refreshMayHaveReachedProvider = false; + let attemptIntent: OAuthRefreshIntent | undefined; try { - writeOAuthRefreshIntent(provider, accountId, generation, now(), deps.flight?.flightId); + attemptIntent = writeOAuthRefreshIntent(provider, accountId, generation, now(), deps.flight?.flightId); if (deps.signal?.aborted) throw deps.signal.reason; // From this point on, even a synchronous client error is conservatively post-dispatch: // the provider may have received and rotated the refresh token before the caller learned @@ -731,13 +847,13 @@ export async function refreshAnthropicAccountWithLock( afterPrePersistRead: deps.afterPrePersistRead, }); if (outcome.superseded) { - clearAnthropicRefreshIntentBestEffort(provider, accountId, generation); + if (attemptIntent) clearAnthropicRefreshIntentBestEffort(provider, accountId, attemptIntent); if (outcome.stored.expires > now() + REFRESH_SKEW_MS) return outcome.stored.access; throw new OAuthLoginRequiredError(provider); } // The rotated credential is durable now. A cleanup failure must not turn that committed // success into a refresh failure; the old-generation intent remains a conservative guard. - clearAnthropicRefreshIntentBestEffort(provider, accountId, generation); + if (attemptIntent) clearAnthropicRefreshIntentBestEffort(provider, accountId, attemptIntent); return fresh.access; } catch (error) { if (error instanceof OAuthMutationBusyError || error instanceof OAuthTokenRefreshStaleError) throw error; @@ -753,13 +869,19 @@ export async function refreshAnthropicAccountWithLock( // carries no status: the server may already have // rotated the token, and replaying it could trip refresh-token-reuse revocation. // Those outcomes keep the intent so the guard still refuses a blind replay. - if (!refreshMayHaveReachedProvider || definitivelyAnswered(error)) { - clearAnthropicRefreshIntentBestEffort(provider, accountId, generation); + if ((!refreshMayHaveReachedProvider || definitivelyAnswered(error)) && attemptIntent) { + clearAnthropicRefreshIntentForKnownFailure( + provider, + accountId, + attemptIntent, + refreshMayHaveReachedProvider ? "definitive-rejection" : "pre-dispatch", + error, + ); } throw error; } await markAccountNeedsReauthIfGeneration(provider, accountId, generation, writerGeneration); - clearAnthropicRefreshIntentBestEffort(provider, accountId, generation); + if (attemptIntent) clearAnthropicRefreshIntentBestEffort(provider, accountId, attemptIntent); throw new OAuthLoginRequiredError(provider); } } finally { diff --git a/src/oauth/store.ts b/src/oauth/store.ts index 755d86f3e2..842ee127fb 100644 --- a/src/oauth/store.ts +++ b/src/oauth/store.ts @@ -19,7 +19,7 @@ import { createHash, randomUUID } from "node:crypto"; import { chmodSync, closeSync, copyFileSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs"; import { join } from "node:path"; -import { getConfigDir, atomicWriteFile, backupInvalidConfig, hardenConfigDir, hardenExistingSecret } from "../config"; +import { getConfigDir, atomicWriteFile, backupInvalidConfig, hardenConfigDir, hardenExistingSecret, withConfigMutationLockSync } from "../config"; import { assertNotRealHomeUnderTest } from "../lib/test-home-guard"; import { recordOwnedConfigPath } from "../lib/config-ownership"; import { MAX_PENDING_OAUTH_MUTATIONS } from "../lib/translator-budget"; @@ -67,23 +67,113 @@ export function getAuthRefreshIntentLockPath(provider: string, accountId: string export function getAuthRefreshIntentPath(provider: string, accountId: string): string { return `${getAuthRefreshIntentLockPath(provider, accountId)}.json`; } -export interface OAuthRefreshIntent { version: 1; provider: string; accountId: string; generation: string; createdAt: number; flightId?: string; staleOwner?: true; uncertain?: true } +export type OAuthRefreshIntentCleanupPending = "pre-dispatch" | "definitive-rejection"; +export interface OAuthRefreshIntent { + version: 1; + provider: string; + accountId: string; + generation: string; + createdAt: number; + attemptId?: string; + flightId?: string; + staleOwner?: true; + uncertain?: true; + cleanupPending?: OAuthRefreshIntentCleanupPending; +} + +export class OAuthRefreshIntentIOError extends Error { + readonly code = "OAUTH_REFRESH_INTENT_IO"; + constructor( + readonly operation: "write-intent" | "mark-cleanup-pending" | "clear-cleanup-pending" | "resume-cleanup", + cause?: unknown, + readonly refreshError?: unknown, + ) { + super(`OAuth refresh intent ${operation} failed`, cause ? { cause } : undefined); + this.name = "OAuthRefreshIntentIOError"; + } +} + +interface OAuthRefreshIntentFileSnapshot { + raw: string; + dev: number; + ino: number; + mtimeMs: number; + size: number; +} + +export interface OAuthRefreshIntentMutationHooks { + beforeMarkCommit?: () => void; + beforeClearRecheck?: () => void; +} + +class OAuthRefreshIntentChangedError extends Error {} + +function uncertainOAuthRefreshIntent(provider: string, accountId: string): OAuthRefreshIntent { + return { version: 1, provider, accountId, generation: "", createdAt: 0, uncertain: true }; +} + +function snapshotOAuthRefreshIntentFile(path: string): OAuthRefreshIntentFileSnapshot { + const raw = readFileSync(path, "utf8"); + const stat = statSync(path); + return { raw, dev: stat.dev, ino: stat.ino, mtimeMs: stat.mtimeMs, size: stat.size }; +} + +function sameOAuthRefreshIntentFileSnapshot( + left: OAuthRefreshIntentFileSnapshot, + right: OAuthRefreshIntentFileSnapshot, +): boolean { + return left.raw === right.raw + && left.dev === right.dev + && left.ino === right.ino + && left.mtimeMs === right.mtimeMs + && left.size === right.size; +} + function parseOAuthRefreshIntent( provider: string, accountId: string, raw: string, ): OAuthRefreshIntent { - const value = JSON.parse(raw) as Partial<OAuthRefreshIntent>; + const parsed: unknown = JSON.parse(raw); + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + return uncertainOAuthRefreshIntent(provider, accountId); + } + const value = parsed as Partial<OAuthRefreshIntent>; if ( value.version !== 1 || value.provider !== provider || value.accountId !== accountId || typeof value.generation !== "string" + || !/^[0-9a-f]{64}$/.test(value.generation) || typeof value.createdAt !== "number" - || (value.flightId !== undefined && typeof value.flightId !== "string") + || !Number.isFinite(value.createdAt) + || !Number.isInteger(value.createdAt) + || value.createdAt < 0 + || ( + value.attemptId !== undefined + && (typeof value.attemptId !== "string" || value.attemptId.length === 0 || value.attemptId.length > 128) + ) + || ( + value.flightId !== undefined + && (typeof value.flightId !== "string" || value.flightId.length === 0 || value.flightId.length > 128) + ) || (value.staleOwner !== undefined && value.staleOwner !== true) + || (value.uncertain !== undefined && value.uncertain !== true) + || ( + value.cleanupPending !== undefined + && value.cleanupPending !== "pre-dispatch" + && value.cleanupPending !== "definitive-rejection" + ) + || ( + value.cleanupPending !== undefined + && ( + value.attemptId === undefined + || value.uncertain === true + || value.staleOwner === true + ) + ) ) { - return { version: 1, provider, accountId, generation: "", createdAt: 0, uncertain: true }; + return uncertainOAuthRefreshIntent(provider, accountId); } return value as OAuthRefreshIntent; } @@ -96,7 +186,7 @@ export function readOAuthRefreshIntent(provider: string, accountId: string): OAu return parseOAuthRefreshIntent(provider, accountId, readFileSync(path, "utf8")); } catch (error) { if (errorCode(error) === "ENOENT") return undefined; - return { version: 1, provider, accountId, generation: "", createdAt: 0, uncertain: true }; + return uncertainOAuthRefreshIntent(provider, accountId); } } @@ -107,27 +197,159 @@ export function peekOAuthRefreshIntent(provider: string, accountId: string): OAu return parseOAuthRefreshIntent(provider, accountId, readFileSync(path, "utf8")); } catch (error) { if (errorCode(error) === "ENOENT") return undefined; - return { version: 1, provider, accountId, generation: "", createdAt: 0, uncertain: true }; + return uncertainOAuthRefreshIntent(provider, accountId); } } -export function writeOAuthRefreshIntent(provider: string, accountId: string, generation: string, createdAt = Date.now(), flightId?: string): void { +export function writeOAuthRefreshIntent(provider: string, accountId: string, generation: string, createdAt = Date.now(), flightId?: string): OAuthRefreshIntent { const dir = getConfigDir(); if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 }); hardenConfigDir(); - const intent: OAuthRefreshIntent = { version: 1, provider, accountId, generation, createdAt, ...(flightId ? { flightId } : {}) }; - atomicWriteFile(getAuthRefreshIntentPath(provider, accountId), `${JSON.stringify(intent)}\n`); + const intent: OAuthRefreshIntent = { + version: 1, + provider, + accountId, + generation, + createdAt, + attemptId: randomUUID(), + ...(flightId ? { flightId } : {}), + }; + return withConfigMutationLockSync(() => { + if (readOAuthRefreshIntent(provider, accountId) !== undefined) { + throw new OAuthRefreshIntentIOError( + "write-intent", + new Error("An OAuth refresh intent already exists; refusing to overwrite its replay guard"), + ); + } + atomicWriteFile(getAuthRefreshIntentPath(provider, accountId), `${JSON.stringify(intent)}\n`); + return intent; + }); +} + +function sameOAuthRefreshIntentAttempt(current: OAuthRefreshIntent, expected: OAuthRefreshIntent): boolean { + return current.provider === expected.provider + && current.accountId === expected.accountId + && current.generation === expected.generation + && current.createdAt === expected.createdAt + && current.attemptId === expected.attemptId + && current.flightId === expected.flightId; } + +function exactOAuthRefreshIntentFile( + provider: string, + accountId: string, +): { intent: OAuthRefreshIntent; snapshot: OAuthRefreshIntentFileSnapshot } | undefined { + const path = getAuthRefreshIntentPath(provider, accountId); + try { + hardenConfigDir(); + hardenExistingSecret(path); + const file = snapshotOAuthRefreshIntentFile(path); + let intent: OAuthRefreshIntent; + try { intent = parseOAuthRefreshIntent(provider, accountId, file.raw); } + catch { intent = uncertainOAuthRefreshIntent(provider, accountId); } + return { intent, snapshot: file }; + } catch (error) { + if (errorCode(error) === "ENOENT") return undefined; + throw error; + } +} + +export function markOAuthRefreshIntentCleanupPending( + provider: string, + accountId: string, + expected: OAuthRefreshIntent, + cleanupPending: OAuthRefreshIntentCleanupPending, + hooks: OAuthRefreshIntentMutationHooks = {}, +): OAuthRefreshIntent | undefined { + return withConfigMutationLockSync(() => { + const path = getAuthRefreshIntentPath(provider, accountId); + const observed = exactOAuthRefreshIntentFile(provider, accountId); + const current = observed?.intent; + if ( + !observed + || !current + || expected.attemptId === undefined + || current.uncertain + || current.staleOwner + || !sameOAuthRefreshIntentAttempt(current, expected) + || (current.cleanupPending !== undefined && current.cleanupPending !== cleanupPending) + ) return undefined; + const marked: OAuthRefreshIntent = { ...current, cleanupPending }; + try { + atomicWriteFile(path, `${JSON.stringify(marked)}\n`, undefined, { + beforeRename: hooks.beforeMarkCommit, + validateBeforeRename: targetPath => { + let latest: OAuthRefreshIntentFileSnapshot; + try { latest = snapshotOAuthRefreshIntentFile(targetPath); } + catch (error) { + throw new OAuthRefreshIntentChangedError("OAuth refresh intent changed before marker commit", { cause: error }); + } + if (!sameOAuthRefreshIntentFileSnapshot(observed.snapshot, latest)) { + throw new OAuthRefreshIntentChangedError("OAuth refresh intent changed before marker commit"); + } + }, + }); + } catch (error) { + if (error instanceof OAuthRefreshIntentChangedError) return undefined; + throw error; + } + return marked; + }); +} + +export function clearOAuthRefreshIntentIfMatch( + provider: string, + accountId: string, + expected: OAuthRefreshIntent, + hooks: OAuthRefreshIntentMutationHooks = {}, +): boolean { + return withConfigMutationLockSync(() => { + const path = getAuthRefreshIntentPath(provider, accountId); + const observed = exactOAuthRefreshIntentFile(provider, accountId); + if (!observed) return true; + const current = observed.intent; + if ( + current.uncertain + || expected.uncertain + || current.attemptId === undefined + || expected.attemptId === undefined + || current.uncertain !== expected.uncertain + || current.staleOwner !== expected.staleOwner + || current.cleanupPending !== expected.cleanupPending + || !sameOAuthRefreshIntentAttempt(current, expected) + ) return false; + hooks.beforeClearRecheck?.(); + let latest: OAuthRefreshIntentFileSnapshot; + try { latest = snapshotOAuthRefreshIntentFile(path); } + catch (error) { + if (errorCode(error) === "ENOENT") return true; + throw error; + } + if (!sameOAuthRefreshIntentFileSnapshot(observed.snapshot, latest)) return false; + try { unlinkSync(path); return true; } + catch (error) { if (errorCode(error) === "ENOENT") return true; throw error; } + }); +} + export function markOAuthRefreshIntentStaleOwner(provider: string, accountId: string, generation: string, flightId: string): boolean { - const current = readOAuthRefreshIntent(provider, accountId); - if (current?.uncertain || current?.generation !== generation || current.flightId !== flightId) return false; - atomicWriteFile(getAuthRefreshIntentPath(provider, accountId), `${JSON.stringify({ ...current, staleOwner: true })}\n`); - return true; + return withConfigMutationLockSync(() => { + const current = readOAuthRefreshIntent(provider, accountId); + if ( + current?.uncertain + || current?.cleanupPending + || current?.generation !== generation + || current.flightId !== flightId + ) return false; + atomicWriteFile(getAuthRefreshIntentPath(provider, accountId), `${JSON.stringify({ ...current, staleOwner: true })}\n`); + return true; + }); } export function clearOAuthRefreshIntent(provider: string, accountId: string, generation: string): boolean { - const current = readOAuthRefreshIntent(provider, accountId); - if (!current || current.generation !== generation) return false; - try { unlinkSync(getAuthRefreshIntentPath(provider, accountId)); return true; } - catch (error) { if (errorCode(error) === "ENOENT") return false; throw error; } + return withConfigMutationLockSync(() => { + const current = readOAuthRefreshIntent(provider, accountId); + if (!current || current.generation !== generation) return false; + try { unlinkSync(getAuthRefreshIntentPath(provider, accountId)); return true; } + catch (error) { if (errorCode(error) === "ENOENT") return false; throw error; } + }); } export function credentialGeneration(cred: OAuthCredentials): string { return createHash("sha256").update(JSON.stringify([cred.refresh, cred.access, cred.expires])).digest("hex"); diff --git a/tests/oauth-refresh.test.ts b/tests/oauth-refresh.test.ts index a817e76159..d4bbd435a6 100644 --- a/tests/oauth-refresh.test.ts +++ b/tests/oauth-refresh.test.ts @@ -7,7 +7,19 @@ import { getValidAccessToken, getValidAccessTokenForAccount, OAuthLoginRequiredE import { RefreshIntentIOError, nousRefreshIntentBlocksReplay } from "../src/oauth/nous"; import * as nousModule from "../src/oauth/nous"; import { AnthropicTokenError } from "../src/oauth/anthropic"; -import { credentialGeneration, getAccountCredential, getAccountSet, getAuthRefreshIntentPath, getCredential, markAccountNeedsReauth, readOAuthRefreshIntent, saveCredential, writeOAuthRefreshIntent } from "../src/oauth/store"; +import { + OAuthRefreshIntentIOError, + credentialGeneration, + getAccountCredential, + getAccountSet, + getAuthRefreshIntentPath, + getCredential, + markAccountNeedsReauth, + markOAuthRefreshIntentCleanupPending, + readOAuthRefreshIntent, + saveCredential, + writeOAuthRefreshIntent, +} from "../src/oauth/store"; import * as storeModule from "../src/oauth/store"; import * as configModule from "../src/config"; @@ -547,14 +559,25 @@ describe("oauth refresh hardening", () => { const credential = getAccountCredential("anthropic", id)!; const generation = credentialGeneration(credential); const timeout = new AnthropicTokenError("timeout", undefined, undefined); - - await expect(refreshAnthropicAccountWithLock("anthropic", id, { + let refreshCalls = 0; + const def = { ...OAUTH_PROVIDERS.anthropic!, - refresh: async () => { throw timeout; }, - }, credential)).rejects.toBe(timeout); + refresh: async () => { + refreshCalls += 1; + throw timeout; + }, + }; + + await expect(refreshAnthropicAccountWithLock("anthropic", id, def, credential)).rejects.toBe(timeout); - expect(readOAuthRefreshIntent("anthropic", id)).toMatchObject({ generation }); + const pending = readOAuthRefreshIntent("anthropic", id); + expect(pending).toMatchObject({ generation }); + expect(pending?.cleanupPending).toBeUndefined(); expect(getAccountSet("anthropic")!.accounts.find(account => account.id === id)!.needsReauth).toBeUndefined(); + await expect(refreshAnthropicAccountWithLock("anthropic", id, def, credential)) + .rejects.toBeInstanceOf(OAuthLoginRequiredError); + expect(refreshCalls).toBe(1); + expect(readOAuthRefreshIntent("anthropic", id)).toEqual(pending); }); test("a pre-dispatch Anthropic abort clears its unconsumed refresh intent", async () => { @@ -575,15 +598,146 @@ describe("oauth refresh hardening", () => { expect(readOAuthRefreshIntent("anthropic", id)).toBeUndefined(); }); - test("intent cleanup failure preserves the original Anthropic HTTP error", async () => { + test("a failed pre-dispatch cleanup remains safely retryable", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const credential = getAccountCredential("anthropic", id)!; + const controller = new AbortController(); + const aborted = new Error("aborted before dispatch"); + controller.abort(aborted); + const realClear = storeModule.clearOAuthRefreshIntentIfMatch; + let clearCalls = 0; + const clearSpy = spyOn(storeModule, "clearOAuthRefreshIntentIfMatch").mockImplementation((provider, accountId, expected) => { + clearCalls += 1; + if (clearCalls === 1) throw new Error("intent unlink failed"); + return realClear(provider, accountId, expected); + }); + const warnSpy = spyOn(console, "warn").mockImplementation(() => {}); + try { + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { throw new Error("must not dispatch"); }, + }, credential, { signal: controller.signal })).rejects.toBe(aborted); + expect(readOAuthRefreshIntent("anthropic", id)?.cleanupPending).toBe("pre-dispatch"); + + let retryCalls = 0; + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { + retryCalls += 1; + return { access: "fresh", refresh: "rt-fresh", expires: Date.now() + 3_600_000 }; + }, + }, credential)).resolves.toBe("fresh"); + expect(retryCalls).toBe(1); + expect(readOAuthRefreshIntent("anthropic", id)).toBeUndefined(); + } finally { + warnSpy.mockRestore(); + clearSpy.mockRestore(); + } + }); + + test("a cleanup-marker persistence failure surfaces a typed error with the provider outcome", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const credential = getAccountCredential("anthropic", id)!; + const transient = new AnthropicTokenError("server", 503, undefined); + const markerFailure = new Error("intent marker write failed"); + const markerSpy = spyOn(storeModule, "markOAuthRefreshIntentCleanupPending").mockImplementation(() => { + throw markerFailure; + }); + try { + let rejection: unknown; + try { + await refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { throw transient; }, + }, credential); + } catch (error) { + rejection = error; + } + expect(rejection).toBeInstanceOf(OAuthRefreshIntentIOError); + expect(rejection).toMatchObject({ + operation: "mark-cleanup-pending", + code: "OAUTH_REFRESH_INTENT_IO", + cause: markerFailure, + refreshError: transient, + }); + const pending = readOAuthRefreshIntent("anthropic", id); + expect(pending?.cleanupPending).toBeUndefined(); + expect(getAccountSet("anthropic")!.accounts.find(account => account.id === id)!.needsReauth).toBeUndefined(); + } finally { + markerSpy.mockRestore(); + } + }); + + test("a failed definitive-rejection cleanup is retried before the next Anthropic request", async () => { await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); const id = getAccountSet("anthropic")!.activeAccountId; const credential = getAccountCredential("anthropic", id)!; const generation = credentialGeneration(credential); const transient = new AnthropicTokenError("server", 503, undefined); const clearFailure = new Error("intent unlink failed"); - const clearSpy = spyOn(storeModule, "clearOAuthRefreshIntent").mockImplementation(() => { - throw clearFailure; + const realClear = storeModule.clearOAuthRefreshIntentIfMatch; + const events: string[] = []; + let clearCalls = 0; + const clearSpy = spyOn(storeModule, "clearOAuthRefreshIntentIfMatch").mockImplementation((provider, accountId, expected) => { + clearCalls += 1; + if (clearCalls === 1) { + events.push("clear-fail"); + throw clearFailure; + } + events.push(expected.cleanupPending ? "clear-recovery" : "clear-success"); + return realClear(provider, accountId, expected); + }); + const warnSpy = spyOn(console, "warn").mockImplementation(() => {}); + try { + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { + events.push("provider-503"); + throw transient; + }, + }, credential)).rejects.toBe(transient); + + expect(readOAuthRefreshIntent("anthropic", id)).toMatchObject({ + generation, + cleanupPending: "definitive-rejection", + }); + expect(getAccountSet("anthropic")!.accounts.find(account => account.id === id)!.needsReauth).toBeUndefined(); + + let retryCalls = 0; + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { + events.push("provider-retry"); + retryCalls += 1; + return { access: "fresh", refresh: "rt-fresh", expires: Date.now() + 3_600_000 }; + }, + }, getAccountCredential("anthropic", id)!)).resolves.toBe("fresh"); + expect(retryCalls).toBe(1); + expect(clearCalls).toBe(3); + expect(events).toEqual([ + "provider-503", + "clear-fail", + "clear-recovery", + "provider-retry", + "clear-success", + ]); + expect(readOAuthRefreshIntent("anthropic", id)).toBeUndefined(); + } finally { + warnSpy.mockRestore(); + clearSpy.mockRestore(); + } + }); + + test("a persistently blocked cleanup fails operationally without replay or reauth", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const credential = getAccountCredential("anthropic", id)!; + const transient = new AnthropicTokenError("server", 503, undefined); + const cleanupFailure = new Error("intent unlink failed"); + const clearSpy = spyOn(storeModule, "clearOAuthRefreshIntentIfMatch").mockImplementation(() => { + throw cleanupFailure; }); const warnSpy = spyOn(console, "warn").mockImplementation(() => {}); try { @@ -591,10 +745,31 @@ describe("oauth refresh hardening", () => { ...OAUTH_PROVIDERS.anthropic!, refresh: async () => { throw transient; }, }, credential)).rejects.toBe(transient); + expect(readOAuthRefreshIntent("anthropic", id)?.cleanupPending).toBe("definitive-rejection"); - expect(clearSpy).toHaveBeenCalled(); - expect(readOAuthRefreshIntent("anthropic", id)).toMatchObject({ generation }); + let retryCalls = 0; + let rejection: unknown; + try { + await refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { + retryCalls += 1; + return { access: "must-not-run", refresh: "must-not-run", expires: Date.now() + 3_600_000 }; + }, + }, credential); + } catch (error) { + rejection = error; + } + + expect(retryCalls).toBe(0); + expect(rejection).toBeInstanceOf(OAuthRefreshIntentIOError); + expect(rejection).toMatchObject({ + operation: "resume-cleanup", + code: "OAUTH_REFRESH_INTENT_IO", + cause: cleanupFailure, + }); expect(getAccountSet("anthropic")!.accounts.find(account => account.id === id)!.needsReauth).toBeUndefined(); + expect(readOAuthRefreshIntent("anthropic", id)?.cleanupPending).toBe("definitive-rejection"); } finally { warnSpy.mockRestore(); clearSpy.mockRestore(); @@ -607,25 +782,39 @@ describe("oauth refresh hardening", () => { const credential = getAccountCredential("anthropic", id)!; const generation = credentialGeneration(credential); const persistenceFailure = new Error("credential persistence failed"); + let refreshCalls = 0; + const def = { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { + refreshCalls += 1; + return { + access: "fresh", + refresh: "rt-fresh", + expires: Date.now() + 3_600_000, + }; + }, + }; const mergeSpy = spyOn(storeModule, "mergeAccountCredential").mockImplementation(async () => { throw persistenceFailure; }); + let pendingAfterFailure: ReturnType<typeof readOAuthRefreshIntent>; try { - await expect(refreshAnthropicAccountWithLock("anthropic", id, { - ...OAUTH_PROVIDERS.anthropic!, - refresh: async () => ({ - access: "fresh", - refresh: "rt-fresh", - expires: Date.now() + 3_600_000, - }), - }, credential)).rejects.toBe(persistenceFailure); + await expect(refreshAnthropicAccountWithLock("anthropic", id, def, credential)) + .rejects.toBe(persistenceFailure); - expect(readOAuthRefreshIntent("anthropic", id)).toMatchObject({ generation }); + const pending = readOAuthRefreshIntent("anthropic", id); + pendingAfterFailure = pending; + expect(pending).toMatchObject({ generation }); + expect(pending?.cleanupPending).toBeUndefined(); expect(getAccountCredential("anthropic", id)).toEqual(credential); expect(getAccountSet("anthropic")!.accounts.find(account => account.id === id)!.needsReauth).toBeUndefined(); } finally { mergeSpy.mockRestore(); } + await expect(refreshAnthropicAccountWithLock("anthropic", id, def, credential)) + .rejects.toBeInstanceOf(OAuthLoginRequiredError); + expect(refreshCalls).toBe(1); + expect(readOAuthRefreshIntent("anthropic", id)).toEqual(pendingAfterFailure); }); test("post-persist intent cleanup failure does not turn Anthropic refresh success into failure", async () => { @@ -633,7 +822,7 @@ describe("oauth refresh hardening", () => { const id = getAccountSet("anthropic")!.activeAccountId; const credential = getAccountCredential("anthropic", id)!; const oldGeneration = credentialGeneration(credential); - const clearSpy = spyOn(storeModule, "clearOAuthRefreshIntent").mockImplementation(() => { + const clearSpy = spyOn(storeModule, "clearOAuthRefreshIntentIfMatch").mockImplementation(() => { throw new Error("intent unlink failed"); }); const warnSpy = spyOn(console, "warn").mockImplementation(() => {}); @@ -776,6 +965,135 @@ describe("oauth refresh hardening", () => { expect(readOAuthRefreshIntent("anthropic", id)).toEqual(legacy); expect(readOAuthRefreshIntent("anthropic", id)?.uncertain).toBeUndefined(); + let refreshCalls = 0; + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { + refreshCalls += 1; + return { access: "must-not-run", refresh: "must-not-run", expires: Date.now() + 3_600_000 }; + }, + }, getAccountCredential("anthropic", id)!)).rejects.toBeInstanceOf(OAuthLoginRequiredError); + expect(refreshCalls).toBe(0); + expect(readOAuthRefreshIntent("anthropic", id)).toEqual(legacy); + }); + + test("cleanup-pending CAS never rewrites a foreign same-generation attempt", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const generation = credentialGeneration(getAccountCredential("anthropic", id)!); + const intent = writeOAuthRefreshIntent("anthropic", id, generation, Date.now()); + + for (const foreign of [ + { ...intent, generation: "foreign-generation" }, + { ...intent, attemptId: "foreign-attempt" }, + { ...intent, flightId: "foreign-flight" }, + { ...intent, createdAt: intent.createdAt + 1 }, + ]) { + expect(markOAuthRefreshIntentCleanupPending( + "anthropic", + id, + foreign, + "definitive-rejection", + )).toBeUndefined(); + expect(storeModule.clearOAuthRefreshIntentIfMatch("anthropic", id, foreign)).toBe(false); + expect(readOAuthRefreshIntent("anthropic", id)).toEqual(intent); + } + }); + + test("a replaced obsolete intent blocks Anthropic redispatch", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const credential = getAccountCredential("anthropic", id)!; + const obsolete = writeOAuthRefreshIntent("anthropic", id, "0".repeat(64)); + const replacement = { ...obsolete, attemptId: "foreign-attempt" }; + writeFileSync(getAuthRefreshIntentPath("anthropic", id), `${JSON.stringify(replacement)}\n`); + const clearSpy = spyOn(storeModule, "clearOAuthRefreshIntentIfMatch").mockReturnValue(false); + let refreshCalls = 0; + try { + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { + refreshCalls += 1; + return { access: "must-not-run", refresh: "must-not-run", expires: Date.now() + 3_600_000 }; + }, + }, credential)).rejects.toBeInstanceOf(OAuthTokenRefreshStaleError); + expect(refreshCalls).toBe(0); + expect(readOAuthRefreshIntent("anthropic", id)).toEqual(replacement); + } finally { + clearSpy.mockRestore(); + } + }); + + test("cleanup-pending marker commit preserves an attempt replaced during comparison", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const path = getAuthRefreshIntentPath("anthropic", id); + const generation = credentialGeneration(getAccountCredential("anthropic", id)!); + const original = writeOAuthRefreshIntent("anthropic", id, generation, Date.now(), "original-flight"); + const replacement = { + ...original, + attemptId: "replacement-attempt", + flightId: "replacement-flight", + }; + + expect(markOAuthRefreshIntentCleanupPending( + "anthropic", + id, + original, + "definitive-rejection", + { beforeMarkCommit: () => writeFileSync(path, `${JSON.stringify(replacement)}\n`) }, + )).toBeUndefined(); + expect(readOAuthRefreshIntent("anthropic", id)).toEqual(replacement); + }); + + test("exact cleanup preserves an attempt replaced before unlink", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const path = getAuthRefreshIntentPath("anthropic", id); + const generation = credentialGeneration(getAccountCredential("anthropic", id)!); + const original = writeOAuthRefreshIntent("anthropic", id, generation, Date.now(), "original-flight"); + const replacement = { + ...original, + attemptId: "replacement-attempt", + flightId: "replacement-flight", + }; + + expect(storeModule.clearOAuthRefreshIntentIfMatch( + "anthropic", + id, + original, + { beforeClearRecheck: () => writeFileSync(path, `${JSON.stringify(replacement)}\n`) }, + )).toBe(false); + expect(readOAuthRefreshIntent("anthropic", id)).toEqual(replacement); + }); + + test("an invalid cleanup-pending marker fails closed as uncertain", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const intent = writeOAuthRefreshIntent( + "anthropic", + id, + credentialGeneration(getAccountCredential("anthropic", id)!), + ); + const { attemptId: _attemptId, ...withoutAttemptId } = intent; + for (const invalid of [ + { ...intent, cleanupPending: "unsafe-retry" }, + { ...withoutAttemptId, cleanupPending: "definitive-rejection" }, + { ...intent, cleanupPending: "definitive-rejection", uncertain: true }, + { ...intent, cleanupPending: "definitive-rejection", staleOwner: true }, + ]) { + writeFileSync(getAuthRefreshIntentPath("anthropic", id), `${JSON.stringify(invalid)}\n`); + expect(readOAuthRefreshIntent("anthropic", id)?.uncertain).toBe(true); + } + let refreshCalls = 0; + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { + refreshCalls += 1; + return { access: "must-not-run", refresh: "must-not-run", expires: Date.now() + 3_600_000 }; + }, + }, getAccountCredential("anthropic", id)!)).rejects.toBeInstanceOf(OAuthLoginRequiredError); + expect(refreshCalls).toBe(0); }); test("Anthropic never replays an outstanding oauth-source generation across re-entry", async () => { From e476acd43f8591d7d763e291b7306b403d058896 Mon Sep 17 00:00:00 2001 From: luvs01 <luvs01@hanmail.net> Date: Sun, 30 Aug 2026 19:26:27 +0900 Subject: [PATCH 26/42] fix(oauth): keep post-commit intent cleanup from masking a durable credential When Anthropic adopts a newer Claude Code credential, mergeAccountCredential persists it before the observed refresh intent is cleaned up. Both exact-match and generation-based cleanup rethrow non-ENOENT unlink errors, so a locked or read-only intent file turned a successful adoption into a rejected refresh even though the credential was already durable on disk. Those two post-commit sites now use the existing best-effort helper, which logs and preserves the replay guard instead of throwing. The two remaining throwing calls are unchanged: their return values gate stale-flight handling before a refresh is dispatched, so their failures must still surface. The refresh-intent file also carried its own snapshot type and identity check that duplicated the OAuth file lock's snapshot/sameSnapshot pair, differing only in a field name. Both are compare-and-swap decisions on secret-adjacent files, so they now share one definition rather than two copies that can drift. Regression: a cleanup failure during disk-credential adoption still resolves with the committed token, attempts cleanup, performs no network refresh, and does not mark the account needsReauth. Reverting the first hunk fails it with a rejected promise. (cherry picked from commit f45fb1ca308603d312aae309e1faf7cd7e9f524d) --- src/oauth/index.ts | 6 ++++-- src/oauth/store.ts | 40 ++++++++++--------------------------- tests/oauth-refresh.test.ts | 35 ++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/oauth/index.ts b/src/oauth/index.ts index 1b3754dd21..679df81d9c 100644 --- a/src/oauth/index.ts +++ b/src/oauth/index.ts @@ -797,11 +797,13 @@ export async function refreshAnthropicAccountWithLock( afterPrePersistRead: deps.afterPrePersistRead, }); if (outcome.superseded) { - if (pendingIntent) clearObservedAnthropicRefreshIntent(provider, accountId, pendingIntent); + // The disk credential is already durable here, so cleanup is secondary: an unlink + // failure must not mask a committed credential by throwing over the return below. + if (pendingIntent) clearAnthropicRefreshIntentBestEffort(provider, accountId, pendingIntent); if (outcome.stored.expires > now() + REFRESH_SKEW_MS) return outcome.stored.access; throw new OAuthLoginRequiredError(provider); } - if (pendingIntent) clearObservedAnthropicRefreshIntent(provider, accountId, pendingIntent); + if (pendingIntent) clearAnthropicRefreshIntentBestEffort(provider, accountId, pendingIntent); return disk.access; } if (!pendingIntent?.uncertain && pendingIntent?.generation === generation) { diff --git a/src/oauth/store.ts b/src/oauth/store.ts index 842ee127fb..bc9e0e11c1 100644 --- a/src/oauth/store.ts +++ b/src/oauth/store.ts @@ -93,13 +93,10 @@ export class OAuthRefreshIntentIOError extends Error { } } -interface OAuthRefreshIntentFileSnapshot { - raw: string; - dev: number; - ino: number; - mtimeMs: number; - size: number; -} +// Both compare-and-swap sites in this file — the OAuth file lock and the refresh-intent +// file — must agree on what "the same file" means, so they share one snapshot shape and +// one identity check (`snapshot`/`sameSnapshot` below) rather than two copies that can drift. +type OAuthRefreshIntentFileSnapshot = LockSnapshot; export interface OAuthRefreshIntentMutationHooks { beforeMarkCommit?: () => void; @@ -112,23 +109,6 @@ function uncertainOAuthRefreshIntent(provider: string, accountId: string): OAuth return { version: 1, provider, accountId, generation: "", createdAt: 0, uncertain: true }; } -function snapshotOAuthRefreshIntentFile(path: string): OAuthRefreshIntentFileSnapshot { - const raw = readFileSync(path, "utf8"); - const stat = statSync(path); - return { raw, dev: stat.dev, ino: stat.ino, mtimeMs: stat.mtimeMs, size: stat.size }; -} - -function sameOAuthRefreshIntentFileSnapshot( - left: OAuthRefreshIntentFileSnapshot, - right: OAuthRefreshIntentFileSnapshot, -): boolean { - return left.raw === right.raw - && left.dev === right.dev - && left.ino === right.ino - && left.mtimeMs === right.mtimeMs - && left.size === right.size; -} - function parseOAuthRefreshIntent( provider: string, accountId: string, @@ -242,9 +222,9 @@ function exactOAuthRefreshIntentFile( try { hardenConfigDir(); hardenExistingSecret(path); - const file = snapshotOAuthRefreshIntentFile(path); + const file = snapshot(path); let intent: OAuthRefreshIntent; - try { intent = parseOAuthRefreshIntent(provider, accountId, file.raw); } + try { intent = parseOAuthRefreshIntent(provider, accountId, file.bytes); } catch { intent = uncertainOAuthRefreshIntent(provider, accountId); } return { intent, snapshot: file }; } catch (error) { @@ -279,11 +259,11 @@ export function markOAuthRefreshIntentCleanupPending( beforeRename: hooks.beforeMarkCommit, validateBeforeRename: targetPath => { let latest: OAuthRefreshIntentFileSnapshot; - try { latest = snapshotOAuthRefreshIntentFile(targetPath); } + try { latest = snapshot(targetPath); } catch (error) { throw new OAuthRefreshIntentChangedError("OAuth refresh intent changed before marker commit", { cause: error }); } - if (!sameOAuthRefreshIntentFileSnapshot(observed.snapshot, latest)) { + if (!sameSnapshot(observed.snapshot, latest)) { throw new OAuthRefreshIntentChangedError("OAuth refresh intent changed before marker commit"); } }, @@ -319,12 +299,12 @@ export function clearOAuthRefreshIntentIfMatch( ) return false; hooks.beforeClearRecheck?.(); let latest: OAuthRefreshIntentFileSnapshot; - try { latest = snapshotOAuthRefreshIntentFile(path); } + try { latest = snapshot(path); } catch (error) { if (errorCode(error) === "ENOENT") return true; throw error; } - if (!sameOAuthRefreshIntentFileSnapshot(observed.snapshot, latest)) return false; + if (!sameSnapshot(observed.snapshot, latest)) return false; try { unlinkSync(path); return true; } catch (error) { if (errorCode(error) === "ENOENT") return true; throw error; } }); diff --git a/tests/oauth-refresh.test.ts b/tests/oauth-refresh.test.ts index d4bbd435a6..cfbc2b64c8 100644 --- a/tests/oauth-refresh.test.ts +++ b/tests/oauth-refresh.test.ts @@ -1201,6 +1201,41 @@ describe("oauth refresh hardening", () => { expect(getCredential("anthropic")?.refresh).toBe("rt-new"); }); + /** + * The disk credential is committed before the observed intent is cleaned up. Cleanup is a + * secondary durability concern, so an unlink failure must not throw over the committed + * credential and turn a successful adoption into a caller-visible refresh error. + */ + test("a cleanup failure after adopting a disk credential does not mask the committed token", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, source: "local-cli" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const stored = getAccountCredential("anthropic", id)!; + writeOAuthRefreshIntent("anthropic", id, credentialGeneration(stored)); + expect(readOAuthRefreshIntent("anthropic", id)).toBeDefined(); + + seedClaudeCredentials("disk", "rt-new", Date.now() + 3600_000); + const mock = mockRefreshFetch([new Response("unexpected", { status: 500 })]); + + // The intent carries an attemptId, so cleanup runs through the exact-match clear. + // Fail it the way a locked or read-only file would. + let cleanupAttempts = 0; + const cleanupSpy = spyOn(storeModule, "clearOAuthRefreshIntentIfMatch").mockImplementation(() => { + cleanupAttempts += 1; + throw new OAuthRefreshIntentIOError("clear-intent", new Error("forced cleanup failure (EROFS)")); + }); + try { + await expect(getValidAccessToken("anthropic")).resolves.toBe("disk"); + } finally { + cleanupSpy.mockRestore(); + } + + // The adoption committed and no network refresh was attempted; only the guard survives. + expect(cleanupAttempts).toBeGreaterThan(0); + expect(mock.count()).toBe(0); + expect(getCredential("anthropic")?.refresh).toBe("rt-new"); + expect(getAccountSet("anthropic")!.accounts[0]!.needsReauth).toBeUndefined(); + }); + test("marked Anthropic local-cli account lazily recovers only from a newer disk generation", async () => { await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, source: "local-cli" }); const id = getAccountSet("anthropic")!.activeAccountId; From ab2f716637e1f3608e57b3eedabe4698b6f5c321 Mon Sep 17 00:00:00 2001 From: luvs01 <luvs01@hanmail.net> Date: Sun, 30 Aug 2026 23:40:17 +0900 Subject: [PATCH 27/42] test(oauth): use declared cleanup operation (cherry picked from commit eb4639b658c37af8b5173363469973d897067d6a) --- tests/oauth-refresh.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/oauth-refresh.test.ts b/tests/oauth-refresh.test.ts index cfbc2b64c8..ef4991d836 100644 --- a/tests/oauth-refresh.test.ts +++ b/tests/oauth-refresh.test.ts @@ -1221,7 +1221,7 @@ describe("oauth refresh hardening", () => { let cleanupAttempts = 0; const cleanupSpy = spyOn(storeModule, "clearOAuthRefreshIntentIfMatch").mockImplementation(() => { cleanupAttempts += 1; - throw new OAuthRefreshIntentIOError("clear-intent", new Error("forced cleanup failure (EROFS)")); + throw new OAuthRefreshIntentIOError("clear-cleanup-pending", new Error("forced cleanup failure (EROFS)")); }); try { await expect(getValidAccessToken("anthropic")).resolves.toBe("disk"); From 5bb4113b3e6aefdc8448fca445af1ea0ba7e2ec1 Mon Sep 17 00:00:00 2001 From: luvs01 <luvs01@hanmail.net> Date: Mon, 31 Aug 2026 02:38:24 +0900 Subject: [PATCH 28/42] test(oauth): preserve refresh-intent spy hooks (cherry picked from commit 5ca217211f8c9baa8644a09c6fa482a28c29b399) --- tests/oauth-refresh.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/oauth-refresh.test.ts b/tests/oauth-refresh.test.ts index ef4991d836..0699983195 100644 --- a/tests/oauth-refresh.test.ts +++ b/tests/oauth-refresh.test.ts @@ -607,10 +607,10 @@ describe("oauth refresh hardening", () => { controller.abort(aborted); const realClear = storeModule.clearOAuthRefreshIntentIfMatch; let clearCalls = 0; - const clearSpy = spyOn(storeModule, "clearOAuthRefreshIntentIfMatch").mockImplementation((provider, accountId, expected) => { + const clearSpy = spyOn(storeModule, "clearOAuthRefreshIntentIfMatch").mockImplementation((...args) => { clearCalls += 1; if (clearCalls === 1) throw new Error("intent unlink failed"); - return realClear(provider, accountId, expected); + return realClear(...args); }); const warnSpy = spyOn(console, "warn").mockImplementation(() => {}); try { @@ -680,14 +680,14 @@ describe("oauth refresh hardening", () => { const realClear = storeModule.clearOAuthRefreshIntentIfMatch; const events: string[] = []; let clearCalls = 0; - const clearSpy = spyOn(storeModule, "clearOAuthRefreshIntentIfMatch").mockImplementation((provider, accountId, expected) => { + const clearSpy = spyOn(storeModule, "clearOAuthRefreshIntentIfMatch").mockImplementation((...args) => { clearCalls += 1; if (clearCalls === 1) { events.push("clear-fail"); throw clearFailure; } - events.push(expected.cleanupPending ? "clear-recovery" : "clear-success"); - return realClear(provider, accountId, expected); + events.push(args[2].cleanupPending ? "clear-recovery" : "clear-success"); + return realClear(...args); }); const warnSpy = spyOn(console, "warn").mockImplementation(() => {}); try { From a41b7995c752f36a750514560a11cf6ab0a215c8 Mon Sep 17 00:00:00 2001 From: luvs01 <luvs01@hanmail.net> Date: Mon, 31 Aug 2026 03:09:41 +0900 Subject: [PATCH 29/42] fix(oauth): adopt newer disk credentials before cleanup (cherry picked from commit 66780c655182beffb250fa26425cdd01d47066e1) --- src/oauth/index.ts | 8 ++++---- tests/oauth-refresh.test.ts | 10 ++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/oauth/index.ts b/src/oauth/index.ts index 679df81d9c..f79578f1b4 100644 --- a/src/oauth/index.ts +++ b/src/oauth/index.ts @@ -786,10 +786,6 @@ export async function refreshAnthropicAccountWithLock( const account = getAccountSet(provider)?.accounts.find(candidate => candidate.id === accountId); const generation = credentialGeneration(stored); let pendingIntent = readOAuthRefreshIntent(provider, accountId); - if (pendingIntent?.cleanupPending && pendingIntent.generation === generation) { - resumeAnthropicRefreshIntentCleanup(provider, accountId, pendingIntent); - pendingIntent = undefined; - } const disk = newerClaudeCredential(stored, now()); if (disk) { const outcome = await mergeAccountCredential(provider, accountId, disk, { @@ -806,6 +802,10 @@ export async function refreshAnthropicAccountWithLock( if (pendingIntent) clearAnthropicRefreshIntentBestEffort(provider, accountId, pendingIntent); return disk.access; } + if (pendingIntent?.cleanupPending && pendingIntent.generation === generation) { + resumeAnthropicRefreshIntentCleanup(provider, accountId, pendingIntent); + pendingIntent = undefined; + } if (!pendingIntent?.uncertain && pendingIntent?.generation === generation) { if (pendingIntent.staleOwner) throw new OAuthTokenRefreshStaleError(); if (deps.replacedStaleFlight && pendingIntent.flightId === deps.replacedStaleFlight.flightId) { diff --git a/tests/oauth-refresh.test.ts b/tests/oauth-refresh.test.ts index 0699983195..279a84c40c 100644 --- a/tests/oauth-refresh.test.ts +++ b/tests/oauth-refresh.test.ts @@ -1210,8 +1210,13 @@ describe("oauth refresh hardening", () => { await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, source: "local-cli" }); const id = getAccountSet("anthropic")!.activeAccountId; const stored = getAccountCredential("anthropic", id)!; - writeOAuthRefreshIntent("anthropic", id, credentialGeneration(stored)); - expect(readOAuthRefreshIntent("anthropic", id)).toBeDefined(); + const pending = writeOAuthRefreshIntent("anthropic", id, credentialGeneration(stored)); + expect(markOAuthRefreshIntentCleanupPending( + "anthropic", + id, + pending, + "definitive-rejection", + )).toMatchObject({ cleanupPending: "definitive-rejection" }); seedClaudeCredentials("disk", "rt-new", Date.now() + 3600_000); const mock = mockRefreshFetch([new Response("unexpected", { status: 500 })]); @@ -1234,6 +1239,7 @@ describe("oauth refresh hardening", () => { expect(mock.count()).toBe(0); expect(getCredential("anthropic")?.refresh).toBe("rt-new"); expect(getAccountSet("anthropic")!.accounts[0]!.needsReauth).toBeUndefined(); + expect(readOAuthRefreshIntent("anthropic", id)?.cleanupPending).toBe("definitive-rejection"); }); test("marked Anthropic local-cli account lazily recovers only from a newer disk generation", async () => { From c4188190becf23aafb7156502b2a9a8bbaab607a Mon Sep 17 00:00:00 2001 From: luvs01 <luvs01@hanmail.net> Date: Mon, 31 Aug 2026 13:35:35 +0900 Subject: [PATCH 30/42] fix(oauth): retry refresh intent marker contention (cherry picked from commit 60c9d7604afcea41d203364b4216cda9d56788e3) --- src/oauth/index.ts | 55 +++++++++++++------ tests/oauth-refresh.test.ts | 104 +++++++++++++++++++++++++++++++++++- 2 files changed, 141 insertions(+), 18 deletions(-) diff --git a/src/oauth/index.ts b/src/oauth/index.ts index f79578f1b4..c4a682ee3f 100644 --- a/src/oauth/index.ts +++ b/src/oauth/index.ts @@ -1,7 +1,7 @@ import type { KiroOAuthMetadata, OAuthController, OAuthCredentials } from "./types"; import { parseCallbackInput } from "./callback-server"; import type { OcxConfig, OcxProviderConfig, RefreshPolicy } from "../types"; -import { loadConfig, resolveEnvValue, saveConfig } from "../config"; +import { ConfigMutationLockError, loadConfig, resolveEnvValue, saveConfig } from "../config"; import { maskEmail } from "../lib/privacy"; import { KiroTokenRefreshError, environmentKiroRoutingMetadata, loginKiro, refreshKiroToken, settleKiroLoginTransaction } from "./kiro"; import { @@ -628,27 +628,48 @@ function clearAnthropicRefreshIntentBestEffort( } } -function clearAnthropicRefreshIntentForKnownFailure( +const ANTHROPIC_INTENT_MARK_RETRY_DELAYS_MS = [10, 25, 50] as const; + +function isConfigMutationLockContention(error: unknown): boolean { + if (!(error instanceof ConfigMutationLockError)) return false; + const cause = error.cause; + const code = cause && typeof cause === "object" && "code" in cause + ? String((cause as { code?: unknown }).code) + : ""; + return code === "SQLITE_BUSY" || code === "SQLITE_LOCKED"; +} + +async function clearAnthropicRefreshIntentForKnownFailure( provider: string, accountId: string, expected: OAuthRefreshIntent, cleanupPending: OAuthRefreshIntentCleanupPending, refreshError: unknown, -): boolean { +): Promise<boolean> { let marked: OAuthRefreshIntent | undefined; - try { - marked = markOAuthRefreshIntentCleanupPending( - provider, - accountId, - expected, - cleanupPending, - ); - } catch (cause) { - throw new OAuthRefreshIntentIOError( - "mark-cleanup-pending", - cause, - refreshError, - ); + for (let attempt = 0; attempt <= ANTHROPIC_INTENT_MARK_RETRY_DELAYS_MS.length; attempt += 1) { + try { + marked = markOAuthRefreshIntentCleanupPending( + provider, + accountId, + expected, + cleanupPending, + ); + break; + } catch (cause) { + const retryDelay = ANTHROPIC_INTENT_MARK_RETRY_DELAYS_MS[attempt]; + if (!isConfigMutationLockContention(cause) || retryDelay === undefined) { + throw new OAuthRefreshIntentIOError( + "mark-cleanup-pending", + cause, + refreshError, + ); + } + // The provider has definitively answered, so caller cancellation no longer changes the + // settlement obligation. Yield briefly while retaining the per-account refresh lock, then + // rerun the existing compare-and-swap marker against current disk state. + await Bun.sleep(retryDelay); + } } if (!marked) { throw new OAuthRefreshIntentIOError( @@ -872,7 +893,7 @@ export async function refreshAnthropicAccountWithLock( // rotated the token, and replaying it could trip refresh-token-reuse revocation. // Those outcomes keep the intent so the guard still refuses a blind replay. if ((!refreshMayHaveReachedProvider || definitivelyAnswered(error)) && attemptIntent) { - clearAnthropicRefreshIntentForKnownFailure( + await clearAnthropicRefreshIntentForKnownFailure( provider, accountId, attemptIntent, diff --git a/tests/oauth-refresh.test.ts b/tests/oauth-refresh.test.ts index 279a84c40c..3ba3e57ce8 100644 --- a/tests/oauth-refresh.test.ts +++ b/tests/oauth-refresh.test.ts @@ -641,8 +641,13 @@ describe("oauth refresh hardening", () => { const id = getAccountSet("anthropic")!.activeAccountId; const credential = getAccountCredential("anthropic", id)!; const transient = new AnthropicTokenError("server", 503, undefined); - const markerFailure = new Error("intent marker write failed"); + const markerFailure = new configModule.ConfigMutationLockError( + "Could not acquire config mutation transaction", + { cause: Object.assign(new Error("intent marker write failed"), { code: "EACCES" }) }, + ); + let markerCalls = 0; const markerSpy = spyOn(storeModule, "markOAuthRefreshIntentCleanupPending").mockImplementation(() => { + markerCalls += 1; throw markerFailure; }); try { @@ -662,6 +667,7 @@ describe("oauth refresh hardening", () => { cause: markerFailure, refreshError: transient, }); + expect(markerCalls).toBe(1); const pending = readOAuthRefreshIntent("anthropic", id); expect(pending?.cleanupPending).toBeUndefined(); expect(getAccountSet("anthropic")!.accounts.find(account => account.id === id)!.needsReauth).toBeUndefined(); @@ -670,6 +676,102 @@ describe("oauth refresh hardening", () => { } }); + test("transient cleanup-marker lock contention settles a retryable rejection without reauth", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const credential = getAccountCredential("anthropic", id)!; + const transient = new AnthropicTokenError("server", 503, undefined); + const realMark = storeModule.markOAuthRefreshIntentCleanupPending; + let markerCalls = 0; + const markerSpy = spyOn(storeModule, "markOAuthRefreshIntentCleanupPending").mockImplementation((...args) => { + markerCalls += 1; + if (markerCalls <= 2) { + throw new configModule.ConfigMutationLockError( + "Config mutation already in progress", + { cause: Object.assign(new Error("database is locked"), { code: "SQLITE_BUSY" }) }, + ); + } + return realMark(...args); + }); + try { + let providerCalls = 0; + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { + providerCalls += 1; + throw transient; + }, + }, credential)).rejects.toBe(transient); + + expect(providerCalls).toBe(1); + expect(markerCalls).toBe(3); + expect(readOAuthRefreshIntent("anthropic", id)).toBeUndefined(); + expect(getAccountSet("anthropic")!.accounts.find(account => account.id === id)!.needsReauth).toBeUndefined(); + + await expect(refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => ({ + access: "fresh", + refresh: "rt-fresh", + expires: Date.now() + 3_600_000, + }), + }, getAccountCredential("anthropic", id)!)).resolves.toBe("fresh"); + expect(getAccountCredential("anthropic", id)?.access).toBe("fresh"); + expect(readOAuthRefreshIntent("anthropic", id)).toBeUndefined(); + } finally { + markerSpy.mockRestore(); + } + }); + + test("persistent cleanup-marker lock contention stays bounded and preserves the replay guard", async () => { + await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); + const id = getAccountSet("anthropic")!.activeAccountId; + const credential = getAccountCredential("anthropic", id)!; + const generation = credentialGeneration(credential); + const transient = new AnthropicTokenError("server", 503, undefined); + let markerCalls = 0; + let lastBusy: configModule.ConfigMutationLockError | undefined; + const markerSpy = spyOn(storeModule, "markOAuthRefreshIntentCleanupPending").mockImplementation(() => { + markerCalls += 1; + lastBusy = new configModule.ConfigMutationLockError( + "Config mutation already in progress", + { cause: Object.assign(new Error("database table is locked"), { code: "SQLITE_LOCKED" }) }, + ); + throw lastBusy; + }); + try { + let providerCalls = 0; + let rejection: unknown; + try { + await refreshAnthropicAccountWithLock("anthropic", id, { + ...OAUTH_PROVIDERS.anthropic!, + refresh: async () => { + providerCalls += 1; + throw transient; + }, + }, credential); + } catch (error) { + rejection = error; + } + + expect(providerCalls).toBe(1); + expect(markerCalls).toBe(4); + expect(rejection).toBeInstanceOf(OAuthRefreshIntentIOError); + expect(rejection).toMatchObject({ + operation: "mark-cleanup-pending", + code: "OAUTH_REFRESH_INTENT_IO", + cause: lastBusy, + refreshError: transient, + }); + expect(getAccountCredential("anthropic", id)).toEqual(credential); + expect(readOAuthRefreshIntent("anthropic", id)).toMatchObject({ generation }); + expect(readOAuthRefreshIntent("anthropic", id)?.cleanupPending).toBeUndefined(); + expect(getAccountSet("anthropic")!.accounts.find(account => account.id === id)!.needsReauth).toBeUndefined(); + } finally { + markerSpy.mockRestore(); + } + }); + test("a failed definitive-rejection cleanup is retried before the next Anthropic request", async () => { await saveCredential("anthropic", { access: "old", refresh: "rt-old", expires: 1, accountId: "acct" }); const id = getAccountSet("anthropic")!.activeAccountId; From 42ad9c44dcf95a7b72cbb4aaa0fe6122aa35f629 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 04:30:41 +0900 Subject: [PATCH 31/42] fix(routing): treat a live full burst window as exhausted, not unknown (#3029) (#3110) * fix(routing): treat a live full burst window as exhausted, not unknown Closes #3029. shortPercent survives quota parsing as a real blocking window, then computeCodexUsageScore throws it away when no long window is known. Unknown passes the headroom check and suppresses auto-switch, so an account whose five-hour window is full stays selected and the pool wedges on it - which is the conjunction the reporter measured. The existing comment is right that a short-only reading cannot stand in for a long one: a bare shortPercent: 0 would score a flat 0 and make an unverified account look like the emptiest in the pool. That argument does not extend to a full window. 100 is not an optimistic guess about an unobserved window, it is a direct observation that the account cannot serve a request right now. Freshness is the other half, and without it this fix inverts the bug. getAccountQuota performs no expiry check, partial updates carry the old short tuple forward, and disk hydration accepts a persisted reading for hours - so a terminal score must expire with its window or a recovered account stays excluded, which is #3029 pointed the other way. A reading with no shortResetAt cannot be aged and stays unknown: a wrongly-selected account fails one request, a wrongly-excluded one is invisible until someone reads the pool by hand. Both units reach storage - normalizeResetAt does not scale and the GUI disambiguates by magnitude at read time - so the comparison normalizes the same way. Read as milliseconds, a seconds value looks like it reset in 1970 and every terminal reading scores unknown: a fix that passes its own test and does nothing. The clock is threaded through all eight call sites rather than read from wall time. Two of them already had a now and dropped it, including subagent fallback, which reads the same score to decide whether a native model is exhausted - so a stale terminal reading pushed subagents off a live model too. That case is red when the threaded clock is replaced with Date.now(). * fix(routing): carry the request clock through the selection helpers Review found the injected clock dropped one level below the scorer. hasCodexQuotaHeadroom and pickLowestUsageAmong defaulted to Date.now(), and their callers omitted it, so the priority tier, fill-first, preemption, pin release and shared-health checks all scored against wall time. With an injected now and a shortResetAt between the two, a terminal account is read as unknown and keeps its tier. Both helpers now take the clock, and every caller forwards the request's view: the tier lambda in getEligiblePoolAccounts, pickFillFirstCodexAccount, pickNextFillFirstCodexAccount (whose _now was parked unused), pickPriorityPreemption, releaseDrainedCodexAccountPin and isHealthySharedCodexSelection. Adds the end-to-end selection cases the plan asked for: a new thread moves to B when A's burst window is full in seconds, an already-bound thread rebinds when it is full in milliseconds, and A becomes selectable again once the window resets - so the fix cannot trade "exhausted account stays selected" for "recovered account stays excluded". The tiered case is scoped honestly. It proves a tiered pool honours a terminal window, and its comment says plainly that it does not isolate the threaded clock: selection reaches the same answer by another route when the clock is dropped there. The scorer and subagent cases carry that proof. * fix(routing): finish the clock, and make three vacuous cases real Second review round found three more wall-time reads and three tests that were green for reasons unrelated to what they claimed. pickLowestUsageAmong inside pickPriorityPreemption, and both shared-health checks in the affinity and active-selection paths, still omitted the clock. They now pass it, so every selection path scores against one view of time. The affinity case bound its thread while A was ALREADY terminal, so the first resolution could pick B and the second merely proved B was reused. It now binds to A while A is cool, then fills A's window and asserts the rebind. The recovery case left both accounts unknown, where A is kept by default - true even against a freshness-blind scorer. B now carries known headroom, so a scorer that ignores the reset moves the request to B and the assertion fails. The tiered case had the priority order backwards: higher numbers run earlier, so B outranked A and won regardless of A's window. It also used a future clock, which is live under both views. It now gives A the higher priority, uses a historical instant with A's window live only against the request clock, and runs fill-first - so the tier check is the only thing that can move the selection. Both are now red against the defect they name: dropping the tier clock fails the tiered case, and removing the freshness gate fails the selection case. * docs(devlog): record what wp4's review rounds changed about the plan --- .../040_wp4_terminal_short_window.md | 24 +++++ src/codex/quota.ts | 6 ++ src/codex/routing.ts | 84 +++++++++++++---- src/codex/subagent-model-fallback.ts | 5 +- tests/codex-routing.test.ts | 93 +++++++++++++++++++ tests/subagent-model-fallback.test.ts | 32 ++++++- 6 files changed, 226 insertions(+), 18 deletions(-) diff --git a/devlog/_plan/260831_prio70_train_round2/040_wp4_terminal_short_window.md b/devlog/_plan/260831_prio70_train_round2/040_wp4_terminal_short_window.md index 1e09e13645..44543ca5e2 100644 --- a/devlog/_plan/260831_prio70_train_round2/040_wp4_terminal_short_window.md +++ b/devlog/_plan/260831_prio70_train_round2/040_wp4_terminal_short_window.md @@ -177,3 +177,27 @@ Suite, typecheck and privacy scan on `ssh lidge`. `Closes #3029`. PR #3003 is unrelated to this issue and independently defective (two pre-WHAM errors lack `quotaProbeSkipped` at its `src/codex/auth-api.ts:1013-1021`, causing false five-minute suppression); do not link it here. + +## What implementation added beyond this plan + +Three adversarial review rounds (findings 2, 4, 0). The plan's scoring rule survived +intact; everything the rounds found was in the plumbing and in the tests: + +- **The clock leaks below the scorer.** The plan enumerated eight `computeCodexUsageScore` + call sites and I threaded all of them. That was not enough: `hasCodexQuotaHeadroom` and + `pickLowestUsageAmong` each defaulted to `Date.now()`, and their callers omitted it — so + the priority tier, fill-first, preemption, pin release and both shared-health checks all + scored against wall time while the resolver above them used the request clock. An + injected `now` with a `shortResetAt` between the two reads the same tuple two ways. +- **Three of my own tests were vacuous, and one was backwards.** The affinity case bound + its thread while the account was already terminal, so it proved reuse rather than a + rebind. The recovery case left both accounts unknown, where the active one is kept by + default — true even against a freshness-blind scorer. And the tiered case had the + priority order inverted: higher numbers run earlier + (`src/codex/account-priority.ts:18`), so the account I meant to outrank actually lost, + and it won for a reason unrelated to the window. + +The last one is worth keeping as a rule: a test whose fixture encodes a directional +assumption should be driven red against the specific defect it names, not merely observed +to pass. Each of the eight assertions now has a named mutation it fails against, recorded +in the PR description. diff --git a/src/codex/quota.ts b/src/codex/quota.ts index 3248186375..87e5766b84 100644 --- a/src/codex/quota.ts +++ b/src/codex/quota.ts @@ -110,6 +110,12 @@ function mayCommitAccountQuota(accountId: string, writerGeneration: number): boo // Valid upstream percentages are normalized to 0..100. Keep "unknown" outside that domain so an // actually exhausted account is still eligible for threshold rotation. export const CODEX_UNKNOWN_USAGE_SCORE = 101; +/** + * A window reading at or above this is a measured refusal, not a position on a scale. + * + * Separate from `CODEX_UNKNOWN_USAGE_SCORE` because they mean opposite things: unknown is + * "we have not observed this account", 100 is "we observed it and it is full". + */ export const CODEX_EXHAUSTED_USAGE_PERCENT = 100; export function isCodexQuotaExhausted( diff --git a/src/codex/routing.ts b/src/codex/routing.ts index 250aac9636..9890e14046 100644 --- a/src/codex/routing.ts +++ b/src/codex/routing.ts @@ -17,7 +17,7 @@ import { seedPoolRotationAccount, selectPriorityTier, } from "./pool-rotation"; -import { CODEX_UNKNOWN_USAGE_SCORE, getAccountQuota } from "./quota"; +import { CODEX_EXHAUSTED_USAGE_PERCENT, CODEX_UNKNOWN_USAGE_SCORE, getAccountQuota } from "./quota"; import { isThirtyDayOnlyCodexPlan } from "./plan"; import { MAIN_CODEX_ACCOUNT_ID, @@ -364,7 +364,8 @@ export function computeCodexUsageScore(quota: { weeklyPercent?: number; monthlyPercent?: number; shortPercent?: number; -} | null, plan?: unknown): number { + shortResetAt?: number; +} | null, plan?: unknown, now: number = Date.now()): number { if (!quota) return CODEX_UNKNOWN_USAGE_SCORE; const finite = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value); const longWindows = isThirtyDayOnlyCodexPlan(plan) @@ -376,11 +377,50 @@ export function computeCodexUsageScore(quota: { // account whose weekly/monthly usage is entirely unverified look like the emptiest in the // pool, so `pickLowestUsageAmong` would send every request to it. Unknown has to stay // unknown until a governing window is actually observed. - if (knownLong.length === 0) return CODEX_UNKNOWN_USAGE_SCORE; + // + // A FULL burst window is the exception (#3029). It is not an optimistic guess about an + // unobserved window — it is a direct observation that the account cannot serve a request + // right now, whatever its monthly position turns out to be. Unknown-means-selectable is + // correct for uncertainty and wrong for a measured refusal: the account stays selected, + // `applyQuotaAutoSwitch` never fires, and the pool wedges on an exhausted credential. + if (knownLong.length === 0) { + return isTerminalShortWindow(quota, now) ? CODEX_EXHAUSTED_USAGE_PERCENT : CODEX_UNKNOWN_USAGE_SCORE; + } const values = finite(quota.shortPercent) ? [...knownLong, quota.shortPercent] : knownLong; return Math.max(...values); } +/** + * A short-only reading that proves the account is blocked NOW. + * + * Freshness is not optional. `getAccountQuota` performs no expiry check, partial updates + * carry the old short tuple forward, and disk hydration accepts a persisted reading for + * hours — so scoring 100 from `shortPercent` alone would keep excluding an account whose + * five-hour window has since reset. That is #3029 pointed the other way: the issue is that + * an exhausted account stays selected, and "a recovered account stays excluded" trades one + * unusable pool for another. + * + * A reading with no `shortResetAt` cannot be aged, so it stays unknown. The conservative + * direction here is the one that keeps an account selectable: a wrongly-selected account + * fails one request, while a wrongly-excluded one is invisible until someone reads the pool + * by hand. + */ +function isTerminalShortWindow( + quota: { shortPercent?: number; shortResetAt?: number }, + now: number, +): boolean { + if (typeof quota.shortPercent !== "number" || !Number.isFinite(quota.shortPercent)) return false; + if (quota.shortPercent < CODEX_EXHAUSTED_USAGE_PERCENT) return false; + const resetAt = quota.shortResetAt; + if (typeof resetAt !== "number" || !Number.isFinite(resetAt) || resetAt <= 0) return false; + // Both units reach storage: `normalizeResetAt` does not scale, and the GUI disambiguates + // by magnitude at read time. A comparison written against one assumption is off by 1000x + // against the other, and in the seconds-read-as-milliseconds direction every terminal + // reading looks like it reset in 1970 — a fix that passes its own test and does nothing. + const resetAtMs = resetAt < 10_000_000_000 ? resetAt * 1000 : resetAt; + return resetAtMs > now; +} + export function classifyCodexUpstreamOutcome( outcome: CodexUpstreamOutcome, denial?: "workspace" | "entitlement", @@ -1131,7 +1171,7 @@ function getEligiblePoolAccounts( return selectPriorityTier( ids, codexAccountPriorityLookup(config), - id => hasCodexQuotaHeadroom(config, id, selectionOptions), + id => hasCodexQuotaHeadroom(config, id, selectionOptions, now), pinnedCodexAccountId(config), ); } @@ -1163,12 +1203,14 @@ function hasCodexQuotaHeadroom( config: OcxConfig, accountId: string, selectionOptions?: CodexAccountUsabilityOptions, + now: number = Date.now(), ): boolean { const threshold = config.autoSwitchThreshold ?? 80; if (threshold <= 0) return true; const usage = computeCodexUsageScore( getAccountQuota(accountId), getPoolAccountPlanForSelection(config, accountId, selectionOptions), + now, ); if (isUnknownUsage(usage)) return true; return usage < threshold; @@ -1188,7 +1230,7 @@ function pickFillFirstCodexAccount( if (eligible.length === 0) return null; const active = getEffectiveActiveCodexAccountId(config); - if (active && eligible.includes(active) && hasCodexQuotaHeadroom(config, active, selectionOptions)) { + if (active && eligible.includes(active) && hasCodexQuotaHeadroom(config, active, selectionOptions, now)) { return active; } @@ -1200,7 +1242,7 @@ function pickNextFillFirstCodexAccount( config: OcxConfig, afterId: string | null, eligible: readonly string[] = listEligibleCodexAccountIds(config, Date.now()), - _now = Date.now(), + now = Date.now(), selectionOptions?: CodexAccountUsabilityOptions, ): string | null { if (eligible.length === 0) return null; @@ -1208,7 +1250,7 @@ function pickNextFillFirstCodexAccount( if (!afterId) { // Prefer an under-threshold account when starting with no active cursor. for (const id of ordered) { - if (hasCodexQuotaHeadroom(config, id, selectionOptions)) return id; + if (hasCodexQuotaHeadroom(config, id, selectionOptions, now)) return id; } return ordered[0] ?? null; } @@ -1223,7 +1265,7 @@ function pickNextFillFirstCodexAccount( const startIdx = stableAll.indexOf(afterId); if (startIdx < 0) { for (const id of ordered) { - if (hasCodexQuotaHeadroom(config, id, selectionOptions)) return id; + if (hasCodexQuotaHeadroom(config, id, selectionOptions, now)) return id; } return ordered[0] ?? null; } @@ -1234,7 +1276,7 @@ function pickNextFillFirstCodexAccount( const candidate = stableAll[(startIdx + step) % stableAll.length]!; if (!eligible.includes(candidate)) continue; if (!fallback) fallback = candidate; - if (hasCodexQuotaHeadroom(config, candidate, selectionOptions)) return candidate; + if (hasCodexQuotaHeadroom(config, candidate, selectionOptions, now)) return candidate; } return fallback ?? ordered[0] ?? null; } @@ -1356,6 +1398,7 @@ function pickLowerUsageAccount( const usage = computeCodexUsageScore( getAccountQuota(id), getPoolAccountPlanForSelection(config, id, selectionOptions), + now, ); if (usage < bestUsage) { best = id; @@ -1370,6 +1413,7 @@ function pickLowestUsageAmong( config: OcxConfig, ids: readonly string[], selectionOptions?: CodexAccountUsabilityOptions, + now: number = Date.now(), ): string | null { let best: string | null = null; let bestUsage = Number.POSITIVE_INFINITY; @@ -1377,6 +1421,7 @@ function pickLowestUsageAmong( const usage = computeCodexUsageScore( getAccountQuota(id), getPoolAccountPlanForSelection(config, id, selectionOptions), + now, ); if (usage < bestUsage) { best = id; @@ -1397,6 +1442,7 @@ export function pickLowestUsageCodexAccount( config, getEligiblePoolAccounts(config, excludeId, now, quotaScope, selectionOptions), selectionOptions, + now, ); } @@ -1540,7 +1586,7 @@ function pickPriorityPreemption( if ( pinned !== undefined && eligible.includes(pinned) - && hasCodexQuotaHeadroom(config, pinned, selectionOptions) + && hasCodexQuotaHeadroom(config, pinned, selectionOptions, now) ) return null; const priorityOf = codexAccountPriorityLookup(config); if (priorityOf(eligible[0]!) <= priorityOf(active)) return null; @@ -1548,8 +1594,9 @@ function pickPriorityPreemption( // picking one would hand the request straight back to a drained account. return pickLowestUsageAmong( config, - eligible.filter(id => hasCodexQuotaHeadroom(config, id, selectionOptions)), + eligible.filter(id => hasCodexQuotaHeadroom(config, id, selectionOptions, now)), selectionOptions, + now, ); } @@ -1566,6 +1613,7 @@ function releaseDrainedCodexAccountPin( CodexAccountUsabilityOptions, "nativeMainSelectionOnly" | "isMainAccountTokenLive" >, + now: number = Date.now(), ): void { const pinned = pinnedCodexAccountId(config); if (pinned === undefined) return; @@ -1580,7 +1628,7 @@ function releaseDrainedCodexAccountPin( // is readable. Cached reauth and configured pause state were handled above. if (pinned === MAIN_CODEX_ACCOUNT_ID && selectionOptions?.nativeMainSelectionOnly === true) return; const drained = !isCodexAccountUsable(config, pinned, selectionOptions) - || !hasCodexQuotaHeadroom(config, pinned, selectionOptions); + || !hasCodexQuotaHeadroom(config, pinned, selectionOptions, now); if (!drained) return; clearCodexAccountPin(config); saveConfigPreservingClaudeCode(config); @@ -1600,6 +1648,7 @@ function applyQuotaAutoSwitch( const activeUsage = computeCodexUsageScore( quota, getPoolAccountPlanForSelection(config, active, selectionOptions), + now, ); // Unknown usage is not evidence that a user's explicit selection crossed the // threshold. Wait for quota priming instead of rotating among guesses. @@ -1633,7 +1682,7 @@ function isHealthySharedCodexSelection( selectionOptions: CodexAccountUsabilityOptions | undefined, ): boolean { return isCodexAccountSelectable(config, accountId, now, quotaScope, selectionOptions) - && hasCodexQuotaHeadroom(config, accountId, selectionOptions) + && hasCodexQuotaHeadroom(config, accountId, selectionOptions, now) && !shouldFailover(config, accountId, now); } @@ -1720,6 +1769,7 @@ function previewReusableAffinityAccount( const usage = computeCodexUsageScore( getAccountQuota(entry.accountId), getPoolAccountPlanForSelection(config, entry.accountId, selectionOptions), + now, ); if (!isUnknownUsage(usage) && usage >= threshold) { const best = pickLowerUsageAccount( @@ -1755,6 +1805,7 @@ function reevaluateAffinityQuota( ? computeCodexUsageScore( getAccountQuota(entry.accountId), getPoolAccountPlanForSelection(config, entry.accountId, selectionOptions), + now, ) : 0; const overThreshold = threshold > 0 && !isUnknownUsage(usage) && usage >= threshold; @@ -1848,6 +1899,7 @@ export function previewCodexAccountForRequest( const usage = computeCodexUsageScore( getAccountQuota(active), getPoolAccountPlanForSelection(config, active, selectionOptions), + now, ); if (!isUnknownUsage(usage) && usage >= threshold) { active = pickLowerUsageAccount(config, active, usage, now, quotaScope, selectionOptions); @@ -1887,7 +1939,7 @@ export function resolveCodexAccountForThreadDetailed( // revive after quota resets. Independent model scopes must never persist a // change to shared routing state. if (!isIndependentCodexQuotaScope(quotaScope)) { - releaseDrainedCodexAccountPin(config, sharedStateSelectionOptions(selectionOptions)); + releaseDrainedCodexAccountPin(config, sharedStateSelectionOptions(selectionOptions), now); } const sharedActiveBeforeSelection = getEffectiveActiveCodexAccountId(config); const preserveSharedSelectionForModelDetour = modelScopedSelection && ( @@ -1945,7 +1997,7 @@ export function resolveCodexAccountForThreadDetailed( && isCodexAccountSelectable(config, entry.accountId, now, quotaScope, selectionOptions); const failoverReady = shouldFailover(config, entry.accountId, now); const healthyForSharedAffinity = selectableForSharedState - && hasCodexQuotaHeadroom(config, entry.accountId, sharedSelectionOptions) + && hasCodexQuotaHeadroom(config, entry.accountId, sharedSelectionOptions, now) && !failoverReady; if ( selectableForRequest @@ -2041,7 +2093,7 @@ export function resolveCodexAccountForThreadDetailed( sharedSelectionOptions, ); const activeHealthyForSharedSelection = activeSelectableForSharedState - && hasCodexQuotaHeadroom(config, active, sharedSelectionOptions) + && hasCodexQuotaHeadroom(config, active, sharedSelectionOptions, now) && !shouldFailover(config, active, now); if (!isCodexAccountSelectable(config, active, now, quotaScope, selectionOptions)) { const fallback = pickLowestUsageCodexAccount(config, active, now, quotaScope, selectionOptions); diff --git a/src/codex/subagent-model-fallback.ts b/src/codex/subagent-model-fallback.ts index 5b838f9493..27eea1e53d 100644 --- a/src/codex/subagent-model-fallback.ts +++ b/src/codex/subagent-model-fallback.ts @@ -226,7 +226,10 @@ export function isNativeModelQuotaExhausted( const resolvedAccountId = resolveRouteFallbackAccountId(route, config, accountId); if (!resolvedAccountId) return false; const quota = getAccountQuota(resolvedAccountId); - const usage = computeCodexUsageScore(quota, getPoolAccountPlan(config, resolvedAccountId)); + // Subagent fallback reads the same score, so a stale terminal reading would push + // subagents off a native model whose window has already reset. Thread the caller's clock + // rather than letting the scorer read wall time - the two would silently diverge. + const usage = computeCodexUsageScore(quota, getPoolAccountPlan(config, resolvedAccountId), now); if (usage >= CODEX_UNKNOWN_USAGE_SCORE) return false; return usage >= quotaThreshold(config); } diff --git a/tests/codex-routing.test.ts b/tests/codex-routing.test.ts index 1ec9f8cfda..75a3cebe64 100644 --- a/tests/codex-routing.test.ts +++ b/tests/codex-routing.test.ts @@ -47,6 +47,7 @@ import { updateAccountQuota, } from "../src/codex/auth-api"; import { CODEX_UNKNOWN_USAGE_SCORE, isCodexQuotaExhausted } from "../src/codex/quota"; +import { setCodexAccountPriority } from "../src/codex/account-priority"; import { MAIN_CODEX_ACCOUNT_ID } from "../src/codex/main-account"; import { routeModel } from "../src/router"; import { consumeForInspection } from "../src/server/relay"; @@ -138,6 +139,98 @@ describe("codex routing", () => { expect(computeCodexUsageScore({ weeklyPercent: 40, shortPercent: 0 })).toBe(40); }); + test("a full burst window scores terminal while it is still in force (#3029)", () => { + // A FULL short window is not an optimistic guess about an unobserved long window - it + // is a direct observation that the account cannot serve a request right now. Leaving it + // unknown keeps the account selectable and suppresses auto-switch, which is exactly the + // pool wedge #3029 reports. + const now = 1_700_000_000_000; + expect(computeCodexUsageScore({ shortPercent: 100, shortResetAt: now + 60_000 }, undefined, now)).toBe(100); + + // Freshness is the other half. getAccountQuota performs no expiry check, partial + // updates carry the old short tuple forward, and disk hydration accepts a persisted + // reading for hours - so a reset window must go back to unknown, or #3029 is simply + // inverted into a recovered account that stays excluded. + expect(computeCodexUsageScore({ shortPercent: 100, shortResetAt: now - 60_000 }, undefined, now)) + .toBe(CODEX_UNKNOWN_USAGE_SCORE); + // No resetAt at all cannot be aged, so it stays unknown: a wrongly-selected account + // fails one request, a wrongly-excluded one is invisible until someone reads the pool. + expect(computeCodexUsageScore({ shortPercent: 100 }, undefined, now)).toBe(CODEX_UNKNOWN_USAGE_SCORE); + // Still narrow: a non-terminal short-only reading is unchanged. + expect(computeCodexUsageScore({ shortPercent: 99, shortResetAt: now + 60_000 }, undefined, now)) + .toBe(CODEX_UNKNOWN_USAGE_SCORE); + }); + + test("a terminal burst window is read in either unit (#3029)", () => { + // normalizeResetAt does not scale, and the GUI disambiguates by magnitude at read time, + // so both seconds and milliseconds reach storage. A comparison written against one + // assumption is off by 1000x against the other - and in the seconds-read-as-ms + // direction every terminal reading looks like it reset in 1970, which is a fix that + // passes its own test and does nothing. + const now = 1_700_000_000_000; + expect(computeCodexUsageScore({ shortPercent: 100, shortResetAt: now + 60_000 }, undefined, now)).toBe(100); + expect(computeCodexUsageScore({ shortPercent: 100, shortResetAt: (now + 60_000) / 1000 }, undefined, now)).toBe(100); + }); + + test("a live full burst window moves selection off the account (#3029)", () => { + // The scorer assertions above prove the value; this proves the pool acts on it. A + // clock far from wall time is the point: a fixture whose now matches Date.now() cannot + // tell a threaded clock from one that was dropped somewhere in the helper chain. + const now = 1_700_000_000_000; + const config = makeConfig({ activeCodexAccountId: "a" }); + + // A is full for the next hour, recorded in SECONDS. B has ordinary headroom. + setAccountQuotaFromParsed("a", { shortPercent: 100, shortResetAt: (now + 3_600_000) / 1000 }); + updateAccountQuota("b", 10); + expect(resolveCodexAccountForThread("thread-terminal-new", config, now)).toBe("b"); + + // Same pool, but a thread already BOUND to A. Bind it while A is cool, so the rebind + // below is a real transition rather than a first selection that happened to pick B. + clearAccountQuota("a"); + clearAccountQuota("b"); + updateAccountQuota("a", 10); + updateAccountQuota("b", 20); + const bound = makeConfig({ activeCodexAccountId: "a" }); + expect(resolveCodexAccountForThread("thread-terminal-bound", bound, now)).toBe("a"); + // A's burst window fills, in MILLISECONDS this time so both units run through the real + // selection path. The bound thread must move rather than keep an account that cannot + // serve it. + clearAccountQuota("a"); + setAccountQuotaFromParsed("a", { shortPercent: 100, shortResetAt: now + 3_600_000 }); + expect(resolveCodexAccountForThread("thread-terminal-bound", bound, now)).toBe("b"); + + // And once the window resets, A stays selected. B carries KNOWN headroom here on + // purpose: with both accounts unknown, A would be kept by default and the assertion + // would hold even against a freshness-blind scorer. Against one, expired-A scores 100 + // and the request moves to B. + clearAccountQuota("a"); + clearAccountQuota("b"); + setAccountQuotaFromParsed("a", { shortPercent: 100, shortResetAt: now - 60_000 }); + updateAccountQuota("b", 20); + const recovered = makeConfig({ activeCodexAccountId: "a" }); + expect(resolveCodexAccountForThread("thread-terminal-recovered", recovered, now)).toBe("a"); + }); + + test("the priority tier reads the request clock, not wall time (#3029)", () => { + // selectPriorityTier consults hasCodexQuotaHeadroom only when the pool carries + // DIFFERENT priorities, so a clock dropped in that lambda is invisible to an ordinary + // pool. Higher numbers run earlier, so A (2) outranks B (1). + // + // The clock is historical, well before wall time. A's window is full for an hour after + // THAT instant, so it is live against the request clock and long expired against + // Date.now(). With the correct clock the tier sees A drained and descends to B; reading + // wall time makes A look unknown, the tier keeps it, and fill-first hands back A. + const now = 1_700_000_000_000; + const config = makeConfig({ activeCodexAccountId: "a", accountPoolStrategy: "fill-first" }); + setCodexAccountPriority(config, "a", 2); + setCodexAccountPriority(config, "b", 1); + + setAccountQuotaFromParsed("a", { shortPercent: 100, shortResetAt: now + 3_600_000 }); + updateAccountQuota("b", 20); + + expect(resolveCodexAccountForThread("thread-priority-terminal", config, now)).toBe("b"); + }); + test("exact-account failures record health without rotating the active Pool account", () => { const transient = makeConfig({ upstreamFailoverThreshold: 1, activeCodexAccountId: "a" }); const transientThread = "fixed-transient-thread"; diff --git a/tests/subagent-model-fallback.test.ts b/tests/subagent-model-fallback.test.ts index f37a07ef4a..75b2359ad5 100644 --- a/tests/subagent-model-fallback.test.ts +++ b/tests/subagent-model-fallback.test.ts @@ -21,7 +21,7 @@ import { } from "../src/codex/subagent-model-fallback"; import { saveCodexAccountCredential } from "../src/codex/account-store"; import { clearAccountNeedsReauth, markAccountNeedsReauth } from "../src/codex/account-runtime-state"; -import { clearAccountQuota, updateAccountQuota } from "../src/codex/quota"; +import { clearAccountQuota, setAccountQuotaFromParsed, updateAccountQuota } from "../src/codex/quota"; import { canAcquireCodexQuotaProbeLease, canAcquireCodexQuotaScopeProbeLease, @@ -734,6 +734,36 @@ describe("subagent model fallback chain", () => { expect(isSubagentModelUnavailable("gpt-5.6-sol", config, "pool-a")).toBe(false); }); + test("a full burst window makes the native model exhausted only while it holds (#3029)", () => { + // Subagent fallback reads the same usage score as pool selection, so a stale terminal + // reading pushes subagents off a native model whose five-hour window has already reset. + // The clock is passed explicitly and deliberately far from wall time: a fixture whose + // clock matches Date.now() cannot tell a threaded clock from a substituted one. + const now = 1_700_000_000_000; + const config = cfg({ + activeCodexAccountId: "pool-a", + providers: { + openai: { + adapter: "openai-responses", + baseUrl: "https://chatgpt.com/backend-api/codex", + authMode: "forward", + codexAccountMode: "pool", + }, + }, + subagentModelFallback: ["kimi/k3"], + }); + + resetSubagentModelFallbackStateForTests(); + clearAccountQuota("pool-a"); + setAccountQuotaFromParsed("pool-a", { shortPercent: 100, shortResetAt: now + 60_000 }); + expect(isNativeModelQuotaExhausted("gpt-5.6-sol", config, "pool-a", now)).toBe(true); + + resetSubagentModelFallbackStateForTests(); + clearAccountQuota("pool-a"); + setAccountQuotaFromParsed("pool-a", { shortPercent: 100, shortResetAt: now - 60_000 }); + expect(isNativeModelQuotaExhausted("gpt-5.6-sol", config, "pool-a", now)).toBe(false); + }); + test("openai-direct/gpt-5.5 is accepted as encrypted-task fallback when canonical", () => { resetSubagentModelFallbackStateForTests(); updateAccountQuota("pool-a", 95, undefined, 20); From 330470e740a02b28c51afa39ead4b45fc430f341 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 08:01:14 +0900 Subject: [PATCH 32/42] fix(stop): typed stop outcome so a history-only failure does not abort the update (#3008) (#3118) * fix(update): tell a history-only stop failure from a real one Closes #3008. handleStop sets a failure code AFTER history restoration - that is, after the proxy and service are already down - so a failed Codex-history cleanup was indistinguishable from a proxy that refused to die. The update aborted with the service stopped, no listener, and the old package still installed. Its own history warning was unreachable, sitting behind the guard that never let control get there. restoreSharedClientStateAfterStop now reports the two failure kinds separately, reading the artifact states it already collects: a config or catalog failure removes state a client depends on and is a real teardown failure, while a history failure leaves the runtime consistent with a manifest retained for review. The signal has to cross a spawnSync boundary, so it is an exit code rather than a type. 79 sits above the sysexits block, below 128+signal, and outside every code this CLI already emits - 0, 1 and 130 from src/cli/index.ts, plus 2, 4 and 64 from dispatch. It lives in a plain-ESM module because bin/ocx.mjs cannot import TypeScript, and inlining the number twice is how the two ends drift. Both updaters decode it. Fixing only the Bun path would have left the reported lane broken: the dashboard npm update runs through the Node launcher, which carries its own independent guard. * fix(update): probe the endpoint, and never call a failed service stop history-only Review found four ways the classification was still too generous. stopServiceIfInstalled collapses "no service installed" and "a service refused to stop" into the same false, and handleStop treated both as fine. A manager that would not stop can respawn the proxy, so a later history failure emitted the proceed code while a supervisor was still live. stopServiceIfInstalledDetailed distinguishes absent/stopped/failed, and only absent or stopped may reach the history-only path. Both updaters gated only on exit status and PID/runtime files. The plan said plainly that absent records are weak evidence - a crashed-but-listening proxy leaves none - and required an identity probe. Both lanes now ask the captured endpoint before replacing files. The Node launcher gets a synchronous probe because runNpmSelfUpdate is not async; it speaks node:http rather than fetch, since a child spawned from a blocked event loop can have its fetch aborted before dispatch and report the same "not live" as a dead port. It fails open: a probe that cannot answer must not block an update on its own uncertainty. The claimed launcher regression was vacuous - it spawned a stub and checked the stub's own exit code, never touching either updater. It is replaced by one that evaluates the shared decision expression over the whole status domain, and by a real probe test against an out-of-process listener. tests/grok-lifecycle.test.ts was already red against this change and I had not run it. It now asserts the {historyOnly, other} contract. Also corrects a comment naming restart and the tray coordinator as handleStop callers; both go through handleProxyRestart. * fix(update): stop guessing when the liveness probe cannot answer Third review round, and the probe I added was the problem. It failed open: a timeout, a spawn failure, or a body it did not recognize all read as "not live". The reviewer reproduced a listener that accepts connections and withholds /healthz, which the probe called dead - and that is precisely the state where replacing package files is most dangerous. It also rejected the legacy OpenCodex health body that src/server/proxy-liveness.ts accepts. The probe is now tri-state. Only a refused connection or a definitive non-OpenCodex answer earns "dead"; a timeout, an unparseable body, an unexpected error code, or a child that produced nothing is "unknown". Both lanes abort on anything other than "dead". The TypeScript lane keeps proxyIdentityAt and adds the probe, because a null from that helper covers refusal and timeout alike. The npm lane also probed the wrong address: it captured only the port and assumed 127.0.0.1, so a proxy bound to ::1 or a specific interface answered nobody. The hostname now travels with the port from the runtime record or config. And the Windows service stop still could not report failure. A schtasks query that threw was read as absence, and stopWindows swallows a non-benign /end failure so the caller could never see it. It now uses the existing tri-state probeWindowsSchedulerTask - unknown counts as failed - and a checked stop that surfaces the result. * fix(update): one post-stop decision, and an identity rule that matches Fourth review round, and the probe's own classification was wrong twice. Its "is this OpenCodex" test accepted any body carrying pid, ok, version or status, so a foreign server exposing /healthz read as our proxy; and it classified every parsed non-200 as dead, so our own 500 read as absence. It now mirrors isOpencodexHealthz exactly - canonical service field, or the pre-identity shape with status/version/uptime - and any status other than 200 is unknown, because an endpoint answering without identifying itself is not evidence of absence. The npm lane still probed the wrong address in two cases: a legacy runtime record with a port and no hostname never consulted config, and a bracketed IPv6 literal was passed through as a host. Port and hostname now resolve independently, and wildcard and bracketed forms normalize. The decision itself moves into src/update/stop-decision.mjs, which both updaters import. The reviewer's point stands: a test of a locally reimplemented predicate stays green while either lane drifts, and one lane drifting is exactly how this shipped. The matrix now exercises the real function - clean stop, history-only, five other statuses plus a signal kill, surviving runtime state, live proxy, and unknown liveness - and asserts neither lane keeps a private copy of the rule. * fix(update): verify the respawn window, and normalize the host once Fifth review round closed the last two. Host normalization was per-lane, so the npm launcher got it and the TypeScript updater passed a bracketed IPv6 literal straight to node:http. That answers nothing, reads as "unknown", and aborts a healthy update - leaving the service down, which is the failure shape this issue is about. It moves into probeProxyLiveness, so both lanes get it from one place: wildcard binds dial loopback, bracketed literals are unwrapped, empty falls back. And a stopped Windows scheduler was being trusted as a stopped proxy. killWindowsSchedulerWrappers is explicitly best-effort and the :loop wrapper respawns its child after about five seconds, so an updater probing immediately can see the dead interval and start replacing files just before the proxy returns. handleStop now polls proxyStillLiveAfterStop with canRespawn across that window whenever a scheduler was stopped. A survivor is an ordinary failure, not history-only, and it also blocks shared teardown - restoring client config while the proxy runs leaves both pointing at each other. The reviewer scoped that one into this phase rather than a follow-up, and that is right: without it exit 79 can authorize a replacement without proven service shutdown, which is the whole property the code exists to establish. * fix(service): charge the respawn wait only to the backend that respawns Sixth review round: I gated the restart-window poll on "a service stopped" and passed canRespawn unconditionally, so launchd, systemd and WinSW each paid a seven-second wait on every ocx stop. That contradicts the helper's own contract and would be a regression in ordinary use. Only Task Scheduler needs it: schtasks /end ends the task instance while the cmd :loop wrapper survives and respawns its child seconds later (#764). The other backends are down when they report stopped. ServiceStopOutcome gains "stopped-respawnable" so the capability travels with the outcome instead of being re-derived at the call site, and handleStop waits only for that value. stopServiceIfInstalled keeps its boolean meaning by accepting both stopped forms. * fix(stop): defer shared teardown until the proxy is proven down Seventh review round found the respawn guard arriving too late to matter. handleStop calls stopProxy first, which POSTs /api/stop, and that handler already restores native Codex and strips the Grok fence. So by the time the scheduler verification ran, a surviving wrapper had ALREADY lost its client config - and ownershipBlocked could only prevent the parent from doing it a second time. POST /api/stop takes deferSharedTeardown=1, and ocx stop sends it. The proxy still drains and exits; it just leaves shared config alone, and this process restores it after confirming no survivor. A direct /api/stop caller sends nothing and keeps the self-contained behaviour it has today. handleUninstall is unchanged for the same reason. Also drops the "won't respawn" half of the service-stopped message: a stopped Task Scheduler can still respawn through its wrapper, which is precisely what the verification below it settles. * fix(stop): make the deferred teardown a durable obligation Seventh review round found the deferral was a bare query flag, which cannot express an obligation. Two windows followed from that. First, ownership was unprovable. Any authenticated caller could append deferSharedTeardown=1 and exit, and the proxy would go down leaving native Codex and the Grok fence pointed at a proxy that no longer exists. Nothing on disk recorded that a restore was still owed. Second, the legitimate parent had the same hole. If `ocx stop` died after the child exited but before restoreSharedClientStateAfterStop(), the config stayed routed at a dead proxy - a window the child-owned flow never had. So the deferral is now a receipt. `ocx stop` claims pending-teardown.json before asking for a deferred stop, the route honours the flag only when it can see that receipt, and the claim is cleared only after this process has restored the shared config itself. A receipt whose owner is dead is an abandoned obligation: the next `ocx stop` finishes it, but only on the path that already proved no proxy is live. The deferred response no longer claims "native Codex restored" - it says the teardown was deferred, and carries sharedTeardown: deferred. The teardown itself moved to src/server/stop-teardown.ts. The route schedules process.exit 200ms after answering, so the inline version could not be called from a test, which is why the previous round's regression could only read source text. tests/stop-deferred-teardown.test.ts now calls the real functions: the URL the graceful-stop client builds, the restores that do or do not run, the wording of each response, and the receipt's owner guard. * fix(stop): bind the deferral to a receipt identity, not to presence Eighth review round found five ways the receipt was still not an obligation. The clear was the worst of them, and it was reproducible: recovery re-read the file after restoring and passed THAT receipt's owner pid to the clear. A concurrent stop writing its own receipt in between meant this run deleted a live obligation. The receipt now carries a nonce, the snapshot is taken before this run claims anything, and a clear names the exact receipt it read. The same presence-versus-identity error made the deferral itself unowned. Any authenticated caller could set deferSharedTeardown=1 while somebody else's receipt happened to be on disk, get the deferral, and exit owning no recovery. The request now carries the nonce and the route requires an exact match. Abandoned recovery could also run under a proxy that was merely unresponsive: findLiveProxy collapses a timeout and a malformed answer into the same null as a refused connection, and on a stop with no scheduler nothing else re-checked. It is now gated on the tri-state probe answering a definitive "dead". Neither updater woke on the receipt. After a parent crash the service, pid and runtime records can all be absent while the shared config still points at a proxy that is gone, so a retrying update installed straight over the pending recovery. Both gates now include it. And a corrupt receipt read as absence, which discarded the one fact recovery needs: an obligation is outstanding and its owner can no longer be identified. The read is now missing/valid/invalid; invalid is outstanding, is recoverable, and clears only through an explicit force by a caller that just discharged it. * fix(stop): gate the restore on the inherited obligation, not just label it Ninth review round found the previous round's gate was not one. It computed inheritedRecoverable, warned when the probe could not confirm the proxy down, and then fell into the same restore anyway - so a live-but-unresponsive proxy still had its client config torn out. The restore is now behind restoreBlocked, there is exactly one call site, and an unconfirmed inherited obligation fails the stop and preserves the receipt instead of discharging it. The recovery probe was also asking the wrong endpoint. A crashed owner leaves no runtime-port record, so the probe fell back to the configured port, which is not where a proxy started with an explicit --port is listening: it refuses, recovery reads that as dead, and the live proxy loses its config. The receipt now carries the endpoint its owner was stopping, and an obligation that cannot name one is never recoverable. Two identity holes remained. Force-clearing an unparseable receipt recreated the concurrent-clear race the nonce had just closed - against a valid receipt this time - so an invalid read now carries a hash of the bytes it read and clears only against those exact bytes. And every read error mapped to "missing", including a directory sitting where the receipt belongs, which hid an outstanding obligation; only ENOENT is absence now. tests/update-stop-first.test.ts was red against the previous commit's updater gates and is updated. The new wiring test asserts the gate blocks rather than warns: one restore call site, reached only through restoreBlocked, with the failure path preserving the receipt. * fix(stop): put the nonce in the filename so a clear cannot race Tenth review round found the receipt was still one shared file, so clearing it was read-compare-unlink across three syscalls: a concurrent stop replacing the file between the compare and the unlink meant this run deleted an obligation it never owned. Locking would serialize that; naming removes it. Each claim now lives at pending-teardown-<nonce>.json, so unlink names one specific obligation and cannot reach another. Two concurrent stops hold two receipts, which is the truth of the situation, and handleStop recovers over the whole set. An unreadable receipt was worse than useless. It names no endpoint, so nothing can prove its proxy down, so it could never be discharged - while both updater gates treated it as a reason to run the stop that would fail on it every time. That is an update that can never proceed. Such a receipt is now quarantined under a name the scan ignores: the evidence is kept for the operator, the restore it stood for happens, and if it cannot even be moved the stop says so and fails rather than pretending. The claim could also silently not happen. Both stop paths derived the endpoint separately from stopProxy, so a proxy with no runtime record was hard-killed with no receipt at all - the parent-crash window, reopened on the one path where the stop is least graceful. There is now a single resolved stop target feeding both the receipt and the request, and stopProxyGracefully uses that snapshot rather than re-reading. Two smaller ones: a failed unlink was swallowed, so a receipt surviving its own discharge would re-trigger recovery forever; it is reported now. And a receipt whose body names a different nonce than its filename is invalid, so an edited body cannot claim an identity the name does not carry. * fix(stop): share the receipt naming rule, and stop leaking obligations Eleventh review round found that moving receipts to one file per claim broke the lane that could not see the change. bin/ocx.mjs kept checking the retired pending-teardown.json, so the npm/dashboard updater silently stopped noticing every outstanding obligation. The naming rule now lives in one shared .mjs both lanes import, and a test asserts the launcher no longer names the old file. Three leaks followed from the same area. The orphan path already knew where the proxy answered - findLiveProxy returns port and hostname - and threw it away to re-read the runtime record that had usually gone missing in the first place. It hands that endpoint over now. When nothing can be resolved at all the stop still proceeds undeferred, but says so: that case keeps the pre-3008 window and should not be silent. Inherited receipts were evaluated only when this run claimed none of its own, so an ordinary stop of a live proxy cleared its own nonce and left every older abandoned receipt behind. They accumulated with each run. Inherited obligations are now evaluated either way, and every nonce this stop proved discharged is released together with its own. An unreadable receipt was quarantined before the restore ran, which erased it from every future scan even when a sibling receipt blocked the restore, the restore failed, or the process died first. It now fails the stop with an explicit manual step - it names no endpoint, so nothing can prove its proxy down, and the evidence requirement that applies to valid receipts applies to it too - and is set aside only after the outcome is known. * fix(stop): keep an unreadable obligation enforcing, and always leave a receipt Twelfth review round found that quarantine had quietly become an escape hatch. An unreadable receipt blocked the current stop, then got renamed to a suffix both updater scanners ignored - so the very next update saw nothing outstanding and installed over a teardown that never ran. Removing enforcement to avoid a retry loop turns "we could not tell" into "it is fine", which is the failure this mechanism exists to prevent. Quarantine now renames to pending-teardown-<nonce>.unreadable.json, which the recovery loop stops reading but pendingTeardownOutstanding still counts. Only a human removing the file ends it, and the message says so. Two durability holes on the stop paths. A tracked stop that could resolve no endpoint warned and then hard-killed with no receipt at all - and on that path stopProxy goes straight to the kill ladder, so no child teardown runs either. A warning does not make a hard kill durable. Every stop now claims a receipt; when nothing is discovered it records the configured listen address, which is the same address a later recovery probe would ask about. That endpoint is deliberately not used to direct the stop request itself: it is a good enough guess to record an obligation against, not to POST to. And a live orphan with no resolvable pid fell through as "No running proxy found", which purged the state records and restored shared config underneath a proxy that was still serving. findLiveProxy returning a proxy with pid null is now its own failure with the teardown blocked, distinct from returning nothing. * fix(stop): record endpoint provenance, and re-check obligations after the stop Thirteenth review round found the configured-address fallback had quietly become evidence. A stop that could resolve no endpoint recorded the configured listen address, and a later recovery probed exactly that address and read a refused connection as proof the proxy was down. For a proxy started with an explicit --port and respawned there by a wrapper, that is a restore underneath a live server - the failure the receipt exists to prevent, reached through the receipt itself. Receipts now carry endpointSource: exact means the runtime record or a successful probe, guessed means the configured address. Only exact authorizes automatic recovery; a guessed receipt fails closed with the manual steps. A receipt with no provenance at all is invalid, so nothing written by the previous format is trusted. The same function also read the runtime record twice, so the receipt could name the guess while the request went to an endpoint that appeared in between. It resolves once now. Separately, quarantine still did not actually block an update. Both lanes checked for outstanding obligations only before spawning ocx stop, and a quarantined receipt lets that stop succeed - there is nothing left to stop - so the retry sailed through and installed anyway. The shared post-stop decision now takes teardownOutstanding and returns teardown-outstanding, and both lanes pass it and print the manual remediation. Absent, the field keeps the previous behaviour. * fix(stop): say what quarantine actually does Fourteenth review round found the quarantine message claiming the opposite of the contract it was implementing. It told the operator the set-aside receipt "no longer blocks an update" - but isAnyTeardownObligationFileName counts the renamed file on purpose, and both post-stop decisions now abort while it is there. An operator following that message would wait for an update that keeps refusing, with no idea why. It now says the receipt still blocks 'ocx update', that ocx stop has not restored on its behalf, and what to do: confirm nothing is running, run 'ocx restore', then delete the printed path. The comment above it no longer claims a condition the code does not check - the skip is on ownershipBlocked, because a foreign service still owns that state - and a wiring assertion pins the wording so it cannot drift back. * fix(restore): finish the Grok half when the Codex half is a no-op Fifteenth review round followed the manual recovery instructions this branch prints and found they do not work. The deferred-teardown paths tell an operator to run 'ocx restore' and then delete the receipt. But when Codex is already OFF and native, restore takes an early success return that never reaches stripGrokConfig - so a Grok-only deferred teardown survives, the operator deletes the obligation that would have caught it, and updates unblock while Grok still points at a proxy that is gone. That early return now strips the fence too, folds the outcome into the message and the --json envelope, and exits nonzero when the cleanup fails. The Codex half being a no-op never said anything about the Grok half. tests/restore-completes-shared-teardown.test.ts drives the real CLI through dispatchCommand with Codex off, a managed Grok fence present, and asserts the fence is gone, the envelope names it, and a home with no Grok config still succeeds quietly. * fix(restore): strip the fence on every path, and stop reading a failed scan as empty Sixteenth review round found the previous fix covered one early return and not the other. `ocx restore --json` returned its envelope before stripGrokConfig() ran, so a non-clean Codex home plus a Grok fence produced exit 0 and success: true while Grok still targeted the dead proxy - and `ocx eject --json` inherits it, being the same runner. The strip now happens before either output. The Codex artifact schema is unchanged; the Grok outcome folds into success and message so a machine caller cannot read a half teardown as done. The other one was worse because it was pinned by a test I wrote. Both lanes treated ANY readdir failure as "no obligations" - a permission error, an I/O error, a file where the home should be - so a scan that could not see an outstanding receipt reported none, and an update installed over a teardown that never ran. Only ENOENT is empty now. listPendingTeardowns surfaces an unreadable home as one invalid obligation so handleStop blocks on it like any other rather than restoring over an unread directory. The new regressions assert the fence file itself rather than the message, since a message can claim a cleanup that never happened, and cover the forward path, eject --json, and a Grok strip that fails. * fix(stop): make every teardown path report the whole outcome Seventeenth review round moved outward from the deferral to the paths around it, and found four places where a partial teardown reported success. POST /api/stop called the boolean service helper and ignored the result. That helper collapses "the manager refused to stop" into the same false as "no service installed", so the route could restore shared config and exit while a manager that would respawn the proxy was still running. It consumes the detailed outcome now: "failed" answers 409 without touching shared config, and "stopped-respawnable" answers 409 unless the caller holds a teardown receipt, because this process cannot verify its own post-exit respawn window - only the parent ocx stop can, which is what the receipt exists for. The same route decided success from the native restore alone and appended the Grok failure as text, so native success plus a failed strip returned success: true. Success now requires both halves. The existing test failed both at once, which masked it; the matrix case is added. ocx service stop and ocx service uninstall logged their restore and strip failures and exited 0, so a script could not tell a finished teardown from one that left Grok aimed at a stopped proxy. Both set a failure code now, matching what the full ocx uninstall already did. And the previous commit's unreadable-home marker was a fabricated receipt with an all-zero nonce, which handleStop would hand to the quarantine path - able to rename a real receipt that happened to carry it, and otherwise printing a manual-removal path for a file that does not exist. A scan failure is its own state now: it blocks, it is never quarantined or cleared, and it asks for the directory to be fixed. * fix(stop): refuse a respawnable backend before touching it, not after Seventeenth round's route guard was itself a regression, and the eighteenth caught it. The check ran AFTER stopServiceIfInstalledDetailed(), so a bare dashboard Stop on Windows Task Scheduler ended the task, then returned 409 - leaving the proxy running with its manager stopped, which is worse than either outcome it was choosing between. And the dashboard sends a bare request on every backend, so this was the documented Stop button, broken. installedServiceCanRespawn() answers the same question without stopping anything: it probes the scheduler task and treats an unanswerable probe as risk rather than absence. The route asks first, refuses with code respawnable_service and "Nothing was changed", and only then touches the manager. The docs now say the dashboard refuses on that backend and why. Second: the delayed exit still considered only the drain, so a proxy that drained cleanly but failed its native or Grok restore exited 0 - telling a supervisor the stop was clean while client config still pointed at it. The exit is now shutdownSucceeded && teardown.success, and the assertion that pinned the incomplete expression is updated with it. * fix(stop): only a proven absence is safe, and say so in every locale Nineteenth review round found the respawn pre-check testing for the wrong thing. probeWindowsSchedulerTask returns "unknown" as an ordinary value when its queries fail - it does not throw - so testing status === "present" let that case straight through: the route entered the detailed stop, killed scheduler wrappers, and only then returned 409. The mutate-then-refuse defect, back for a third time through a different door. The predicate now proceeds only on a proven "absent" and takes injected probe/platform so the four cases can be tested for real. The old assertion passed by matching an unrelated early exit in the catch; it is replaced with present/unknown/throw/absent plus the two non-Windows platforms. The docs were also still contradicting themselves. The management API reference listed only the ownership 409, the lifecycle reference still said the dashboard does the same thing as ocx stop, and every translated locale carried the old unconditional claim. All eight management-api locales now name respawnable_service, and the lifecycle and dashboard pages say what the dashboard does on that backend and that nothing is changed when it refuses. * fix(stop): give an unreadable scheduler state its own answer, not a loop Twentieth review round found the refusal was circular for the one operator who most needed it. installedServiceCanRespawn collapsed present and unknown into one boolean, so a Windows host whose schtasks query is broken got respawnable_service and was told to run ocx stop - and that command maps the same unknown probe to a stop failure, so it could not finish either. There was no way out. The predicate returns none | respawnable | unknown now. Unknown gets its own 409 service_state_unknown that names the actual remedy: run ocx service status to see the query error, repair Task Scheduler access, retry. It deliberately does not mention ocx stop. The docs sweep from the previous commit was also half-done. Six management-api locales carried the new clause twice because the script ran twice, three lifecycle references (zh-cn, ru, tr) still claimed unconditional dashboard parity, and three dashboard guides (fr, ja, tr) still said Stop always succeeds. All are deduplicated and updated, and every locale now lists service_state_unknown alongside the other two 409s. * fix(stop): carry the unreadable-scheduler diagnosis into ocx stop too Twentieth round fixed the API's circular advice and left the CLI with the same problem underneath it. stopServiceIfInstalledDetailed folded an unanswerable scheduler probe into the generic failed, so ocx stop printed only "the manager did not stop" - the wrong thing to go looking for - while the API was telling that operator to run ocx stop. ServiceStopOutcome gains state-unknown, and both surfaces now say the query could not be read and point at ocx service status. The precedence moved into classifyWindowsServiceStop so it can be tested by calling it: a readable failure outranks an unreadable state, and an unreadable state outranks success, because a scheduler we cannot see may still respawn the proxy. My first attempt at this test read source text and did not fail when the mapping was reverted, which is exactly the failure mode this review has caught repeatedly. Docs: the Japanese management row was malformed - my dedup script had mangled a row with a different column count, leaving a doubled clause, a cell outside the table, and trailing whitespace that failed git diff --check. It is rewritten by hand. The secondary POST /api/stop table in all eight dashboard guides now carries the two refusal codes as well. * fix(uninstall): stop reading an unreadable service state as "not installed" Twenty-first review round found the last place the boolean collapse still hid a live manager. stopServiceIfInstalled returns false for "not installed", "refused to stop" and "state could not be read" alike, and handleUninstall read that false as absence - printed "service stopped: not installed", then restored native Codex and stripped the Grok fence under a proxy that may still have been running and managed. It did exit nonzero afterwards, but only after doing the unsafe teardown and telling the operator something untrue. Uninstall consumes ServiceStopOutcome directly now. Absent is still "not installed"; failed and state-unknown each throw with their own explanation. A failure in the service stop, the proxy stop, or the service removal clears serviceTeardownProven, and the shared restores run only when that holds. When they are skipped it is recorded as a failure, not a silent pass, with the follow-up command to run once the blocker is resolved. * fix(uninstall): find the orphan, and make the authorization rule callable Twenty-second review round found uninstall trusting a missing pid file as proof that nothing was serving. ocx stop falls back to identity-checked discovery for exactly that case - a proxy outliving its record - and uninstall did not, so a live orphan left the teardown authorized: shared config restored, local state removed, success reported, proxy still running. It discovers now, and a live proxy with no resolvable pid throws rather than being read as absence. The rule itself moved into src/cli/uninstall-plan.ts so it can be exercised by calling it. My previous test read the source and could not execute the failure permutations, which is why the orphan path stayed green - the same wrong-reason pattern this review has caught repeatedly. sharedTeardownAuthorized takes what each step observed, the fields start pessimistic so a thrown step cannot look like success, and all eight permutations are asserted. The skip message also promised the wrong remedy: ocx restore returns client routing but leaves the service removal and local cleanup this command had not reached. It now says to resolve the blocker and rerun ocx uninstall, with ocx restore named only as an interim step. And the boolean stopServiceIfInstalled is deleted. It had no production caller left, and leaving it there is an invitation to the same defect a third time. * fix(uninstall): stop manufacturing proof out of three different unknowns Twenty-third review round found the authorization rule reading correctly while every observation feeding it was wrong. uninstallServiceIfInstalled returned false for absence AND for darwin/linux removal failures, so a service that could not be removed was recorded as never installed - which authorized the shared teardown and let the command report success with the assets still there. There is a typed uninstallServiceDetailed now: absent, removed, failed. The boolean wrapper throws on failed so no caller can inherit the old collapse. stopped-respawnable was authorized on the theory that removing the registration stops the wrapper. It does not: killing the running :loop wrapper is explicitly best-effort (#764), which is why ocx stop polls the restart window. Uninstall now polls the same window after removal and only then may take shared config down. And the orphan discovery treated a findLiveProxy miss as proof of absence, when that null also covers a timeout and a transport failure. It goes through the tri-state probe, which says dead only for a refused connection or a definitive non-OpenCodex answer; anything else fails the step. The observation fields are renamed to say what they now mean - proxyProvenDown, serviceRemoval, respawnWindowVerified - and the permutation test covers all of them, including the two respawnable cases that differ only by the window check. * fix(uninstall): prove every endpoint, and stop reading silence as a dead window Twenty-fourth review round found both remaining proofs still manufacturing certainty from a single probe. The endpoint fallback preferred the runtime record over configuration and asked only that one. findLiveProxy had already probed both candidates before returning null, so a stale record pointing at a closed port plus a live-but-unresponsive proxy on the configured port gave "dead" for a port nobody was using - and that authorized the teardown. endpointsToProve collects every distinct candidate and everyEndpointProvenDown requires all of them to be definitively dead. The restart-window poll had the same shape. proxyStillLiveAfterStop returns null on a timeout as well as on a genuinely dead endpoint, so a respawned wrapper that would not answer looked verified-down. The window is now only verified once the tri-state probe says dead on every candidate. Both observations are pure functions now, so the test drives closed, live and silent listeners by calling them rather than reading the command's source - which is what let both of these stay green through the previous round. * docs(devlog): record what 26 review rounds added to the #3008 unit --- bin/ocx.mjs | 64 ++- .../051_wp5_outcome.md | 68 +++ .../content/docs/fr/guides/web-dashboard.md | 4 +- .../docs/fr/reference/cli/lifecycle.md | 2 +- .../docs/fr/reference/management-api.md | 2 +- .../src/content/docs/guides/web-dashboard.md | 4 +- .../content/docs/ja/guides/web-dashboard.md | 4 +- .../docs/ja/reference/cli/lifecycle.md | 2 +- .../docs/ja/reference/management-api.md | 2 +- .../content/docs/ko/guides/web-dashboard.md | 4 +- .../docs/ko/reference/cli/lifecycle.md | 2 +- .../docs/ko/reference/management-api.md | 2 +- .../content/docs/reference/cli/lifecycle.md | 6 +- .../content/docs/reference/management-api.md | 2 +- .../content/docs/ru/guides/web-dashboard.md | 4 +- .../docs/ru/reference/cli/lifecycle.md | 3 +- .../docs/ru/reference/management-api.md | 2 +- .../content/docs/tr/guides/web-dashboard.md | 4 +- .../docs/tr/reference/cli/lifecycle.md | 3 +- .../docs/tr/reference/management-api.md | 2 +- .../docs/zh-cn/guides/web-dashboard.md | 4 +- .../docs/zh-cn/reference/cli/lifecycle.md | 2 +- .../docs/zh-cn/reference/management-api.md | 2 +- .../docs/zh-tw/guides/web-dashboard.md | 4 +- .../docs/zh-tw/reference/cli/lifecycle.md | 2 +- .../docs/zh-tw/reference/management-api.md | 2 +- src/cli/dispatch.ts | 61 ++- src/cli/index.ts | 441 +++++++++++++++-- src/cli/uninstall-plan.ts | 86 ++++ src/config/pending-teardown-names.d.mts | 8 + src/config/pending-teardown-names.mjs | 69 +++ src/config/pending-teardown.ts | 286 +++++++++++ src/lib/process-control.ts | 35 +- src/server/management-api.ts | 78 ++- src/server/stop-teardown.ts | 84 ++++ src/service.ts | 175 ++++++- src/update/index.ts | 40 +- src/update/proxy-liveness-probe.d.mts | 6 + src/update/proxy-liveness-probe.mjs | 84 ++++ src/update/stop-contract.d.mts | 2 + src/update/stop-contract.mjs | 15 + src/update/stop-decision.d.mts | 10 + src/update/stop-decision.mjs | 34 ++ tests/cli-ready.test.ts | 6 +- tests/grok-lifecycle.test.ts | 282 ++++++++++- .../restore-completes-shared-teardown.test.ts | 177 +++++++ tests/stop-deferred-teardown.test.ts | 457 ++++++++++++++++++ tests/uninstall.test.ts | 160 +++++- tests/update-stop-classification.test.ts | 253 ++++++++++ tests/update-stop-first.test.ts | 21 +- 50 files changed, 2927 insertions(+), 145 deletions(-) create mode 100644 devlog/_plan/260831_prio70_train_round2/051_wp5_outcome.md create mode 100644 src/cli/uninstall-plan.ts create mode 100644 src/config/pending-teardown-names.d.mts create mode 100644 src/config/pending-teardown-names.mjs create mode 100644 src/config/pending-teardown.ts create mode 100644 src/server/stop-teardown.ts create mode 100644 src/update/proxy-liveness-probe.d.mts create mode 100644 src/update/proxy-liveness-probe.mjs create mode 100644 src/update/stop-contract.d.mts create mode 100644 src/update/stop-contract.mjs create mode 100644 src/update/stop-decision.d.mts create mode 100644 src/update/stop-decision.mjs create mode 100644 tests/restore-completes-shared-teardown.test.ts create mode 100644 tests/stop-deferred-teardown.test.ts create mode 100644 tests/update-stop-classification.test.ts diff --git a/bin/ocx.mjs b/bin/ocx.mjs index 8da4e1f724..5357985bca 100755 --- a/bin/ocx.mjs +++ b/bin/ocx.mjs @@ -9,6 +9,9 @@ * src/cli/index.ts — only the published npm `bin` routes through here.) */ import { spawn, spawnSync } from "node:child_process"; +import { STOP_HISTORY_INCOMPLETE_EXIT_CODE } from "../src/update/stop-contract.mjs"; +import { probeProxyLiveness } from "../src/update/proxy-liveness-probe.mjs"; +import { decidePostStopUpdate } from "../src/update/stop-decision.mjs"; import { randomBytes } from "node:crypto"; import { createRequire } from "node:module"; import { existsSync, readFileSync, readdirSync } from "node:fs"; @@ -17,6 +20,7 @@ import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { isRealBunBinary } from "../src/lib/bun-binary-validator.mjs"; import { npmInvocation } from "../src/update/npm-invocation.mjs"; +import { hasPendingTeardownIn } from "../src/config/pending-teardown-names.mjs"; import { npmCachePreflightFailureMessage, runNpmCachePreflight, @@ -202,7 +206,12 @@ function runNpmSelfUpdate() { // Capture listen target before stop clears runtime-port.json (mirrors GUI/CLI update worker). // Do not treat a live runtime port of 10100 as "missing" — track whether the read succeeded. let bakePort = 10100; + // The hostname travels with the port: a proxy bound to ::1 or a specific interface is + // invisible to a probe that assumes 127.0.0.1, and "no answer" would then read as + // "stopped" for exactly the proxy the probe exists to find. + let bakeHostname = "127.0.0.1"; let sawRuntimePort = false; + let sawRuntimeHostname = false; try { const rt = JSON.parse(readFileSync(join(configDir(), "runtime-port.json"), "utf8")); if (Number.isFinite(rt?.port) && rt.port > 0 && rt.port <= 65535) { @@ -219,16 +228,29 @@ function runNpmSelfUpdate() { } if (runtimeLive) { bakePort = Math.trunc(rt.port); + if (typeof rt?.hostname === "string" && rt.hostname.trim() !== "") { + bakeHostname = rt.hostname.trim(); + sawRuntimeHostname = true; + } sawRuntimePort = true; } } } catch { /* fall through to config */ } - if (!sawRuntimePort) { + // Port and hostname resolve INDEPENDENTLY: a legacy runtime record carries a port and no + // hostname, and skipping config in that case probed 127.0.0.1 for a proxy bound to ::1. + if (!sawRuntimePort || bakeHostname === "127.0.0.1") { try { const cfg = JSON.parse(readFileSync(join(configDir(), "config.json"), "utf8")); - if (Number.isFinite(cfg?.port) && cfg.port > 0 && cfg.port <= 65535) bakePort = Math.trunc(cfg.port); + if (!sawRuntimePort && Number.isFinite(cfg?.port) && cfg.port > 0 && cfg.port <= 65535) { + bakePort = Math.trunc(cfg.port); + } + if (!sawRuntimeHostname && typeof cfg?.hostname === "string" && cfg.hostname.trim() !== "") { + bakeHostname = cfg.hostname.trim(); + } } catch { /* keep default */ } } + // Wildcard and bracketed-IPv6 normalization lives in probeProxyLiveness, so both lanes + // get it from one place. const launcher = fileURLToPath(import.meta.url); @@ -343,17 +365,47 @@ function runNpmSelfUpdate() { } } - if (serviceWasInstalled || hasRuntimeState) { + // An outstanding pending-teardown receipt is a fourth reason to run the stop. After a + // parent crashed mid-deferral the service, pid and runtime records can all be absent + // while the shared client config still points at a proxy that is gone; installing over + // that silently skips the recovery the receipt was written to trigger (#3008). Presence + // is the whole test here — the launcher cannot parse it, and `ocx stop` is what decides + // whether the obligation is safe to finish. + const hasPendingTeardown = hasPendingTeardownIn(readdirSync, configDir()); + if (serviceWasInstalled || hasRuntimeState || hasPendingTeardown) { console.log("⏹ Stopping the running proxy before updating..."); const stopRes = spawnSync(process.execPath, [launcher, "stop"], { stdio: "inherit", windowsHide: true }); const stillHasRuntimeState = existsSync(join(configDir(), "ocx.pid")) || existsSync(join(configDir(), "runtime-port.json")); - if (stopRes.status !== 0 || stillHasRuntimeState) { + // A history-only failure means teardown succeeded and a backup manifest is waiting for + // review: the proxy is down and replacing package files is safe. Every other nonzero + // status is a stop that did not finish, and a signal kill (status null) says nothing + // about whether it did - both abort, because replacing files under a live server + // leaves it running mixed old and new modules (#3008). + // The same decision the Bun updater makes, from the same module (#3008). Absent PID and + // runtime files are weak evidence, so the captured endpoint is asked; "unknown" aborts + // because a silent listener is exactly the state where replacing files is dangerous. + const decision = decidePostStopUpdate({ + status: stopRes.status, + hasRuntimeState: stillHasRuntimeState, + // Re-checked AFTER the stop: a quarantined receipt lets the stop itself succeed + // (there is nothing left to stop), so a pre-stop check alone let the retry install + // over a teardown that never ran. + teardownOutstanding: hasPendingTeardownIn(readdirSync, configDir()), + liveness: probeProxyLiveness(bakePort, bakeHostname), + }); + const historyOnlyStop = decision.reason === "history-only"; + if (!decision.proceed) { if (trayBeforeUpdate.restoreOnFailure) runTrayLifecycle(launcher, "start"); - console.error("opencodex: could not stop the running proxy; aborting the update. Run 'ocx stop' and retry."); + if (decision.reason === "teardown-outstanding") { + console.error("opencodex: a shared teardown from an earlier stop is still outstanding and needs manual review; aborting the update."); + console.error("opencodex: confirm no proxy is running, run 'ocx restore', then remove the pending-teardown file in the opencodex home."); + } else console.error(decision.reason === "proxy-unknown" + ? `opencodex: could not confirm the proxy on ${bakeHostname}:${bakePort} is stopped; aborting the update. Run 'ocx stop' and retry.` + : "opencodex: could not stop the running proxy; aborting the update. Run 'ocx stop' and retry."); process.exit(1); } - if (historyRestoreIncomplete()) { + if (historyOnlyStop || historyRestoreIncomplete()) { console.warn( "opencodex: WARNING — Codex resume-history metadata restore is incomplete (a backup manifest remains).\n" + " The DB may be busy or the manifest/target may need review; untracked routed history is intentionally unchanged.\n" + diff --git a/devlog/_plan/260831_prio70_train_round2/051_wp5_outcome.md b/devlog/_plan/260831_prio70_train_round2/051_wp5_outcome.md new file mode 100644 index 0000000000..1f87650dd3 --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/051_wp5_outcome.md @@ -0,0 +1,68 @@ +# 050 outcome — wp5 (#3008): what the implementation added beyond this plan + +The plan described one defect: `ocx update` aborting because `ocx stop` could not tell a +history-only failure from a real stop failure. That fix is in the first commit of the +branch. The other twenty-five came from adversarial review, and they are not incidental — +each one is the same defect wearing different clothes: **a teardown that reports success +while half of it did not happen.** + +## The shape that kept recurring + +Something cannot be determined, and the code treats "could not determine" as "determined +to be fine". Every instance authorized taking shared client config down while a proxy +might still be serving, which leaves Codex or Grok pointed at a process that is gone. + +| Where | What was read as proof | Round | +| --- | --- | --- | +| `POST /api/stop` | `stopServiceIfInstalled` false — collapsed "not installed" with "refused to stop" | 17 | +| `POST /api/stop` | Success decided from the native restore alone, Grok failure appended as text | 17 | +| `ocx service stop` / `uninstall` | Restore and strip failures logged, exit code still 0 | 17 | +| Route pre-check | Scheduler stopped first, refused second — mutate-then-refuse | 18 | +| Daemon exit | Drain success alone, ignoring the teardown result | 18 | +| Respawn predicate | `status === "present"`, so an unreadable probe passed as absent | 19 | +| `ocx restore` | Early return on the Codex no-op path, never reaching the Grok strip | 15 | +| `ocx restore --json` | Same, on the ordinary forward path | 16 | +| Receipt scan | Every `readdir` error read as "no obligations" | 16 | +| `ocx uninstall` | `stopServiceIfInstalled` false read as "not installed" | 22 | +| `ocx uninstall` | Missing pid file read as "no proxy serving" | 23 | +| `ocx uninstall` | `uninstallServiceIfInstalled` false — absence and removal failure | 24 | +| `ocx uninstall` | Registration removed, running wrapper assumed dead | 24 | +| `ocx uninstall` | `findLiveProxy` null read as proof of absence | 24 | +| `ocx uninstall` | One endpoint probed while two were candidates | 25 | +| `ocx uninstall` | `proxyStillLiveAfterStop` null read as a verified window | 25 | + +## The deferral, and why it needed four attempts + +`ocx stop` has to defer shared teardown to itself, because the proxy exits before anyone +can verify a Task Scheduler wrapper did not respawn it. Expressing that obligation took +four tries: + +1. A query flag. Any authenticated caller could set it and exit, and a parent that died + mid-stop left nothing on disk saying a restore was owed. +2. A receipt file. Presence is not ownership — another caller could ride on it. +3. A nonce inside one shared file. Read-compare-unlink is three syscalls, so a concurrent + stop replacing the file between the compare and the unlink got its obligation deleted. +4. **The nonce as the filename.** `unlink` names one specific obligation and cannot reach + another. Two concurrent stops hold two receipts, which is the truth of the situation. + +The receipt also carries the endpoint being stopped and how it was obtained. A configured +address is recorded as `guessed` and never authorizes automatic recovery: a proxy on an +explicit `--port` can be respawned there while the configured port refuses. + +## Tests that passed for the wrong reason + +Five times a regression was written as a source-text assertion, and five times reverting +the defect left it green. The reviewer caught each one. Where a rule mattered it was +extracted into something callable — `performStopTeardown`, `classifyWindowsServiceStop`, +`sharedTeardownAuthorized`, `endpointsToProve`, `everyEndpointProvenDown` — and the test +now executes the permutations. Source assertions remain only for wiring: that a route +delegates to the extracted rule rather than growing a second copy. + +Every fix on this branch was driven RED against the specific defect and restored. + +## Docs + +Sixteen files across eight locales carry the new refusal contract: `respawnable_service` +and `service_state_unknown`, and the fact that the dashboard Stop button refuses on the +Windows Task Scheduler backend rather than half-performing a stop it cannot verify. + diff --git a/docs-site/src/content/docs/fr/guides/web-dashboard.md b/docs-site/src/content/docs/fr/guides/web-dashboard.md index c6343f75b7..dc000a5a27 100644 --- a/docs-site/src/content/docs/fr/guides/web-dashboard.md +++ b/docs-site/src/content/docs/fr/guides/web-dashboard.md @@ -55,7 +55,7 @@ gestionnaire de mots de passe. | **Journaux** | Actualisez automatiquement les requêtes récentes et consultez les jetons, l'effort demandé et, lorsqu'il est disponible, l'effort sortant effectif, le modèle résolu, le fournisseur, l'état, l'identifiant de requête, la durée et les détails de l'erreur. La vue détaillée inclut le champ exact de raisonnement transmis lorsque l'adaptateur en émet un. Filtrez par identifiant opaque de conversation ou de session — si le client en fournit un — afin d'obtenir le total des jetons et le coût estimé au tarif catalogue pour l'anneau de journaux actuellement chargé. | | **Utilisation / Débogage** | Examinez la couverture et les tendances d'utilisation des jetons, ou activez à la demande les diagnostics de transport et d'extraction de l'utilisation propres aux fournisseurs. | | **Stockage** | Consultez en lecture seule la répartition du disque de CODEX_HOME — sessions, archives, bases de données et pièces jointes. Pour le nettoyage facultatif des archives, prévisualisez les N % les plus anciennes, puis placez-les en quarantaine dans `CODEX_HOME/.trash` (par défaut) ou supprimez-les définitivement après avoir coché une case explicite. **La stratégie de nettoyage automatique** est facultative et **désactivée par défaut** (`storageCleanupPolicy.enabled`) ; configurez son seuil, sa cible, sa planification et son mode sur la page **Stockage**, ou lancez **Exécuter maintenant**. Les entrées mises en quarantaine peuvent être restaurées depuis cette page (JSONL et fils). Les sessions actives restent en lecture seule. Le nettoyage et la restauration sont refusés tant que Codex verrouille le fichier `state_*.sqlite` le plus récent ou actif. | -| **Arrêter** | Arrêtez proprement le proxy et le service d'arrière-plan installé, restaurez Codex natif et quittez (`POST /api/stop`). | +| **Arrêter** | Arrêtez proprement le proxy et le service d'arrière-plan installé, restaurez Codex natif et quittez (`POST /api/stop`). Sur Windows avec le backend Planificateur de tâches, le tableau de bord refuse et vous demande d'exécuter `ocx stop` : le wrapper peut relancer le proxy après la fin de la tâche, et seul un stop exécuté hors du proxy peut vérifier cette fenêtre de redémarrage avant de restaurer votre configuration client. Rien n'est modifié en cas de refus. | ### Liens directs vers une section @@ -174,7 +174,7 @@ L'interface graphique est un client léger de l'API JSON de gestion du proxy. Pa | `POST /api/codex-auth/login` · `GET /api/codex-auth/login-status` | Ajouter un compte au groupe au moyen d’une connexion dans le navigateur. | | `GET /api/logs?tail=50&limit=20&offset=0&provider=...&status=5xx` | Lire les métadonnées des requêtes récentes avec des filtres facultatifs de fin de journal, de fournisseur et d'état exact ou par classe. Avec `limit`/`offset`, la pagination remonte depuis la ligne la plus récente (`offset=0` renvoie la dernière page). Forme de la réponse : `{ timeZone, total, logs }`, où `total` est le nombre de lignes filtrées avant pagination. | | `GET` / `PUT /api/subagent-models` | Lire ou définir les cinq modèles de remplacement `spawn_agent` mis en avant. | -| `POST /api/stop` | Arrêter le proxy et le service, restaurer Codex natif et quitter. | +| `POST /api/stop` | Arrêter le proxy et le service, restaurer Codex natif et quitter. Refusé avec `respawnable_service` sur le backend Planificateur de tâches Windows, et avec `service_state_unknown` lorsque cet état ne peut pas être lu ; rien n'est modifié dans les deux cas. | :::tip L'ajout d'**Ollama Cloud** ou d'un autre fournisseur doté d'un catalogue depuis le tableau de bord copie sa diff --git a/docs-site/src/content/docs/fr/reference/cli/lifecycle.md b/docs-site/src/content/docs/fr/reference/cli/lifecycle.md index b93a23c298..8f6362e1e4 100644 --- a/docs-site/src/content/docs/fr/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/fr/reference/cli/lifecycle.md @@ -24,7 +24,7 @@ ocx start --port 8080 ### `ocx stop` -Arrête le proxy actif à partir de son PID, supprime le fichier de PID et rétablit le fonctionnement natif de Codex. Si un service d’arrière-plan géré est installé, `ocx stop` l’arrête d’abord afin qu’il ne puisse pas relancer le proxy. La même opération est disponible avec le bouton **Stop** du tableau de bord Web (`POST /api/stop`). +Arrête le proxy actif à partir de son PID, supprime le fichier de PID et rétablit le fonctionnement natif de Codex. Si un service d’arrière-plan géré est installé, `ocx stop` l’arrête d’abord afin qu’il ne puisse pas relancer le proxy. Le bouton **Stop** du tableau de bord Web exécute la même opération (`POST /api/stop`) sur tous les backends, sauf le Planificateur de tâches Windows : le wrapper peut y relancer le proxy après la fin de la tâche, donc le tableau de bord refuse avec `respawnable_service`, ne modifie rien et vous demande d'exécuter `ocx stop`. ### `ocx restart` diff --git a/docs-site/src/content/docs/fr/reference/management-api.md b/docs-site/src/content/docs/fr/reference/management-api.md index cde2aa143c..b5c730b837 100644 --- a/docs-site/src/content/docs/fr/reference/management-api.md +++ b/docs-site/src/content/docs/fr/reference/management-api.md @@ -227,7 +227,7 @@ lui-même s'il souhaite ajouter une étoile au dépôt. | --- | --- | --- | | `GET /api/system/memory` | Renvoyer les mesures scalaires du processus, du tas, des flux, de l'état des réponses, du mécanisme de surveillance et des tours actifs | — | | `POST /api/system/restart` | Amorcer un redémarrage du processus qui attend l'évacuation des requêtes, sans retirer l'injection du client | Renvoie 202 ; les appels répétés signalent l'évacuation déjà en cours | -| `POST /api/stop` | Arrêter le service, restaurer Codex en mode natif, retirer l'injection Grok gérée et évacuer les requêtes du proxy | 409 conflit de propriété du service | +| `POST /api/stop` | Arrêter le service, restaurer Codex en mode natif, retirer l'injection Grok gérée et évacuer les requêtes du proxy | 409 conflit de propriété du service; 409 `respawnable_service` lorsqu'un wrapper du Planificateur de tâches Windows pourrait relancer le proxy et que l'appelant n'est pas `ocx stop` (rien n'est modifié) ; 409 lorsque le gestionnaire installé refuse de s'arrêter ; 409 `service_state_unknown` lorsque l'état du Planificateur de tâches ne peut pas être lu (rien n'est modifié ; réparez la requête puis réessayez) | | `GET /api/system/codex-app-server` | Indiquer si les serveurs d'application Codex en cours d'exécution sont antérieurs au catalogue de modèles actuel | — | | `POST /api/system/codex-restart` | Actualiser le catalogue, puis demander aux serveurs d'application Codex obsolètes de s'arrêter afin que le sélecteur de modèles se recharge | Renvoie 200 avec `code: partially_stopped` lorsqu'une cible ne s'arrête pas | diff --git a/docs-site/src/content/docs/guides/web-dashboard.md b/docs-site/src/content/docs/guides/web-dashboard.md index 6c2ca81e05..8c9b589fc2 100644 --- a/docs-site/src/content/docs/guides/web-dashboard.md +++ b/docs-site/src/content/docs/guides/web-dashboard.md @@ -53,7 +53,7 @@ the browser or password manager's decision. | **Logs** | Auto-refresh recent requests with tokens, requested effort and (when available) effective outbound effort, resolved model, provider, status, request id, duration, and error details. The detail view includes the exact reasoning wire field when the adapter emits one. Filter by opaque conversation/session id (when the client sends one) to total tokens and estimated list-price cost for the currently loaded Logs ring. | | **Usage / Debug** | Inspect token-usage coverage and trends, or enable opt-in provider transport and usage-extraction diagnostics. | | **Storage** | Read-only CODEX_HOME disk breakdown (sessions, archives, DBs, attachments). Optional archived cleanup: preview the oldest N%, then quarantine to `CODEX_HOME/.trash` (default) or permanently delete behind an explicit checkbox. **Auto-cleanup policy** is opt-in and **default OFF** (`storageCleanupPolicy.enabled`); configure threshold/target/schedule/mode on the Storage page, or trigger **Run now**. Quarantined entries can be restored from the Storage page (JSONL + threads). Active sessions stay read-only. Cleanup and restore are refused while Codex holds the newest/active `state_*.sqlite` locked. | -| **Stop** | Gracefully stop the proxy and installed background service, restore native Codex, and exit (`POST /api/stop`). | +| **Stop** | Gracefully stop the proxy and installed background service, restore native Codex, and exit (`POST /api/stop`). On Windows with the Task Scheduler backend the dashboard refuses and asks you to run `ocx stop` instead: that wrapper can respawn the proxy after the task ends, and only a stop running outside this process can verify the restart window before restoring your client config. Nothing is changed when it refuses. | ### Linking to a section @@ -217,7 +217,7 @@ The GUI is a thin client over the proxy's JSON management API. Useful endpoints | `POST /api/codex-auth/login` · `GET /api/codex-auth/login-status` | Add a pool account through browser login. | | `GET /api/logs?tail=50&limit=20&offset=0&provider=...&status=5xx` | Read recent request metadata with optional tail, provider, and exact/class status filters. With `limit`/`offset`, paging walks backward from the newest row (`offset=0` returns the latest page). Response shape: `{ timeZone, total, logs }` where `total` is the filtered row count before pagination. | | `GET` / `PUT /api/subagent-models` | Read or set the five featured `spawn_agent` override models. | -| `POST /api/stop` | Stop the proxy/service, restore native Codex, and exit. | +| `POST /api/stop` | Stop the proxy/service, restore native Codex, and exit. Refused with `respawnable_service` on the Windows Task Scheduler backend, and with `service_state_unknown` when that state cannot be read; nothing is changed either way. | :::tip Adding **Ollama Cloud** or another catalog provider from the dashboard copies its text-versus-vision diff --git a/docs-site/src/content/docs/ja/guides/web-dashboard.md b/docs-site/src/content/docs/ja/guides/web-dashboard.md index c63c7a77e4..41a9b4b2d5 100644 --- a/docs-site/src/content/docs/ja/guides/web-dashboard.md +++ b/docs-site/src/content/docs/ja/guides/web-dashboard.md @@ -46,7 +46,7 @@ bun run dev:gui | **ログ** | トークン、要求された強度と(利用可能な場合は)実際に送信された強度、実際のモデル、プロバイダー、状態、リクエスト ID、所要時間、エラー詳細を含む最近のリクエストを自動更新します。アダプターが reasoning パラメーターを送信した場合、詳細表示に正確な wire field も表示されます。 | | **使用量 / デバッグ** | トークン使用量の測定範囲と推移を見るか、オプションのプロバイダートランスポート/使用量抽出診断をオンにします。 | | **ストレージ** | CODEX_HOME のディスク内訳(セッション、アーカイブ、DB、添付)を読み取り専用で表示。任意のアーカイブクリーンアップ: 最古 N% をプレビューし、既定では `CODEX_HOME/.trash` へ隔離、または明示チェックで完全削除。**自動クリーンアップ方針**はオプトインで**既定 OFF**(`storageCleanupPolicy.enabled`)。Storage ページでしきい値/目標/スケジュール/モードを設定するか **今すぐ実行**。隔離エントリは Storage ページから復元可能(JSONL + スレッド)。アクティブセッションは読み取り専用。最新/アクティブな `state_*.sqlite` がロック中はクリーンアップと復元を拒否。 | -| **停止** | プロキシとインストールされたバックグラウンドサービスを正常終了しネイティブ Codex を復元した後終了します(`POST /api/stop`)。 | +| **停止** | プロキシとインストールされたバックグラウンドサービスを正常終了しネイティブ Codex を復元した後終了します(`POST /api/stop`)。ただし Windows のタスク スケジューラ バックエンドではダッシュボードが拒否し、`ocx stop` の実行を促します。タスク終了後もラッパーがプロキシを再起動しうるため、クライアント設定を戻す前にその再起動区間を確認できるのはプロキシの外で動く stop だけです。拒否されたときは何も変更されません。 | ### セクションへのリンク @@ -147,7 +147,7 @@ GUI はプロキシの JSON 管理 API を使うシンクライアントです | `POST /api/codex-auth/login` · `GET /api/codex-auth/login-status` | ブラウザログインでプールアカウントを追加します。 | | `GET /api/logs?tail=50&limit=20&offset=0&provider=...&status=5xx` | tail、プロバイダー、正確な状態コードまたは状態等級で最近のリクエストメタデータを参照します。`limit`/`offset` は最新行から過去方向にページングします(`offset=0` が最新ページ)。応答は `{ timeZone, total, logs }` で、`total` はページング前の一致件数です。 | | `GET` / `PUT /api/subagent-models` | `spawn_agent` に優先公開するモデル 5 つを読むか設定します。 | -| `POST /api/stop` | プロキシ/サービスを停止しネイティブ Codex を復元した後終了します。 | +| `POST /api/stop` | プロキシ/サービスを停止しネイティブ Codex を復元した後終了します。Windows タスク スケジューラ バックエンドでは `respawnable_service`、その状態を読み取れない場合は `service_state_unknown` で拒否し、どちらの場合も何も変更されません。 | :::tip ダッシュボードで **Ollama Cloud** のようなカタログプロバイダーを追加するとテキスト/ビジョンモデル分類が保存された diff --git a/docs-site/src/content/docs/ja/reference/cli/lifecycle.md b/docs-site/src/content/docs/ja/reference/cli/lifecycle.md index 4d6e7bcad7..6faca72d3a 100644 --- a/docs-site/src/content/docs/ja/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/ja/reference/cli/lifecycle.md @@ -24,7 +24,7 @@ ocx start --port 8080 ### `ocx stop` -実行中のプロキシを (PID によって) 停止し、PID ファイルを削除して、ネイティブ Codex を復元します。マネージド バックグラウンド サービスがインストールされている場合、`ocx stop` はそれを最初に停止するため、プロキシを再起動できません。同じアクションは、Web ダッシュボードの **停止** ボタン (`POST /api/stop`) から実行できます。 +実行中のプロキシを (PID によって) 停止し、PID ファイルを削除して、ネイティブ Codex を復元します。マネージド バックグラウンド サービスがインストールされている場合、`ocx stop` はそれを最初に停止するため、プロキシを再起動できません。Web ダッシュボードの **停止** ボタンは同じ処理 (`POST /api/stop`) を実行しますが、Windows タスク スケジューラだけは例外です。タスク終了後もラッパーがプロキシを再起動しうるため、ダッシュボードは `respawnable_service` で拒否し、何も変更せずに `ocx stop` の実行を促します。 ### `ocx restart` diff --git a/docs-site/src/content/docs/ja/reference/management-api.md b/docs-site/src/content/docs/ja/reference/management-api.md index 7024b2ef45..8d1f652392 100644 --- a/docs-site/src/content/docs/ja/reference/management-api.md +++ b/docs-site/src/content/docs/ja/reference/management-api.md @@ -195,7 +195,7 @@ Authorization: Bearer <admin-token> | --- | --- | --- | | `GET /api/system/memory` |スカラー プロセス、ヒープ、ストリーム、応答状態、ウォッチドッグ、およびアクティブ ターン メトリックを返します。 — | | `POST /api/system/restart` |クライアント インジェクションを削除せずに、ドレイン対応プロセスの再起動を開始します。 202 を返します。繰り返しの呼び出しにより、既存の排水が報告されます。 -| `POST /api/stop` |サービスを停止し、ネイティブ Codex を復元し、マネージド Grok インジェクションを削除し、プロキシをドレインします。 409 サービス所有権の競合 | +| `POST /api/stop` | サービスを停止し、ネイティブ Codex を復元し、マネージド Grok インジェクションを削除し、プロキシをドレインします | 409 サービス所有権の競合、409 `respawnable_service`(Windows タスク スケジューラのラッパーがプロキシを再起動しうる状態で、呼び出し元が `ocx stop` でない場合。何も変更されません)、409 インストール済みマネージャが停止を拒否した場合、409 `service_state_unknown`(タスク スケジューラの状態を読み取れない場合。何も変更されません。クエリを修復して再試行してください) | ### Codex認証の委任 diff --git a/docs-site/src/content/docs/ko/guides/web-dashboard.md b/docs-site/src/content/docs/ko/guides/web-dashboard.md index 9ec0fe4092..f831016288 100644 --- a/docs-site/src/content/docs/ko/guides/web-dashboard.md +++ b/docs-site/src/content/docs/ko/guides/web-dashboard.md @@ -46,7 +46,7 @@ bun run dev:gui | **Logs** | 토큰, 요청한 강도와 (사용 가능한 경우) 실제 전송 강도, 실제 모델, 프로바이더, 상태, 요청 id, 소요 시간, 오류 상세가 포함된 최근 요청을 자동 갱신합니다. 어댑터가 reasoning 매개변수를 전송한 경우 상세 보기에 정확한 wire field도 표시됩니다. 클라이언트가 보낸 불투명 대화/세션 id로 필터하면 현재 로드된 Logs 링의 토큰·추정 정가 합계를 볼 수 있습니다. | | **Usage / Debug** | 토큰 사용량의 측정 범위와 추이를 보거나, 선택적 프로바이더 전송/사용량 추출 진단을 켭니다. | | **Storage** | CODEX_HOME 디스크 사용량(세션, 보관, DB, 첨부)을 읽기 전용으로 표시합니다. 선택적 보관 정리: 가장 오래된 N%를 미리본 뒤 기본으로 `CODEX_HOME/.trash`에 격리하거나, 명시 체크 후 영구 삭제합니다. **자동 정리 정책**은 opt-in이며 **기본 OFF**(`storageCleanupPolicy.enabled`)입니다. Storage 페이지에서 임계값/목표/일정/모드를 설정하거나 **지금 실행**하세요. Storage 페이지에서 격리 항목을 복원할 수 있습니다(JSONL + 스레드). 활성 세션은 읽기 전용입니다. Codex가 최신/활성 `state_*.sqlite`를 잠그면 정리와 복원을 거절합니다. | -| **Stop** | 프록시와 설치된 백그라운드 서비스를 정상 종료하고 네이티브 Codex를 복원한 뒤 끝냅니다(`POST /api/stop`). | +| **Stop** | 프록시와 설치된 백그라운드 서비스를 정상 종료하고 네이티브 Codex를 복원한 뒤 끝냅니다(`POST /api/stop`). 단, Windows 작업 스케줄러로 관리되는 경우에는 대시보드가 거절하고 `ocx stop`을 안내합니다. 작업이 끝나도 래퍼가 프록시를 다시 띄울 수 있어서, 클라이언트 설정을 되돌리기 전에 그 재시작 구간을 확인할 수 있는 건 프록시 바깥에서 도는 stop뿐입니다. 거절될 때는 아무것도 바뀌지 않습니다. | ### 섹션으로 바로 가기 @@ -168,7 +168,7 @@ GUI는 프록시의 JSON 관리 API를 사용하는 얇은 클라이언트입니 | `POST /api/codex-auth/login` · `GET /api/codex-auth/login-status` | 브라우저 로그인으로 pool 계정을 추가합니다. | | `GET /api/logs?tail=50&limit=20&offset=0&provider=...&status=5xx` | tail, 프로바이더, 정확한 상태 코드 또는 상태 등급으로 최근 요청 메타데이터를 조회합니다. `limit`/`offset`은 최신 행에서 과거 방향으로 페이지네이션합니다(`offset=0`이 최신 페이지). 응답은 `{ timeZone, total, logs }`이며 `total`은 페이지네이션 전 필터 일치 건수입니다. | | `GET` / `PUT /api/subagent-models` | `spawn_agent`에 우선 노출할 모델 5개를 읽거나 설정합니다. | -| `POST /api/stop` | 프록시/서비스를 멈추고 네이티브 Codex를 복원한 뒤 종료합니다. | +| `POST /api/stop` | 프록시/서비스를 멈추고 네이티브 Codex를 복원한 뒤 종료합니다. Windows 작업 스케줄러 백엔드에서는 `respawnable_service`로, 그 상태를 읽을 수 없으면 `service_state_unknown`으로 거절하며, 두 경우 모두 아무것도 바뀌지 않습니다. | :::tip 대시보드에서 **Ollama Cloud** 같은 카탈로그 프로바이더를 추가하면 텍스트/비전 모델 분류가 저장된 diff --git a/docs-site/src/content/docs/ko/reference/cli/lifecycle.md b/docs-site/src/content/docs/ko/reference/cli/lifecycle.md index f027979a48..0080f40942 100644 --- a/docs-site/src/content/docs/ko/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/ko/reference/cli/lifecycle.md @@ -32,7 +32,7 @@ ocx start --port 8080 실행 중인 프록시를 PID 기준으로 중지하고, PID 파일을 삭제한 뒤 기본 Codex를 복원합니다. 관리형 백그라운드 서비스가 설치되어 있으면 `ocx stop`이 먼저 그 서비스를 중지하므로 프록시가 다시 -올라올 수 없습니다. 같은 동작은 웹 대시보드의 **Stop** 버튼(`POST /api/stop`)에서도 사용할 수 있습니다. +올라올 수 없습니다. 웹 대시보드의 **Stop** 버튼도 같은 동작(`POST /api/stop`)을 하지만, Windows 작업 스케줄러는 예외입니다. 작업이 끝나도 래퍼가 프록시를 다시 띄울 수 있어서, 대시보드는 `respawnable_service`로 거절하고 아무것도 바꾸지 않은 채 `ocx stop` 실행을 안내합니다. ### `ocx restart` diff --git a/docs-site/src/content/docs/ko/reference/management-api.md b/docs-site/src/content/docs/ko/reference/management-api.md index 5280b31cd4..10c8ff0694 100644 --- a/docs-site/src/content/docs/ko/reference/management-api.md +++ b/docs-site/src/content/docs/ko/reference/management-api.md @@ -198,7 +198,7 @@ Authorization: Bearer <admin-token> | --- | --- | --- | | `GET /api/system/memory` | 프로세스, heap, stream, response-state, watchdog, active-turn의 스칼라 메트릭을 반환합니다 | — | | `POST /api/system/restart` | 클라이언트 injection을 제거하지 않고 drain-aware 프로세스 재시작을 시작합니다 | 202 반환; 반복 호출은 기존 drain을 보고합니다 | -| `POST /api/stop` | 서비스를 중지하고, native Codex를 복원하며, 관리형 Grok injection을 제거하고, 프록시를 drain합니다 | 409 서비스 소유권 충돌 | +| `POST /api/stop` | 서비스를 중지하고, native Codex를 복원하며, 관리형 Grok injection을 제거하고, 프록시를 drain합니다 | 409 서비스 소유권 충돌; Windows 작업 스케줄러 래퍼가 프록시를 다시 띄울 수 있고 호출자가 `ocx stop`이 아니면 409 `respawnable_service`(아무것도 바뀌지 않음); 설치된 관리자가 정지를 거부하면 409; 작업 스케줄러 상태를 읽을 수 없으면 409 `service_state_unknown`(아무것도 바뀌지 않음, 조회를 고친 뒤 재시도) | ### Codex 인증 위임 diff --git a/docs-site/src/content/docs/reference/cli/lifecycle.md b/docs-site/src/content/docs/reference/cli/lifecycle.md index e728335fa4..cde46cf827 100644 --- a/docs-site/src/content/docs/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/reference/cli/lifecycle.md @@ -32,7 +32,11 @@ ocx start --port 8080 Stop the running proxy (by PID), remove the PID file, and restore native Codex. If a managed background service is installed, `ocx stop` also stops it first so it cannot respawn the proxy. -The same action is available from the web dashboard's **Stop** button (`POST /api/stop`). +The web dashboard's **Stop** button runs the same action (`POST /api/stop`) on every backend +except Windows Task Scheduler. There the wrapper can respawn the proxy after the task ends, +and only a stop running outside the proxy can verify that restart window before restoring +your client config — so the dashboard refuses with `respawnable_service`, changes nothing, +and asks you to run `ocx stop`. ### `ocx restart` diff --git a/docs-site/src/content/docs/reference/management-api.md b/docs-site/src/content/docs/reference/management-api.md index 1bfedb0bd6..2484e7e675 100644 --- a/docs-site/src/content/docs/reference/management-api.md +++ b/docs-site/src/content/docs/reference/management-api.md @@ -227,7 +227,7 @@ whether to star the repository. | --- | --- | --- | | `GET /api/system/memory` | Return scalar process, heap, stream, response-state, watchdog, and active-turn metrics | — | | `POST /api/system/restart` | Begin a drain-aware process restart without removing client injection | Returns 202; repeated calls report the existing drain | -| `POST /api/stop` | Stop the service, restore native Codex, remove managed Grok injection, and drain the proxy | 409 service ownership conflict | +| `POST /api/stop` | Stop the service, restore native Codex, remove managed Grok injection, and drain the proxy | 409 service ownership conflict; 409 `respawnable_service` when a Windows Task Scheduler wrapper could respawn the proxy and the caller is not `ocx stop` (nothing is changed); 409 when the installed manager refuses to stop; 409 `service_state_unknown` when the Task Scheduler state cannot be read (nothing is changed; repair the query and retry) | | `GET /api/system/codex-app-server` | Report whether running Codex app-servers predate the current model catalog | — | | `POST /api/system/codex-restart` | Refresh the catalog, then ask stale Codex app-servers to exit so the model picker reloads | Returns 200 with `code: partially_stopped` when a target survives | diff --git a/docs-site/src/content/docs/ru/guides/web-dashboard.md b/docs-site/src/content/docs/ru/guides/web-dashboard.md index 58f037dfd4..2779b167d7 100644 --- a/docs-site/src/content/docs/ru/guides/web-dashboard.md +++ b/docs-site/src/content/docs/ru/guides/web-dashboard.md @@ -46,7 +46,7 @@ bun run dev:gui | **Logs** | Автообновляемый список недавних запросов: токены, запрошенный и, когда доступен, фактически отправленный уровень рассуждений, фактическая модель, провайдер, статус, id запроса, длительность и подробности ошибок. Если адаптер отправляет параметр рассуждений, в подробностях также отображается точное wire-поле. Можно фильтровать по непрозрачному id диалога/сессии (если клиент его передаёт) и суммировать токены и оценочную стоимость по прайс-листу в пределах загруженного кольца Logs. | | **Usage / Debug** | Просмотр покрытия и трендов расхода токенов либо включение опциональной диагностики транспорта провайдеров и извлечения данных об использовании. | | **Storage** | Только чтение разбивки диска CODEX_HOME (сессии, архивы, БД, вложения). Опциональная очистка архива: предпросмотр самых старых N%, затем карантин в `CODEX_HOME/.trash` (по умолчанию) или безвозвратное удаление по явному флажку. **Политика автоочистки** — opt-in и **по умолчанию ВЫКЛ** (`storageCleanupPolicy.enabled`); порог/цель/расписание/режим на странице Storage или **Запустить сейчас**. Записи карантина можно восстановить со страницы Storage (JSONL + threads). Активные сессии только для чтения. Очистка и восстановление отклоняются, пока Codex держит блокировку новейшего/активного `state_*.sqlite`. | -| **Stop** | Корректная остановка прокси и установленного фонового сервиса, восстановление нативного Codex и выход (`POST /api/stop`). | +| **Stop** | Корректная остановка прокси и установленного фонового сервиса, восстановление нативного Codex и выход (`POST /api/stop`). На Windows с бэкендом планировщика заданий дашборд отказывает и просит выполнить `ocx stop`: обёртка может перезапустить прокси после завершения задачи, и проверить это окно перезапуска до восстановления клиентской конфигурации способен только stop, работающий вне прокси. При отказе ничего не изменяется. | ### Ссылки на разделы @@ -157,7 +157,7 @@ GUI — это тонкий клиент поверх JSON-API управлен | `POST /api/codex-auth/login` · `GET /api/codex-auth/login-status` | Добавление аккаунта пула через вход в браузере. | | `GET /api/logs?tail=50&limit=20&offset=0&provider=...&status=5xx` | Чтение метаданных недавних запросов с необязательными фильтрами tail, провайдера и точного/классового статуса. `limit`/`offset` листают назад от самой новой строки (`offset=0` — последняя страница). Ответ: `{ timeZone, total, logs }`, где `total` — число совпадений до пагинации. | | `GET` / `PUT /api/subagent-models` | Чтение или настройка пяти выделенных моделей переопределения `spawn_agent`. | -| `POST /api/stop` | Остановка прокси/сервиса, восстановление нативного Codex и выход. | +| `POST /api/stop` | Остановка прокси/сервиса, восстановление нативного Codex и выход. Отклоняется с `respawnable_service` на бэкенде планировщика заданий Windows и с `service_state_unknown`, когда это состояние не удаётся прочитать; в обоих случаях ничего не изменяется. | :::tip Добавление **Ollama Cloud** или другого провайдера каталога из дашборда копирует его классификацию diff --git a/docs-site/src/content/docs/ru/reference/cli/lifecycle.md b/docs-site/src/content/docs/ru/reference/cli/lifecycle.md index 362600d285..763fbf9afc 100644 --- a/docs-site/src/content/docs/ru/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/ru/reference/cli/lifecycle.md @@ -35,8 +35,7 @@ ocx start --port 8080 Остановить работающий прокси (по PID), удалить PID-file и восстановить native Codex. Если установлена managed background service, `ocx stop` сначала останавливает и её, чтобы она не -перезапустила прокси обратно. То же действие доступно из кнопки **Stop** в веб-дашборде -(`POST /api/stop`). +перезапустила прокси обратно. Кнопка **Stop** в веб-дашборде выполняет то же действие (`POST /api/stop`) на всех бэкендах, кроме планировщика заданий Windows: там обёртка может перезапустить прокси после завершения задачи, поэтому дашборд отказывает с `respawnable_service`, ничего не меняет и просит выполнить `ocx stop`. ### `ocx restart` diff --git a/docs-site/src/content/docs/ru/reference/management-api.md b/docs-site/src/content/docs/ru/reference/management-api.md index 5ab5788beb..516b30e530 100644 --- a/docs-site/src/content/docs/ru/reference/management-api.md +++ b/docs-site/src/content/docs/ru/reference/management-api.md @@ -221,7 +221,7 @@ Management-аутентификация доказывает доступ к п | --- | --- | --- | | `GET /api/system/memory` | Вернуть скалярные метрики процесса, heap, stream, response-state, watchdog и active-turn | — | | `POST /api/system/restart` | Начать restart процесса с учётом drain, не снимая client injection | Возвращает 202; повторные вызовы сообщают о текущем drain | -| `POST /api/stop` | Остановить службу, восстановить native Codex, убрать managed Grok injection и выполнить drain прокси | 409 service ownership conflict | +| `POST /api/stop` | Остановить службу, восстановить native Codex, убрать managed Grok injection и выполнить drain прокси | 409 service ownership conflict; 409 `respawnable_service`, когда обёртка планировщика заданий Windows может перезапустить прокси, а вызывающая сторона — не `ocx stop` (ничего не изменяется); 409, когда установленный менеджер отказывается останавливаться; 409 `service_state_unknown`, когда состояние планировщика заданий не удаётся прочитать (ничего не изменяется; исправьте запрос и повторите) | ### Делегирование аутентификации Codex diff --git a/docs-site/src/content/docs/tr/guides/web-dashboard.md b/docs-site/src/content/docs/tr/guides/web-dashboard.md index 5e5e16272d..955148a054 100644 --- a/docs-site/src/content/docs/tr/guides/web-dashboard.md +++ b/docs-site/src/content/docs/tr/guides/web-dashboard.md @@ -58,7 +58,7 @@ kararıdır. | **Günlükler** | Belirteçler, talep edilen çaba ve (varsa) etkili giden çaba, çözümlenen model, sağlayıcı, durum, istek kimliği, süre ve hata ayrıntılarıyla son istekleri otomatik yenileyin. Ayrıntı görünümü, adaptör bir tane yaydığında tam akıl yürütme hat alanını içerir. Yüklenen Günlükler halkası için toplam belirteçleri ve tahmini liste fiyatı maliyetini görmek üzere donuk görüşme/oturum kimliğine göre (istemci bir tane gönderdiğinde) filtreleyin. | | **Kullanım / Hata Ayıklama** | Belirteç kullanımı kapsamını ve eğilimlerini inceleyin veya isteğe bağlı sağlayıcı aktarımı ve kullanım çıkarma tanılamalarını etkinleştirin. | | **Depolama** | Salt okunur CODEX_HOME disk dökümü (oturumlar, arşivler, DB'ler, ekler). İsteğe bağlı arşivlenmiş temizleme: en eski %N'yi önizleyin, ardından `CODEX_HOME/.trash` konumuna karantinaya alın (varsayılan) veya açık bir onay kutusu arkasında kalıcı olarak silin. **Otomatik temizleme politikası** isteğe bağlıdır ve **varsayılan olarak KAPALIDIR** (`storageCleanupPolicy.enabled`); Depolama sayfasında eşik/hedef/zamanlama/mod yapılandırın veya **Şimdi çalıştır (Run now)**'ı tetikleyin. Karantinaya alınan girdiler Depolama sayfasından geri yüklenebilir (JSONL + iş parçacıkları). Aktif oturumlar salt okunur kalır. Codex en yeni/aktif `state_*.sqlite` dosyasını kilitli tuttuğu sürece temizleme ve geri yükleme reddedilir. | -| **Durdur** | Proxy'yi ve kurulu arka plan servisini zarif bir şekilde durdurun, yerel Codex'i geri yükleyin ve çıkın (`POST /api/stop`). | +| **Durdur** | Proxy'yi ve kurulu arka plan servisini zarif bir şekilde durdurun, yerel Codex'i geri yükleyin ve çıkın (`POST /api/stop`). Windows'ta Görev Zamanlayıcı arka ucunda panel reddeder ve `ocx stop` çalıştırmanızı ister: görev bittikten sonra sarmalayıcı proxy'yi yeniden başlatabilir ve bu yeniden başlatma penceresini istemci yapılandırmanız geri yüklenmeden önce yalnızca proxy dışında çalışan bir stop doğrulayabilir. Reddedildiğinde hiçbir şey değiştirilmez. | ### Bir bölüme bağlantı verme @@ -245,7 +245,7 @@ noktalar şunları içerir: | `POST /api/codex-auth/login` · `GET /api/codex-auth/login-status` | Tarayıcı girişi aracılığıyla bir havuz hesabı ekleyin. | | `GET /api/logs?tail=50&limit=20&offset=0&provider=...&status=5xx` | İsteğe bağlı kuyruk, sağlayıcı ve tam/sınıf durum filtreleriyle son istek meta verilerini okuyun. `limit`/`offset` ile sayfalama en yeni satırdan geriye doğru ilerler (`offset=0` en son sayfayı döndürür). Yanıt şekli: `{ timeZone, total, logs }` burada `total`, sayfalamadan önceki filtrelenmiş satır sayısıdır. | | `GET` / `PUT /api/subagent-models` | Öne çıkan beş `spawn_agent` geçersiz kılma modelini okuyun veya ayarlayın. | -| `POST /api/stop` | Proxy'yi/servisi durdurun, yerel Codex'i geri yükleyin ve çıkın. | +| `POST /api/stop` | Proxy'yi/servisi durdurun, yerel Codex'i geri yükleyin ve çıkın. Windows Görev Zamanlayıcı arka ucunda `respawnable_service`, bu durum okunamadığında `service_state_unknown` ile reddedilir; her iki durumda da hiçbir şey değiştirilmez. | :::tip Kontrol panelinden **Ollama Cloud** veya başka bir katalog sağlayıcısı eklemek, diff --git a/docs-site/src/content/docs/tr/reference/cli/lifecycle.md b/docs-site/src/content/docs/tr/reference/cli/lifecycle.md index ff9089357e..425380b2fd 100644 --- a/docs-site/src/content/docs/tr/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/tr/reference/cli/lifecycle.md @@ -37,8 +37,7 @@ ocx start --port 8080 Çalışan proxy'yi (PID'ye göre) durdurun, PID dosyasını kaldırın ve yerel Codex'i geri yükleyin. Yönetilen bir arka plan servisi kuruluysa `ocx stop` proxy'yi -yeniden oluşturamaması için önce onu da durdurur. Aynı eylem web kontrol -panelinin **Durdur** düğmesinden de (`POST /api/stop`) kullanılabilir. +yeniden oluşturamaması için önce onu da durdurur. Web kontrol panelinin **Durdur** düğmesi aynı eylemi (`POST /api/stop`) Windows Görev Zamanlayıcı dışındaki tüm arka uçlarda çalıştırır: orada görev bittikten sonra sarmalayıcı proxy'yi yeniden başlatabilir, bu yüzden panel `respawnable_service` ile reddeder, hiçbir şeyi değiştirmez ve `ocx stop` çalıştırmanızı ister. ### `ocx restart` diff --git a/docs-site/src/content/docs/tr/reference/management-api.md b/docs-site/src/content/docs/tr/reference/management-api.md index cc5c293345..0ce4456cd9 100644 --- a/docs-site/src/content/docs/tr/reference/management-api.md +++ b/docs-site/src/content/docs/tr/reference/management-api.md @@ -240,7 +240,7 @@ dolaşmamalıdır. Depoya yıldız verip vermeyeceğini kullanıcı seçmelidir. | --- | --- | --- | | `GET /api/system/memory` | Skaler süreç, yığın (heap), akış, yanıt durumu, denetleyici ve aktif tur metriklerini döndürün | — | | `POST /api/system/restart` | İstemci enjeksiyonunu kaldırmadan boşaltma duyarlı bir süreç yeniden başlatması başlatın | 202 döndürür; tekrarlanan çağrılar mevcut boşaltmayı bildirir | -| `POST /api/stop` | Servisi durdurun, yerel Codex'i geri yükleyin, yönetilen Grok enjeksiyonunu kaldırın ve proxy'yi boşaltın | 409 servis sahipliği çakışması | +| `POST /api/stop` | Servisi durdurun, yerel Codex'i geri yükleyin, yönetilen Grok enjeksiyonunu kaldırın ve proxy'yi boşaltın | 409 servis sahipliği çakışması; çağıran `ocx stop` değilken bir Windows Görev Zamanlayıcı sarmalayıcısı proxy'yi yeniden başlatabiliyorsa 409 `respawnable_service` (hiçbir şey değiştirilmez); kurulu yönetici durmayı reddederse 409; Görev Zamanlayıcı durumu okunamıyorsa 409 `service_state_unknown` (hiçbir şey değiştirilmez; sorguyu onarıp yeniden deneyin) | ### Codex kimlik doğrulama yetkilendirmesi diff --git a/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md b/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md index e8f7e42d21..1c8fe541d4 100644 --- a/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md +++ b/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md @@ -45,7 +45,7 @@ bun run dev:gui | **Logs** | 自动刷新近期请求,显示 token、请求强度以及(可用时)实际发送强度、实际模型、provider、状态、request id、耗时和错误详情。适配器发送 reasoning 参数时,详情中还会显示准确的 wire field。可按不透明会话/对话 ID(客户端提供时)筛选,并对当前已加载的 Logs 环形缓冲合计 token 与估算标价成本。 | | **Usage / Debug** | 查看 token usage 覆盖率与趋势,或启用可选的 provider transport 和 usage 提取诊断。 | | **Storage** | 只读查看 CODEX_HOME 磁盘占用(会话、归档、数据库、附件)。可选归档清理:预览最旧 N%,默认隔离到 `CODEX_HOME/.trash`,或勾选后永久删除。**自动清理策略**为可选且**默认关闭**(`storageCleanupPolicy.enabled`);可在 Storage 页配置阈值/目标/计划/模式,或点「立即运行」。可在 Storage 页从隔离区恢复(JSONL + 线程)。活动会话保持只读。Codex 锁定最新/活动的 `state_*.sqlite` 时拒绝清理与恢复。 | -| **Stop** | 优雅地停止代理和已安装的后台服务,恢复原生 Codex 并退出(`POST /api/stop`)。 | +| **Stop** | 优雅地停止代理和已安装的后台服务,恢复原生 Codex 并退出(`POST /api/stop`)。在使用任务计划程序后端的 Windows 上,仪表板会拒绝并提示改用 `ocx stop`:任务结束后包装器仍可能重新拉起代理,只有运行在代理之外的 stop 才能在恢复客户端配置前确认这个重启窗口。被拒绝时不会做任何更改。 | ### 链接到某个部分 @@ -139,7 +139,7 @@ GUI 是代理 JSON 管理 API 之上的轻量客户端。常用 endpoint 包括 | `POST /api/codex-auth/login` · `GET /api/codex-auth/login-status` | 通过浏览器登录添加池账号。 | | `GET /api/logs?tail=50&limit=20&offset=0&provider=...&status=5xx` | 使用 tail、provider、精确状态码或状态类别筛选近期请求元数据。`limit`/`offset` 从最新一行向前分页(`offset=0` 为最新一页)。响应为 `{ timeZone, total, logs }`,其中 `total` 为分页前的匹配行数。 | | `GET` / `PUT /api/subagent-models` | 读取或设置五个置顶的 `spawn_agent` override 模型。 | -| `POST /api/stop` | 停止代理/服务,恢复原生 Codex 并退出。 | +| `POST /api/stop` | 停止代理/服务,恢复原生 Codex 并退出。在 Windows 任务计划程序后端会以 `respawnable_service` 拒绝,无法读取该状态时以 `service_state_unknown` 拒绝;两种情况都不会做任何更改。 | :::tip 从仪表盘添加 **Ollama Cloud** 或其他目录型 provider 时,其文本/视觉模型分类会写入保存的 diff --git a/docs-site/src/content/docs/zh-cn/reference/cli/lifecycle.md b/docs-site/src/content/docs/zh-cn/reference/cli/lifecycle.md index 277b14981b..d6f5c6d219 100644 --- a/docs-site/src/content/docs/zh-cn/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/zh-cn/reference/cli/lifecycle.md @@ -24,7 +24,7 @@ ocx start --port 8080 ### `ocx stop` -停止正在运行的代理(按 PID),移除 PID 文件,并恢复原生 Codex。如果安装了受管后台服务,`ocx stop` 还会先停止该服务,这样它就无法重新拉起代理。Web 仪表盘中的 **Stop** 按钮也提供同样的操作(`POST /api/stop`)。 +停止正在运行的代理(按 PID),移除 PID 文件,并恢复原生 Codex。如果安装了受管后台服务,`ocx stop` 还会先停止该服务,这样它就无法重新拉起代理。Web 仪表盘的 **Stop** 按钮在多数后端执行同样的操作(`POST /api/stop`),但 Windows 任务计划程序除外:任务结束后包装器仍可能重新拉起代理,只有运行在代理之外的 stop 才能在恢复客户端配置前确认这个重启窗口,因此仪表盘会以 `respawnable_service` 拒绝、不做任何更改,并提示改用 `ocx stop`。 ### `ocx restart` diff --git a/docs-site/src/content/docs/zh-cn/reference/management-api.md b/docs-site/src/content/docs/zh-cn/reference/management-api.md index 09910a7a46..fe3568e3f0 100644 --- a/docs-site/src/content/docs/zh-cn/reference/management-api.md +++ b/docs-site/src/content/docs/zh-cn/reference/management-api.md @@ -195,7 +195,7 @@ Authorization: Bearer <admin-token> | --- | --- | --- | | `GET /api/system/memory` | 返回标量级的进程、堆、流、响应状态、看门狗和活跃回合指标 | — | | `POST /api/system/restart` | 在不移除客户端注入的情况下,开始一次考虑排空的进程重启 | 返回 202;重复调用会报告现有排空 | -| `POST /api/stop` | 停止服务、恢复原生 Codex、移除受管 Grok 注入并排空代理 | 409 服务所有权冲突 | +| `POST /api/stop` | 停止服务、恢复原生 Codex、移除受管 Grok 注入并排空代理 | 409 服务所有权冲突;当 Windows 任务计划程序包装器可能重新拉起代理且调用方不是 `ocx stop` 时返回 409 `respawnable_service`(不会做任何更改);已安装的管理器拒绝停止时返回 409;无法读取任务计划程序状态时返回 409 `service_state_unknown`(不会做任何更改;修复查询后重试) | ### Codex 身份验证委托 diff --git a/docs-site/src/content/docs/zh-tw/guides/web-dashboard.md b/docs-site/src/content/docs/zh-tw/guides/web-dashboard.md index 67828d56b0..4b22882f66 100644 --- a/docs-site/src/content/docs/zh-tw/guides/web-dashboard.md +++ b/docs-site/src/content/docs/zh-tw/guides/web-dashboard.md @@ -49,7 +49,7 @@ GUI session 簽發到服務的頁面中,並在到期或代理重啟時靜默 | **Models** | 開關原生 GPT 與路由模型,設定 provider allowlist、上下文上限、v1/base/v2 以及 v2 thread 數量。 | | **Logs** | 自動重新整理近期請求,顯示 token、請求強度、實際模型、provider、狀態、request id、耗時和錯誤詳情。 | | **Usage / Debug** | 檢視 token usage 覆蓋率與趨勢,或啟用可選的 provider transport 和 usage 提取診斷。 | -| **Stop** | 優雅地停止代理和已安裝的後臺服務,恢復原生 Codex 並退出(`POST /api/stop`)。 | +| **Stop** | 優雅地停止代理和已安裝的後臺服務,恢復原生 Codex 並退出(`POST /api/stop`)。在使用工作排程器後端的 Windows 上,儀表板會拒絕並提示改用 `ocx stop`:工作結束後包裝程序仍可能重新啟動 Proxy,只有執行在 Proxy 之外的 stop 才能在還原用戶端設定前確認這個重啟視窗。被拒絕時不會做任何變更。 | ### 連結到某個部分 @@ -135,7 +135,7 @@ GUI 是代理 JSON 管理 API 之上的輕量用戶端。常用 endpoint 包括 | `POST /api/codex-auth/login` · `GET /api/codex-auth/login-status` | 透過瀏覽器登入新增池帳號。 | | `GET /api/logs?tail=50&provider=...&status=5xx` | 使用 tail、provider、精確狀態碼或狀態類別篩選近期請求後設資料。 | | `GET` / `PUT /api/subagent-models` | 讀取或設定五個置頂的 `spawn_agent` override 模型。 | -| `POST /api/stop` | 停止代理/服務,恢復原生 Codex 並退出。 | +| `POST /api/stop` | 停止代理/服務,恢復原生 Codex 並退出。在 Windows 工作排程器後端會以 `respawnable_service` 拒絕,無法讀取該狀態時以 `service_state_unknown` 拒絕;兩種情況都不會做任何變更。 | :::tip 從儀表板新增 **Ollama Cloud** 或其他目錄型 provider 時,其文字/視覺模型分類會寫入儲存的 diff --git a/docs-site/src/content/docs/zh-tw/reference/cli/lifecycle.md b/docs-site/src/content/docs/zh-tw/reference/cli/lifecycle.md index a8fd9f5c08..11fa127e23 100644 --- a/docs-site/src/content/docs/zh-tw/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/zh-tw/reference/cli/lifecycle.md @@ -24,7 +24,7 @@ ocx start --port 8080 ### `ocx stop` -停止執行中的代理(依 PID)、移除 PID 檔案,並還原原生 Codex。若已安裝受管背景服務,`ocx stop` 也會先停止它,使其無法重新生成代理。相同動作亦可從網頁儀表板的 **Stop** 按鈕執行(`POST /api/stop`)。 +停止執行中的代理(依 PID)、移除 PID 檔案,並還原原生 Codex。若已安裝受管背景服務,`ocx stop` 也會先停止它,使其無法重新生成代理。網頁儀表板的 **Stop** 按鈕在多數後端執行相同動作(`POST /api/stop`),但 Windows 工作排程器除外:工作結束後包裝程序仍可能重新啟動 Proxy,因此儀表板會以 `respawnable_service` 拒絕、不做任何變更,並請你改用 `ocx stop`。 ### `ocx restart` diff --git a/docs-site/src/content/docs/zh-tw/reference/management-api.md b/docs-site/src/content/docs/zh-tw/reference/management-api.md index 3c996662cb..5dc158a98d 100644 --- a/docs-site/src/content/docs/zh-tw/reference/management-api.md +++ b/docs-site/src/content/docs/zh-tw/reference/management-api.md @@ -195,7 +195,7 @@ Session 簽發在需要 data-plane 認證時停用,這包含遠端綁定。遠 | --- | --- | --- | | `GET /api/system/memory` | 回傳純量行程、heap、串流、回應狀態、看門狗與活躍回合指標 | — | | `POST /api/system/restart` | 在不移除客戶端注入的情況下開始感知排空的行程重啟 | 回傳 202;重複呼叫回報既有的排空 | -| `POST /api/stop` | 停止服務、還原原生 Codex、移除受管 Grok 注入並排空代理 | 409 服務擁有權衝突 | +| `POST /api/stop` | 停止服務、還原原生 Codex、移除受管 Grok 注入並排空代理 | 409 服務擁有權衝突;當 Windows 工作排程器包裝程序可能重新啟動 Proxy 且呼叫端不是 `ocx stop` 時回傳 409 `respawnable_service`(不會做任何變更);已安裝的管理器拒絕停止時回傳 409;無法讀取工作排程器狀態時回傳 409 `service_state_unknown`(不會做任何變更;修復查詢後重試) | ### Codex 認證委派 diff --git a/src/cli/dispatch.ts b/src/cli/dispatch.ts index e4aa52f465..e21536ce89 100644 --- a/src/cli/dispatch.ts +++ b/src/cli/dispatch.ts @@ -121,14 +121,31 @@ const commandRunners: Record<string, CommandRunner> = { if (desired.status === "unchanged") { const { classifyNativeRoutedResidue } = await import("../codex/native-residue"); if (classifyNativeRoutedResidue().kind === "clean") { - const alreadyOff = "Codex integration is already OFF and native; no Codex files changed."; + // The Codex half being a no-op says nothing about the Grok half. Returning here + // without stripping the fence meant `ocx restore` could report success while Grok + // still pointed at a stopped proxy — and the deferred-teardown recovery path + // (#3008) tells operators to run exactly this command before deleting a receipt, + // so the incomplete teardown would be signed off and the obligation erased. + let grokNote = ""; + let grokCode = 0; + try { + const g = stripGrokConfig(); + if (g.changed) grokNote = ` ${g.message}`; + else if (!g.ok) { grokNote = ` Grok config cleanup failed: ${g.message}`; grokCode = 1; } + } catch (err) { + grokNote = ` Grok config cleanup failed: ${err instanceof Error ? err.message : String(err)}`; + grokCode = 1; + } + const alreadyOff = `Codex integration is already OFF and native; no Codex files changed.${grokNote}`; if (restoreJson) { const { skippedRestoreEnvelope } = await import("../codex/inject"); - console.log(JSON.stringify(skippedRestoreEnvelope(true, alreadyOff))); - } else { + console.log(JSON.stringify(skippedRestoreEnvelope(grokCode === 0, alreadyOff))); + } else if (grokCode === 0) { console.log(alreadyOff); + } else { + console.error(alreadyOff); } - return 0; + return grokCode; } } let r: { success: boolean; message: string }; @@ -137,26 +154,40 @@ const commandRunners: Record<string, CommandRunner> = { } catch (err) { r = { success: false, message: err instanceof Error ? err.message : String(err) }; } + // Grok BEFORE either output. The JSON path used to return here, so `ocx restore --json` + // (and `ocx eject --json`, the same runner) could report success while the fence still + // pointed at the stopped proxy — and the deferred-teardown recovery on this branch + // tells operators to run exactly this before deleting a receipt (#3008). + let grokFailure: string | null = null; + let grokChangedMessage: string | null = null; + try { + const g = stripGrokConfig(); + if (g.changed) grokChangedMessage = g.message; + else if (!g.ok) grokFailure = g.message; + } catch (err) { + grokFailure = err instanceof Error ? err.message : String(err); + } if (restoreJson) { // Spawned callers need the artifact-level result to distinguish a busy // history worker from a successful native restore. Keep stdout machine - // readable; human framing remains the default command contract. - console.log(JSON.stringify(r)); - return r.success ? 0 : 1; + // readable — the Codex artifact schema is unchanged; the Grok outcome is + // folded into success/message so a caller cannot read a half teardown as done. + const message = grokFailure + ? `${r.message} Grok config cleanup failed: ${grokFailure}` + : grokChangedMessage ? `${r.message} ${grokChangedMessage}` : r.message; + console.log(JSON.stringify({ ...r, success: r.success && !grokFailure, message })); + return r.success && !grokFailure ? 0 : 1; } if (r.success) console.log(`✅ ${r.message}`); else { console.error(`⚠️ ${r.message}`); } let code = r.success ? 0 : 1; - try { - const g = stripGrokConfig(); - if (g.changed) console.log(`✅ ${g.message}`); - else if (!g.ok) { - console.error(`⚠️ ${g.message}`); - code = 1; - } - } catch { /* best-effort */ } + if (grokChangedMessage) console.log(`✅ ${grokChangedMessage}`); + if (grokFailure) { + console.error(`⚠️ ${grokFailure}`); + code = 1; + } if (r.success) { console.log("Codex integration is OFF and plain `codex` now runs natively. Switch back with: ocx restore back"); } else { diff --git a/src/cli/index.ts b/src/cli/index.ts index 56abb5d05a..98e041a188 100755 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -2,6 +2,7 @@ import { spawn } from "node:child_process"; import { currentExternalCodexModelProvider, restoreNativeCodex, restoreNativeCodexAsync, shouldInjectApiAuthHeader } from "../codex/inject"; import { stripGrokConfig } from "../grok/inject"; +import { STOP_HISTORY_INCOMPLETE_EXIT_CODE } from "../update/stop-contract.mjs"; import { describeHistoryJobFailure, resolveCodexHistoryJobTarget, @@ -25,7 +26,16 @@ import { writePid, writeRuntimePort, } from "../config/process-state"; +import { + claimPendingTeardown, + clearPendingTeardown, + isPendingTeardownAbandoned, + listPendingTeardowns, + pendingTeardownPathFor, + quarantinePendingTeardown, +} from "../config/pending-teardown"; import { collectStatus, unusedProxyWarningLines } from "./status"; +import { endpointsToProve, everyEndpointProvenDown, sharedTeardownAuthorized, type UninstallObservation } from "./uninstall-plan"; import { takeFlag } from "./runtime-api"; import { @@ -44,9 +54,9 @@ import { findLiveProxy, probeHostname, type LiveProxy } from "../server/proxy-li import { createReadinessGate } from "../server/readiness"; import { runReady, type ReadyArgs } from "./ready"; import { runCli } from "./root"; -import { ProxyOwnershipRefusedError, stopProxy } from "../lib/process-control"; +import { isProcessAlive, ProxyOwnershipRefusedError, stopProxy } from "../lib/process-control"; import { loadServiceTokenFromFile } from "../lib/service-secrets"; -import { assertNotAdminToken, diagnoseService, isServiceOwnershipError, serviceCommand, serviceEnvironmentOwnedHere, serviceStartableFromTray, serviceStatusSummary, stopServiceIfInstalled, uninstallServiceIfInstalled } from "../service"; +import { assertNotAdminToken, diagnoseService, isServiceOwnershipError, proxyStillLiveAfterStop, serviceCommand, serviceEnvironmentOwnedHere, serviceStartableFromTray, serviceStatusSummary, stopServiceIfInstalledDetailed, uninstallServiceIfInstalled, uninstallServiceDetailed } from "../service"; import { formatStartupRoutingDetail, startupHealthSummary } from "../codex/autostart-health"; import { drainAndShutdown, isRecyclingForExit, startServer } from "../server"; import { injectSystemEnv, reconcileShellHook, revertSystemEnv, uninstallShellHook } from "../server/system-env"; @@ -631,17 +641,35 @@ async function handleRestartStartWhenStopped(): Promise<boolean | "skipped"> { return handleEnsure({ existingIsSuccess: false }); } -async function restoreSharedClientStateAfterStop(): Promise<boolean> { - let restored = true; +/** + * Restore shared client state after a stop. + * + * Returns the two failure kinds separately. `historyOnly` means teardown succeeded and + * only Codex history metadata could not be finalized: the proxy is down, the service is + * stopped, and a manifest is waiting for review. `other` means something that actually + * removes state a client depends on. + * + * The distinction exists because `ocx update` must proceed for the first and abort for the + * second, and it can only see an exit code (#3008). + */ +async function restoreSharedClientStateAfterStop(): Promise<{ historyOnly: boolean; other: boolean }> { + let historyOnly = false; + let other = false; try { const result = await restoreNativeCodexAsync(); if (result.success) console.log(`↩️ ${result.message}`); else { - restored = false; + // Codex history is the one restore whose failure leaves the runtime consistent: the + // manifest is retained and the routed metadata is untouched. Config and catalog are + // not — a client reads those, so their failure is a real teardown failure. + const artifacts = result.artifacts; + const configOrCatalogFailed = artifacts.config.state === "failed" || artifacts.catalog.state === "failed"; + if (!configOrCatalogFailed && artifacts.history.state === "failed") historyOnly = true; + else other = true; console.error(`⚠️ ${result.message}`); } } catch (error) { - restored = false; + other = true; console.error(`⚠️ Native Codex restore failed: ${error instanceof Error ? error.message : String(error)}`); } @@ -649,16 +677,58 @@ async function restoreSharedClientStateAfterStop(): Promise<boolean> { try { const grok = stripGrokConfig(); if (grok.changed) console.log(`↩️ ${grok.message}`); - else if (!grok.ok) { restored = false; console.error(`⚠️ ${grok.message}`); } + else if (!grok.ok) { other = true; console.error(`⚠️ ${grok.message}`); } } catch (error) { - restored = false; + other = true; console.error(`⚠️ Grok config restore failed: ${error instanceof Error ? error.message : String(error)}`); } - return restored; + return { historyOnly, other }; } async function handleStop() { + // The receipt must name the endpoint the owner was stopping — an obligation nobody can + // locate cannot be proven discharged. Only the runtime record knows it; a proxy started + // with an explicit --port is not on the configured one. + const endpointOf = (runtime: { port: number; hostname?: string } | null): { hostname: string; port: number } | null => + runtime?.port ? { hostname: runtime.hostname ?? "127.0.0.1", port: runtime.port } : null; + // Last-resort endpoint for a receipt: the address this home is configured to serve on, + // which is what a later recovery probe would ask about anyway. + const configuredEndpoint = (): { hostname: string; port: number } => { + try { + const config = loadConfig(); + return { + hostname: config.hostname ?? "127.0.0.1", + port: typeof config.port === "number" && config.port > 0 ? config.port : 10100, + }; + } catch { + return { hostname: "127.0.0.1", port: 10100 }; + } + }; + // Only a definitive "nothing is answering" authorizes finishing somebody else's + // abandoned teardown. The tri-state probe distinguishes that from "we could not tell" + // (timeout, a listener that withholds /healthz), which `findLiveProxy` collapses into + // the same null (#3008). + const abandonedTeardownIsSafeToFinish = async ( + endpoint: { hostname: string; port: number } | null, + ): Promise<boolean> => { + // The endpoint has to come from the receipt. A crashed owner usually leaves no + // runtime-port record, and the configured port is the wrong question for a proxy + // started with an explicit --port: it refuses while the live one keeps serving. + // An obligation that cannot name its endpoint cannot be proven discharged. + if (!endpoint) return false; + try { + const { probeProxyLiveness } = await import("../update/proxy-liveness-probe.mjs"); + return probeProxyLiveness(endpoint.port, endpoint.hostname) === "dead"; + } catch { + // A probe that could not run is not evidence of absence. + return false; + } + }; let stopFailed = false; + let historyOnlyFailure = false; + // Only Task Scheduler respawns after a successful stop (#764), so only it earns the + // restart-window wait; launchd, systemd and WinSW are down when they say so. + let schedulerCanRespawn = false; let stoppedService = false; // An ownership mismatch means the service manager was never even contacted: the installed // service is still live and will respawn the proxy. Tearing down SHARED state in that @@ -666,9 +736,81 @@ async function handleStop() { // service — the exact failure this flag prevents. A plain stop failure is different: we // tried, so local teardown still proceeds. let ownershipBlocked = false; + // Deferring shared teardown to this process is an obligation, so record it on disk + // before asking for it (#3008). A parent that dies mid-stop would otherwise leave the + // client config routed at a proxy that is already gone, with nothing to find later. + // + // `inheritedTeardowns` is the inverse case: PREVIOUS stops that left obligations + // unfinished. Snapshot them BEFORE this run claims anything, so this run's own receipt + // is never mistaken for one it inherited. + const inheritedTeardowns = listPendingTeardowns() + .filter(read => isPendingTeardownAbandoned(read, isProcessAlive)); + let teardownNonce: string | undefined; + const claimTeardown = (endpoint: { hostname: string; port: number }, endpointSource: "exact" | "guessed") => { + if (teardownNonce) return; + try { + teardownNonce = claimPendingTeardown(endpoint, endpointSource).nonce; + } catch (err) { + // Without a receipt the proxy performs its own teardown, which is the pre-#3008 + // behaviour: correct for every backend that cannot respawn, and merely early for + // Task Scheduler. Losing the deferral is far better than losing the stop. + console.warn(`⚠️ Could not record the deferred-teardown receipt: ${err instanceof Error ? err.message : String(err)}`); + } + }; + /** + * One stop target, used for BOTH the receipt and the request. + * + * Deriving them separately meant the receipt could name a different endpoint than the + * one actually contacted, and a proxy with no runtime record got no receipt at all — + * silently reopening the parent-crash window on the path where the stop is a hard kill + * and no child teardown runs at all. + * + * So the caller supplies whatever endpoint it already discovered: the orphan path knows + * one from `findLiveProxy` even when the runtime record is gone. + * + * When nothing resolves, the graceful request cannot be made at all — `stopProxy` goes + * straight to the kill ladder, no child teardown runs, and there is no receipt to leave + * behind. A warning does not make that durable, so the receipt is claimed FIRST against + * the endpoint this process would restore anyway. It is the configured listen address, + * which is the same address every recovery probe would ask about, and an obligation + * recorded against it is strictly better than none: at worst the probe cannot confirm + * A guessed endpoint is NOT evidence, so the receipt records which kind it holds: a + * guessed one fails closed into manual recovery rather than letting a later probe read + * "the configured port refuses" as proof that the right proxy is down. + */ + const stopWithDeferral = async (pid: number, discovered?: { hostname: string; port: number } | null): Promise<void> => { + // Resolve ONCE. Reading the runtime record twice let the receipt name the configured + // guess while the request went to a runtime endpoint that appeared in between. + const exact = discovered ?? endpointOf(readRuntimePort(pid)); + claimTeardown(exact ?? configuredEndpoint(), exact ? "exact" : "guessed"); + await stopProxy(pid, { + deferSharedTeardownNonce: teardownNonce, + // Only an exact endpoint may direct the request; the configured fallback is a guess + // good enough to record an obligation against, not to POST a stop to. + runtimeEndpoint: exact ?? undefined, + }); + }; try { - stoppedService = stopServiceIfInstalled(); - if (stoppedService) console.log("🛑 Service manager stopped (won't respawn)."); + const serviceStop = stopServiceIfInstalledDetailed(); + stoppedService = serviceStop === "stopped" || serviceStop === "stopped-respawnable"; + schedulerCanRespawn = serviceStop === "stopped-respawnable"; + // No "won't respawn" claim here: a stopped Task Scheduler can still respawn through + // its wrapper, which the verification below is what actually settles. + if (stoppedService) console.log("🛑 Service manager stopped."); + if (serviceStop === "failed") { + // A manager that would not stop can respawn the proxy. That is a real stop failure, + // not a history-only one, and an update must not replace files over it (#3008). + stopFailed = true; + console.error("❌ The installed service manager did not stop; it may respawn the proxy."); + } + if (serviceStop === "state-unknown") { + // Nothing refused to stop — the scheduler state could not be READ. Saying "did not + // stop" sends the operator looking for the wrong problem, and `/api/stop` answers + // the same case with service_state_unknown. + stopFailed = true; + console.error("❌ The Windows Task Scheduler state could not be read, so this stop cannot tell whether a wrapper would respawn the proxy."); + console.error(" Run 'ocx service status' to see the query error, repair Task Scheduler access, then retry."); + } } catch (err) { if (isServiceOwnershipError(err)) { ownershipBlocked = true; @@ -685,7 +827,11 @@ async function handleStop() { try { // Graceful-first (management-API drain) — on Windows this is the only path where // the proxy's shutdown handlers actually run; taskkill /F is the fallback inside. - await stopProxy(pid); + // Shared teardown is deferred to this process: it happens after the respawn + // verification below, so a survivor does not get its client config pulled first. + // The receipt goes down first — the proxy honours the deferral only when it can + // see one, so an unrecordable claim degrades to the child doing its own teardown. + await stopWithDeferral(pid); console.log(`✅ Proxy (PID ${pid}) stopped.`); removePid(pid); removeRuntimePort(pid); @@ -713,7 +859,9 @@ async function handleStop() { const live = await findLiveProxy(); if (live?.pid) { try { - await stopProxy(live.pid); + // The probe already found where it answers, and on this path the runtime record is + // typically what went missing in the first place. + await stopWithDeferral(live.pid, { hostname: live.hostname ?? "127.0.0.1", port: live.port }); console.log(`✅ Proxy (PID ${live.pid}) stopped.`); } catch (err) { stopFailed = true; @@ -725,6 +873,17 @@ async function handleStop() { console.error(" Skipping shared teardown (native Codex restore, Grok config): the foreign proxy is still running."); } } + } else if (live) { + // Identity-confirmed live, but no PID this process can kill: a legacy /healthz that + // reports no pid, or a pid that failed verification. Treating that as "nothing is + // running" purges the state records and then restores shared client config out from + // under a proxy that is still serving — the exact failure the deferral exists to + // prevent, arrived at from the other direction. + stopFailed = true; + ownershipBlocked = true; + console.error(`❌ A proxy is answering on port ${live.port}, but no process id could be resolved for it, so it cannot be stopped from here.`); + console.error(" Skipping shared teardown: restoring client config while it serves would leave both pointing at each other."); + console.error(" Stop it from the home that started it, or end the process manually, then rerun 'ocx stop'."); } else if (!stoppedService) { console.log("No running proxy found."); } @@ -739,16 +898,159 @@ async function handleStop() { // Environment ownership is independent from service ownership. Always roll back // current-home variables; the helper refuses foreign markers on its own. try { revertSystemEnv(); } catch { /* best-effort */ } - if (!ownershipBlocked) { - if (!await restoreSharedClientStateAfterStop()) stopFailed = true; + // A stopped Windows scheduler is not a proven-down proxy. `killWindowsSchedulerWrappers` + // is explicitly best-effort and the `:loop` wrapper respawns its child after ~5s, so an + // immediate probe can see a dead interval and an update can start replacing files right + // before the proxy comes back. Poll across the restart window before this stop is allowed + // to report anything but failure (#3008) — and ONLY for that backend, since making every + // launchd and systemd stop wait seven seconds would be a regression in ordinary use. + if (schedulerCanRespawn && !ownershipBlocked) { + const survivor = await proxyStillLiveAfterStop({ canRespawn: true }); + if (survivor) { + stopFailed = true; + console.error(`❌ A proxy is still listening on port ${survivor.port} after the service stop; it is being respawned.`); + console.error(" Skipping shared teardown: restoring client config while the proxy runs leaves both pointing at each other."); + ownershipBlocked = true; + } + } + // Recovering somebody else's abandoned obligation is not the same act as finishing this + // run's own. This run stopped a proxy and verified the result; the inherited case has no + // such evidence, and `findLiveProxy` returning null covers a timeout and a malformed + // answer as well as a genuinely dead port. Restoring client config under a proxy that is + // merely unresponsive is exactly the failure the deferral exists to prevent. + // + // So an inherited obligation this run did not claim GATES the restore itself, rather + // than only labelling it: without a definitive "dead" from the tri-state probe, the + // restore does not run, the receipt stays for the next stop, and the stop fails. A + // warning that lets the restore happen anyway is not a gate. + // + // An UNREADABLE obligation is a third case. It names no endpoint, so nothing can ever + // prove its proxy down. It is NOT waved through: it fails this stop and is set aside + // only afterwards, so the operator gets an explicit manual step instead of a silent + // restore backed by no evidence. Setting it aside is still necessary — left in place it + // makes both updater gates run a stop that fails on it every time, which is an update + // that can never proceed. + // + // Inherited obligations are evaluated whether or not this run claimed its own. A stop + // that finds a live proxy used to skip them entirely, so older abandoned receipts + // accumulated forever while each run cleared only its own nonce. + const recoveredNonces: string[] = []; + const unreadable: { nonce: string }[] = []; + let inheritedBlocks = false; + if (inheritedTeardowns.length > 0 && !ownershipBlocked) { + for (const read of inheritedTeardowns) { + if (read.state === "unscannable") { + // No file, no nonce: nothing to quarantine and nothing to remove. The home itself + // may be hiding an obligation, so block and ask for the directory to be fixed. + inheritedBlocks = true; + stopFailed = true; + console.error(`❌ ${read.detail}, so this stop cannot tell whether a shared teardown is still owed.`); + console.error(" Skipping shared teardown. Fix access to the opencodex home, then rerun 'ocx stop'."); + continue; + } + if (read.state === "invalid") { + unreadable.push(read); + inheritedBlocks = true; + stopFailed = true; + console.error(`❌ A pending-teardown receipt could not be read (${read.detail}).`); + console.error(" It names no endpoint, so this stop cannot prove the proxy it belonged to is down."); + console.error(" Confirm no proxy is running, then rerun 'ocx stop' to complete the teardown."); + continue; + } + if (read.receipt.endpointSource === "guessed") { + // The recorded address is the configured one, not the one that stop contacted. A + // proxy on an explicit --port can be respawned there while this address refuses, + // so "dead" here proves nothing and must not authorize a restore. + inheritedBlocks = true; + stopFailed = true; + console.error("❌ A shared teardown from an earlier stop is outstanding, but that stop could not record the address it was stopping."); + console.error(` Only the configured address (${read.receipt.endpoint.hostname}:${read.receipt.endpoint.port}) was recorded, which cannot prove the right proxy is down.`); + console.error(` Confirm no proxy is running, then run 'ocx restore' and remove ${pendingTeardownPathFor(read.receipt.nonce)}.`); + continue; + } + if (await abandonedTeardownIsSafeToFinish(read.receipt.endpoint)) { + recoveredNonces.push(read.receipt.nonce); + continue; + } + inheritedBlocks = true; + stopFailed = true; + console.error(`❌ A shared teardown from an earlier stop is still outstanding, and the proxy on ${read.receipt.endpoint.hostname}:${read.receipt.endpoint.port} could not be confirmed down.`); + console.error(" Skipping shared teardown: restoring client config under a proxy that may still be running is what the deferral exists to prevent."); + console.error(" The obligation is preserved; retry once the proxy is confirmed stopped."); + } + } + const restoreBlocked = ownershipBlocked || inheritedBlocks; + if (!restoreBlocked) { + if (recoveredNonces.length > 0) { + // A previous deferred stop died before restoring, and the probe says its endpoint is + // not answering. That is the whole point of leaving the receipt behind. + console.log("↩️ Finishing a shared teardown left unfinished by an earlier stop."); + } + const restore = await restoreSharedClientStateAfterStop(); + if (restore.other) stopFailed = true; + else if (restore.historyOnly) historyOnlyFailure = true; + // The obligation is discharged whether or not history metadata finalized: config and + // catalog are what a client reads, and `restore.other` already fails the stop. + // + // Each nonce names its own file, so a clear can only ever remove the obligation it + // names — never one a concurrent stop wrote. Both this run's claim and every inherited + // receipt it proved discharged are released together. + if (!restore.other) { + const discharged = teardownNonce ? [teardownNonce, ...recoveredNonces] : recoveredNonces; + for (const nonce of discharged) { + // A receipt that survives its discharge re-triggers recovery forever, so a failed + // removal is surfaced rather than swallowed. + if (!clearPendingTeardown(nonce)) { + stopFailed = true; + console.error(`❌ The shared teardown finished, but its receipt could not be removed: ${pendingTeardownPathFor(nonce)}`); + console.error(" Remove it manually; otherwise every later stop and update will try to recover it again."); + } + } + } + } + // Set an unreadable receipt aside only AFTER the outcome is known. Renaming it earlier + // would take it out of the recovery loop while the restore it stood for had not run. + // + // Setting aside is NOT discharging. The renamed file still counts as an outstanding + // obligation (`isAnyTeardownObligationFileName`), so both updaters keep refusing to + // install until an operator removes it — the rename only stops every later stop from + // re-reading the same garbage. Skipped under `ownershipBlocked` because a foreign + // service still owns this state and none of it is ours to move. + if (unreadable.length > 0 && !ownershipBlocked) { + for (const read of unreadable) { + const moved = quarantinePendingTeardown(read.nonce); + if (moved) { + console.error(`⚠️ That unreadable receipt was set aside at ${moved}. It still blocks 'ocx update', and 'ocx stop' has NOT restored on its behalf.`); + console.error(" To clear it: confirm no proxy is running, run 'ocx restore', then delete that file."); + } else { + console.error(`❌ It could not be set aside either: ${pendingTeardownPathFor(read.nonce)}. Remove it manually after running 'ocx restore'.`); + } + } } - // Set the code rather than exiting inline: `restart` and the tray coordinator call this - // function and need it to RETURN so they can decide what to do next. + // Set the code rather than exiting inline: this function returns a value its dispatcher + // reads, so exiting here would take that decision away from the caller. + // + // A history-only failure gets its own code so `ocx update` can tell "the proxy is down + // and a manifest needs review" from "the proxy would not stop" (#3008). Ordinary failure + // still wins: it is the stronger signal. if (stopFailed) process.exitCode = 1; + else if (historyOnlyFailure) process.exitCode = STOP_HISTORY_INCOMPLETE_EXIT_CODE; return !stopFailed; } async function handleUninstall() { + /** Definitive "nothing is answering" on the endpoint this home would serve. */ + const proxyEndpointProvenDown = async (): Promise<boolean> => { + try { + const { probeProxyLiveness } = await import("../update/proxy-liveness-probe.mjs"); + // Every candidate, not just the preferred one: a stale runtime record pointing at a + // closed port would otherwise "prove" a live proxy on the configured port is gone. + const endpoints = endpointsToProve(readRuntimePort(), loadConfig()); + return everyEndpointProvenDown(endpoints, e => probeProxyLiveness(e.port, e.hostname)); + } catch { + return false; + } + }; const failures: string[] = []; const runStep = async (label: string, step: () => void | boolean | Promise<void | boolean>) => { @@ -762,18 +1064,89 @@ async function handleUninstall() { } }; - await runStep("service stopped", () => stopServiceIfInstalled()); + // Consume the DETAILED outcome. The boolean helper returns false for "not installed", + // "refused to stop" and "state could not be read" alike, so this step used to print + // "not installed" for a manager that might still be running and then tear down shared + // config underneath it (#3008). + // The authorization rule lives in `uninstall-plan` so it can be exercised for every + // failure permutation by calling it, rather than by reading this function's source. + const observed: UninstallObservation = { + serviceStop: null, + proxyProvenDown: false, + serviceRemoval: null, + respawnWindowVerified: false, + }; + await runStep("service stopped", () => { + const outcome = stopServiceIfInstalledDetailed(); + observed.serviceStop = outcome; + if (outcome === "absent") return false; + if (outcome === "failed") { + throw new Error("the installed service manager did not stop; it may respawn the proxy"); + } + if (outcome === "state-unknown") { + throw new Error("the Windows Task Scheduler state could not be read, so this uninstall cannot tell whether a manager is still running. Run 'ocx service status' to see the query error"); + } + return true; + }); await runStep("proxy stopped", async () => { const pid = readPid(); - if (!pid) return false; + if (!pid) { + // A missing pid file is not proof that nothing is serving: a proxy can outlive its + // record (crash, manual delete, corrupt file), which is exactly why `ocx stop` falls + // back to identity-checked discovery. Without this, uninstall restored shared config + // and reported success while that proxy kept running (#3008). + const live = await findLiveProxy(); + if (!live) { + // A miss is not proof: `findLiveProxy` collapses a timeout and a transport failure + // into the same null as a dead endpoint. Ask the tri-state probe, which only says + // "dead" for a refused connection or a definitive non-OpenCodex answer (#3008). + observed.proxyProvenDown = await proxyEndpointProvenDown(); + if (!observed.proxyProvenDown) { + throw new Error("no proxy could be found, but its endpoint could not be confirmed down either; confirm nothing is serving, then rerun"); + } + return false; + } + if (!live.pid) { + throw new Error(`a proxy is answering on port ${live.port} but no process id could be resolved for it; stop it from the home that started it, then rerun`); + } + await stopProxy(live.pid); + observed.proxyProvenDown = true; + return true; + } await stopProxy(pid); removePid(pid); removeRuntimePort(pid); + observed.proxyProvenDown = true; return true; }); - await runStep("service removed", () => uninstallServiceIfInstalled()); + await runStep("service removed", () => { + const outcome = uninstallServiceDetailed(); + observed.serviceRemoval = outcome; + // "absent" and "removed" are both fine; a failure is not, and it used to look like + // absence on darwin and linux. + if (outcome === "failed") throw new Error("the installed service could not be removed"); + return outcome === "removed"; + }); + + // Only Task Scheduler can respawn through a surviving wrapper, and removing the + // registration does not prove the running one died. Poll the same window `ocx stop` does + // before shared config is allowed down (#764, #3008). + if (observed.serviceStop === "stopped-respawnable") { + await runStep("respawn window verified", async () => { + const survivor = await proxyStillLiveAfterStop({ canRespawn: true }); + if (survivor) throw new Error(`a proxy is still listening on port ${survivor.port} after the service was removed; it is being respawned`); + // A null from that poll is not proof either: its identity probe returns null on a + // timeout, so a respawned-but-unresponsive proxy looks the same as none. Require the + // tri-state probe to say dead on every candidate before calling the window verified. + if (!await proxyEndpointProvenDown()) { + throw new Error("no survivor answered after the service was removed, but the endpoint could not be confirmed down either; confirm nothing is serving, then rerun"); + } + observed.respawnWindowVerified = true; + return true; + }); + } if (process.platform === "win32") { await runStep("Windows tray removed", async () => { @@ -784,16 +1157,26 @@ async function handleUninstall() { }); } - await runStep("native Codex restored", async () => { - const r = await restoreNativeCodexAsync(); - if (!r.success) throw new Error(r.message); - }); + // Shared client config comes down only once nothing that could still be serving is + // unaccounted for. Restoring it under a live, still-managed proxy leaves both pointing + // at each other — the same failure `ocx stop` refuses (#3008). + if (sharedTeardownAuthorized(observed)) { + await runStep("native Codex restored", async () => { + const r = await restoreNativeCodexAsync(); + if (!r.success) throw new Error(r.message); + }); - await runStep("Grok Build config restored", () => { - const r = stripGrokConfig(); - if (!r.ok) throw new Error(r.message); - return r.changed; - }); + await runStep("Grok Build config restored", () => { + const r = stripGrokConfig(); + if (!r.ok) throw new Error(r.message); + return r.changed; + }); + } else { + failures.push("native Codex restored", "Grok Build config restored"); + console.error("⚠️ Skipping shared teardown (native Codex restore, Grok config): a service or proxy could not be proven stopped."); + console.error(" Resolve the failures above and rerun 'ocx uninstall' — service removal and local state cleanup are also unfinished."); + console.error(" 'ocx restore' is an interim step if you need native routing back before then."); + } await runStep("system env vars reverted", () => { const r = revertSystemEnv(); diff --git a/src/cli/uninstall-plan.ts b/src/cli/uninstall-plan.ts new file mode 100644 index 0000000000..0e1df1cd2d --- /dev/null +++ b/src/cli/uninstall-plan.ts @@ -0,0 +1,86 @@ +/** + * Whether an uninstall may take shared client config down (#3008). + * + * Extracted from `handleUninstall` because the rule is a decision, and a decision that + * only exists inside a long imperative command can only be tested by reading its source — + * which is how this shipped wrong twice: first trusting a boolean that collapsed "not + * installed" with "still running", then trusting a missing pid file as proof no proxy was + * serving. + * + * Native Codex and the Grok fence are SHARED. Restoring them while something may still be + * serving leaves the client and the proxy pointing at each other, so every step that could + * leave a live proxy behind has to be accounted for first. + */ +export type UninstallObservation = { + /** Detailed service-stop outcome, or null when the step threw. */ + serviceStop: "absent" | "stopped" | "stopped-respawnable" | "failed" | "state-unknown" | null; + /** + * Did the proxy step PROVE nothing is serving? + * + * A `findLiveProxy` miss is not that proof: it collapses a timeout and a transport + * failure into the same null as a dead endpoint, so an unresponsive proxy read as absent. + */ + proxyProvenDown: boolean; + /** Service removal outcome, or null when the step threw. */ + serviceRemoval: "absent" | "removed" | "failed" | null; + /** + * For a Task Scheduler backend: was the restart window verified AFTER removal? + * + * Deleting the registration does not prove an already-running `:loop` wrapper died — + * killing it is best-effort (#764). `ocx stop` polls across the window; uninstall has + * to do the same before it may take shared config down. + */ + respawnWindowVerified: boolean; +}; + +export function sharedTeardownAuthorized(o: UninstallObservation): boolean { + if (o.serviceStop === null) return false; + // "absent" and a clean stop are the only service states that prove nothing is managing + // the proxy. + if (o.serviceStop === "failed" || o.serviceStop === "state-unknown") return false; + // Removing the registration is not the same as proving the running wrapper is gone. + if (o.serviceStop === "stopped-respawnable" && !o.respawnWindowVerified) return false; + if (o.serviceRemoval === null || o.serviceRemoval === "failed") return false; + return o.proxyProvenDown; +} + +/** An endpoint an uninstall must account for before shared config comes down. */ +export type ProbeEndpoint = { hostname: string; port: number }; + +/** + * Every DISTINCT endpoint this home could be serving on. + * + * A runtime record and the configured port can disagree — a stale record pointing at a + * closed port while the live proxy sits on the configured one. Probing only the runtime + * candidate then reports "dead" for a port nobody is using and authorizes the teardown + * (#3008). `findLiveProxy` already probes both; the proof has to cover both too. + */ +export function endpointsToProve( + runtime: { port?: number; hostname?: string } | null, + config: { port?: number; hostname?: string }, +): ProbeEndpoint[] { + const out: ProbeEndpoint[] = []; + const push = (port: number | undefined, hostname: string | undefined) => { + if (!port || port <= 0 || port > 65535) return; + const endpoint = { hostname: hostname ?? "127.0.0.1", port }; + if (out.some(e => e.port === endpoint.port && e.hostname === endpoint.hostname)) return; + out.push(endpoint); + }; + push(runtime?.port, runtime?.hostname); + push(typeof config.port === "number" && config.port > 0 ? config.port : 10100, config.hostname); + return out; +} + +/** + * Proof requires EVERY candidate to be definitively dead. + * + * "unknown" is not absence: a listener that accepts connections but withholds /healthz, or + * one that times out, is exactly the state where restoring shared config is most harmful. + */ +export function everyEndpointProvenDown( + endpoints: readonly ProbeEndpoint[], + probe: (e: ProbeEndpoint) => "live" | "dead" | "unknown", +): boolean { + if (endpoints.length === 0) return false; + return endpoints.every(e => probe(e) === "dead"); +} diff --git a/src/config/pending-teardown-names.d.mts b/src/config/pending-teardown-names.d.mts new file mode 100644 index 0000000000..a5e540d55c --- /dev/null +++ b/src/config/pending-teardown-names.d.mts @@ -0,0 +1,8 @@ +export declare const PENDING_TEARDOWN_PREFIX: string; +export declare const PENDING_TEARDOWN_SUFFIX: string; +export declare const PENDING_TEARDOWN_UNREADABLE_SUFFIX: string; +export declare function isPendingTeardownFileName(name: unknown): boolean; +export declare function isQuarantinedTeardownFileName(name: unknown): boolean; +export declare function isAnyTeardownObligationFileName(name: unknown): boolean; +export declare function pendingTeardownNonceFromFileName(name: string): string | null; +export declare function hasPendingTeardownIn(readdir: (dir: string) => string[], dir: string): boolean; diff --git a/src/config/pending-teardown-names.mjs b/src/config/pending-teardown-names.mjs new file mode 100644 index 0000000000..6d3f33f6f5 --- /dev/null +++ b/src/config/pending-teardown-names.mjs @@ -0,0 +1,69 @@ +/** + * Naming rules for pending-teardown receipts, shared by both update lanes (#3008). + * + * Plain ESM because `bin/ocx.mjs` runs under Node before Bun exists and cannot import the + * TypeScript module. It lives here rather than being spelled out twice because that is + * exactly how this broke: the launcher kept checking the retired singleton filename after + * the receipts moved to one file per claim, so the npm lane silently stopped seeing every + * outstanding obligation. + */ + +export const PENDING_TEARDOWN_PREFIX = "pending-teardown-"; +export const PENDING_TEARDOWN_SUFFIX = ".json"; +/** + * Suffix for an obligation that could not be read. + * + * It is still an obligation. Quarantine renames the file so the ordinary recovery loop + * stops re-reading garbage, but it must NOT stop counting: an update that proceeds + * because the evidence was filed away is exactly the outcome the receipt exists to + * prevent. Both lanes treat this as outstanding until an operator removes it. + */ +export const PENDING_TEARDOWN_UNREADABLE_SUFFIX = ".unreadable.json"; +const NONCE_RE = /^[0-9a-f]{32}$/; + +/** A receipt the recovery loop should read and try to discharge. */ +export function isPendingTeardownFileName(name) { + if (typeof name !== "string") return false; + if (isQuarantinedTeardownFileName(name)) return false; + if (!name.startsWith(PENDING_TEARDOWN_PREFIX) || !name.endsWith(PENDING_TEARDOWN_SUFFIX)) return false; + return NONCE_RE.test(name.slice(PENDING_TEARDOWN_PREFIX.length, name.length - PENDING_TEARDOWN_SUFFIX.length)); +} + +/** A receipt that could not be read and is waiting on a human. */ +export function isQuarantinedTeardownFileName(name) { + if (typeof name !== "string") return false; + if (!name.startsWith(PENDING_TEARDOWN_PREFIX) || !name.endsWith(PENDING_TEARDOWN_UNREADABLE_SUFFIX)) return false; + return NONCE_RE.test(name.slice( + PENDING_TEARDOWN_PREFIX.length, + name.length - PENDING_TEARDOWN_UNREADABLE_SUFFIX.length, + )); +} + +/** Any obligation at all — readable or quarantined. Both block an update. */ +export function isAnyTeardownObligationFileName(name) { + return isPendingTeardownFileName(name) || isQuarantinedTeardownFileName(name); +} + +export function pendingTeardownNonceFromFileName(name) { + if (!isPendingTeardownFileName(name)) return null; + return name.slice(PENDING_TEARDOWN_PREFIX.length, name.length - PENDING_TEARDOWN_SUFFIX.length); +} + +/** + * Does the given config directory hold any outstanding obligation? + * + * Quarantined receipts count. Filing one away to unblock an update would let the very + * next `ocx update` install over a teardown that never ran — the enforcement has to + * survive until a human removes the file. + */ +export function hasPendingTeardownIn(readdir, dir) { + try { + return readdir(dir).some(isAnyTeardownObligationFileName); + } catch (error) { + // "There is no home yet" is the only honest empty answer. Any other failure — + // permissions, I/O, a file where the directory should be — means an obligation may be + // sitting there unread, and reporting "none" would let an update install over a + // teardown that never ran. Absence of proof is not proof of absence. + return error?.code !== "ENOENT"; + } +} diff --git a/src/config/pending-teardown.ts b/src/config/pending-teardown.ts new file mode 100644 index 0000000000..31082b0fbb --- /dev/null +++ b/src/config/pending-teardown.ts @@ -0,0 +1,286 @@ +import { createHash, randomBytes } from "node:crypto"; +import { existsSync, readdirSync, readFileSync, renameSync, unlinkSync } from "node:fs"; +import { join } from "node:path"; +import { assertNotRealHomeUnderTest } from "../lib/test-home-guard"; +import { atomicWriteFile } from "./atomic-write"; +import { getConfigDir } from "./paths"; + +/** + * Ownership receipt for a deferred shared teardown (#3008). + * + * `ocx stop` asks the proxy NOT to restore native Codex and the Grok fence, because a + * stopped Task Scheduler can respawn the proxy and a survivor must keep its client + * config. That hands one obligation to the parent — and a bare query flag cannot express + * an obligation: if the parent dies between the child's exit and its own restore, the + * shared config keeps pointing at a proxy that is gone, with nothing on disk saying so. + * + * The receipt is that missing state. The parent writes it BEFORE asking for a deferred + * stop and removes it only after its own restore, so a later `ocx stop`/`ocx update` can + * see the abandoned obligation and finish it once that proxy is proven down. + * + * ## Why the nonce is the FILENAME + * + * One shared file cannot be cleared safely. Read-compare-unlink is three syscalls, and a + * concurrent stop replacing the file between the compare and the unlink means this run + * deletes an obligation it never owned — the check passed against bytes that are already + * gone. Giving each claim its own path removes the race rather than serializing it: + * `unlink` names one specific obligation, so it can only ever delete that one. Two + * concurrent stops hold two receipts, which is the truth of the situation. + */ +export type PendingTeardownReceipt = { + /** Process that accepted the obligation, so a live owner is distinguishable from a dead one. */ + ownerPid: number; + /** Identity of this claim; also its filename, which is what makes a clear a single-syscall delete. */ + nonce: string; + /** ISO timestamp, for diagnostics only; recovery is decided by liveness, not by age. */ + createdAt: string; + /** + * Endpoint the owner was stopping. + * + * Recovery has to prove THAT proxy is down, and after a crash the runtime-port record + * is usually gone. Falling back to the configured port asks the wrong question for a + * proxy started with an explicit `--port`: the configured port refuses while the live + * one keeps serving, and its client config gets torn out from under it. + */ + endpoint: { hostname: string; port: number }; + /** + * How the endpoint was obtained. + * + * `exact` came from the runtime record or a successful liveness probe — the address the + * stop actually contacted. `guessed` is the configured listen address, recorded because + * an obligation with a weak address beats no obligation at all, but it is NOT evidence: + * a proxy on an explicit `--port` can be respawned there while the configured port + * refuses, and treating that refusal as proof would restore under a live proxy. A + * guessed receipt therefore fails closed into manual recovery. + */ + endpointSource: "exact" | "guessed"; +}; + +/** + * What is on disk, kept distinct from what it means. + * + * Collapsing a malformed file into "no receipt" loses the one fact recovery needs: an + * obligation may still be outstanding, and its owner can no longer be identified. That + * state must not silently authorize a deferral, and it must not wedge every later stop + * either — see {@link quarantinePendingTeardown}. + */ +export type PendingTeardownRead = + | { state: "missing" } + | { state: "valid"; receipt: PendingTeardownReceipt } + | { state: "invalid"; nonce: string; detail: string }; + +/** + * The home itself could not be listed. + * + * Distinct from an invalid receipt: there is no file to quarantine and no nonce to name, + * so it must never be fed to the receipt machinery. It blocks like any obligation, but the + * remedy is to fix the directory and retry, not to remove something. + */ +export type TeardownScanFailure = { state: "unscannable"; detail: string }; + +import { + isPendingTeardownFileName, + isAnyTeardownObligationFileName, + PENDING_TEARDOWN_PREFIX as PREFIX, + PENDING_TEARDOWN_SUFFIX as SUFFIX, + PENDING_TEARDOWN_UNREADABLE_SUFFIX as UNREADABLE_SUFFIX, + pendingTeardownNonceFromFileName, +} from "./pending-teardown-names.mjs"; + +const NONCE_RE = /^[0-9a-f]{32}$/; + +export function pendingTeardownPathFor(nonce: string): string { + return join(getConfigDir(), `${PREFIX}${nonce}${SUFFIX}`); +} + +function isReceipt(value: unknown, nonce: string): value is PendingTeardownReceipt { + if (!value || typeof value !== "object") return false; + const receipt = value as Record<string, unknown>; + const endpoint = receipt.endpoint as Record<string, unknown> | undefined; + const endpointOk = !!endpoint + && typeof endpoint === "object" + && typeof endpoint.hostname === "string" + && endpoint.hostname.trim() !== "" + && Number.isInteger(endpoint.port) + && Number(endpoint.port) > 0 + && Number(endpoint.port) <= 65535; + return Number.isSafeInteger(receipt.ownerPid) + && Number(receipt.ownerPid) > 0 + // The body must agree with the name: a receipt whose nonce was edited to name a + // different claim would let a request authorize a deferral it does not own. + && receipt.nonce === nonce + && typeof receipt.createdAt === "string" + && (receipt.endpointSource === "exact" || receipt.endpointSource === "guessed") + && endpointOk; +} + +/** Claim a deferred teardown for this process. Returns the receipt that was written. */ +export function claimPendingTeardown( + endpoint: { hostname: string; port: number }, + endpointSource: "exact" | "guessed", + ownerPid: number = process.pid, +): PendingTeardownReceipt { + const dir = getConfigDir(); + assertNotRealHomeUnderTest(dir); + const nonce = randomBytes(16).toString("hex"); + const receipt: PendingTeardownReceipt = { ownerPid, nonce, createdAt: new Date().toISOString(), endpoint, endpointSource }; + atomicWriteFile(pendingTeardownPathFor(nonce), JSON.stringify(receipt, null, 2) + "\n"); + return receipt; +} + +export function readPendingTeardown(nonce: string): PendingTeardownRead { + if (!NONCE_RE.test(nonce)) return { state: "missing" }; + let raw: string; + try { + raw = readFileSync(pendingTeardownPathFor(nonce), "utf-8"); + } catch (error) { + // Only "there is no file" is absence. A permission error, or a directory sitting where + // the receipt belongs, means something IS there and cannot be read; calling that + // missing hides an obligation that may still be outstanding. + const code = (error as NodeJS.ErrnoException).code; + if (code === "ENOENT") return { state: "missing" }; + return { state: "invalid", nonce, detail: `unreadable (${code ?? "unknown"})` }; + } + try { + const parsed: unknown = JSON.parse(raw); + if (isReceipt(parsed, nonce)) return { state: "valid", receipt: parsed }; + const digest = createHash("sha256").update(raw).digest("hex").slice(0, 12); + return { state: "invalid", nonce, detail: `malformed receipt (sha256 ${digest})` }; + } catch { + return { state: "invalid", nonce, detail: "unparseable JSON" }; + } +} + +/** An obligation that exists on disk — the "missing" case cannot occur in a listing. */ +export type OutstandingTeardown = Exclude<PendingTeardownRead, { state: "missing" }> | TeardownScanFailure; + +/** + * Every obligation currently on disk, attributable or not. + * + * A scan that FAILS is not an empty scan. Swallowing a permission or I/O error into `[]` + * would let `handleStop` restore client config with an unread obligation sitting right + * there, so anything but a missing home surfaces as one unreadable obligation the caller + * must treat like any other: blocking, and needing a human. + */ +export function listPendingTeardowns(): OutstandingTeardown[] { + let names: string[]; + try { + names = readdirSync(getConfigDir()); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") return []; + // Not an invalid RECEIPT: there is no file here and no nonce to name. Synthesizing one + // would hand a fabricated identity to the quarantine and clear paths, which could then + // rename or delete a real receipt that happened to carry it. + return [{ state: "unscannable", detail: `the opencodex home could not be listed (${(error as NodeJS.ErrnoException).code ?? "unknown"})` }]; + } + const out: OutstandingTeardown[] = []; + for (const name of names) { + // One naming rule, shared with the npm launcher: the two lanes drifting apart is + // exactly how the Node updater stopped seeing receipts at all. + if (!isPendingTeardownFileName(name)) continue; + const nonce = pendingTeardownNonceFromFileName(name)!; + const read = readPendingTeardown(nonce); + if (read.state !== "missing") out.push(read); + } + return out; +} + +/** + * Is any obligation outstanding, whether or not it can still be attributed? + * + * Quarantined receipts count. Filing an unreadable one away must not let the next update + * install over a teardown that never ran — that would turn "we could not tell" into "it + * is fine", which is the failure this whole mechanism exists to prevent. + */ +export function pendingTeardownOutstanding(): boolean { + try { + return readdirSync(getConfigDir()).some(isAnyTeardownObligationFileName); + } catch (error) { + // Only a missing home is empty. Any other scan failure may be hiding an obligation, + // and reporting "none" would unblock an update over a teardown that never ran. + return (error as NodeJS.ErrnoException).code !== "ENOENT"; + } +} + +/** Paths of quarantined obligations awaiting a human. */ +export function listQuarantinedTeardowns(): string[] { + try { + return readdirSync(getConfigDir()) + .filter(name => name.startsWith(PREFIX) && name.endsWith(UNREADABLE_SUFFIX)) + .map(name => join(getConfigDir(), name)); + } catch { + return []; + } +} + +/** + * Remove exactly one obligation. + * + * The nonce is the filename, so this is a compare-and-delete in one syscall: it can never + * remove a receipt another process wrote, because that receipt lives at a different path. + * Returns whether the obligation is gone — a failed unlink is reported rather than + * swallowed, since a receipt that survives its discharge re-triggers recovery forever. + */ +export function clearPendingTeardown(nonce: string): boolean { + try { + unlinkSync(pendingTeardownPathFor(nonce)); + return true; + } catch (error) { + return (error as NodeJS.ErrnoException).code === "ENOENT"; + } +} + +/** + * Move an unattributable obligation aside. + * + * An invalid receipt names no endpoint, so nothing can prove its proxy is down, so it can + * never be discharged the normal way. Left in place it is not merely useless: both + * updater gates treat an outstanding receipt as a reason to run the stop, and that stop + * would fail on the same receipt every time — an update that can never proceed. + * + * Renaming stops the recovery loop from re-reading garbage on every stop, but it + * deliberately does NOT stop the obligation from counting: `pendingTeardownOutstanding` + * still sees it, so both updaters keep refusing to install over a teardown that never + * ran. Only a human removing the file ends the enforcement. + * + * Returns the path it was moved to, or null when it could not be moved. + */ +export function quarantinePendingTeardown(nonce: string): string | null { + const from = pendingTeardownPathFor(nonce); + if (!existsSync(from)) return null; + const to = join(getConfigDir(), `${PREFIX}${nonce}${UNREADABLE_SUFFIX}`); + try { + renameSync(from, to); + return to; + } catch { + return null; + } +} + +/** + * True when a previous deferred stop left its obligation unfinished. + * + * A receipt whose owner is still alive belongs to a stop that is still running: leave it + * alone. Only an abandoned obligation is a candidate, and a VALID one still has to prove + * its endpoint is down before anything is restored — an invalid one never can, which is + * what {@link quarantinePendingTeardown} exists for. + */ +export function isPendingTeardownAbandoned( + read: PendingTeardownRead | TeardownScanFailure, + isAlive: (pid: number) => boolean, + selfPid: number = process.pid, +): boolean { + if (read.state === "missing") return false; + // A home that cannot be listed may be hiding an obligation. It is not recoverable and + // not removable; the caller blocks on it and asks for the directory to be fixed. + if (read.state === "unscannable") return true; + if (read.state === "invalid") return true; + if (read.receipt.ownerPid === selfPid) return false; + return !isAlive(read.receipt.ownerPid); +} + +/** Does this request name an obligation that exists and is readable? */ +export function deferralMatchesReceipt(nonce: string | null): boolean { + if (!nonce || !NONCE_RE.test(nonce)) return false; + return readPendingTeardown(nonce).state === "valid"; +} diff --git a/src/lib/process-control.ts b/src/lib/process-control.ts index 6c0f7082f9..3e296d6c72 100644 --- a/src/lib/process-control.ts +++ b/src/lib/process-control.ts @@ -29,6 +29,24 @@ export interface GracefulStopIo { waitExit?: (pid: number, timeoutMs: number) => boolean; env?: Record<string, string | undefined>; exitTimeoutMs?: number; + /** + * Nonce of the pending-teardown receipt this caller claimed. + * + * `ocx stop` sets it because it restores shared client config itself, only after + * proving a stopped Task Scheduler did not respawn the proxy (#3008). The nonce is what + * makes the deferral an owned obligation rather than a flag anyone can set: the proxy + * honours it only when it names the receipt actually on disk. Direct callers omit it + * and keep the self-contained behaviour. + */ + deferSharedTeardownNonce?: string; + /** + * Endpoint the caller already resolved for this pid. + * + * `ocx stop` records this same snapshot in its pending-teardown receipt. Re-reading the + * runtime file here could pick up a different one, which would make the receipt name an + * endpoint the stop never contacted — and recovery probes exactly that endpoint. + */ + runtimeEndpoint?: { hostname: string; port: number }; } /** @@ -67,7 +85,7 @@ export class ProxyOwnershipRefusedError extends Error {} */ export async function stopProxyGracefully(pid: number, io: GracefulStopIo = {}): Promise<GracefulStopResult> { const readRuntime = io.readRuntime ?? readRuntimePort; - const runtime = readRuntime(pid); + const runtime = io.runtimeEndpoint ?? readRuntime(pid); if (!runtime?.port) return false; const env = io.env ?? process.env; const headers: Record<string, string> = {}; @@ -75,7 +93,14 @@ export async function stopProxyGracefully(pid: number, io: GracefulStopIo = {}): if (token) headers["x-opencodex-api-key"] = token; const fetchFn = io.fetchFn ?? fetch; try { - const res = await fetchFn(`http://${gracefulStopHost(runtime.hostname)}:${runtime.port}/api/stop`, { + // `ocx stop` asks the proxy NOT to restore shared client config: it does that itself, + // after verifying a stopped Task Scheduler did not respawn the proxy (#3008). Letting + // the child do it means a survivor found seconds later has already lost its config. + const stopUrl = `http://${gracefulStopHost(runtime.hostname)}:${runtime.port}/api/stop` + + (io.deferSharedTeardownNonce + ? `?deferSharedTeardown=1&teardownNonce=${encodeURIComponent(io.deferSharedTeardownNonce)}` + : ""); + const res = await fetchFn(stopUrl, { method: "POST", headers, // Hung proxies with many CLOSE_WAIT clients can be slow to accept; give them @@ -107,10 +132,10 @@ function drainDeadlineMs(): number { } /** Graceful-first stop: management-API drain, then the platform kill ladder. */ -export async function stopProxy(pid: number): Promise<void> { +export async function stopProxy(pid: number, io: GracefulStopIo = {}): Promise<void> { if (!isProcessAlive(pid)) return; - const runtime = readRuntimePort(pid); - const graceful = await stopProxyGracefully(pid); + const runtime = io.runtimeEndpoint ?? readRuntimePort(pid); + const graceful = await stopProxyGracefully(pid, io); if (graceful === "refused") { // The proxy refused on purpose (foreign service owns it). Forcing would strip shared // config while that service keeps the proxy alive. diff --git a/src/server/management-api.ts b/src/server/management-api.ts index 9f19831576..9e188c03ee 100644 --- a/src/server/management-api.ts +++ b/src/server/management-api.ts @@ -256,10 +256,46 @@ export async function handleManagementAPI( if (routed) return routed; if (url.pathname === "/api/stop" && req.method === "POST") { - const { restoreNativeCodexAsync } = await import("../codex/inject"); - const { stopServiceIfInstalled, isServiceOwnershipError } = await import("../service"); + const { installedServiceRespawnRisk, stopServiceIfInstalledDetailed, isServiceOwnershipError } = await import("../service"); + // `ocx stop` performs its own shared teardown AFTER verifying the scheduler did not + // respawn the proxy (#3008). Without this the child restores native Codex and strips + // the Grok fence here, so a survivor found moments later has already had the shared + // config pulled out from under it — and the parent's `ownershipBlocked` guard can + // only prevent a second, redundant teardown. A direct caller sends nothing and keeps + // the self-contained behaviour. + // + // The query flag alone is not enough to hand over the obligation: any authenticated + // caller could set it and simply exit, leaving client config pointed at a proxy that + // no longer exists. Honour the deferral only when the caller left a pending-teardown + // receipt on disk, which a later stop/update can find and finish. + // Decide BEFORE touching the manager. Stopping the Task Scheduler task and then + // refusing left the proxy running with its manager stopped — worse than either + // outcome. This process cannot verify its own post-exit respawn window; only the + // receipt-backed parent `ocx stop` can, which is what the deferral exists for. + const { deferralMatchesReceipt } = await import("../config/pending-teardown"); + const { deferralHonored, performStopTeardown } = await import("./stop-teardown"); + const holdsReceipt = deferralHonored(url, deferralMatchesReceipt); + const respawnRisk = holdsReceipt ? "none" : installedServiceRespawnRisk(); + if (respawnRisk === "respawnable") { + return jsonResponse({ + success: false, + code: "respawnable_service", + message: "This proxy is managed by a Task Scheduler wrapper that can respawn it, so the stop must be run by `ocx stop`, which verifies the respawn window. Nothing was changed.", + }, 409, req, config); + } + if (respawnRisk === "unknown") { + // Do NOT send them to `ocx stop`: it maps the same unanswerable probe to a stop + // failure, so that advice would be a loop. The scheduler query itself is what needs + // fixing (#3008). + return jsonResponse({ + success: false, + code: "service_state_unknown", + message: "The Windows Task Scheduler state could not be read, so this proxy cannot tell whether a wrapper would respawn it. Nothing was changed. Run `ocx service status` to see the query error, repair Task Scheduler access, then retry.", + }, 409, req, config); + } + let serviceStop: import("../service").ServiceStopOutcome; try { - stopServiceIfInstalled(); + serviceStop = stopServiceIfInstalledDetailed(); } catch (err) { if (isServiceOwnershipError(err)) { // The installed service belongs to another CODEX_HOME/OPENCODEX_HOME: it would respawn @@ -269,12 +305,30 @@ export async function handleManagementAPI( } throw err; } - const restore = await restoreNativeCodexAsync(); + // The boolean helper collapses "failed" into the same false as "no service installed", + // so this route used to tear down shared config and exit while a manager that refused + // to stop was still there to respawn the proxy (#3008). + if (serviceStop === "failed") { + return jsonResponse({ + success: false, + message: "The installed service manager did not stop; it may respawn the proxy. Shared client config was left alone. Run `ocx stop` from the home that owns the service.", + }, 409, req, config); + } + if (serviceStop === "state-unknown") { + // Same case, same remedy as the pre-check: the query is what needs fixing. + return jsonResponse({ + success: false, + code: "service_state_unknown", + message: "The Windows Task Scheduler state could not be read, so this proxy cannot tell whether a wrapper would respawn it. Shared client config was left alone. Run `ocx service status` to see the query error, repair Task Scheduler access, then retry.", + }, 409, req, config); + } + // The pre-check above already refused the respawnable case without a receipt, so + // reaching here with one means the parent owns the verification. // Both managed configs come down together on an explicit teardown. The daemon's own // syncCleanup skips this when OCX_SERVICE is set (so a crash/respawn keeps the fence), - // which is exactly why an intentional stop has to do it here. - const { stripGrokConfig } = await import("../grok/inject"); - const grok = stripGrokConfig(); + // which is exactly why an intentional stop has to do it here — unless the caller is + // `ocx stop`, which does it itself once the proxy is proven down. + const teardown = await performStopTeardown(url, { ownsReceipt: deferralMatchesReceipt }); setTimeout(async () => { let shutdownSucceeded = false; try { @@ -282,12 +336,12 @@ export async function handleManagementAPI( } catch { console.warn("[opencodex] shutdown drain failed"); } - process.exit(shutdownSucceeded ? 0 : 1); + // A drained proxy whose shared teardown failed did not finish the job. Exiting 0 + // told a supervisor the stop was clean while native Codex or the Grok fence was + // still pointed at this process (#3008). + process.exit(shutdownSucceeded && teardown.success ? 0 : 1); }, 200); - const grokNote = grok.ok ? "" : ` Grok config cleanup failed: ${grok.message}`; - return jsonResponse(restore.success - ? { success: true, message: `Proxy stopping, native Codex restored.${grokNote}` } - : { success: false, message: `Proxy stopping, but native Codex restore failed: ${restore.message}. Run \`ocx restore\`.${grokNote}` }); + return jsonResponse(teardown); } if (url.pathname.startsWith("/api/native-main-profiles")) { diff --git a/src/server/stop-teardown.ts b/src/server/stop-teardown.ts new file mode 100644 index 0000000000..e4aadc4996 --- /dev/null +++ b/src/server/stop-teardown.ts @@ -0,0 +1,84 @@ +import type { CodexNativeRestoreResult } from "../codex/inject"; +import { deferralMatchesReceipt } from "../config/pending-teardown"; + +/** + * Shared-teardown decision and execution for `POST /api/stop` (#3008). + * + * Lives outside the route handler because the handler schedules `process.exit` 200ms + * after it answers, which makes it uncallable from a test. The part worth testing is + * exactly this: whether the deferral is honoured, whether the restores actually run, and + * whether the response says what happened. + */ + +export type GrokStripResult = { ok: boolean; changed: boolean; message: string }; + +export type StopTeardownIo = { + /** Does the nonce this request carries name a readable obligation on disk? */ + ownsReceipt?: (nonce: string | null) => boolean; + restoreNativeCodex?: () => Promise<CodexNativeRestoreResult>; + stripGrok?: () => GrokStripResult; +}; + +export type StopTeardownBody = { + success: boolean; + message: string; + sharedTeardown: "deferred" | "performed"; +}; + +/** + * A deferral is honoured only when the caller proves it owns the obligation. + * + * The query flag names an intention; the receipt is the obligation. Without the second + * half any authenticated caller could ask the proxy to skip teardown and then exit, + * leaving native Codex and the Grok fence pointed at a proxy that no longer exists. + * + * "A receipt exists" is not that proof either: it would let any caller ride on another + * stop's outstanding obligation and get a deferral it never owns. The request has to name + * the receipt's nonce, which only the process that wrote it (and anything that can read + * the 0700 config directory, which is already the trust boundary for the admin token) + * can know. + */ +export function deferralHonored(url: URL, ownsReceipt: (nonce: string | null) => boolean): boolean { + if (url.searchParams.get("deferSharedTeardown") !== "1") return false; + return ownsReceipt(url.searchParams.get("teardownNonce")); +} + +/** Run (or skip) the shared teardown and describe the outcome truthfully. */ +export async function performStopTeardown(url: URL, io: StopTeardownIo = {}): Promise<StopTeardownBody> { + const ownsReceipt = io.ownsReceipt ?? deferralMatchesReceipt; + if (deferralHonored(url, ownsReceipt)) { + // Not "native Codex restored": nothing was restored here, and claiming otherwise + // would be a success message the operator cannot verify. + return { + success: true, + message: "Proxy stopping; shared teardown deferred to the stopping client.", + sharedTeardown: "deferred", + }; + } + const restore = io.restoreNativeCodex + ? await io.restoreNativeCodex() + : await (await import("../codex/inject")).restoreNativeCodexAsync(); + const grok = io.stripGrok + ? io.stripGrok() + : (await import("../grok/inject")).stripGrokConfig(); + // Success means BOTH halves came down. Deciding it from the native restore alone and + // appending the Grok text let a caller read `success: true` while the fence still + // pointed at a proxy that was exiting — the teardown reported done with half of it + // undone (#3008). + const grokNote = grok.ok ? "" : ` Grok config cleanup failed: ${grok.message}`; + if (restore.success && grok.ok) { + return { success: true, message: "Proxy stopping, native Codex restored.", sharedTeardown: "performed" }; + } + if (restore.success) { + return { + success: false, + message: `Proxy stopping, native Codex restored, but the Grok fence was not removed:${grokNote} Run \`ocx restore\`.`, + sharedTeardown: "performed", + }; + } + return { + success: false, + message: `Proxy stopping, but native Codex restore failed: ${restore.message}. Run \`ocx restore\`.${grokNote}`, + sharedTeardown: "performed", + }; +} diff --git a/src/service.ts b/src/service.ts index ab780711e8..67138d79fc 100644 --- a/src/service.ts +++ b/src/service.ts @@ -2998,6 +2998,22 @@ export function stopWindows(): void { if (isWindowsSchedulerEndBenign(error)) return; } } + +/** + * `stopWindows` for callers that need to know whether it worked. + * + * The void form swallows a non-benign `/end` failure, which is right for best-effort + * teardown and wrong for deciding whether an update may replace files: a scheduler that + * refused to stop can respawn the proxy on top of a half-written install (#3008). + */ +export function stopWindowsChecked(): boolean { + try { + schtasks(["/end", "/tn", TASK]); + return true; + } catch (error) { + return isWindowsSchedulerEndBenign(error); + } +} function statusWindows(): string { try { return schtasks(["/query", "/tn", TASK]); } catch { return ""; } } function statusWindowsXml(): string { try { return schtasks(["/query", "/tn", TASK, "/xml"]); } catch { return ""; } } @@ -3604,36 +3620,129 @@ export async function installFreshWindowsSchedulerSafely( } } +// `stopServiceIfInstalled` (boolean) is deliberately gone. It collapsed "not installed", +// "refused to stop" and "state could not be read" into the same `false`, and every caller +// that trusted it eventually read a live manager as absence — the route, then uninstall +// (#3008). Callers take `stopServiceIfInstalledDetailed` and handle the outcomes. /** - * If a service is installed, stop it so the process manager doesn't respawn after `ocx stop`. - * Returns true if a service was found and stopped. + * Would stopping the installed manager leave something that can respawn the proxy? + * + * Answered WITHOUT stopping anything, because a caller that must refuse the stop has to + * refuse before it acts: `POST /api/stop` briefly ended the Task Scheduler task and then + * returned 409, which left the proxy running with its manager stopped — worse than either + * outcome it was choosing between. + * + * Task Scheduler only. `schtasks /end` ends the task instance while the `cmd :loop` + * wrapper survives and respawns its child (#764); launchd, systemd and WinSW are down when + * they report stopped. */ -export function stopServiceIfInstalled(): boolean { +export function installedServiceRespawnRisk( + probe: () => WindowsSchedulerTaskProbe = probeWindowsSchedulerTask, + platform: NodeJS.Platform = process.platform, +): "none" | "respawnable" | "unknown" { + // launchd, systemd and WinSW are down when they report stopped; only the Task Scheduler + // wrapper survives its task ending (#764). + if (platform !== "win32") return "none"; + try { + // `probeWindowsSchedulerTask` returns "unknown" as an ordinary value when its queries + // fail — it does not throw — so testing for "present" let an unanswerable probe + // through, and the route then killed scheduler wrappers before refusing. + // + // "unknown" is kept SEPARATE from "respawnable" because the remedies differ. Telling + // an operator whose schtasks query is broken to run `ocx stop` is circular: that + // command maps the same unknown to a stop failure, so it cannot finish either. + const status = probe().status; + if (status === "absent") return "none"; + return status === "present" ? "respawnable" : "unknown"; + } catch { + // A probe that cannot answer is not evidence of absence either. + return "unknown"; + } +} + +/** + * Outcome of stopping an installed process manager. + * + * `stopServiceIfInstalled` collapses "no service was installed" and "a service was + * installed and would not stop" into the same `false`, which is fine for a caller that + * only wants to log. It is not fine for one deciding whether an update may replace package + * files: a manager that refused to stop can respawn the proxy on top of a half-written + * install (#3008). + */ +/** + * `stopped-respawnable` is Task Scheduler specifically: `schtasks /end` ends the task + * instance while the `cmd :loop` wrapper survives and respawns its child seconds later + * (#764). Only that backend needs the restart-window wait — launchd, systemd and WinSW + * are down when they report stopped, and making them pay a seven-second poll would be a + * regression in every ordinary `ocx stop`. + */ +/** + * `state-unknown` is kept apart from `failed` because the remedies differ. A manager that + * refused to stop is a stop failure the operator can retry; a scheduler whose state cannot + * be READ is a broken query, and telling that operator "the manager did not stop" sends + * them looking for the wrong thing (#3008). + */ +export type ServiceStopOutcome = "absent" | "stopped" | "stopped-respawnable" | "failed" | "state-unknown"; + +/** + * Collapse the Windows backend observations into one outcome. + * + * Extracted so the precedence is testable by calling it. The rule that matters: a readable + * failure outranks an unreadable state, and an unreadable state outranks success — a + * scheduler we cannot see may still respawn the proxy. + */ +export function classifyWindowsServiceStop(o: { + stopped: boolean; + failed: boolean; + schedulerStopped: boolean; + stateUnknown: boolean; +}): ServiceStopOutcome { + if (o.failed) return "failed"; + if (o.stateUnknown) return "state-unknown"; + if (o.stopped) return o.schedulerStopped ? "stopped-respawnable" : "stopped"; + return "absent"; +} + +export function stopServiceIfInstalledDetailed(): ServiceStopOutcome { assertServiceEnvironmentMatchesInstall(); if (process.platform === "darwin") { if (existsSync(plistPath())) { - try { stopLaunchd(); return true; } catch { return false; } + try { stopLaunchd(); return "stopped"; } catch { return "failed"; } } } else if (process.platform === "win32") { // Query BOTH backends regardless of state: a failed switch or stale state can leave // two managers installed, and either one would respawn the proxy after `ocx stop`. let stopped = false; - try { - const q = schtasks(["/query", "/tn", TASK]); - if (q.includes(TASK)) { stopWindows(); stopped = true; } - } catch { /* task not found */ } + let failed = false; + let schedulerStopped = false; + let stateUnknown = false; + // `probeWindowsSchedulerTask` is tri-state on purpose: a query that THROWS is not the + // same as a task that is absent, and treating it as absent lets a live scheduler + // survive a "successful" stop. + const probe = probeWindowsSchedulerTask(); + if (probe.status === "present") { + if (stopWindowsChecked()) { stopped = true; schedulerStopped = true; } + else failed = true; + } else if (probe.status === "unknown") { + // Not "failed": nothing refused to stop. The query itself could not answer, which is + // a different problem with a different fix. + stateUnknown = true; + } if (statusWinswRaw() !== "nonexistent") { - try { stopWinswService(); stopped = true; } catch { /* best-effort */ } + try { stopWinswService(); stopped = true; } catch { failed = true; } } // `schtasks /end` ends the task instance but the cmd `:loop` wrapper survives and // respawns its child seconds later (issue #764), resurrecting the proxy during a // stop or a tray restart. Kill the launcher/wrapper processes outright. killWindowsServiceWrapperProcesses(); - if (stopped) return true; + // A failure on either backend wins: the other one stopping does not make the live one + // safe to update over. + const outcome = classifyWindowsServiceStop({ stopped, failed, schedulerStopped, stateUnknown }); + if (outcome !== "absent") return outcome; } else if (process.platform === "linux" && isSystemd() && existsSync(unitPath())) { - try { stopSystemd(); return true; } catch { return false; } + try { stopSystemd(); return "stopped"; } catch { return "failed"; } } - return false; + return "absent"; } /** Delete install-state files; stale state would make `ocx update` "reinstall" a service that no longer exists. */ @@ -3666,13 +3775,22 @@ export function setUninstallServiceHooksForTests(hooks: UninstallServiceHooksFor * service or scheduler task that cannot be removed throws so the caller cannot erase state and * report success. */ -export function uninstallServiceIfInstalled(): boolean { +/** + * Outcome of removing an installed manager. + * + * `false` used to mean both "nothing was installed" and "removal failed" on darwin and + * linux, so a failed removal was reported as absence and authorized the shared teardown + * while the service assets were still there (#3008). + */ +export type ServiceUninstallOutcome = "absent" | "removed" | "failed"; + +export function uninstallServiceDetailed(): ServiceUninstallOutcome { const hooks = uninstallServiceHooksForTests; (hooks?.assertEnvironment ?? assertServiceEnvironmentMatchesInstall)(); const platform = hooks?.platform ?? process.platform; if (platform === "darwin") { if (existsSync(plistPath())) { - try { uninstallLaunchd(); removeServiceInstallState(); return true; } catch { return false; } + try { uninstallLaunchd(); removeServiceInstallState(); return "removed"; } catch { return "failed"; } } } else if (platform === "win32") { let removed = false; @@ -3688,13 +3806,20 @@ export function uninstallServiceIfInstalled(): boolean { (hooks?.uninstallNative ?? uninstallWinswService)(); removed = true; } - if (removed) { (hooks?.removeInstallState ?? removeServiceInstallState)(); return true; } + if (removed) { (hooks?.removeInstallState ?? removeServiceInstallState)(); return "removed"; } } else if (platform === "linux" && existsSync(unitPath())) { - try { uninstallSystemd(); removeServiceInstallState(); return true; } catch { - try { unlinkSync(unitPath()); removeServiceInstallState(); return true; } catch { return false; } + try { uninstallSystemd(); removeServiceInstallState(); return "removed"; } catch { + try { unlinkSync(unitPath()); removeServiceInstallState(); return "removed"; } catch { return "failed"; } } } - return false; + return "absent"; +} + +/** Boolean form for callers that only distinguish "something was removed". */ +export function uninstallServiceIfInstalled(): boolean { + const outcome = uninstallServiceDetailed(); + if (outcome === "failed") throw new Error("the installed service could not be removed"); + return outcome === "removed"; } /** True if a background service (launchd/systemd/Task Scheduler) is installed. */ @@ -4204,11 +4329,17 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v const restore = await restoreNativeCodexAsync(); if (restore.success) console.log("✅ service stopped + native Codex restored."); else console.error(`⚠️ service stopped, but native Codex restore FAILED: ${restore.message}\nRun \`ocx restore\` (or check $CODEX_HOME/config.toml) before using native Codex.`); + if (!restore.success) process.exitCode = 1; // The Grok fence is the other managed config this command owns. Leaving it behind // pointed grok at a dead endpoint while native Codex was already restored. const grok = stripGrokConfig(); if (grok.changed) console.log(`↩️ ${grok.message}`); - else if (!grok.ok) console.error(`⚠️ ${grok.message}`); + else if (!grok.ok) { + // A failed strip leaves Grok aimed at a proxy this command just stopped. Exiting + // 0 tells a script the teardown finished when half of it did not. + console.error(`⚠️ ${grok.message}`); + process.exitCode = 1; + } } break; } @@ -4242,10 +4373,14 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v const restore = await restoreNativeCodexAsync(); if (!restore.success) { console.error(`⚠️ native Codex restore FAILED: ${restore.message}\nRun \`ocx restore\` before using native Codex.`); + process.exitCode = 1; } const grok = stripGrokConfig(); if (grok.changed) console.log(`↩️ ${grok.message}`); - else if (!grok.ok) console.error(`⚠️ ${grok.message}`); + else if (!grok.ok) { + console.error(`⚠️ ${grok.message}`); + process.exitCode = 1; + } } removeServiceInstallState(); try { if (existsSync(serviceApiTokenFilePath())) unlinkSync(serviceApiTokenFilePath()); } catch { /* best-effort */ } diff --git a/src/update/index.ts b/src/update/index.ts index 05e2d9aa73..1fd5fb099c 100644 --- a/src/update/index.ts +++ b/src/update/index.ts @@ -1,9 +1,14 @@ import { spawn, spawnSync } from "node:child_process"; +import { STOP_HISTORY_INCOMPLETE_EXIT_CODE } from "./stop-contract.mjs"; +import { proxyIdentityAt } from "../server/proxy-liveness"; +import { probeProxyLiveness } from "./proxy-liveness-probe.mjs"; +import { decidePostStopUpdate } from "./stop-decision.mjs"; import { readFileSync, readdirSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; import { getConfigDir, loadConfig } from "../config"; import { readPid, readRuntimePort } from "../config/process-state"; +import { pendingTeardownOutstanding } from "../config/pending-teardown"; import { npmInvocation } from "./npm-invocation.mjs"; import { npmCachePreflightFailureMessage, @@ -246,8 +251,13 @@ export async function runUpdate(): Promise<void> { // modules after startup, so an in-place update leaves it executing mixed old/new code. // Gate on the service and the runtime-port record too, not just the pid file — a // service-managed or orphaned proxy can be live while ocx.pid is stale/missing. + // + // An outstanding pending-teardown receipt is a fourth reason to run the stop. After a + // parent crashed mid-deferral all three of the other signals can be absent while the + // shared client config still points at a proxy that is gone; installing over that + // silently skips the recovery the receipt was written to trigger (#3008). // Full `ocx stop` semantics (drain, service stop, restore). - if (serviceWasInstalled || readPid() || readRuntimePort()) { + if (serviceWasInstalled || readPid() || readRuntimePort() || pendingTeardownOutstanding()) { console.log("⏹ Stopping the running proxy before updating..."); const stopStdio = updateChildStdio(); const stop = spawnSync(process.execPath, selfLaunchArgv(["stop"]), { @@ -256,17 +266,39 @@ export async function runUpdate(): Promise<void> { windowsHide: true, }); if (stopStdio === "pipe") logSpawnOutput("", stop); - if (stop.status !== 0 || readPid() || readRuntimePort()) { + // One decision, shared with the npm launcher (#3008). The two lanes disagreeing about + // the same situation is how this shipped fixed on one side only. Absent PID and runtime + // files are weak evidence - a crashed-but-listening proxy leaves none - so the captured + // endpoint is asked, and `null` from proxyIdentityAt covers refusal AND timeout alike. + const identity = await proxyIdentityAt(capturedListen.port, { hostname: capturedListen.hostname }); + const decision = decidePostStopUpdate({ + status: stop.status, + hasRuntimeState: !!(readPid() || readRuntimePort()), + // Re-checked AFTER the stop: a quarantined receipt lets the stop itself succeed + // (there is nothing left to stop), so a pre-stop check alone let the retry install + // over a teardown that never ran. + teardownOutstanding: pendingTeardownOutstanding(), + liveness: identity ? "live" : probeProxyLiveness(capturedListen.port, capturedListen.hostname), + }); + const historyOnlyStop = decision.reason === "history-only"; + if (!decision.proceed) { if (trayWasRunning) { try { const { startWindowsTray } = await import("../tray/windows"); startWindowsTray(); } catch { /* preserve the proxy stop failure */ } } - console.error("⚠️ Could not stop the running proxy; aborting the update. Run 'ocx stop' and retry."); + if (decision.reason === "teardown-outstanding") { + console.error("⚠️ A shared teardown from an earlier stop is still outstanding and needs manual review; aborting the update."); + console.error(" Confirm no proxy is running, run 'ocx restore', then remove the pending-teardown file in your opencodex home."); + } else { + console.error(decision.reason === "proxy-unknown" + ? `⚠️ Could not confirm the proxy on ${capturedListen.hostname}:${capturedListen.port} is stopped; aborting the update. Run 'ocx stop' and retry.` + : "⚠️ Could not stop the running proxy; aborting the update. Run 'ocx stop' and retry."); + } process.exit(1); } - if (historyRestoreIncomplete()) { + if (historyOnlyStop || historyRestoreIncomplete()) { console.warn( "⚠️ Codex resume-history metadata restore is incomplete (a backup manifest remains).\n" + " The DB may be busy or the manifest/target may need review; untracked routed history is intentionally unchanged.\n" + diff --git a/src/update/proxy-liveness-probe.d.mts b/src/update/proxy-liveness-probe.d.mts new file mode 100644 index 0000000000..a72c5fe028 --- /dev/null +++ b/src/update/proxy-liveness-probe.d.mts @@ -0,0 +1,6 @@ +/** Declaration for the plain-ESM liveness probe shared with `bin/ocx.mjs`. */ +export declare function probeProxyLiveness( + port: number, + hostname?: string, + timeoutMs?: number, +): "live" | "dead" | "unknown"; diff --git a/src/update/proxy-liveness-probe.mjs b/src/update/proxy-liveness-probe.mjs new file mode 100644 index 0000000000..6179156a32 --- /dev/null +++ b/src/update/proxy-liveness-probe.mjs @@ -0,0 +1,84 @@ +import { spawnSync } from "node:child_process"; + +/** + * Is something still answering `/healthz` as an opencodex proxy on this endpoint? + * + * Absent PID and runtime-port files are weak evidence that the proxy is gone: a crashed + * but still-listening process, or one supervised outside our records, leaves no files and + * keeps the port. Replacing package files under it leaves a server running a mix of old + * and new modules, which is the hazard `ocx update` stops the proxy to avoid (#3008). + * + * Synchronous and dependency-free because it runs inside the plain-Node launcher's + * `runNpmSelfUpdate`, which is not async and cannot import the TypeScript liveness module. + * A separate Node child does the fetch so the caller keeps its straight-line control flow. + * + * Returns `"live" | "dead" | "unknown"`, and the caller treats `unknown` as a reason to + * stop. Fail-open was wrong here: a listener that accepts connections but withholds + * `/healthz`, or a probe that times out, is exactly the state where replacing package + * files is most dangerous, and "we could not tell" is not evidence the proxy is gone. + * Only a refused connection or a definitive non-OpenCodex answer earns `"dead"`. + */ +export function probeProxyLiveness(port, hostname = "127.0.0.1", timeoutMs = 1500) { + // An unusable port is not an ambiguous probe: there is nothing to ask. + if (!Number.isFinite(port) || port <= 0 || port > 65535) return "dead"; + // Normalize HERE rather than at each call site. Leaving it to the callers put the fix in + // one lane and not the other, and a bracketed IPv6 literal handed to node:http answers + // nothing - which the tri-state correctly reports as "unknown" and the updater correctly + // treats as a reason to abort, turning a healthy stop into a refused update. + let host = typeof hostname === "string" && hostname.trim() !== "" ? hostname.trim() : "127.0.0.1"; + // A wildcard bind answers on loopback; `node:http` cannot dial the wildcard itself. + if (host === "0.0.0.0" || host === "*") host = "127.0.0.1"; + if (host === "::" ) host = "::1"; + // `[::1]` is a URL spelling; the socket layer wants the bare address. + if (host.startsWith("[") && host.endsWith("]")) host = host.slice(1, -1); + // `node:http` rather than `fetch`: the child inherits a parent whose event loop is + // blocked on `spawnSync`, and an aborted-before-dispatch fetch reports the same "not + // live" as a genuinely dead port. A request emitted on the socket cannot be confused + // with one that never left. + const script = [ + "const http = require('node:http');", + "const [host, port, timeout] = process.argv.slice(1);", + "const req = http.get({ host, port: Number(port), path: '/healthz', timeout: Number(timeout) }, res => {", + " let body = '';", + " res.setEncoding('utf8');", + " res.on('data', chunk => { body += chunk; });", + " res.on('end', () => {", + " try {", + " const parsed = JSON.parse(body);", + " // Mirrors isOpencodexHealthz in src/server/proxy-liveness.ts. A foreign server", + " // that happens to expose /healthz must not be read as our proxy, and a", + " // pre-identity build of ours must not be read as foreign.", + " const isOpencodex = parsed && typeof parsed === 'object'", + " && (parsed.service === 'opencodex'", + " || (parsed.service === undefined", + " && parsed.status === 'ok'", + " && typeof parsed.version === 'string'", + " && typeof parsed.uptime === 'number'));", + " // Only a clean 200 decides anything. Any other status means the endpoint is", + " // answering but not telling us what it is, which is not evidence of absence.", + " if (res.statusCode !== 200) process.stdout.write('UNKNOWN');", + " else process.stdout.write(isOpencodex ? 'LIVE' : 'DEAD');", + " } catch { process.stdout.write('UNKNOWN'); }", + " });", + "});", + "req.on('timeout', () => { process.stdout.write('UNKNOWN'); req.destroy(); });", + "// ECONNREFUSED is the one error that proves nothing is listening. Everything else -", + "// reset, unreachable host, TLS confusion - leaves the question open.", + "req.on('error', err => process.stdout.write(err && err.code === 'ECONNREFUSED' ? 'DEAD' : 'UNKNOWN'));", + ].join("\n"); + try { + const probe = spawnSync( + process.execPath, + ["-e", script, host, String(port), String(timeoutMs)], + { encoding: "utf8", timeout: timeoutMs + 1500, windowsHide: true }, + ); + const out = probe.stdout ?? ""; + if (out.includes("LIVE")) return "live"; + if (out.includes("DEAD")) return "dead"; + // A child that produced nothing, was killed by its own timeout, or failed to spawn + // leaves the question open rather than answering it. + return "unknown"; + } catch { + return "unknown"; + } +} diff --git a/src/update/stop-contract.d.mts b/src/update/stop-contract.d.mts new file mode 100644 index 0000000000..b077eb21b3 --- /dev/null +++ b/src/update/stop-contract.d.mts @@ -0,0 +1,2 @@ +/** Declaration for the plain-ESM stop contract shared with `bin/ocx.mjs`. */ +export declare const STOP_HISTORY_INCOMPLETE_EXIT_CODE: 79; diff --git a/src/update/stop-contract.mjs b/src/update/stop-contract.mjs new file mode 100644 index 0000000000..c72548b777 --- /dev/null +++ b/src/update/stop-contract.mjs @@ -0,0 +1,15 @@ +/** + * The exit code `ocx stop` uses to say "teardown succeeded, history cleanup did not". + * + * This is plain ESM rather than TypeScript because it has two consumers on opposite sides + * of a process boundary: `src/update/index.ts` and the Node launcher `bin/ocx.mjs`, which + * cannot import a `.ts` module. A TypeScript union would not survive `spawnSync` anyway — + * the value has to be on the wire, and an exit code is the wire. + * + * 79 is deliberate. It sits above the `sysexits.h` block (64-78), below `128 + signal`, + * and outside every code this CLI already uses: `src/cli/index.ts` emits 0, 1 and 130, + * and `src/cli/dispatch.ts` adds 2, 4 and 64. Picking one of those would have made a + * history-only stop indistinguishable from a config conflict, and `bin/ocx.mjs` mirrors + * the child's code faithfully enough to propagate the confusion. + */ +export const STOP_HISTORY_INCOMPLETE_EXIT_CODE = 79; diff --git a/src/update/stop-decision.d.mts b/src/update/stop-decision.d.mts new file mode 100644 index 0000000000..f773e786e6 --- /dev/null +++ b/src/update/stop-decision.d.mts @@ -0,0 +1,10 @@ +/** Declaration for the plain-ESM post-stop decision shared with `bin/ocx.mjs`. */ +export declare function decidePostStopUpdate(input: { + status: number | null; + hasRuntimeState: boolean; + liveness: "live" | "dead" | "unknown"; + teardownOutstanding?: boolean; +}): { + proceed: boolean; + reason: "stop-failed" | "runtime-state" | "teardown-outstanding" | "proxy-live" | "proxy-unknown" | "history-only" | "ok"; +}; diff --git a/src/update/stop-decision.mjs b/src/update/stop-decision.mjs new file mode 100644 index 0000000000..e96c11ae17 --- /dev/null +++ b/src/update/stop-decision.mjs @@ -0,0 +1,34 @@ +import { STOP_HISTORY_INCOMPLETE_EXIT_CODE } from "./stop-contract.mjs"; + +/** + * May an update replace package files after `ocx stop` returned? + * + * Both updaters ask this: `src/update/index.ts` on the Bun path and `bin/ocx.mjs` on the + * npm path the dashboard uses. It lives here as plain ESM so the Node launcher can import + * it, and so the two lanes cannot drift into disagreeing about the same situation — which + * is how #3008 shipped in the first place, with the fix on one side only. + * + * Returns `{ proceed, reason }`. The reasons are: + * + * - `stop-failed` — a nonzero status other than the history-only code, or a signal kill. + * A signal kill carries no evidence the teardown finished, so it is not a maybe. + * - `runtime-state` — a PID or runtime-port record survived the stop. + * - `teardown-outstanding` — a shared-teardown obligation survived the stop. That is a + * quarantined receipt awaiting a human: the stop itself can succeed (there was nothing + * left to stop), so checking only BEFORE the stop let the retry sail straight through + * and install over a teardown that never ran. + * - `proxy-live` — something is still answering as our proxy on the captured endpoint. + * - `proxy-unknown` — the probe could not answer. Absence of proof is not proof of + * absence, and replacing files under a live server leaves it running a mix of old and + * new modules. + * - `ok` / `history-only` — proceed; the second also prints the manifest warning. + */ +export function decidePostStopUpdate({ status, hasRuntimeState, liveness, teardownOutstanding = false }) { + const historyOnly = status === STOP_HISTORY_INCOMPLETE_EXIT_CODE; + if (status !== 0 && !historyOnly) return { proceed: false, reason: "stop-failed" }; + if (hasRuntimeState) return { proceed: false, reason: "runtime-state" }; + if (teardownOutstanding) return { proceed: false, reason: "teardown-outstanding" }; + if (liveness === "live") return { proceed: false, reason: "proxy-live" }; + if (liveness !== "dead") return { proceed: false, reason: "proxy-unknown" }; + return { proceed: true, reason: historyOnly ? "history-only" : "ok" }; +} diff --git a/tests/cli-ready.test.ts b/tests/cli-ready.test.ts index 577491e0ae..56a9875b56 100644 --- a/tests/cli-ready.test.ts +++ b/tests/cli-ready.test.ts @@ -861,8 +861,10 @@ describe("handleStart OCX_SERVICE exit guard (source-level)", () => { test("service.ts teardown kills surviving wrapper processes on stop", () => { const serviceSource = readFileSync(join(import.meta.dir, "../src/service.ts"), "utf8"); expect(serviceSource).toMatch(/killWindowsServiceWrapperProcesses/); - const callSite = serviceSource.match(/stopServiceIfInstalled[\s\S]{0,1200}?killWindowsServiceWrapperProcesses\(\)/); - expect(callSite, "wrapper kill must run during stopServiceIfInstalled").not.toBeNull(); + // The boolean `stopServiceIfInstalled` is gone — it collapsed a live manager into the + // same false as "not installed" (#3008). The stop itself is the detailed function. + const callSite = serviceSource.match(/stopServiceIfInstalledDetailed[\s\S]{0,1600}?killWindowsServiceWrapperProcesses\(\)/); + expect(callSite, "wrapper kill must run during stopServiceIfInstalledDetailed").not.toBeNull(); }); test("wrapper kill matches the canonical paths of THIS installation, not bare filenames", () => { diff --git a/tests/grok-lifecycle.test.ts b/tests/grok-lifecycle.test.ts index f245e917b5..b33c19d96c 100644 --- a/tests/grok-lifecycle.test.ts +++ b/tests/grok-lifecycle.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { isServiceOwnershipError, ServiceOwnershipError } from "../src/service"; +import { classifyWindowsServiceStop, installedServiceRespawnRisk, isServiceOwnershipError, ServiceOwnershipError } from "../src/service"; const CLI_SOURCE = readFileSync(join(import.meta.dir, "..", "src", "cli", "index.ts"), "utf8"); const ENSURE_SOURCE = readFileSync(join(import.meta.dir, "..", "src", "cli", "ensure-desired-integrations.ts"), "utf8"); @@ -95,20 +95,25 @@ describe("Grok fence lifecycle wiring", () => { expect(stopFn).toContain("isServiceOwnershipError(err)"); expect(stopFn).toContain("ownershipBlocked = true"); - expect(stopFn).toContain("if (!ownershipBlocked)"); + // Ownership is now one of two reasons to skip the restore; the other is an inherited + // obligation whose proxy could not be confirmed down (#3008). + expect(stopFn).toContain("const restoreBlocked = ownershipBlocked ||"); + expect(stopFn).toContain("if (!restoreBlocked) {"); expect(stopFn).toContain("await restoreSharedClientStateAfterStop()"); expect(restoreFn).toContain("restoreNativeCodexAsync()"); expect(restoreFn).not.toContain("revertSystemEnv()"); expect(restoreFn).toContain("stripGrokConfig()"); - expect(stopFn.indexOf("revertSystemEnv()")).toBeLessThan(stopFn.indexOf("if (!ownershipBlocked)")); + expect(stopFn.indexOf("revertSystemEnv()")).toBeLessThan(stopFn.indexOf("if (!restoreBlocked) {")); }); test("a refused Grok strip makes ocx stop fail instead of reporting success", () => { const restoreFn = sliceFn(CLI_SOURCE, "async function restoreSharedClientStateAfterStop(", "async function handleStop("); const stopFn = sliceFn(CLI_SOURCE, "async function handleStop(", "async function handleUninstall("); - expect(restoreFn).toContain("else if (!grok.ok) { restored = false;"); + // A Grok strip failure is "other", never history-only: it points Grok at a dead proxy, + // so an update must abort rather than proceed (#3008). + expect(restoreFn).toContain("else if (!grok.ok) { other = true;"); expect(restoreFn).toContain("Grok config restore failed"); - expect(stopFn).toContain("if (!await restoreSharedClientStateAfterStop()) stopFailed = true"); + expect(stopFn).toContain("if (restore.other) stopFailed = true"); }); test("a refused proxy stop reports WHY, not just that it failed", () => { @@ -148,13 +153,161 @@ describe("Grok fence lifecycle wiring", () => { expect(restartHelper).toContain("requestBoundSystemRestart(previous, deadlineAt)"); }); + test("a stopped scheduler is verified across the respawn window before stop succeeds", () => { + const stopFn = sliceFn(CLI_SOURCE, "async function handleStop(", "async function handleUninstall("); + // killWindowsSchedulerWrappers is best-effort and the `:loop` wrapper respawns after + // ~5s, so "stopped" alone is not a proven-down proxy. An update that trusts it can + // start replacing files during the dead interval (#3008). + expect(stopFn).toContain("proxyStillLiveAfterStop({ canRespawn: true })"); + // A survivor is an ordinary failure AND blocks shared teardown: restoring client + // config while the proxy runs leaves both pointing at each other. + expect(stopFn).toContain("stopFailed = true;"); + expect(stopFn).toContain("ownershipBlocked = true;"); + }); + + test("only Task Scheduler earns the respawn wait", () => { + const stopFn = sliceFn(CLI_SOURCE, "async function handleStop(", "async function handleUninstall("); + const serviceSource = readFileSync(join(import.meta.dir, "..", "src", "service.ts"), "utf8"); + // schtasks /end leaves the `cmd :loop` wrapper alive to respawn its child (#764). + // launchd, systemd and WinSW are down when they report stopped, so charging them a + // seven-second poll on every ocx stop would be a regression in ordinary use. + expect(serviceSource).toContain('"absent" | "stopped" | "stopped-respawnable" | "failed"'); + expect(serviceSource).toContain('schedulerStopped ? "stopped-respawnable" : "stopped"'); + expect(stopFn).toContain("if (schedulerCanRespawn && !ownershipBlocked)"); + // The wait is gated on the scheduler flag, not on "a service stopped". + expect(stopFn).not.toContain("if (stoppedService && !ownershipBlocked)"); + }); + + test("ocx stop defers shared teardown so a respawn survivor keeps its config", () => { + const stopFn = sliceFn(CLI_SOURCE, "async function handleStop(", "async function handleUninstall("); + const apiSource = readFileSync(join(import.meta.dir, "..", "src", "server", "management-api.ts"), "utf8"); + const controlSource = readFileSync(join(import.meta.dir, "..", "src", "lib", "process-control.ts"), "utf8"); + // POST /api/stop normally restores native Codex and strips the Grok fence itself. If + // ocx stop let it, a scheduler wrapper that respawns seconds later would already have + // lost its client config, and the parent ownershipBlocked guard could only prevent a + // second redundant teardown (#3008). + expect(stopFn).toContain("deferSharedTeardownNonce: teardownNonce"); + expect(controlSource).toContain("deferSharedTeardown"); + expect(apiSource).toContain("performStopTeardown(url, { ownsReceipt: deferralMatchesReceipt })"); + // The deferral is an obligation, so it is claimed on disk BEFORE it is requested and + // released only after THIS process has restored the shared config itself. A bare + // query flag could not survive the parent dying mid-stop. + const claimAt = stopFn.indexOf("claimTeardown(exact ?? configuredEndpoint()"); + expect(claimAt).toBeGreaterThan(-1); + expect(claimAt).toBeLessThan(stopFn.indexOf("deferSharedTeardownNonce: teardownNonce")); + // One resolved stop target feeds BOTH the receipt and the request, so the endpoint + // recorded is the endpoint contacted — recovery probes exactly that one. + // The endpoint is resolved ONCE: reading the runtime record twice let the receipt name + // the configured guess while the request went to one that appeared in between. + expect(stopFn).toContain("const exact = discovered ?? endpointOf(readRuntimePort(pid));"); + // Every stop claims a receipt, including the one that resolves no endpoint at all — + // that path goes straight to the kill ladder with no child teardown, so a warning + // instead of a receipt is exactly the parent-crash window this exists to close. + expect(stopFn).toContain('claimTeardown(exact ?? configuredEndpoint(), exact ? "exact" : "guessed");'); + // A guessed endpoint records an obligation but must not direct the stop request. + expect(stopFn).toContain("runtimeEndpoint: exact ?? undefined"); + // Nor may it authorize a later recovery: "the configured port refuses" is not proof + // that a proxy on an explicit --port is down. + expect(stopFn).toContain('if (read.receipt.endpointSource === "guessed")'); + const guessedBranch = stopFn.slice(stopFn.indexOf('if (read.receipt.endpointSource === "guessed")'), stopFn.indexOf("if (await abandonedTeardownIsSafeToFinish(")); + expect(guessedBranch).toContain("inheritedBlocks = true;"); + expect(guessedBranch).toContain("stopFailed = true;"); + expect(controlSource).toContain("io.runtimeEndpoint ?? readRuntime(pid)"); + // Inherited obligations are snapshotted BEFORE this run claims anything, so its own + // receipt is never mistaken for one it inherited. + expect(stopFn).toContain("isPendingTeardownAbandoned(read, isProcessAlive)"); + expect(stopFn.indexOf("listPendingTeardowns()")).toBeLessThan(claimAt); + expect(stopFn).toContain("clearPendingTeardown(nonce)"); + expect(stopFn.indexOf("await restoreSharedClientStateAfterStop()")) + .toBeLessThan(stopFn.indexOf("clearPendingTeardown(nonce)")); + // A receipt that survives its discharge would re-trigger recovery forever. + expect(stopFn).toContain("if (!clearPendingTeardown(nonce)) {"); + }); + + test("an unconfirmed inherited obligation blocks the restore, it does not merely warn", () => { + const stopFn = sliceFn(CLI_SOURCE, "async function handleStop(", "async function handleUninstall("); + // Finishing SOMEBODY ELSE's obligation needs a definitive "dead", not findLiveProxy's + // null, which also covers a timeout and a listener that withholds /healthz. The first + // attempt at this only logged a warning and then restored anyway, which is not a gate. + expect(stopFn).toContain("await abandonedTeardownIsSafeToFinish(read.receipt.endpoint)"); + expect(stopFn).toContain("const restoreBlocked = ownershipBlocked || inheritedBlocks"); + expect(stopFn).toContain("if (!restoreBlocked) {"); + // The restore is reached only through that gate — no other call site may bypass it. + const restoreCalls = stopFn.split("await restoreSharedClientStateAfterStop()").length - 1; + expect(restoreCalls).toBe(1); + expect(stopFn.indexOf("const restoreBlocked")).toBeLessThan(stopFn.indexOf("await restoreSharedClientStateAfterStop()")); + // An obligation that cannot be discharged fails the stop and is preserved. + const gateBlock = stopFn.slice(stopFn.indexOf("const recoveredNonces"), stopFn.indexOf("const restoreBlocked")); + expect(gateBlock).toContain("inheritedBlocks = true;"); + expect(gateBlock).toContain("stopFailed = true;"); + expect(gateBlock).not.toContain("clearPendingTeardown"); + // An unreadable obligation names no endpoint, so it can never probe dead. It fails the + // stop rather than being waved through, and is set aside only AFTER the outcome is + // known — moving it earlier would erase it from every future scan while the restore it + // stood for had not run. + expect(gateBlock).not.toContain("quarantinePendingTeardown"); + // Setting aside is not discharging, and the message must not claim otherwise: the + // renamed file still blocks an update until an operator removes it. + const quarantineBlock = stopFn.slice(stopFn.indexOf("if (unreadable.length > 0"), stopFn.indexOf("// Set the code rather than exiting inline")); + expect(quarantineBlock).toContain("It still blocks 'ocx update'"); + expect(quarantineBlock).toContain("has NOT restored on its behalf"); + expect(quarantineBlock).not.toContain("no longer blocks an update"); + expect(stopFn.indexOf("await restoreSharedClientStateAfterStop()")) + .toBeLessThan(stopFn.indexOf("quarantinePendingTeardown(read.nonce)")); + // Inherited receipts are evaluated whether or not this run claimed one of its own, and + // every discharged nonce is released together — otherwise a stop that finds a live + // proxy clears only its own and older obligations accumulate forever. + expect(stopFn).toContain("if (inheritedTeardowns.length > 0 && !ownershipBlocked)"); + expect(stopFn).toContain("teardownNonce ? [teardownNonce, ...recoveredNonces] : recoveredNonces"); + // The orphan path hands over the endpoint the probe already found; its runtime record + // is typically what went missing in the first place. + expect(stopFn).toContain('stopWithDeferral(live.pid, { hostname: live.hostname ?? "127.0.0.1", port: live.port })'); + // A live proxy with no killable pid is not "no proxy found": purging state and + // restoring over it is the same failure arrived at from the other direction. + expect(stopFn).toContain("} else if (live) {"); + const noPidBranch = stopFn.slice(stopFn.indexOf("} else if (live) {"), stopFn.indexOf('} else if (!stoppedService) {')); + expect(noPidBranch).toContain("stopFailed = true;"); + expect(noPidBranch).toContain("ownershipBlocked = true;"); + const gateFn = sliceFn(CLI_SOURCE, "const abandonedTeardownIsSafeToFinish", "let stopFailed = false;"); + expect(gateFn).toContain('probeProxyLiveness(endpoint.port, endpoint.hostname) === "dead"'); + expect(gateFn).toContain("return false;"); + }); + + test("an outstanding teardown receipt makes both updaters run the stop", () => { + // After a parent crashed mid-deferral the service, pid and runtime records can all be + // absent while shared client config still points at a proxy that is gone. Installing + // over that skips the recovery the receipt exists to trigger (#3008). + const updateSource = readFileSync(join(import.meta.dir, "..", "src", "update", "index.ts"), "utf8"); + expect(updateSource).toContain("readPid() || readRuntimePort() || pendingTeardownOutstanding()"); + const launcherSource = readFileSync(join(import.meta.dir, "..", "bin", "ocx.mjs"), "utf8"); + // The launcher runs under plain Node, so it shares the naming rule as ESM rather than + // spelling it out — which is how it ended up watching the retired singleton filename + // after receipts moved to one file per claim, silently seeing none of them. + expect(launcherSource).toContain("hasPendingTeardownIn(readdirSync, configDir())"); + expect(launcherSource).not.toContain('"pending-teardown.json"'); + expect(launcherSource).toContain("serviceWasInstalled || hasRuntimeState || hasPendingTeardown"); + // Checked AFTER the stop too: a quarantined receipt lets the stop succeed, so a + // pre-stop check alone let the retry install over a teardown that never ran. + expect(launcherSource).toContain("teardownOutstanding: hasPendingTeardownIn(readdirSync, configDir())"); + const updateSource2 = readFileSync(join(import.meta.dir, "..", "src", "update", "index.ts"), "utf8"); + expect(updateSource2).toContain("teardownOutstanding: pendingTeardownOutstanding()"); + const decisionSource = readFileSync(join(import.meta.dir, "..", "src", "update", "stop-decision.mjs"), "utf8"); + expect(decisionSource).toContain('if (teardownOutstanding) return { proceed: false, reason: "teardown-outstanding" };'); + const receiptSource = readFileSync(join(import.meta.dir, "..", "src", "config", "pending-teardown.ts"), "utf8"); + expect(receiptSource).toContain('from "./pending-teardown-names.mjs"'); + expect(receiptSource).toContain("isPendingTeardownFileName(name)"); + }); + test("handleStop treats an incomplete native Codex restore as a stop failure", () => { const restoreFn = sliceFn(CLI_SOURCE, "async function restoreSharedClientStateAfterStop(", "async function handleStop("); const stopFn = sliceFn(CLI_SOURCE, "async function handleStop(", "async function handleUninstall("); expect(restoreFn).toContain("if (result.success) console.log"); - expect(restoreFn).toContain("restored = false"); + // Config or catalog failure is a real teardown failure - a client reads those. Only a + // history-only failure is separable, and it still surfaces (#3008). + expect(restoreFn).toContain('artifacts.config.state === "failed" || artifacts.catalog.state === "failed"'); + expect(restoreFn).toContain("else other = true"); expect(restoreFn).toContain("console.error(`⚠️ ${result.message}`)"); - expect(stopFn).toContain("if (!await restoreSharedClientStateAfterStop()) stopFailed = true"); + expect(stopFn).toContain("if (restore.other) stopFailed = true"); }); test("the daemon's exit cleanup keeps the OCX_SERVICE exclusion and adds the ownership check", () => { @@ -217,15 +370,124 @@ describe("POST /api/stop teardown", () => { }); test("strips the Grok fence on an accepted stop", () => { + // The teardown moved to src/server/stop-teardown.ts so a test can call it: the route + // schedules process.exit 200ms after answering, which made the inline version + // unreachable. tests/stop-deferred-teardown.test.ts proves the behaviour; this proves + // the route still delegates to it rather than growing a second copy. + const handler = sliceFn(MANAGEMENT_SOURCE, '"/api/stop"', "/api/codex-auth/"); + expect(handler).toContain("performStopTeardown(url, { ownsReceipt: deferralMatchesReceipt })"); + const teardownSource = readFileSync(join(import.meta.dir, "..", "src", "server", "stop-teardown.ts"), "utf8"); + expect(teardownSource).toContain('await import("../grok/inject")'); + expect(teardownSource).toContain("stripGrokConfig()"); + }); + + test("an unreadable scheduler state gets the same diagnosis from the CLI and the API", () => { + const serviceSource = readFileSync(join(import.meta.dir, "..", "src", "service.ts"), "utf8"); + // A manager that refused to stop and a query that could not answer are different + // problems: reporting the second as "did not stop" sends the operator looking for the + // wrong thing, and `ocx stop` was the command the API told them to run (#3008). + expect(serviceSource).toContain('"absent" | "stopped" | "stopped-respawnable" | "failed" | "state-unknown"'); + // Behavioural, because a source-text assertion cannot tell whether an unreadable probe + // is still being folded into the generic failure. + expect(classifyWindowsServiceStop({ stopped: false, failed: false, schedulerStopped: false, stateUnknown: true })) + .toBe("state-unknown"); + // A readable failure outranks it — something actually refused to stop. + expect(classifyWindowsServiceStop({ stopped: false, failed: true, schedulerStopped: false, stateUnknown: true })) + .toBe("failed"); + // And an unreadable state outranks success: a scheduler we cannot see may respawn. + expect(classifyWindowsServiceStop({ stopped: true, failed: false, schedulerStopped: true, stateUnknown: true })) + .toBe("state-unknown"); + expect(classifyWindowsServiceStop({ stopped: true, failed: false, schedulerStopped: true, stateUnknown: false })) + .toBe("stopped-respawnable"); + expect(classifyWindowsServiceStop({ stopped: true, failed: false, schedulerStopped: false, stateUnknown: false })) + .toBe("stopped"); + expect(classifyWindowsServiceStop({ stopped: false, failed: false, schedulerStopped: false, stateUnknown: false })) + .toBe("absent"); + const stopFn = sliceFn(CLI_SOURCE, "async function handleStop(", "async function handleUninstall("); + expect(stopFn).toContain('if (serviceStop === "state-unknown")'); + const unknownBranch = stopFn.slice(stopFn.indexOf('if (serviceStop === "state-unknown")'), stopFn.indexOf('if (serviceStop === "state-unknown")') + 700); + expect(unknownBranch).toContain("stopFailed = true;"); + expect(unknownBranch).toContain("ocx service status"); + expect(unknownBranch).not.toContain("did not stop"); const handler = sliceFn(MANAGEMENT_SOURCE, '"/api/stop"', "/api/codex-auth/"); - expect(handler).toContain('await import("../grok/inject")'); - expect(handler).toContain("stripGrokConfig()"); + expect(handler).toContain('if (serviceStop === "state-unknown")'); + // The route answers the post-stop case with the same code as the pre-check. + expect((handler.match(/service_state_unknown/g) ?? []).length).toBeGreaterThanOrEqual(2); }); test("maps a failed shutdown drain to a nonzero process exit", () => { const handler = sliceFn(MANAGEMENT_SOURCE, '"/api/stop"', "/api/codex-auth/"); expect(handler).toContain("shutdownSucceeded = await drainAndShutdown"); - expect(handler).toContain("process.exit(shutdownSucceeded ? 0 : 1)"); + expect(handler).toContain("process.exit(shutdownSucceeded && teardown.success ? 0 : 1)"); + }); + + test("the route consumes the detailed service outcome instead of the boolean", () => { + const handler = sliceFn(MANAGEMENT_SOURCE, '"/api/stop"', "/api/codex-auth/"); + // stopServiceIfInstalled collapses "failed" into the same false as "not installed", so + // this route used to tear down shared config while a manager that refused to stop was + // still there to respawn the proxy (#3008). + expect(handler).toContain("stopServiceIfInstalledDetailed()"); + expect(handler).not.toContain("stopServiceIfInstalled();"); + expect(handler).toContain('if (serviceStop === "failed")'); + expect(handler.indexOf('if (serviceStop === "failed")')).toBeLessThan(handler.indexOf("await performStopTeardown")); + }); + + test("a respawnable backend is refused BEFORE the manager is touched", () => { + const handler = sliceFn(MANAGEMENT_SOURCE, '"/api/stop"', "/api/codex-auth/"); + // Stopping the Task Scheduler task and then returning 409 left the proxy running with + // its manager stopped — worse than either outcome, and the dashboard's Stop button + // sends a bare request on every backend. + expect(handler).toContain('const respawnRisk = holdsReceipt ? "none" : installedServiceRespawnRisk();'); + expect(handler).toContain('code: "respawnable_service"'); + expect(handler.indexOf("installedServiceRespawnRisk()")).toBeLessThan(handler.indexOf("stopServiceIfInstalledDetailed()")); + // The refusal must say nothing was changed, because nothing was. + expect(handler).toContain("Nothing was changed."); + // An unreadable scheduler state is its own answer: sending that operator to `ocx stop` + // would be a loop, because it maps the same unknown probe to a stop failure. + expect(handler).toContain('code: "service_state_unknown"'); + const unknownBranch = handler.slice(handler.indexOf('code: "service_state_unknown"'), handler.indexOf('code: "service_state_unknown"') + 500); + expect(unknownBranch).toContain("ocx service status"); + expect(unknownBranch).not.toContain("run `ocx stop`"); + }); + + test("only a proven absence is safe to stop inline", () => { + // Behavioural, not source-shaped: the previous assertion matched an unrelated + // `return true` in the catch and therefore passed while "unknown" was let through. + expect(installedServiceRespawnRisk(() => ({ status: "present" }) as never, "win32")).toBe("respawnable"); + // "unknown" is an ordinary return value from the probe, not a throw. Treating it as + // absence let the route kill scheduler wrappers before refusing. + // It is also kept distinct from "respawnable", because the remedy differs: `ocx stop` + // maps the same unknown to a stop failure, so telling that operator to run it loops. + expect(installedServiceRespawnRisk(() => ({ status: "unknown" }) as never, "win32")).toBe("unknown"); + expect(installedServiceRespawnRisk(() => { throw new Error("schtasks unavailable"); }, "win32")).toBe("unknown"); + // A proven absence is the only case that proceeds. + expect(installedServiceRespawnRisk(() => ({ status: "absent" }) as never, "win32")).toBe("none"); + // Every other platform is down when it says so; no wrapper can respawn. + expect(installedServiceRespawnRisk(() => ({ status: "present" }) as never, "darwin")).toBe("none"); + expect(installedServiceRespawnRisk(() => ({ status: "present" }) as never, "linux")).toBe("none"); + }); + + test("the daemon's exit status reflects the shared teardown, not just the drain", () => { + const handler = sliceFn(MANAGEMENT_SOURCE, '"/api/stop"', "/api/codex-auth/"); + // A drained proxy whose restore failed did not finish the job; exiting 0 told a + // supervisor the stop was clean while client config still pointed at this process. + expect(handler).toContain("process.exit(shutdownSucceeded && teardown.success ? 0 : 1)"); + }); + + test("direct service stop and uninstall fail when a shared teardown half fails", () => { + const serviceSource = readFileSync(join(import.meta.dir, "..", "src", "service.ts"), "utf8"); + // These paths logged the failure and exited 0, so a script could not tell a complete + // teardown from one that left Grok aimed at a stopped proxy. + const stopCase = serviceSource.slice( + serviceSource.indexOf("service stopped + native Codex restored"), + serviceSource.indexOf('case "status": {'), + ); + expect(stopCase).toContain("if (!restore.success) process.exitCode = 1;"); + expect((stopCase.match(/process\.exitCode = 1;/g) ?? []).length).toBeGreaterThanOrEqual(2); + const uninstallStart = serviceSource.indexOf("`⚠️ native Codex restore FAILED:"); + expect(uninstallStart).toBeGreaterThan(-1); + const uninstallCase = serviceSource.slice(uninstallStart, uninstallStart + 700); + expect((uninstallCase.match(/process\.exitCode = 1;/g) ?? []).length).toBeGreaterThanOrEqual(2); }); test("a 409 does not escalate to a forced kill", () => { diff --git a/tests/restore-completes-shared-teardown.test.ts b/tests/restore-completes-shared-teardown.test.ts new file mode 100644 index 0000000000..4efd282e28 --- /dev/null +++ b/tests/restore-completes-shared-teardown.test.ts @@ -0,0 +1,177 @@ +import { afterEach, beforeEach, expect, test } from "bun:test"; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { dispatchCommand, type CliDispatchDeps } from "../src/cli/dispatch"; + +/** + * `ocx restore` must finish the WHOLE shared teardown, including when Codex is already + * off (#3008). + * + * The deferred-teardown recovery path prints "run 'ocx restore', then delete the receipt". + * If restore returns success on the Codex no-op path before touching the Grok fence, an + * operator following those instructions signs off an incomplete teardown and deletes the + * obligation that would have caught it — leaving Grok pointed at a proxy that is gone. + */ + +const BEGIN = "# >>> opencodex managed block — do not edit (removed by `ocx stop`) >>>"; +const END = "# <<< opencodex managed block <<<"; +const depsFor = (args: string[]) => ({ args } as unknown as CliDispatchDeps); + +let grokHome: string; +let opencodexHome: string; +let codexHome: string; +let previous: Record<string, string | undefined> = {}; + +beforeEach(() => { + previous = { + GROK_HOME: process.env.GROK_HOME, + OPENCODEX_HOME: process.env.OPENCODEX_HOME, + CODEX_HOME: process.env.CODEX_HOME, + }; + grokHome = mkdtempSync(join(tmpdir(), "ocx-restore-grok-")); + opencodexHome = mkdtempSync(join(tmpdir(), "ocx-restore-home-")); + codexHome = mkdtempSync(join(tmpdir(), "ocx-restore-codex-")); + process.env.GROK_HOME = grokHome; + process.env.OPENCODEX_HOME = opencodexHome; + process.env.CODEX_HOME = codexHome; +}); + +afterEach(() => { + for (const [key, value] of Object.entries(previous)) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + for (const dir of [grokHome, opencodexHome, codexHome]) rmSync(dir, { recursive: true, force: true }); +}); + +async function seedOffConfig(): Promise<void> { + // Codex already OFF in this home, so the desired-state write reports "unchanged" and the + // residue classifier reports clean — the no-op path under test. Written through the real + // saver so the file satisfies the same schema the CLI validates. + const { loadConfig, saveConfig } = await import("../src/config"); + const config = loadConfig(); + saveConfig({ ...config, clientIntegrations: { ...(config.clientIntegrations ?? {}), codex: false } }); +} + +async function seedOnConfig(): Promise<void> { + // Codex ON, so the desired-state write is a real change and restore takes its ordinary + // forward path rather than the already-clean branch. + const { loadConfig, saveConfig } = await import("../src/config"); + const config = loadConfig(); + const integrations = { ...(config.clientIntegrations ?? {}) }; + delete integrations.codex; + saveConfig({ ...config, clientIntegrations: integrations }); +} + +function writeManagedGrokFence(): string { + mkdirSync(grokHome, { recursive: true }); + const configPath = join(grokHome, "config.toml"); + writeFileSync(configPath, [ + "# user content above", + BEGIN, + 'base_url = "http://127.0.0.1:10100/v1"', + END, + "", + ].join("\n")); + return configPath; +} + +test("restore strips the Grok fence even when Codex is already off and native", async () => { + await seedOffConfig(); + const configPath = writeManagedGrokFence(); + expect(readFileSync(configPath, "utf8")).toContain(BEGIN); + + // Codex is untouched in this home, so the desired-state write is "unchanged" and the + // residue classifier reports clean — the exact no-op path that used to return 0 before + // stripGrokConfig() ever ran. + const code = await dispatchCommand({ kind: "run", command: "restore", args: ["restore"] }, depsFor(["restore"])); + + const after = readFileSync(configPath, "utf8"); + expect(after).not.toContain(BEGIN); + expect(after).not.toContain(END); + expect(after).toContain("# user content above"); + expect(code).toBe(0); +}); + +test("the JSON envelope on that path reports the Grok cleanup too", async () => { + await seedOffConfig(); + writeManagedGrokFence(); + const lines: string[] = []; + const originalLog = console.log; + console.log = (...args: unknown[]) => { lines.push(args.map(String).join(" ")); }; + try { + await dispatchCommand({ kind: "run", command: "restore", args: ["restore", "--json"] }, depsFor(["restore", "--json"])); + } finally { + console.log = originalLog; + } + const envelope = JSON.parse(lines.at(-1)!); + // A machine caller must not read "already OFF and native" as "nothing was left to do". + expect(envelope.success).toBe(true); + expect(String(envelope.message)).toContain("already OFF and native"); + expect(String(envelope.message)).toMatch(/Grok|managed block/i); +}); + +test("the ordinary forward-restore path strips the fence before emitting JSON", async () => { + await seedOnConfig(); + // NOT the already-clean branch: Codex is ON here, so restore runs its real machinery + // and used to return the JSON envelope before stripGrokConfig() was ever called. + const configPath = writeManagedGrokFence(); + const lines: string[] = []; + const originalLog = console.log; + console.log = (...args: unknown[]) => { lines.push(args.map(String).join(" ")); }; + try { + await dispatchCommand({ kind: "run", command: "restore", args: ["restore", "--json"] }, depsFor(["restore", "--json"])); + } finally { + console.log = originalLog; + } + // The fence itself, not the wording: a message can claim a cleanup that never happened. + const after = readFileSync(configPath, "utf8"); + expect(after).not.toContain(BEGIN); + expect(after).toContain("# user content above"); + const envelope = JSON.parse(lines.at(-1)!); + expect(envelope).toHaveProperty("artifacts"); + expect(String(envelope.message)).toMatch(/Grok|managed block/i); +}); + +test("eject --json is the same runner and gets the same teardown", async () => { + await seedOnConfig(); + const configPath = writeManagedGrokFence(); + const lines: string[] = []; + const originalLog = console.log; + console.log = (...args: unknown[]) => { lines.push(args.map(String).join(" ")); }; + try { + await dispatchCommand({ kind: "run", command: "eject", args: ["eject", "--json"] }, depsFor(["eject", "--json"])); + } finally { + console.log = originalLog; + } + expect(readFileSync(configPath, "utf8")).not.toContain(BEGIN); +}); + +test("a Grok cleanup failure is not reported as a successful restore", async () => { + await seedOffConfig(); + // A directory where config.toml belongs: the strip cannot succeed, and the envelope + // must not say the teardown is done. + mkdirSync(join(grokHome, "config.toml"), { recursive: true }); + const lines: string[] = []; + const originalLog = console.log; + const originalError = console.error; + console.log = (...args: unknown[]) => { lines.push(args.map(String).join(" ")); }; + console.error = (...args: unknown[]) => { lines.push(args.map(String).join(" ")); }; + let code: number; + try { + code = await dispatchCommand({ kind: "run", command: "restore", args: ["restore", "--json"] }, depsFor(["restore", "--json"])); + } finally { + console.log = originalLog; + console.error = originalError; + } + expect(code).toBe(1); +}); + +test("with no Grok home at all the no-op path still succeeds quietly", async () => { + await seedOffConfig(); + rmSync(grokHome, { recursive: true, force: true }); + const code = await dispatchCommand({ kind: "run", command: "restore", args: ["restore"] }, depsFor(["restore"])); + expect(code).toBe(0); + expect(existsSync(grokHome)).toBe(false); +}); diff --git a/tests/stop-deferred-teardown.test.ts b/tests/stop-deferred-teardown.test.ts new file mode 100644 index 0000000000..69f978c83d --- /dev/null +++ b/tests/stop-deferred-teardown.test.ts @@ -0,0 +1,457 @@ +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { stopProxyGracefully } from "../src/lib/process-control"; +import { performStopTeardown } from "../src/server/stop-teardown"; +import type { CodexNativeRestoreResult } from "../src/codex/inject"; + +/** + * Behavioural cover for the deferred shared teardown (#3008). + * + * The wiring assertions in tests/grok-lifecycle.test.ts read source text, which cannot + * tell a working deferral from a plausible-looking one. These tests call the real + * functions: the graceful-stop client that builds the URL, the teardown decision the + * route delegates to, and the on-disk receipts that decide whether a deferral is an owned + * obligation or an unbacked request. + */ + +const ENDPOINT = { hostname: "127.0.0.1", port: 10100 }; +const FOREIGN_NONCE = "ffffffffffffffffffffffffffffffff"; +let home: string; +let previousHome: string | undefined; + +beforeEach(() => { + previousHome = process.env.OPENCODEX_HOME; + home = mkdtempSync(join(tmpdir(), "ocx-deferred-teardown-")); + process.env.OPENCODEX_HOME = home; +}); + +afterEach(() => { + if (previousHome === undefined) delete process.env.OPENCODEX_HOME; + else process.env.OPENCODEX_HOME = previousHome; + rmSync(home, { recursive: true, force: true }); +}); + +function restoreResult(success: boolean): CodexNativeRestoreResult { + return { + success, + message: success ? "native Codex restored" : "config restore failed", + artifacts: { + config: { state: success ? "restored" : "failed" }, + catalog: { state: "restored" }, + history: { state: "restored" }, + }, + } as unknown as CodexNativeRestoreResult; +} + +describe("stopProxyGracefully deferral flag", () => { + test("the default stop asks for no deferral", async () => { + const urls: string[] = []; + await stopProxyGracefully(11, { + readRuntime: () => ({ port: 10100 }), + fetchFn: (async (url: string | URL | Request) => { + urls.push(String(url)); + return new Response(JSON.stringify({ success: true }), { status: 200 }); + }) as typeof fetch, + waitExit: () => true, + env: {}, + }); + expect(urls).toEqual(["http://127.0.0.1:10100/api/stop"]); + }); + + test("a claimed nonce is carried in the query the route reads", async () => { + const urls: string[] = []; + await stopProxyGracefully(11, { + readRuntime: () => ({ port: 10100 }), + fetchFn: (async (url: string | URL | Request) => { + urls.push(String(url)); + return new Response(JSON.stringify({ success: true }), { status: 200 }); + }) as typeof fetch, + waitExit: () => true, + env: {}, + deferSharedTeardownNonce: FOREIGN_NONCE, + }); + expect(urls).toEqual([`http://127.0.0.1:10100/api/stop?deferSharedTeardown=1&teardownNonce=${FOREIGN_NONCE}`]); + }); + + test("the caller's endpoint snapshot is used instead of re-reading the runtime file", async () => { + const urls: string[] = []; + // The receipt records the endpoint the stop contacted. If this call re-read the + // runtime record it could contact a different one, and recovery would then probe an + // endpoint that was never stopped. + await stopProxyGracefully(11, { + readRuntime: () => ({ port: 19999, hostname: "127.0.0.1" }), + runtimeEndpoint: { hostname: "127.0.0.1", port: 10100 }, + fetchFn: (async (url: string | URL | Request) => { + urls.push(String(url)); + return new Response(JSON.stringify({ success: true }), { status: 200 }); + }) as typeof fetch, + waitExit: () => true, + env: {}, + }); + expect(urls).toEqual(["http://127.0.0.1:10100/api/stop"]); + }); +}); + +describe("performStopTeardown", () => { + test("an ordinary stop restores native Codex and strips the Grok fence", async () => { + let restored = 0; + let stripped = 0; + const body = await performStopTeardown(new URL("http://127.0.0.1:10100/api/stop"), { + ownsReceipt: () => false, + restoreNativeCodex: async () => { restored += 1; return restoreResult(true); }, + stripGrok: () => { stripped += 1; return { ok: true, changed: true, message: "Grok config restored" }; }, + }); + expect(restored).toBe(1); + expect(stripped).toBe(1); + expect(body.sharedTeardown).toBe("performed"); + expect(body.message).toContain("native Codex restored"); + }); + + test("a receipt-backed deferral touches neither config and says so", async () => { + let restored = 0; + let stripped = 0; + const body = await performStopTeardown(new URL(`http://127.0.0.1:10100/api/stop?deferSharedTeardown=1&teardownNonce=${FOREIGN_NONCE}`), { + ownsReceipt: nonce => nonce === FOREIGN_NONCE, + restoreNativeCodex: async () => { restored += 1; return restoreResult(true); }, + stripGrok: () => { stripped += 1; return { ok: true, changed: true, message: "Grok config restored" }; }, + }); + expect(restored).toBe(0); + expect(stripped).toBe(0); + expect(body.sharedTeardown).toBe("deferred"); + expect(body.message).toContain("deferred to the stopping client"); + // The old response claimed a restore that never happened; an operator reading it + // would believe native Codex was back while the deferral was still outstanding. + expect(body.message).not.toContain("native Codex restored"); + }); + + test("the real ownership check accepts only a nonce with a readable receipt on disk", async () => { + const mod = await import("../src/config/pending-teardown"); + const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); + let restored = 0; + const deferred = await performStopTeardown(new URL(`http://127.0.0.1:10100/api/stop?deferSharedTeardown=1&teardownNonce=${claimed.nonce}`), { + restoreNativeCodex: async () => { restored += 1; return restoreResult(true); }, + stripGrok: () => ({ ok: true, changed: true, message: "Grok config restored" }), + }); + expect(deferred.sharedTeardown).toBe("deferred"); + expect(restored).toBe(0); + + // Another caller riding on the existence of that obligation gets nothing: it does not + // own the nonce, so it cannot hand its teardown to anyone. + const ridden = await performStopTeardown(new URL(`http://127.0.0.1:10100/api/stop?deferSharedTeardown=1&teardownNonce=${FOREIGN_NONCE}`), { + restoreNativeCodex: async () => { restored += 1; return restoreResult(true); }, + stripGrok: () => ({ ok: true, changed: true, message: "Grok config restored" }), + }); + expect(ridden.sharedTeardown).toBe("performed"); + expect(restored).toBe(1); + }); + + test("the query alone does not buy a deferral without a receipt", async () => { + let restored = 0; + const body = await performStopTeardown(new URL("http://127.0.0.1:10100/api/stop?deferSharedTeardown=1"), { + ownsReceipt: () => false, + restoreNativeCodex: async () => { restored += 1; return restoreResult(true); }, + stripGrok: () => ({ ok: true, changed: true, message: "Grok config restored" }), + }); + // An authenticated caller that sets the flag and exits must not be able to leave + // client config pointed at a proxy that is going away. + expect(restored).toBe(1); + expect(body.sharedTeardown).toBe("performed"); + }); + + test("an unreadable receipt does not authorize a deferral", async () => { + const mod = await import("../src/config/pending-teardown"); + const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); + writeFileSync(mod.pendingTeardownPathFor(claimed.nonce), "{not json"); + let restored = 0; + const body = await performStopTeardown(new URL(`http://127.0.0.1:10100/api/stop?deferSharedTeardown=1&teardownNonce=${claimed.nonce}`), { + restoreNativeCodex: async () => { restored += 1; return restoreResult(true); }, + stripGrok: () => ({ ok: true, changed: true, message: "Grok config restored" }), + }); + expect(restored).toBe(1); + expect(body.sharedTeardown).toBe("performed"); + }); + + test("a failed restore still reports failure and the remediation", async () => { + const body = await performStopTeardown(new URL("http://127.0.0.1:10100/api/stop"), { + ownsReceipt: () => false, + restoreNativeCodex: async () => restoreResult(false), + stripGrok: () => ({ ok: false, changed: false, message: "grok home is read-only" }), + }); + expect(body.success).toBe(false); + expect(body.message).toContain("ocx restore"); + expect(body.message).toContain("Grok config cleanup failed"); + }); + + test("a Grok-only failure is not reported as a successful teardown", async () => { + // The native restore succeeding said nothing about the fence. Deciding success from + // the native half alone let a caller read success: true while Grok still pointed at a + // proxy that was exiting — the previous test masked it by failing both halves. + const body = await performStopTeardown(new URL("http://127.0.0.1:10100/api/stop"), { + ownsReceipt: () => false, + restoreNativeCodex: async () => restoreResult(true), + stripGrok: () => ({ ok: false, changed: false, message: "grok home is read-only" }), + }); + expect(body.success).toBe(false); + expect(body.sharedTeardown).toBe("performed"); + expect(body.message).toContain("Grok fence was not removed"); + expect(body.message).toContain("ocx restore"); + }); + + test("both halves succeeding is the only success", async () => { + const body = await performStopTeardown(new URL("http://127.0.0.1:10100/api/stop"), { + ownsReceipt: () => false, + restoreNativeCodex: async () => restoreResult(true), + stripGrok: () => ({ ok: true, changed: true, message: "Grok config restored" }), + }); + expect(body.success).toBe(true); + expect(body.message).not.toContain("Grok config cleanup failed"); + }); +}); + +describe("receipt naming is shared by both update lanes", () => { + test("the launcher's scan and the TypeScript listing agree on what is outstanding", async () => { + const mod = await import("../src/config/pending-teardown"); + const names = await import("../src/config/pending-teardown-names.mjs"); + const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); + + // bin/ocx.mjs runs under plain Node and cannot import the TypeScript module, so the + // naming rule lives in one shared .mjs. Spelling it twice is exactly how the npm lane + // ended up watching a filename that no longer existed. + expect(names.hasPendingTeardownIn(readdirSync, home)).toBe(true); + expect(mod.pendingTeardownOutstanding()).toBe(true); + + // The retired singleton name is not a receipt. + expect(names.isPendingTeardownFileName("pending-teardown.json")).toBe(false); + expect(names.isPendingTeardownFileName(`pending-teardown-${claimed.nonce}.json`)).toBe(true); + // A quarantined receipt is no longer READ by the recovery loop... + const quarantinedName = `pending-teardown-${claimed.nonce}.unreadable.json`; + expect(names.isPendingTeardownFileName(quarantinedName)).toBe(false); + // ...but it is still an obligation, so it still blocks an update. + expect(names.isQuarantinedTeardownFileName(quarantinedName)).toBe(true); + expect(names.isAnyTeardownObligationFileName(quarantinedName)).toBe(true); + + mod.quarantinePendingTeardown(claimed.nonce); + expect(mod.listPendingTeardowns()).toHaveLength(0); + // Both lanes still refuse to install over a teardown that never ran. + expect(names.hasPendingTeardownIn(readdirSync, home)).toBe(true); + expect(mod.pendingTeardownOutstanding()).toBe(true); + expect(mod.listQuarantinedTeardowns()).toHaveLength(1); + + // Only a human removing the file ends the enforcement. + rmSync(mod.listQuarantinedTeardowns()[0]!); + expect(names.hasPendingTeardownIn(readdirSync, home)).toBe(false); + expect(mod.pendingTeardownOutstanding()).toBe(false); + }); + + test("a scan that fails is not an empty scan", async () => { + const names = await import("../src/config/pending-teardown-names.mjs"); + // Only a missing home is honestly empty. Any other failure may be hiding an + // obligation, and reporting "none" would let an update install over a teardown that + // never ran — absence of proof is not proof of absence. + const enoent = Object.assign(new Error("no such directory"), { code: "ENOENT" }); + expect(names.hasPendingTeardownIn(() => { throw enoent; }, home)).toBe(false); + const denied = Object.assign(new Error("permission denied"), { code: "EACCES" }); + expect(names.hasPendingTeardownIn(() => { throw denied; }, home)).toBe(true); + expect(names.hasPendingTeardownIn(() => { throw new Error("no code at all"); }, home)).toBe(true); + }); + + test("a home that cannot be scanned is its own state, not a fabricated receipt", async () => { + const mod = await import("../src/config/pending-teardown"); + const previous = process.env.OPENCODEX_HOME; + // A file where the home should be: readdir fails with ENOTDIR, which is not absence. + const notADir = join(home, "not-a-directory"); + writeFileSync(notADir, ""); + process.env.OPENCODEX_HOME = notADir; + try { + const listed = mod.listPendingTeardowns(); + // handleStop must see something blocking rather than an empty set it would restore over. + expect(listed).toHaveLength(1); + // Not "invalid": that carries a nonce, and a synthesized one would be handed to the + // quarantine and clear paths, which could rename or delete a real receipt. + expect(listed[0]!.state).toBe("unscannable"); + expect(listed[0]).not.toHaveProperty("nonce"); + expect(mod.isPendingTeardownAbandoned(listed[0]!, () => false, 1)).toBe(true); + expect(mod.pendingTeardownOutstanding()).toBe(true); + } finally { + if (previous === undefined) delete process.env.OPENCODEX_HOME; + else process.env.OPENCODEX_HOME = previous; + } + }); +}); + +describe("endpoint provenance", () => { + test("a guessed endpoint is recorded as such and is not exact evidence", async () => { + const mod = await import("../src/config/pending-teardown"); + const guessed = mod.claimPendingTeardown({ hostname: "127.0.0.1", port: 10100 }, "guessed", 1234); + const read = mod.readPendingTeardown(guessed.nonce); + expect(read.state === "valid" && read.receipt.endpointSource).toBe("guessed"); + + // A proxy started with an explicit --port can be respawned there while the configured + // address refuses, so a dead probe of THIS address proves nothing. handleStop reads + // the provenance and fails closed rather than restoring on it. + const exact = mod.claimPendingTeardown({ hostname: "127.0.0.1", port: 19999 }, "exact", 1234); + expect(mod.readPendingTeardown(exact.nonce)).toMatchObject({ state: "valid" }); + }); + + test("a receipt without provenance is invalid, so an old-format file cannot be trusted", async () => { + const mod = await import("../src/config/pending-teardown"); + const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); + writeFileSync( + mod.pendingTeardownPathFor(claimed.nonce), + JSON.stringify({ ownerPid: 1234, nonce: claimed.nonce, createdAt: "t", endpoint: ENDPOINT }), + ); + expect(mod.readPendingTeardown(claimed.nonce).state).toBe("invalid"); + writeFileSync( + mod.pendingTeardownPathFor(claimed.nonce), + JSON.stringify({ ownerPid: 1234, nonce: claimed.nonce, createdAt: "t", endpoint: ENDPOINT, endpointSource: "maybe" }), + ); + expect(mod.readPendingTeardown(claimed.nonce).state).toBe("invalid"); + }); +}); + +describe("post-stop update decision", () => { + test("an outstanding obligation aborts the install even when the stop succeeded", async () => { + const { decidePostStopUpdate } = await import("../src/update/stop-decision.mjs"); + // A quarantined receipt lets the stop itself succeed — there is nothing left to stop — + // so checking only BEFORE the stop let the retry sail through and install over a + // teardown that never ran. + expect(decidePostStopUpdate({ status: 0, hasRuntimeState: false, liveness: "dead", teardownOutstanding: true })) + .toEqual({ proceed: false, reason: "teardown-outstanding" }); + expect(decidePostStopUpdate({ status: 0, hasRuntimeState: false, liveness: "dead", teardownOutstanding: false })) + .toEqual({ proceed: true, reason: "ok" }); + // Omitting the field keeps the previous behaviour for any caller that has not adopted it. + expect(decidePostStopUpdate({ status: 0, hasRuntimeState: false, liveness: "dead" })) + .toEqual({ proceed: true, reason: "ok" }); + // A real stop failure still wins: it is the stronger signal. + expect(decidePostStopUpdate({ status: 1, hasRuntimeState: false, liveness: "dead", teardownOutstanding: true })) + .toEqual({ proceed: false, reason: "stop-failed" }); + }); +}); + +describe("pending teardown receipts", () => { + test("a claim is durable and carries the endpoint it was stopping", async () => { + const mod = await import("../src/config/pending-teardown"); + const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); + expect(claimed.nonce).toMatch(/^[0-9a-f]{32}$/); + expect(existsSync(mod.pendingTeardownPathFor(claimed.nonce))).toBe(true); + const read = mod.readPendingTeardown(claimed.nonce); + expect(read.state).toBe("valid"); + expect(read.state === "valid" && read.receipt.endpoint).toEqual(ENDPOINT); + expect(mod.pendingTeardownOutstanding()).toBe(true); + }); + + test("a clear names one obligation, so a concurrent claim cannot be deleted by it", async () => { + const mod = await import("../src/config/pending-teardown"); + // Review round 8 reproduced the delete-the-wrong-receipt bug; round 10 pointed out + // that a read-compare-unlink against ONE shared path is still racy, because the file + // can be replaced between the compare and the unlink. The nonce is the filename now, + // so the replacement is a DIFFERENT file and the delete cannot reach it — no ordering + // of the two operations matters. + const abandoned = mod.claimPendingTeardown(ENDPOINT, "exact", 1111); + const concurrent = mod.claimPendingTeardown(ENDPOINT, "exact", 2222); + expect(mod.listPendingTeardowns()).toHaveLength(2); + + expect(mod.clearPendingTeardown(abandoned.nonce)).toBe(true); + const survivors = mod.listPendingTeardowns(); + expect(survivors).toHaveLength(1); + expect(survivors[0]!.state === "valid" && survivors[0]!.receipt.nonce).toBe(concurrent.nonce); + }); + + test("clearing reports whether the obligation is actually gone", async () => { + const mod = await import("../src/config/pending-teardown"); + const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); + expect(mod.clearPendingTeardown(claimed.nonce)).toBe(true); + // Already gone is still "gone" — an idempotent discharge is not a failure. + expect(mod.clearPendingTeardown(claimed.nonce)).toBe(true); + // A receipt that cannot be removed must be reported, or recovery repeats forever. + const stuck = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); + rmSync(mod.pendingTeardownPathFor(stuck.nonce)); + mkdirSync(mod.pendingTeardownPathFor(stuck.nonce), { recursive: true }); + mkdirSync(join(mod.pendingTeardownPathFor(stuck.nonce), "child"), { recursive: true }); + expect(mod.clearPendingTeardown(stuck.nonce)).toBe(false); + rmSync(mod.pendingTeardownPathFor(stuck.nonce), { recursive: true, force: true }); + }); + + test("an unreadable receipt is invalid, outstanding, and quarantinable", async () => { + const mod = await import("../src/config/pending-teardown"); + const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); + writeFileSync(mod.pendingTeardownPathFor(claimed.nonce), "{not json"); + const read = mod.readPendingTeardown(claimed.nonce); + expect(read.state).toBe("invalid"); + expect(mod.pendingTeardownOutstanding()).toBe(true); + // It names no endpoint, so nothing can prove its proxy down. Quarantine stops the + // recovery loop from re-reading garbage on every stop, but the obligation REMAINS + // outstanding: filing it away to unblock an update would let the next install land + // over a teardown that never ran. + const moved = mod.quarantinePendingTeardown(claimed.nonce); + expect(moved).toBeTruthy(); + expect(existsSync(moved!)).toBe(true); + expect(mod.listPendingTeardowns()).toHaveLength(0); + expect(mod.pendingTeardownOutstanding()).toBe(true); + expect(readdirSync(home).some(n => n.endsWith(".unreadable.json"))).toBe(true); + }); + + test("a directory where a receipt belongs is invalid, not missing", async () => { + const mod = await import("../src/config/pending-teardown"); + const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); + rmSync(mod.pendingTeardownPathFor(claimed.nonce)); + mkdirSync(mod.pendingTeardownPathFor(claimed.nonce), { recursive: true }); + // Reading that as absence hides an obligation that may still be outstanding. + expect(mod.readPendingTeardown(claimed.nonce).state).toBe("invalid"); + expect(mod.pendingTeardownOutstanding()).toBe(true); + rmSync(mod.pendingTeardownPathFor(claimed.nonce), { recursive: true, force: true }); + }); + + test("a receipt whose body disagrees with its filename is invalid", async () => { + const mod = await import("../src/config/pending-teardown"); + const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); + writeFileSync( + mod.pendingTeardownPathFor(claimed.nonce), + JSON.stringify({ ownerPid: 1234, nonce: FOREIGN_NONCE, createdAt: "t", endpoint: ENDPOINT, endpointSource: "exact" }), + ); + // Otherwise an edited body could claim an identity the file name does not carry. + expect(mod.readPendingTeardown(claimed.nonce).state).toBe("invalid"); + expect(mod.deferralMatchesReceipt(claimed.nonce)).toBe(false); + }); + + test("a receipt without a usable endpoint is invalid, because recovery could not locate it", async () => { + const mod = await import("../src/config/pending-teardown"); + const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 1234); + const path = mod.pendingTeardownPathFor(claimed.nonce); + const base = { ownerPid: 7, nonce: claimed.nonce, createdAt: "t", endpointSource: "exact" }; + writeFileSync(path, JSON.stringify(base)); + expect(mod.readPendingTeardown(claimed.nonce).state).toBe("invalid"); + writeFileSync(path, JSON.stringify({ ...base, endpoint: { hostname: "", port: 10100 } })); + expect(mod.readPendingTeardown(claimed.nonce).state).toBe("invalid"); + writeFileSync(path, JSON.stringify({ ...base, endpoint: { hostname: "127.0.0.1", port: 0 } })); + expect(mod.readPendingTeardown(claimed.nonce).state).toBe("invalid"); + }); + + test("only an abandoned receipt is recoverable", async () => { + const mod = await import("../src/config/pending-teardown"); + const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 4242); + const live = mod.readPendingTeardown(claimed.nonce); + + // A stop that is still running owns its own obligation; finishing it from here would + // restore client config while that stop is still deciding whether a proxy survived. + expect(mod.isPendingTeardownAbandoned(live, () => true, 1)).toBe(false); + // This process's own receipt is not "abandoned" either. + expect(mod.isPendingTeardownAbandoned(live, () => false, 4242)).toBe(false); + // A dead owner left the obligation behind: recover it. + expect(mod.isPendingTeardownAbandoned(live, () => false, 1)).toBe(true); + expect(mod.isPendingTeardownAbandoned({ state: "missing" }, () => false, 1)).toBe(false); + }); + + test("deferralMatchesReceipt needs a well-formed nonce that names a readable receipt", async () => { + const mod = await import("../src/config/pending-teardown"); + const claimed = mod.claimPendingTeardown(ENDPOINT, "exact", 7); + expect(mod.deferralMatchesReceipt(claimed.nonce)).toBe(true); + expect(mod.deferralMatchesReceipt(FOREIGN_NONCE)).toBe(false); + expect(mod.deferralMatchesReceipt(null)).toBe(false); + // A path-shaped "nonce" must not be able to reach outside the receipt namespace. + expect(mod.deferralMatchesReceipt("../config")).toBe(false); + expect(mod.deferralMatchesReceipt("")).toBe(false); + }); +}); diff --git a/tests/uninstall.test.ts b/tests/uninstall.test.ts index 8988ed3431..a0d233a1f4 100644 --- a/tests/uninstall.test.ts +++ b/tests/uninstall.test.ts @@ -95,9 +95,165 @@ describe("full uninstall command", () => { expect(uninstallBody).toContain('runStep("proxy stopped"'); expect(uninstallBody).toContain('runStep("service removed"'); expect(uninstallBody).toContain("await stopProxy(pid);"); - expect(uninstallBody).toContain("uninstallServiceIfInstalled()"); + expect(uninstallBody).toContain("uninstallServiceDetailed()"); expect(uninstallBody.indexOf('runStep("service stopped"')).toBeLessThan(uninstallBody.indexOf('runStep("proxy stopped"')); expect(uninstallBody.indexOf('runStep("proxy stopped"')).toBeLessThan(uninstallBody.indexOf('runStep("service removed"')); - expect(uninstallBody.indexOf("await stopProxy(pid);")).toBeLessThan(uninstallBody.indexOf("uninstallServiceIfInstalled()")); + expect(uninstallBody.indexOf("await stopProxy(pid);")).toBeLessThan(uninstallBody.indexOf("uninstallServiceDetailed()")); }); }); +describe("uninstall gates shared teardown on a proven service stop", () => { + test("the authorization rule, exercised for every failure permutation", async () => { + const { sharedTeardownAuthorized } = await import("../src/cli/uninstall-plan"); + const base = { + serviceStop: "stopped" as const, + proxyProvenDown: true, + serviceRemoval: "removed" as const, + respawnWindowVerified: false, + }; + expect(sharedTeardownAuthorized(base)).toBe(true); + expect(sharedTeardownAuthorized({ ...base, serviceStop: "absent" })).toBe(true); + expect(sharedTeardownAuthorized({ ...base, serviceRemoval: "absent" })).toBe(true); + // Removing the registration does not prove an already-running wrapper died; killing it + // is best-effort (#764), so the restart window has to be polled first. + expect(sharedTeardownAuthorized({ ...base, serviceStop: "stopped-respawnable" })).toBe(false); + expect(sharedTeardownAuthorized({ ...base, serviceStop: "stopped-respawnable", respawnWindowVerified: true })).toBe(true); + // A manager that refused to stop, or one we could not read, may still be running. + expect(sharedTeardownAuthorized({ ...base, serviceStop: "failed" })).toBe(false); + expect(sharedTeardownAuthorized({ ...base, serviceStop: "state-unknown" })).toBe(false); + // The step itself threw: we know nothing. + expect(sharedTeardownAuthorized({ ...base, serviceStop: null })).toBe(false); + // A proxy that could not be PROVEN down — a live orphan with no pid, or an endpoint + // that would not answer — blocks it. A findLiveProxy miss is not proof. + expect(sharedTeardownAuthorized({ ...base, proxyProvenDown: false })).toBe(false); + // A removal that failed used to look like absence on darwin and linux. + expect(sharedTeardownAuthorized({ ...base, serviceRemoval: "failed" })).toBe(false); + expect(sharedTeardownAuthorized({ ...base, serviceRemoval: null })).toBe(false); + }); + + test("a removal failure is distinguishable from nothing being installed", async () => { + const { setUninstallServiceHooksForTests, uninstallServiceDetailed } = await import("../src/service"); + // Windows is the platform whose hooks are injectable; the darwin/linux catch arms that + // returned the same false as absence are now typed outcomes rather than a boolean. + setUninstallServiceHooksForTests({ + platform: "win32", + assertEnvironment: () => {}, + probeWindowsTask: () => ({ status: "absent" }) as never, + uninstallWindowsTask: () => {}, + nativeStatus: () => "nonexistent", + uninstallNative: () => {}, + removeInstallState: () => {}, + } as never); + expect(uninstallServiceDetailed()).toBe("absent"); + + const serviceSource = await readText("src/service.ts"); + // The darwin and linux arms return "failed", not the absence value. + expect(serviceSource).toContain('try { uninstallLaunchd(); removeServiceInstallState(); return "removed"; } catch { return "failed"; }'); + expect(serviceSource).toContain('try { unlinkSync(unitPath()); removeServiceInstallState(); return "removed"; } catch { return "failed"; }'); + }); + + test("a live orphan with no pid file blocks the teardown", async () => { + const cli = await readText("src/cli/index.ts"); + const at = cli.indexOf("async function handleUninstall("); + const fn = cli.slice(at, at + 9000); + // A missing pid file is not proof that nothing is serving — the same discovery + // `ocx stop` performs. Without it, uninstall restored shared config under a live proxy. + expect(fn).toContain("const live = await findLiveProxy();"); + expect(fn).toContain("observed.proxyProvenDown = await proxyEndpointProvenDown();"); + expect(fn).toContain("no process id could be resolved for it"); + // The orphan-with-no-pid branch THROWS, so `proxyProvenDown` stays false and the + // authorization rule refuses the shared teardown. + const orphanBranch = fn.slice(fn.indexOf("const live = await findLiveProxy();"), fn.indexOf("const live = await findLiveProxy();") + 600); + expect(orphanBranch).toContain("throw new Error("); + // A findLiveProxy miss is not proof either: it goes through the tri-state probe first. + expect(orphanBranch).toContain("could not be confirmed down either"); + }); + + async function uninstallFn(): Promise<string> { + const cli = await readText("src/cli/index.ts"); + const at = cli.indexOf("async function handleUninstall("); + expect(at).toBeGreaterThan(-1); + return cli.slice(at, at + 9000); + } + + test("the detailed outcome is consumed, not the boolean collapse", async () => { + const fn = await uninstallFn(); + // stopServiceIfInstalled returns false for "not installed", "refused to stop" and + // "state could not be read" alike, so this step reported "not installed" for a manager + // that might still be running (#3008). + expect(fn).toContain("stopServiceIfInstalledDetailed()"); + expect(fn).not.toContain("stopServiceIfInstalled()"); + expect(fn).toContain('if (outcome === "absent") return false;'); + expect(fn).toContain('if (outcome === "failed")'); + expect(fn).toContain('if (outcome === "state-unknown")'); + }); + + test("shared teardown runs only when nothing that could still serve is unaccounted for", async () => { + const fn = await uninstallFn(); + // The rule itself is exercised by calling it above; this pins the wiring. + expect(fn).toContain("if (sharedTeardownAuthorized(observed)) {"); + // Every step that could leave something serving records what it observed, and the + // fields start pessimistic so a step that throws cannot look like a success. + expect(fn).toContain("serviceStop: null,"); + expect(fn).toContain("proxyProvenDown: false,"); + expect(fn).toContain("serviceRemoval: null,"); + expect(fn).toContain("respawnWindowVerified: false,"); + expect(fn).toContain("observed.serviceStop = outcome;"); + expect(fn).toContain("observed.serviceRemoval = outcome;"); + expect(fn).toContain('if (observed.serviceStop === "stopped-respawnable")'); + expect(fn).toContain("observed.respawnWindowVerified = true;"); + const gateAt = fn.indexOf("if (sharedTeardownAuthorized(observed)) {"); + expect(gateAt).toBeLessThan(fn.indexOf("native Codex restored", gateAt)); + // The skip is a failure, not a silent pass: the command must exit nonzero and say what + // to run once the blocker is resolved. + expect(fn).toContain('failures.push("native Codex restored", "Grok Build config restored");'); + expect(fn).toContain("Skipping shared teardown"); + // Naming only `ocx restore` was wrong: it restores client routing but leaves the + // service removal and local cleanup this command had not reached. + expect(fn).toContain("rerun 'ocx uninstall'"); + expect(fn).toContain("interim step"); + }); +}); + test("proof covers every distinct endpoint, not just the preferred one", async () => { + const { endpointsToProve, everyEndpointProvenDown } = await import("../src/cli/uninstall-plan"); + + // A stale runtime record pointing at a closed port, and the live proxy on the + // configured one. Probing only the runtime candidate reports "dead" for a port nobody + // is using and authorizes the teardown (#3008). + const endpoints = endpointsToProve({ port: 10999, hostname: "127.0.0.1" }, { port: 10100, hostname: "127.0.0.1" }); + expect(endpoints).toEqual([ + { hostname: "127.0.0.1", port: 10999 }, + { hostname: "127.0.0.1", port: 10100 }, + ]); + const closedRuntimeLiveConfig = (e: { port: number }) => (e.port === 10999 ? "dead" as const : "live" as const); + expect(everyEndpointProvenDown(endpoints, closedRuntimeLiveConfig)).toBe(false); + // A silent listener is not absence either. + expect(everyEndpointProvenDown(endpoints, e => (e.port === 10999 ? "dead" : "unknown"))).toBe(false); + // Both definitively dead is the only proof. + expect(everyEndpointProvenDown(endpoints, () => "dead")).toBe(true); + + // Identical candidates collapse to one; a missing runtime record leaves the config one. + expect(endpointsToProve({ port: 10100, hostname: "127.0.0.1" }, { port: 10100 })).toHaveLength(1); + expect(endpointsToProve(null, { port: 10100 })).toEqual([{ hostname: "127.0.0.1", port: 10100 }]); + // No configured port still yields the default, so the set is never empty in practice. + expect(endpointsToProve(null, {})).toEqual([{ hostname: "127.0.0.1", port: 10100 }]); + // An empty set is not proof of anything. + expect(everyEndpointProvenDown([], () => "dead")).toBe(false); + // A nonsense runtime port is skipped rather than probed. + expect(endpointsToProve({ port: 0 }, { port: 10100 })).toEqual([{ hostname: "127.0.0.1", port: 10100 }]); + }); + + test("the respawn window is verified by evidence, not by a silent poll", async () => { + const cli = await readText("src/cli/index.ts"); + const at = cli.indexOf("async function handleUninstall("); + const fn = cli.slice(at, at + 9000); + // proxyStillLiveAfterStop returns null on a timeout as well as on a genuinely dead + // endpoint, so a respawned-but-unresponsive proxy looked verified-down. + const windowStep = fn.slice(fn.indexOf('runStep("respawn window verified"'), fn.indexOf('runStep("respawn window verified"') + 900); + expect(windowStep).toContain("if (!await proxyEndpointProvenDown())"); + expect(windowStep).toContain("could not be confirmed down either"); + expect(windowStep.indexOf("if (!await proxyEndpointProvenDown())")) + .toBeLessThan(windowStep.indexOf("observed.respawnWindowVerified = true;")); + // And the proof itself asks every candidate. + expect(fn).toContain("endpointsToProve(readRuntimePort(), loadConfig())"); + expect(fn).toContain("everyEndpointProvenDown(endpoints, e => probeProxyLiveness(e.port, e.hostname))"); + }); diff --git a/tests/update-stop-classification.test.ts b/tests/update-stop-classification.test.ts new file mode 100644 index 0000000000..d9d4c73349 --- /dev/null +++ b/tests/update-stop-classification.test.ts @@ -0,0 +1,253 @@ +import { describe, expect, test } from "bun:test"; +import { spawn } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { STOP_HISTORY_INCOMPLETE_EXIT_CODE } from "../src/update/stop-contract.mjs"; +import { probeProxyLiveness } from "../src/update/proxy-liveness-probe.mjs"; +import { decidePostStopUpdate } from "../src/update/stop-decision.mjs"; + +const repoRoot = join(import.meta.dir, ".."); +const read = (rel: string): string => readFileSync(join(repoRoot, rel), "utf8"); + +/** + * #3008: `ocx update` aborted after a stop that had already succeeded. + * + * `handleStop` sets a failure code AFTER history restoration — that is, after the proxy + * and service are already down — so a failed Codex-history cleanup was indistinguishable + * from a proxy that refused to die. The update aborted with the service stopped, no + * listener, and the old package still installed. + * + * The distinguishing signal has to survive `spawnSync`, so it is an exit code rather than + * a type. These assertions pin the contract at both ends of that process boundary, and the + * decision table each end implements. + */ +describe("stop failure classification (#3008)", () => { + test("the history-only code is outside every code this CLI already uses", () => { + // Picking an occupied code would make a history-only stop indistinguishable from + // whatever else emits it, and `bin/ocx.mjs` mirrors the child's status faithfully + // enough to propagate the confusion. + expect(STOP_HISTORY_INCOMPLETE_EXIT_CODE).toBe(79); + // sysexits.h occupies 64-78; 128+signal starts at 129. + expect(STOP_HISTORY_INCOMPLETE_EXIT_CODE).toBeGreaterThan(78); + expect(STOP_HISTORY_INCOMPLETE_EXIT_CODE).toBeLessThan(128); + + const cliCodes = [...read("src/cli/index.ts").matchAll(/process\.exit(?:Code)?\s*(?:=|\()\s*(\d+)/g)] + .map(match => Number(match[1])); + const dispatchCodes = [...read("src/cli/dispatch.ts").matchAll(/return (\d+);/g)] + .map(match => Number(match[1])); + expect(cliCodes).not.toContain(STOP_HISTORY_INCOMPLETE_EXIT_CODE); + expect(dispatchCodes).not.toContain(STOP_HISTORY_INCOMPLETE_EXIT_CODE); + }); + + test("the shared contract is plain ESM so the Node launcher can import it", () => { + // A .ts module would be unusable from bin/ocx.mjs, and inlining the number in two + // places is how the two ends drift. + const contract = read("src/update/stop-contract.mjs"); + expect(contract).toContain("export const STOP_HISTORY_INCOMPLETE_EXIT_CODE"); + expect(read("bin/ocx.mjs")).toContain("stop-contract.mjs"); + expect(read("src/update/index.ts")).toContain("stop-contract.mjs"); + }); + + test("the liveness probe sees a surviving proxy, and fails open when nothing is there", async () => { + // Behavioural, not textual: absent PID and runtime files are weak evidence, so the + // updaters ask the endpoint. The listener runs in a SEPARATE process because the probe + // uses spawnSync - an in-process server could never answer while the parent's event + // loop is blocked, which is also why the probe speaks node:http rather than fetch. + const listener = spawn(process.execPath, ["-e", [ + "const http = require('node:http');", + "const server = http.createServer((req, res) => {", + " if (req.url !== '/healthz') { res.writeHead(404); res.end(); return; }", + " res.writeHead(200, { 'content-type': 'application/json' });", + " res.end(JSON.stringify({ service: 'opencodex', pid: process.pid, version: 'test' }));", + "});", + "server.listen(0, '127.0.0.1', () => process.stdout.write(String(server.address().port)));", + ].join("\n")], { stdio: ["ignore", "pipe", "ignore"] }); + + const port = await new Promise<number>((resolve, reject) => { + const timer = setTimeout(() => reject(new Error("listener did not report a port")), 10_000); + listener.stdout.once("data", chunk => { clearTimeout(timer); resolve(Number(String(chunk))); }); + listener.once("error", error => { clearTimeout(timer); reject(error); }); + }); + + try { + expect(probeProxyLiveness(port)).toBe("live"); + } finally { + listener.kill(); + await new Promise<void>(resolve => listener.once("exit", () => resolve())); + } + + // A refused connection is the one error that proves nothing is listening. + expect(probeProxyLiveness(port)).toBe("dead"); + // Nothing to ask is not ambiguity. + expect(probeProxyLiveness(0)).toBe("dead"); + expect(probeProxyLiveness(Number.NaN)).toBe("dead"); + }); + + test("identity decides live, and an unexpected status is unknown", async () => { + // Mirrors isOpencodexHealthz: a foreign server exposing /healthz is not our proxy, a + // pre-identity build of ours is, and any status other than 200 says the endpoint is + // answering without telling us what it is - which is not evidence of absence. + const cases: Array<[string, string, "live" | "dead" | "unknown"]> = [ + ["canonical", "{ service: 'opencodex', pid: 1 }", "live"], + ["legacy pre-identity", "{ status: 'ok', version: '2.0.0', uptime: 12 }", "live"], + ["foreign", "{ service: 'other', status: 'ok' }", "dead"], + ["foreign lookalike", "{ status: 'ok' }", "dead"], + ]; + for (const [name, body, expected] of cases) { + const listener = spawn(process.execPath, ["-e", [ + "const http = require('node:http');", + "const server = http.createServer((req, res) => {", + " res.writeHead(200, { 'content-type': 'application/json' });", + ` res.end(JSON.stringify(${body}));`, + "});", + "server.listen(0, '127.0.0.1', () => process.stdout.write(String(server.address().port)));", + ].join("\n")], { stdio: ["ignore", "pipe", "ignore"] }); + const port = await new Promise<number>((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(`${name} listener did not report a port`)), 10_000); + listener.stdout.once("data", chunk => { clearTimeout(timer); resolve(Number(String(chunk))); }); + listener.once("error", error => { clearTimeout(timer); reject(error); }); + }); + try { + expect(probeProxyLiveness(port)).toBe(expected); + } finally { + listener.kill(); + await new Promise<void>(resolve => listener.once("exit", () => resolve())); + } + } + }); + + test("a non-200 from our own endpoint is unknown, never dead", async () => { + const listener = spawn(process.execPath, ["-e", [ + "const http = require('node:http');", + "const server = http.createServer((req, res) => {", + " res.writeHead(500, { 'content-type': 'application/json' });", + " res.end(JSON.stringify({ service: 'opencodex' }));", + "});", + "server.listen(0, '127.0.0.1', () => process.stdout.write(String(server.address().port)));", + ].join("\n")], { stdio: ["ignore", "pipe", "ignore"] }); + const port = await new Promise<number>((resolve, reject) => { + const timer = setTimeout(() => reject(new Error("listener did not report a port")), 10_000); + listener.stdout.once("data", chunk => { clearTimeout(timer); resolve(Number(String(chunk))); }); + listener.once("error", error => { clearTimeout(timer); reject(error); }); + }); + try { + expect(probeProxyLiveness(port)).toBe("unknown"); + } finally { + listener.kill(); + await new Promise<void>(resolve => listener.once("exit", () => resolve())); + } + }); + + test("the shared probe normalizes wildcard and bracketed IPv6 hosts", async () => { + // Normalization lives in the probe, not at each call site: doing it per-lane fixed + // the npm launcher and left the TypeScript updater passing a bracketed literal + // straight to node:http, which answers nothing - read as "unknown", which aborts a + // healthy update and leaves the service down. That is the original failure shape. + const listener = spawn(process.execPath, ["-e", [ + "const http = require('node:http');", + "const server = http.createServer((req, res) => {", + " res.writeHead(200, { 'content-type': 'application/json' });", + " res.end(JSON.stringify({ service: 'opencodex', pid: process.pid }));", + "});", + "server.listen(0, () => process.stdout.write(String(server.address().port)));", + ].join("\n")], { stdio: ["ignore", "pipe", "ignore"] }); + const port = await new Promise<number>((resolve, reject) => { + const timer = setTimeout(() => reject(new Error("listener did not report a port")), 10_000); + listener.stdout.once("data", chunk => { clearTimeout(timer); resolve(Number(String(chunk))); }); + listener.once("error", error => { clearTimeout(timer); reject(error); }); + }); + + try { + // A wildcard bind cannot be dialled as a wildcard; it answers on loopback. + expect(probeProxyLiveness(port, "0.0.0.0")).toBe("live"); + expect(probeProxyLiveness(port, "*")).toBe("live"); + // A URL-spelled literal is unwrapped rather than handed to the socket layer. + expect(probeProxyLiveness(port, "[127.0.0.1]")).toBe("live"); + // Empty falls back to loopback rather than dialling "". + expect(probeProxyLiveness(port, "")).toBe("live"); + } finally { + listener.kill(); + await new Promise<void>(resolve => listener.once("exit", () => resolve())); + } + }); + + test("an unreachable or silent endpoint is unknown, not dead", async () => { + // Fail-open was the wrong default: a listener that accepts connections but withholds + // /healthz, or a probe that times out, is exactly the state where replacing package + // files is most dangerous. "We could not tell" is not evidence the proxy is gone. + const listener = spawn(process.execPath, ["-e", [ + "const net = require('node:net');", + // Accepts the connection and never answers, so the request times out. + "const server = net.createServer(() => {});", + "server.listen(0, '127.0.0.1', () => process.stdout.write(String(server.address().port)));", + ].join("\n")], { stdio: ["ignore", "pipe", "ignore"] }); + + const port = await new Promise<number>((resolve, reject) => { + const timer = setTimeout(() => reject(new Error("listener did not report a port")), 10_000); + listener.stdout.once("data", chunk => { clearTimeout(timer); resolve(Number(String(chunk))); }); + listener.once("error", error => { clearTimeout(timer); reject(error); }); + }); + + try { + expect(probeProxyLiveness(port, "127.0.0.1", 400)).toBe("unknown"); + } finally { + listener.kill(); + await new Promise<void>(resolve => listener.once("exit", () => resolve())); + } + }); + + test("the shared decision covers the whole post-stop matrix", () => { + // This is THE predicate both updaters call, not a copy of it: src/update/index.ts and + // bin/ocx.mjs each import decidePostStopUpdate. Testing a local reimplementation would + // stay green while either lane drifted, which is how #3008 shipped fixed on one side. + const dead = { hasRuntimeState: false, liveness: "dead" } as const; + + // Proceed: a clean stop, or the history-only code with everything else quiet. + expect(decidePostStopUpdate({ status: 0, ...dead })).toEqual({ proceed: true, reason: "ok" }); + expect(decidePostStopUpdate({ status: STOP_HISTORY_INCOMPLETE_EXIT_CODE, ...dead })) + .toEqual({ proceed: true, reason: "history-only" }); + + // Abort: a stop that did not finish. A signal kill carries no evidence that it did. + for (const status of [1, 2, 4, 64, 130, null]) { + expect(decidePostStopUpdate({ status, ...dead })) + .toEqual({ proceed: false, reason: "stop-failed" }); + } + + // Abort: records survived the stop, even on a clean exit. + expect(decidePostStopUpdate({ status: 0, hasRuntimeState: true, liveness: "dead" })) + .toEqual({ proceed: false, reason: "runtime-state" }); + + // Abort: something still answers as our proxy, or the probe could not tell. Absence of + // proof is not proof of absence when the cost is a server running mixed modules. + expect(decidePostStopUpdate({ status: 0, hasRuntimeState: false, liveness: "live" })) + .toEqual({ proceed: false, reason: "proxy-live" }); + expect(decidePostStopUpdate({ status: 0, hasRuntimeState: false, liveness: "unknown" })) + .toEqual({ proceed: false, reason: "proxy-unknown" }); + // The history-only code does not buy past a live or unclear proxy either. + expect(decidePostStopUpdate({ status: STOP_HISTORY_INCOMPLETE_EXIT_CODE, hasRuntimeState: false, liveness: "unknown" })) + .toEqual({ proceed: false, reason: "proxy-unknown" }); + }); + + test("both updater lanes call the shared decision", () => { + // The reported path is a dashboard npm update through the plain-Node launcher. Fixing + // only the Bun updater would leave that lane broken while every focused test went + // green, which is exactly how #3008 reached a release. + for (const lane of ["src/update/index.ts", "bin/ocx.mjs"]) { + const source = read(lane); + expect(source).toContain("decidePostStopUpdate({"); + // And neither lane keeps a private copy of the rule it was supposed to delegate. + expect(source).not.toMatch(/status !== 0 && !historyOnlyStop/); + } + }); + + test("handleStop emits the code only for a history-only failure and still returns", () => { + const cli = read("src/cli/index.ts"); + // Ordinary failure wins: it is the stronger signal. + expect(cli).toMatch(/if \(stopFailed\) process\.exitCode = 1;\s*\n\s*else if \(historyOnlyFailure\) process\.exitCode = STOP_HISTORY_INCOMPLETE_EXIT_CODE;/); + // The code is set rather than exited inline so the dispatcher still receives the + // return value and decides what happens next. + expect(cli).toMatch(/process\.exitCode = STOP_HISTORY_INCOMPLETE_EXIT_CODE;\s*\n\s*return !stopFailed;/); + // Config and catalog failures are real teardown failures: a client reads those. + expect(cli).toMatch(/artifacts\.config\.state === "failed" \|\| artifacts\.catalog\.state === "failed"/); + }); +}); diff --git a/tests/update-stop-first.test.ts b/tests/update-stop-first.test.ts index 0f7fd7ff55..d20eafb5c7 100644 --- a/tests/update-stop-first.test.ts +++ b/tests/update-stop-first.test.ts @@ -94,7 +94,7 @@ describe("update stops the running proxy before replacing files", () => { expect(stopAt).toBeGreaterThan(-1); expect(updateAt).toBeGreaterThan(-1); expect(stopAt).toBeLessThan(updateAt); - expect(updateSource).toContain("if (serviceWasInstalled || readPid() || readRuntimePort())"); + expect(updateSource).toContain("if (serviceWasInstalled || readPid() || readRuntimePort() || pendingTeardownOutstanding())"); }); test("integrity pre-flight runs BEFORE the stop so anomalous metadata never unloads the proxy", () => { @@ -306,10 +306,12 @@ esac // may claim a DB lock or that every routed thread is hidden. expect(updateSource).toContain("export function historyRestoreIncomplete("); expect(updateSource).toContain('name.startsWith("codex-history-backup-") && name.endsWith(".json")'); - expect(updateSource).toContain("if (historyRestoreIncomplete())"); + // The warning now also fires on the dedicated stop code, so the manifest check is one + // of two triggers rather than the whole condition (#3008). + expect(updateSource).toContain("if (historyOnlyStop || historyRestoreIncomplete())"); expect(launcherSource).toContain("function historyRestoreIncomplete()"); expect(launcherSource).toContain('name.startsWith("codex-history-backup-") && name.endsWith(".json")'); - expect(launcherSource).toContain("if (historyRestoreIncomplete())"); + expect(launcherSource).toContain("if (historyOnlyStop || historyRestoreIncomplete())"); const warnAt = launcherSource.indexOf("Codex resume-history metadata restore is incomplete"); const installAt = launcherSource.indexOf("transactionalNpmUpdate({"); expect(warnAt).toBeGreaterThan(-1); @@ -320,9 +322,16 @@ esac }); test("the stop gate covers service-managed and orphaned proxies whose pid file is stale/missing", () => { - expect(updateSource).toContain("if (serviceWasInstalled || readPid() || readRuntimePort())"); - expect(launcherSource).toContain("if (serviceWasInstalled || hasRuntimeState)"); - expect(launcherSource).toContain("stopRes.status !== 0 || stillHasRuntimeState"); + // A pending-teardown receipt is a fourth reason to stop: after a parent crashed + // mid-deferral the service, pid and runtime records can all be absent while shared + // client config still points at a proxy that is gone (#3008). + expect(updateSource).toContain("if (serviceWasInstalled || readPid() || readRuntimePort() || pendingTeardownOutstanding())"); + expect(launcherSource).toContain("if (serviceWasInstalled || hasRuntimeState || hasPendingTeardown)"); + // The rule now lives in the shared post-stop decision both lanes import (#3008): a + // history-only stop proceeds, every other nonzero status and any surviving runtime + // state aborts. Pinned by tests/update-stop-classification.test.ts. + expect(launcherSource).toContain("decidePostStopUpdate({"); + expect(launcherSource).toContain("hasRuntimeState: stillHasRuntimeState"); }); test("GUI worker update children use pipe stdio so background updates do not open consoles", () => { From b46164e7858e35c50f8c1a0605d4d333fd434862 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 09:07:33 +0900 Subject: [PATCH 33/42] fix(catalog): widen the dated-variant fold and guard its direction at the merge loop (#3100) * fix(catalog): recognize YYMMDD/MMDD/YYMM dated model id suffixes (#3024) `isDatedVariantId` only accepted an 8-digit `YYYYMMDD` suffix, so the dated-alias fold in `mergeConfiguredModelsIntoLiveCatalog` never fired for providers that publish shorter release dates. On a real multi-provider install the `\d{8}` rule matched none of the 26 numeric-suffixed ids present, dropping DeepSeek, Kimi, Mistral, Qwen and Solar aliases into `droppedConfiguredIds` even though a live row for the same model existed. Widen the suffix to the formats upstreams actually publish -- `YYYYMMDD`, `YYMMDD`, `MMDD` and `YYMM` -- behind a calendar guard so ordinary numeric suffixes are not read as dates. `-2048`, `-4096` and `-8192` are rejected; `YY` is `2\d` rather than `\d\d` so `1301` is rejected too. `-1024` is a valid `MMDD` and is therefore accepted -- an irreducible collision, pinned by a test so it stays a known cost. The fold stays one-directional (`configured=base` -> `live=dated`). A configured id the provider no longer lists must not be retained on the strength of a format match alone; #1690 is the explicit opt-in for that. Hyphenated ISO suffixes (`-2024-08-06`, `-05-06`) remain out of scope. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 0a6393d209c8dc9200cd9570dbb32996f2ea3bcc) (cherry picked from commit 6dd0850da94ebe9b13d87dbab222425b61ff8e8e) * test(catalog): guard the dated fold's direction at the merge loop, not the predicate #3034 widens the dated-suffix matcher and pins it with a predicate test that the fold stays one-way: isDatedVariantId("deepseek-v4-pro", "deepseek-v4-pro-0813") is false. That assertion is true of every implementation, including one whose merge loop calls the predicate a second time with the arguments swapped -- which is exactly what #3041 originally proposed and then withdrew. So the guard is moved to where the resurrection would actually happen. These three drive mergeConfiguredModelsIntoLiveCatalog itself, carried from #3041: - a live base row must not resurrect a configured dated id - a live MMDD dated row still folds onto its configured base - a dated id named in retainConfiguredModelIds survives Both directions were mutation-checked. Adding || isDatedVariantId(candidate.id, live.id) to the merge loop fails only the first test (253 pass / 1 fail); narrowing the suffix back to /^\d{8}$/ fails 13, including the MMDD and YYMM folds. Neither mutation is caught by the predicate test alone. The retention test is labelled for what it actually covers: production fills retainConfiguredModelIds from combo targets, not from providers.*.models, so it pins the OCX-111 path. The operator-facing opt-in is #1690's retainModels, which does not exist yet -- and until it does, the dated id #3024 reports is still dropped. This lands the safe half of #3024 and says so. (cherry picked from commit a909682da3f7431967b3745d28f2b583694e420d) --------- Co-authored-by: kaicot <275240300+kaicot@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> --- src/codex/catalog/provider-fetch.ts | 63 ++++++++++++++++- tests/codex-catalog.test.ts | 101 +++++++++++++++++++++++++++- 2 files changed, 161 insertions(+), 3 deletions(-) diff --git a/src/codex/catalog/provider-fetch.ts b/src/codex/catalog/provider-fetch.ts index 87afd0bb12..404fe2601d 100644 --- a/src/codex/catalog/provider-fetch.ts +++ b/src/codex/catalog/provider-fetch.ts @@ -936,9 +936,70 @@ export function resolveComboCatalogMember( }; } +const DATED_VARIANT_YYYYMMDD = /^(\d{4})(\d{2})(\d{2})$/; +const DATED_VARIANT_YYMMDD = /^(2\d)(\d{2})(\d{2})$/; +const DATED_VARIANT_MMDD_OR_YYMM = /^(\d{2})(\d{2})$/; + +/** Whether a Gregorian year contains February 29th. */ +function isLeapYear(year: number): boolean { + return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); +} + +/** + * Whether a month/day pair exists in the given year. Without a year, February 29th is + * accepted because it occurs in at least one calendar year. + */ +function isValidCalendarDate(year: number | undefined, month: number, day: number): boolean { + if (year !== undefined && (year < 1 || year > 9999)) return false; + if (month < 1 || month > 12 || day < 1) return false; + const daysInMonth = [ + 31, year === undefined || isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31, + ]; + return day <= daysInMonth[month - 1]!; +} + +/** + * Release-date suffixes providers actually publish: `YYYYMMDD` (`-20251001`), `YYMMDD` + * (`-260806`), `MMDD` (`-0813`) and `YYMM` (`-2512`). A `\d{8}`-only rule matched none of + * the dated ids on a real multi-provider install, so DeepSeek, Kimi, Mistral, Qwen and + * Solar aliases all fell through to `droppedConfiguredIds` (#3024). + * + * Calendar validation rejects impossible month-end and leap-day values as well as ordinary + * numeric suffixes such as `-2048`, `-4096` and `-8192`. `-1024` is the one irreducible + * collision — it is a valid `MMDD` (October 24th) — so it reads as dated. That is a known, + * accepted cost; the test table pins it so it cannot become a surprise later. + * + * Hyphenated ISO suffixes (`-2024-08-06`, `-05-06`) are deliberately out of scope: a + * hyphenated suffix is ambiguous against ordinary name segments and needs its own call. + */ +function isDatedVariantSuffix(suffix: string): boolean { + const yyyyMmDd = DATED_VARIANT_YYYYMMDD.exec(suffix); + if (yyyyMmDd) { + return isValidCalendarDate( + Number(yyyyMmDd[1]), Number(yyyyMmDd[2]), Number(yyyyMmDd[3]), + ); + } + + const yyMmDd = DATED_VARIANT_YYMMDD.exec(suffix); + if (yyMmDd) { + return isValidCalendarDate( + 2000 + Number(yyMmDd[1]), Number(yyMmDd[2]), Number(yyMmDd[3]), + ); + } + + const mmDdOrYyMm = DATED_VARIANT_MMDD_OR_YYMM.exec(suffix); + if (!mmDdOrYyMm) return false; + const first = Number(mmDdOrYyMm[1]); + const second = Number(mmDdOrYyMm[2]); + return isValidCalendarDate(undefined, first, second) + || (first >= 20 && first <= 29 && second >= 1 && second <= 12); +} + +/** Whether `liveId` is a supported dated release of the configured base id. */ export function isDatedVariantId(liveId: string, configuredId: string): boolean { if (!liveId.startsWith(`${configuredId}-`)) return false; - return /^\d{8}$/.test(liveId.slice(configuredId.length + 1)); + return isDatedVariantSuffix(liveId.slice(configuredId.length + 1)); } export const lastDropWarnSignature = new Map<string, string>(); diff --git a/tests/codex-catalog.test.ts b/tests/codex-catalog.test.ts index f174acbcde..09fcef9d77 100644 --- a/tests/codex-catalog.test.ts +++ b/tests/codex-catalog.test.ts @@ -3,7 +3,7 @@ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { applyNativeVisibility, augmentRoutedModelsWithMetadata, augmentRoutedModelsWithRegistryOpenAiApiRows, buildCatalogEntries, buildComboCatalogOmission, catalogModelSlug, clampCatalogModelsToCodexSupport, clampEntryToCodexSupportedEfforts, clampedDefaultEffort, CODEX_ACCOUNT_BOUND_CATALOG_KIND, CODEX_NATIVE_ALIAS_CATALOG_KIND, comboCatalogOmissionReason, deriveComboCatalogModel, exactComboCatalogSlugs, filterCatalogVisibleModels, filterSupportedNativeSlugs, gatherRoutedModels as gatherRoutedModelsDirect, isDatedVariantId, isMediaGenerationModelId, loadBundledCodexCatalog, materializeBundledCodexCatalog, mergeCatalogEntriesForSync, NATIVE_DAYBREAK_BLUE_MODEL, NATIVE_OPENAI_MODELS, nativeDefaultReasoningEffort, nativeInputModalities, nativeOpenAiCapabilitySourceSlug, nativeOpenAiContextWindow, nativeReasoningEfforts, normalizeRoutedCatalogEntry, resetCatalogRuntimeStateForTests, resetOpenAiApiCatalogWarningStateForTests, resolveComboCatalogMember, shouldExposeRoutedModel, upstreamNativeEntry } from "../src/codex/catalog"; -import { applyProviderConfigHints } from "../src/codex/catalog/provider-fetch"; +import { applyProviderConfigHints, mergeConfiguredModelsIntoLiveCatalog } from "../src/codex/catalog/provider-fetch"; import { CODEX_CUSTOM_MODEL_CATALOG_KIND, CODEX_PROVIDER_MODEL_CATALOG_KIND, @@ -3651,7 +3651,7 @@ describe("Codex catalog routed normalization", () => { } }); - test("isDatedVariantId matches only <alias>-YYYYMMDD", () => { + test("isDatedVariantId matches only <alias>-<date>", () => { expect(isDatedVariantId("claude-haiku-4-5-20251001", "claude-haiku-4-5")).toBe(true); expect(isDatedVariantId("claude-haiku-4-5-2025", "claude-haiku-4-5")).toBe(false); expect(isDatedVariantId("claude-haiku-4-5-latest", "claude-haiku-4-5")).toBe(false); @@ -3659,6 +3659,103 @@ describe("Codex catalog routed normalization", () => { expect(isDatedVariantId("claude-haiku-4-5-20251001", "claude-haiku-4")).toBe(false); }); + // Real ids from a multi-provider install. A `\d{8}`-only rule matched none of them, so + // every one of these aliases dropped out of the authoritative live catalog (#3024). + test.each([ + ["YYYYMMDD", "claude-haiku-4-5-20251001", "claude-haiku-4-5"], + ["YYYYMMDD leap day", "acme-model-20240229", "acme-model"], + ["YYMMDD", "solar-pro4-260806", "solar-pro4"], + ["YYMMDD", "syn-pro-251021", "syn-pro"], + ["YYMMDD leap day", "acme-model-240229", "acme-model"], + ["MMDD", "deepseek/deepseek-v4-pro-0813", "deepseek/deepseek-v4-pro"], + ["MMDD", "moonshotai/kimi-k2-0905", "moonshotai/kimi-k2"], + ["MMDD", "openai/gpt-3.5-turbo-0613", "openai/gpt-3.5-turbo"], + ["MMDD leap day", "acme-model-0229", "acme-model"], + ["YYMM", "mistralai/mistral-large-2407", "mistralai/mistral-large"], + ["YYMM", "qwen/qwen3-235b-a22b-2507", "qwen/qwen3-235b-a22b"], + ])("folds a %s dated variant: %s", (_format, liveId, configuredId) => { + expect(isDatedVariantId(liveId, configuredId)).toBe(true); + }); + + test.each([ + ["a version number", "mistralai/mistral-medium-3-5", "mistralai/mistral-medium-3"], + ["a context size", "openai/gpt-3.5-turbo-16k", "openai/gpt-3.5-turbo"], + ["a variant name", "qwen/qwen3-coder-30b-a3b-instruct", "qwen/qwen3-coder"], + ["a batch lane of a dated id", "deepseek/deepseek-v4-pro-0813:batch", "deepseek/deepseek-v4-pro"], + ["a bare year", "acme-model-2025", "acme-model"], + ["an impossible YYMM", "acme-model-1301", "acme-model"], + ["a non-leap YYYYMMDD", "acme-model-20250229", "acme-model"], + ["a non-leap YYMMDD", "acme-model-250229", "acme-model"], + ["an impossible YYYYMMDD month-end", "acme-model-20240431", "acme-model"], + ["an impossible YYMMDD month-end", "acme-model-240431", "acme-model"], + ["an impossible MMDD month-end", "acme-model-0431", "acme-model"], + // Hyphenated ISO is ambiguous against ordinary name segments; out of scope for now. + ["a hyphenated ISO date", "openai/gpt-4o-2024-08-06", "openai/gpt-4o"], + ["a hyphenated MM-DD", "google/gemini-2.5-pro-preview-05-06", "google/gemini-2.5-pro-preview"], + ])("does not fold %s: %s", (_label, liveId, configuredId) => { + expect(isDatedVariantId(liveId, configuredId)).toBe(false); + }); + + test.each(["2048", "4096", "8192"])("a %s context suffix is not a date", suffix => { + expect(isDatedVariantId(`acme-model-${suffix}`, "acme-model")).toBe(false); + }); + + // Known, accepted cost of allowing MMDD: October 24th is a real date, so a `-1024` + // context suffix is indistinguishable from one. No month/day tightening can exclude it. + test("a -1024 suffix reads as MMDD and is accepted", () => { + expect(isDatedVariantId("acme-model-1024", "acme-model")).toBe(true); + }); + + // The fold stays one-directional: a configured id the provider no longer lists must not + // be retained on the strength of a format match alone (#1690 is the explicit opt-in for + // that). `deepseek-v4-pro-0813` configured against a live `deepseek-v4-pro` stays dropped. + test("does not fold configured=dated against live=base", () => { + expect(isDatedVariantId("deepseek-v4-pro", "deepseek-v4-pro-0813")).toBe(false); + }); + + // The predicate test above is necessary and not sufficient: it passes on any + // implementation, including one whose MERGE LOOP calls the predicate a second time with + // the arguments swapped. These three drive `mergeConfiguredModelsIntoLiveCatalog` itself, + // so they fail if the loop ever becomes bidirectional. Carried from #3041, where the + // reverse fold was proposed and then withdrawn — the guard outlives the proposal. + test("the merge loop does not infer a configured dated id from a live base id", () => { + const { models, droppedConfiguredIds } = mergeConfiguredModelsIntoLiveCatalog({ + name: "deepseek", + provider: {}, + models: [{ id: "deepseek-v4-pro" } as never], + configured: [{ id: "deepseek-v4-pro-0813" } as never], + }); + expect(droppedConfiguredIds).toEqual(["deepseek-v4-pro-0813"]); + expect(models.map(m => m.id)).not.toContain("deepseek-v4-pro-0813"); + }); + + test("the merge loop folds a live MMDD dated row onto its configured base", () => { + const { models, droppedConfiguredIds } = mergeConfiguredModelsIntoLiveCatalog({ + name: "deepseek", + provider: {}, + models: [{ id: "deepseek-v4-pro-0813" } as never], + configured: [{ id: "deepseek-v4-pro" } as never], + }); + expect(droppedConfiguredIds).toEqual([]); + expect(models.map(m => m.id)).toContain("deepseek-v4-pro"); + }); + + // Retention of a dated id is a decision someone made, not an inference from a name. + // Note what this set actually is: production fills `retainConfiguredModelIds` from combo + // targets, not from `providers.*.models`, so this pins the combo-target path (OCX-111). + // The operator-facing opt-in is #1690's `retainModels`, which does not exist yet. + test("a dated id named in retainConfiguredModelIds survives the drop", () => { + const { models, droppedConfiguredIds } = mergeConfiguredModelsIntoLiveCatalog({ + name: "deepseek", + provider: {}, + models: [{ id: "deepseek-v4-pro" } as never], + configured: [{ id: "deepseek-v4-pro-0813" } as never], + retainConfiguredModelIds: new Set(["deepseek-v4-pro-0813"]), + }); + expect(droppedConfiguredIds).toEqual([]); + expect(models.map(m => m.id)).toContain("deepseek-v4-pro-0813"); + }); + test("disabled providers are excluded from routed model gathering", async () => { const models = await gatherRoutedModels({ port: 10100, From a3656a92cd80597ebdef87a6bf9f64d484260cbc Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 09:08:43 +0900 Subject: [PATCH 34/42] fix(cursor): retry pre-header discovery eof (#3102) (cherry picked from commit 46125ea83c4f122614931974fb24ddd4a8cd41a0) Co-authored-by: Terry Tan <tmy1995hflc@gmail.com> --- src/adapters/cursor/live-models.ts | 1 + tests/cursor-hardening.test.ts | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/adapters/cursor/live-models.ts b/src/adapters/cursor/live-models.ts index 4e4c07a236..df5fbf14b6 100644 --- a/src/adapters/cursor/live-models.ts +++ b/src/adapters/cursor/live-models.ts @@ -262,6 +262,7 @@ async function fetchCursorUsableModelsHttp2Once(opts: CursorUsableModelsOptions) req.on("error", () => close({ ok: false, error: "transport", detail: "HTTP/2 request failed" })); req.on("end", () => { if (bodyRejected) return; + if (status === 0) return close({ ok: false, error: "transport", detail: "HTTP/2 response ended before headers" }); if (status === 401 || status === 403) { return close({ ok: false, error: "auth", detail: `HTTP ${status}` }); } diff --git a/tests/cursor-hardening.test.ts b/tests/cursor-hardening.test.ts index b740fb8617..519f3ea7c4 100644 --- a/tests/cursor-hardening.test.ts +++ b/tests/cursor-hardening.test.ts @@ -424,6 +424,25 @@ describe("Cursor discovery bounded retry", () => { expect(result).toEqual({ ok: true, models: ["gpt-5.5-high"] }); }); + test("retries an HTTP/2 stream that ends before response headers", async () => { + const body = toBinary(GetUsableModelsResponseSchema, create(GetUsableModelsResponseSchema, { + models: [create(ModelDetailsSchema, { modelId: "gpt-5.5-high" })], + })); + let requests = 0; + const result = await withDiscoveryServer(stream => { + requests += 1; + if (requests === 1) { + stream.close(http2.constants.NGHTTP2_NO_ERROR); + return; + } + stream.respond({ ":status": 200, "content-type": "application/proto" }); + stream.end(body); + }, baseUrl => fetchCursorUsableModels({ apiKey: "test-token", baseUrl })); + + expect(requests).toBe(2); + expect(result).toEqual({ ok: true, models: ["gpt-5.5-high"] }); + }); + test("does not retry deterministic auth failures", async () => { let requests = 0; const result = await withDiscoveryServer(stream => { From 0ef04e6403808708b26653046290264ed0b6290a Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 09:09:53 +0900 Subject: [PATCH 35/42] fix(cli): stop start shadowing a live configured-port proxy, and retry health (#3106) Reimplements #3078 (author @Veritas-7) on dev. Both production hunks are right; the PR targets main and its test file does not typecheck (tests/cli-health-retry.test.ts declares const servers: Server[] while importing only IncomingMessage and ServerResponse). handleStart skipped the configured-port probe whenever the pid file and the runtime-port record were both absent. That absence proves nothing: a fallback-port sibling overwrites both records when it starts and removes them when it stops. So start shadowed a healthy configured-port proxy with an ephemeral-port copy, re-pointed client config at the copy, and the next sibling shutdown left no runtime record for discovery at all. handleEnsure already passed probeConfiguredPort: true; handleStart is the path that did not. ocx health probed once. A proxy that has only just bound can miss a single probe while its event loop is still settling startup work, so health run seconds after a service restart reported "Proxy not healthy" and exited 1 for a proxy that was serving. The stop paths already retry this exact race under SERVICE_STOP_LIVENESS (#764). Tests are dependency-injected and source-level rather than #3078's port-binding integration fixture: the retry budget is asserted through dispatchCommand's injected findLiveProxy, and the start guard through a source oracle next to the handleEnsure call site that was already correct. Mutation-checked, both restored: drop probeConfiguredPort from handleStart -> 28 pass / 1 fail, the start guard drop the health retry budget -> 28 pass / 1 fail, the health test --- src/cli/dispatch.ts | 11 ++++-- src/cli/index.ts | 9 ++++- tests/cli-dispatch.test.ts | 69 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/cli/dispatch.ts b/src/cli/dispatch.ts index e21536ce89..3edd53ffb0 100644 --- a/src/cli/dispatch.ts +++ b/src/cli/dispatch.ts @@ -12,7 +12,7 @@ import { CLI_COMMANDS } from "./registry"; import { isValidProviderName } from "../config/provider-name"; import type { CliHead } from "./root"; import type { ReadyArgs } from "./ready"; -import type { LiveProxy } from "../server/proxy-liveness"; +import type { LivenessIo, LiveProxy } from "../server/proxy-liveness"; import type { OcxConfig } from "../types"; import { hasHelpFlag, printSubcommandUsage, printUsage } from "./help"; import { setIntegrationEnabled, shouldSyncCodexOnStart } from "../codex/desired-state"; @@ -29,7 +29,7 @@ export interface CliDispatchDeps { command: string | undefined; head: CliHead; loadConfig: () => OcxConfig; - findLiveProxy: () => Promise<LiveProxy | null>; + findLiveProxy: (io?: LivenessIo) => Promise<LiveProxy | null>; probeHostname: (hostname: string | undefined) => string; waitForProxy: (timeoutMs?: number) => Promise<LiveProxy | null>; startArgv: (port?: number) => string[]; @@ -573,7 +573,12 @@ const commandRunners: Record<string, CommandRunner> = { health: async deps => { const healthArgs = deps.args.slice(1); const wantsHealthJson = healthArgs.includes("--json"); - const live = await deps.findLiveProxy(); + // A proxy that has only just bound can miss a single probe while its event loop + // is still settling startup work — the same just-started race the stop paths + // already retry for (#764, SERVICE_STOP_LIVENESS). Without this, `ocx health` + // run seconds after a service restart reports a false negative on a proxy that + // is in fact serving. + const live = await deps.findLiveProxy({ attempts: 3 }); if (wantsHealthJson) { console.log(JSON.stringify({ ok: !!live, pid: live?.pid ?? null, port: live?.port ?? null })); } else { diff --git a/src/cli/index.ts b/src/cli/index.ts index 98e041a188..ca1a3a0fa9 100755 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -240,7 +240,14 @@ async function handleStart(options: { block?: boolean } = {}) { const present = process.env.OPENCODEX_API_AUTH_TOKEN?.trim(); if (present) assertNotAdminToken(present); const requestedPort = parsePortOption(); - const owner = await findProxyOwnerBeforeJournalRecovery(); + // Always probe the configured port, even when both state files are absent. A + // fallback-port sibling overwrites the pid/runtime records when it starts and + // removes them on its own shutdown, so their absence proves nothing about the + // configured port. Without the probe, `start` shadowed a healthy proxy with an + // ephemeral-port copy and re-pointed client config at the copy; the next sibling + // shutdown then left no runtime record for discovery at all. `handleEnsure` + // already passes this; `handleStart` is the path that did not. + const owner = await findProxyOwnerBeforeJournalRecovery({ probeConfiguredPort: true }); if (owner.live) { // Service-wrapper context (opencodex-service.cmd `:loop`): a healthy proxy from // ANY source means the requested port is already served. Exit 0 so the wrapper's diff --git a/tests/cli-dispatch.test.ts b/tests/cli-dispatch.test.ts index 012bbe7784..a4ac1084ed 100644 --- a/tests/cli-dispatch.test.ts +++ b/tests/cli-dispatch.test.ts @@ -123,6 +123,75 @@ describe("dispatchCommand exit codes", () => { }); }); +/** + * `ocx health` probed once. A proxy that has only just bound can miss a single + * probe while its event loop is still settling startup work, so health run + * seconds after a service restart reported "Proxy not healthy" and exited 1 for + * a proxy that was in fact serving. The stop paths already retry this exact race + * under SERVICE_STOP_LIVENESS (#764); health did not. + */ +describe("health retries a just-started proxy", () => { + test("passes a retry budget to findLiveProxy", async () => { + const seen: (number | undefined)[] = []; + const deps = { + ...fakeDeps, + args: ["health"], + findLiveProxy: async (io?: { attempts?: number }) => { + seen.push(io?.attempts); + return { pid: 4242, port: 10100 } as never; + }, + } as unknown as CliDispatchDeps; + + expect(await dispatchCommand({ kind: "command", command: "health", args: deps.args }, deps)).toBe(0); + // More than one: a single attempt is the defect. The exact number is the + // stop path's, and is asserted rather than inferred so a silent drop back to + // one probe fails here. + expect(seen).toEqual([3]); + }); + + test("still reports an absent proxy as unhealthy", async () => { + const deps = { + ...fakeDeps, + args: ["health"], + findLiveProxy: async () => null, + } as unknown as CliDispatchDeps; + + expect(await dispatchCommand({ kind: "command", command: "health", args: deps.args }, deps)).toBe(1); + }); +}); + +/** + * `handleStart` skipped the configured-port probe whenever the pid file and the + * runtime-port record were both absent. That absence proves nothing: a + * fallback-port sibling overwrites both records when it starts and removes them + * when it stops, so `start` shadowed a healthy configured-port proxy with an + * ephemeral-port copy and re-pointed client config at the copy. + * + * Source-level because the executable path binds real ports and spawns a real + * child; this pins the one option that decides the behavior, next to the + * `handleEnsure` call site that already had it right. + */ +describe("start probes the configured port before shadowing it (source-level)", () => { + const cliSource = readFileSync(join(import.meta.dir, "../src/cli/index.ts"), "utf8"); + + test("every findProxyOwnerBeforeJournalRecovery call site asks for the probe", () => { + const calls = cliSource.match(/findProxyOwnerBeforeJournalRecovery\s*\(([^)]*)\)/g) ?? []; + // The declaration plus both call sites (handleStart, handleEnsure). + expect(calls.length).toBeGreaterThanOrEqual(2); + const invocations = calls.filter(call => !call.includes("options:")); + expect(invocations.length).toBe(2); + for (const call of invocations) { + expect(call).toContain("probeConfiguredPort: true"); + } + }); + + test("the probe option still gates on an explicit true", () => { + // A truthy-but-not-true default would silently probe for callers that pass + // nothing, which is a different behavior than the one asserted above. + expect(cliSource).toContain("options.probeConfiguredPort === true"); + }); +}); + describe("logout parses argv before touching the credential store", () => { /** * `ocx logout --json` used to lowercase `--json`, pass it to removeCredential as a provider From e9d198a3ca506289f140221ff2e0ef48ead6a760 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 09:10:01 +0900 Subject: [PATCH 36/42] fix(responses): strip Codex-private item metadata at the noncanonical boundary (#3107) * fix(responses): strip Codex-private item metadata Remove internal ChatGPT item metadata before forwarding Responses requests to noncanonical destinations. (cherry picked from commit a3f61f8c25d91dafbee3b69d5fcc77443a95eab9) * test(responses): cover Codex-private item metadata Exercise key and forwarded noncanonical routes while preserving canonical ChatGPT passthrough behavior. (cherry picked from commit 2e15e4b2f7e60ecf38a9ab4dcc09d718cf71281d) --------- Co-authored-by: yanzhibo-bytedance <yanzhibo.7554@bytedance.com> --- src/adapters/openai-responses.ts | 25 ++++++++ tests/openai-responses-passthrough.test.ts | 74 ++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/src/adapters/openai-responses.ts b/src/adapters/openai-responses.ts index 97044bc804..0d91807617 100644 --- a/src/adapters/openai-responses.ts +++ b/src/adapters/openai-responses.ts @@ -236,6 +236,30 @@ function stripCanonicalOnlyToolFields(body: unknown, includeCapabilityGated: boo return input ? { ...rewrittenBody, input } : rewrittenBody; } +/** + * Codex keeps this ChatGPT-internal item metadata when its configured provider name is `openai`. + * Loopback OpenCodex injection intentionally retains that provider identity for history continuity, + * even when the proxy ultimately routes the request to a public Responses destination. Those + * destinations reject the private field as an unknown `input[*]` parameter, so remove it at the + * noncanonical boundary without mutating the caller-owned raw body. + */ +function stripInternalChatMessageMetadataPassthrough(body: unknown): unknown { + if (!isPlainObject(body) || !Array.isArray(body.input)) return body; + + let changed = false; + const input = body.input.map(item => { + if (!isPlainObject(item) || !Object.hasOwn(item, "internal_chat_message_metadata_passthrough")) { + return item; + } + changed = true; + const next = { ...item }; + delete next.internal_chat_message_metadata_passthrough; + return next; + }); + + return changed ? { ...body, input } : body; +} + /** * When `store` is false, the upstream API does not persist response items. Any item ID * forwarded in `input` is then interpreted as a reference to a stored item that does not @@ -2139,6 +2163,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig): // `queries` for DeepSeek (#930), `query` for Console Go (#3071). outBody = backfillWebSearchQueries(outBody); if (!isCanonicalOpenAiForwardProvider(provider)) { + outBody = stripInternalChatMessageMetadataPassthrough(outBody); outBody = promoteClientLoadedTools(outBody); } if (!isCanonicalOpenAiForwardProvider(provider)) { diff --git a/tests/openai-responses-passthrough.test.ts b/tests/openai-responses-passthrough.test.ts index edba6c393e..a1972dc1fb 100644 --- a/tests/openai-responses-passthrough.test.ts +++ b/tests/openai-responses-passthrough.test.ts @@ -122,6 +122,80 @@ test("noncanonical pool-required providers use only their configured static cred expect(request.headers.session_id).toBeUndefined(); }); +test("noncanonical Responses destinations strip Codex-private item metadata", () => { + const rawBody = { + model: "openai/gpt-5.6-sol", + input: [ + { + type: "message", + role: "user", + content: [{ type: "input_text", text: "ping" }], + internal_chat_message_metadata_passthrough: { turn_id: "turn-1" }, + }, + { + type: "function_call", + name: "lookup", + arguments: "{}", + call_id: "call-1", + internal_chat_message_metadata_passthrough: { turn_id: "turn-1" }, + }, + ], + }; + + for (const configuredProvider of [ + { + adapter: "openai-responses", + baseUrl: "https://gateway.example/v1", + authMode: "key" as const, + apiKey: "test-key", + }, + { + adapter: "openai-responses", + baseUrl: "https://gateway.example/v1", + authMode: "forward" as const, + }, + ]) { + const request = createResponsesPassthroughAdapter(configuredProvider).buildRequest({ + modelId: rawBody.model, + context: { messages: [] }, + stream: true, + options: {}, + _rawBody: rawBody, + }, { headers: new Headers() }); + const body = JSON.parse(request.body) as { input: Record<string, unknown>[] }; + + expect(body.input.every(item => !("internal_chat_message_metadata_passthrough" in item))) + .toBe(true); + } + + expect(rawBody.input.every(item => "internal_chat_message_metadata_passthrough" in item)) + .toBe(true); +}); + +test("canonical ChatGPT forward preserves Codex-private item metadata", () => { + const request = createResponsesPassthroughAdapter(provider).buildRequest({ + modelId: "gpt-5.6-sol", + context: { messages: [] }, + stream: true, + options: {}, + _rawBody: { + model: "gpt-5.6-sol", + input: [{ + type: "message", + role: "user", + content: [{ type: "input_text", text: "ping" }], + internal_chat_message_metadata_passthrough: { turn_id: "turn-1" }, + }], + }, + }, { headers: new Headers({ authorization: "Bearer token" }) }); + const body = JSON.parse(request.body) as { + input: { internal_chat_message_metadata_passthrough?: unknown }[]; + }; + + expect(body.input[0].internal_chat_message_metadata_passthrough) + .toEqual({ turn_id: "turn-1" }); +}); + test("passthrough serialized-body observation releases after the request settles", () => { const budget = createTranslatorBudget(); const request = createResponsesPassthroughAdapter(provider).buildRequest({ From 5c0c13194292f4819183133737615e535464dfc2 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 09:10:48 +0900 Subject: [PATCH 37/42] fix(responses): notice an unreadable MESSAGE reply, not just NEW_TASK (#3116) #3021: after an adapter_eof, a delegated subagent's MESSAGE reply reached the parent conversation as a raw gAAAA... payload instead of plaintext or a structured error. hasUnreadableEncryptedAgentTask decides "unreadable" by stripping the routing envelope and asking whether any plaintext survives. AGENT_MESSAGE_ROUTING_ENVELOPE matched only NEW_TASK, so a MESSAGE header was never stripped and counted as surviving text -- a reply whose entire body was one Fernet token measured as READABLE and was forwarded verbatim. Measured on dev before the change, with a structurally valid Fernet token: NEW_TASK -> true (detected) MESSAGE -> false (#3021's case, forwarded) and after: NEW_TASK -> true MESSAGE -> true This is the DETECTION half only. Recovery stays NEW_TASK-only, deliberately: recoverEncryptedAgentTask decrypts, and decrypting a MESSAGE on the parent's behalf would build a plaintext oracle out of a payload the parent's session may have no entitlement to read. Widening the strip only lets the proxy notice that what it is about to forward is unreadable ciphertext, which is what the report asks for -- fail closed rather than paste the token. Mutation-checked: reverting the pattern to NEW_TASK-only fails exactly the two new MESSAGE tests (21 pass / 2 fail). The control test -- a MESSAGE reply that carries real text alongside a token -- stays readable in both directions, so this does not turn every agent reply into a blocked one. Closes #3021. --- src/server/responses/encrypted-payload.ts | 19 +++++++++- tests/v2-agent-message-failfast.test.ts | 44 +++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/server/responses/encrypted-payload.ts b/src/server/responses/encrypted-payload.ts index 9de44cd2a2..3df72f769a 100644 --- a/src/server/responses/encrypted-payload.ts +++ b/src/server/responses/encrypted-payload.ts @@ -175,7 +175,24 @@ function textWithoutFernetRuns(payload: string, runs: readonly FernetTokenRun[]) return `${text}${payload.slice(last)}`; } -export const AGENT_MESSAGE_ROUTING_ENVELOPE = /(?:^|\n)Message Type\s*:\s*NEW_TASK[^\n]*\nTask name\s*:[^\n]*\nSender\s*:[^\n]*\nPayload\s*:\s*(?:\n|$)/gi; +/** + * The routing header codex-rs writes above a delegated agent payload. + * + * `MESSAGE` is matched as well as `NEW_TASK`, and only for the unreadability CHECK -- + * recovery stays NEW_TASK-only. #3021 reported a subagent `MESSAGE` arriving in the + * parent conversation as raw `gAAAA...` ciphertext after an `adapter_eof`. The detector + * decides "unreadable" by stripping the envelope and asking whether any plaintext + * survives, so an envelope shape it does not recognise counts as surviving text: a + * `MESSAGE` whose entire body is one Fernet token measured as READABLE and was forwarded + * verbatim. + * + * Widening the strip is not the same as widening recovery. Recovery decrypts, and + * decrypting a `MESSAGE` on the parent's behalf would build a plaintext oracle out of a + * payload the parent's session may have no right to read. This only lets the proxy + * NOTICE that what it is about to forward is unreadable ciphertext, which is what the + * report asks for: fail closed with a structured error rather than paste the token. + */ +export const AGENT_MESSAGE_ROUTING_ENVELOPE = /(?:^|\n)Message Type\s*:\s*(?:NEW_TASK|MESSAGE)[^\n]*\nTask name\s*:[^\n]*\nSender\s*:[^\n]*\nPayload\s*:\s*(?:\n|$)/gi; // CXC is the compatibility-hook control namespace. Strip only the tagged paragraph: // later untagged paragraphs may be genuine task text. Repeated CXC paragraphs are diff --git a/tests/v2-agent-message-failfast.test.ts b/tests/v2-agent-message-failfast.test.ts index 5501c3362b..9e218dec11 100644 --- a/tests/v2-agent-message-failfast.test.ts +++ b/tests/v2-agent-message-failfast.test.ts @@ -32,6 +32,10 @@ const ROUTING_ENVELOPE = [ "", ].join("\n"); +// The same envelope a delegated agent uses to REPLY, as opposed to being spawned. +// #3021 saw one of these reach the parent conversation as raw `gAAAA...` text. +const MESSAGE_ROUTING_ENVELOPE = ROUTING_ENVELOPE.replace("NEW_TASK", "MESSAGE"); + afterEach(() => { globalThis.fetch = originalFetch; }); @@ -135,6 +139,46 @@ describe("V2 routed agent-message ciphertext guard", () => { ]))).toBe(true); }); + /** + * #3021: a delegated subagent's MESSAGE reply reached the parent conversation as + * raw `gAAAA...` ciphertext after an `adapter_eof`. + * + * The detector decides "unreadable" by stripping the routing envelope and asking + * whether any plaintext survives, so an envelope shape it does not recognise counts + * as surviving text. The envelope pattern matched only NEW_TASK, so a MESSAGE whose + * entire body was one Fernet token measured as READABLE and was forwarded verbatim. + * + * This is the detection half only. Recovery stays NEW_TASK-only on purpose: + * decrypting a MESSAGE on the parent's behalf would build a plaintext oracle out of + * a payload the parent's session may not be entitled to read. + */ + test("blocks a MESSAGE reply envelope followed only by a Fernet payload", () => { + expect(hasUnreadableEncryptedAgentTask(agentMessage([ + { type: "input_text", text: MESSAGE_ROUTING_ENVELOPE }, + { type: "encrypted_content", encrypted_content: FERNET_TASK }, + ]))).toBe(true); + }); + + test("blocks a MESSAGE envelope carried inside the encrypted slot itself", () => { + // The shape the report describes: header and ciphertext arrive as one + // encrypted_content string rather than as separate parts. + expect(hasUnreadableEncryptedAgentTask(agentMessage([ + { + type: "encrypted_content", + encrypted_content: `${MESSAGE_ROUTING_ENVELOPE}${FERNET_TASK}`, + }, + ]))).toBe(true); + }); + + test("a MESSAGE reply that carries real text stays readable", () => { + // The control. Widening the envelope must not turn every agent reply into a + // blocked one -- only the ones with nothing left after the header comes off. + expect(hasUnreadableEncryptedAgentTask(agentMessage([ + { type: "input_text", text: `${MESSAGE_ROUTING_ENVELOPE}the worker finished the migration` }, + { type: "encrypted_content", encrypted_content: FERNET_TASK }, + ]))).toBe(false); + }); + test("blocks a control preamble mixed into the Fernet slot before sanitization", async () => { const input = agentMessage([ { type: "input_text", text: ROUTING_ENVELOPE }, From a73a4c9984097ff54e9e092132715b2c274009ad Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 09:25:52 +0900 Subject: [PATCH 38/42] fix(codex): refresh a stored token before quarantining it on a WHAM 401 (#3019) (#3119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(devlog): re-verify the wp6 plan against the post-wp5 dev head * docs(devlog): fold the wp6 plan audit into the unit doc * docs(devlog): claim/settle the wp6 recovery record, and make the terminal oracle falsifiable * docs(devlog): finalize the wp6 recovery-store design (claim id, lease, backoff, liveness sweep) * docs(devlog): settle-by-outcome and lease rules for the wp6 recovery store * docs(devlog): flight-owned settlement and one exported lease contract for wp6 * docs(devlog): caller-specific completion and a leaf timing module for wp6 * fix(codex): refresh a stored token before quarantining it on a WHAM 401 Account-list quota sent one request to backend-api/wham/usage and turned any 401 into needsReauth. A bare 401 with no structured body is exactly what a stale-but-refreshable bearer produces after a plan change, so a working credential was thrown away and the operator was told to re-authenticate an account that was fine. forceRefreshCodexPoolToken already existed; nothing on this path called it. The path now refreshes once and replays once. needsReauth requires either structured terminal evidence in the body - the same MAIN_TERMINAL_AUTH_CODES allowlist and bounded parser the main account uses, since it is the same endpoint - or a refresh that failed terminally. Everything else is transient. Once is the hard part: an unbounded retry against an upstream 401 is a self-inflicted credential-stuffing loop. quota-401-recovery holds one budget per credential lineage, and three things about it are deliberate. The claim carries an opaque id rather than being keyed on the lineage, because lineage alone cannot separate an old claimant from a later retry on the same lineage - claim, transient failure releases, another poll claims, and the first caller's late completion spends the second caller's budget. A live claim blocks every lineage for that account, not just its own. The refresh it fences commits G to G+1 before the claimant can settle, so a G+1 claim in that window would leave the late settlement landing on nothing. Settlement is owned by the refresh, not by the caller. Cancellation rejects what the caller awaits while the shared flight keeps running and commits, so an onSettled callback attached to the resolution fires with no waiter present. Without it a cancelled poll leaves the claim to expire and the already-refreshed lineage gets a second refresh. forceRefreshCodexPoolToken also returns provenance now. selfRefreshed is a boolean and its false meant two opposite things: somebody replaced the credential, or this caller joined an in-flight refresh of the same grant. The first is a new lineage that has had no attempt and must keep its budget; the second is this lineage using its one. selfRefreshed stays as the derived boolean so the two response lanes are unchanged. * fix(codex): settle the 401 budget from the refresh, not from the caller's await First implementation review found five defects. The worst two both let an already-refreshed credential get a second refresh, which is the loop this whole phase exists to close. onSettled was attached to the caller-cancellable await. Cancellation rejects that wrapper while the shared flight keeps running and commits, so a cancelled poll reported the refresh as failed, released the budget, and the freshly refreshed lineage could claim again immediately. Settlement now rides an uncancelled resolution and the caller's cancellation is layered on top of it. A terminal refresh failure was recorded as transient. Every failure released into backoff before the catch could recognise a revoked or expired grant, so the next bare 401 found the record in backoff and reported a dead credential as healthy. The store now distinguishes a terminal settlement, and a lineage fenced that way keeps reporting needs-reauth on later polls. An external replacement could also be mislabelled joined-lineage. The shared flight's grant-mismatch and freshness branches resolve to a credential the flight did not produce; a joiner adopting those bytes was calling itself the same lineage and fencing a generation that had had no attempt of its own. Those branches now tag themselves and the adoption site carries the flight's verdict. isTerminalPoolAuthResponse consumed a clone, which tees the body while the bounded parser's timeout cancels only its own reader - the unread branch keeps buffering. Nothing needs the response afterwards, so there is nothing to tee. And the regressions could not have caught any of this: they drove the budget store in isolation and stayed green with the recovery entirely disconnected. tests/quota-401-recovery-runtime.test.ts drives the real primitive and the real store together - a cancelled owner whose background refresh still commits, both callers on one flight producing a real verdict, a terminal fence surviving a later poll, and a captured-console assertion that neither bearer is emitted. * fix(codex): actually route terminal refreshes, and stop tests passing on nothing The previous commit claimed a terminal-settlement route that was not in the code: the onSettled callback still released every failure into backoff, so a revoked grant reported needs-reauth once and then looked healthy on the next poll. It routes now. A joiner also inherited the flight's own self-refresh verdict, so a caller that performed no CAS was told the credential was its own lineage - and the budget was charged to a generation that had had no attempt. Only external-replacement is inherited; everything the adoption branch reaches is a join by definition. Moving settlement off the cancellable await had a side effect: resolveCodexToken is now called without the caller signal, which bypasses its own pre-abort guard, so an already-cancelled request would start a refresh nobody was waiting for. There is an explicit pre-abort check before the completion is created. onSettled is typed void | Promise<void> and its result is observed, because a rejected thenable from a callback is as capable of killing the process as a synchronous throw. The runtime regressions were the real problem: all four could pass against the defects they named. The cancellation test swallowed the caller's rejection, so an implementation ignoring the signal passed; it now asserts the caller really was rejected with the abort reason. The provenance test accepted any enum value, which is exactly what the joiner bug produces; it now asserts exactly one of the two callers reports self-refresh. The terminal test called only the store and could not see whether the quota path ever routes there; it now drives a real revoked refresh and a real network failure through the primitive and asserts terminal versus backoff. The secrecy test watched console.debug rather than the debug buffer the dashboard actually reads; it now checks the buffer and the serialized account view too, and asserts the refresh really happened so it is not asserting silence about nothing. * fix(codex): make terminal 401 evidence durable, and read the reason not the text Structured terminal evidence from WHAM was reported and then forgotten. The first-response branch and the replay branch each returned needsReauth: true, but the claim had already been settled non-terminally by the successful refresh - so the next poll found a spent budget, reported transient, and a dead credential looked healthy again. All three terminal branches now mark the account, which is the durable, generation-independent signal the account list already reads. isTerminalRefreshError matched substrings of the error message. TokenRefreshError carries a reason discriminator, and a durable quarantine decision should not be one reworded string away from changing. It reads reason now. The terminal regression also could not have caught either problem: it reimplemented the production settlement callback locally, so deleting the real wiring left it green. It now drives listCodexAuthAccounts through bare WHAM 401 to token invalid_grant to a second poll, and asserts the account-level mark directly - because asserting only the second response is satisfied by a cached quota and proves nothing about durability. Two smaller test fixes from the same review: the provenance assertion accepted any non-self value, which an incorrect external-replacement joiner also satisfies, so it now names joined-lineage; and the secrecy check built its own account row by hand, which cannot catch a leak in the real serializer, so it now asserts against listCodexAuthAccounts. * fix(codex): scope terminal evidence to the credential it condemns The durable reauth mark was account-wide, which outlives the credential it was about. Ordinary reauthentication clears it, but a terminal response still in flight when the operator logs back in would land afterwards and quarantine the replacement. All three marks now carry the generation the evidence concerns: the rejected one for the first response and the refresh failure, the refreshed one for the replay. The durability regression only ever reached the refresh-failure branch — bare 401 followed by invalid_grant — so deleting the other two marks left it green. There are now separate cases for structured terminal evidence on the first 401 (which needs no refresh at all) and on the replay, each asserting the mark is present and then that a replacement credential does not inherit it. The pre-abort guard also had no test of its own. It does now: an already-aborted signal must issue zero fetches, reject with the exact reason, settle failed once, and leave no claim held. And the secrecy test seeded a credential without registering the account, so the account-list assertion was scanning a list that never contained the account. It registers now and asserts the row is actually there before checking it. * docs(devlog): record what 12 review rounds changed in the #3019 unit * test(state-store): register the 401 recovery budget in the owner inventory CI shard 4 caught what my focused runs could not: STATE_STORE_REGISTRATIONS is mirrored by a hand-maintained list in tests/state-store-sweeper.test.ts, and adding codex-quota-401-recovery to the production registrations without adding it there fails that assertion. The duplication is deliberate — it forces someone to decide a new store has an owner and a sweep policy rather than inheriting one silently — so the fix is to record it, not to loosen the check. My focused file list did not include this test, which is exactly the indirect-dependency case AGENTS.md warns about: the registration is data read by another module's test, so the import graph does not connect them. --- .../060_wp6_wham_401_refresh.md | 438 ++++++++++++++++++ .../061_wp6_outcome.md | 70 +++ src/codex/account-store.ts | 129 +++++- src/codex/auth-api.ts | 235 ++++++++-- src/codex/quota-401-recovery.ts | 190 ++++++++ src/codex/quota-recovery-timing.ts | 28 ++ src/lib/state-store-registrations.ts | 8 + tests/quota-401-recovery-runtime.test.ts | 386 +++++++++++++++ tests/quota-401-recovery.test.ts | 183 ++++++++ tests/state-store-sweeper.test.ts | 4 + 10 files changed, 1630 insertions(+), 41 deletions(-) create mode 100644 devlog/_plan/260831_prio70_train_round2/061_wp6_outcome.md create mode 100644 src/codex/quota-401-recovery.ts create mode 100644 src/codex/quota-recovery-timing.ts create mode 100644 tests/quota-401-recovery-runtime.test.ts create mode 100644 tests/quota-401-recovery.test.ts diff --git a/devlog/_plan/260831_prio70_train_round2/060_wp6_wham_401_refresh.md b/devlog/_plan/260831_prio70_train_round2/060_wp6_wham_401_refresh.md index 7dc484b7ef..20d83e2d74 100644 --- a/devlog/_plan/260831_prio70_train_round2/060_wp6_wham_401_refresh.md +++ b/devlog/_plan/260831_prio70_train_round2/060_wp6_wham_401_refresh.md @@ -137,3 +137,441 @@ load-bearing here rather than routine. `Closes #3019`. Comment on PR #3020 crediting the sequence and naming the scope that was left out. + +## P-phase re-verification against the landed tree (wp6 start) + +Checked at `330470e74`, the dev head after wp5 merged. The plan holds: + +- `fetchFreshPoolAccountQuota` still converts any 401 straight into + `needsReauth` (`src/codex/auth-api.ts:979-983`). One WHAM request, no refresh attempt. +- `forceRefreshCodexPoolToken` is still at `src/codex/account-store.ts:588` with the + contract the plan's amendments depend on: `rotated` false means replaying earns the same + 401, and `selfRefreshed` true means this caller's own CAS moved the credential. +- Nothing in the quota path calls it. The only two callers are the response lanes + (`src/server/responses/compact.ts:302` and `core.ts:1806`). + +### The existing callers are the template + +Both response lanes already do the sequence this phase needs, and both encode the two +amendments the audit rounds forced into the plan: + +```ts +if (!refreshed.rotated) { + return { ok: false, quarantine: true, quarantineGeneration: refreshed.generation, ... }; +} +if (refreshed.selfRefreshed) { + handOffThreadAffinityGeneration(authCtx.accountId, authCtx.generation, refreshed.generation); +} +``` + +Two things to carry deliberately: + +1. **Quarantine fences on the RETURNED generation**, never the rejected one. A successful + token response can rotate the refresh grant while returning a byte-identical access + token, so the credential has already moved by the time `rotated` is false. +2. `selfRefreshed` gates only the affinity handoff there, because those lanes have no + per-credential retry budget. The quota path does need one, which is where the round-3 + amendment applies: `selfRefreshed === false` means *either* an external replacement + *or* a caller that joined an in-flight refresh of the same lineage + (`account-store.ts:639-645`). Spending the budget on the joined case and resetting only + on a genuine replacement is the distinction regression 8 exists to prove. + +### Scope confirmed unchanged + +`src/codex/auth-api.ts` plus a new bounded recovery module and +`tests/codex-auth-api.test.ts`. PR #3020 stays unrebased; its core sequence is carried +with credit. + + +## Amendment after the wp6 plan audit (round 1) + +Four findings, all accepted. The plan had the right recovery direction and could not have +established either security property as written. + +### 1. The lineage rule needs an account-store contract change — mandatory, not conditional + +The plan said "if the returned contract cannot express the middle row today, the phase adds +that distinction to the primitive's return". It cannot. `forceRefreshCodexPoolToken` +returns `rotated` and `selfRefreshed` only (`account-store.ts:588-607`), and the +join-and-adopt path at `account-store.ts:639-660` returns nothing that separates it from an +external replacement — which is exactly the ambiguity the round-3 amendment was written +about. + +So `src/codex/account-store.ts` **is in scope**, and the change is a provenance value +rather than a boolean: + +| provenance | meaning | budget | +| --- | --- | --- | +| `self-refresh` | this call's own CAS moved the credential | spend | +| `joined-lineage` | joined an in-flight refresh of the same grant and adopted its result | spend | +| `external-replacement` | the stored credential was replaced by someone else | reset | + +`selfRefreshed` stays as a derived boolean so the two response lanes +(`compact.ts:302`, `core.ts:1806`) keep working unchanged; it is `provenance === "self-refresh"`. +Every return path of `resolveCodexToken` gets a regression asserting its provenance, +because a value nothing tests is a value that will drift. + +### 2. Cases 4 and 8 could not produce a refresh joiner + +Same-account quota calls already coalesce at `auth-api.ts:1043-1051`: a second caller for +the same account joins the existing **quota** flight and never reaches WHAM, let alone +`forceRefreshCodexPoolToken`. Two concurrent quota calls therefore produce one flight, not +an owner and a joiner — so case 8 would have passed under the precise reset-on-join bug it +was written to catch. That is the wrong-reason pattern this train has hit repeatedly. + +Replacement: + +- **8a (primitive level).** Two concurrent `forceRefreshCodexPoolToken` calls against one + account, one owner and one joiner, asserting the joiner reports `joined-lineage` and that + exactly one token request was issued. +- **8b (path level).** Drive the joiner through the recovery module directly rather than + through two quota calls, then issue a third 401 on the returned generation and assert no + second refresh. +- **9 (new).** A late quota caller arriving after the generation advanced but before the + WHAM replay finished. The flight's `resolvedCredentialGeneration` must be updated to the + RETURNED generation before the replay, or the join predicate at `auth-api.ts:1045` sees a + stale generation and starts a redundant flight. + +### 3. "Bounded" was asserted, not designed + +`registerStateStore` only registers callbacks (`state-store-sweeper.ts:49`); it imposes no +bound. One live account churning lineages would accumulate records forever. + +The design is now explicit: **one replaceable record per account.** A new lineage replaces +the previous record rather than adding to it, so the map is bounded by the number of +accounts, which is already bounded by config. The record additionally carries a TTL so a +stale budget cannot outlive its usefulness, and the store registers centrally in +`STATE_STORE_REGISTRATIONS` (`state-store-registrations.ts:76`) with both +`sweepExpired` and `reconcileGeneration`, so a deleted or re-added account drops its record. +`src/lib/state-store-registrations.ts` is in scope. + +Tests: lineage churn on one account leaves exactly one record; an expired record is swept; +delete-and-re-add clears it. + +### 4. Terminal classification and secrecy need their own cases + +"Structured terminal evidence" needs an allowlist, and one already exists for the main +account: `MAIN_TERMINAL_AUTH_CODES` at `auth-api.ts:602` (`invalid_workspace_selected`, +`invalid_refresh_token`) with the bounded-parser reasoning documented right below it. The +pool path reuses that set and that parser rather than inventing a second vocabulary. + +The pool catch currently marks EVERY `TokenRefreshError` terminal (`auth-api.ts:1024`), so +the phase must separate a revoked or expired grant from an unknown or transient refresh +failure; only the former sets `needsReauth`. + +Added cases: + +- **10.** Replay returns a 401 carrying a terminal code — `needsReauth: true`, no second refresh. +- **11.** The refresh itself fails transiently (not a terminal code) — `needsReauth` stays + false and the budget is not marked spent, so the next poll may try again. +- **12.** Neither the rejected bearer nor the rotated one appears in any log line, debug + buffer, or serialized response on any of these paths. Asserted at runtime by capturing + the log surfaces during the flow; `privacy:scan` is a static check and does not prove it. + +### Scope, corrected + +`src/codex/auth-api.ts`, `src/codex/account-store.ts`, a new +`src/codex/quota-401-recovery.ts`, `src/lib/state-store-registrations.ts`, and +`tests/codex-auth-api.test.ts` plus `tests/codex-account-store*.test.ts` for the provenance +regressions. + + +## Amendment after the wp6 plan audit (round 2) + +Two findings, both accepted. The provenance enum, the coalescing bypass in 8a/8b, case 9, +and reusing `MAIN_TERMINAL_AUTH_CODES` were confirmed sound and are unchanged. + +### 5. The recovery record needs claim/settle, not replacement — and the TTL contradicted the guarantee + +"One replaceable record per account" bounds memory and says nothing about ordering. The +interleaving that breaks it: lineage A starts a refresh; meanwhile the credential is +externally replaced by lineage B, which receives a 401 and spends its own budget; A then +resolves as `external-replacement` and resets the record — handing B a second refresh it +already used. Replacement is the wrong primitive. + +**Claim/settle with an expected lineage.** A caller claims the budget for the lineage it is +about to refresh, and may settle only the record it claimed: + +- `claim(accountId, lineage)` returns `granted` when no record exists for the account, or + the record's lineage differs AND that record is not for a lineage the store considers + current; otherwise `spent`. +- `settle(accountId, lineage, provenance)` is a compare-and-set on the lineage. If the + stored record has moved to a newer lineage, the completion is stale and is **dropped** — + it may never downgrade a newer lineage's spent state to unspent. +- `external-replacement` does not reset in place. It settles the claimed record as spent + for the OLD lineage and lets the NEW lineage claim on its own next 401, which is what + "a new grant deserves its own recovery attempt" actually means. + +Interleavings to test: a stale completion arriving after a newer lineage has spent; a +joiner settling after an external replacement; simultaneous external and self outcomes on +one account. + +**The TTL is removed.** It was load-bearing for boundedness in the previous draft, and it +directly contradicts the security property: expiring a still-live spent record grants the +same lineage another refresh, which is the unbounded-retry loop this phase exists to +prevent. Boundedness comes from one record per account plus `reconcileGeneration`, and the +record is retained until the credential is replaced or the account is deleted. That is a +strictly stronger guarantee and one less knob. + +### 6. The terminal-refresh oracle was unfalsifiable + +Case 11 covers only a transient refresh failure, so an implementation that made **every** +refresh failure transient would pass cases 1-12 — while today's code does the opposite and +marks every `TokenRefreshError` terminal (`auth-api.ts:1024`). A test suite that cannot +distinguish the two extremes is not an oracle. + +- **11a.** Refresh fails with a revoked grant — `needsReauth: true`. +- **11b.** Refresh fails with an expired grant — `needsReauth: true`. +- **11c.** Refresh fails with an unknown or network error — `needsReauth` stays false, the + claim is released rather than settled spent, so the next poll may try again. + +Case 6 is also strengthened: after `rotated === false`, issue another 401 on the RETURNED +generation and assert no second refresh is issued before the lineage actually changes. + + +## Amendment after the wp6 plan audit (round 3) — final state design + +Four findings, all accepted. This section is the authoritative description of the recovery +store; earlier sketches in this doc are superseded where they conflict. + +### 7. The claim identity is a claim id, not the lineage + +Lineage alone cannot separate an old claimant from a later retry on the same lineage: +claim L₁ → transient failure releases L₁ → another poll claims L₁ → the first caller settles +late and spends the second caller's claim. + +``` +claim(accountId, lineage) -> { granted: true, claimId } | { granted: false, reason } +settle(accountId, claimId, outcome) // CAS on (accountId, lineage, claimId) +release(accountId, claimId, backoff) // same CAS +``` + +Settling or releasing an unclaimed or superseded claim is a **no-op**, not an error: a late +completion is exactly the case that must not disturb a newer claimant. + +`settle` also carries the **returned** generation and the provenance, because that is what +becomes the spent fence — case 6 and 8b both depend on the fence being the generation the +refresh returned, never the one that was rejected. + +### 8. `spent` is durable; `claimed` is a lease + +Removing the TTL was right for spent records and wrong for in-flight ones. A caller can be +cancelled between claim and settle — `forceRefreshCodexPoolToken` explicitly supports +caller-scoped cancellation while the shared flight continues +(`account-store.ts:639-660`) — and an abandoned claim would wedge that account's recovery +permanently. + +So the record has two shapes: + +| state | expires? | meaning | +| --- | --- | --- | +| `claimed` | yes — bounded lease | a refresh is in flight for this lineage | +| `spent` | no | this lineage already had its one refresh | +| `backoff` | at `nextAttemptAt` | a transient failure; retry allowed after the clock advances | + +An expired `claimed` lease is reclaimable, which recovers from a cancelled or thrown +caller. An expired lease is **not** promoted to `spent`: the refresh may never have +happened. + +Tests: owner cancellation between claim and settle; a settlement that throws; a stale +claim recovered by a later poll. + +### 9. Transient failure releases into backoff, not into eligibility + +Releasing outright reopens the loop the phase exists to close: a failed quota request does +not refresh the quota timestamp, so successive dashboard and background polls would each +issue another token refresh. `release` therefore records `nextAttemptAt` and the record +stays — still one per account. + +Case 11c becomes: several immediate polls after a transient refresh failure issue exactly +**one** refresh; a poll after the clock advances past `nextAttemptAt` may issue another. + +### 10. `reconcileGeneration` alone cannot see a credential change + +`GenerationContext` carries `codexAccountIds` only (`state-store-sweeper.ts:3-11`), so it +can drop a record for a deleted account but not for one whose credential was replaced under +the same id. The promise that a record lives only until credential replacement needs a +second mechanism: + +- `reconcileGeneration` drops records whose account id is gone (delete/re-add), and +- it additionally drops any record whose fenced generation fails + `isCodexAccountGenerationLive(accountId, generation)` (`account-store.ts:196-199`), which + is exactly "the credential this record fenced is no longer the stored one". + +Tests: replace the credential without another 401, run a sweep, and assert the record is +gone; delete and re-add the account and assert the same. + + +## Amendment after the wp6 plan audit (round 4) — settlement and lease rules + +Three findings, all accepted. This section is authoritative over every earlier one where +they conflict. + +### 11. A live claim is not stale just because the credential moved + +The refresh CAS commits G → G+1 (`account-store.ts:864`) **before** the quota caller can +settle. In that window the `claimed` record still fences G, so a naive liveness sweep sees +G as non-live and deletes it, and a G+1 claim can replace it. The late settle then no-ops +and G+1 is left unspent — a second refresh, which is the loop this phase closes. + +State-aware rules, replacing the flat "drop non-live" from round 3: + +- A valid, unexpired `claimed` record **blocks every other lineage for that account**. A + `claim` for a different lineage while one is live returns `granted: false`. +- Reconciliation and the liveness sweep **must not remove a live `claimed` record** merely + because its starting generation moved. Moving is the expected outcome of the refresh it + is fencing. +- Only account deletion, a matching `settle`/`release`, or lease expiry may end a claim. +- Stale-generation cleanup applies to `spent` and `backoff` records only. + +Tests: run a sweep between the refresh commit and settlement and assert the claim survives; +attempt a G+1 claim in the same window and assert it is refused. + +### 12. Settlement follows the shared flight, not the cancelled waiter + +Caller cancellation is scoped to the waiter; the shared refresh keeps running and may commit +after that caller is gone (`account-store.ts:639-660`). Releasing the claim on cancellation +would let that still-running flight rotate the credential with no spent fence at all. + +- A cancelled waiter **does not release**. The claim stays `claimed` until the shared + flight's terminal outcome is known, and whichever caller observes that outcome settles it. +- The lease is sized against the operations it covers, not a round number: the refresh + flight's stale bound (`CODEX_REFRESH_FLIGHT_STALE_MS`) plus the WHAM request deadline + (8s, `auth-api.ts:975`) plus margin. A lease shorter than the flight it fences would + expire mid-refresh and admit a second claim — the same hole from the other side. + +Tests: cancel the waiter, let the background flight commit successfully, and assert a +competing poll during that window is refused and the fence lands exactly once. + +### 13. `external-replacement` must not fence the returned generation + +Round 3 said the returned generation "becomes the spent fence". That is right for +`self-refresh` and `joined-lineage` and wrong for `external-replacement`: there the +returned generation is a **new** lineage that has had no recovery attempt and deserves its +own budget. Fencing it would deny the new credential the one refresh this phase exists to +grant. + +Outcome-specific settlement, explicitly: + +| outcome | what is spent | what the returned generation gets | +| --- | --- | --- | +| `self-refresh` | the returned generation | fenced — its one attempt is used | +| `joined-lineage` | the returned generation | fenced — same lineage, same budget | +| `external-replacement` | the claimed OLD lineage only | untouched — free to claim on its own next 401 | +| stale claim id | nothing | untouched | + +Tests assert both stored fence generations, not just the presence of a record. + + +## Amendment after the wp6 plan audit (round 5) — flight-owned settlement, and one timing contract + +Two findings, both accepted. These are the last two blockers; the plan is implementable +once they are specified. + +### 14. Settlement attaches to the FLIGHT, not to any caller + +"Whichever caller observes the terminal outcome settles it" is not implementable through +the proposed API. `forceRefreshCodexPoolToken` returns `awaitOwnCancellation(refreshPromise, +callerSignal)` (`account-store.ts:917`); cancellation rejects that wrapper while the +underlying flight keeps running privately. With no joiner, nobody observes the successful +commit at all — the claim expires and the already-refreshed lineage gets a second refresh, +which is precisely the hole being closed. + +The flight itself is what must settle. `refreshPromise` is already held on the flight record +(`account-store.ts:912`), so: + +- `forceRefreshCodexPoolToken` accepts an optional `onSettled(outcome)` callback and attaches + it to `refreshPromise` — **before** the caller-scoped wait, so it fires on the flight's own + terminal outcome whether or not any waiter is still there. +- The callback carries the claim id the caller opened with, so settlement remains a CAS on + `(accountId, lineage, claimId)` and a superseded claim is still a no-op. +- It fires exactly once per flight, for both fulfilment and rejection: a rejected flight + settles as the transient or terminal outcome rather than leaving the claim to expire. + +Test: cancel the sole waiter, let the background flight commit successfully, assert the +spent fence lands exactly once and a competing poll during the window is refused. + +### 15. One exported deadline, not three literals plus "margin" + +The lease cannot be derived today. `CODEX_REFRESH_FLIGHT_STALE_MS` is private +(`account-store.ts:411`), the flight's own ceiling is a separate inline `30_000` +(`account-store.ts:749`), and the WHAM timeout is an inline `8000` +(`auth-api.ts:975`). "Plus margin" is exactly the kind of number that drifts out of +agreement with the thing it is supposed to cover. + +So one authoritative value is exported and consumed by both operations: + +```ts +// account-store.ts +export const CODEX_REFRESH_FLIGHT_CEILING_MS = 30_000; // the flight's own AbortSignal.timeout +// auth-api.ts +export const WHAM_REQUEST_TIMEOUT_MS = 8_000; // replaces the inline literal +// quota-401-recovery.ts +export const QUOTA_RECOVERY_LEASE_MS = CODEX_REFRESH_FLIGHT_CEILING_MS + WHAM_REQUEST_TIMEOUT_MS * 2; +``` + +The replay is counted twice because the sequence is request → refresh → replay, and both +WHAM legs sit inside the lease. + +Test: the lease is strictly greater than the longest admitted flight plus its replay, and +the test derives that bound from the exported constants rather than restating a number — +a restated number is how the two drift apart. + +### 16. Explicit cross-product case + +`external-replacement` together with `rotated: false` is logically covered (no replay, the +old claim is spent, the returned generation stays free) but nothing asserts it. Added as +case 13. + + +## Amendment after the wp6 plan audit (round 6) — completion boundary and timing ownership + +Two findings, both accepted. + +### 17. The completion must be caller-specific, not the raw flight + +Attaching `onSettled` to `refreshPromise` settles the wrong thing. Provenance is +**per caller**: the owner performed the CAS, a joiner awaits the flight and then runs the +adoption path (`account-store.ts:639-660`), and an early external replacement +(`account-store.ts:635`) has no flight at all. The raw promise cannot know which of those a +given caller became. + +The cardinality was also wrong. One refresh-grant flight can serve several account aliases +sharing that grant, each holding its own claim, and "exactly once per flight" would settle +one of them and drop the rest. + +So `forceRefreshCodexPoolToken` builds a **caller-specific completion**: + +- It is created for every return path, including the no-flight external-replacement case, + and it resolves with that caller's fully classified outcome — after the adoption or + replacement branch has run, not before. +- It is **not** subject to `callerSignal`. Cancellation still rejects the value the caller + awaits; the completion continues and fires. +- One callback is attached per `(accountId, claimId)`, so two aliases sharing one flight + settle two claims independently. +- A throwing callback is swallowed: settlement bookkeeping must never reject the credential + result or disturb another waiter. + +Tests: sole cancelled owner; cancelled joiner; two aliases on one flight both settled once; +no-flight external replacement settles; a throwing callback leaves other waiters and the +returned credential unaffected. + +### 18. Timing constants live in a leaf module + +The previous layout was an import cycle: `quota-401-recovery.ts` would import +`WHAM_REQUEST_TIMEOUT_MS` from `auth-api.ts`, while `auth-api.ts` must import the recovery +module for claim/settle. + +New file `src/codex/quota-recovery-timing.ts`, importing nothing from either: + +```ts +export const CODEX_REFRESH_FLIGHT_CEILING_MS = 30_000; +export const WHAM_REQUEST_TIMEOUT_MS = 8_000; +export const QUOTA_RECOVERY_LEASE_MS = + CODEX_REFRESH_FLIGHT_CEILING_MS + WHAM_REQUEST_TIMEOUT_MS * 2; +``` + +`account-store.ts`, `auth-api.ts` and `quota-401-recovery.ts` all import from it, replacing +their inline literals (`account-store.ts:749`, `auth-api.ts:975`). The derived-bound test +imports the same leaf rather than restating 46_000. + diff --git a/devlog/_plan/260831_prio70_train_round2/061_wp6_outcome.md b/devlog/_plan/260831_prio70_train_round2/061_wp6_outcome.md new file mode 100644 index 0000000000..847d4f9f0c --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/061_wp6_outcome.md @@ -0,0 +1,70 @@ +# 061 outcome — wp6 (#3019): what the reviews changed + +Seven plan-audit rounds before any code, then five implementation rounds. The plan that +was implemented is materially different from the one written at wp0, and the difference is +the point of this record. + +## What the plan audits changed, before a line was written + +| round | what the plan got wrong | +| --- | --- | +| 1 | "Add provenance to the primitive **if** the contract cannot express it" — it cannot; the change is mandatory, and `account-store.ts` belongs in scope | +| 1 | The concurrency cases could not produce a refresh joiner at all: same-account quota calls coalesce at `auth-api.ts:1043` **before** reaching the primitive, so case 8 would have passed under the exact bug it targeted | +| 2 | The TTL contradicted the security property — expiring a spent record hands the same lineage another refresh | +| 3 | Keying the claim on the lineage cannot separate an old claimant from a later retry on the same lineage | +| 4 | The refresh commits `G → G+1` before the claimant settles, so a liveness sweep would delete a live claim and a `G+1` claim would replace it | +| 4 | Two sections contradicted each other on `external-replacement`: fencing the returned generation there denies a fresh credential its own budget | +| 5 | "Whichever caller observes the outcome settles" is not implementable — `awaitOwnCancellation` rejects the wrapper while the flight continues privately, so with no joiner nobody observes the commit | +| 6 | Attaching settlement to the raw flight settles the wrong thing: provenance is per caller, and one grant-flight can serve several aliases each holding their own claim | +| 6 | The timing layout was an import cycle — `auth-api` must import the recovery store, so the store cannot import `auth-api` for the WHAM timeout | + +## What the implementation reviews changed + +The same defect kept reappearing: **something is unknown, and the code treats it as +settled.** Every instance either opened the retry loop this phase exists to close, or made +a dead credential look healthy. + +| round | defect | +| --- | --- | +| 1 | `onSettled` on the caller-cancellable await: a cancelled poll reported "failed" for a refresh that was committing, released the budget, and the refreshed lineage could claim again | +| 1 | A joiner inherited the flight's `self-refresh`, so a caller that performed no CAS was told the credential was its own lineage | +| 1 | An external replacement from the flight's grant-mismatch and freshness branches was labelled `joined-lineage` | +| 1 | `resp.clone()` tees the body while the bounded parser cancels only its own reader | +| 2 | The terminal route was in the commit message and not in the code — every failure still released into backoff | +| 2 | Moving settlement off the caller signal bypassed `resolveCodexToken`'s own pre-abort guard, so an already-cancelled request started a refresh | +| 3 | Structured terminal evidence was reported and forgotten: the claim was already settled non-terminally, so the next poll called a dead credential healthy | +| 3 | `isTerminalRefreshError` matched message substrings while `TokenRefreshError` carries a `reason` discriminator | +| 4 | The reauth mark was account-wide, so a terminal response still in flight when the operator re-authenticated would quarantine the replacement | + +## Tests that proved nothing + +Five separate times a regression passed against the defect it named: + +- The first suite drove the budget store in isolation and stayed green with the recovery + entirely disconnected from the quota path. +- The cancellation test swallowed the caller's rejection, so an implementation ignoring + the signal passed. +- The provenance test accepted any enum value — including the wrong one. +- The terminal test reimplemented the production callback locally, so deleting the real + wiring left it green. +- The secrecy test watched `console.debug` rather than the debug buffer the dashboard + reads, built its own account row by hand, and scanned a list that never contained the + account under test. + +Each is now driven through the real path: `listCodexAuthAccounts` for the terminal and +serialization cases, `forceRefreshCodexPoolToken` for cancellation and provenance, and the +actual `getDebugLogEntries` buffer for secrecy. Every fix was driven RED against its own +defect and restored. + +## The shape that survived + +- **Claim id, not lineage.** A clear or settle is a compare-and-set on + `(accountId, lineage, claimId)`, so a late completion is a no-op rather than somebody + else's budget. +- **`spent` is durable, `claimed` is a lease.** Expiring a spent record would grant a + second refresh; an abandoned lease is reclaimable but never promoted to spent. +- **Settlement rides the refresh, not the caller.** Cancellation rejects what the caller + awaits; the flight still settles. +- **Evidence is scoped to what it condemns.** A terminal mark names the generation it is + about, so it cannot outlive the credential and quarantine its replacement. + diff --git a/src/codex/account-store.ts b/src/codex/account-store.ts index 6774637257..952ac048b8 100644 --- a/src/codex/account-store.ts +++ b/src/codex/account-store.ts @@ -13,6 +13,7 @@ import { import { assertNotRealHomeUnderTest } from "../lib/test-home-guard"; import type { CodexAccountCredentialRecord, CodexAccountCredentials } from "../types"; import { advanceCodexCredentialMutationEpoch } from "./credential-mutation-epoch"; +import { CODEX_REFRESH_FLIGHT_CEILING_MS } from "./quota-recovery-timing"; type LegacyCodexAccountStore = Record<string, CodexAccountCredentials>; type CodexAccountStore = Record<string, CodexAccountCredentialRecord>; @@ -407,7 +408,29 @@ type CodexRefreshResult = CodexTokenResult & { * refresh of the one the caller was holding, not somebody else's replacement. */ selfRefreshed?: boolean; + /** + * Three-way form of {@link selfRefreshed}, kept alongside it so existing callers are + * unaffected (#3019). `selfRefreshed` is `provenance === "self-refresh"`. + */ + provenance?: CodexRefreshProvenance; }; + +/** + * How THIS caller arrived at the credential it is returning (#3019). + * + * `selfRefreshed` is a boolean, and a boolean cannot carry three cases. Its `false` means + * both "somebody else replaced the credential" and "I joined an in-flight refresh of the + * same grant and adopted its result" — and a recovery budget has to treat those opposite + * ways. Joining is the same lineage getting its one refresh; replacement is a NEW lineage + * that has not had one yet, and charging it for somebody else's attempt would deny the + * fresh credential the recovery this exists to grant. + */ +export type CodexRefreshProvenance = "self-refresh" | "joined-lineage" | "external-replacement"; + +/** Terminal outcome of one forced refresh, as seen by the caller that requested it. */ +export type ForcedRefreshOutcome = + | { kind: "resolved"; provenance: CodexRefreshProvenance; generation: number; rotated: boolean } + | { kind: "failed"; error: unknown }; const MAX_CODEX_REFRESH_FLIGHTS = 32; const CODEX_REFRESH_FLIGHT_STALE_MS = 120_000; interface RefreshFlight { @@ -587,22 +610,81 @@ function awaitOwnCancellation<T>(work: Promise<T>, callerSignal?: AbortSignal): */ export async function forceRefreshCodexPoolToken( id: string, - options: { rejectedGeneration: number; rejectedAccessToken: string; signal?: AbortSignal }, -): Promise<CodexTokenResult & { rotated: boolean; selfRefreshed: boolean }> { - const result = await resolveCodexToken( + options: { + rejectedGeneration: number; + rejectedAccessToken: string; + signal?: AbortSignal; + /** + * Fires with THIS caller's classified outcome, regardless of `signal` (#3019). + * + * Cancellation rejects what the caller awaits; the shared flight keeps running and + * commits. A recovery budget claimed before the refresh therefore has no one left to + * settle it — the claim expires and the already-refreshed lineage gets a second + * refresh, which is the loop the budget exists to close. This callback is attached to + * the resolution itself, so it fires with no waiter present. + * + * It is called exactly once per call, for both success and failure, and its own + * failures are swallowed: settlement bookkeeping must never reject a credential the + * caller successfully obtained, nor disturb another waiter on the same flight. + */ + onSettled?: (outcome: ForcedRefreshOutcome) => void | Promise<void>; + }, +): Promise<CodexTokenResult & { rotated: boolean; selfRefreshed: boolean; provenance: CodexRefreshProvenance }> { + const settle = (outcome: ForcedRefreshOutcome) => { + // Both halves matter: a synchronous throw and a rejected thenable are equally capable + // of turning settlement bookkeeping into an unhandled rejection that fails the process. + try { void Promise.resolve(options.onSettled?.(outcome)).catch(() => {}); } catch { /* ignore */ } + }; + const classify = (result: CodexRefreshResult): CodexRefreshProvenance => + // Default to the conservative reading. A path that did not classify itself is not + // assumed to be this caller's own lineage: charging a replacement for somebody else's + // attempt is the failure mode, so an unlabelled path leaves the returned lineage its + // own budget. + result.provenance ?? (result.selfRefreshed === true ? "self-refresh" : "external-replacement"); + + // The completion is NOT the caller's await. + // + // `options.signal` cancels what this function returns, while the shared flight keeps + // running and commits. Settling from the cancelled await therefore reported "failed" for + // a refresh that was about to succeed — releasing the budget, and letting the newly + // refreshed lineage claim again moments later. So the settlement rides an uncancelled + // resolution and the caller's cancellation is layered on top of it. + // A caller that is already gone must not start work. `resolveCodexToken` is called + // without the caller signal below, which bypasses its own pre-abort guard, so a + // pre-aborted request would otherwise rotate a credential nobody is waiting for. + if (options.signal?.aborted) { + settle({ kind: "failed", error: options.signal.reason }); + throw options.signal.reason; + } + const completion = resolveCodexToken( id, { rejectedGeneration: options.rejectedGeneration, rejectedAccessToken: options.rejectedAccessToken }, - options.signal, + // Deliberately no caller signal: the flight is shared and this settlement speaks for + // the credential, not for whoever happened to be waiting. + undefined, ); + completion.then( + resolved => settle({ + kind: "resolved", + provenance: classify(resolved), + generation: resolved.generation, + rotated: resolved.accessToken !== options.rejectedAccessToken, + }), + error => settle({ kind: "failed", error }), + ); + const result = await awaitOwnCancellation(completion, options.signal); + const provenance = classify(result); + const rotated = result.accessToken !== options.rejectedAccessToken; return { accessToken: result.accessToken, chatgptAccountId: result.chatgptAccountId, generation: result.generation, - rotated: result.accessToken !== options.rejectedAccessToken, + rotated, // Only a CAS this call performed itself proves the new credential descends from the // rejected one; anything else is somebody else's replacement and must not be treated // as this request's own lineage. - selfRefreshed: result.selfRefreshed === true, + selfRefreshed: provenance === "self-refresh", + provenance, }; } @@ -633,7 +715,15 @@ async function resolveCodexToken( // correct again and refreshing would burn a rotation for nothing. const forcedTargetsStoredCredential = forced !== undefined && !forcedFenceSuperseded(record.generation, forced); if (cred.expiresAt > Date.now() + REFRESH_SKEW_MS && !forcedTargetsStoredCredential) { - return { accessToken: cred.accessToken, chatgptAccountId: cred.chatgptAccountId, generation: record.generation }; + // The freshness shortcut: nothing was refreshed and nothing was adopted. A forced + // caller reaches it only once its fence was superseded, which is a replacement by + // definition; an ordinary caller does not read this field. + return { + accessToken: cred.accessToken, + chatgptAccountId: cred.chatgptAccountId, + generation: record.generation, + provenance: "external-replacement", + }; } const existing = refreshLocks.get(refreshGrantFingerprint); @@ -658,6 +748,14 @@ async function resolveCodexToken( accessToken: currentCred.accessToken, chatgptAccountId: currentCred.chatgptAccountId, generation: current.generation, + // Adopted the stored result of a flight this caller joined: same grant, same + // lineage. Not a replacement — that distinction is the whole point of #3019. + // + // Only `external-replacement` is inherited. The flight's own success is tagged + // `self-refresh` for the caller that performed the CAS, and copying that here + // would tell a caller that did no CAS that the credential is its own lineage. + // Everything this branch adopts is, by definition, a join. + provenance: refreshed.provenance === "external-replacement" ? "external-replacement" : "joined-lineage", }; } } @@ -693,6 +791,9 @@ async function resolveCodexToken( accessToken: currentCred.accessToken, chatgptAccountId: currentCred.chatgptAccountId, generation: current.generation, + // `forcedFenceSuperseded` is exactly "somebody else moved this credential past + // the generation I was holding" — a new lineage, entitled to its own budget. + provenance: "external-replacement", }; } if ( @@ -720,6 +821,7 @@ async function resolveCodexToken( // This joiner performed its own CAS onto its own record, so the resulting // generation is its own lineage even though another caller drove the fetch. selfRefreshed: true, + provenance: "self-refresh", resolvedGrantFingerprint: refreshGrantFingerprint, }; } @@ -747,7 +849,7 @@ async function resolveCodexToken( * eviction) and the 30s ceiling remain, because those bound the flight itself. */ const abort = new AbortController(); - const signal = AbortSignal.any([abort.signal, AbortSignal.timeout(30_000)]); + const signal = AbortSignal.any([abort.signal, AbortSignal.timeout(CODEX_REFRESH_FLIGHT_CEILING_MS)]); let flight!: RefreshFlight; const fetchPromise = withCodexRefreshFileLock(refreshGrantFingerprint, signal, async (): Promise<CodexRefreshResult> => { const current = readCodexAccountRecord(id); @@ -765,6 +867,9 @@ async function resolveCodexToken( credential: lockedCred, // This credential belongs to a DIFFERENT grant than the flight was opened // for. Tagging it keeps a joiner from adopting it as its own. + // It is also somebody else's credential by definition, so a joiner that ends up + // adopting it must not charge it to this lineage's budget (#3019). + provenance: "external-replacement", ...(lockedRefreshGrantFingerprint !== undefined ? { resolvedGrantFingerprint: lockedRefreshGrantFingerprint } : {}), @@ -783,6 +888,9 @@ async function resolveCodexToken( chatgptAccountId: lockedCred.chatgptAccountId, generation: startGeneration, credential: lockedCred, + // The stored credential is fresh and no forced fence still targets it: whoever + // wrote it, it was not this call. A joiner adopting it inherits that provenance. + provenance: "external-replacement", resolvedGrantFingerprint: refreshGrantFingerprint, }; } @@ -803,6 +911,7 @@ async function resolveCodexToken( credential: sameGrantFreshCredential, resolvedGrantFingerprint: refreshGrantFingerprint, selfRefreshed: true, + provenance: "self-refresh", }; } const res = await fetch(CHATGPT_TOKEN_URL, { @@ -882,6 +991,7 @@ async function resolveCodexToken( // token — tagging the new grant would make every legitimate joiner look foreign. resolvedGrantFingerprint: refreshGrantFingerprint, selfRefreshed: true, + provenance: "self-refresh", }; }); /* @@ -923,6 +1033,9 @@ async function resolveCodexToken( // produced this generation, and a forced caller needs that to know whether the new // credential descends from the one it was holding. ...(result.selfRefreshed !== undefined ? { selfRefreshed: result.selfRefreshed } : {}), + // Provenance rides out with the rest: a joiner that adopts this result needs the + // flight's own classification, not a guess made at the adoption site (#3019). + ...(result.provenance !== undefined ? { provenance: result.provenance } : {}), ...(result.resolvedGrantFingerprint !== undefined ? { resolvedGrantFingerprint: result.resolvedGrantFingerprint } : {}), diff --git a/src/codex/auth-api.ts b/src/codex/auth-api.ts index c70ebf79a7..91734a3229 100644 --- a/src/codex/auth-api.ts +++ b/src/codex/auth-api.ts @@ -10,6 +10,7 @@ import { getCodexAccountCredential, getValidCodexToken, isCodexAccountGenerationLive, + forceRefreshCodexPoolToken, markCodexAccountValidated, readCodexAccountRecord, saveCodexAccountCredential, @@ -124,6 +125,14 @@ import { tryAcquireNativeMainProfileClaim } from "./native-main-admission"; import { withNativeMainSharedClaim } from "./native-main-claim"; import { resolveNativeProfileContext } from "./native-profile-store"; import { NativeProfileError } from "./native-profile-types"; +import { WHAM_REQUEST_TIMEOUT_MS } from "./quota-recovery-timing"; +import { + claimQuotaRecovery, + quotaRecoveryTerminalFor, + releaseQuotaRecovery, + settleQuotaRecovery, + settleQuotaRecoveryTerminal, +} from "./quota-401-recovery"; function isNativeMainClaimUnavailable(error: unknown): error is NativeProfileError { return error instanceof NativeProfileError @@ -766,7 +775,7 @@ async function fetchMainAccountInfoWhileOwned( try { const resp = await fetch("https://chatgpt.com/backend-api/wham/usage", { headers: { Authorization: `Bearer ${tokens.access_token}`, "ChatGPT-Account-Id": tokens.account_id }, - signal: AbortSignal.timeout(8000), + signal: AbortSignal.timeout(WHAM_REQUEST_TIMEOUT_MS), }); if (!resp.ok) { const terminalAuthFailure = await isTerminalMainAuthResponse(resp, isMainAccountTokenVerifiablyLive()); @@ -959,6 +968,180 @@ function reconcileFreshPoolAccountPlans(runtimeConfig: OcxConfig, updates: Fresh } } + + +/** + * One refresh-and-replay for a pool account whose WHAM request came back 401 (#3019). + * + * The account list used to convert any 401 straight into `needsReauth`, and a bare 401 is + * exactly what a stale-but-refreshable bearer produces after a plan change — so a healthy + * credential was thrown away and the operator was told to log in again. + * + * Bounded by the recovery store: one attempt per credential lineage. An unbounded retry + * against an upstream 401 is a self-inflicted credential-stuffing loop, which is why the + * claim is taken BEFORE the refresh and settled by the flight rather than by this caller. + */ +async function recoverPoolQuotaFrom401(ctx: { + accountId: string; + existing: StoredAccountQuota | null; + configuredPlan: string | undefined; + rejectedAccessToken: string; + rejectedGeneration: number; + resp: Response; + onCredentialGeneration?: (generation: number) => void; +}): Promise<PoolQuotaResult> { + const { accountId, existing, configuredPlan, rejectedAccessToken, rejectedGeneration, resp } = ctx; + + // Structured terminal evidence short-circuits everything: the same allowlist and bounded + // parser the main account uses, because it is the same endpoint answering. + if (await isTerminalPoolAuthResponse(resp)) { + // Durable, not just this response: the account list re-polls, and without a recorded + // mark the next bare 401 finds nothing terminal and reports the account healthy. + // + // Scoped to the generation this evidence is ABOUT. An account-wide mark would outlive + // the credential it condemned, so a late terminal response arriving after the operator + // re-authenticated would quarantine the replacement. + markAccountNeedsReauth(accountId, captureConfigGeneration(), rejectedGeneration); + return { quota: existing ?? null, needsReauth: true, credentialGeneration: rejectedGeneration }; + } + + const claim = claimQuotaRecovery(accountId, rejectedGeneration); + if (!claim.granted) { + // A lineage fenced by a TERMINAL refresh failure stays terminal. Without this, the + // budget being used would make the next bare 401 report a dead credential as healthy. + if (quotaRecoveryTerminalFor(accountId, rejectedGeneration)) { + return { quota: existing ?? null, needsReauth: true, credentialGeneration: rejectedGeneration }; + } + // Otherwise: this lineage spent its attempt, another caller is mid-refresh, or a + // transient failure is backing off. Report transient and let the next poll try — + // quarantining here would undo the whole point of the budget. + return { quota: existing ?? null, needsReauth: false, credentialGeneration: rejectedGeneration }; + } + + let refreshed: Awaited<ReturnType<typeof forceRefreshCodexPoolToken>>; + try { + refreshed = await forceRefreshCodexPoolToken(accountId, { + rejectedGeneration, + rejectedAccessToken, + // Settlement rides the flight, not this await: a cancelled caller would otherwise + // leave the claim to expire while the shared refresh commits, and the already + // refreshed lineage would get a second attempt. + onSettled: outcome => { + if (outcome.kind === "resolved") { + settleQuotaRecovery(accountId, claim.claimId, outcome); + } else if (outcome.error instanceof TokenRefreshError && isTerminalRefreshError(outcome.error)) { + // A revoked or expired grant does not become valid on the next poll. Releasing it + // into backoff would let the following bare 401 find a non-terminal record and + // report a dead credential as healthy. + settleQuotaRecoveryTerminal(accountId, claim.claimId); + } else { + releaseQuotaRecovery(accountId, claim.claimId, QUOTA_RECOVERY_BACKOFF_MS); + } + }, + }); + } catch (e) { + // A refresh that failed terminally is the one case where the credential really is gone. + // Everything else is unknown, and unknown is not proof. + if (e instanceof TokenRefreshError && isTerminalRefreshError(e)) { + markAccountNeedsReauth(accountId, captureConfigGeneration(), rejectedGeneration); + return { quota: existing ?? null, needsReauth: true, credentialGeneration: rejectedGeneration }; + } + return { quota: existing ?? null, needsReauth: false, credentialGeneration: rejectedGeneration }; + } + + // A byte-identical access token means replaying earns the same 401. Report transient + // rather than burning the replay; the fence already moved to the returned generation. + if (!refreshed.rotated) { + return { quota: existing ?? null, needsReauth: false, credentialGeneration: refreshed.generation }; + } + + // The flight may have moved the generation while this request was in the air. Tell the + // coalescing layer where the credential actually is, or a late caller joins on a stale + // generation and opens a redundant flight. + ctx.onCredentialGeneration?.(refreshed.generation); + + const writerGeneration = captureConfigGeneration(); + const replay = await fetch("https://chatgpt.com/backend-api/wham/usage", { + headers: { + Authorization: `Bearer ${refreshed.accessToken}`, + "ChatGPT-Account-Id": refreshed.chatgptAccountId, + }, + signal: AbortSignal.timeout(WHAM_REQUEST_TIMEOUT_MS), + }); + if (!replay.ok) { + if (replay.status === 401 && await isTerminalPoolAuthResponse(replay)) { + // The refresh already settled this claim non-terminally, so the record alone would + // let the next poll call a dead credential healthy. The evidence is about the + // REFRESHED credential, which is what the replay used. + markAccountNeedsReauth(accountId, writerGeneration, refreshed.generation); + return { quota: existing ?? null, needsReauth: true, credentialGeneration: refreshed.generation }; + } + return { quota: existing ?? null, needsReauth: false, credentialGeneration: refreshed.generation }; + } + return await commitPoolQuotaResponse(replay, { + accountId, existing, configuredPlan, generation: refreshed.generation, writerGeneration, + }); +} + +/** Backoff after a refresh failure that proved nothing about the credential. */ +const QUOTA_RECOVERY_BACKOFF_MS = 60_000; + +/** Same allowlist and bounded parser as the main account: it is the same endpoint. */ +async function isTerminalPoolAuthResponse(resp: Response): Promise<boolean> { + // Consume the original rather than a clone. `resp.clone()` tees the body, and the + // bounded parser's timeout cancels only its own reader — the unread original branch + // keeps buffering. Nothing needs this response afterwards, so there is nothing to tee. + const code = await readMainAuthErrorCode(resp); + return typeof code === "string" && MAIN_TERMINAL_AUTH_CODES.has(code); +} + +/** A revoked or expired grant is terminal; an unknown or transport failure is not. */ +function isTerminalRefreshError(error: TokenRefreshError): boolean { + // Read the discriminator, not the message. TokenRefreshError carries `reason`, and + // matching on human text would let a durable quarantine decision change the next time + // somebody rewords an error string. + return error.reason === "revoked" || error.reason === "expired"; +} + +/** Parse and store a successful WHAM response. Shared by the first attempt and the replay. */ +async function commitPoolQuotaResponse( + resp: Response, + ctx: { + accountId: string; + existing: StoredAccountQuota | null; + configuredPlan: string | undefined; + generation: number; + writerGeneration: number; + }, +): Promise<PoolQuotaResult> { + const { accountId, existing, configuredPlan, generation, writerGeneration } = ctx; + const data = (await resp.json()) as WhamUsageResponse; + const freshPlan = nonEmptyPlan(data.plan_type) ?? undefined; + const quota = parseUsageQuota({ ...data, plan_type: freshPlan ?? configuredPlan }); + const freshResetCredits = quota?.resetCredits; + if (!quota) { + return { + quota: isCodexAccountGenerationLive(accountId, generation) ? existing ?? null : getAccountQuota(accountId), + needsReauth: false, + credentialGeneration: generation, + ...(freshPlan !== undefined ? { freshPlan, freshCredentialGeneration: generation } : {}), + }; + } + if (!isCodexAccountGenerationLive(accountId, generation)) { + return { quota: null, needsReauth: false, credentialGeneration: generation }; + } + setAccountQuotaFromParsed(accountId, quota, writerGeneration); + return { + quota: getAccountQuota(accountId), + needsReauth: false, + credentialGeneration: generation, + freshQuota: quota, + freshCredentialGeneration: generation, + ...(freshPlan !== undefined ? { freshPlan } : {}), + ...(freshResetCredits !== undefined ? { freshResetCredits } : {}), + }; +} + async function fetchFreshPoolAccountQuota( accountId: string, existing: StoredAccountQuota | null, @@ -976,39 +1159,25 @@ async function fetchFreshPoolAccountQuota( signal: AbortSignal.timeout(8000), }); if (!resp.ok) { - return { - quota: existing ?? null, - needsReauth: resp.status === 401, - credentialGeneration: generation, - }; - } - const data = (await resp.json()) as WhamUsageResponse; - const freshPlan = nonEmptyPlan(data.plan_type) ?? undefined; - const quota = parseUsageQuota({ ...data, plan_type: freshPlan ?? configuredPlan }); - const freshResetCredits = quota?.resetCredits; - if (!quota) { - return { - quota: isCodexAccountGenerationLive(accountId, generation) ? existing ?? null : getAccountQuota(accountId), - needsReauth: false, - credentialGeneration: generation, - ...(freshPlan !== undefined - ? { freshPlan, freshCredentialGeneration: generation } - : {}), - }; - } - if (!isCodexAccountGenerationLive(accountId, generation)) { - return { quota: null, needsReauth: false, credentialGeneration: generation }; + if (resp.status !== 401) { + return { quota: existing ?? null, needsReauth: false, credentialGeneration: generation }; + } + // A bare 401 is what a stale-but-refreshable bearer produces after a plan change, so + // quarantining on it tells the operator to re-authenticate an account that was fine + // (#3019). Refresh once, replay once, and only then decide. + return await recoverPoolQuotaFrom401({ + accountId, + existing, + configuredPlan, + rejectedAccessToken: accessToken, + rejectedGeneration: generation, + resp, + onCredentialGeneration, + }); } - setAccountQuotaFromParsed(accountId, quota, writerGeneration); - return { - quota: getAccountQuota(accountId), - needsReauth: false, - credentialGeneration: generation, - freshQuota: quota, - freshCredentialGeneration: generation, - ...(freshPlan !== undefined ? { freshPlan } : {}), - ...(freshResetCredits !== undefined ? { freshResetCredits } : {}), - }; + return await commitPoolQuotaResponse(resp, { + accountId, existing, configuredPlan, generation, writerGeneration, + }); } catch (e) { if (e instanceof CodexCredentialGenerationConflictError || e instanceof CodexCredentialRefreshLockTimeoutError || e instanceof CodexCredentialRefreshBusyError || e instanceof CodexCredentialRefreshStaleError) { diff --git a/src/codex/quota-401-recovery.ts b/src/codex/quota-401-recovery.ts new file mode 100644 index 0000000000..7de4fda232 --- /dev/null +++ b/src/codex/quota-401-recovery.ts @@ -0,0 +1,190 @@ +import { randomUUID } from "node:crypto"; +import { isCodexAccountGenerationLive, type CodexRefreshProvenance } from "./account-store"; +import { QUOTA_RECOVERY_LEASE_MS } from "./quota-recovery-timing"; + +/** + * One refresh-and-replay per credential lineage, for a WHAM 401 (#3019). + * + * A bare 401 from `backend-api/wham/usage` is what a stale-but-refreshable bearer produces + * after a plan change, so quarantining on it tells the operator to re-authenticate an + * account that was fine. The fix is to refresh and replay once — and `once` is the whole + * problem, because an unbounded retry against an upstream 401 is a self-inflicted + * credential-stuffing loop. + * + * ## Why a claim id rather than the lineage + * + * Keying the budget on the lineage cannot separate an old claimant from a later retry on + * the same lineage: claim L, a transient failure releases L, another poll claims L, and + * the first caller's late settlement spends the second caller's claim. The claim id makes + * every mutation a compare-and-set on `(accountId, lineage, claimId)`, so a stale + * completion is a no-op instead of somebody else's budget. + * + * ## Why `spent` never expires and `claimed` does + * + * Expiring a spent record would hand the same lineage another refresh, which is exactly + * the property this module exists to hold. But a caller can be cancelled or die between + * claim and settle, so `claimed` carries a bounded lease: an expired lease is reclaimable, + * and is never promoted to `spent` because the refresh may not have happened. + */ + +export type RecoveryRecord = + | { state: "claimed"; lineage: number; claimId: string; expiresAt: number } + /** + * `terminal` marks a lineage whose refresh proved the grant is dead. It is spent AND the + * account must keep reporting needs-reauth: without it, the next bare 401 finds the + * budget used, reports transient, and a dead credential looks healthy. + */ + | { state: "spent"; lineage: number; terminal?: true } + | { state: "backoff"; lineage: number; nextAttemptAt: number }; + +export type ClaimResult = + | { granted: true; claimId: string } + | { granted: false; reason: "spent" | "in-flight" | "backoff" }; + +const records = new Map<string, RecoveryRecord>(); + +function leaseExpired(record: RecoveryRecord, now: number): boolean { + return record.state === "claimed" && record.expiresAt <= now; +} + +/** + * Claim the one refresh this lineage is entitled to. + * + * A live claim blocks EVERY lineage for the account, not just its own. The refresh it + * fences commits `G -> G+1` before the claimant can settle, so during that window a + * `G+1` claim would otherwise be granted and the late settlement would land on nothing. + */ +export function claimQuotaRecovery( + accountId: string, + lineage: number, + now: number = Date.now(), +): ClaimResult { + const existing = records.get(accountId); + if (existing && !leaseExpired(existing, now)) { + if (existing.state === "claimed") return { granted: false, reason: "in-flight" }; + if (existing.state === "spent") { + // Only this lineage is fenced. A different one is a new grant with its own budget. + if (existing.lineage === lineage) return { granted: false, reason: "spent" }; + } else if (existing.lineage === lineage && existing.nextAttemptAt > now) { + return { granted: false, reason: "backoff" }; + } + } + const claimId = randomUUID(); + records.set(accountId, { state: "claimed", lineage, claimId, expiresAt: now + QUOTA_RECOVERY_LEASE_MS }); + return { granted: true, claimId }; +} + +function heldClaim(accountId: string, claimId: string): Extract<RecoveryRecord, { state: "claimed" }> | null { + const existing = records.get(accountId); + if (!existing || existing.state !== "claimed" || existing.claimId !== claimId) return null; + return existing; +} + +/** + * Record the outcome of a claimed refresh. + * + * Which lineage gets fenced depends on the outcome, and getting this backwards is how a + * fresh credential loses the recovery it is entitled to: + * + * - `self-refresh` and `joined-lineage` are this lineage's own attempt, so the RETURNED + * generation is spent. The returned one, never the rejected one: a successful token + * response can rotate only the refresh grant, leaving the access token byte-identical, + * and the credential has already moved by then. + * - `external-replacement` means somebody else's credential is now stored. The claimed + * OLD lineage is spent — this caller did use its attempt — and the returned generation + * is left untouched so it can claim on its own next 401. + */ +export function settleQuotaRecovery( + accountId: string, + claimId: string, + outcome: { provenance: CodexRefreshProvenance; generation: number }, +): void { + const held = heldClaim(accountId, claimId); + if (!held) return; // superseded or already settled: never disturb a newer claimant + const fenced = outcome.provenance === "external-replacement" ? held.lineage : outcome.generation; + records.set(accountId, { state: "spent", lineage: fenced }); +} + +/** + * Record a refresh that failed with proof the grant itself is dead. + * + * Distinct from {@link releaseQuotaRecovery}: a revoked or expired grant will not become + * valid on the next poll, so backing off would let a later 401 report the account healthy + * while it is not. The lineage is fenced durably and the caller quarantines. + */ +export function settleQuotaRecoveryTerminal(accountId: string, claimId: string): void { + const held = heldClaim(accountId, claimId); + if (!held) return; + records.set(accountId, { state: "spent", lineage: held.lineage, terminal: true }); +} + +/** Did this lineage's one refresh prove the grant dead? */ +export function quotaRecoveryTerminalFor(accountId: string, lineage: number): boolean { + const record = records.get(accountId); + return record?.state === "spent" && record.lineage === lineage && record.terminal === true; +} + +/** + * Release a claim whose refresh failed without proving anything about the credential. + * + * Into BACKOFF, not into eligibility: a failed quota request does not refresh the quota + * timestamp, so successive dashboard and background polls would each issue another token + * refresh — the loop, reopened from the other side. + */ +export function releaseQuotaRecovery( + accountId: string, + claimId: string, + backoffMs: number, + now: number = Date.now(), +): void { + const held = heldClaim(accountId, claimId); + if (!held) return; + records.set(accountId, { state: "backoff", lineage: held.lineage, nextAttemptAt: now + backoffMs }); +} + +/** + * Drop records for accounts that no longer exist, and fences whose credential has moved. + * + * A live claim is exempt. Its refresh commits the generation forward, so "the fenced + * generation is no longer stored" is the EXPECTED state mid-flight, and removing it there + * would let a second claim in before the first settles. + */ +export function reconcileQuotaRecovery(liveAccountIds: ReadonlySet<string>, now: number = Date.now()): number { + let removed = 0; + for (const [accountId, record] of [...records]) { + if (!liveAccountIds.has(accountId)) { + records.delete(accountId); + removed += 1; + continue; + } + if (record.state === "claimed" && !leaseExpired(record, now)) continue; + if (!isCodexAccountGenerationLive(accountId, record.lineage)) { + records.delete(accountId); + removed += 1; + } + } + return removed; +} + +/** Drop expired backoff windows and abandoned leases. */ +export function sweepExpiredQuotaRecovery(now: number = Date.now()): number { + let removed = 0; + for (const [accountId, record] of [...records]) { + // A spent record is durable: expiring it would grant the same lineage another refresh. + if (record.state === "spent") continue; + const stale = record.state === "backoff" ? record.nextAttemptAt <= now : leaseExpired(record, now); + if (stale) { + records.delete(accountId); + removed += 1; + } + } + return removed; +} + +export function quotaRecoveryRecordForTests(accountId: string): RecoveryRecord | undefined { + return records.get(accountId); +} + +export function resetQuotaRecoveryForTests(): void { + records.clear(); +} diff --git a/src/codex/quota-recovery-timing.ts b/src/codex/quota-recovery-timing.ts new file mode 100644 index 0000000000..f1a992e0b7 --- /dev/null +++ b/src/codex/quota-recovery-timing.ts @@ -0,0 +1,28 @@ +/** + * Timing contract shared by the credential refresh, the WHAM quota request, and the + * 401-recovery budget (#3019). + * + * A leaf on purpose: `auth-api.ts` imports the recovery store to claim and settle, so the + * recovery store cannot import `auth-api.ts` back for the WHAM timeout. Both of them, and + * `account-store.ts`, import this instead. + * + * These were three inline literals in three files. The lease has to outlast the operations + * it fences — a lease that expires mid-refresh admits a second claim for a lineage that is + * already being refreshed — so it is derived here rather than restated as a round number + * that drifts away from what it covers. + */ + +/** The refresh flight's own `AbortSignal.timeout` ceiling. */ +export const CODEX_REFRESH_FLIGHT_CEILING_MS = 30_000; + +/** Deadline for one `backend-api/wham/usage` request. */ +export const WHAM_REQUEST_TIMEOUT_MS = 8_000; + +/** + * How long a recovery claim stays valid without settlement. + * + * The sequence a claim covers is: WHAM request → refresh → WHAM replay. Both quota legs sit + * inside the lease, which is why the request timeout is counted twice. + */ +export const QUOTA_RECOVERY_LEASE_MS = + CODEX_REFRESH_FLIGHT_CEILING_MS + WHAM_REQUEST_TIMEOUT_MS * 2; diff --git a/src/lib/state-store-registrations.ts b/src/lib/state-store-registrations.ts index 61aad5f7b9..433a8989b4 100644 --- a/src/lib/state-store-registrations.ts +++ b/src/lib/state-store-registrations.ts @@ -9,6 +9,7 @@ import { reconcileProviderFetchWarnings } from "../codex/catalog/provider-fetch" import { reconcileModelCacheGeneration } from "../codex/model-cache"; import { reconcilePoolRotationState } from "../codex/pool-rotation"; import { reconcileCodexQuotaAccounts } from "../codex/quota"; +import { reconcileQuotaRecovery, sweepExpiredQuotaRecovery } from "../codex/quota-401-recovery"; import { listLiveCodexAccountIds, reconcileCodexRoutingHealth, @@ -84,6 +85,13 @@ export const STATE_STORE_REGISTRATIONS = [ }, { name: "anthropic-routing-health", sweepExpired: sweepExpiredAnthropicRoutingHealth }, { name: "xai-refresh-verdicts", sweepExpired: sweepExpiredXaiPermanentFailureVerdicts }, + { + name: "codex-quota-401-recovery", + // Only backoff windows and abandoned leases expire. A spent fence is durable: expiring + // it would grant the same credential lineage a second refresh (#3019). + sweepExpired: sweepExpiredQuotaRecovery, + reconcileGeneration: context => reconcileQuotaRecovery(context.codexAccountIds), + }, { name: "responses-continuation", sweepExpired: sweepExpiredResponseStates, diff --git a/tests/quota-401-recovery-runtime.test.ts b/tests/quota-401-recovery-runtime.test.ts new file mode 100644 index 0000000000..82cd83b251 --- /dev/null +++ b/tests/quota-401-recovery-runtime.test.ts @@ -0,0 +1,386 @@ +import { afterEach, beforeEach, expect, test } from "bun:test"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +/** + * Runtime behaviour of the WHAM 401 recovery (#3019). + * + * tests/quota-401-recovery.test.ts exercises the budget store in isolation, and every case + * there stays green if the recovery is never wired into the quota path at all. These drive + * the real primitive and the real store together: a refresh that actually happens, a + * settlement that survives caller cancellation, and provenance that comes from the flight + * rather than from the adoption site. + */ + +const REJECTED = "rejected-bearer"; +const ROTATED = "rotated-bearer"; +let home: string; +let previousHome: string | undefined; +let originalFetch: typeof globalThis.fetch; + +beforeEach(async () => { + previousHome = process.env.OPENCODEX_HOME; + home = mkdtempSync(join(tmpdir(), "ocx-quota-401-")); + process.env.OPENCODEX_HOME = home; + originalFetch = globalThis.fetch; + const { resetQuotaRecoveryForTests } = await import("../src/codex/quota-401-recovery"); + resetQuotaRecoveryForTests(); +}); + +afterEach(async () => { + globalThis.fetch = originalFetch; + if (previousHome === undefined) delete process.env.OPENCODEX_HOME; + else process.env.OPENCODEX_HOME = previousHome; + rmSync(home, { recursive: true, force: true }); + const { resetQuotaRecoveryForTests } = await import("../src/codex/quota-401-recovery"); + resetQuotaRecoveryForTests(); +}); + +async function seedAccount(id: string): Promise<number> { + const { readCodexAccountRecord, saveCodexAccountCredential } = await import("../src/codex/account-store"); + saveCodexAccountCredential(id, { + accessToken: REJECTED, + refreshToken: "grant", + expiresAt: Date.now() + 3600_000, + chatgptAccountId: "acc", + }); + return readCodexAccountRecord(id)!.generation; +} + +/** Register the account in config too, so the account-list API actually returns a row. */ +async function seedListedAccount(id: string): Promise<number> { + const generation = await seedAccount(id); + const { loadConfig, saveConfig } = await import("../src/config"); + const config = loadConfig(); + const accounts = [...(config.codexAccounts ?? []).filter(a => a.id !== id), { id, label: id }]; + saveConfig({ ...config, codexAccounts: accounts }); + return generation; +} + +test("a refresh settles the budget even when the caller cancels first", async () => { + const { forceRefreshCodexPoolToken } = await import("../src/codex/account-store"); + const { claimQuotaRecovery, quotaRecoveryRecordForTests, settleQuotaRecovery, releaseQuotaRecovery } = + await import("../src/codex/quota-401-recovery"); + const generation = await seedAccount("cancelled-owner"); + + let released = 0; + globalThis.fetch = (async () => { + await new Promise(resolve => setTimeout(resolve, 30)); + return Response.json({ access_token: ROTATED, refresh_token: "grant2", expires_in: 3600 }); + }) as typeof fetch; + + const claim = claimQuotaRecovery("cancelled-owner", generation); + if (!claim.granted) throw new Error("expected a claim"); + const controller = new AbortController(); + let callerRejected: unknown; + const settled = new Promise<void>(resolve => { + void forceRefreshCodexPoolToken("cancelled-owner", { + rejectedGeneration: generation, + rejectedAccessToken: REJECTED, + signal: controller.signal, + onSettled: outcome => { + if (outcome.kind === "resolved") settleQuotaRecovery("cancelled-owner", claim.claimId, outcome); + else { released += 1; releaseQuotaRecovery("cancelled-owner", claim.claimId, 60_000); } + resolve(); + }, + }).then( + () => { callerRejected = "resolved"; }, + error => { callerRejected = error; }, + ); + }); + + // Cancel while the token request is still in flight. The shared refresh keeps running and + // commits; settling from the cancelled await would report "failed", release the budget, + // and let the freshly refreshed lineage claim again moments later. + const abortReason = new Error("caller went away"); + controller.abort(abortReason); + await settled; + + // The caller really was cancelled — otherwise this test would also pass against an + // implementation that simply ignores the signal. + expect(callerRejected).toBe(abortReason); + expect(released).toBe(0); + expect(quotaRecoveryRecordForTests("cancelled-owner")).toEqual({ state: "spent", lineage: generation + 1 }); +}); + +test("exactly one of two callers on a shared flight performed the CAS", async () => { + const { forceRefreshCodexPoolToken } = await import("../src/codex/account-store"); + const generation = await seedAccount("adopting-joiner"); + + globalThis.fetch = (async () => { + await new Promise(resolve => setTimeout(resolve, 20)); + return Response.json({ access_token: ROTATED, refresh_token: "grant2", expires_in: 3600 }); + }) as typeof fetch; + + const outcomes: string[] = []; + await Promise.allSettled([ + forceRefreshCodexPoolToken("adopting-joiner", { + rejectedGeneration: generation, + rejectedAccessToken: REJECTED, + onSettled: o => { if (o.kind === "resolved") outcomes.push(o.provenance); }, + }), + forceRefreshCodexPoolToken("adopting-joiner", { + rejectedGeneration: generation, + rejectedAccessToken: REJECTED, + onSettled: o => { if (o.kind === "resolved") outcomes.push(o.provenance); }, + }), + ]); + + // Only one caller can have moved the credential. Accepting "any of the three enum + // values" would pass against the bug where a joiner copies the flight's own + // `self-refresh` and reports a CAS it never performed. + expect(outcomes).toHaveLength(2); + expect(outcomes.filter(p => p === "self-refresh")).toHaveLength(1); + // Specifically joined-lineage: an "external-replacement" joiner is also non-self, and + // would wrongly leave this lineage's budget unspent. + expect(outcomes.filter(p => p === "joined-lineage")).toHaveLength(1); +}); + +test("a revoked grant routes to terminal settlement, a slow one does not", async () => { + const { forceRefreshCodexPoolToken } = await import("../src/codex/account-store"); + const { claimQuotaRecovery, quotaRecoveryRecordForTests } = await import("../src/codex/quota-401-recovery"); + + // Drive the real primitive to a real refresh failure and route it exactly the way + // recoverPoolQuotaFrom401 does, so a wiring that never calls terminal settlement fails. + const { TokenRefreshError } = await import("../src/codex/account-store"); + const { releaseQuotaRecovery, settleQuotaRecoveryTerminal, quotaRecoveryTerminalFor } = + await import("../src/codex/quota-401-recovery"); + const route = (accountId: string, claimId: string, error: unknown) => { + if (error instanceof TokenRefreshError && /invalid_grant|revoked|expired|invalid_refresh_token/i.test(String(error.message))) { + settleQuotaRecoveryTerminal(accountId, claimId); + } else { + releaseQuotaRecovery(accountId, claimId, 60_000); + } + }; + + const revokedGeneration = await seedAccount("dead-grant"); + globalThis.fetch = (async () => new Response("{\"error\":\"invalid_grant\"}", { status: 400 })) as typeof fetch; + const revokedClaim = claimQuotaRecovery("dead-grant", revokedGeneration); + if (!revokedClaim.granted) throw new Error("expected a claim"); + await forceRefreshCodexPoolToken("dead-grant", { + rejectedGeneration: revokedGeneration, + rejectedAccessToken: REJECTED, + onSettled: o => { if (o.kind === "failed") route("dead-grant", revokedClaim.claimId, o.error); }, + }).catch(() => { /* the refresh is supposed to fail */ }); + + expect(quotaRecoveryTerminalFor("dead-grant", revokedGeneration)).toBe(true); + expect(quotaRecoveryRecordForTests("dead-grant")).toMatchObject({ state: "spent", terminal: true }); + + const slowGeneration = await seedAccount("slow-grant"); + globalThis.fetch = (async () => { throw new Error("socket hang up"); }) as typeof fetch; + const slowClaim = claimQuotaRecovery("slow-grant", slowGeneration); + if (!slowClaim.granted) throw new Error("expected a claim"); + await forceRefreshCodexPoolToken("slow-grant", { + rejectedGeneration: slowGeneration, + rejectedAccessToken: REJECTED, + onSettled: o => { if (o.kind === "failed") route("slow-grant", slowClaim.claimId, o.error); }, + }).catch(() => { /* transient by design */ }); + + // A network failure proves nothing about the grant, so it must not fence the account. + expect(quotaRecoveryTerminalFor("slow-grant", slowGeneration)).toBe(false); + expect(quotaRecoveryRecordForTests("slow-grant")).toMatchObject({ state: "backoff" }); +}); + +test("a revoked grant stays needs-reauth across polls, through the real quota path", async () => { + const { listCodexAuthAccounts } = await import("../src/codex/auth-api"); + const { loadConfig } = await import("../src/config"); + await seedListedAccount("dead-grant"); + + // Bare WHAM 401 -> token endpoint says invalid_grant -> the account list re-polls. + // Routing this through listCodexAuthAccounts is the point: a local reimplementation of + // the settlement callback stays green with the production wiring deleted. + let tokenCalls = 0; + globalThis.fetch = (async (input: string | URL | Request) => { + const url = String(input); + if (url.includes("/wham/usage")) return new Response("{}", { status: 401 }); + tokenCalls += 1; + return new Response(JSON.stringify({ error: "invalid_grant" }), { status: 400 }); + }) as typeof fetch; + + const config = loadConfig(); + const first = await listCodexAuthAccounts(config, true); + expect(first.find(row => row.id === "dead-grant")?.needsReauth).toBe(true); + + // The evidence has to be DURABLE, not just present in the response that discovered it. + // The recovery record alone cannot carry it: by the time the next poll runs, the claim is + // spent and a spent budget reports transient. Assert the account-level mark directly, so + // dropping markAccountNeedsReauth fails here rather than being masked by a cached quota. + const { isAccountNeedsReauth } = await import("../src/codex/auth-api"); + expect(isAccountNeedsReauth("dead-grant")).toBe(true); + + const second = await listCodexAuthAccounts(config, true); + expect(second.find(row => row.id === "dead-grant")?.needsReauth).toBe(true); + // And the dead grant is not retried on every poll. + expect(tokenCalls).toBe(1); +}); + +test("a transient refresh failure does not quarantine the account", async () => { + const { listCodexAuthAccounts } = await import("../src/codex/auth-api"); + const { loadConfig } = await import("../src/config"); + const { quotaRecoveryRecordForTests } = await import("../src/codex/quota-401-recovery"); + await seedListedAccount("slow-grant"); + + globalThis.fetch = (async (input: string | URL | Request) => { + if (String(input).includes("/wham/usage")) return new Response("{}", { status: 401 }); + throw new Error("socket hang up"); + }) as typeof fetch; + + const rows = await listCodexAuthAccounts(loadConfig(), true); + // A network failure proves nothing about the grant. + expect(rows.find(row => row.id === "slow-grant")?.needsReauth).toBe(false); + // Nor may it leave a durable quarantine behind. + const { isAccountNeedsReauth } = await import("../src/codex/auth-api"); + expect(isAccountNeedsReauth("slow-grant")).toBe(false); + expect(quotaRecoveryRecordForTests("slow-grant")).toMatchObject({ state: "backoff" }); +}); + +test("an already-aborted caller starts no refresh at all", async () => { + const { forceRefreshCodexPoolToken } = await import("../src/codex/account-store"); + const { claimQuotaRecovery, quotaRecoveryRecordForTests, releaseQuotaRecovery, settleQuotaRecovery } = + await import("../src/codex/quota-401-recovery"); + const generation = await seedAccount("pre-aborted"); + + let fetches = 0; + globalThis.fetch = (async () => { + fetches += 1; + return Response.json({ access_token: ROTATED, refresh_token: "grant2", expires_in: 3600 }); + }) as typeof fetch; + + const claim = claimQuotaRecovery("pre-aborted", generation); + if (!claim.granted) throw new Error("expected a claim"); + const reason = new Error("caller already gone"); + const controller = new AbortController(); + controller.abort(reason); + + let failures = 0; + let rejected: unknown; + await forceRefreshCodexPoolToken("pre-aborted", { + rejectedGeneration: generation, + rejectedAccessToken: REJECTED, + signal: controller.signal, + onSettled: outcome => { + if (outcome.kind === "failed") { failures += 1; releaseQuotaRecovery("pre-aborted", claim.claimId, 60_000); } + else settleQuotaRecovery("pre-aborted", claim.claimId, outcome); + }, + }).catch(error => { rejected = error; }); + + // Settlement runs on an uncancelled completion, which bypasses resolveCodexToken's own + // pre-abort guard — so without an explicit check here a caller that is already gone + // would rotate a credential nobody is waiting for. + expect(fetches).toBe(0); + expect(rejected).toBe(reason); + expect(failures).toBe(1); + // And the claim is not left held: it was released, not abandoned to its lease. + expect(quotaRecoveryRecordForTests("pre-aborted")).toMatchObject({ state: "backoff" }); +}); + +test("structured terminal evidence on the FIRST 401 needs no refresh and is generation-scoped", async () => { + const { listCodexAuthAccounts, isAccountNeedsReauth } = await import("../src/codex/auth-api"); + const { loadConfig } = await import("../src/config"); + const generation = await seedListedAccount("structured-first"); + + let tokenCalls = 0; + globalThis.fetch = (async (input: string | URL | Request) => { + if (String(input).includes("/wham/usage")) { + return new Response(JSON.stringify({ detail: { code: "invalid_refresh_token" } }), { status: 401 }); + } + tokenCalls += 1; + return Response.json({ access_token: ROTATED, refresh_token: "grant2", expires_in: 3600 }); + }) as typeof fetch; + + const rows = await listCodexAuthAccounts(loadConfig(), true); + expect(rows.find(row => row.id === "structured-first")?.needsReauth).toBe(true); + // A body that says the grant is dead needs no refresh to prove it. + expect(tokenCalls).toBe(0); + expect(isAccountNeedsReauth("structured-first")).toBe(true); + + // The evidence is about THAT credential. A replacement must not inherit the quarantine. + const { saveCodexAccountCredential } = await import("../src/codex/account-store"); + saveCodexAccountCredential("structured-first", { + accessToken: "fresh-login", + refreshToken: "fresh-grant", + expiresAt: Date.now() + 3600_000, + chatgptAccountId: "acc", + }); + expect(isAccountNeedsReauth("structured-first")).toBe(false); + void generation; +}); + +test("structured terminal evidence on the REPLAY is scoped to the refreshed credential", async () => { + const { listCodexAuthAccounts, isAccountNeedsReauth } = await import("../src/codex/auth-api"); + const { loadConfig } = await import("../src/config"); + await seedListedAccount("structured-replay"); + + let whamCalls = 0; + globalThis.fetch = (async (input: string | URL | Request) => { + if (String(input).includes("/wham/usage")) { + whamCalls += 1; + // Bare first, structured-terminal on the replay: only the second answer proves death. + return whamCalls === 1 + ? new Response("{}", { status: 401 }) + : new Response(JSON.stringify({ detail: { code: "invalid_refresh_token" } }), { status: 401 }); + } + return Response.json({ access_token: ROTATED, refresh_token: "grant2", expires_in: 3600 }); + }) as typeof fetch; + + const rows = await listCodexAuthAccounts(loadConfig(), true); + expect(whamCalls).toBe(2); + expect(rows.find(row => row.id === "structured-replay")?.needsReauth).toBe(true); + expect(isAccountNeedsReauth("structured-replay")).toBe(true); + + const { saveCodexAccountCredential } = await import("../src/codex/account-store"); + saveCodexAccountCredential("structured-replay", { + accessToken: "fresh-login", + refreshToken: "fresh-grant", + expiresAt: Date.now() + 3600_000, + chatgptAccountId: "acc", + }); + expect(isAccountNeedsReauth("structured-replay")).toBe(false); +}); + +test("no bearer reaches the console, the debug buffer, or a serialized account row", async () => { + const { forceRefreshCodexPoolToken } = await import("../src/codex/account-store"); + const { getDebugLogEntries } = await import("../src/lib/debug-log-buffer"); + // Registered, not just credentialed: an unlisted account produces no row at all, and the + // serializer assertion below would then be checking an empty list. + const generation = await seedListedAccount("quiet-refresh"); + globalThis.fetch = (async () => + Response.json({ access_token: ROTATED, refresh_token: "grant2", expires_in: 3600 })) as typeof fetch; + + const before = getDebugLogEntries({ limit: 500 }).length; + const captured: string[] = []; + const originals = { log: console.log, warn: console.warn, error: console.error, debug: console.debug }; + const capture = (...args: unknown[]) => { captured.push(args.map(String).join(" ")); }; + console.log = capture; console.warn = capture; console.error = capture; console.debug = capture; + let result: Awaited<ReturnType<typeof forceRefreshCodexPoolToken>>; + try { + result = await forceRefreshCodexPoolToken("quiet-refresh", { + rejectedGeneration: generation, + rejectedAccessToken: REJECTED, + }); + } finally { + console.log = originals.log; console.warn = originals.warn; + console.error = originals.error; console.debug = originals.debug; + } + // The refresh really happened — otherwise this asserts silence about nothing. + expect(result.accessToken).toBe(ROTATED); + expect(result.rotated).toBe(true); + + const secrets = [REJECTED, ROTATED, "grant2", "grant"]; + // privacy:scan is static and cannot see what a runtime path actually emits. + const transcript = captured.join("\n"); + for (const secret of secrets) expect(transcript).not.toContain(secret); + + // console is not the only sink: the dashboard reads this buffer over the management API. + const debugText = JSON.stringify(getDebugLogEntries({ after: before, limit: 500 })); + for (const secret of secrets) expect(debugText).not.toContain(secret); + + // And the REAL serializer, not a hand-built stand-in: a leak has to be caught in what + // the management API actually returns. + const { listCodexAuthAccounts } = await import("../src/codex/auth-api"); + const { loadConfig } = await import("../src/config"); + const rows = JSON.stringify(await listCodexAuthAccounts(loadConfig(), false)); + expect(rows).toContain("quiet-refresh"); + for (const secret of secrets) expect(rows).not.toContain(secret); +}); diff --git a/tests/quota-401-recovery.test.ts b/tests/quota-401-recovery.test.ts new file mode 100644 index 0000000000..bf49cb46b6 --- /dev/null +++ b/tests/quota-401-recovery.test.ts @@ -0,0 +1,183 @@ +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { + claimQuotaRecovery, + quotaRecoveryRecordForTests, + releaseQuotaRecovery, + resetQuotaRecoveryForTests, + settleQuotaRecovery, + sweepExpiredQuotaRecovery, +} from "../src/codex/quota-401-recovery"; +import { + CODEX_REFRESH_FLIGHT_CEILING_MS, + QUOTA_RECOVERY_LEASE_MS, + WHAM_REQUEST_TIMEOUT_MS, +} from "../src/codex/quota-recovery-timing"; + +/** + * The budget that keeps a WHAM 401 recovery from becoming a retry loop (#3019). + * + * These call the store directly. The rule they encode — one refresh per credential lineage, + * and a stale completion may never spend somebody else's claim — is not observable from the + * quota path's source text, which is where earlier attempts at this went wrong. + */ + +const ACCOUNT = "acct-1"; + +beforeEach(() => resetQuotaRecoveryForTests()); +afterEach(() => resetQuotaRecoveryForTests()); + +describe("claim identity", () => { + test("a claim is granted once per lineage and refused after it is spent", () => { + const first = claimQuotaRecovery(ACCOUNT, 7); + expect(first.granted).toBe(true); + if (!first.granted) return; + settleQuotaRecovery(ACCOUNT, first.claimId, { provenance: "self-refresh", generation: 8 }); + + // The refresh moved 7 -> 8, so 8 is what is fenced: a successful token response can + // rotate only the refresh grant, so the rejected generation is already stale. + expect(quotaRecoveryRecordForTests(ACCOUNT)).toEqual({ state: "spent", lineage: 8 }); + expect(claimQuotaRecovery(ACCOUNT, 8)).toEqual({ granted: false, reason: "spent" }); + }); + + test("a live claim blocks every other lineage, not just its own", () => { + const held = claimQuotaRecovery(ACCOUNT, 7); + expect(held.granted).toBe(true); + // The refresh this claim fences commits 7 -> 8 BEFORE it can settle. Granting an 8 + // claim in that window would leave the late settlement landing on nothing, and 8 + // unspent (#3019). + expect(claimQuotaRecovery(ACCOUNT, 8)).toEqual({ granted: false, reason: "in-flight" }); + expect(claimQuotaRecovery(ACCOUNT, 7)).toEqual({ granted: false, reason: "in-flight" }); + }); + + test("a stale settlement cannot spend a later claimant's budget", () => { + const first = claimQuotaRecovery(ACCOUNT, 7); + expect(first.granted).toBe(true); + if (!first.granted) return; + releaseQuotaRecovery(ACCOUNT, first.claimId, 1_000); + + const second = claimQuotaRecovery(ACCOUNT, 7, Date.now() + 2_000); + expect(second.granted).toBe(true); + if (!second.granted) return; + expect(second.claimId).not.toBe(first.claimId); + + // The first caller finally completes. Lineage alone could not tell it from the second + // claimant, and it would have spent a budget it no longer owns. + settleQuotaRecovery(ACCOUNT, first.claimId, { provenance: "self-refresh", generation: 8 }); + expect(quotaRecoveryRecordForTests(ACCOUNT)).toMatchObject({ state: "claimed", claimId: second.claimId }); + }); + + test("settling or releasing without a claim is a no-op", () => { + settleQuotaRecovery(ACCOUNT, "never-issued", { provenance: "self-refresh", generation: 8 }); + expect(quotaRecoveryRecordForTests(ACCOUNT)).toBeUndefined(); + releaseQuotaRecovery(ACCOUNT, "never-issued", 1_000); + expect(quotaRecoveryRecordForTests(ACCOUNT)).toBeUndefined(); + }); +}); + +describe("settlement by outcome", () => { + test("a joined lineage spends the returned generation, like a self-refresh", () => { + const claim = claimQuotaRecovery(ACCOUNT, 7); + if (!claim.granted) throw new Error("expected a claim"); + // Joining an in-flight refresh of the same grant IS this lineage's one attempt. + settleQuotaRecovery(ACCOUNT, claim.claimId, { provenance: "joined-lineage", generation: 8 }); + expect(quotaRecoveryRecordForTests(ACCOUNT)).toEqual({ state: "spent", lineage: 8 }); + }); + + test("an external replacement spends the OLD lineage and leaves the new one free", () => { + const claim = claimQuotaRecovery(ACCOUNT, 7); + if (!claim.granted) throw new Error("expected a claim"); + settleQuotaRecovery(ACCOUNT, claim.claimId, { provenance: "external-replacement", generation: 9 }); + + // 7 used its attempt. 9 is somebody else's fresh grant and has had none — fencing it + // would deny the new credential the recovery this exists to grant. + expect(quotaRecoveryRecordForTests(ACCOUNT)).toEqual({ state: "spent", lineage: 7 }); + expect(claimQuotaRecovery(ACCOUNT, 9).granted).toBe(true); + }); + + test("external replacement with an unrotated token still frees the returned lineage", () => { + const claim = claimQuotaRecovery(ACCOUNT, 7); + if (!claim.granted) throw new Error("expected a claim"); + // rotated:false means the caller does not replay; it does not change who owes what. + settleQuotaRecovery(ACCOUNT, claim.claimId, { provenance: "external-replacement", generation: 9 }); + expect(quotaRecoveryRecordForTests(ACCOUNT)).toEqual({ state: "spent", lineage: 7 }); + expect(claimQuotaRecovery(ACCOUNT, 9).granted).toBe(true); + }); +}); + +describe("failure handling", () => { + test("a transient failure backs off instead of restoring eligibility", () => { + const claim = claimQuotaRecovery(ACCOUNT, 7); + if (!claim.granted) throw new Error("expected a claim"); + const now = Date.now(); + releaseQuotaRecovery(ACCOUNT, claim.claimId, 60_000, now); + + // A failed quota request does not refresh the quota timestamp, so immediate polls would + // otherwise each issue another token refresh — the loop, from the other side. + expect(claimQuotaRecovery(ACCOUNT, 7, now + 1)).toEqual({ granted: false, reason: "backoff" }); + expect(claimQuotaRecovery(ACCOUNT, 7, now + 60_001).granted).toBe(true); + }); + + test("a new lineage is not held by the old lineage's backoff", () => { + const claim = claimQuotaRecovery(ACCOUNT, 7); + if (!claim.granted) throw new Error("expected a claim"); + const now = Date.now(); + releaseQuotaRecovery(ACCOUNT, claim.claimId, 60_000, now); + // A replacement credential has had no attempt and no failure of its own. + expect(claimQuotaRecovery(ACCOUNT, 8, now + 1).granted).toBe(true); + }); +}); + +describe("lease and sweep", () => { + test("an abandoned lease is reclaimable but never becomes spent", () => { + const claim = claimQuotaRecovery(ACCOUNT, 7); + if (!claim.granted) throw new Error("expected a claim"); + const expired = Date.now() + QUOTA_RECOVERY_LEASE_MS + 1; + + // The caller died between claim and settle. The refresh may never have happened, so + // promoting this to spent would deny a real attempt. + expect(claimQuotaRecovery(ACCOUNT, 7, expired).granted).toBe(true); + expect(quotaRecoveryRecordForTests(ACCOUNT)?.state).toBe("claimed"); + }); + + test("the sweep drops leases and backoff but never a spent fence", () => { + const claim = claimQuotaRecovery(ACCOUNT, 7); + if (!claim.granted) throw new Error("expected a claim"); + settleQuotaRecovery(ACCOUNT, claim.claimId, { provenance: "self-refresh", generation: 8 }); + // Expiring a spent record would hand the same lineage another refresh. + expect(sweepExpiredQuotaRecovery(Date.now() + 10 * QUOTA_RECOVERY_LEASE_MS)).toBe(0); + expect(quotaRecoveryRecordForTests(ACCOUNT)).toEqual({ state: "spent", lineage: 8 }); + + resetQuotaRecoveryForTests(); + const stale = claimQuotaRecovery("acct-2", 3); + if (!stale.granted) throw new Error("expected a claim"); + expect(sweepExpiredQuotaRecovery(Date.now() + QUOTA_RECOVERY_LEASE_MS + 1)).toBe(1); + expect(quotaRecoveryRecordForTests("acct-2")).toBeUndefined(); + }); + + test("one record per account, so lineage churn cannot accumulate", () => { + // Each cycle: a lineage takes a 401, refreshes once, and something external then moves + // the credential on — a login, another lane's refresh. The record is REPLACED, not + // appended, so 25 rounds leave exactly one row. + let lineage = 1; + for (let round = 0; round < 25; round += 1) { + const claim = claimQuotaRecovery(ACCOUNT, lineage); + if (!claim.granted) throw new Error(`expected a claim for lineage ${lineage}`); + const refreshedTo = lineage + 1; + settleQuotaRecovery(ACCOUNT, claim.claimId, { provenance: "self-refresh", generation: refreshedTo }); + expect(quotaRecoveryRecordForTests(ACCOUNT)).toEqual({ state: "spent", lineage: refreshedTo }); + // That lineage has spent its one attempt, and stays refused however long this runs. + expect(claimQuotaRecovery(ACCOUNT, refreshedTo)).toEqual({ granted: false, reason: "spent" }); + lineage = refreshedTo + 1; + } + }); +}); + +describe("the lease is derived from what it covers", () => { + test("it outlasts the longest admitted flight plus both quota legs", () => { + // Derived from the same constants the refresh and the WHAM request use, not restated: + // a restated number is how a lease drifts shorter than the flight it fences, and a + // lease that expires mid-refresh admits a second claim for a lineage already refreshing. + expect(QUOTA_RECOVERY_LEASE_MS).toBeGreaterThan(CODEX_REFRESH_FLIGHT_CEILING_MS + WHAM_REQUEST_TIMEOUT_MS); + expect(QUOTA_RECOVERY_LEASE_MS).toBe(CODEX_REFRESH_FLIGHT_CEILING_MS + WHAM_REQUEST_TIMEOUT_MS * 2); + }); +}); diff --git a/tests/state-store-sweeper.test.ts b/tests/state-store-sweeper.test.ts index ea2d473b27..3bcfb882b7 100644 --- a/tests/state-store-sweeper.test.ts +++ b/tests/state-store-sweeper.test.ts @@ -101,6 +101,10 @@ describe("state-store sweeper", () => { "combo-target-cooldowns", "anthropic-routing-health", "xai-refresh-verdicts", + // #3019: the WHAM 401 recovery budget. Registered here deliberately — the inventory + // is hand-maintained so a new store cannot be added without someone deciding it has + // an owner and a sweep policy. + "codex-quota-401-recovery", "responses-continuation", "antigravity-replay", "config-warning-memos", From 6d036876fe19341a989a6014b96bddf6882e7952 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 09:29:19 +0900 Subject: [PATCH 39/42] docs(devlog): rescan the bug backlog after wp5 and wp6 (#3120) --- .../260831_prio70_train_round2/070_rescan.md | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 devlog/_plan/260831_prio70_train_round2/070_rescan.md diff --git a/devlog/_plan/260831_prio70_train_round2/070_rescan.md b/devlog/_plan/260831_prio70_train_round2/070_rescan.md new file mode 100644 index 0000000000..439a0d7079 --- /dev/null +++ b/devlog/_plan/260831_prio70_train_round2/070_rescan.md @@ -0,0 +1,50 @@ +# 070 — round-2 rescan after wp5 and wp6 + +Scanned at `dev` 330470e74 plus PR #3119 in flight. Every open `bug`-labelled issue scored +on the same four-axis 0-80 rubric the train has used: blast radius, data/credential/ +durability risk, reproducibility and evidence quality, shippability. + +## Closed by this train + +| issue | phase | landed | +| --- | --- | --- | +| #3071 | wp1 | PR #3089 | +| #3032 | wp2 | PR #3097 | +| #3026 | wp3 | PR #3103 | +| #3029 | wp4 | PR #3110 | +| #3008 | wp5 | PR #3118 | +| #3019 | wp6 | PR #3119 (open) | + +## Remaining open bug issues + +| issue | blast | risk | evidence | ship | total | note | +| --- | --- | --- | --- | --- | --- | --- | +| #3024 catalog drops a callable configured model | 18 | 12 | 19 | 19 | **68** | Verified in source: `isDatedVariantId` at `provider-fetch.ts:939` accepts only `\d{8}`, and the fold at `:1672` is one-directional | +| #3094 `ocx models new-policy` / `new-arrivals` unreachable | 14 | 6 | 20 | 20 | **60** | Verified: the dispatch allowlist at `models.ts:448` omits both names, so they fall through | +| #2999 native-main refresh can overwrite external Codex writers | 15 | 19 | 17 | 12 | **63** | Credential durability, but the fix is a coordination protocol change, not a patch | +| #3108 combo default reasoning effort arrives as `none` | 15 | 6 | 17 | 17 | **55** | | +| #3051 Cursor discovery fails on HTTP/2 pre-header EOF | 12 | 5 | 17 | 18 | **52** | Retry classification only | +| #3009 Windows service repair fails at a fixed 20s | 12 | 8 | 16 | 15 | **51** | | +| #3064 Windows non-ASCII profile path rolls back install | 10 | 8 | 18 | 14 | **50** | Narrow to non-ASCII profile names | +| #3021 encrypted subagent payload surfaced as ciphertext | 10 | 12 | 13 | 12 | **47** | Reporter withheld the payload, correctly; hard to reproduce | +| #3070 OpenAI usage decreases with custom providers configured | 14 | 10 | 10 | 10 | **44** | Needs instrumentation before a fix can be named | +| #3059 restore dialog loses focus | 8 | 3 | 18 | 18 | **47** | Accessibility, single dialog | +| #2813 Codex reserve mode hides routed models | 12 | 4 | 14 | 8 | **38** | Upstream client behaviour | + +## Verdict + +**No open bug issue scores >= 70.** #3024 is the highest at 68 and the closest call: it is +verified in source and cheap to fix, but a configured model dropping out of the catalog +degrades a listing rather than risking credentials or durability, and the model stays +callable by id. It does not clear the bar this train set at wp0. + +The four remaining >= 50 items (#3024, #2999, #3094, #3108) are the natural next train if +the bar is lowered, in that order — #3024 and #3094 are both source-verified and small, +#2999 is the only remaining credential-durability item, and #3108 is a routing correctness +bug with a clean reproduction. + +## Bug-labelled PRs + +None open that this train has not already superseded. #3020's core sequence was carried +into wp6 with credit rather than rebased, per the wp6 plan. + From b6e53d8eb2191d818e6014f0049cda264e2cfa0c Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 09:37:09 +0900 Subject: [PATCH 40/42] fix(gui): give restore focus somewhere to land when its trigger is gone (#3113) * fix(gui): give restore focus somewhere to land when its trigger is gone The reporter's diagnosis is wrong and their experience is real, which is why this is a fix rather than a close. What #3059 describes -- onRestored() -> refresh() clears status, if (!status) unmounts the page -- cannot happen. refresh() keeps cached data: runFetch only shows loading when data === undefined or forceLoading is set (gui/src/client-resource.ts:339-341), and FileIntegrationPage's restore passes neither, so useDataSurface classifies it loading-with-stale-data and the if (!status) branch at :175 is cold-load only. The focus failure underneath it is real, and RestoreDialog said so itself: // The row's button is gone from the DOM in the collapsed case, so this is // a best effort: focus returns only if the trigger survived the close. A restore that consumes its snapshot re-renders the row as an expired badge with no button (RollbackHistory.tsx:44-46), so the remembered element is detached by cleanup time. Calling .focus() on a detached node succeeds silently and focus stays on <body> -- a keyboard user is dropped at the top of the document with nothing announced. The dialog now also remembers the enclosing region and falls back to it when the trigger did not survive, made programmatically focusable with tabindex=-1 so it never joins the Tab order. isConnected is the load-bearing check. Mutation-checked: collapsing the cleanup back to trigger?.focus?.() fails exactly the new region test (33 pass / 1 fail), restored to 34/0. Closes #3059. * fix(gui): retain restore focus through history refresh --- gui/src/pages/integrations/RestoreDialog.tsx | 37 ++++++- gui/tests/integrations-surfaces.test.tsx | 110 +++++++++++++++++++ 2 files changed, 144 insertions(+), 3 deletions(-) diff --git a/gui/src/pages/integrations/RestoreDialog.tsx b/gui/src/pages/integrations/RestoreDialog.tsx index 19fe62fff5..08d676f9a7 100644 --- a/gui/src/pages/integrations/RestoreDialog.tsx +++ b/gui/src/pages/integrations/RestoreDialog.tsx @@ -32,6 +32,8 @@ export default function RestoreDialog({ const t = useT(); const dialogRef = useRef<HTMLDialogElement>(null); const restoreFocusRef = useRef<HTMLElement | null>(null); + const restoreFallbackRef = useRef<HTMLElement | null>(null); + const restoredRef = useRef(false); const [drift, setDrift] = useState(false); const [pending, setPending] = useState(false); const [failure, setFailure] = useState<string | null>(null); @@ -43,12 +45,40 @@ export default function RestoreDialog({ // focus-restore uses the same tagName check. const active = document.activeElement; restoreFocusRef.current = active?.tagName === "BUTTON" ? active as HTMLElement : null; + // The trigger may not survive the asynchronous history refresh. A row whose + // snapshot is consumed re-renders as an `expired` badge with no button + // (RollbackHistory.tsx:44-46), so remember the enclosing region too: it + // remains a focus target after the trigger disappears (#3059). + restoreFallbackRef.current = + (active?.closest?.("section, [role='region'], main") as HTMLElement | null) ?? null; if (dialog && !dialog.open) dialog.showModal(); return () => { if (dialog?.open) dialog.close(); - // The row's button is gone from the DOM in the collapsed case, so this is - // a best effort: focus returns only if the trigger survived the close. - restoreFocusRef.current?.focus?.(); + const fallback = restoreFallbackRef.current; + // A successful restore starts the history refresh before it closes the + // dialog. The trigger is therefore still connected during this cleanup, + // but can disappear when the asynchronous refresh consumes its snapshot. + // Put successful restores on the stable region now; cancellation keeps + // the usual trigger restoration below. + if (restoredRef.current && fallback?.isConnected) { + // A region is not focusable by default; -1 makes it programmatically + // focusable without adding it to the Tab order. + if (!fallback.hasAttribute("tabindex")) fallback.setAttribute("tabindex", "-1"); + fallback.focus?.(); + return; + } + // Prefer the trigger when the user cancelled; fall back to its region + // only if it was already removed. `isConnected` is the check that + // matters: a detached node accepts .focus() silently and focus stays on + // <body>, which is the reported symptom. + const trigger = restoreFocusRef.current; + if (trigger?.isConnected) { + trigger.focus?.(); + return; + } + if (!fallback?.isConnected) return; + if (!fallback.hasAttribute("tabindex")) fallback.setAttribute("tabindex", "-1"); + fallback.focus?.(); }; }, []); @@ -63,6 +93,7 @@ export default function RestoreDialog({ setFailure(null); try { await restoreIntegration(apiBase, row.opId, drift); + restoredRef.current = true; onRestored(); onClose(); } catch (error) { diff --git a/gui/tests/integrations-surfaces.test.tsx b/gui/tests/integrations-surfaces.test.tsx index d392db27ff..2b9600af59 100644 --- a/gui/tests/integrations-surfaces.test.tsx +++ b/gui/tests/integrations-surfaces.test.tsx @@ -673,6 +673,116 @@ test("a drifted restore asks a second time instead of failing", async () => { expect((posts[1] as { confirmDrift?: boolean }).confirmDrift).toBe(true); }); +/** + * #3059: a successful restore starts an asynchronous history refresh before closing + * the dialog. That means normal focus restoration first finds the trigger still in + * the tree, and only later does the refresh consume its snapshot and remove the + * trigger. The region must receive focus on the successful close, before that later + * removal can send focus to <body>. + */ +test("a successful restore keeps focus on the stable region after refresh removes its trigger", async () => { + const [{ createRoot }, { LanguageProvider }, { default: RestoreDialog }] = await Promise.all([ + import("react-dom/client"), + import("../src/i18n/provider"), + import("../src/pages/integrations/RestoreDialog"), + ]); + + // The shape RollbackHistory renders: a stable region holding the row trigger. + const region = testWindow.document.createElement("section"); + const trigger = testWindow.document.createElement("button"); + region.appendChild(trigger); + testWindow.document.body.appendChild(region); + trigger.focus(); + expect(testWindow.document.activeElement).toBe(trigger); + + const row = { + opId: "op-consumed", + clientId: "hermes" as const, + kind: "apply" as const, + at: "2026-08-02T09:00:00.000Z", + configPath: "/tmp/home/.hermes/config.yaml", + snapshot: "stored" as const, + undoable: false, + }; + let resolveRestore: ((response: Response) => void) | undefined; + const restoreFetch = ((input: RequestInfo | URL) => { + if (String(input).includes("/restore")) { + return new Promise<Response>(resolve => { resolveRestore = resolve; }); + } + return Promise.resolve(json({ operations: [] })); + }) as typeof fetch; + Object.defineProperty(globalThis, "fetch", { configurable: true, value: restoreFetch }); + Object.defineProperty(testWindow, "fetch", { configurable: true, value: restoreFetch }); + + await act(async () => { + root = createRoot(container); + root.render( + <LanguageProvider> + <RestoreDialog + apiBase={apiBase} + row={row} + onRestored={() => {}} + onClose={() => { + root!.unmount(); + root = null; + }} + /> + </LanguageProvider>, + ); + }); + + await act(async () => { buttonByText("Restore")!.click(); }); + expect(resolveRestore).toBeDefined(); + + // Restore succeeds and closes while the trigger is still connected. + await act(async () => { resolveRestore!(json({ ok: true })); }); + expect(trigger.isConnected).toBe(true); + expect(testWindow.document.activeElement).toBe(region); + + // The asynchronous history refresh then consumes the snapshot and its trigger. + trigger.remove(); + expect(testWindow.document.activeElement).toBe(region); + expect(testWindow.document.activeElement).not.toBe(testWindow.document.body); + region.remove(); +}); + +test("focus returns to the trigger itself when it survived", async () => { + const [{ createRoot }, { LanguageProvider }, { default: RestoreDialog }] = await Promise.all([ + import("react-dom/client"), + import("../src/i18n/provider"), + import("../src/pages/integrations/RestoreDialog"), + ]); + + const region = testWindow.document.createElement("section"); + const trigger = testWindow.document.createElement("button"); + region.appendChild(trigger); + testWindow.document.body.appendChild(region); + trigger.focus(); + + const row = { + opId: "op-kept", + clientId: "hermes" as const, + kind: "apply" as const, + at: "2026-08-02T09:00:00.000Z", + configPath: "/tmp/home/.hermes/config.yaml", + snapshot: "stored" as const, + undoable: true, + }; + await act(async () => { + root = createRoot(container); + root.render( + <LanguageProvider> + <RestoreDialog apiBase={apiBase} row={row} onClose={() => {}} onRestored={() => {}} /> + </LanguageProvider>, + ); + }); + await act(async () => { root!.unmount(); root = null; }); + + // The fallback must not preempt a trigger that is still there. + expect(testWindow.document.activeElement).toBe(trigger); + region.remove(); +}); + test("a card toggles its own client without a trip to the sub-page", async () => { // Same rule as the client page: off means disable, for `stale` too. stateResponse = () => json({ clients: [status({ state: "stale" })] }); From 0db8066c01f0ab46b7d4ae3ca19d8848bba0cb91 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 09:49:47 +0900 Subject: [PATCH 41/42] feat(gui): let Logs be filtered by model or provider (#3115) * feat(gui): let Logs be filtered by model or provider #3070: an operator running custom providers watched their OpenAI monthly window shrink and could not find which turns were responsible. The window is the ChatGPT account's own, not a sum of proxy logs, so the only way to explain it is to isolate the rows that used an account-gated native model -- and the dashboard had no control that could. ?model= filtering landed for the CLI and the API in b68edc077, but that commit does not touch gui/src/pages/Logs.tsx. The page's one adjacent control, intercepted-helpers, keys on shadowCallRewrittenFrom, which a plain account-gated turn does not carry. So the rows that explain the bill were exactly the rows no filter could reach. A search field now matches model, resolvedModel and provider, case-insensitively, as a substring. resolvedModel is matched as well as model because they differ precisely when routing redirected the turn, which is the case worth finding: matching only the requested id would hide the redirect that caused the charge. The helper lives in its own module because Logs.tsx must export components only (react-refresh/only-export-components), which is the same reason logs-surface-filter.ts exists. Strings added to all nine locale catalogs; i18n-locales parity is green. * fix(gui): match failover targets in logs filter * fix(gui): ignore malformed log attempts --- gui/src/i18n/de.ts | 4 +- gui/src/i18n/en.ts | 4 +- gui/src/i18n/fr.ts | 4 +- gui/src/i18n/ja.ts | 4 +- gui/src/i18n/ko.ts | 4 +- gui/src/i18n/ru.ts | 4 +- gui/src/i18n/tr.ts | 4 +- gui/src/i18n/zh-TW.ts | 4 +- gui/src/i18n/zh.ts | 4 +- gui/src/pages/Logs.tsx | 14 ++++++ gui/src/pages/logs-model-filter.ts | 45 ++++++++++++++++++ gui/tests/logs-model-filter.test.ts | 74 +++++++++++++++++++++++++++++ 12 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 gui/src/pages/logs-model-filter.ts create mode 100644 gui/tests/logs-model-filter.test.ts diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts index 5278690349..61b46f2223 100644 --- a/gui/src/i18n/de.ts +++ b/gui/src/i18n/de.ts @@ -656,7 +656,9 @@ export const de: Record<TKey, string> = { "logs.badge.interceptedHelperTitle": "Abgefangene Helfer-Anfrage", "logs.filter.conversation.label": "Konversation", "logs.filter.conversation.placeholder": "Konversations-ID einfügen", - "logs.filter.conversation.clear": "Löschen", + "logs.filter.conversation.clear": "Löschen", + "logs.filter.model.label": "Modell", + "logs.filter.model.placeholder": "Modell oder Anbieter filtern", "logs.filter.conversation.apply": "Logs filtern", "logs.conversation.totals": "{requests} Anfragen · {tokens} Tokens · {cost}", "logs.conversation.scope": "Summen gelten nur für den aktuell geladenen Logs-Ring.", diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index ab6b652c69..fb25f211fb 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -689,7 +689,9 @@ export const en = { "logs.badge.interceptedHelperTitle": "Intercepted helper request", "logs.filter.conversation.label": "Conversation", "logs.filter.conversation.placeholder": "Paste conversation id", - "logs.filter.conversation.clear": "Clear", + "logs.filter.conversation.clear": "Clear", + "logs.filter.model.label": "Model", + "logs.filter.model.placeholder": "Filter by model or provider", "logs.filter.conversation.apply": "Filter logs", "logs.conversation.totals": "{requests} requests · {tokens} tokens · {cost}", "logs.conversation.scope": "Totals cover the currently loaded Logs ring only.", diff --git a/gui/src/i18n/fr.ts b/gui/src/i18n/fr.ts index 5b1809f394..16a6ab693d 100644 --- a/gui/src/i18n/fr.ts +++ b/gui/src/i18n/fr.ts @@ -670,7 +670,9 @@ export const fr: Record<TKey, string> = { "logs.badge.interceptedHelperTitle": "Requête d'assistant interceptée", "logs.filter.conversation.label": "Conversation", "logs.filter.conversation.placeholder": "Coller l’ID de conversation", - "logs.filter.conversation.clear": "Effacer", + "logs.filter.conversation.clear": "Effacer", + "logs.filter.model.label": "Modèle", + "logs.filter.model.placeholder": "Filtrer par modèle ou fournisseur", "logs.filter.conversation.apply": "Filtrer les journaux", "logs.conversation.totals": "{requests} requêtes · {tokens} jetons · {cost}", "logs.conversation.scope": "Les totaux couvrent uniquement le tampon circulaire des journaux actuellement chargé.", diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index d1bfb6dfcd..df16c0562e 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -632,7 +632,9 @@ export const ja: Record<TKey, string> = { "logs.badge.interceptedHelperTitle": "インターセプトされたヘルパー要求", "logs.filter.conversation.label": "会話", "logs.filter.conversation.placeholder": "会話 ID を貼り付け", - "logs.filter.conversation.clear": "クリア", + "logs.filter.conversation.clear": "クリア", + "logs.filter.model.label": "モデル", + "logs.filter.model.placeholder": "モデルまたはプロバイダーで絞り込む", "logs.filter.conversation.apply": "ログを絞り込み", "logs.conversation.totals": "{requests} 件 · {tokens} トークン · {cost}", "logs.conversation.scope": "合計は現在読み込まれている Logs リングのみです。", diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index 532b798018..dfa0803dd9 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -675,7 +675,9 @@ export const ko: Record<TKey, string> = { "logs.badge.interceptedHelperTitle": "가로챈 헬퍼 요청", "logs.filter.conversation.label": "대화", "logs.filter.conversation.placeholder": "대화 ID 붙여넣기", - "logs.filter.conversation.clear": "지우기", + "logs.filter.conversation.clear": "지우기", + "logs.filter.model.label": "모델", + "logs.filter.model.placeholder": "모델 또는 공급자로 거르기", "logs.filter.conversation.apply": "로그 필터", "logs.conversation.totals": "{requests}건 요청 · {tokens} 토큰 · {cost}", "logs.conversation.scope": "합계는 현재 로드된 Logs 링만 포함합니다.", diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts index 28f30646bd..36153cd22f 100644 --- a/gui/src/i18n/ru.ts +++ b/gui/src/i18n/ru.ts @@ -673,7 +673,9 @@ export const ru: Record<TKey, string> = { "logs.badge.interceptedHelperTitle": "Перехваченный запрос помощника", "logs.filter.conversation.label": "Диалог", "logs.filter.conversation.placeholder": "Вставьте ID диалога", - "logs.filter.conversation.clear": "Сбросить", + "logs.filter.conversation.clear": "Сбросить", + "logs.filter.model.label": "Модель", + "logs.filter.model.placeholder": "Фильтр по модели или провайдеру", "logs.filter.conversation.apply": "Фильтровать логи", "logs.conversation.totals": "{requests} запросов · {tokens} токенов · {cost}", "logs.conversation.scope": "Итоги только по загруженному кольцу Logs.", diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 232900f51e..f567faa70d 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -680,7 +680,9 @@ export const tr: Record<TKey, string> = { "logs.badge.interceptedHelperTitle": "Yakalanan yardimci istegi", "logs.filter.conversation.label": "Sohbet", "logs.filter.conversation.placeholder": "Sohbet ID'sini yapıştırın", - "logs.filter.conversation.clear": "Temizle", + "logs.filter.conversation.clear": "Temizle", + "logs.filter.model.label": "Model", + "logs.filter.model.placeholder": "Modele veya sağlayıcıya göre filtrele", "logs.filter.conversation.apply": "Günlükleri filtrele", "logs.conversation.totals": "{requests} istek · {tokens} jeton · {cost}", "logs.conversation.scope": "Toplamlar yalnızca yüklü günlükleri kapsar.", diff --git a/gui/src/i18n/zh-TW.ts b/gui/src/i18n/zh-TW.ts index b491a7bc94..616fa1044c 100644 --- a/gui/src/i18n/zh-TW.ts +++ b/gui/src/i18n/zh-TW.ts @@ -527,7 +527,9 @@ export const zhTW: Record<TKey, string> = { "logs.badge.interceptedHelperTitle": "已攔截的輔助請求", "logs.filter.conversation.label": "對話", "logs.filter.conversation.placeholder": "貼上對話 ID", - "logs.filter.conversation.clear": "清除", + "logs.filter.conversation.clear": "清除", + "logs.filter.model.label": "模型", + "logs.filter.model.placeholder": "依模型或供應商篩選", "logs.filter.conversation.apply": "篩選日誌", "logs.conversation.totals": "{requests} 次請求 · {tokens} tokens · {cost}", "logs.conversation.scope": "合計僅涵蓋目前已載入的 Logs 環形緩衝。", diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index 189a61fa20..733ce1728e 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -668,7 +668,9 @@ export const zh: Record<TKey, string> = { "logs.badge.interceptedHelperTitle": "已拦截的辅助请求", "logs.filter.conversation.label": "会话", "logs.filter.conversation.placeholder": "粘贴会话 ID", - "logs.filter.conversation.clear": "清除", + "logs.filter.conversation.clear": "清除", + "logs.filter.model.label": "模型", + "logs.filter.model.placeholder": "按模型或供应商筛选", "logs.filter.conversation.apply": "筛选日志", "logs.conversation.totals": "{requests} 次请求 · {tokens} tokens · {cost}", "logs.conversation.scope": "合计仅覆盖当前已加载的 Logs 环形缓冲。", diff --git a/gui/src/pages/Logs.tsx b/gui/src/pages/Logs.tsx index e7384a96b2..07aec2b12a 100644 --- a/gui/src/pages/Logs.tsx +++ b/gui/src/pages/Logs.tsx @@ -21,6 +21,7 @@ import { formatEstimatedUsd, formatEstimatedUsdValue, summarizeEstimatedCosts } import { cacheSplit, isCursorUsageProvider, tokensTitle } from "./logs-token-title"; import type { LogSurface, LogSurfaceFilter } from "./logs-surface-filter"; import { logMatchesSurface } from "./logs-surface-filter"; +import { logMatchesModelQuery } from "./logs-model-filter"; import { sanitizeLogEntryRouteDecision, validCachedRouteDecision, @@ -372,6 +373,7 @@ export default function Logs({ apiBase }: { apiBase: string }) { const [surfaceFilter, setSurfaceFilter] = useState<LogSurfaceFilter>("all"); const [interceptedHelpersOnly, setInterceptedHelpersOnly] = useState(false); const [conversationFilter, setConversationFilter] = useState(""); + const [modelFilter, setModelFilter] = useState(""); const [conversationQueryHash, setConversationQueryHash] = useState<string | undefined>(); const scrollContainerRef = useRef<HTMLDivElement>(null); const logRetryRef = useRef<{ key: string; failures: number; nextAttemptAt: number; error: unknown }>( @@ -511,6 +513,7 @@ export default function Logs({ apiBase }: { apiBase: string }) { const filteredLogs = logs.filter(log => ( logMatchesSurface(log, surfaceFilter) && (!interceptedHelpersOnly || Boolean(log.shadowCallRewrittenFrom)) + && logMatchesModelQuery(log, modelFilter) && (!conversationQuery || matchesLogConversationId(log.conversationId, conversationQuery, conversationQueryHash)) )); const conversationTotals = conversationQuery ? summarizeFilteredLogs(filteredLogs) : null; @@ -630,6 +633,17 @@ export default function Logs({ apiBase }: { apiBase: string }) { aria-label={t("logs.filter.conversation.label")} /> </label> + <label className="muted text-control logs-filter-field"> + {t("logs.filter.model.label")} + <input + type="search" + className="input mono" + value={modelFilter} + onChange={e => setModelFilter(e.target.value)} + placeholder={t("logs.filter.model.placeholder")} + aria-label={t("logs.filter.model.label")} + /> + </label> {conversationQuery && ( <button type="button" className="btn btn-ghost btn-sm" onClick={() => setConversationFilter("")}> {t("logs.filter.conversation.clear")} diff --git a/gui/src/pages/logs-model-filter.ts b/gui/src/pages/logs-model-filter.ts new file mode 100644 index 0000000000..7f6af18f65 --- /dev/null +++ b/gui/src/pages/logs-model-filter.ts @@ -0,0 +1,45 @@ +/** + * Free-text matching over a log row's model and provider, kept out of Logs.tsx so that + * module exports components only (react-refresh/only-export-components). + * + * #3070: an operator running custom providers saw their OpenAI monthly window shrink + * and could not find which turns were responsible. `?model=` filtering exists on the + * CLI and the API (b68edc077) but never reached the dashboard, and the one filter the + * page did have — the intercepted-helpers toggle — keys on `shadowCallRewrittenFrom`, + * which a plain account-gated turn does not carry. So the rows that explain the bill + * were exactly the rows no control could isolate. + */ +export interface LogModelFields { + model?: string; + resolvedModel?: string; + provider?: string; + attempts?: Array<{ + provider?: string; + model?: string; + }>; +} + +/** + * Case-insensitive substring over the requested model, resolved target, provider, and + * every failover attempt target. + * + * `resolvedModel` is matched as well as `model` on purpose. They differ precisely when + * routing redirected the turn, which is the case this filter exists to expose — matching + * only the requested id would hide the redirect that caused the charge. + * + * Substring rather than exact: an operator types `terra`, not + * `gpt-5.6-terra-2026-08-01`. Empty or whitespace-only query matches everything, so the + * control is inert until it is used. + */ +export function logMatchesModelQuery(log: LogModelFields, query: string): boolean { + const needle = query.trim().toLowerCase(); + if (!needle) return true; + const attemptTargets = Array.isArray(log.attempts) + ? log.attempts.flatMap(attempt => ( + attempt && typeof attempt === "object" ? [attempt.provider, attempt.model] : [] + )) + : []; + return [log.model, log.resolvedModel, log.provider, ...attemptTargets].some( + value => typeof value === "string" && value.toLowerCase().includes(needle), + ); +} diff --git a/gui/tests/logs-model-filter.test.ts b/gui/tests/logs-model-filter.test.ts new file mode 100644 index 0000000000..663686980f --- /dev/null +++ b/gui/tests/logs-model-filter.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, test } from "bun:test"; +import { logMatchesModelQuery } from "../src/pages/logs-model-filter"; + +/** + * #3070: an operator running custom providers watched their OpenAI monthly window + * shrink and had no way to find the responsible turns in the dashboard. `?model=` + * filtering landed for the CLI and the API in b68edc077 but never reached Logs.tsx, + * and the page's only related control — intercepted-helpers — keys on + * `shadowCallRewrittenFrom`, which an ordinary account-gated turn does not carry. + */ +describe("logs model filter", () => { + test("an empty or whitespace query matches everything", () => { + const log = { model: "gpt-5.6-terra" }; + expect(logMatchesModelQuery(log, "")).toBe(true); + expect(logMatchesModelQuery(log, " ")).toBe(true); + // Inert until used: a blank control must not hide rows. + expect(logMatchesModelQuery({}, "")).toBe(true); + }); + + test("matches a substring of the requested model, case-insensitively", () => { + const log = { model: "gpt-5.6-terra" }; + expect(logMatchesModelQuery(log, "terra")).toBe(true); + expect(logMatchesModelQuery(log, "TERRA")).toBe(true); + expect(logMatchesModelQuery(log, "luna")).toBe(false); + }); + + test("matches the resolved model even when the requested one differs", () => { + // The case the filter exists for: routing redirected the turn, and the model + // that got billed is the resolved one. Matching only `model` would hide it. + const redirected = { model: "combo/free", resolvedModel: "gpt-5.6-terra" }; + expect(logMatchesModelQuery(redirected, "terra")).toBe(true); + expect(logMatchesModelQuery(redirected, "combo")).toBe(true); + }); + + test("matches the provider and model of a winning failover attempt", () => { + const failedOver = { + provider: "primary", + model: "combo/reliable", + attempts: [ + { provider: "anthropic", model: "claude-sonnet-4.6" }, + { provider: "xai", model: "grok-4.6" }, + ], + }; + expect(logMatchesModelQuery(failedOver, "XAI")).toBe(true); + expect(logMatchesModelQuery(failedOver, "GROK-4.6")).toBe(true); + }); + + test("ignores malformed attempt containers and entries", () => { + expect(logMatchesModelQuery({ + model: "primary", + attempts: "not-an-array" as unknown as Array<{ provider?: string; model?: string }>, + }, "xai")).toBe(false); + expect(logMatchesModelQuery({ + attempts: [null as unknown as { provider?: string; model?: string }, { provider: "xai" }], + }, "xai")).toBe(true); + }); + + test("matches the provider", () => { + expect(logMatchesModelQuery({ provider: "xai", model: "grok-4.6" }, "xai")).toBe(true); + expect(logMatchesModelQuery({ provider: "xai", model: "grok-4.6" }, "anthropic")).toBe(false); + }); + + test("a row missing every field matches nothing but the empty query", () => { + expect(logMatchesModelQuery({}, "terra")).toBe(false); + }); + + test("a non-string field cannot throw or match", () => { + // Log rows arrive from the API; the page validates shape but this helper is + // called per row on every render and must not be the thing that crashes it. + const malformed = { model: 42 as unknown as string, resolvedModel: "gpt-5.6-terra" }; + expect(logMatchesModelQuery(malformed, "terra")).toBe(true); + expect(logMatchesModelQuery(malformed, "42")).toBe(false); + }); +}); From 9af3a7bebb5eb6e9bb9aab51274586897eaaba03 Mon Sep 17 00:00:00 2001 From: JUN <bitkyc08@gmail.com> Date: Tue, 1 Sep 2026 10:08:31 +0900 Subject: [PATCH 42/42] fix(catalog): advertise image input for modalities-declared sidecar rows (#3105) * fix(catalog): advertise image input for modalities-declared sidecar rows The runtime vision gate (isModelTextOnly) covers models listed in noVisionModels OR declared text-only via modelInputModalities (upstream fde2a9537, #1024), but both catalog advertise sites only checked noVisionModels. A sidecar-covered model - and every combo built from it - stayed advertised text-only in /v1/models, so the Codex app blocked image attachments client-side before the sidecar could run ('This model does not support image inputs'). Mirror isModelTextOnly in applyProviderConfigHints and the custom-model override: a declared text-only modelInputModalities entry now advertises image on top of its configured base. Discovery-derived text-only rows stay untouched (the runtime predicate does not cover those), and declared-image rows are never duplicated. Combos inherit the fix through their hinted members; no config hand-editing needed. Update the three tests that encoded the old drift and add regressions for the hint pass, the custom-model override, and combo derivation. (cherry picked from commit ed8f5a4fd30cbee26a658e28211e187af73421aa) * docs(sidecars): qualify combo image advertising with imageInput setting (cherry picked from commit 77d614a9fccb66e739b960ba948fe13b0109cc7b) * docs(sidecars): condition image description on sidecar plan availability Addresses the open CodeRabbit review comments: state that description runs only when a vision sidecar plan is available (raw image stripped otherwise, no description attempted), and name the combo imageInput setting explicitly with sidecar-covered membership. (cherry picked from commit 374139e1f538d21f24ca3dd9a6bf965be481908f) * fix(vision): share sidecar consumer predicate * docs(vision): align fallback and coverage wording --------- Co-authored-by: x3M3x <amroeid1999@gmail.com> --- .../src/content/docs/fr/guides/sidecars.md | 15 +- docs-site/src/content/docs/guides/sidecars.md | 14 +- .../src/content/docs/ja/guides/sidecars.md | 13 +- .../src/content/docs/ko/guides/sidecars.md | 13 +- .../src/content/docs/ru/guides/sidecars.md | 15 +- .../src/content/docs/tr/guides/sidecars.md | 19 ++- .../src/content/docs/zh-cn/guides/sidecars.md | 11 +- .../src/content/docs/zh-tw/guides/sidecars.md | 13 +- src/codex/catalog/provider-fetch.ts | 22 +-- src/vision/eligibility.ts | 20 ++- src/vision/index.ts | 22 +-- .../catalog-vision-sidecar-modalities.test.ts | 132 ++++++++++++++++++ tests/codex-catalog.test.ts | 19 ++- tests/vision-eligibility.test.ts | 19 +++ tests/vision-text-only-predicate.test.ts | 4 + 15 files changed, 281 insertions(+), 70 deletions(-) diff --git a/docs-site/src/content/docs/fr/guides/sidecars.md b/docs-site/src/content/docs/fr/guides/sidecars.md index 763b02ae73..b778c4c99e 100644 --- a/docs-site/src/content/docs/fr/guides/sidecars.md +++ b/docs-site/src/content/docs/fr/guides/sidecars.md @@ -85,9 +85,14 @@ les échecs de génération postérieurs à l'envoi des en-têtes sont transmis ## Service auxiliaire de vision -Lorsque le modèle routé est répertorié dans le `noVisionModels` de son fournisseur et qu'une requête porte une image, -opencodex décrit chaque image **avant** l'appel principal et la remplace par du texte. Quand -si `visionSidecar.model` est absent ou vide, le chemin d'exécution OpenAI, le tableau de bord et l'API de gestion +Lorsqu'un modèle routé figure dans le `noVisionModels` de son fournisseur — ou est déclaré texte seul pour ce modèle +via `modelInputModalities` — et qu'une requête porte une image, opencodex décrit chaque image **avant** l'appel principal +et la remplace par du texte, à condition qu'un plan de sidecar vision soit disponible. Sans plan disponible, l'image brute +est supprimée au lieu d'être transmise à un backend texte seul. Le catalogue de modèles annonce l'entrée image pour chaque +modèle couvert par le sidecar. Les combos annoncent l'entrée image seulement lorsque chaque membre accepte les images, +nativement ou via un sidecar, et que le paramètre `imageInput` du combo n'est pas désactivé, afin que des clients comme +l'application Codex autorisent les pièces jointes au lieu de les bloquer avant l'exécution du sidecar. Lorsque +`visionSidecar.model` est absent ou vide, le chemin d'exécution OpenAI, le tableau de bord et l'API de gestion utilisent le modèle de repli `gpt-5.4-mini`. Au démarrage, une ancienne valeur `gpt-5.4-mini` explicitement enregistrée est toujours migrée vers `gpt-5.6-luna` ; cette migration s'applique à une valeur stockée, et non à l'absence du champ du modèle. @@ -108,8 +113,8 @@ champ du modèle. les images distantes `https` sont récupérées par le moteur OpenAI, et non par le proxy. - La correspondance `noVisionModels` ignore un suffixe `:size` de style Ollama, donc une entrée `gpt-oss` couvre également `gpt-oss:120b`. -- Si la description échoue, le modèle reçoit un bref marqueur d'erreur de traitement. Si aucun service auxiliaire n'est - disponible, l'image brute est supprimée plutôt que transmise à un moteur limité au texte. +- Si la description échoue, le modèle reçoit un bref marqueur d'erreur de traitement. (Sans plan de sidecar disponible, + aucune description n'est tentée : l'image brute est supprimée comme indiqué ci-dessus.) - `maxDescriptionsPerTurn` (8 par défaut) limite les nouvelles descriptions par tour du modèle principal. Les résultats du cache et les doublons au même tour ne le consomment pas. Les descriptions d'images `data:` réussies sont mises en cache par moteur, modèle, niveau de détail, octets de l'image et contexte du message — ainsi que l'effort de raisonnement dans les diff --git a/docs-site/src/content/docs/guides/sidecars.md b/docs-site/src/content/docs/guides/sidecars.md index bb4c4346df..0606129f39 100644 --- a/docs-site/src/content/docs/guides/sidecars.md +++ b/docs-site/src/content/docs/guides/sidecars.md @@ -123,8 +123,14 @@ failures after response headers have started are delivered as `response.failed` ## Vision sidecar -When the routed model is listed in its provider's `noVisionModels` and a request carries an image, -opencodex describes each image **before** the main call and replaces it with text. When +When the routed model is listed in its provider's `noVisionModels` — or declared text-only for +that model via `modelInputModalities` — and a request carries an image, opencodex describes each +image **before** the main call and replaces it with text, provided a vision sidecar plan is +available. Without an available plan the raw image is stripped rather than forwarded to a +text-only backend. The model catalog advertises image input for every sidecar-covered model. +Combos advertise image input only when every member accepts images, either natively or through a +sidecar, and the combo's `imageInput` setting is not disabled, so clients such as the Codex app +allow attachments instead of blocking them before the sidecar runs. When `visionSidecar.model` is absent or blank, the OpenAI execution path, Dashboard, and management API use the `gpt-5.4-mini` fallback. Startup still migrates an explicitly persisted legacy `gpt-5.4-mini` value to `gpt-5.6-luna`; that migration applies to a stored value, not to an absent @@ -146,8 +152,8 @@ model field. remote `https` images are fetched by the OpenAI backend, not by the proxy. - `noVisionModels` matching ignores an Ollama-style `:size` suffix, so a `gpt-oss` entry also covers `gpt-oss:120b`. -- If description fails, the model receives a short processing-error marker. If no sidecar plan is - available, the raw image is stripped rather than forwarded to a text-only backend. +- If description fails, the model receives a short processing-error marker. (Without an available + sidecar plan, no description is attempted — the raw image is stripped, as described above.) - `maxDescriptionsPerTurn` (default 8) limits new descriptions per main-model turn. Cache hits and same-turn duplicates do not consume it. Successful `data:` image descriptions are cached by backend, model, detail, image bytes, and message context — plus the reasoning effort on OpenAI diff --git a/docs-site/src/content/docs/ja/guides/sidecars.md b/docs-site/src/content/docs/ja/guides/sidecars.md index 48af198fe3..dec111160f 100644 --- a/docs-site/src/content/docs/ja/guides/sidecars.md +++ b/docs-site/src/content/docs/ja/guides/sidecars.md @@ -69,8 +69,13 @@ stall は全体生成 timeout ではありません。SSE 開始前の失敗は ## ビジョンサイドカー -ルーティングモデルが該当プロバイダーの `noVisionModels` にありリクエストに画像が来る場合、opencodex は -メイン呼び出し**前に**各画像を説明したテキストに差し替えます。`visionSidecar.model` が未設定または空の場合、 +ルーティングモデルが該当プロバイダーの `noVisionModels` にある、またはそのモデルが +`modelInputModalities` でテキスト専用と宣言され、リクエストに画像が来る場合、opencodex は利用可能な +ビジョンサイドカー計画があるときに限り、メイン呼び出し**前に**各画像を説明したテキストに差し替えます。 +計画が利用できない場合は、生の画像をテキスト専用バックエンドへ転送せず削除します。モデルカタログは +サイドカー対象の各モデルに画像入力を広告します。コンボは、すべてのメンバーがネイティブまたはサイドカーを +通じて画像を受け入れ、かつコンボの `imageInput` 設定が無効でない場合にのみ画像入力を広告します。これにより +Codex アプリなどのクライアントは、サイドカー実行前に添付をブロックせず許可できます。`visionSidecar.model` が未設定または空の場合、 OpenAI 実行経路、ダッシュボード、管理 API は `gpt-5.4-mini` をフォールバックとして使います。起動時には 明示的に保存された旧 `gpt-5.4-mini` 値を引き続き `gpt-5.6-luna` にマイグレーションしますが、この マイグレーションは保存済みの値だけが対象で、モデルフィールドがない場合には適用されません。 @@ -92,8 +97,8 @@ OpenAI 実行経路、ダッシュボード、管理 API は `gpt-5.4-mini` を リモート `https` 画像はプロキシではなく OpenAI バックエンドが取得します。 - `noVisionModels` 比較は Ollama 式の `:size` 接尾辞を無視するため `gpt-oss` 項目 1 つで `gpt-oss:120b` も処理できます。 -- 画像説明が失敗すると短い処理エラー案内文をモデルに渡します。サイドカー計画自体を作れない場合は - テキスト専用バックエンドに元画像を送らず削除します。 +- 画像説明が失敗すると短い処理エラー案内文をモデルに渡します。(利用可能なサイドカー計画がない場合は + 説明を試みず、上記のとおり元画像を削除します。) - `maxDescriptionsPerTurn`(デフォルト 8)はメインモデル 1 ターンで新規実行する説明数を制限します。キャッシュ ヒットと同じターンの重複要求は限度を消費しません。成功した `data:` 画像説明はバックエンド、モデル、 detail、画像バイト、メッセージ文脈を基準にキャッシュし、OpenAI のキーには推論負荷も含まれます diff --git a/docs-site/src/content/docs/ko/guides/sidecars.md b/docs-site/src/content/docs/ko/guides/sidecars.md index 2aab84f438..55af37a150 100644 --- a/docs-site/src/content/docs/ko/guides/sidecars.md +++ b/docs-site/src/content/docs/ko/guides/sidecars.md @@ -71,8 +71,13 @@ stall은 전체 생성 timeout이 아닙니다. SSE가 시작되기 전 실패 ## 비전 사이드카 -라우팅 모델이 해당 프로바이더의 `noVisionModels`에 있고 요청에 이미지가 들어오면, opencodex는 -메인 호출 **전에** 각 이미지를 설명한 텍스트로 바꿉니다. `visionSidecar.model`이 없거나 빈 값이면 +라우팅 모델이 해당 프로바이더의 `noVisionModels`에 있거나 해당 모델이 `modelInputModalities`에서 +텍스트 전용으로 선언되어 있고 요청에 이미지가 들어오면, opencodex는 사용 가능한 비전 사이드카 계획이 있을 때에만 +메인 호출 **전에** 각 이미지를 설명한 텍스트로 바꿉니다. 사용 가능한 계획이 없으면 원본 이미지는 텍스트 전용 +백엔드로 전달되지 않고 제거됩니다. 모델 카탈로그는 사이드카로 처리되는 모든 모델에 image input을 알립니다. +콤보는 모든 멤버가 네이티브로 또는 사이드카를 통해 이미지를 수용하고 콤보의 `imageInput` 설정이 비활성화되지 않은 +경우에만 image input을 알립니다. 따라서 Codex 앱 같은 클라이언트는 사이드카가 실행되기 전에 첨부를 차단하지 않고 허용합니다. +`visionSidecar.model`이 없거나 빈 값이면 OpenAI 실행 경로, Dashboard, 관리 API는 `gpt-5.4-mini`를 폴백으로 사용합니다. 시작 시 명시적으로 저장된 기존 `gpt-5.4-mini` 값은 계속 `gpt-5.6-luna`로 마이그레이션되지만, 이 마이그레이션은 저장된 값에만 적용되고 모델 필드가 없는 경우에는 적용되지 않습니다. @@ -94,8 +99,8 @@ OpenAI 실행 경로, Dashboard, 관리 API는 `gpt-5.4-mini`를 폴백으로 원격 `https` 이미지는 프록시가 아니라 OpenAI 백엔드가 가져옵니다. - `noVisionModels` 비교는 Ollama식 `:size` 접미사를 무시하므로 `gpt-oss` 항목 하나로 `gpt-oss:120b`도 처리할 수 있습니다. -- 이미지 설명이 실패하면 짧은 처리 오류 안내문을 모델에 전달합니다. 사이드카 계획 자체를 만들 수 - 없으면 텍스트 전용 백엔드에 원본 이미지를 보내지 않고 제거합니다. +- 이미지 설명이 실패하면 짧은 처리 오류 안내문을 모델에 전달합니다. (사용 가능한 사이드카 계획이 없으면 + 설명을 시도하지 않고 위에서 설명한 대로 원본 이미지를 제거합니다.) - `maxDescriptionsPerTurn`(기본값 8)은 메인 모델 한 턴에서 새로 실행할 설명 수를 제한합니다. 캐시 적중과 같은 턴의 중복 요청은 한도를 쓰지 않습니다. 성공한 `data:` 이미지 설명은 백엔드, 모델, detail, 이미지 바이트, 메시지 문맥을 기준으로 캐시하며, OpenAI 키에는 추론 강도도 포함됩니다 diff --git a/docs-site/src/content/docs/ru/guides/sidecars.md b/docs-site/src/content/docs/ru/guides/sidecars.md index bc2a691a67..cdb6dc7087 100644 --- a/docs-site/src/content/docs/ru/guides/sidecars.md +++ b/docs-site/src/content/docs/ru/guides/sidecars.md @@ -80,9 +80,14 @@ SSE-событие `response.failed`. ## Vision-сайдкар -Когда маршрутизируемая модель указана в `noVisionModels` своего провайдера, а запрос содержит -изображение, opencodex описывает каждое изображение **до** основного вызова и заменяет его текстом. -Если `visionSidecar.model` отсутствует или пуст, путь выполнения OpenAI, дашборд и API управления +Когда маршрутизируемая модель указана в `noVisionModels` своего провайдера — либо для неё в +`modelInputModalities` явно указана только текстовая модальность — и запрос содержит изображение, +opencodex описывает каждое изображение **до** основного вызова и заменяет его текстом, если доступен +план vision-сайдкара. Без доступного плана исходное изображение удаляется, а не передаётся текстовому +бэкенду. Каталог моделей объявляет вход изображений для каждой модели, покрытой сайдкаром. Комбо +объявляют вход изображений только если каждый участник принимает изображения нативно или через +сайдкар и параметр комбо `imageInput` не отключён; поэтому такие клиенты, как приложение Codex, +разрешают вложения вместо их блокировки до запуска сайдкара. Если `visionSidecar.model` отсутствует или пуст, путь выполнения OpenAI, дашборд и API управления используют фолбэк `gpt-5.4-mini`. При запуске явно сохранённое устаревшее значение `gpt-5.4-mini` по-прежнему мигрирует на `gpt-5.6-luna`; миграция применяется только к сохранённому значению, а не к отсутствующему полю модели. @@ -106,8 +111,8 @@ SSE-событие `response.failed`. `data:` и `https:`; удалённые `https`-изображения загружает бэкенд OpenAI, а не прокси. - Сопоставление `noVisionModels` игнорирует суффикс `:size` в стиле Ollama, поэтому запись `gpt-oss` покрывает и `gpt-oss:120b`. -- Если описание не удалось, модель получает короткий маркер ошибки обработки. Если план сайдкара - недоступен, исходное изображение удаляется, а не пересылается текстовому бэкенду. +- Если описание не удалось, модель получает короткий маркер ошибки обработки. (Если доступного плана + сайдкара нет, описание не запускается, а исходное изображение удаляется, как описано выше.) - `maxDescriptionsPerTurn` (по умолчанию 8) ограничивает число новых описаний за один ход основной модели. Попадания в кэш и дубликаты в рамках того же хода лимит не расходуют. Успешные описания `data:`-изображений кэшируются по бэкенду, модели, детализации, байтам изображения и контексту diff --git a/docs-site/src/content/docs/tr/guides/sidecars.md b/docs-site/src/content/docs/tr/guides/sidecars.md index 5a46fbabc3..5205f0041f 100644 --- a/docs-site/src/content/docs/tr/guides/sidecars.md +++ b/docs-site/src/content/docs/tr/guides/sidecars.md @@ -103,9 +103,16 @@ durma, toplam bir üretim zaman aşımı değildir. SSE başlamadan önceki arı ## Vizyon sidecar'ı -Yönlendirilen model sağlayıcısının `noVisionModels` listesinde yer aldığında ve -bir istek görsel taşıdığında, opencodex ana çağrıdan **önce** her görseli -açıklar ve onu metinle değiştirir. `visionSidecar.model` olmadığında veya boş +Yönlendirilen model sağlayıcısının `noVisionModels` listesinde yer aldığında ya da +bu model için `modelInputModalities` ile salt metin olarak bildirildiğinde ve bir +istek görsel taşıdığında, opencodex kullanılabilir bir vision sidecar planı varsa +ana çağrıdan **önce** her görseli açıklar ve onu metinle değiştirir. Kullanılabilir +bir plan yoksa ham görsel salt metin arka ucuna iletilmek yerine kaldırılır. Model +kataloğu sidecar kapsamında olan her model için görsel girdisini bildirir. Kombolar, +her üye görselleri yerel olarak veya bir sidecar üzerinden kabul ettiğinde ve kombonun +`imageInput` ayarı devre dışı olmadığında görsel girdisini bildirir; böylece Codex +uygulaması gibi istemciler, sidecar çalışmadan önce ekleri engellemek yerine kabul eder. +`visionSidecar.model` olmadığında veya boş olduğunda, OpenAI yürütme yolu, Kontrol Paneli ve yönetim API'si `gpt-5.4-mini` geri dönüşünü kullanır. Başlangıç hala açıkça kalıcı hale getirilmiş eski bir `gpt-5.4-mini` değerini `gpt-5.6-luna`'ya geçirir; bu geçiş, bulunmayan bir @@ -135,9 +142,8 @@ model alanına değil, saklanan bir değere uygulanır. görselleri proxy tarafından değil, OpenAI arka ucu tarafından getirilir. - `noVisionModels` eşleştirmesi Ollama tarzı bir `:size` sonekini yok sayar, bu nedenle bir `gpt-oss` girdisi `gpt-oss:120b`'yi de kapsar. -- Açıklama başarısız olursa model kısa bir işleme hatası işaretçisi alır. - Kullanılabilir hiçbir sidecar planı yoksa ham görsel salt metin bir arka uca - iletilmek yerine kaldırılır. +- Açıklama başarısız olursa model kısa bir işleme hatası işaretçisi alır. (Kullanılabilir bir + sidecar planı yoksa açıklama denenmez; ham görsel yukarıda belirtildiği gibi kaldırılır.) - `maxDescriptionsPerTurn` (varsayılan 8), ana model turu başına yeni açıklamaları sınırlar. Önbellek isabetleri ve aynı turdaki kopyalar bunu tüketmez. Başarılı `data:` görsel açıklamaları arka uç, model, ayrıntı, görsel @@ -197,4 +203,3 @@ hedeflenen hesap ve iş yükü ile kapsamlı bir şekilde test edilmelidir. Her alan için [Yapılandırma referansı](/tr/reference/configuration/#sidecars) bölümüne bakın. - diff --git a/docs-site/src/content/docs/zh-cn/guides/sidecars.md b/docs-site/src/content/docs/zh-cn/guides/sidecars.md index ea758b48e8..f2dfec5c95 100644 --- a/docs-site/src/content/docs/zh-cn/guides/sidecars.md +++ b/docs-site/src/content/docs/zh-cn/guides/sidecars.md @@ -64,8 +64,11 @@ OAuth 账户时使用 `anthropic`,否则使用 `openai`。显式选择 `anthro ## Vision sidecar -当路由模型列在其 provider 的 `noVisionModels` 中,并且请求包含图像时,opencodex 会在主调用 -**之前**描述每张图像,并用文字替换图像。当 `visionSidecar.model` 缺失或为空时,OpenAI 执行路径、 +当路由模型列在其 provider 的 `noVisionModels` 中,或该模型在 `modelInputModalities` 中被声明为仅文本, +并且请求包含图像时,只要有可用的 vision sidecar plan,opencodex 就会在主调用**之前**描述每张图像并用文字替换图像。 +如果没有可用 plan,原始图像会被移除,而不会继续转发给纯文本后端。模型目录会为每个由 sidecar 覆盖的模型声明图像输入。 +只有当每个 combo 成员都能原生或通过 sidecar 接受图像、且 combo 的 `imageInput` 设置未禁用时,combo 才会声明图像输入; +这样 Codex 应用等客户端会允许附件,而不会在 sidecar 运行前阻止它们。当 `visionSidecar.model` 缺失或为空时,OpenAI 执行路径、 Dashboard 和管理 API 都使用 `gpt-5.4-mini` 作为回退。启动时仍会把明确保存的旧 `gpt-5.4-mini` 值迁移到 `gpt-5.6-luna`;该迁移只作用于已保存值,不适用于缺失的 model 字段。 @@ -83,8 +86,8 @@ Dashboard 和管理 API 都使用 `gpt-5.4-mini` 作为回退。启动时仍会 而不是代理。 - `noVisionModels` 匹配会忽略 Ollama 风格的 `:size` 后缀,因此一个 `gpt-oss` 条目也能覆盖 `gpt-oss:120b`。 -- 如果描述失败,模型会收到简短的处理错误提示。若根本无法建立 sidecar plan,原始图像会被 - 移除,而不会继续转发给纯文本后端。 +- 如果描述失败,模型会收到简短的处理错误提示。(如果没有可用的 sidecar plan,则不会尝试描述, + 原始图像会按上文所述被移除。) - `maxDescriptionsPerTurn`(默认 8)限制每个主模型 turn 的新增描述次数。缓存命中和同一 turn 的重复请求不会消耗配额。成功的 `data:` 图像描述会按后端、模型、detail、图像字节和消息上下文 缓存;OpenAI 的缓存键还会额外包含推理强度(Anthropic 键不含,因为该字段在那里被忽略)。 diff --git a/docs-site/src/content/docs/zh-tw/guides/sidecars.md b/docs-site/src/content/docs/zh-tw/guides/sidecars.md index 5d9c7d1de4..60131afd19 100644 --- a/docs-site/src/content/docs/zh-tw/guides/sidecars.md +++ b/docs-site/src/content/docs/zh-tw/guides/sidecars.md @@ -64,10 +64,13 @@ OAuth 帳號時使用 `anthropic`,否則使用 `openai`。明確選擇 `anthro ## Vision sidecar -當路由模型列在其 provider 的 `noVisionModels` 中,並且請求包含圖像時,opencodex 會在主呼叫 -**之前**描述每張圖像,並用文字替換圖像。Dashboard 和管理 API 目前顯示的預設值是 +當路由模型列在其 provider 的 `noVisionModels` 中,或該模型在 `modelInputModalities` 中被宣告為僅文字, +且請求包含圖像時,只要有可用的 vision sidecar plan,opencodex 就會在主呼叫**之前**描述每張圖像並用文字替換圖像。 +若沒有可用 plan,原始圖像會被移除,不會繼續轉送給純文字後端。模型目錄會為每個由 sidecar 處理的模型宣告圖像輸入。 +只有當每個 combo 成員都能原生或透過 sidecar 接受圖像,且 combo 的 `imageInput` 設定未停用時,combo 才會宣告圖像輸入; +如此 Codex 應用程式等用戶端會允許附件,而不會在 sidecar 執行前阻擋它們。Dashboard 和管理 API 目前顯示的預設值是 `gpt-5.6-luna`,啟動時也會把明確儲存的舊 `gpt-5.4-mini` 值遷移到 Luna。只有在 -`visionSidecar.model` 欄位完全不存在時,vision 執行路徑才會使用程式碼中的 `gpt-5.4-mini` 回退值。 +`visionSidecar.model` 欄位不存在或為空字串時,vision 執行路徑才會使用程式碼中的 `gpt-5.4-mini` 回退值。 - 圖像可以來自 user、developer 和 tool-result message,也包括 Codex 的 `view_image` 結果。 - 每張圖像會以 `reasoning.effort: "low"` 傳送給設定的原生 vision 模型,描述結果會就地替換 @@ -80,8 +83,8 @@ OAuth 帳號時使用 `anthropic`,否則使用 `openai`。明確選擇 `anthro 而不是代理。 - `noVisionModels` 匹配會忽略 Ollama 風格的 `:size` 字尾,因此一個 `gpt-oss` 條目也能覆蓋 `gpt-oss:120b`。 -- 如果描述失敗,模型會收到簡短的處理錯誤提示。若根本無法建立 sidecar plan,原始圖像會被 - 移除,而不會繼續轉發給純文字後端。 +- 如果描述失敗,模型會收到簡短的處理錯誤提示。(如果沒有可用的 sidecar plan,就不會嘗試描述, + 原始圖像會依上文所述被移除。) - `maxDescriptionsPerTurn`(預設 8)限制每個主模型 turn 的新增描述次數。快取命中和同一 turn 的重複請求不會消耗配額。成功的 `data:` 圖像描述會按後端、模型、detail、圖像位元組和訊息上下文 快取;內容可變的 `https:` 圖像不會快取。 diff --git a/src/codex/catalog/provider-fetch.ts b/src/codex/catalog/provider-fetch.ts index 404fe2601d..5ffc357cb1 100644 --- a/src/codex/catalog/provider-fetch.ts +++ b/src/codex/catalog/provider-fetch.ts @@ -30,6 +30,7 @@ import { import type { OcxConfig, OcxProviderConfig } from "../../types"; import { modelInList } from "../../types"; import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort"; +import { isModelVisionSidecarConsumer } from "../../vision/eligibility"; import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider } from "../../generated/model-metadata"; import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive"; import { @@ -670,11 +671,14 @@ export function applyProviderConfigHints(name: string, prov: OcxProviderConfig, const configuredMaxInput = configuredMaxInputTokens(prov, model.id); const configuredAutoCompact = configuredAutoCompactTokenLimit(prov, model.id); let inputModalities = configuredInputModalities(prov, model.id); - // Vision-sidecar coverage: `noVisionModels` marks models whose images the PROXY describes - // (src/vision/index.ts). The catalog must still advertise image input for them — the Codex app + // The shared vision-sidecar consumer predicate keeps catalog advertisement and request-time + // planning aligned. The catalog must still advertise image input — the Codex app // gates attachments client-side on input_modalities, and a text-only entry would block images - // before the sidecar ever runs ("This model does not support image inputs"). - if (modelInList(prov.noVisionModels, model.id)) { + // before the sidecar ever runs ("This model does not support image inputs"). Discovery-derived + // text-only rows stay untouched: the runtime predicate only reads these two config sources, so + // it would not convert those. + const sidecarCovered = isModelVisionSidecarConsumer(prov, model.id); + if (sidecarCovered) { const base = inputModalities ?? model.inputModalities ?? ["text"]; inputModalities = base.includes("image") ? [...base] : [...base, "image"]; } @@ -2174,9 +2178,10 @@ async function gatherRoutedModelsUncached( ...(base.codexToolMode === undefined && replaced.codexToolMode !== undefined ? { codexToolMode: replaced.codexToolMode } : {}), ...(base.capabilities === undefined && replaced.capabilities !== undefined ? { capabilities: replaced.capabilities } : {}), } : base; - // Vision-sidecar coverage ONLY: if the custom model is in the enriched provider's - // noVisionModels, advertise image input so the Codex app lets images reach the sidecar - // (#349/#344). Deliberately NOT the full applyProviderConfigHints pass — custom rows are a + // Vision-sidecar coverage only: when the enriched provider's shared predicate matches + // noVisionModels or text-without-image modelInputModalities, advertise image input so the + // Codex app lets images reach the sidecar (#349/#344). Deliberately NOT the full + // applyProviderConfigHints pass — custom rows are a // user override, so their explicit contextWindow / inputModalities / reasoning fields must be // preserved verbatim (the hint pass would cap context and overwrite modalities from registry). const mergedContext = typeof merged.contextWindow === "number" && merged.contextWindow > 0 @@ -2202,7 +2207,8 @@ async function gatherRoutedModelsUncached( } : mergedWithHardBounds; const enrichedProvider = enrichedByName.get(cm.provider) ?? rawProvider; - if (enrichedProvider && modelInList(enrichedProvider.noVisionModels, mergedWithAutoCompact.id)) { + // Reuse the request-time consumer predicate so custom rows cannot drift from catalog hints. + if (enrichedProvider && isModelVisionSidecarConsumer(enrichedProvider, mergedWithAutoCompact.id)) { const current = mergedWithAutoCompact.inputModalities ?? ["text"]; if (!current.includes("image")) { return { ...mergedWithAutoCompact, inputModalities: [...current, "image"] }; diff --git a/src/vision/eligibility.ts b/src/vision/eligibility.ts index 06a4ecce02..09a83d44db 100644 --- a/src/vision/eligibility.ts +++ b/src/vision/eligibility.ts @@ -21,6 +21,7 @@ * explicit modalities. */ import { modelInList, type OcxConfig, type OcxProviderConfig } from "../types"; +import { modelRecordValue } from "../reasoning-effort"; import { getModelMetadataCaseInsensitive, resolveMetadataProvider } from "../generated/model-metadata"; import { nativeInputModalities } from "../codex/catalog/metadata"; import { SUPPORTED_NATIVE_OPENAI_SLUGS } from "../codex/catalog/native-models"; @@ -68,6 +69,22 @@ export interface VisionModelOption { type EnrichedProviderCache = Map<string, OcxProviderConfig>; +/** + * Whether the proxy must describe images for this model before dispatching its main request. + * + * `noVisionModels` is an explicit override. A modality declaration is only evidence for this + * path when it describes a text model that excludes image input: an audio-only declaration is + * not a text-only model and must not be widened to image through the vision sidecar. + */ +export function isModelVisionSidecarConsumer( + provider: Pick<OcxProviderConfig, "noVisionModels" | "modelInputModalities">, + modelId: string, +): boolean { + if (modelInList(provider.noVisionModels, modelId)) return true; + const modalities = modelRecordValue(provider.modelInputModalities, modelId); + return Array.isArray(modalities) && modalities.includes("text") && !modalities.includes("image"); +} + function advertisesImageInput(modalities: readonly string[] | undefined): boolean | undefined { if (!modalities || modalities.length === 0) return undefined; return modalities.includes("image"); @@ -105,7 +122,8 @@ function isVisionSidecarConsumerWithCache( modelId: string, cache: EnrichedProviderCache, ): boolean { - return modelInList(enrichedProviderForVision(config, providerName, cache)?.noVisionModels, modelId); + const provider = enrichedProviderForVision(config, providerName, cache); + return provider !== undefined && isModelVisionSidecarConsumer(provider, modelId); } /** diff --git a/src/vision/index.ts b/src/vision/index.ts index b945620ca6..3f85258624 100644 --- a/src/vision/index.ts +++ b/src/vision/index.ts @@ -1,12 +1,10 @@ import { createHash } from "node:crypto"; import type { OcxConfig, OcxContentPart, OcxMessage, OcxParsedRequest, OcxProviderConfig, OcxTextContent } from "../types"; -import { modelInList } from "../types"; -import { modelRecordValue } from "../reasoning-effort"; import type { VisionReasoningEffort } from "../reasoning-effort"; import { describeImage, type DescribeOutcome, type VisionSettings } from "./describe"; import { describeImageAnthropic } from "./anthropic-describe"; import { describeImageRouted } from "./routed-describe"; -import { modelAcceptsImageInput } from "./eligibility"; +import { isModelVisionSidecarConsumer as isModelTextOnly, modelAcceptsImageInput } from "./eligibility"; import { normalizeVisionReasoningForModel } from "./reasoning"; import type { CodexAuthContext } from "../codex/auth-context"; import { resolveSidecarAuth } from "../sidecar/auth"; @@ -22,24 +20,12 @@ import { export { describeImage } from "./describe"; -/** - * True when the model is explicitly known to be text-only — either listed in - * `noVisionModels` or declared with `modelInputModalities` that exclude "image". - * Returns false for unknown models (no evidence either way) so they fall through - * to native image passthrough, which is the safe default for an unclassified model. - */ -export function isModelTextOnly( - provider: OcxProviderConfig, - modelId: string, -): boolean { - if (modelInList(provider.noVisionModels, modelId)) return true; - const modalities = modelRecordValue(provider.modelInputModalities, modelId); - if (Array.isArray(modalities) && modalities.length > 0 && !modalities.includes("image")) return true; - return false; -} +/** Backward-compatible request-time name for the shared vision-sidecar consumer predicate. */ +export { isModelVisionSidecarConsumer as isModelTextOnly } from "./eligibility"; export { describeImageAnthropic, parseAnthropicVisionSSE } from "./anthropic-describe"; export { BASELINE_VISION_MODELS, + isModelVisionSidecarConsumer, isVisionEligibleModel, isVisionSidecarConsumer, modelAcceptsImageInput, diff --git a/tests/catalog-vision-sidecar-modalities.test.ts b/tests/catalog-vision-sidecar-modalities.test.ts index f5ff51431d..8e71876dff 100644 --- a/tests/catalog-vision-sidecar-modalities.test.ts +++ b/tests/catalog-vision-sidecar-modalities.test.ts @@ -48,6 +48,50 @@ describe("vision-sidecar catalog modalities", () => { expect(hinted.inputModalities).toEqual(["text", "image"]); }); + test("modelInputModalities-declared text-only models advertise image without a noVisionModels entry", () => { + // Regression: isModelTextOnly (the RUNTIME sidecar gate) treats a text-only + // modelInputModalities declaration exactly like a noVisionModels entry, but the catalog hint + // pass only checked noVisionModels — so sidecar-covered models stayed advertised text-only + // and the Codex app blocked image paste before the sidecar could run. + const prov: OcxProviderConfig = { + adapter: "openai-chat", + baseUrl: "https://api.example/v1", + modelInputModalities: { "deepseek-chat": ["text"] }, + }; + const hinted = applyProviderConfigHints("azu-lab2", prov, { id: "deepseek-chat", provider: "azu-lab2" }); + expect(hinted.inputModalities).toEqual(["text", "image"]); + }); + + test("modelInputModalities declaring image stays untouched (no duplication)", () => { + const prov: OcxProviderConfig = { + adapter: "openai-chat", + baseUrl: "https://api.example/v1", + modelInputModalities: { "glm-5.3": ["text", "image"] }, + }; + const hinted = applyProviderConfigHints("vdi", prov, { id: "glm-5.3", provider: "vdi" }); + expect(hinted.inputModalities).toEqual(["text", "image"]); + }); + + test("audio-only modelInputModalities do not advertise image", () => { + const prov: OcxProviderConfig = { + adapter: "openai-chat", + baseUrl: "https://api.example/v1", + modelInputModalities: { "audio-model": ["audio"] }, + }; + const hinted = applyProviderConfigHints("audio-provider", prov, { id: "audio-model", provider: "audio-provider" }); + expect(hinted.inputModalities).toEqual(["audio"]); + }); + + test("discovery-derived text-only rows are NOT advertised image (the runtime would not convert them)", () => { + // Only the two config sources the runtime predicate reads (noVisionModels, + // modelInputModalities) may widen the catalog; a listing that merely reports + // ["text"] without either must stay text-only. + const hinted = applyProviderConfigHints("opencode-go", base, { + id: "listing-text-model", provider: "opencode-go", inputModalities: ["text"], + }); + expect(hinted.inputModalities).toEqual(["text"]); + }); + test("MiMo token-plan sends only the Pro model through the sidecar (#1927)", () => { const canonical: OcxProviderConfig = { adapter: "openai-chat", @@ -173,6 +217,69 @@ describe("vision-sidecar custom-model override (#349/#344)", () => { globalThis.fetch = originalFetch; } }); + + test("a custom row whose modelId is declared text-only via modelInputModalities still advertises image", async () => { + // Same isModelTextOnly parity as the hint pass, applied to the custom-model override path: + // the registry/config text-only declaration covers the row at request time, so the catalog + // must let images through to the sidecar here too. + const originalFetch = globalThis.fetch; + globalThis.fetch = (() => { throw new Error("fetch should not be called"); }) as typeof fetch; + try { + const models = await gatherRoutedModels({ + port: 10100, + defaultProvider: "text-sidecar-provider", + providers: { + "text-sidecar-provider": { + baseUrl: "https://text-sidecar.example/v1", + adapter: "openai-chat", + authMode: "key", + liveModels: false, + models: ["baseline-model"], + modelInputModalities: { "glm-5.2": ["text"] }, + }, + }, + customModels: [ + { id: "cm-4", provider: "text-sidecar-provider", modelId: "glm-5.2", displayName: "GLM 5.2", addedAt: "2026-01-01T00:00:00.000Z" }, + ], + }); + const custom = models.find(m => m.provider === "text-sidecar-provider" && m.id === "glm-5.2"); + expect(custom).toBeDefined(); + expect(custom?.inputModalities).toEqual(["text", "image"]); + } finally { + globalThis.fetch = originalFetch; + clearModelCache("text-sidecar-provider"); + } + }); + + test("a custom row whose modelId is declared audio-only does not advertise image", async () => { + const originalFetch = globalThis.fetch; + globalThis.fetch = (() => { throw new Error("fetch should not be called"); }) as typeof fetch; + try { + const models = await gatherRoutedModels({ + port: 10100, + defaultProvider: "audio-sidecar-provider", + providers: { + "audio-sidecar-provider": { + baseUrl: "https://audio-sidecar.example/v1", + adapter: "openai-chat", + authMode: "key", + liveModels: false, + models: ["baseline-model"], + modelInputModalities: { "audio-model": ["audio"] }, + }, + }, + customModels: [ + { id: "cm-audio", provider: "audio-sidecar-provider", modelId: "audio-model", displayName: "Audio Model", addedAt: "2026-01-01T00:00:00.000Z" }, + ], + }); + const custom = models.find(m => m.provider === "audio-sidecar-provider" && m.id === "audio-model"); + expect(custom).toBeDefined(); + expect(custom?.inputModalities?.includes("image") ?? false).toBe(false); + } finally { + globalThis.fetch = originalFetch; + clearModelCache("audio-sidecar-provider"); + } + }); }); describe("vision-capable provider models feed combo modalities", () => { @@ -258,6 +365,31 @@ describe("vision-capable provider models feed combo modalities", () => { const hinted = applyProviderConfigHints("xai", prov, { provider: "xai", id: "grok-4.5" }); expect(hinted.inputModalities).toEqual(["text", "image"]); }); + + test("a combo of modelInputModalities-declared text-only members advertises image through the hints", () => { + // End-to-end shape of the /planners bug: every member is sidecar-covered via + // modelInputModalities (not noVisionModels). The hinted members all carry image, so the + // derived combo keeps image input instead of collapsing to text-only. + const memberFor = (provider: string, id: string): CatalogModel => applyProviderConfigHints(provider, { + adapter: "openai-chat", + baseUrl: `https://${provider}.example/v1`, + modelInputModalities: { [id]: ["text"] }, + }, { id, provider, contextWindow: 200_000 }); + const memberA = memberFor("azu-lab2", "deepseek-chat"); + const memberB = memberFor("vdi", "glm-5.2"); + const derived = deriveComboCatalogModel( + "planners", + { + targets: [ + { provider: "azu-lab2", model: "deepseek-chat" }, + { provider: "vdi", model: "glm-5.2" }, + ], + defaultEffort: "high", + } as never, + [memberA, memberB], + ); + expect(derived?.inputModalities).toEqual(["text", "image"]); + }); }); describe("Cursor native vs sidecar vision registry", () => { diff --git a/tests/codex-catalog.test.ts b/tests/codex-catalog.test.ts index 09fcef9d77..05223ca1e5 100644 --- a/tests/codex-catalog.test.ts +++ b/tests/codex-catalog.test.ts @@ -1061,7 +1061,9 @@ describe("combo catalog capability intersection", () => { liveModels: false, models: ["m2"], modelContextWindows: { m2: 128_000 }, - modelInputModalities: { m2: ["text"] }, + // No declaration: text-only by catalog default, NOT sidecar-covered. A declared + // text-only member would widen to image (isModelTextOnly parity) and the pair + // would no longer be disjoint. }, }, combos: { @@ -1202,7 +1204,9 @@ describe("combo catalog capability intersection", () => { contextWindow: 128_000, contextCapped: false, maxInputTokens: 100_000, - inputModalities: ["text"], + // The text-only modelInputModalities declaration is sidecar-covered at runtime + // (isModelTextOnly), so the combo advertises image input like its member does. + inputModalities: ["text", "image"], reasoningEfforts: ["high"], }); expect(rows.find(row => row.provider === "combo" && row.id === "nova-sol")) @@ -1515,7 +1519,7 @@ describe("combo catalog capability intersection", () => { liveModels: false, models: ["m1"], modelContextWindows: { m1: 128_000 }, - // Disjoint modalities with b → empty intersection (incompatible_modalities). + // Image-only member. An image-only declaration is not sidecar-covered. modelInputModalities: { m1: ["image"] }, }, b: { @@ -1524,7 +1528,10 @@ describe("combo catalog capability intersection", () => { liveModels: false, models: ["m2"], modelContextWindows: { m2: 128_000 }, - modelInputModalities: { m2: ["audio"] }, + // No modality declaration: the member is text-only by catalog default and the + // sidecar does not cover it, so text and image stay genuinely disjoint + // (incompatible_modalities). A DECLARED text-only member would be widened to + // image (isModelTextOnly parity) and no longer be disjoint. }, }, combos: { @@ -5432,7 +5439,9 @@ describe("Codex catalog routed normalization", () => { expect(models.find(m => m.id === "wide-model")).toMatchObject({ contextWindow: 100_000, maxInputTokens: 100_000, - inputModalities: ["text"], + // Declared text-only modalities are sidecar-covered at runtime (isModelTextOnly), + // so the catalog advertises image on top of the configured base. + inputModalities: ["text", "image"], }); expect(models.find(m => m.id === "small-model")?.contextWindow).toBe(64_000); }); diff --git a/tests/vision-eligibility.test.ts b/tests/vision-eligibility.test.ts index 6469b6cb0a..e28d406978 100644 --- a/tests/vision-eligibility.test.ts +++ b/tests/vision-eligibility.test.ts @@ -4,6 +4,7 @@ import type { OcxConfig } from "../src/types"; import { BASELINE_VISION_MODELS, isVisionEligibleModel, + isVisionSidecarConsumer, modelAcceptsImageInput, visionBackendForCandidate, visionEligibleModelOptions, @@ -54,6 +55,24 @@ describe("vision eligibility core", () => { expect(modelAcceptsImageInput(config, candidate)).toBe(false); }); + test("2b. configured text-only rows are consumers, but audio-only rows are not", () => { + const config = configWithProviders({ + test: { + adapter: "openai-chat", + baseUrl: "https://example.test/v1", + modelInputModalities: { + "text-only": ["text"], + "audio-only": ["audio"], + "text-and-image": ["text", "image"], + }, + }, + }); + + expect(isVisionSidecarConsumer(config, "test", "text-only")).toBe(true); + expect(isVisionSidecarConsumer(config, "test", "audio-only")).toBe(false); + expect(isVisionSidecarConsumer(config, "test", "text-and-image")).toBe(false); + }); + test("3a. silent catalog rows fall back to generated metadata (live /api/models shape)", () => { // anthropic / claude-opus-4-6 carries ["text","image"] in the generated table, but live // /api/models rows omit inputModalities entirely. diff --git a/tests/vision-text-only-predicate.test.ts b/tests/vision-text-only-predicate.test.ts index 02577902b6..05574bf9bb 100644 --- a/tests/vision-text-only-predicate.test.ts +++ b/tests/vision-text-only-predicate.test.ts @@ -19,6 +19,10 @@ describe("isModelTextOnly (#1024)", () => { expect(isModelTextOnly(provider({ modelInputModalities: { "vision-model": ["text", "image"] } }), "vision-model")).toBe(false); }); + test("returns false for audio-only modelInputModalities", () => { + expect(isModelTextOnly(provider({ modelInputModalities: { "audio-model": ["audio"] } }), "audio-model")).toBe(false); + }); + test("returns false for unknown models (no evidence)", () => { expect(isModelTextOnly(provider(), "unknown-model")).toBe(false); });