Changelog
v0.2.39
Fixes
- app.foxl.ai white screen fixed. A vite chunking quirk was bundling two copies of React into the relay-only build; the second copy hit the `__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE` symbol mismatch on boot and rendered nothing. The build now dedupes React across the workspace symlink and lands all of `react`, `react-dom`, and `react/jsx-runtime` in a single `vendor-react` chunk. Verified live: zero `CLIENT_INTERNALS` references in `vendor-radix` / `vendor-ui` / `index` chunks.
- Sidebar borders no longer render black, electron no longer starts in dark mode. `index.css` was using `@import '@foxl/ui/styles.css'` after `@font-face` and `@tailwind` directives, which PostCSS silently drops. Every `--background` / `--border` / `--sidebar-*` HSL var was undefined; tailwind's `border-border` resolved to `hsl()` (invalid) and the browser fell back to black. Tokens are inlined into `index.css` again so the rule order does not matter. `@foxl/ui/styles.css` stays the canonical source for forge/web.
Cross-product
- Foxl Code (code.foxl.ai) shipped as a separate product on the same monorepo. Foxl Desktop and Foxl Code share `relay.foxl.ai` for auth and the credit pool but version independently. See `VERSIONING.md` at the monorepo root for the full strategy. Foxl Code release tags carry the `forge-v` prefix so the two products do not interfere.
- `packages/ui` now exposes shared layout primitives (`AppShell`, `NavSidebar`, `TopBar`, `Breadcrumbs`) consumed by Foxl Desktop and Foxl Code so the chrome stays in sync between the two products.
- React bumped to 19. `foxl/apps/web` and `packages/ui` now agree on `react ^19.0.0` and `react-dom ^19.0.0`; recharts bumped to `^3.8.1` to keep peer-dep alignment. No user-visible UI change beyond the rendering fixes above.
Foxl Code (code.foxl.ai)
- `/tasks` rebuilt around a bottom-pinned composer. Active tasks (queued/running/review) and recent five closed tasks render above the composer on the empty-state view, with an 8s background poll so status changes show up without a manual refresh. Pressing Enter now surfaces a clear inline error if no repo is connected or no paste is typed - it used to swallow the keystroke silently. Submit + topbar GitHub buttons flattened (no shadow / border).
- `/repos` populates after install. The orchestrator gained a state-bound install URL handler, a manual `POST /api/installations/sync` endpoint, an `adopt`-by-owner endpoint for the legacy install path, and webhook handlers for `installation` + `installation_repositories` so future installs sync without manual action. The Repos page auto-syncs once when installations exist with no repos and exposes a "Sync repos" button. Empty-install state shows both "Sync repos" and "Configure on GitHub".
- Workspace page added at `/workspace`. Per-user view of the shared S3 Files mount (`/mnt/repos`) - finder-style lazy tree, resizable sidebar, search across loaded files, context menu (Copy path), text / binary preview with a 1 MiB inline cap. Shape ported from Foxl Desktop's WorkspacePage, data layer swapped for the orchestrator's new `/api/files` (S3 ListObjectsV2 + GetObject via aws4fetch). Read-only - the AgentCore container owns writes. Tenant isolation is the bucket prefix `users/{user_id}/{org}/{repo}/...` enforced server-side.
- Terraform shared S3 Files mount enabled. `forge/infra/terraform` now provisions the VPC, NAT gateways, S3 bucket + S3 Files file system, mount targets per AZ, IAM service role, and the access point. AgentCore endpoint flipped from PUBLIC to VPC mode so the shared cache is reachable. Updated CLAUDE.md to reflect that Pro/Ultra share one mount with prefix-based tenancy (was previously documented as Enterprise-only).
v0.2.38
Gateway "Enable remote access" actually works now
- Click was a no-op when no access token existed. The `tunnel-toggle` IPC handler returned `{ok: true}` immediately and only kicked off `connectTunnel(at, rt, ...)` if `at` was truthy. If the user wasn't signed in, `at` was empty, the function silently bailed, and the button looked frozen forever.
- Renderer polls `tunnelStatus()` for up to 15 seconds after a successful start, refreshes `fetchAll()` once the WebSocket attaches, and shows a clear timeout message if it never does.
- Loading state on the button - spinning Power icon + localized "Connecting…" copy + disabled while awaiting, so a click no longer looks like a no-op.
Gateway page i18n
- Gateway "No desktop" copy now resolves in every locale. v0.2.37 added new English strings (`relay_no_desktop_desc_v2`, `relay_turn_on_desktop`, `relay_desktop_not_connected`, `relay_desktop_not_connected_desc`, `relay_enable_remote_access`) but didn't add them to the other 9 locale files - users on Korean / Japanese / Chinese / etc. saw raw key names. v0.2.38 ships translations for all 10 locales (en, ko, ja, zh, es, fr, de, pt, ru, ar) plus the new connecting/error strings.
v0.2.37
Sidebar tooltips
- Collapsed sidebar (Cmd+B) now shows the conversation title as a dark tooltip on hover. Previously the title only rendered when expanded; with the sidebar collapsed to icons there was no way to read which conversation was which. Tooltip mirrors the Cmd+B nav-item style.
- Top-level nav items and the settings group also surface their label as a tooltip when collapsed - Chat, Workspace, Usage, Settings, Account. Same dark popover styling.
- Truncated rows show a hover tooltip with the full text in both the sidebar conversation list and the Workspace file/folder tree. The tooltip only opens when the inner span actually overflows (`scrollWidth > clientWidth`); short titles don't trigger it. 700ms delay so it's not noisy.
Workspace polish
- Editor header reads `MEMORY.md` instead of `WORKSPACE / MEMORY.md`. The redundant `WORKSPACE /` crumb prefix was removed - the sidebar already says WORKSPACE, the breadcrumb just doubled the label.
- File and folder rows restored to v0.2.15-era density (`text-sm` / `py-1.5`). The earlier compaction to `text-[13px]` made the tree feel cramped against the rest of the app.
app.foxl.ai conversation pagination fix
- Sidebar conversation list now pages past the first 50. On app.foxl.ai (relay-only mode with desktop tunnel online) the IntersectionObserver attached to the conv scroll container saw `convScrollRef.current === null` even after the list had rendered, so the "load more" sentinel never fired. Switched to a callback-ref + state pattern so the effect re-runs the moment the scroll element actually mounts, plus replaced the IntersectionObserver with a plain `scroll` event listener for reliability inside the nested hidden-scrollbar container.
Gateway page "No desktop" actions
- Web (app.foxl.ai): replaced the lone Download button with two CTAs - "Turn on desktop" (deep-links to `foxl://open` to launch the installed app) and "Download for macOS" (fallback if the app isn't installed yet).
- Desktop: when looking at /gateway from inside the installed app, the page no longer suggests downloading the desktop app (you're already in it). Instead it surfaces an "Enable remote access" button that flips the tunnel toggle, with copy explaining what relay connection gets you.
v0.2.36
max_tokens overhaul (Claude Code parity)
- Every Claude path now requests the model's full output capacity. Previously the desktop chat sent `max_tokens: 4096` whenever thinking was off, the agent layer hard-coded `8192` for every provider, and the relay-only web app capped output at `64000` regardless of model. Long generations were silently truncated. Now Opus 4.7 / 4.6 ask for 128k, Sonnet 4.6 / Haiku 4.5 ask for 64k, matching what the Claude Code CLI sends.
- Agent layer reads from the catalog instead of a hard-coded constant. `strands-agent.ts` now resolves `entry.maxOutput` per model for Bedrock, Anthropic, OpenAI, OpenAI-OAuth, Claude Code OAuth, and the foxl relay path.
- Claude Code OAuth catalog corrected. The 32k metadata was misleading - real requests already went out at the catalog max. Updated to 128k (Opus) / 64k (Sonnet) so `/context` percentages and autocompaction triggers reflect actual behavior.
Relay billing fixes
- Opus 4.7 unblocked on the relay. D1 `model_pricing` was missing a row for `claude-opus-4-7`, so `meter.canRequest()` rejected every request with "Unknown model" - even though the registry listed it. Migration `0011_opus_4_7_pricing.sql` adds the row.
- Opus pricing corrected to Anthropic's published rates. Catalog and pricing previously inherited Opus 4.1's $15/$75 per MTok. Both Opus 4.7 and Opus 4.6 are $5/$25 per MTok. Updated `model-catalog.ts` and the local D1 row.
Sidebar UX fixes
- Cmd+N (and the File → New Chat menu item) always opens a fresh chat. Previously, pressing Cmd+N from a non-chat page resurrected the previous conversation because `navigate('chat')` re-pushed `/chat/
` from a stale closure value, and the popstate handler reloaded that conversation. - Clicking a different conversation actually opens that conversation. Selecting B from the sidebar after viewing A used to re-load A - same closure-staleness bug routed `/chat/` instead of `/chat/`. Now the click goes through `navigate('chat', { conversationId: id })`, which uses the explicit clicked id.
- A → B switching no longer flashes empty state. Conversation messages stay on screen during the fetch; the new conversation's messages swap in once they arrive. No suggestions/greeting flicker between selections.
- Conversations are no longer highlighted while you're on Workspace, Usage, or other non-chat pages. Sidebar conversation rows now check `activeNav === 'chat'` before applying the active style. Clicking the top-level Chat nav from those pages opens a new chat instead of resurrecting the previously-selected one.
- Infinite-scroll loader no longer spins forever when conversation pagination fails. On app.foxl.ai (relay-only mode) the desktop conversation list endpoint isn't reachable; the failed fetch used to leave the sentinel mounted, retriggering the IntersectionObserver in a loop. Now any non-OK response or thrown error sets `hasMoreConversations: false` and the spinner goes away.
v0.2.35
foxl.ai migrated to Next.js SSG for SEO
- Google can now index foxl.ai. The landing page was a pure CSR React SPA - crawlers saw empty HTML. Migrated to Next.js 15 App Router with static export. Every page is pre-rendered as full HTML with proper meta tags, canonical URLs, and Open Graph data.
- JSON-LD structured data for sitelinks. Organization, WebSite (with SearchAction), SoftwareApplication, and SiteNavigationElement schema in root layout. Signals Google to show the site hierarchy (Blog, Pricing, Security, Changelog, Docs) as sitelinks beneath the main search result.
- sitemap.xml with 22 URLs. All static pages + 17 blog posts. robots.txt allows AI crawlers (GPTBot, ClaudeBot, PerplexityBot) and blocks training-only crawlers.
- worker.js simplified. Removed the entire SPA routing layer (HTMLRewriter meta injection). Next.js handles per-page metadata natively. Worker now only handles security headers, llms.txt proxy, and old .html redirects.
- Blog preserved exactly. Same thumbnail grid, pagination, category filters, search, and individual post layout as before.
Landing page redesign
- Pricing moved to dedicated /pricing page with detailed credit breakdown, BYOK explanation, local model info, and FAQ.
- Security section redesigned. Clean 3-column architecture diagram with vertical mobile connectors, prose-style feature descriptions replacing card grid.
- "What people ask" replaced with "What other agents can't do." 6 quote-heavy cards condensed to 3 focused differentiators: mobile coding, 24/7 background agent, multi-provider single interface.
- FAQ trimmed from 11 to 5. Ordered by user priority (features, competitors, pricing, privacy, mobile access). First item open by default. Links to docs/Discord for more.
Agents page bug fixes
- Running agents no longer flash as "completed" the moment the detail view opens. AgentChatView receives `initialStatus` from the Agents page (source of truth) so it starts with the correct status the user already saw, eliminating the race where in-memory state clears before the detail view fetch resolves.
- Assistant responses stop rendering twice. `broadcast()` was double-recording every agent event to DB (once inside broadcast, once in the runAgent callback). Removed the redundant call. Also, `runAgent()` no longer emits intermediate `message` events for no-tool turns - only `complete` carries the final response.
- New agents started from the Agents page now show their progress events. Session ID minted up-front so WS broadcasts and DB rows share one ID.
v0.2.34
Relay, Schedules, Feed, Activities, Logs redesigned
- Unified hero-stat pattern across five pages. Each page now opens with a single headline answer ("12 active schedules", "Live: streaming to 2 clients", "9 unread items", "247 events", "1,248 lines streamed") plus three supporting stat cells — the same rhythm as Agents and Usage. Replaced the nested Card-in-Card chrome with flat sections separated by dividers.
- Relay (Gateway) rebuilt. Flat device topology with a single connected-desktop row, inline quick-actions (update / restart / unpair), web and mobile clients as tree branches off the bottom edge, and audit history in a collapsible scroll panel below. Tunnel "Remote access" is now a pill toggle next to Refresh instead of a standalone card. Legacy local gateway drops into one collapsible at the bottom for advanced users. Cleaned up dead code from the deleted pairing-code flow.
- Schedules stopped pretending to be a spreadsheet. Dense table became a divided list sorted enable-state-first, then by upcoming-run proximity. Each row leads with a status dot, the human-readable cron (raw expression lives in the tooltip), and prefers "next in 18m" over stale "last ran 3h ago". 7-day success rate and average runtime are now prominent hero stats. Recent Runs uses pill inputs and chip filters, grouped by day bucket. Heartbeat rows are labeled system-managed and can't be deleted.
- Feed reads like an inbox. Segmented pill toggle (Inbox / Archive) with per-view counts. Each item is a single clickable row — click archives, click again unarchives. Urgent dot goes amber, unread dot stays foreground, archived items fade. Hero leads with unread count and amber urgent subtext when applicable.
- Activities chip filter. Time range matches Agents' pill style; type filter is a horizontal chip bar derived from actual event frequencies so users can focus on tool_start vs thinking vs error without a dropdown. Outer Card wrapper removed so the list can breathe.
- Logs terminal has a frame that doesn't fight it. Hero shows stream state (live vs paused+buffered count) plus error/warning/info totals. Level filter is a segmented pill; source filter is a chip row behind the Filter icon. Console is one bordered rounded-xl panel instead of a Card wrapping another Card.
- UX ⇄ API alignment. Killed the duplicate "Connected Desktop" surfaces — the relay is now the single source of truth for devices and the local gateway is explicitly flagged as advanced/optional.
- Mobile. Every page is single-column on narrow viewports, pill inputs wrap cleanly, hero drops to stacked layout, toolbar icons stay tappable (min 32px hit targets), horizontal overflow killed with `overflow-x-hidden` at every shell.
- i18n. 100+ new keys across all 10 locales (en / ko / ja / zh / es / fr / de / pt / ru / ar). Key parity check passes at 97–100% per locale.
UX polish pass
- Consistent semantic tones across hero stat blocks. Warning stats were mixing `text-foreground`, `text-yellow-*`, and `text-amber-*` across Feed, Activities, and Logs — every page now uses the same `warn=amber / danger=destructive` pair, so a 2 next to "Warnings" reads the same wherever it appears.
- Feed rows stop auto-archiving on a misclick. The whole row used to be an archive button with a stealth time→action crossfade on hover; now the row is a passive list item and an explicit "Archive" hover-only pill handles the action. Unread dot uses `bg-primary` instead of pure foreground so it matches the brand.
- Refresh / Auto collapsed into one control on Activities. The separate Refresh button next to the "Auto" pill was redundant — now the toolbar shows a live-pulse dot when auto-refresh is on (click to pause) and a Refresh icon when it's off. State change is explicit, not dual.
- Activity hero totals match what the list shows. Hero used `dbEvents` while the list rendered `mergedEvents` (db + live), so in-flight live events were invisible to the tally and the type chip tallies. Both now derive from the same unfiltered merged stream.
- Logs source filter comes out of hiding. The source chip row used to live behind a separate Filter icon toggle — only discoverable via curiosity. It's inline now, rendered whenever the server reports sources. One less click, one less icon.
- Schedules filter rail simplified. Status is a segmented pill (the axis users actually filter on); Range / Type / Sort / Schedule-picker drop to auto-width dropdowns. Triple "shown of loaded of total" counter replaced with a single "N of M" that only shows when a filter is active.
- Destructive actions gate on confirm. Schedules detail dialog and row dropdown no longer delete on a single click — a confirm prompt names the schedule so users see what they're about to lose.
- Gateway audit rows stop showing a Clock icon next to every timestamp. Redundant with the time on the right side of the row. Dead `relayConnected` / `tunnelConnected` state and an unused `Wifi` import removed.
- Schedules day-group labels respect locale. "Today / Yesterday / This week" were hardcoded English; now they go through `chat.today / yesterday / this_week` so they follow the UI language.
- Overview "Top tools" aligned to 24h window. Hero counter is 24h but the top-tools list was pulling lifetime stats labeled "Most used" — both are now 24h so the page tells a single consistent story.
v0.2.33
Workspace on mobile actually scrolls now
- Mobile Workspace stopped clipping the editor at ~50% of the viewport. The previous layout cut the internal `h-full` and `overflow-hidden` at the md breakpoint so the page could rubber-band on iOS, but the side-effect was that the frontmatter + editor textarea got pushed off-screen below the sidebar header. The page now clamps to `100dvh - 2.75rem` (app header) on every breakpoint and manages scroll internally — the tree scrolls inside the sidebar, the textarea scrolls inside the editor, and mobile gets the same full-viewport behavior as desktop.
Demo preview caught up with the Usage rename
- Demo/preview mode's "Connect your desktop to use Overview" card now says "Usage" (plus a fresh one-line description about token burn / cost / model breakdown). The rename in v0.2.32 only covered the live app; the preview copy still referred to the page by its old name. Updated across all ten locales.
Landing page redesign
- Demo sidebar now matches the real app exactly. Icons corrected to match navigation.ts (Plug for Integrations, Clock for Schedules, Activity for Feed, Settings gear, Users for Agents, BarChart3 for Usage). Every sidebar item is now interactive with dedicated mock content panels for Usage, Schedules, Feed, and Settings.
- Foxl blue brand color system. Added `--brand` CSS variable (desaturated blue from the Foxl logo) with light/dark mode variants. Applied as a gradient accent on the hero headline and subtle radial glows behind the hero and CTA sections.
- Real provider icons in demo. The integrations grid now shows actual Microsoft, Slack, GitHub, Google Workspace, Notion, and Obsidian icons instead of placeholder letters.
- Logo in demo uses `logo-title.svg` with correct size and spacing matching the actual app sidebar.
New tray icon
- macOS menu bar icon redesigned with clean inner line separation. The new 2-piece SVG clearly distinguishes the left wing from the right body with a visible gap. Sized to 80% of the tray canvas for proper macOS menu bar breathing room.
v0.2.32
"Overview" is now "Usage"
- The sidebar, page title, breadcrumb, and every localized label switched from "Overview" to "Usage" across all ten translations (en, ko, ja, zh, es, fr, de, pt, ru, ar). The page was never really an overview - it was the place users went to see token burn, cost, and model usage. The new name says that out loud. The internal route key stays `overview` so existing bookmarks, localStorage entries, and deep-links keep working.
Agents page editorial redesign
- Agents leads with a headline number instead of admin chrome. The old header stacked a small `h2 Agent team` + subtitle + a row of badges (`1 active / 2 done`) that all competed for attention. The page now opens with one large tabular number for running agents ("2 agents running") plus an inline three-stat rail (Active / Completed / Failed), same uppercase-tracked labels as the Usage dashboard.
- Run bar became a single pill input. Goal field is now a rounded-full search-style input with a leading Send glyph, Run sits as the primary action, and Stop-all + Pixel-office toggle collapse into rounded-full secondaries. No more cramped `h-8` row stuck between the status badges and the list.
- Session history dropped the per-row Card chrome and rebuilt as a `
- ` with `divide-y` separators and uppercase date group labels ("ACTIVE / TODAY / THIS WEEK"). Status filters light up with `bg-muted` instead of swapping badge variants on every click. iter / tools / duration / error counts use `tabular-nums` everywhere so the row doesn't jitter as numbers tick up.
- Consistent gutters. Agents now uses `px-4 sm:px-8 max-w-6xl mx-auto`, matching Usage / Settings / Skills / Integrations so mobile stops reading edge-to-edge.
- Every hardcoded English string moved to i18n (`agents.starting`, `agents.agent_running_singular`, `agents.no_sessions`, `agents.err`, etc.) and the translation table got filled out across all ten locales.
Workspace gets the editorial treatment
- Workspace page dropped the Card wrappers and now reads as one continuous surface: file tree on the left separated by a single border, editor on the right. Header uses the same uppercase-tracked label + tight title as Overview, file path renders in mono, and the save button sits inline. Frontmatter pills switched to compact `key value` badges.
- Workspace action icons moved to the app header, matching Chat's pattern. Search / Open folder / Refresh now sit in the top header bar on the right, so the sidebar owns a single editorial label row instead of two competing action strips. The empty-state panel lost its large folder glyph in favor of a tiny uppercase `WORKSPACE` chip plus the hint copy, and the editor pane became edge-to-edge without border/ring chrome so it reads like a real Linear/Vercel writing surface.
- Workspace tree now shows a spinner during the initial fetch instead of two bare border lines with a void between them. The spinner only fires before `treeLoaded` flips true, so manual refreshes don't flash it over a populated tree.
Overview loads faster
- Dashboard query does fewer scans. The `/api/dashboard` endpoint was running seven separate `SELECT COUNT(*)` statements across `conversations` and `messages`, most of which had no usable index for their `WHERE created_at >= ?` / `WHERE role = ?` clauses. Replaced the per-metric counts with two CASE-sum aggregates (one per table) and added `idx_messages_created`, `idx_messages_role_created`, `idx_conversations_created`, and `idx_tool_history_name`. On a 12K-message / 365MB database the overview request dropped from a visible pause to effectively instant.
- Overview caches its last render. Navigating away and back used to show a spinner for every round trip. The page now reads from `sessionStorage` on mount and refreshes silently in the background, so subsequent visits are immediate.
- Removed the "Uptime 15m" badge from the hero row - it was visual noise next to the token total.
- Usage Explorer range chips no longer clip off the left edge.
Overview + Usage Explorer redesign
- Overview got a proper dashboard instead of four identical admin cards. The page now leads with a hero row - last-24h tokens as one large number plus cost, a live uptime dot, and a "View Usage Explorer" chevron - and demotes conversations / messages / tool calls into a compact three-column strip next to it. Activity, Model usage, and Top Tools became bare typographic sections separated by horizontal dividers; the bar chart and list rows have more room to breathe without card chrome eating 48px on every side.
- Usage Explorer uses the same editorial layout. Breadcrumb + refresh on one row, chip-style range picker under it, 2x2 summary stats on mobile and 4-col on desktop, per-model rows with an inline progress bar and a 4-up breakdown (input / output / cache write / cache read) that stays on 2 columns on small screens. Custom date range inputs now flex to full-width on mobile instead of spilling off-screen.
- Numbers use `tabular-nums` everywhere so columns don't jitter when values change during live polling.
Custom tool env-vars editor now behaves like a real textarea
- Enter, Space, and blank lines work again in the MCP env-vars box. Editing an MCP tool's environment variables used to feel broken: pressing Enter did nothing, trailing spaces disappeared, and the cursor would jump every keystroke. Root cause: the textarea re-parsed the whole text on every keystroke, dropped the parsed map back into props, and the parent re-serialized it from scratch, which stripped newlines and whitespace. The editor now holds the raw text locally and only pushes the parsed map up, so typing feels normal. Comments (`# ...`) are also preserved across saves.
Heartbeat can run on any schedule now
- Feed Generator frequency is no longer capped at 6 hours. Settings -> Feed Generator now shows preset chips (15m, 30m, 1h, 3h, 6h, 12h, Once a day) plus an "Other" option that takes hours + minutes directly, so you can pick 17h 30m, once every 2 hours, once a day, or anything up to 7 days. The preset chips replace the old single-select dropdown. Custom values are clamped to a minimum of 1 minute and a maximum of 7 days to keep the scheduler sane.
- "Once a day" and "12 hours" are now first-class presets. Users who just wanted a morning briefing had to fake it with `7am cron` schedules; now they can leave the Feed Generator on Heartbeat and pick the interval that matches their actual cadence.
v0.2.31
`exec` is back
- `exec` and `process` are now core tools again. Some users reported the agent refusing to run a simple `git status` and falling back to the heavier terminal PTY path every time. Root cause: `exec` and `process` were gated behind the `exec-tool` skill, and a single `enabled: false` line in a synced `SKILL.md` was enough to silently delete both from the tool registry at startup. They are now unconditional built-ins alongside `code_search`, `browser`, `browser_extension`, and `web_fetch`. Approval gating still applies. Only OAuth-backed integration tools (Outlook, Slack) remain skill-gated, since they genuinely require a connected account to work.
v0.2.30
Critical hotfix (rebuild)
- Fixed: app would not start after install. A route-pattern change that shipped with the first v0.2.30 build used `/api/workspace/:filename(*)` syntax, which is rejected by the Express 5 path parser at boot. Every launch crashed before the embedded server bound its port, so the app sat on a retry screen forever (Attempt 1, 2, 3…). The wildcard routes for workspace files and webhook triggers are back on the working `*filename` / `*splat` form. If you were stuck on the loading screen, updating to this build fixes it.
- New startup screen. When the local server is still coming up or has crashed, the loading screen now shows phase, port, PID, the last server error line, a rolling log tail, and three buttons: Restart server, Open logs, Copy diagnostics. Replaces the previous "Attempt N…" spinner that gave no indication of what was wrong.
Faster search across all your conversations
- Cross-conversation search now loads as you scroll. Before, Cmd+Shift+F only ever returned 20 results - anything past that was silently dropped, which was especially painful on large archives. The sidebar and the mobile search overlay now fetch the next page automatically when you scroll near the bottom, no extra "Load more" tap needed, until there's nothing left to match.
More auto-update fixes
- Clicking "Install Now" on the macOS native notification no longer crashes the app. The notification's action handler was focusing the main window before applying the update, which raced with the server shutdown that runs during quitAndInstall. The install path now defers to the next tick and skips the focus step so the update applies cleanly.
Strands SDK 1.1.0
- Upgraded `@strands-agents/sdk` from 1.0.0 to 1.1.0. Brings proactive context compression, interrupt support for human-in-the-loop workflows, per-tool timeouts, retry strategies with configurable backoff, and MCP server log surfacing. Agent behavior is unchanged for existing usage; the new features will roll out in follow-up versions.
Use Foxl in your language, on any screen
- Integrations, Skills, Channels, and Tools pages now speak your language. Every page under the Integrations tab - including the Skills library, Channels, System tools, and Custom tools - reads in all 10 supported languages instead of always showing English. Section headings, filter buttons, search boxes, connect dialogs, error messages, and OAuth permission lists are all translated. Settings page too.
- Mobile-friendly layout across the whole app. Pages used to overflow on phones - long tab rows cut off, filter buttons got squished, dialogs didn't fit the screen. Now the tab bar at the top scrolls sideways with a soft fade on the right edge (so you can tell there's more), filter pills scroll the same way instead of shrinking their text, dialogs fit inside narrow screens, and search + filter rows stack vertically when there isn't room side-by-side.
- Preview mode on app.foxl.ai now matches the real app pixel-for-pixel. When you sign in without a desktop running, the sidebar shows every page (Agents, Workspace, Schedules, Feed, Relay, etc.) instead of only Chat + Settings. Clicking any data page shows a simple "Install Foxl" panel. The Install button auto-detects Mac, Windows, or Linux and shows the matching platform button.
Notes for developers
- New i18n blocks in the locale files: `integrations`, `skills_page`, `channels_page`, `tools_page`, `settings_page`, `header_tabs`.
- Demo components live in `apps/web/src/components/demo/` (`ConnectDesktopProvider`, `DemoPage`, `DemoIntegrations`) so preview surfaces stay separate from the real pages.
- None of the preview-mode behavior runs in the Electron desktop build - `IS_RELAY_ONLY` is compile-time false there.
v0.2.29
Auto-update fixes
- Mac "Install Now" button actually works now. Clicking "Install Now" in the native macOS notification used to do nothing. Fixed.
- Stale "update ready" notification cleaned up after install. After Foxl auto-updated itself, the old "ready to install" row would hang around in the notification bell and the button did nothing when clicked. Now the notification disappears automatically once the update has been applied.
Onboarding polish
- Skill library count in onboarding replaced with "Extensible skill library". The skill count kept rotting (73 → 68 → 35) every time we trimmed the library. Removed the number so it stops going stale.
Small fixes
- Clicking "Manage tools & MCP servers" in the HUD now goes to the right tab. It used to land on an older Tools page without the top tabs visible.
- Workspace file rows click on Windows again. Left-clicking a file in the Workspace sidebar was broken on Windows; fixed.
Workspace: real folder tree
- Workspace sidebar is now a proper folder tree you can expand and collapse. Replaces the three fixed sections (Core / Daily Memory / Sessions). Folders sort before files, hidden files stay hidden, and the tree remembers what you had open when you come back. Non-markdown files open in Finder instead of the editor.
- Workspace remembers the last file you opened. Reopen Workspace and you land on the file you were editing, with the folders around it already expanded.
v0.2.28
Cleaner System and Custom tools pages
- System and Custom tools pages redesigned to match Integrations and Skills. Centered title, pill-shaped search, and category-grouped rows instead of the old card grid. Built-in tools are now grouped by type (Memory, Files, Web, Browser, etc.) so the list reads at a glance.
- Tool preset is now a dropdown. The four big preset cards became a single compact dropdown next to the search bar.
Sidebar restructure
- Tools moved under the Integrations tab. "Built-in" and "Custom" tools now live as two tabs inside Integrations (next to Integrations, Skills, Channels) instead of having their own sidebar entry. URLs: `/system` and `/custom`.
- Agents tab no longer shows sub-tabs. Agents used to have a Tools sub-tab next to it - that's gone.
Tray menu fixes
- Menu bar dropdown now shows Recent chats and 7-day usage in the packaged app. This worked in development but was silently blank in the installed version - fixed.
- Clicking the menu bar icon no longer pops the window open. It opens just the dropdown. Use "Open Foxl" in the dropdown to bring the window back.
- First click after launch no longer shows an empty menu while the recent-chats data is still loading.
Skills cleanup
- Removed the "Trigger" field. The `trigger: manual / schedule / webhook` field in skills never actually did anything (scheduling lives in the Schedules page, webhooks go through channels). Removed from the skill editor and from the UI.
app.foxl.ai URL fixes
- Cleaner URLs when you open Skills, Channels, System, or Custom directly. No more `#skills`, `#tools`, etc. hanging off the URL.
- Direct links to /agents, /integrations, /feed now work on app.foxl.ai. Before, refreshing on these pages would bounce you back to Chat.
Landing page demo mock updates
- Desktop and mobile mocks on foxl.ai match the current app. Updated sidebar groups, model selector label ("Opus 4.7 Thinking"), Integrations/Skills/Tools panels rebuilt.
- Subtle parallax tilt. The mocks lean toward your cursor when it's in the surrounding whitespace, snap flat when hovering over the mock itself.
v0.2.27 blog post
- Published the v0.2.27 release notes as a blog post: "Trimming the Sidebar, the Skill Library, and the Tray."
Misc
- Pricing and "Try it out" sections on the landing page now visually separate from the sections above and below.
- SEO metadata updated on app.foxl.ai and docs.foxl.ai for Claude Opus 4.7.
v0.2.27
Integrations / Skills / Tools: cleaner separation
- Sidebar reordered: Integrations → Skills → Tools → Schedules. The flow now reads left-to-right: connect a service → what the agent can do → when it runs.
- Integrations page only shows real third-party services. Before, anything with a "requires" field showed up here - including local document processors (PDF, Excel, etc.) and developer tools (exec, git, browser). Those all moved to the Skills page. Integrations is now just the OAuth connectors (Microsoft 365, Slack) and external SaaS skills (Notion, GitHub, Google Workspace, Obsidian, Spotify, etc.).
- Git removed as a built-in tool. The dedicated git tool was redundant - the agent handles git fine through the shell (`exec`) tool, which gives more flexibility for pipes and custom flags.
Integrations page polish
- Rotating hero at the top now shows skills too, not just OAuth connectors. Each row gets its own gradient and sample prompt (e.g. "Review my pull requests and flag regressions" for GitHub).
- Microsoft 365 and Slack open a proper connect dialog first. Before, clicking Connect immediately kicked you out to the browser. Now you see a preview with the permissions being requested (and Slack's workspace input) before the OAuth flow starts.
- Channel icons show up properly. Every channel row was showing two-letter initials instead of its real logo - fixed.
- Connected channels no longer show a redundant "+" button. Just Stop / Edit / Remove.
Slimmer skill library
- Skill library trimmed ~60%. Deleted 32 rarely-used skills (Apple Notes, 1Password, various specialty tools). 21 commonly-used skills (browser, code search, exec, Notion, GitHub, Google Workspace, Outlook, PDF, etc.) stay enabled by default. Everything else defaults to off so they don't clutter the agent's context.
Streamlined tray menu
- Menu bar dropdown trimmed to essentials. Clicking the Foxl icon in your menu bar now shows: 3 most recent chats (click to jump right to the conversation), 7-day usage summary, then New Chat / Open Foxl / Quit Foxl. Removed rarely-used toggles (Stop Agent, Dark Mode, Always on Top, etc.).
Pricing restored
- Pricing section back on foxl.ai. Pro ($20/mo) and Ultra ($200/mo) plans, Credit Top-up, and the upgrade card in Account are all visible again. The brief "billing disabled" freeze from April is over.
Credit progress in the sidebar
- Click your avatar to see your credit usage. Compact progress bar showing how much of your monthly credits you've used, with a Pro/Ultra tier badge. Bar turns amber above 80% and red above 95%. Free-tier users get an "Upgrade" shortcut.
Smaller fixes
- Logos and icons render at full opacity everywhere. Removed the various `opacity-50` / `opacity-60` / `opacity-70` dimming on the Foxl logo, platform icons under the download buttons, and the landing page's channel logos.
- Docs site refreshed. Model pages, FAQ, and credits pricing now reference Claude Opus 4.7 as the flagship.
- Landing page SEO refreshed. Updated titles, meta, and structured data to reflect the current product (Opus 4.7, Windows support, Pro/Ultra plans).
v0.2.26
New Foxl app icon (macOS + Windows + extensions)
- New Apple-squircle brand mark. Replaced the orange polygonal fox with a dark Apple-style squircle (dark monochrome fox silhouette + blue crescent accent). Canvas/padding match the previous icon layout (100px margin inside 1024 canvas), so the app tile reads at the same size on the Dock and in Finder.
- Covered every surface: `icon.icns` (macOS Dock), `icon.ico` (Windows NSIS + taskbar, multi-res 16/32/48/64/128/256), `icon-256/512.png`, Windows installer `build/icons/*.png` (16-512), Chrome extension toolbar icons (16/32/48/128), and the web PWA `apple-touch-icon.png` + `icon-192/512.png`.
- macOS menubar tray now renders from a new `menubar.svg` (background-removed wordmark) at 22x22 standard and 44x44 retina, sized at ~80% of canvas so the tray icon visually matches surrounding menu-bar glyphs instead of looking oversized.
- Unified brand across favicons and sidebar logos. All favicon SVGs (`favicon.svg`, `favicon-home.svg`), sidebar logo variants (`logo-color/dark/light/title/tray.svg`), web PNG favicons (`favicon.png`, `favicon-16x16.png`), and landing-page favicons now use the new transparent mark — web app, landing, and docs all share the same brand asset.
Message queue drains as a single turn
- Multiple queued messages now combine into one turn. When you piled up several messages while the assistant was streaming, the old drain logic pulled them off the queue one at a time and ran a separate end-to-end turn per message — so three quick "also", "and", "oh one more thing" messages meant the model answered your first message fully, then your second, then your third, each without seeing the others. Drain now takes the whole queue for the current conversation on each wake-up, joins the entries with a blank line, and sends them as a single user turn. Matches Claude Code's behavior where rapid follow-ups land as one message.
- Applied to all three chat hooks: `useStreamingChat` (local), `useRelayChat` (relay-only / app.foxl.ai), `useTunnelChat` (remote tunnel).
v0.2.25
Generated image context menu
- Copy Image and Save Image As now work. Right-clicking a generated image (either the inline thumbnail or the zoom modal) and choosing "Copy Image" or "Save Image As..." was silently failing before. Two root causes: (1) the renderer's `fetch('file://...')` is blocked by the default `webSecurity: true`, so the saved-to-disk path fallback rejected with a network error; (2) after our async `fetch` → `createImageBitmap` → `canvas.toBlob` round-trip, the browser had already dropped the user-activation window that `navigator.clipboard.write` requires and the write rejected with `SecurityError`. Both paths now route through new Electron main-process IPC handlers (`copy-image-from-path`, `copy-image-from-data-url`, `save-image-as`) that use `nativeImage.createFromPath` + `clipboard.writeImage` for copy and `dialog.showSaveDialog` + `fs.copyFileSync`/`fs.writeFileSync` for save. Browser-only builds still get a `navigator.clipboard.write` + anchor-download fallback.
- Removed "Copy Image Data URL". The menu item duplicated "Copy Image" for most users' mental model and produced huge base64 blobs when pasted into a normal text field.
- Explicit toast feedback. Copy and save outcomes now show a sonner toast on success/failure, so a silent `navigator.clipboard.write` rejection can no longer look like "nothing happened".
v0.2.24
Subagent recursion guard (the real one)
- Subagents can no longer spawn subagents. Previously fork children kept the full `sessions_*` tool schema (for byte-identical prompt-cache prefixes) and relied on two guards to prevent recursion: a `BeforeToolCallEvent` hook that was referenced in comments but never actually registered, and an `isInForkChild()` scan that looked at the main conversation agent's messages via `getParentMessagesFn` / a hardcoded `'default'` fallback — never the running child's own messages — so it always evaluated false. Net effect: a child that decided to "delegate search" could fan out another 5 children, each of which could fan out 5 more, etc. Tree depth was bounded only by the 6-concurrent-agent cap racing against new spawns.
- New hard guard: `sessionsSpawn.callback` now checks `getCurrentAgentId()` (backed by a new `AsyncLocalStorage` context that `runAgent` enters for each subagent) and rejects if the caller isn't the `'default'` main agent. `runWithAgentId(agentId, () => runAgent(...))` wraps the spawn site in `subagent.ts`.
- Belt + suspenders: the `BeforeToolCallEvent` hook that comments claimed existed is now actually wired up in `strands-agent.ts` for every subagent Agent instance (fork or not). It cancels any `sessions_spawn / sessions_status / sessions_stop / sessions_message` call at runtime, regardless of whether the tool schema was filtered out or left in for cache sharing.
- Dead code removed: `isInForkChild` import and the two call sites that scanned the wrong message lists. `parseForkResult` was imported but never used — also removed.
- parentId lineage fix: spawned subagents now record the real spawner's `agentId` via `getCurrentAgentId()` instead of a hardcoded `'default'`, so long-running-watch messages and UI parent links point at the agent that actually asked for the work. (Recursion is blocked before this matters in practice, but the registry data is now correct instead of lying.)
- System prompt guidance rewritten to match Claude Code's delegation model. The old "RESEARCH & WEB SEARCH" block told the agent to "Spawn 3-5 subagents in parallel with different search angles" and to poll with `exec sleep N` between `sessions_status` calls — which encouraged fan-out on every research query and wasted tool calls on sleeps the runtime doesn't need (results arrive automatically). Replaced with explicit delegate-vs-work-directly rules: delegate for multi-file changes / refactors / debugging / broad research / verification / context-flooding outputs, work directly for trivial ops. Brief each subagent as a cold teammate (goal + in/out scope + brevity). Launch independent subagents in a single turn, don't duplicate delegated work, don't poll, and explicitly: subagents cannot spawn further subagents. Matching update in `system-prompt.ts` Multi-Agent System section.
Subagent result gathering matches Claude Code's Task-tool semantics
- Batch-complete gather. Before, every individual subagent completion notified the server to drain, so a fan-out of 5 parallel subagents could trigger 2–5 separate auto-continue passes on the same conversation — the parent would synthesize mid-batch, then get poked again with the stragglers. `onSubagentDone` in `subagent.ts` now counts peers that share the same `conversationId` and `status === 'running'`, defers the drain notification until it's the last one reporting, and only then fires `autoContinueFn`. Results still land in the durable DB queue as they arrive (so no completion is lost if the lock is briefly held by a mid-flight drain), but the synthesis pass runs once per batch — matching Claude Code's Task-tool behavior where a batch of parallel sub-tasks returns as a single `tool_result` on the parent's next turn.
- Registry extended with `conversationId`. Required to scope peer-running counts to the right conversation so two concurrent chats don't delay each other.
- Consolidated message shape. The auto-continue prompt is now `"N subagents have returned. Synthesize their findings into a single response for the user. Do not delegate further; do not restate the mechanism."` followed by labeled `### Subagent i/N —
` sections separated by `---`, with failed ones carrying a `[FAILED]` tag. Previous boilerplate ("The following subagent results have arrived. Synthesize them into a clear, organized response...") encouraged the model to restate the mechanism back to the user. - Dead parser removed. `strands-agent.ts` had a branch that rewrote the prompt if an injected message contained `[SUBAGENT COMPLETED:` / `[SUBAGENT FAILED:`, but those tokens haven't been produced anywhere since auto-continuation moved to the DB queue — the branch never fired. Dropped.
- Tool + prompt docs synced. `sessions_spawn` tool description, `strands-agent.ts` AUTONOMOUS_AGENT_INSTRUCTIONS, and `system-prompt.ts` Multi-Agent System section now all describe the same batch-complete delivery shape instead of the old "[SUBAGENT COMPLETED] message arrives per agent" promise.
Notification popover cleanup
- Consolidated notification action buttons to a single "Open" per row. Previously each entry had a type-specific primary label ("Open Agent" / "View" / "Reply") plus a redundant "Dismiss" button; the action row could show "Open / Dismiss / Dismiss" when a notification was rehydrated with a stale secondary action. Now there's exactly one action button labeled "Open", and dismiss lives as a per-row X icon that appears on hover.
- Native macOS banner actions also collapsed to a single "Open" button. macOS's notification shell already provides a close affordance, so an explicit "Dismiss" button was duplicating the OS chrome.
v0.2.23
Notifications: actions, deep-links, and a working Install Now
- Action buttons on every notification type. Native macOS banners now carry "Open Agent / View / Reply / Dismiss" for agent done/failed, schedule done/failed, chat/channel message, and feed events. Previously only the update-ready toast had actions; everything else rendered as an unactionable banner that did nothing when clicked.
- Notification body-click navigates to the event page AND deep-links to the specific resource. Clicking a "Schedule: daily-briefing" banner now opens `/schedules#{scheduleId}`, an "Agent Done" banner opens `/agents/{sessionId}`, a Slack channel message opens `/chat/{conversationId}`, and a feed alert opens `/activities`. Before, body-click only focused the window and dropped the user on the default page.
- Action button clicks carry meta through the full stack. Server broadcast → WS → renderer → Electron main → back to renderer — the originating event's `sessionId` / `conversationId` / `scheduleId` is preserved end-to-end so deep-link navigation works for both the body click AND the action button.
- Install Now on the update-ready toast actually installs. `autoUpdater.quitAndInstall` is now scheduled on the next tick (was racing with the notification dismiss on macOS, which is why the button looked like it did nothing), explicitly sets `autoInstallOnAppQuit = true` as a belt, and falls back to `app.quit()` if the native call throws.
- Bell popover and native toast are consistent. The in-app bell popover shows the same action buttons as the OS toast, so the experience is uniform on Windows/Linux where native actions aren't available.
- Server-side meta added to every emission site. `subagent.ts` now attaches `{agentId, sessionId, conversationId}` to completion notifications. `server.ts` schedule emissions carry `{scheduleId, scheduleName}`. Channel message emissions carry `{conversationId, channel, channelId}`. Single-agent start/goal endpoints carry `{sessionId: 'default'}`.
Notifications Settings cleanup
- Settings test picker now goes through the real server broadcast path. The picker was using a `CustomEvent` shortcut that bypassed the WebSocket round-trip, which meant it could pass locally while production was broken. It now hits `POST /api/test/notification` which broadcasts over WS the same way production agents do, so clicking "Send test" exercises the exact same pipeline as a real event.
- Settings test scenarios now mirror actual server emissions. Added `channel_message_whatsapp` and `channel_message_web` pickers (missing before). Dropped `pet_expedition_return` / `pet_item_drop` scenarios — no code ever emitted those types, so the pickers were dead UI. Also removed the matching pet toggles from the settings schema.
- Test picker resolves deep-link IDs from the server. When you test an agent notification, the picker fetches your most recent agent session and uses its ID as `meta.sessionId` so the click actually lands on a concrete row instead of the bare list. Same for schedules and conversations.
Agents page
- Active agents now render in the list. The header "N active" badge read in-memory team state, but the list below it only showed DB-persisted sessions — so chat-driven runs and not-yet-persisted agents appeared in the badge count but were missing from the list entirely. Live members are now merged as synthetic rows in a pinned "Active" group at the top, and running sessions bypass the Today / 7 Days / 30 Days time filter so a long-running agent stays visible even when the selected window excludes its start time.
Tests
- `notifications-e2e.spec.ts` gained 17 new tests: meta propagation (5), Settings scenarios mirror server emissions (11, one per real type), subagent completion shape (1, skipped when no provider configured).
- New `notification-actions-ui-e2e.spec.ts` covers the UI half: Settings picker hits the real endpoint, bell popover renders action buttons, each action's pushState observed via a `history.pushState` spy for race-free deep-link verification.
v0.2.22
Schedule timeout UI
- Schedule execution timeout is now visible and configurable in the create/edit/detail dialogs. Displayed in minutes (stored as seconds), default 10 min. Hidden for heartbeat type (managed by server).
Fixes
- Fenced code blocks without a language tag (triple backtick) were rendered as inline code, collapsing newlines into a single line. Now checks content for newlines to determine block vs inline rendering.
v0.2.21
Strands SDK v1.0.0 + Express 5 upgrade
- Upgraded `@strands-agents/sdk` from v1.0.0-rc.5 to stable v1.0.0. Concurrent tool execution enabled by default - the LLM decides when to run multiple tools in parallel within a single turn.
- Added `BeforeInvocationEvent` and `BeforeModelCallEvent` cancellation hooks (SDK v1.0.0 feature). Agent stop requests now prevent the next API call from firing, saving cost and reducing latency on user-initiated stops.
- Refactored per-agent tool event routing from a global Map (`agentToolEventFns`) to SDK-native `invocationState` - each `agent.stream()` call carries its own callback via the per-invocation state bag, eliminating shared mutable state.
- Upgraded Express from v4.18 to v5.2 with `@types/express` v5. Migrated all wildcard route patterns to Express 5 `path-to-regexp` syntax (`:param(*)` to `*param`, unnamed `*` to `*splat`). Fixed wildcard param handling - Express 5 returns arrays for `*` captures, now joined with `/` for path reconstruction.
Fixes
- Workspace file read/write via nested paths (e.g. `memory/daily/2026-04-30.md`) broken by Express 5 array params - fixed with `Array.isArray()` guard and `join('/')`.
- Webhook trigger endpoint updated for Express 5 named wildcard (`*splat`).
Tests
- Added `sdk-upgrade-e2e.spec.ts` with 11 new Playwright tests covering Express 5 wildcard routes, chat stop/cancellation, server health, and provider/model endpoints.
v0.2.20
Custom tools & MCP: servers now actually reach the agent
- Custom tools stored in the `custom_tools` DB (shell, http, file, script, `mcp_local`, `mcp_remote`) were never wrapped as Strands tools — the LLM literally couldn't see or call them. Added `buildStrandsToolsFromCustom()` in `server/tools/custom-tools.ts` that wraps every enabled custom tool as a proper Strands `tool()` and appended them to `getEnabledTools()`. Custom tools bypass the profile system — if the user enabled them, the agent gets them.
- MCP tools now default to expand-all mode: one MCP custom tool exposes every tool the remote server advertises. Each remote tool becomes its own Strands tool named `
__ ` (e.g. `server_time__get_current_time`, `server_time__convert_time`). Removed the stale `mcpToolName` single-target plumbing that was silently misrouting calls. - JSON Schema → zod converter ports the MCP server's `inputSchema` (types, `required`, `enum`, `description`) into the Strands tool. Fixes the bug where the agent saw an empty `{}` schema and had to guess argument names / required-ness.
- `executeMcpLocal` / `executeMcpRemote` now honor a per-call `__mcpToolName` override injected by the wrapper, so expand-all routes correctly to the right remote tool on every call.
Connection status tracking & auto-probe
- New `McpStatus` cache + `refreshMcpStatus(def)` / `refreshAllMcpStatuses()` helpers. States: `unknown | connecting | connected | error | disabled`, with `toolCount`, `tools[] (name/description/inputSchema)`, and `error`.
- Probe is triggered automatically on: server boot (for every MCP tool in the DB), custom-tool create, update (including enable toggle), delete (cache pruned for missing ids). No manual step needed.
- New endpoints: `GET /api/tools/custom/status` (id → status map) and `POST /api/tools/custom/:id/refresh` (force re-check). Route ordering fixed so `/status` matches before the `/:id` catch-all.
- `inputSchema` preserved in the cached status payload so the tool builder can forward the real JSON Schema (including `required`) to the agent. Without this the model couldn't tell that `timezone` was required on `mcp-server-time`.
HUD redesign: flat, obviously-clickable, with MCP status
- ElectronTitlebar HUD rebuilt in the shadcn/ui design language: flat borderless pill with `text-muted-foreground → text-foreground` hover, chevron down indicator, status dot on the left, optional MCP badge on the right. No more raised button edges.
- Popover expanded to 320px with clear sections separated by `Separator`: status header, metrics grid (Agents / Tool calls / Iteration), context usage bar, MCP servers list (per-server status dot + tool count or truncated error), recent tool calls, and a clickable footer row that navigates to the Tools page (amber alert icon when any MCP errored).
- Trigger auto-fetches MCP statuses on mount and polls every 5s while the popover is open — the number-of-connected badge updates without a manual refresh.
Tools page: edit existing custom tools
- Each custom tool row is now fully clickable (hover background, keyboard focus) and opens an Edit dialog that pre-populates every field (name, description, type, config, parameters). For `mcp_local` the space-joined `command` / `args` field is reconstructed so the UI round-trips cleanly.
- Dedicated Pencil edit button next to Play / Delete / Refresh. Dialog header and save-button label switch between Create / Edit mode automatically. Saving an edited MCP tool triggers a re-probe immediately.
- Non-MCP rows keep the same inline controls; action buttons stop click propagation so they don't accidentally open the edit dialog.
Misc
- `image-viewer.tsx` tightened to match the rest of the tool-flip UI (generated / viewed images render with consistent collapsed / expanded states and zoom controls).
- Landing site: FAQ additions, About / Legal / Blog copy edits, v0.2.19 blog post polish, new `humans.txt`, updated `iphone-frame.svg`, sitemap regen.
- `site/worker.js` updated alongside new static asset routes (`.well-known/`, blog hero images).
- Docs regenerated from latest MDX (`docs/public/docs/{models,providers,tools}.md` + `llms-full.txt` + sitemap) — covers the `gpt-image-2` + OpenAI OAuth changes shipped in v0.2.19.
- `foxl/scripts/debug-codex.mjs`, `debug-image-tool.mjs` kept in-tree for future Codex / image-gen wire triage.
v0.2.19
`generate_image` tool: gpt-image-2 through ChatGPT subscription
- New built-in `generate_image` tool that produces or edits raster images via `gpt-image-2` without an OpenAI API key. Powered entirely by the user's existing ChatGPT Plus/Pro OAuth session (`~/.codex/auth.json` / `CODEX_HOME` / `CHATGPT_LOCAL_HOME`); no per-image billing outside the $20/month subscription quota. The tool posts to `chatgpt.com/backend-api/codex/responses` with `model: "gpt-5.4"` carrying `tools: [{ type: "image_generation", model: "gpt-image-2" }]` — the same shape the official Codex CLI uses for its built-in `image_gen` tool.
- Tool is gated to accounts with ChatGPT OAuth: only registered when the credential file is discoverable at module load, skipped otherwise so non-OAuth users don't see an unusable affordance.
- Accepts multi-image input for edits and composition. `inputImages: string[]` (max 10 paths) switches gpt-image-2 from text-to-image into edit / compose mode — "이 사진 만화풍으로 바꿔", "이 두 이미지 합쳐줘", reference-guided generation, etc.
- Token-efficient wire shape: only file paths cross the model's tool-call args, never base64. The tool reads each path from disk inside the server process and encodes it as `input_image` data URIs directly on the Codex request. Return payload to the model is trimmed to `Saved to:
` + a sharp-resized 768px JPEG preview (~100KB) — the full-resolution PNG stays on disk at `outPath`. - Attached chat images auto-persist to `data/workspace/attachments/chat-
- . ` and their absolute paths are injected into the user's text block as `[Attached image paths: "..."]`. The model therefore always knows the exact disk path to pass to `generate_image({ inputImages: [...] })`, closing the loop from drag-and-drop → edit. - Saved outputs live at `data/workspace/generated/generated-
- - .png`. Workspace root (SOUL/USER/MEMORY/TOOLS/AGENTS/HEARTBEAT/BOOTSTRAP.md) stays uncluttered; every generated image carries its date and a readable slug derived from the first six words of the prompt. - `data/workspace/generated/` excluded from workspace file listing automatically (subfolder convention), accessible via the Open Folder button now on the Workspace page header.
- ChatGPT-style UI: generated images render expanded by default directly in the chat stream (not tucked inside a collapsible tool flip), with the standard Task chevron for collapse and consistent sizing with other tool calls. `view_image` keeps its original collapsed-by-default compact UX. The full-resolution preview modal offers click-to-zoom (25%–400%) and an Open Folder button that reveals the saved PNG in Finder / Explorer / xdg-open.
- Tool-profile wiring: added `media` category (`['generate_image']`) to `tool-profiles.ts` and included it in `default`, `standard`, and `full` profiles. `minimal` omits it to preserve its token budget.
OpenAI OAuth: catalog + model availability
- Added `openai-oauth/gpt-5.5` to the catalog (1M context, reasoning + streaming). Verified serving on the ChatGPT OAuth endpoint via live probe. `gpt-5.5-codex`, `gpt-5.5-mini`, `gpt-5.5-pro` are all gated to API-key accounts (`400 Bad Request: "not supported when using Codex with a ChatGPT account"`) and therefore excluded.
- Final catalog: `gpt-5.5` (1M), `gpt-5.4` (1M), `gpt-5.4-mini` (400K), `gpt-5.3-codex` (400K).
- Removed `openai-oauth/gpt-5.4-codex` and `openai-oauth/gpt-image-2` as top-level language-model entries. The ChatGPT OAuth backend returns `400 Bad Request: "The 'gpt-5.4-codex' model is not supported when using Codex with a ChatGPT account"` (same for `gpt-image-2`) despite the OpenAI Developer Community announcement. `gpt-image-2` is still reachable, but through the new `generate_image` tool above.
- Wired up `imageModel()` / `image()` on `createOpenAIOAuth` regardless, backed by `@ai-sdk/openai/internal` `OpenAIImageModel` reused through the same OAuth fetch. Ready to flip on the moment OpenAI enables raw image models on the ChatGPT OAuth endpoint.
Fix: `openai-oauth/gpt-5.4` was silently routed to gpt-4.1
- `shared/model-resolver.ts` had a stale `NAMESPACE_ALIASES` entry mapping `openai-oauth/gpt-5.4` → `gpt-4.1` (and `openai-oauth/gpt-5.4-codex` → `gpt-4.1`). Any time the user selected the OAuth GPT-5.4, `setModel → toCanonicalId` resolved the alias and stored `gpt-4.1` as the active model — which Codex then rejected with a bare `400 Bad Request` because ChatGPT OAuth accounts don't serve `gpt-4.1` either. Removed both aliases so the transport-prefixed id passes through untouched. Verified end-to-end: `/api/providers/select` now persists `openai-oauth/gpt-5.4` verbatim and the follow-up `/api/chat/stream` returns reasoning + text deltas as expected.
Debug: surface real Codex upstream errors behind `FOXL_CODEX_DEBUG=1`
- Added an opt-in `FOXL_CODEX_DEBUG=1` env flag in `openai-oauth/core/transport.ts` that clones and logs the response body + request body on any non-2xx from `chatgpt.com/backend-api/codex/*`. Strands wraps upstream errors as `"Language model stream error: Bad Request"`, which discards the real cause; this flag is the single switch that reveals it. Off by default (no logging overhead in production).
UX: Workspace page "Open Folder" button
- New folder-open icon button in the Workspace header (next to Search and Refresh) that opens `data/workspace/` in Finder on macOS, Explorer on Windows, and `xdg-open` on Linux. Electron shell path via `window.pilot.openPath`; non-Electron falls back to new `POST /api/system/open` endpoint.
Skills: remove duplicate image-gen skills from foxl-ai/skills
- Removed `openai-image-gen` (OPENAI_API_KEY + Python script path) and `nano-banana-pro` (GEMINI_API_KEY + uv + Python) from the skills repo. Both are superseded by the native `generate_image` tool above. One canonical image path across the product.
Dependencies: Strands SDK → v1.0.0-rc.5
- Bumped `@strands-agents/sdk` from `^1.0.0-rc.3` to `^1.0.0-rc.5`. Picks up: mid-execution cancellation (`agent.cancel()` / `cancellationSignal` via `AbortSignal.timeout()`), agent-as-tool (`agent.asTool()`), multi-agent session persistence, Bedrock thinking+forced tool_choice conflict fix, Bedrock context window overflow detection matching the Python SDK, invocation lock leak fix on consumer stream break, AgentSkillsPlugin → AgentSkills rename, `contextWindowLimit` added to `BaseModelConfig`, and OTEL JS SDK v2 upgrade. No call-site changes needed in Foxl.
Frontend hygiene: zero web typecheck errors
- Fixed 19 pre-existing TypeScript errors across the web codebase so `tsc --noEmit` in `apps/web` is now green. Bumped `target`/`lib` from ES2020 → ES2023 and added `vite/client` to the `types` array (unlocks `Array.prototype.findLastIndex`, stricter `ArrayBuffer` typing, and `import.meta.env` typing). Deleted unused `FloatingLines.tsx` (referenced a missing `three` dep). Tightened cast sites in `useWebSocket.ts` (`WebSocketMessage` had an `[key: string]: unknown` index signature that made field narrowing impossible), `tunnel-crypto.ts` (BufferSource / Uint8Array overloads), and `search-bar.tsx` (RefObject nullability). None of these were blocking the Vite build, but keeping `tsc` green is a non-negotiable guardrail for future refactors.
Claude Code (OAuth): Opus 4.7 gets its 1M context back
- Opus 4.7 on the Claude Code OAuth transport now reports its true 1M context window. Since Opus 4.7 shipped in v0.2.11, `claude-code/opus` had been hard-coded to 200K because the CC OAuth catalog predated the 1M rollout. The result: `/context` usage percentages showed ~5× inflated values and auto-compaction kicked in five times earlier than it should have, effectively capping Opus 4.7 sessions at 200K. A live probe against `api.anthropic.com` with a 320K-token prompt confirmed Opus 4.7 on the subscription pool accepts the full input at `service_tier: standard` with no `context-1m-*` beta header — 1M is the native default, the beta flag is only for back-compat with older models.
- Sonnet 4.6 on the same CC OAuth transport stays at 200K. A parallel probe (identical shape, only `model` swapped) returned HTTP 429 "Extra usage is required for long context requests" — Anthropic routes Sonnet's long-context requests into the pay-as-you-go pool instead of the subscription pool. Haiku 4.5 is likewise kept at 200K.
- Added `resolveEffectiveContextWindow(modelId)` in `shared/model-resolver.ts` so UI context gauges (`useStreamingChat.ts`) and server-side auto-compaction (`server/agent/compaction.ts`) both see the transport-adjusted ceiling rather than blindly trusting the catalog entry. Catalog entries keep their paper-spec numbers (Sonnet 4.6 = 1M via Bedrock/BYOK) and the helper narrows them only for transports that impose a cap.
Claude Code (OAuth) model ids: full Anthropic name after the prefix
- The CC OAuth catalog now exposes `claude-code/claude-opus-4-7` and `claude-code/claude-sonnet-4-6` as the canonical ids. The legacy short-form ids (`claude-code/opus`, `claude-code/sonnet`, `claude-code/haiku`) are preserved as aliases in `CLAUDE_CODE_MODEL_ALIASES` and in the shared `NAMESPACE_ALIASES` map, so previously persisted user selections (localStorage `foxl-selected-model`, relay `/api/providers/select` state) keep resolving correctly. Verified end-to-end with a live probe — all four id shapes (new/old × Opus/Sonnet) route to the expected Anthropic model with `service_tier: standard`.
Fix: model switch was a no-op on foxl.ai provider
- Switching model mid-conversation on the foxl.ai provider (e.g. Kimi K2.5 → Opus 4.7) kept running the previous model because `conversationAgents` caches Strands `Agent` instances and `Agent` is constructed with a baked-in model + provider. The POST `/api/chat/stream` handler already called `setModel(newModel)` from the request body, but the cached agent ignored it. Added a parallel `conversationAgentModels` map that records each cached agent's construction-time model, and `handleStreamingChat()` now discards the cached agent as soon as `cachedModel !== config.model`. All 16 existing `conversationAgents.delete(…)` / `.clear()` sites were mirrored so the maps never drift.
Relay: prompt caching on the Bedrock forward path
- `relay/src/proxy.ts` now injects block-level `cache_control: { type: 'ephemeral' }` breakpoints on every Anthropic-format `/v1/messages` body before it hands off to Bedrock InvokeModel. The tool list, the system prompt, and the last user turn each get one breakpoint (up to Bedrock's 4-breakpoint limit) so `tools → system → messages` prefix caching kicks in automatically across turns. Bodies that already carry explicit `cache_control` markers are left alone. Verified with a dedicated unit-test suite (`relay/tests/09-cache-breakpoints.test.ts`, 7 cases covering string/array system shapes, tool lists, multi-turn conversations, and `tool_result` blocks).
- Strands' `AnthropicModel` config only exposes a `cacheConfig` that `BedrockModel` honors — `AnthropicModel` itself doesn't auto-inject cache points, so the relay-side injection is where caching on the foxl relay path actually turns on. Bedrock InvokeModel does not honor the top-level automatic-caching shorthand described in Anthropic's API docs (that's Claude API + Azure AI Foundry only), which is why block-level markers are the right place to hook.
Site: foxl.ai gets a real dark mode
- Added a `.dark` palette to `site/landing/src/index.css` (background/foreground/card/muted/border/ring/accent all defined) and an inline pre-paint script in `index.html` that applies the saved or system theme before first paint so there's no FOUC on hard refresh. A new `ThemeToggle` component (`site/landing/src/components/landing/ThemeToggle.tsx`) sits in the Navbar next to the Download button on desktop and next to the hamburger on mobile, persisting the user's choice under `localStorage['foxl-theme']` and falling back to `prefers-color-scheme` when unset.
- Swept landing components for hardcoded light palette: `Security.tsx` cards switched from `bg-white` to the semantic `bg-card`, and the LegalPage changelog version badge switched from `bg-zinc-200 text-zinc-700` to `bg-muted text-muted-foreground`. The only remaining hardcoded colors are intentional (the iPhone notch in `MobileDemo.tsx` stays `bg-black`, the toggle knob in `DesktopDemo.tsx` stays `bg-white`).
v0.2.18
Revert: Claude Code (OAuth) compat layer is back on
- v0.2.17 disabled the Claude Code (OAuth) compat-mode tool rewriter based on raw `/v1/messages` probes that appeared to show Anthropic's Pro/Max subscription pool now accepting Foxl-native tool names. In production users immediately hit "Language model stream error: You're out of extra usage" — the raw probes did not reproduce the shape that Strands + Vercel AI SDK put on the wire in a real agent loop, so the subscription pool kept rejecting the request and Anthropic fell back to pay-as-you-go billing.
- Reverted: `strands-agent.ts` and `server/api/server.ts` call `buildCompatTools()` again for `providerType === 'claude-code'`, the tool-adapter banner is back to its v0.2.16 wording, and `claude-code/haiku` is excluded from the model catalog again (it was re-added in v0.2.17 based on the same faulty probes).
- Compat mode returns to being the supported Claude Code (OAuth) path. Investigation to capture a real failing body and validate an end-to-end bypass is tracked in TODO.md; the probe scripts stay on disk as regression guards for the compat path.
Auto-update notification actions
- Clicking "Install Now" (or the notification body) on the "Foxl update ready" toast now actually installs the update and restarts the app. Two separate notifications used to race on download completion: a renderer-side rich notification with action buttons and an actionless main-process Notification. The main-process toast usually won, its `on('click')` handler only focused the window, and the user saw "Install Now" do nothing. Removed the duplicate toast so the renderer's action-bearing notification is the only one the user sees.
- `show-notification` native-toast click handler now treats `type: 'app_update'` as an install affordance rather than a focus-only click, so clicking the toast body (on any platform) installs the update the same way the explicit "Install Now" action does.
v0.2.17
Claude Code (OAuth): full native tool registry
- Claude Code (OAuth) now sees Foxl's full native tool registry (memory, workspace memory, subagents, schedule, channel send, browser, view_image, etc.) — everything that was dropped by v0.2.16's compat-mode tool adapter. Anthropic's Claude Pro/Max subscription pool no longer gates on CC-shaped tool names, so the Bash/Read/Grep/WebFetch shim layer is bypassed in `strands-agent.ts` and `server/api/server.ts`. Proven by two new probe scripts that route every combination (Opus/Sonnet/Haiku × native tool lists) through `api.anthropic.com/v1/messages` and verify `usage.service_tier === 'standard'` on both the initial request and a full `tool_use`/`tool_result` round-trip.
- `server/providers/claude-code-oauth/compat/tool-adapter.ts` is kept on disk (no longer imported) so the rewriting strategy is instantly recoverable if Anthropic re-enables the gate. `compat/message-rewriter.ts` is still wired up — the subscription pool remains sensitive to the SYSTEM block shape, so Foxl still relocates persona/context from `system` to a `
` prefix on the first user turn. - Re-added `claude-code/haiku` (Haiku 4.5) to the Claude Code (OAuth) catalog. The 2026-04-22 probe sweep showed Haiku now routes cleanly across the full native tool set — the instability that caused it to be excluded in v0.2.16 no longer reproduces.
Claude Code (OAuth) probe scripts
- `scripts/test-claude-code-oauth-compat-probe.mjs` — 3×3 matrix (Opus/Sonnet/Haiku × no-tools/single-tool/full-CC-set) verifying subscription-pool routing against the CC-shape rewrite.
- `scripts/test-claude-code-oauth-native-tools-probe.mjs` — 3×3 matrix using Foxl-native tool names (exec, file_read, code_search, web_fetch, memory_save, sessions_spawn) to prove the CC-shape rewrite is no longer required.
- `scripts/test-claude-code-oauth-roundtrip-probe.mjs` — drives a full tool_use → tool_result → follow-up turn with Foxl-native names and asserts subscription routing on both turns.
- `scripts/test-claude-code-oauth-claims-probe.mjs` — empirically confirms the "OpenClaw re-allowed" announcement's claims against the live API: `service_tier: auto` and `standard_only` work, `cache_control` is a no-op over OAuth, `context-1m-2025-08-07` beta is rejected on `sk-ant-oat-*` tokens, `claude -p` CLI still runs, adaptive thinking plumbing returns `thinking` content blocks.
Notification click/action routing
- Notification bell items now actually navigate to the relevant page when clicked. Previously, any notification that carried action buttons (Install / Later on "update ready", or anything with explicit actions) swallowed the body click and stayed on the current page. Agent-complete, feed, schedule, and channel notifications now route to their owning page on click and close the popover afterwards.
- Action buttons whose id is not a special case (`install-update`, `dismiss-update`) now fall back to navigating to the notification's navTarget, so "Open Agents" / "View Feed" style buttons in test notifications and future richer actions work without extra wiring.
- Native OS notification clicks now route for `agent_complete`, `agent_error`, `feed`, `feed_urgent`, and `heartbeat` types as well (previously only chat / schedules were handled; everything else just focused the window).
- Deep-link resolution extended: navigating to `chat` with `meta.conversationId` now calls `chat.loadConversation` and updates the URL; navigating to `schedules` or `activities` with `meta.scheduleId` / `meta.feedId` writes the id into the URL hash so the target page can scroll or expand the relevant row.
Settings: interactive Test notifications
- The Test card in Settings > Notifications is now a working end-to-end test instead of a preview-only helper. Picking a scenario and hitting Send inserts a live rich notification (with an "Open" action) directly into the bell popover via a CustomEvent bridge, so you can verify click and action routing without waiting for a real event.
- Each scenario resolves a real deep-link target: agent scenarios fetch the latest `/api/agent-sessions` and route clicks to that session's detail page; schedule scenarios use the first `/api/schedules` row; channel_message scenarios use the most recent conversation. When no rows exist, the placeholder id still exercises the deep-link plumbing.
- New "Also fire native OS notification" checkbox now calls `pilot.showNotification` directly from the renderer so the actions array reaches the Electron main process and the native banner renders the same "Open" button. Previously the test went through `/api/test/notification` which stripped actions and also inserted a duplicate row in the bell.
- Dismiss action removed from the test notifications — macOS notifications have a built-in close and the bell popover closes on outside click, so a second dismiss button was redundant. The action label is now just "Open" across all scenarios, localized in all 10 locales.
v0.2.16
Claude Code (OAuth) compatibility mode
- Re-enabled the "Claude Code (OAuth)" provider with Opus 4.7 and Sonnet 4.6 entries. Haiku stays excluded because even the official CLI tends to 429 once the tool list grows past a few entries on Haiku.
- Added a transport-level rewrite layer (`server/providers/claude-code-oauth/compat/`) that conforms Foxl's agent-shape requests to what Anthropic's Pro/Max subscription pool actually routes. Two transforms run on every `/v1/messages` call:
- Foxl-exclusive tools with no CC counterpart (memory, workspace memory, subagents, schedules, channel send, view image, browser extension, ...) are dropped from the tool list in compat mode - the model never sees them and cannot call them. First-time selection surfaces an AlertDialog explaining the trade-off (translated into all 10 supported locales).
- Docs: restored the Claude Code (OAuth) sections in `docs/content/docs/models.mdx` and `providers.mdx` with compat-mode caveats.
v0.2.15
Empty-chat layout
- When the terminal pane is open, the empty-state greeting + input no longer try to vertically center themselves over the terminal. The content now sits top-aligned above the terminal so both are fully visible.
Claude Code (OAuth) temporarily disabled
- Hidden the "Claude Code (OAuth)" provider and its Opus/Sonnet/Haiku entries from the model picker, settings page, and `/api/providers` response. The transport, credential loader, and Strands wiring are kept in the codebase so this can be re-enabled later.
- Why: Anthropic's Claude Pro/Max subscription pool rejects Foxl's agent-shape requests. Raw probes against `api.anthropic.com` using the real OAuth token showed that a 1-line preamble with no tools routes to the subscription pool (200 + `service_tier: standard`), but adding Foxl's 22-tool registry or a 23KB persona system prompt causes Anthropic to silently re-route to the pay-as-you-go API pool. Users on Pro/Max were burning "Extra usage" without realizing the calls had fallen off their subscription.
- Investigation also aligned the OAuth transport with the official Claude Code CLI (`anthropic-beta: oauth-2025-04-20,claude-code-20250219`, `user-agent: claude-cli/...`, `x-app: cli`, single merged system text block, `output_config` stripped, Haiku uses `budget_tokens` thinking instead of `adaptive`). Those fixes stay in the tree for when Anthropic ships a subscription-compatible agent surface.
- Workaround for users: use Anthropic API key (BYOK) or the foxl.ai relay for Claude access.
v0.2.14
Empty-chat redesign (desktop + web app)
- New chats open with a Codex/Claude-style centered layout: a single "What should we work on?" greeting directly above the input box, vertically centered in the viewport. As soon as the first message is sent the input snaps back to the normal bottom dock.
- Dropped the six hardcoded prompt cards (Web Search / File / Debug / Code / Terminal / Analysis). They were generic enough to be noise. Pill-style AI suggestions still render directly under the input when `/api/suggestions/generate` returns any.
- Foxl logo no longer prints in the empty state.
- Mobile keeps the earlier v0.2.11 layout (top-aligned logo + greeting + bottom-docked input) so the on-screen keyboard does not fight the centered overlay.
Fixes
- Suggestions fetch used an `AbortController` whose cleanup fired during React StrictMode's double-invoke effect, cancelling the inflight request so pills never appeared in dev. Switched to a cancelled-flag pattern - the request completes and the cache is warmed, results are just ignored if the component unmounted first.
- app.foxl.ai (relay-only mode) now pulls greeting + suggestions from the connected desktop via the tunnel API (`/tunnel/api` -> `/api/suggestions/generate`), so mobile web users see the same cached AI-generated pills their desktop already produced. Previously mobile only showed the greeting because relay-only short-circuited the suggestions fetch.
- Desktop UI no longer calls `/api/suggestions/generate` on its own and no longer renders the "Try asking something like:" label. Suggestions are mobile-only now - the desktop empty state is just the centered greeting + input.
Changes
- Sidebar icon label renamed from "Config" to "Settings" (English only; other locales already said "Settings"/"설정"/"設定").
Site
- Added v0.2.13 blog post with pixel-office hero image; registered in `blog.tsx`, worker SPA meta, and sitemap. Post is marked featured so it pins to the top of /blog.
v0.2.13
Pixel Office — full redesign pass
- New cozy startup aesthetic: warm cream walls with wainscot trim, light marble-tile floor (6x3 staggered slabs with veining), wood-framed windows with sills, wall decorations (framed picture, clock, cork pinboard), and slowly-rotating ceiling fans. Ambient golden light beams with dithered edges and drifting dust motes near windows.
- Harmonized furniture palette: every piece now draws from the same ~18-color palette. Desks have warm oak tops with a single centered accessory (coffee mug, paper stack, or desk plant - deterministic per position). Chairs are backless 2-wide terracotta stools so a pet clearly perches on the cushion. Plants, lamps, shelves, whiteboards, and the water cooler all use coordinated wood + terracotta + sage tones.
- 2.5D object sprites: tall items (floor lamp 1x2, floor plant 1x2, bookshelf 3x1 drawn 2 tiles tall, whiteboard 4x1 drawn 2 tiles tall) protrude upward beyond their grid footprint without colliding with surrounding furniture. No more "floating head" plants or clipped shelves at the wall edge.
- Balanced layout: 4 workstations (left), meeting room with whiteboard + 3x3 rug + table + 2 chairs (center), lounge with rug + 2 cushions + tall lamp + tall plant (right), kitchenette with water cooler + bookshelf + pet feeder/bowl/cushion (far right). Mix of 1x1 and 1x2 items, perimeter plants and lamps anchor each zone.
- Agent-to-pet assignment is randomized: earlier the leftmost free pet and leftmost free seat always got picked, so the same pet always worked. Now random pick means workstations rotate.
- Clickable agent popover when a working pet is selected: compact 320px card with status dot, pet name, current task, live tool indicator with animated dots, last error, top-used tools, iteration/tool/elapsed stats, and Stop + Open Full actions. Much cleaner than the old cramped popover.
- Real-time pet-position persistence: pet positions (plus direction and state) now save to localStorage every 3 seconds and on tab blur / window close. Returning to the Agents page keeps every pet exactly where it was. Writes are batched so there is only one localStorage set per cycle.
- Notifications are deep-linkable: agent-completion notifications now carry the session id in their payload. Clicking "Agent Done" in the bell popover jumps straight into that agent's detail page. Works across the in-app bell, the Electron titlebar bell, and the Windows header bell.
Pixel Office - earlier in v0.2.13
- Claude-Code-inspired rare encounter journal (18 critters across 5 tiers, seasonal/night/agent-completion gates).
- Welcome-back summary modal with sparks/items/sightings earned while away.
- Pets replace human agent sprites; each walks to a chair and "works" with an animated monitor.
- Background pet simulation always runs when the Pixel Office is enabled (no separate opt-in).
- Wall-clock catch-up simulation so pet stats and rare rolls progress accurately after long absences.
- Server-side state backup via `/api/pixel-office/state` (single-row blob store, overwrites in place - no history accumulation).
- Visitor system retired and unified under the rare-encounter journal (sightings-only).
- Inventory now surfaces only placeable items; starter seed is a single comfy cushion.
- Auto-forage removed; pets stay home.
Fixes
- Feed sidebar black dot that couldn't be dismissed (legacy Activities-era code).
- Goal-started agents now appear as separate rows in the history list (each run mints its own session id).
- Agent popover used to show 2 running agents for 1 goal because the in-memory "default" row and the fresh session row both appeared - now deduped.
- Sidebar label "Agent Team" simplified to "Agents" across all 10 locales.
- i18n interpolation accepts both `{var}` and `{{var}}` placeholder styles.
- Monitor flicker on working pet's desk now aligns to the desk's centered monitor and refreshes at a subtle 2 fps instead of strobing.
- Wall-anchored tall items (whiteboard, shelf) previously got clipped or hidden when placed near the wall seam - render order fixed.
- Settings showed a stray "Background Pet Simulation" toggle even though the feature is always on - removed.
- Notifications now support action buttons. Previously the in-app bell dropdown only showed title + body - there was no way to trigger "Install", "Dismiss", etc. - so the new-version notification in particular had no actionable path. The Electron native OS notification also gains action buttons (macOS) and opens the bell popover when clicked (Windows/Linux), bringing the in-app action chips to every platform.
- "Update ready" notification now posts to the in-app notification center with Install Now / Later buttons, not just a silent system toast. Dedupes across multiple download cycles via a fixed notification id.
- Fix tunnel "restart desktop" and update-install restart paths leaking the spawned Node server subprocess. Previously `app.exit(0)` / `quitAndInstall` skipped the `before-quit` lifecycle, so the server kept running on port 13847 and the relaunched app could not bind the port - making restart worse for users who were trying to recover from a stuck state. Restart now explicitly tears down the server subprocess and tunnel client before handing off to the updater / relaunch.
- Fix Claude Code OAuth credential detection on Windows. The previous code path tried to read tokens from Windows Credential Manager via the community `Get-StoredCredential` / `New-StoredCredential` cmdlets, which are NOT installed by default on Windows - the shell-out silently failed for every Windows user and then fell through to the file fallback. Claude Code on Windows actually stores tokens in `%USERPROFILE%\.claude\.credentials.json` (matches upstream openclaw / pi-ai reference behavior), so we now read/write that file directly with no PowerShell spawn and no module dependency. Token refresh writes back to the same file `claude login` uses.
- Fix Feed Generator interval changes not applying. Changing "Frequency" in Settings → Feed Generator from 5 minutes to 1 hour updated only the heartbeat-runner but left the scheduler's paired `Heartbeat` schedule spinning its own `setInterval` at the old interval forever. Execution is now owned exclusively by the heartbeat-runner; the `Heartbeat` entry in the Schedules UI is display-only (scheduler skips attaching a timer for the auto-seeded entry). Settings ↔ Schedules UI are kept in sync both directions: interval/enabled changes from Settings mirror to the schedule row, and toggles/interval edits from the Schedules UI mirror back to HEARTBEAT.md + the runner.
Changes
- Feed Generator is now the single Heartbeat schedule. Users can no longer create additional heartbeat-type schedules from the Schedules UI - that type is removed from the create dialog, and `POST /api/schedules` rejects `type: "heartbeat"` requests. Use Cron instead for custom periodic schedules.
v0.2.12
Features
- New OpenAI (OAuth) provider: use your ChatGPT Plus/Pro subscription directly — no API key needed. Foxl reads `~/.codex/auth.json` (created by `npx @openai/codex login`), auto-refreshes the OAuth token, and calls OpenAI's Codex Responses API directly through Strands' VercelModel adapter. No local CLI subprocess, no proxy server. Tool use, reasoning/thinking blocks, and streaming all flow through the normal Strands agent loop. Models: GPT-5.4, GPT-5.4 Codex.
- Claude Code (OAuth) rewritten on Vercel AI SDK: drops the `claude` CLI subprocess. Foxl now reads the user's Claude Code OAuth tokens (macOS Keychain, Windows Credential Manager, or `~/.claude/.credentials.json`) plus long-lived API keys from `claude setup-token`, auto-refreshes OAuth tokens, and calls `api.anthropic.com/v1/messages` directly through Strands' VercelModel adapter. Foxl's full tool registry, thinking/reasoning blocks, and streaming now work end-to-end on Claude Pro/Max subscriptions — same path as the OpenAI OAuth integration.
- Settings > Providers list now groups each OAuth login right under its API-key counterpart (OpenAI (OAuth) under OpenAI, Claude Code (OAuth) under Anthropic, Gemini CLI under Google) with the provider's own icon.
Changes
- Renamed the Claude Code provider surface from "SSO" to "OAuth" everywhere (UI labels, status field `ssoConfigured` -> `oauthConfigured`, docs). The underlying auth mechanism is unchanged — just more accurate terminology.
- Removed the legacy Codex CLI provider (subprocess-based). Replaced by OpenAI (OAuth), which talks to OpenAI's servers directly using the same `~/.codex/auth.json` credentials created by `codex login`.
- Removed the legacy CLI-subprocess Claude Code provider. `@anthropic-ai/claude-code` is no longer a runtime requirement — Foxl just reuses whichever credentials Claude Code already stored (keychain entry or `~/.claude/.credentials.json`).
- Settings > Providers help text for Claude Code now points users to `claude /login` / `claude setup-token` instead of asking them to install the CLI.
- Subscription-OAuth providers (OpenAI, Claude Code, Gemini CLI) now show a one-line disclaimer in Settings clarifying that they are unofficial community integrations using local subscription credentials — use at your own risk and never share the auth file.
Fixes
- Fix scheduler (cron + heartbeat) + several helper endpoints that would throw `openai-oauth requires async model creation` / `claude-code requires async model creation` when those providers were the active default. All internal `createModel()` call sites now use `createModelAsync()` so OAuth-backed providers work everywhere, not just chat.
v0.2.11
Features
- New Feed page (replaces Activities): For You tab for AI-curated items, Activities tab for agent event stream, Logs tab with live SSE log viewer. Persists selected tab per user.
- New Feed Generator (was "Heartbeat" in settings): a background agent that periodically surfaces items in the For You feed. Most runs stay quiet; only items marked urgent trigger system notifications, so it can run hourly without spamming you.
- New `feed_add` tool exclusive to the Feed Generator executor (not exposed to chat or subagents). Accepts title, body, priority (quiet/urgent), category.
- Feed items support Archive / Unarchive / Clear with a clean Inbox/Archive view toggle. Hover anywhere on the row to archive in one click.
- HEARTBEAT.md is the single source of truth for the Feed Generator prompt. Edit it in Settings or in the workspace; both refresh the runner immediately.
- Windows taskbar overlay icon now renders the unread count (16x16 SVG) for parity with the macOS Dock badge.
Improvements
- Sidebar restructured: Chat / Overview / Workspace in the top group; new "Extend" group with Agents, Tools, Skills, Schedules; Connect (Relay, Integrations); Feed.
- Page header uses i18n via `nav.
` keys (was a static English NAV_TITLES lookup). - Schedules page Recent Runs gets a full filter bar: search, Status, Type (All / Excluding heartbeat / Cron / Heartbeat only / Webhook), Schedule, Time range, Sort. Grouped by Today / Yesterday / This week with relative timestamps.
- Skills page: Enable/Disable-all toolbar button removed, replaced by a subtle counter pill ("3/12 on") with Enable all / Disable all / Invert actions. Per-skill right-click context menu with Enable, Open in Finder, Delete.
- Notification settings redesigned: Behavior (Only in background / Sound) merged into the master toggle card at top; new Feed section with separate urgent/quiet toggles; test scenarios collapsed into a single dropdown plus Send Test button with a live preview card that shows the notification action chips.
- Heartbeat-type schedules no longer emit the "Schedule: ..." system notification or bump the sidebar badge. They only surface via explicit `feed_add` calls.
- Feed Generator default interval changed from 5 minutes to 1 hour (recommended). Interval options are now 15m / 30m / 1h / 3h / 6h, with 5m kept for debugging only.
- Settings → Feed Generator card compacted: inline status strip, Frequency and Quiet Hours side-by-side, Instructions collapsed by default. Save button disabled unless there are unsaved changes; a Revert action discards local edits.
Removed
- Live Logs section removed from Config page. Use the Feed page's Logs tab instead (same SSE stream).
Fixes
- Fix black screen on app relaunch: retry loadURL up to 10 times on connection refused instead of showing error page immediately.
- Error recovery page now probes multiple ports (13847-13851) to find server after EADDRINUSE fallback.
- Fix relay model tier check rejecting prefixed model IDs (e.g. global.anthropic.claude-sonnet-4-6) for free tier users.
- Fix crash in Feed Generator settings when `activeHours` is null in the heartbeat runner state.
v0.2.10
Features
- Merge Channels and Integrations into a single "Integrations" tab (sidebar Connect section)
- Slack OAuth workspace selector: enter workspace name (e.g. "mycompany") to connect a specific workspace directly
- Slack App registered (OAuth V2, user-token scopes, any workspace installable)
- Relay Slack exchange endpoint deployed with client_id/client_secret secrets
Fixes
- Fix OAuth loopback server blocking on repeated Connect clicks (async close + explicit cancel of previous flow)
- Fix Connect button staying disabled after first click (busy state now clears immediately)
- Auto-register/unregister integration tools (slack, outlook) on connect/disconnect without server restart
v0.2.9
Features
- New Integrations page (sidebar → Connect → Integrations) for connecting Microsoft 365 and Slack via one-click OAuth
- New `outlook` tool (21 actions): email (inbox/read/send/reply/forward/search/folders/drafts/attachments/contacts/move/categories/update), calendar (view/meeting/availability/room booking/search/shared list), and Microsoft To-Do (lists/tasks/checklist) via Microsoft Graph API
- New `slack` tool (16 actions) using the user's OAuth token: search_messages, whoami, get_recent_messages, channels_list, conversations_history/replies/add_message/open/members, check_replies_batch, users_lookup/profile_get, attachment_get_data, reactions_add/remove, file_upload
- Both tools are skill-gated: only registered after the user connects the corresponding account (no noisy tool schema otherwise)
- Microsoft OAuth uses PKCE + loopback (public client, no shared secret). Slack OAuth exchanges the auth code through relay.foxl.ai so the Slack client_secret is never bundled in the desktop app.
- Deep-link fallback (`foxl://integrations/callback`) for environments where the loopback port is blocked
- Electron `openExternal` IPC exposed for renderer → browser navigation (http/https only)
Changes
- Existing `outlook-web` skill (browser-extension DOM scraping) marked as legacy/disabled - superseded by the native `outlook` tool
- Existing `slack` skill rewritten from bot-token stub to real user-token tool
Infrastructure
- New `foxl/server/integrations/` module: shared OAuth loopback, token store (agent_memory-backed), Microsoft Graph wrapper (auto-refresh on 401), Slack Web API wrapper
- New relay endpoint `POST /integrations/slack/exchange` holds the Slack client_secret
- Skill eligibility now honors `requires: [integration:
]` - auto-hides tools until connected
v0.2.8
Improvements
- Migrate to SDK-native cooperative cancellation (agent.cancel()) for faster, more reliable stop
- Stop button now cancels at SDK checkpoint (between model events and before tool execution) instead of polling flags
- Subagent stop also uses SDK cancel
- Strands SDK updated to 1.0.0-rc.3, Bedrock SDK to 3.1030
v0.2.7
Fixes
- Fix Opus 4.7 thinking not displaying: Opus 4.7 defaults thinking.display to "omitted" (empty text, signature only). Now explicitly sets display:"summarized" to restore thinking content in responses.
v0.2.6
Fixes
- Fix context usage percentage showing inflated values for Opus 4.7 (was using 200K instead of 1M as context window)
- Context window size now reads from shared model catalog instead of hardcoded string matching
- Future models automatically get correct context % without code changes
- Fix Opus 4.7 thinking error: "reasoning content format incorrect" - Bedrock SDK updated to 3.1030 for new thinking block format (thinking field + signature)
Infrastructure
- Add @shared Vite alias for frontend access to shared model catalog
- Strands SDK updated to 1.0.0-rc.3
v0.2.5
New Model
- Claude Opus 4.7 support - Anthropic's most capable model, available on AWS Bedrock (us.anthropic.claude-opus-4-7)
- Opus 4.7 set as default model and routing target for expert-level tasks
- Opus 4.6 retained in catalog for backward compatibility
Fixes
- Fix Opus 4.7 thinking error: "thinking.type.enabled is not supported" - now correctly uses adaptive thinking for all Opus/Sonnet 4.6+ models
- Fix Electron EPIPE crash when tunnel log writes to closed pipe (uncaughtException handler)
v0.2.4
Fixes
- Fix context compaction triggering at 180K tokens instead of model's actual limit (Opus/Sonnet now compact at 900K, was 180K)
- Fix scheduler cron times interpreted in system timezone instead of user's configured timezone
- Fix hardcoded Asia/Seoul timezone in schedule display (now reads from USER.md)
- Fix heartbeat active hours check using wrong timezone
Improvements
- Context compaction now stays in the same conversation (no more "Continued:" new chats)
- Compaction inserts a visible divider message so users know it happened
- Context usage percentage resets after compaction
- Sidebar conversation list now supports infinite scroll (was limited to 50)
v0.2.3
Fixes
- Fix model ID resolution in agent core: canonical IDs now correctly converted to provider-specific ARNs via catalog (was passing `claude-opus-4-6` directly to Bedrock instead of `global.anthropic.claude-opus-4-6-v1`)
- Fix subagent model routing: subagents now resolve models through the same catalog as parent agent
- Fix GLM-5 and Kimi K2.5 not receiving `reasoning_config` on Bedrock (string-match detection replaced with catalog family lookup)
- Fix navbar hash links navigating to wrong page from blog (`#security` -> `/#security`)
SEO
- docs.foxl.ai: favicon (SVG), robots.txt, sitemap.xml (21 pages), OG/Twitter meta, canonical URLs per page
- foxl.ai: BreadcrumbList JSON-LD replaces SiteNavigationElement (fixes Google breadcrumb display)
- app.foxl.ai: SVG-only favicon (remove PNG fallback)
Blog
- New post: Foxl v0.2.2 release notes
- New post: What Breaks When Your AI Agent Runs on a Real Desktop
- Blog posts split into individual files with lazy loading (main bundle -28KB)
Build
- foxl.ai: Vite plugin auto-generates sitemap.xml on build
- docs.foxl.ai: `npm run build` auto-generates llms.txt + sitemap.xml from MDX sources
v0.2.2
Model Registry
- Unified model catalog: single source of truth for all models across desktop, relay, and providers
- Model IDs normalized to canonical short form (e.g. `claude-opus-4-6` instead of `global.anthropic.claude-opus-4-6-v1`)
- Adding a new model now requires editing 1 file instead of 5+
- Usage tracking shows clean canonical names (no more duplicate model entries)
- Relay derives model list from shared catalog
Settings
- Moved Logs from sidebar to Settings > Live Logs (collapsible card)
- System Report: download diagnostic file with system info, conversations, audit trail, and noise-filtered server logs
- Reports generated server-side for speed and richer data (audit.jsonl, DB conversations)
Chat
- Edit messages with attachment management: remove images/documents via X button before regenerating
- Save & Regenerate works even without text changes (useful for re-running with removed attachments)
- Auto-focus chat input on conversation switch (click sidebar conversation or new chat)
Sidebar
- Click "Chat" nav to start a new conversation (when already on chat page)
- Right-click "Chat" context menu: New Chat, Search, Delete All (all i18n)
- Right-click conversation context menu items now fully localized
Relay
- Non-Claude Bedrock models (GLM-5, Kimi K2.5) routed via Converse API instead of Anthropic InvokeModel
- Full tool support and streaming SSE translation for non-Claude models
Browser
- Dedicated Foxl browser profile (separate from personal Chrome)
- Login state persists across sessions within the dedicated profile
SEO
- docs.foxl.ai: add favicon (SVG), robots.txt, sitemap.xml (21 pages), OG/Twitter meta, canonical URLs
- docs.foxl.ai: per-page OG title/description/URL for all docs pages
- foxl.ai: replace SiteNavigationElement with BreadcrumbList JSON-LD (fixes Google showing all nav links as breadcrumb)
- foxl.ai: per-route BreadcrumbList via Worker (blog posts get Foxl > Blog > Title)
- app.foxl.ai: remove PNG favicon references, SVG-only favicon
Dev
- `RELAY_URL=http://localhost:4200` for local dev with foxl relay mode
v0.2.1
Skills
- Install skills from [skills.sh](https://skills.sh) registry directly in the Skills page
Scheduler
- Fix cron weekday schedules firing on wrong days after laptop sleep/wake
Overview
- Redesigned dashboard: today-focused metrics, cleaner layout, reduced polling
- Token usage by model with provider labels (Bedrock, Ollama, etc.)
- Usage Explorer shows which provider handled each model request
- Local data disclaimer in Usage Explorer
Search
- In-chat search (Cmd+F): find text within current conversation with persistent yellow highlights
- Cross-conversation search (Cmd+Shift+F): search across all conversations from sidebar
- Multi-word search: "hello world" finds messages containing both words
- Match navigation with up/down arrows and match counter (N/M)
- Click sidebar search result to navigate to exact message with highlights
- Korean and multi-byte text search support
UI
- Increase button spacing in prompt input toolbar for mobile
- Protocol-level WebSocket keepalive for more reliable connections
v0.2.0
Multi-Agent
- DB-backed auto-continuation queue — replaces two competing in-memory queues that caused duplicate agent spawns (3 agents becoming 6)
- SQLite lock table serializes drain execution — atomic INSERT OR IGNORE mutex, stale lock cleanup
- Remove injectMessage for subagent completion — fixes raw [SUBAGENT COMPLETED] text appearing in chat during auto-continuation
- Sidebar agent list redesign: elapsed timer, stop button (hover), shadcn Badge for collapsed mode
- AgentChatView: group consecutive thinking+tool items into single Message block (matches main chat spacing)
- AgentChatView: fix thinking blocks not displaying — each DB event rendered as separate timeline item
- Per-agent tool event callbacks — each agent's tool calls scoped to its own instance (no cross-agent leaking)
- Subagent thinking capture — model reasoning blocks (reasoningContentDelta/thinking_delta) recorded to DB
- Auto-continuation triggers per-completion — each agent result delivered independently (no 30s batching)
- Parent breaks after spawning subagents — waits for auto-continuation instead of re-prompting
- Fork children keep full tool list for prompt cache sharing — spawn blocked at hook level
- Block subagent spawning during auto-continuation turn (DB lock = spawn guard)
- Remove goal-based dedup guard (Claude Code doesn't do this)
- AgentChatView fully DB-driven timeline — survives WS disconnect, complete/error events rendered
- Remove duplicate goal message bubble + duplicate result rendering
- Completed agent goal display — merge DB goal into in-memory state
- Remove all subagent result/tool truncation (was 1000-5000 chars)
- Remove duplicate active agents section from Agents page (fork tags sanitized)
Tool Approval
- "Always Allow" auto-approves remaining parallel pending tool calls matching new permission rule
- Load DB permission rules at server startup — "auto approve all tools" was silently ignored (ESM require fix)
Channels
- Auto-reconnect channels on server restart (Telegram, Signal)
Scheduler
- Fix cron weekday field parsing — was completely ignored, causing schedules to run on wrong days
- Support ranges (1-5), lists (1,3,5), and mixed weekday expressions
UI
- Message queue redesign: accent background, line-clamp, hover-reveal delete button
- Terminal session count sync: fix stale closure in WS handler, sessions refresh on spawn/exit events
Skills
- Fix YAML folded scalar (`>`) and literal (`|`) block parsing in skill frontmatter
- Multiline description fields now correctly joined instead of showing `>` literal
Build
- Fix esbuild banner variable collision (`_cr` -> `__foxl_cr`) — prevented production app from starting
- Fix 6 pre-existing TS errors: GoogleModel import, ProviderType ('foxl'), PermissionSource ('user'), SQLInputValue
v0.1.9
Relay Dashboard
- New unified Relay card on gateway page with connection topology tree
- Desktop node shows online/offline status, platform, connection time
- Web client branches display browser, OS, and connection duration
- Remote restart: restart desktop app from any device via tunnel
- Remote update check: trigger auto-update remotely
- Force disconnect individual web clients by fingerprint
- Session history timeline with audit log (deduplicated, last 10 entries)
- Tunnel toggle integrated into dashboard header (desktop mode)
- Account email and relay connection status in card description
- 15s auto-refresh with visibility-aware polling
- Full i18n support across 10 languages (27 new keys)
Tunnel Cost Optimization
- On-demand heartbeat: idle 5min, active 30s (was 24/7 30s)
- HTTP heartbeat bypasses Durable Object entirely (zero DO request cost)
- Slow/fast heartbeat switching on web client connect/disconnect
- Dead connection detection maintained via HTTP liveness check
- Estimated tunnel capacity: ~630 DAU on $5/mo Cloudflare plan (was ~115)
Mobile Terminal
- Compact quick keys: smaller buttons prevent horizontal overflow
- Scroll-to-bottom button in quick keys bar
- Auto scroll-to-bottom after terminal resize (prevents mobile scroll jump)
Foxl Notes Landing
- White/light theme redesign for foxl.ai/note
- Pricing section removed
- Modernized mockup styling
Landing Page
- "Products" renamed to "Projects" (portfolio tone)
- FAQ rewritten: free/open project, Claude Code/Dispatch comparison, mobile access, privacy
- Hero subtitle: "A personal AI agent... Free and open."
- CTA: "Try it out" with "No account required"
- Footer: "An open project by foxl-ai" (was copyright notice)
- Refund link removed from footer
- Billing/subscription language hidden from Terms and Privacy (comment-preserved)
- Nacho entity reference hidden from legal pages
Fixes
- Model selector: foxl relay models now appear first when tunnel connected
- Discord bot: pricing-disabled instruction prevents outdated billing answers
- Terminal scroll position preserved across resize events
v0.1.8
Tunnel Stability
- Fix DO hibernate state loss: persist userId/deviceId to durable storage, restore on wake
- Fix tunnel status: check is_online flag for instant disconnect detection (was only using stale last_seen_at)
Tunnel Cost Optimization
- Durable Object Hibernatable WebSocket API: DO sleeps when idle, wakes on message
- Client-driven heartbeat: desktop sends ping every 30s (no more server-side setInterval)
- Chat delta batching: 100ms window reduces DO wake-ups by ~90%
- Estimated DO cost reduction: $8.38/mo to ~$0.41/mo per 2 tunnel users
Subagent Fork
- Fork subagent: children inherit parent's full conversation as byte-identical prefix for prompt cache sharing
- Identical system prompt + tool definitions across fork children (Claude Code pattern)
- Anti-spawn via fork directive prompt (soft guard) + isInForkChild() code check (hard guard)
Terminal Reopen Fix
- Terminal panel loads scrollback on reopen (no more blank black screen)
- Works for both running and restored sessions
Mobile Chat Copy
- "Copy Entire Text" works on app.foxl.ai (reads from localStorage in relay-only mode)
- Message copy/edit/regenerate buttons always visible on mobile (no hover needed)
Fixes
- Fixed auto-update "Relaunch" button only hiding window instead of restarting on macOS
- Fixed 10+ TypeScript type errors across App, AgentEvent, ImageAttachment, ConversationItem, ScheduleExecution
v0.1.7
Native 1M Context Window
- Opus 4.6 and Sonnet 4.6 now have native 1M context window (no beta header needed)
- Removed separate "(1M)" model variants -- all Opus/Sonnet 4.6 are 1M by default
- Simplified model selector: single entry per model instead of two
Adaptive Thinking Effort
- New effort level selector: Low, Medium, High, Max (replaces token budget)
- Adaptive thinking with effort parameter sent via Bedrock `output_config`
- Max effort is Opus 4.6 exclusive (other models fall back to High)
- Default effort level: Medium
Tool Approval
- Claude Code-style permission prompts inline with tool calls: Allow, Always allow, Deny
- Pattern-based trust: "Always allow" on `exec ls` creates `exec(ls*)` rule
- Permission rules persist to DB across restarts, with `*` wildcard as default
- Settings page: toggle auto-approve-all, add/remove patterns manually
- Built-in safety: destructive commands (`rm -rf /`, `mkfs`, `dd`) are always denied
Agent Core v2
- Context compaction: auto-summarize long conversations to stay within context window
- Error retry with exponential backoff for transient failures
- Hook system: BeforeToolCall / AfterToolCall lifecycle for permission checks and plugins
- Fork subagent: spawn child agents from parent conversation context
- System prompt token optimization (34K to 19K chars, 44% reduction)
GLM 5 + Kimi K2.5
- GLM 5 (Z.AI) available on Bedrock + relay with reasoning_config thinking support
- Kimi K2.5 (Moonshot AI) with 256K context, vision, and tool use
Context Usage in HUD
- Context window usage percentage displayed in macOS titlebar HUD
- Per-conversation tracking from actual token metrics
- Real-time updates during streaming, persists across conversation switches
Claude Code SSO Fixes
- Fixed double text output and shimmer stuck during streaming
- Rewrote CLI stream parser for reliable tool result handling
File Attachments
- Drag & drop files onto chat area with visual drop zone overlay
- Accept any file type via paste or file picker (json, yaml, py, ts, etc.)
- Unsupported Bedrock formats (json, xml, yaml) mapped to txt server-side
- Document attachment pill constrained to message bubble (no overflow)
Subagents
- Removed execution timeout — agents run until completion (no more premature stops)
- Auto-continuation max-wait safety timer (30s) for result synthesis
Settings Persistence
- AWS profile, region, and thinking effort persist across server restarts
- Saved to server-config.json in data directory
Schedule Reliability
- Schedules auto-disable after 3 consecutive failures (including stuck task timeouts)
- Prevents infinite retry loops for broken schedules
Large Text Paste
- Virtualized text viewer for attached text blocks (handles 1M+ chars without freezing)
- indexOf-based parser replaces regex (no backtracking on huge content)
- Copy button still copies full original content
Provider Detection
- Bedrock provider correctly reports unconfigured when no AWS credentials exist
- foxl.ai relay models listed above BYOK/bedrock models in model selector
Update Banner
- "Updated to vX.X.X" with Relaunch button in sidebar when auto-update completes
Landing Page
- Animated SoftAurora background with mouse interaction and fade-in
- Redesigned Security section with CSS Grid layout
- Discord button, pricing display, license URL fixes
Electron
- Electron 41, electron-builder v26
- Bundled Node.js runtime for non-developer users
- Source map protection (never ship .map files)
Fixes
- Model/provider sent in chat stream request body (fixes race condition)
- Fixed tunnel double-connection on sleep/wake reconnect
- Fixed production terminal, schedule persistence, provider switching
- Auto-close terminal panel when last session killed
- Fixed spinner stuck after stream completion (agentRunning:false broadcast)
- OS-aware download button in navbar
- Sidebar refresh after OAuth login
v0.1.6
Claude Code SSO
- Use your Claude Pro/Max subscription directly in Foxl -no API key needed
- Auto-detects credentials from macOS Keychain or ~/.claude/.credentials.json
- Just run "claude auth login" once, then select Claude Code (SSO) in Settings
- Token-level streaming with thinking/reasoning separation
- Available models: Opus 4.6, Sonnet 4.6, Haiku 4.5 via your subscription
BYOK Relay Passthrough
- Use your own Anthropic API key through app.foxl.ai -zero Foxl credits consumed
- Relay acts as stateless proxy -your key is never stored server-side
- Usage tracked for analytics only (cost: $0)
Terminal Relay Interface
- Built-in terminal in Chat UI -spawn PTY sessions, tabs, split view, resize
- JetBrains Mono Nerd Font bundled (Ghostty-style zero config)
- Remote monitoring via relay tunnel -view desktop terminals from mobile
- Terminal tool: agent can spawn, write, read, kill sessions programmatically
- ANSI strip filter for clean LLM output
Chrome Extension Multi-Tab
- Multi-tab browser control -switch between tracked tabs with labels
- New commands: switch_tab, list_contexts for tab management
- Sidepanel tab bar UI with chips and 3s polling
Bedrock Prompt Caching
- Automatic prompt caching via cacheConfig strategy for Opus/Sonnet/Haiku
- Applied across server, agent, and provider layers
Relay
- Ultra tier upgraded to 10,000 credits/month
- Free tier Opus access blocked (Haiku + Sonnet only)
- Tunnel pending request cap (50) + chat stream cap (10) for stability
Async Subagents
- Auto-continuation: when all subagents complete, main agent auto-synthesizes results
- Completion queue prevents concurrent auto-continuations on same conversation
- Schedule persistence across app restarts
Agents UX
- Terminal fullscreen mode in chat panel
- Crew-style suggestion cards on empty chat screen
- AI-generated suggestions with structured defaults
Skills Sync
- Auto-sync skills from foxl-ai/skills repo on first install
- Skills sync button in SkillsPage UI
- Local edits preserved via content comparison during sync
Mobile
- Terminal persistence, mobile IME fix, mobile terminal UI
- Mobile terminal return icon and Del key
Windows Support
- Native Windows desktop app -NSIS installer + portable exe
- CRLF line ending normalization for skills parsing
- PowerShell terminal with smart shell detection (pwsh.exe, powershell.exe)
- Native Windows title bar, portable Node.js detection
- Code signing with self-signed certificate
Auto-Update Fix
- Universal build targets -single DMG/ZIP works on both Intel and Apple Silicon
- Fixed latest-mac.yml referencing non-existent arch-specific files
Discord Integration
- Discord Q&A bot with /ask slash command (Cloudflare Worker)
- Gateway bot with presence detection and auto-respond in channels
- Relay auth with KV token persistence for bot sessions
Release Automation
- Changelog automation pipeline: CHANGELOG.md as single source of truth
- Release scripts: `release.sh` (full desktop release), `sync-changelog.sh` (changelog-only deploy)
- Generated changelog surfaces: foxl.ai/#changelog, foxl.ai/changelog.md, GitHub Release notes
Fixes
- Enable AI suggestions in relay-only mode via tunnel
- Terminal mobile return button visibility and light theme text color
- app.foxl.ai: relay-only sidebar now correctly shows only Chat and Account pages
- Message copy handler preserves hyperlinks as "text (url)"
- Gateway page i18n -hardcoded strings replaced with translations
- Terminal line ending normalization for cross-platform PTY compatibility
- Terminal tool: backspace character processing for clean AI output
- Terminal tool: literal \n escape handling for reliable command execution
- Schedule execution prevConvId not defined error
- esbuild-bundle server to fix 5+ minute NSIS install time (16K to 420 files)
- NSIS installer auto-closes running Foxl before install
- Windows taskbar icon uses exe embedded icon for proper size
- 800+ translation keys across 10 locales (en, ko, ja, zh, es, fr, de, pt, ru, ar)
v0.1.5
Auto-Update
- VSCode-style silent auto-update -download in background, notify when ready
- Settings toggle to enable/disable auto-update
Tunnel: Login = Access
- Pairing code flow removed -relay auto-registers device on connect
- Web clients connect immediately (no approval blocking)
- E2E encryption (ECDH) on all WebSocket messages
Tunnel E2E Streaming
- Chat streaming via relay tunnel with SSE passthrough
- Image and document upload through tunnel
- Reconnect recovery with snapshot hydration
Fixes
- Electron UA: real platform/arch (was hardcoded Intel Mac)
- Session detection: Foxl/ UA parsing for device info
- Model naming: "Opus 4.6[1m]" style (Claude Code convention)
- sharp added to server-bundle for screenshot tool
v0.1.4
Multi-Agent Thread UI
- Codex-style subagent orchestration with collapsible thread sidebar
- Real-time subagent streaming via WebSocket events (200ms batched flush)
- Event-driven child-to-parent result injection on subagent completion
- SpawnIndicator inline in parent chat with live elapsed timer
- Thread history endpoint (GET /api/agents/:agentId/history)
Streaming Recovery
- Snapshot endpoint for instant page-refresh recovery (text, tool calls, threads)
- Sequence-based SSE subscribe with gap detection
- Recovery state machine (idle, fetching_snapshot, subscribing, live, disconnected)
- WS streaming_state enrichment with currentText, toolCalls, threads
- Fallback to SSE replay when snapshot unavailable
Model Selector
- app.foxl.ai now follows desktop model/provider selection via tunnel
- Desktop BYOK models visible in relay-only mode when tunnel connected
- Model selection syncs back to desktop via tunnel API
- Fix: model selector silently overriding local model with foxl relay
SDK
- Strands Agents SDK upgraded to v0.7.0 (Plugin system, prompt caching)
Fixes
- Credit exhausted popup removed from AccountPage
- Chrome extension Pilot renamed to Foxl branding
- Default provider fallback changed from 'foxl' to 'bedrock'
- WS multi-subscriber Set pattern (prevents callback overwrite)
v0.1.3
Tunnel
- Tunnel resilience: heartbeat 3x failure reconnect with backoff reset
- Online-gated routing: __foxlTunnelOnline flag, sidebar isOnline
- Tunnel auto-refresh on online/offline transition
- Load desktop conversations via tunnel API when connected
Chat
- Mobile-first ChatPage layout (relay-only always full-width)
- Model ID resolution: compound prefix + version suffix handling
- WS 4003 token mismatch auto-reload
Fixes
- Chrome extension health check 500ms AbortSignal timeout
- GatewayPage device field mapping normalized
- Provider selection persists via localStorage key
- All model tiers accessible on all plans (restriction removed)
v0.1.2
Web App
- app.foxl.ai: relay-only chat with localStorage conversations
- OAuth hash login, Google/Apple sign-in on web and desktop
- AccountPage: credits display, subscription, lazy sessions
- Onboarding flow: welcome, extension, login, chat
Relay
- Welcome 200 credits on signup, monthly 10 credit refill (cron)
- Atomic credit deduction (D1 batch transactions)
- D1-backed rate limiting, OAuth CSRF protection
- Strict model ID validation, 5 Claude models enabled
- relay-worker/ consolidated to relay/
Desktop
- Universal macOS build (arm64 + x64)
- Random port + connection token (VSCode-style security)
- Server crash protection, CORS hardening
- sessions_status tool: waitSeconds parameter
Docs & Infra
- Starlight documentation site (16 pages, full-text search)
- llms.txt / llms-full.txt from real documentation
- CI workflows: deploy-app, deploy-docs, deploy-relay, deploy-site
- Version unified to 0.1.2 across all packages
v0.1.1
Agent
- AgentsPage: make expanded session card scrollable
Chat
- Channel: group mention filter, Signal greeting, conversation history API
UI
- Topup UI, webhook idempotency, tray menu, deep link, build fix
- i18n: add useTranslation hook to all 29 sub-components
- i18n: apply t() to 109 strings across 6 pages
i18n
- i18n expansion (496 keys, 10 locales), dev data path fix
- 251 keys across 10 locales, sync script, comprehensive coverage
- Expand to 10 languages, system language detection, complete translations
Skills
- Revert nested skills scan, use symlink instead
- Support nested skills dir + add .omc to gitignore
Fixes
- Fix SettingsPage crash: add useTranslation hook to main component
- Fix i18n: add useTranslation import and hook to all pages
- Fix DialogTitle accessibility warning, add PILOT_SKIP_SERVER
- Fix message queue leaking across conversations
- CI cache-dependency-path for setup-node
Other
- Add Foxl proprietary license
- Security: path traversal protection, JWT production guard
- Show git commit hash in app version (Settings > About)
v0.0.2
Agent System
- OpenClaw-parity heartbeat runner with active hours, stuck detection, requests-in-flight skip
- Context overflow auto-continuation (creates new conversation with summary)
- Subagent spawn with result/error saved to conversation messages
- Subagents blocked from spawning other subagents (infinite recursion prevention)
- WebSocket reconnection restores agent state automatically
Chat
- Tool call display with per-tool icons (15 icon types: Monitor, Globe, Terminal, Database, Git, etc.)
- Human-readable tool titles for all 20+ tools
- Confirmation component (shadcn AI pattern) for tool approval
- Extended thinking with collapsible reasoning display
- Image upload, paste, and document attachment support
Skills
- 68 built-in skills (ported from OpenClaw)
- Skills always loaded regardless of binary availability (install at runtime)
- Lazy skill loading (list only, read on demand) for token savings
- Skill eligibility check with missing dependency display
UI
- Agents page redesign: date grouping, time range filter, status filter
- Agent detail dialog: stepper activity timeline, tools used summary, last response
- Settings: Heartbeat config card, provider SVG icons, active hours selector
- Workspace/Skills: Cmd+F search with content search, search button
- Usage Explorer: 30-day default, estimated cost per model
- Dock badge: notifications + unread chat count
Providers
- 7 relay proxy providers: Bedrock, Anthropic, OpenAI, Google, Groq, xAI, DeepSeek
- 24 local providers via OpenAI-compatible API
- Provider SVG icons (light/dark mode)
- Credit system with 10% margin (1 credit = $0.04)
Channels
- Telegram bot integration (non-blocking start)
- Signal CLI integration (foxl-ai/signal-cli)
- Channel send tool for proactive outbound messaging
v0.0.1
- Initial Electron desktop app with embedded server
- Pixel Office view with agent characters and pet system
- Code splitting: 14 lazy-loaded pages (1,407KB to 177KB main bundle)
- Token usage tracking with SQLite persistence
- Session memory with LLM-generated summaries
- Context compaction via Bedrock compact API
- Tool approval system with always-allow option
- Browser automation with accessibility tree snapshots (93% token reduction)
- Workspace memory system (SOUL.md, USER.md, MEMORY.md, daily logs)
- Scheduling system (cron, heartbeat, webhooks)
- Multi-provider support (Bedrock, Anthropic, OpenAI, Google, Ollama)