Xeo Forge v1.25.0 — Local-First Agent Workbench
Xeo Forge v1.25.0 — The First-Class Desktop
The desktop build stops being a second-class citizen. Seven phases, eighteen items, one governing law carried from v1.24: verify in the actual desktop runtime → fix → prove live — nothing was declared done because it "looked logical", and every claim below carries its evidence (behavior tests RED-then-GREEN plus six screenshots captured from the real Electron shell, not the dev server).
The operator was being stranded — follow-up and decisions (Phase 3.1)
Traced "the follow-up box only appears after a failure" to three distinct defects, each proven RED by test before the fix: claimTaskForFollowUp refused cancelled tasks (D1); resolveTaskDecision hard-rejected late decisions, leaving the operator with no decision gate, a 409 from the composer, and a 409 from the decision route (D2); and the client ignored the terminal task_status event while waiting for a done that never comes on those paths (D3). All three are fixed: follow-up and decision paths never strand the operator on any terminal state, late decisions are audited as decided_late, and the messages API states honestly what happened. test/work-followup-contract.test.ts — 11 assertions, 7 of them RED before the fix. Commit abe7db1.
Providers stop being a dead end — editing, adding, removing, switching (Phases 2 + 3.2)
The discovery: the provider Edit API (PATCH provider/model, DELETE model, manual model add) always existed — only the UI hid it. The UI now exposes all of it: provider editing with a write-only API-key field (the UI shows an api_key_set signal, never the stored key), manual "add model" by id, and per-model delete inside a 60-model catalog. And the operator no longer leaves a live session to change its brain: an in-session model switcher lives in the governance rail (locked while a run is executing), wired through POST /api/tasks/[id]/model behind a updateTaskModel gate, emitting a model_switch audit event with old model → new model and the timestamp. Running sessions on the same provider are untouched by key edits — the key is read per provider call, never baked into a run. test/model-switch.test.ts — 10 assertions. Commit 05034da.
Sessions get real names (Phases 1.2 + 3.3)
Session titles are now generated from the first real exchange — a deterministic summary, not the raw opener — with bidi-safe word-boundary truncation (no more slice() cutting Arabic/RTL words mid-glyph), and temporal discrimination (today / yesterday / date) instead of a wall of identical rows. Greeting-only openers ("اهلا" fifteen times) stay untitled until the first assistant answer fills a real title; legacy rows fall back through the same truncation. Backed by a tasks.title column migration. Chat and Work both inherit the fix (item 3.3 verified, not assumed). test/session-titles.test.ts — 10 assertions. Commit 70211d5.
Browser Bridge pairing — from four manual steps to one approval (Phase 6.2)
Diagnosis first: "Not connected" with the extension loaded was a real product bug, not user error — the bridge behaved silently when no token was present, and a genuine reconnect-churn bug (the close event of an intentionally-closed socket tore down the new healthy connection every ~2s) made stable pairing impossible. The pairing flow is redesigned: the desktop shows a pairing request and the user approves it in the app — no token copying at all; approval persists and the browser reconnects automatically afterwards; denial closes honestly; the manual token path is demoted to an Advanced disclosure. scripts/smoke-browser-bridge.mjs — 13 live checks. Commit e83c928.
Preview stops lying; external browsing stays consent-first (Phase 6.1)
"Audit the code" failing with no message was diagnosed first and separately, as demanded: it is a task-level provider failure, not a preview defect — but the investigation exposed that the Preview tab had been hiding failures entirely. The Preview tab now shows the classified failure reason taken from the run's own error event. External browsing did not get opened up: the new domain allowlist editor lives in Settings → Runtime with external access OFF by default, explicit per-domain approval chips, and exactly one governance system — the agent layer reads the live bridge policy; no parallel policy path exists. Commit 21786c3.
Sandbox and Subagents get their Settings sections (Phase 5)
Settings grows section 07 Sandbox (the three isolation tiers described verbatim as the executor enforces them — standard honestly labeled "no OS-level isolation", strict as hardened process, docker as real container isolation — with a live Docker probe and honest, actionable guidance when Docker is absent: nothing downloads silently) and 08 Subagents (the four delegation guarantees stated verbatim plus the write-delegation boundary). A new standing rule lands in AGENTS.md §17: any future core governance feature ships with a visible Settings section from day one — never only buried in a task-start form. test/settings-governance-sections.test.ts. Commit 33c2ac7.
The window is ours — frameless shell with a custom titlebar (Phase 1.1)
frame: false plus a custom DesktopTitleBar built from the same design tokens as the rest of the app: brand line, drag region, minimize/maximize/close through IPC only, maximize-state published to the renderer, and a clean null on web so the component cannot leak into the browser build. Mounted in AppShell with explicit height budgets so no surface clips. Works on Windows and Linux — the evidence screenshots come from the Linux shell under Xvfb, and the Windows build ships from the same component. test/desktop-titlebar.test.ts — 6 assertions. Commit c8e18a3.
Design tokens: audited, not refactored for show (Phase 1.3)
The audit found something better than divergence to fix: app/globals.css is the only stylesheet in the repo — there was no desktop/web token drift to unify, because the desktop shell renders the same CSS. The real delta was the OS native frame, which no token could reach — and that is exactly what Phase 1.1 replaced with the tokened titlebar. Recorded honestly in the audit document instead of manufacturing a refactor.
Desktop-runtime evidence — and a real fix it surfaced
Proving things "on the desktop specifically" required a desktop harness, and building it caught a real defect: desktop:dev was unbootable — the dev-server path pointed at the app-router directory instead of the .next/standalone output the Electron shell loads. Fixed, plus desktop:prepare packaging. The capture harness (scripts/desktop-live-capture.mjs) boots the real Electron shell under Xvfb, seeds state through the live API, and captures six screenshots to download/desktop-evidence/: 01 titlebar home, 02 sidebar session titles, 03 work surface (failure handling + composer + governance rail + MODEL section), 04 Sandbox tiers, 05 Runtime pairing, 06 Providers. Commit 72e9fef.
What is deliberately NOT in this release
- Write-capable subagent delegation — no code, by order and by law. The design the README demanded now exists as a draft:
docs/subagent-write-concurrency-design.md— a per-task write ledger (leases + file generation counters + read-stamps), a refuse-never-merge conflict policy (stale writes fail closed with attribution; the agent re-reads and adapts; repeated conflicts lock a subagent out of the path; parent outranks subs), andfile_mutationaudit events making every write attributable (agent: parent | sub-N, generations, hashes). It ships as a document only — every line of code waits for the owner's explicit approval of the design. README gap #5 now points at the draft; the Settings → Subagents page boundary statement is unchanged. - Per-subagent follow-ups and per-subagent model overrides (gap #6): loop-level design, not rushed in.
- Split/collapse layout controls for Chat and Work panes (gap #7, Phase 7, lowest priority): not built, disclosed.
Verification honesty
- Full vitest suite: 934/934 green across 55 files (891 at v1.24.0 → 934; +43 assertions across the five new/expanded contract suites).
tsc --noEmitclean; ESLint clean on every touched file. CI is the canonical gate. - Live desktop evidence: six screenshots from the real Electron shell under Xvfb, stored in
download/desktop-evidence/, plus the smoke suites (smoke-browser-bridge.mjs13 checks,smoke-desktop.mjs). - Every one of the nine commits was pushed immediately after its proof; the phase-by-phase evidence record with commit hashes lives in
docs/audits/2026-08-29-desktop-parity.md.