You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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: <path> + 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-<timestamp>-<rand>.<ext> 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-<YYYY-MM-DD>-<prompt-slug>-<suffix>.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/internalOpenAIImageModel 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).