Treat the mobile web surface as a constrained runtime so sync loads smaller message pages, keeps fewer warm session caches, and evicts heavy inactive sessions instead of retaining them across switches.
Limit mobile message-record and turn-model caches to reduce memory pressure on phones while preserving bounded initial page expansion for large final turns.
Split the mobile session status bar so the collapsed state avoids subscribing to the full session/status list; the expensive grouping work now only mounts for the expanded list.
Verified with bun run type-check and bun run lint.
Prevents queued messages from being sent to a newly opened session
Adds explicit session targeting for queued auto-send
Covers the behavior with a unit test
Reconnects and resyncs active sessions when live updates stall
Normalizes synthetic session status events
Uses authoritative status snapshots to clear stale busy states
Improve chat session switching and history pagination, with most of the aggressive limits scoped to the VS Code webview where the freezes were observed.
Session history loading and pagination:
- Reduce the VS Code message page size to 30 records so switching sessions does not immediately hydrate large histories into the webview.
- Keep manual Load older messages in VS Code fixed at 30 records per request instead of growing the request size over time.
- Add a bounded VS Code initial-tail expansion path from 30 to 50, 80, and 120 records only when the initial page has no user-message turn boundary, preventing large final turns from rendering as an empty chat.
- Lower the normal web message page size from 200 to 150 for a mild shared optimization without adopting the aggressive VS Code limits.
- Make session pagination metadata reactive per session so ChatContainer receives cursor updates from materialization and reconnect paths without requiring a switch away and back.
- Write pagination metadata before publishing newly materialized messages so the first render sees the correct has-more state.
- Store cursor information from direct materialization and reconnect message fetches in the shared session prefetch metadata cache.
VS Code cache and memory pressure reductions:
- Use a shared per-directory session recency map so cache eviction is based on app-level recency instead of whichever useSync instance happened to run.
- Limit VS Code warm session cache retention to 4 sessions and evict heavy inactive message caches after switching away from a large session.
- Disable sidebar session prefetch in VS Code because warming extra sessions was increasing webview memory and GC pressure during navigation.
- Remove dropdown background message prefetch so opening the switcher does not start additional session materialization work.
- Drop cached session-message-record snapshots when evicting session data so stale derived records do not remain after the raw session cache is cleared.
- Add bounded LRU caching for session message record snapshots, with much smaller VS Code limits and a VS Code cap that avoids caching snapshots above 30 messages.
- Bound the turn-window model cache in VS Code and avoid caching turn models for sessions above the VS Code message-page size.
Chat render-path reductions:
- Reuse ChatContainer's already-materialized message records in plan detection instead of adding a second active-session message subscription.
- Add a no-op guard when marking session plan availability so repeated detections do not create new Map references and fan out renders.
- Add no-op guards for session switcher and dropdown open state updates to avoid unnecessary store updates and renders.
- Convert several session-specific hooks to useSyncExternalStore with empty-session no-subscribe behavior so empty IDs do not subscribe to broad store updates.
- Remount the chat viewport when the current session changes, isolating per-session viewport and list state.
- Change the virtualized message-list fallback to render only a tail window when the virtualizer has not produced rows yet, instead of rendering an entire large history.
VS Code layout and header improvements:
- Remove the broad useSessions subscription from the VS Code layout header path and subscribe only to the active session title and initial-session existence.
- Unmount the compact VS Code session sidebar when the user is in chat view instead of keeping the hidden session list mounted and subscribed.
- Compute the latest assistant model and latest context-token usage in a single reverse scan of current-session messages instead of scanning the same list twice.
- Remove switcher git-status warmup work so the switcher reads already-loaded branch labels without starting extra background git status requests.
Markdown and file-reference safeguards:
- Skip expensive syntax highlighting for very large code blocks, with a 200-line cap in VS Code and a softer 1200-line cap in web.
- Add an LRU cap to file-reference stat lookups so the cache cannot grow without bound across many rendered messages.
- Limit the number of file references annotated per render to 40 in VS Code and 200 in web to prevent large assistant outputs from spawning too many stat checks.
- Clear file-link annotations when file-reference mode is disabled so stale attributes and handlers do not remain on previously annotated nodes.
Assistant-message action and preview reductions:
- Skip preview URL scanning on VS Code, mobile, and mini-chat surfaces so assistant text and tool output are not scanned where the preview action is unavailable.
- Skip Save-as-Plan project lookup on VS Code, mini-chat, and mobile surfaces.
- Hide Save-as-Plan and Start MultiRun assistant-message actions on VS Code, mini-chat, and mobile surfaces.
- Resolve the current session directory on demand for assistant actions instead of subscribing each assistant message to the full session list.
Tool and task rendering optimizations:
- Prefer finalized task metadata summaries without fetching child-session messages when the summary is already present.
- Avoid polling or final-fetching task child sessions once a final metadata summary is available.
- Use VS Code-specific task child fetch limits of 30 records for initial, active, and idle fetches.
- Parse diff stats by scanning patch text line-by-line instead of splitting large patches into arrays.
- Count write-tool lines by scanning content instead of allocating a split array for large files.
- Avoid trimming large patch strings just to test whether they contain content.
- Memoize diff and write statistics so unchanged tool parts do not recalculate them on every render.
VS Code bridge improvements:
- Return JSON and text proxy responses through the VS Code bridge as bodyText instead of base64 so the webview avoids synchronous base64 decoding for common API responses.
- Keep binary responses on the base64 path while making bodyBase64 optional in the bridge contract.
- Strip content-length, content-encoding, and transfer-encoding headers from proxied responses because the bridge reconstructs the Response body.
Validation:
- bun run type-check
- bun run lint
- bun run vscode:build
Move VS Code/Cursor desktop notifications onto the webview Notification API instead of the extension-host watcher path, which could not reliably produce native OS notifications.
Route OpenCode runtime events from the shared sync pipeline into the VS Code webview so completion, error, question, and permission notifications use the same live event stream as the UI.
Respect the OpenChamber notification settings in VS Code, including template rendering, completion cooldowns, permission auto-accept suppression, and the notify-while-focused mode.
Use VS Code's window focus signal from the extension host instead of document.hasFocus() inside the webview, so hidden-only notifications are suppressed while Cursor or VS Code is focused across platforms.
* fix: preserve state when reconnect-time fetches fail
Several client API methods swallowed fetch/SDK errors and returned an
empty value (`[]`, `{}`), which was indistinguishable from a successful
"server says nothing here" response. Reconnect resync paths trusted that
empty result as authoritative and deleted local state — so after a
network blip (sleep/wake, wifi reconnect, tunnel switch), the UI could
show:
- sessions stuck on the "running" indicator (status never cleared)
- pending permission prompts disappearing from the UI
- pending question prompts disappearing from the UI
and only a page reload would recover. A related case: `listAgents`
silently returning `[]` defeated the 3-attempt retry loop in
`useAgentsStore` because the loop never saw an error.
The systematic fix:
- `getSessionStatusForDirectory` now returns `null` on fetch failure
(vs the previous `{}`); the reconnect resync treats only a non-null
response as authoritative — candidates missing from the response are
written as `{type: "idle"}`, candidates after a failure are left
untouched.
- `listPendingPermissions`, `listPendingQuestions`, and `listAgents`
now throw on SDK/network failure. The pre-existing outer try/catch
blocks in `resyncBlockingRequestsForDirectory` and the retry loop in
`useAgentsStore` were already in the right shape — they just never
fired because no exception was thrown. A small `formatSdkError`
helper renders the SDK `{data, error}` shape into the thrown message.
- `permissionStore.setSessionAutoAccept` catches the new throw and
falls back to whatever sync-store snapshots provide; the next SSE
event or reconnect resync will catch up anything missed.
AGENTS.md gets a new "Distinguish fetch failure from empty success"
subsection documenting the principle (throw vs `T | null` patterns,
when to pick which, the retry-loop trap) so this doesn't regress.
Adds 3 regression tests covering the resync paths: existing
questions/permissions are preserved when the corresponding `list*`
method throws, and a permission-fetch failure does not block the
question block from running (verifies per-block try/catch isolation).
* fix: pause reconnect loop when offline or hidden
The SSE/WebSocket reconnect loop retried indefinitely with no awareness
of whether the browser was online or whether the tab was even visible.
Three issues compounded:
- No `online`/`offline` event handling. With a foreground tab on a dead
network, we'd hit the server every ~5s forever, and on network
recovery we'd wait up to ~5s for the next probe instead of reacting
to the `online` event.
- No visibility awareness. A backgrounded PWA on a flaky link kept
probing at the same rate as a foreground tab. The browser does
throttle hidden-tab timers, but the intent wasn't expressed in code.
- The "exponential backoff" math
`min(5000, max(retryDelayMs, 250) * (failures <= 1 ? 1 : 2))`
re-initialized `retryDelayMs` to 250 every iteration, so the cap of
5s was never reached — we waited 500ms forever after the second
failure. Not actually exponential.
Now:
- `online` event aborts the current attempt (if disconnected) and
cuts inter-attempt waits short. `offline` event aborts so the loop
enters the slow-probe path immediately.
- `computeRetryDelay` returns the long cap (60s) when `navigator.onLine`
is false or the tab is hidden; the short cap (5s) when foreground +
online. The `online` event is the expected recovery path; the 60s cap
is a fallback for browsers that miss the event.
- Real exponential growth: `BASE * 2^min(failures-1, 8)`, clamped.
- New `waitForRetry` helper interrupts on `online`,
visibility-becomes-visible, and abort signal — so visibility/network
recovery doesn't wait out the rest of the current sleep.
AGENTS.md gets a "Reconnect-loop pacing" subsection alongside the
fetch-failure rule, since they're the same family of resilience
concerns.
One regression test: simulates offline + failed first attempt + `online`
event after the failure; verifies the next attempt fires within seconds
instead of waiting the full 60s offline cap.
* fix: long-cap backoff for permanent 4xx server errors
Before this commit the reconnect loop didn't distinguish HTTP error
types. A stuck-path client (wrong URL after server upgrade) or an
expired-auth client (stale token) would hit the server at the normal
5-second cap forever — ~12 reqs/min, indefinitely, with no path to
recovery besides the user reloading.
Now the catch block extracts an HTTP status (looking on `error.status`
and `error.response.status` — the SDK exposes both depending on the
code path) and overrides the backoff:
- 4xx other than 408/429 → use the long cap (60s) immediately.
Blind retries won't fix wrong path / bad auth / forbidden, so don't
pound the server. waitForRetry's `online` / visibility-visible
interrupters still apply — when an operator fixes the server-side
config and the client comes back to foreground, recovery is prompt.
- 408 (Request Timeout) and 429 (Too Many Requests) → normal
exponential path. Those are retryable in spirit.
- 5xx / network / unknown → normal exponential path. Unchanged.
AGENTS.md gets a new bullet under "Reconnect-loop pacing" covering
this — the rule fits naturally alongside the existing `navigator.onLine`
and visibility signals.
Two regression tests:
- A 404-throwing SDK doesn't fire a second attempt within 250ms (proves
we left the exponential path). After `online` interrupts the wait,
subsequent attempts fire promptly — proves the override doesn't break
recovery once the underlying problem is fixed.
- A 429-throwing SDK recovers within 2s — proves 429 still hits the
fast exponential path and isn't caught by the permanent-error branch.
---------
Co-authored-by: vhqtvn <8930337+vhqtvn@users.noreply.github.com>
* chore: add .worktrees/ to gitignore for worktree workflow
* fix(chat): restore file attachments when reverting or forking messages
* fix(chat): address review findings in attachment restoration
- Move filePartsToAttachments helper below all imports into its own
'Attachment helpers' section (was incorrectly placed between imports)
- Compute size from base64 data URL for pasted screenshots instead of
hardcoding 0; file:// URLs keep size 0 which formatFileSize suppresses
gracefully
- Capture prevAttachedFiles before optimistic mutation and restore on
SDK revert failure
- Always use source: 'local' for restored attachments so they are
visible and removable in the composer regardless of URL scheme
* fix(chat): resolve merge conflicts and restore attachments in fork
- Merge upstream main which already added attachment restoration to
revertToMessage via addRestoredAttachment
- Add !isSyntheticPart filter to revertToMessage file part collection
(upstream was missing this)
- Add attachment restoration to forkFromMessage (was not fixed upstream)
- Use upstream's addRestoredAttachment approach for consistency
* fix(chat): clear restored attachments when opening new session draft
Reverted-message attachments (and any other pending attachments in the
global input store) were carrying over to the new session input because
openNewSessionDraft did not clear attachedFiles.
Clear attachedFiles in openNewSessionDraft, which is the navigation-away
event for new sessions (it already sets currentSessionId: null). This
matches the semantics of starting a fresh conversation.
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* feat(ui): revert indicator with undo/redo, message list, and attachment restore
Add bidirectional undo/redo with redo stack navigation and expandable
revert indicator in StatusRow. List reverted messages with inline
revert/fork actions. Restore file attachments on revert.
- Add revert indicator with count, expandable popover, per-button spinner
- Restore file/image attachments when reverting to a message
- Clear previous attachments on revert when target has none
- Restore-all bypasses redo stack for direct unrevert
- Survive popover close/reopen without losing loading state
- Fix flash when sending message after revert
- Fix count disappearing on browser refresh
- Remove dead code (undoStack, getRevertHistory, fork-from-here)
- Fix toast grammar (Undid -> Reverted, Redid -> Redone)
- Add i18n keys for revert popover across all locales
* fix(ui): Greptile review fixes and i18n for revert toasts
- Fix handleSlashUndo toast always showing [No text] (moved getSyncParts before revertToMessage)
- Add inputStore rollback in revertToMessage catch (restore attachments + text on API failure)
- Change portal ID to per-session (prevent multi-session collisions)
- Add i18n keys for undo/redo/restored toasts across all 7 locales
- Use formatMessage in store for localized toast strings
* fix(ui): add missing sessionId to click-outside effect deps
* fix(ui): remove unused sessionActions import in ChatMessage
* fix(ui): derive revert dock from session state
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
Add dedicated Electron Mini Chat windows for focused chat sessions without the full desktop shell. Mini Chat can open existing sessions or draft sessions, supports pinning above other windows, transfers sessions or drafts back to the main window, and deduplicates existing-session windows.
Expose Mini Chat entry points from the main header, session sidebar, command palette, and `mod+alt+n`. Add a dedicated Vite entry and React runtime so the compact surface can stay isolated from full-app chrome while still sharing chat, sync, theme, locale, model, agent, and worktree behavior.
Keep Mini Chat behavior scoped to the compact surface:
- limit assistant/user message actions to the appropriate Mini Chat set
- hide workspace changed-files UI in Mini Chat
- keep draft worktree selection and streaming directory state in sync
- mark sessions viewed while they are open in Mini Chat
- support Mini Chat-specific keyboard shortcuts for input focus, model selection, thinking variant cycling, favorite model cycling, and opening new Mini Chat drafts
Harden Electron integration by gating Mini Chat controls on desktop IPC availability, restricting pin/unpin IPC to Mini Chat windows, and only closing Mini Chat after the main window handoff succeeds.
Canonicalize session message/part materialization across load, prefetch, reconnect, and recovery paths so OpenChamber restores session snapshots through one consistent merge flow.
Preserve live assistant streaming text when stale or delayed snapshots arrive, while still replacing optimistic user parts with confirmed server snapshots to avoid duplicated user messages.
Narrow recovery triggers to explicit incomplete snapshot signals instead of broad session-event fallbacks, reducing unnecessary session refetches during active streaming.
Keep turn windowing aligned with parented assistant replies and add regression coverage for materialization gaps, stale snapshot protection, optimistic user replacement, reconnect recovery, and turn grouping.
Limits reconnect and repair message fetches to recent messages
Reduces repeated bandwidth for large tool outputs
Keeps normal session loading unchanged
* fix(sync): preserve pending questions across session switch and directory eviction
Closes#918, completes the gap left by #909.
The 'agent question disappears after switching session / coming back
later' bug had two root causes that #909 only partially addressed:
1. Directory-eviction TTL (20 min) silently dropped child stores that
held pending questions/permissions. The discard wasn't gated on
in-flight blocking-request state, so any 'question.asked' event
that arrived during the eviction-then-rehydrate window was routed
to a non-existent store and silently lost.
2. PR #909 re-fetches listPendingQuestions/Permissions only on SSE
reconnect. Switching sessions within the same socket — including
navigating back to a directory whose child store was rebuilt after
eviction — left the UI relying on store state that may have missed
events that fired while a different session was active.
Three edits, in src/sync:
- eviction.ts / types.ts / child-store.ts: add hasPendingBlockingRequests
to EvictPlan + DisposeCheck and never evict a directory whose store
carries a non-empty state.question or state.permission record.
- sync-context.tsx: extract resyncBlockingRequestsForDirectory from the
reconnect path and call it on currentSessionId changes (debounced
250ms), reusing PR #909's signature-based merge so concurrent SSE
updates aren't clobbered.
- __tests__/eviction.test.ts, __tests__/session-switch-resync.test.ts:
new unit coverage for the eviction guard and resync semantics
(deduped fetch per switch, in-flight SSE preservation, stale entry
cleanup, unknown-session filtering).
* fix(sync): refresh store before blocking request resync
---------
Co-authored-by: Alexander Busse <alex@ableph.net>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* feat(chat): add active editor file context and related functionality
* feat(chat): improve active editor file context handling and update translations
* feat(i18n): standardize quotation marks in file attachment messages
* update Korean translation for image removal action in file attachment
* feat(chat): refine active editor file handling and optimize broadcast logic
* fix(chat): stabilize VS Code editor context chips
---------
Signed-off-by: David Saz <david.saz.g@gmail.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* fix(server): increase WS buffer/replay limits and add backpressure warning
During long-running agent sessions (e.g. ultrawork loops with many tool
calls), the browser WebSocket client can briefly fall behind the server.
When the outbound buffer exceeds the limit, the server force-disconnects
with close code 1013, and the small replay buffer (512 events) is
insufficient to recover all missed events — leaving the UI permanently
stalled.
Changes:
- Raise MESSAGE_STREAM_WS_MAX_BUFFERED_BYTES from 4 MB to 16 MB to
tolerate larger bursts without disconnecting
- Add MESSAGE_STREAM_WS_BACKPRESSURE_WARN_BYTES (12 MB) threshold that
sends a one-shot "backpressure" frame to the client before the hard
disconnect, giving it a chance to shed low-priority updates
- Raise MESSAGE_STREAM_GLOBAL_REPLAY_LIMIT from 512 to 2048 so more
events survive brief reconnection gaps
- Add tests for the backpressure warning behavior (emit, dedup, reset)
* fix(ui): batch event flushes under backpressure
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* fix: restore scroll position when switching chat sessions
When switching between chat sessions, scroll position now restores to
where the user left off instead of always jumping to the bottom.
- Save pixel-level scrollPosition (scrollTop/scrollHeight/clientHeight)
in viewport store on every scroll event
- Add restoreSavedScrollPosition to timeline controller for ratio-based
restoration (handles content size changes between visits)
- Suppress intermediate scroll events during session transition with an
explicit flag, cleared deterministically after restore completes
- Cancel in-flight animations/follow-loops on session switch
- Preserve scrollPosition when session-ui-store rebuilds SessionMemoryState
* fix: keep streaming sessions pinned on restore
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* feat: embedded preview proxy for local dev servers
Add a same-origin server proxy under /api/preview/proxy/:id and
matching UI surfaces so local dev servers (Vite, Next, etc.) can be
embedded inside OpenChamber.
Server (packages/web/server):
- New lib/preview/proxy-runtime.js: cookie-gated HTTP+WebSocket proxy
to loopback hosts only, with TTL'd targets and SSRF allowlist.
- index.js wires the runtime alongside terminal/event-stream.
UI (packages/ui):
- ContextPanel preview tab with iframe, reload, and open-in-browser.
- Inline html code-block preview in MarkdownRenderer.
- Terminal auto-detects loopback URLs and offers to open them.
- i18n keys across en, es, pt-BR, uk, zh-CN.
* perf(preview): cache proxy targets across PreviewPane remounts
Module-scoped Map keyed by upstream URL so tab switches and component
remounts within the same page session reuse the existing proxy
registration instead of POSTing a fresh target each time.
In-memory only by design: the server holds the target map in memory
and the auth cookie is HttpOnly + scoped to the proxy id, so a stale
persisted entry would 404 after a server restart. Entries are evicted
on registration error and on a 30s safety margin before TTL expiry.
* feat(preview): surface dev-server-down state with retry overlay
Iframes don't expose HTTP status to the parent, so when the proxy
returns a 502 (upstream dev server is offline) the iframe just renders
the raw JSON error body. Probe the proxy URL out-of-band with HEAD
(falling back to GET on 404/405) and replace the iframe with a
friendly 'Dev server is not responding' overlay + retry button when
the upstream is unreachable.
Re-probes on reload, on URL change, and on proxy re-registration.
* feat(preview): strip frame-busting response headers
Many dev servers (Next.js, others) send X-Frame-Options: SAMEORIGIN
and/or a CSP with frame-ancestors that block embedding inside the
OpenChamber iframe. The proxy is same-origin and already
authenticated per-target, so embedding is otherwise safe.
- Drop X-Frame-Options outright on proxied responses.
- Surgically remove only the frame-ancestors directive from
Content-Security-Policy and Content-Security-Policy-Report-Only,
preserving every other directive. Drops the header entirely if no
directives remain.
- Verified end-to-end: upstream sending both headers comes through
with X-Frame-Options removed, CSP retaining default-src/script-src
but no frame-ancestors, and unrelated headers untouched.
* docs(preview): design for remote-host relay agent
Design-only doc for the next phase of the embedded preview feature:
when OpenChamber runs remotely (cloud/shared/tunnel) and the user's
dev server runs on their local machine. Covers architecture (local
agent + outbound control WebSocket + server dispatch), pairing flow,
wire protocol, security model, failure modes, open questions, and
implementation milestones. No code changes.
* feat(preview): auto-open preview pane for loopback URLs in chat
Detect http(s) loopback URLs in incoming assistant messages and open the
preview pane automatically, deduped per (session, url) pair so re-renders
or repeated mentions do not steal focus. Add an inline Preview button
next to loopback links in chat markdown as a manual fallback when the
auto-open was dismissed or the URL appeared in an older message.
- url.ts: isLoopbackHttpUrl / extractLoopbackUrls helpers
- ChatContainer: module-level dedupe Set + effect on active session tail
- MarkdownRendererImpl: optional onPreviewLoopback in main renderer only
(SimpleMarkdownRenderer for tool diffs is intentionally untouched)
- Reuses existing terminalView.preview.open i18n keys
* feat: preview enhancements, dev shutdown, and reliability fixes
Add preview start/stop UI in ContextPanel/Header, improve URL detection (Python HTTP server logs, trailing punctuation, IPv6 loopback), fix proxy path filtering to avoid disrupting non-preview WebSockets. Add dev-only /api/system/dev-shutdown endpoint and Header button to terminate local dev processes and orphaned preview servers. Improve terminal cleanup with process group killing, event pipeline reconnect backoff. Update file read APIs with optional flag and cache control. Add /api/system/free-port endpoint, detectDevServer.ts utility, and preview/shutdown i18n strings for 5 languages.
* fix: harden preview support
* fix: keep terminal toolbar interactive
* fix: keep expanded terminal below header
* fix: keep preview iframe under proxy path
* fix: respect project action preview urls
* fix: rewrite preview asset urls
* feat: capture preview console logs
* feat: annotate preview elements
* feat: attach preview annotation screenshots
* fix: improve proxied preview hmr
* feat: refine preview action UX
* fix: address preview review feedback
* fix: show auto-discover preview wait state
---------
Co-authored-by: William Biggers <will@Williams-MacBook-Pro.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* fix: reconnect SSE immediately on OS wake-from-sleep
When the desktop app resumes from OS sleep, TCP connections are dead
but timers were paused during sleep so the heartbeat watchdog doesn't
fire until ~30s after wake.
Add Electron powerMonitor.resume → renderer notification → event-pipeline
immediate abort, cutting reconnection delay from ~30s to ~0ms.
Changes:
- electron/main.mjs: import powerMonitor, emit openchamber:system-resume
to all renderer windows on OS resume
- ui/sync/event-pipeline.ts: listen for openchamber:system-resume, set
attemptAbortReason and abort the active SSE/WS attempt to trigger
immediate reconnection with retryDelayMs=0 and lastEventId preservation
* fix: reconnect SSE immediately on OS wake-from-sleep
When the desktop app resumes from OS sleep, TCP connections are dead
but timers were paused during sleep so the heartbeat watchdog doesn't
fire until ~30s after wake.
Add Electron powerMonitor.resume → renderer notification → event-pipeline
immediate abort, cutting reconnection delay from ~30s to ~0ms.
Changes:
- electron/main.mjs: import powerMonitor, emit openchamber:system-resume
to all renderer windows on OS resume
- ui/sync/event-pipeline.ts: listen for openchamber:system-resume via
globalThis.window, set attemptAbortReason and abort the active SSE/WS
attempt to trigger immediate reconnection with retryDelayMs=0 and
lastEventId preservation
- Test: event-pipeline-resume.test.js verifies abort → reconnect flow
* fix: preserve lastEventId in SSE path and add proxy heartbeat
- Extract event.id from SSE stream events in event-pipeline.ts so that
reconnects carry the correct Last-Event-ID header for gapless replay.
- Emit :heartbeat comment every 20s in the direct SSE proxy to keep
the UI heartbeat watchdog from aborting idle connections.
* fix: handle SSE metadata through SDK callback
* Guard SSE proxy heartbeats
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* fix: update sidebar store after worktree creation
* fix: update sidebar store after worktree removal
* fix: update sidebar store with correct key in removeProjectWorktree
- Fixed key mismatch in sidebar store updates for worktree removal
- Removed worktrees now properly disappear from sidebar
- Aligned remove path with create path key resolution
* fix: resolve sidebar stale state after folder, worktree, project, and session mutations
* fix: now builds
* Fix worktree sidebar store key
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* feat: improve agent and quick model picker behavior
* fix: keep the active model highlighted in the quick picker
* feat: streamline mobile model selection
Open the full mobile model picker directly and remove the intermediate controls drawer so mobile model changes follow the same core selection flow as desktop.
Add inline thinking-mode chips that show each model's remembered or default variant, apply model and variant together on tap, and fall back to a dedicated overflow panel for larger variant sets. Also keep favorites and recents searchable on mobile and fix clearing remembered default variants so the picker stays consistent across sessions.
* fix: polish desktop model picker interactions
Stabilize desktop model picker behavior by keeping keyboard and hover selection in sync, preventing hover-driven closes, and making the footer hints visually stable.
Also make quick-picker thinking mode changes apply consistently when switching plan/build or agent mode inside the picker, clamp left/right variant cycling at the ends, and keep thinking feedback visible even when the selected variant cannot move further.
* fix: condense mobile model picker rows
Tighten the mobile model picker to use a more compact, consistent row layout across favorites, recents, and provider sections while keeping context length and capability icons easy to scan.
Also preserve inline thinking-mode selection, improve metadata spacing, and keep the mobile controls readable without reintroducing the heavier drawer-based flow.
* fix: include all primary-like agents in picker cycling
Keep desktop Tab cycling and mobile tap cycling aligned with the rest of the selection UI by including agents marked as all or left unset, not just strict primary agents.
* fix: preserve remembered agent variants in picker flows
* Fix model picker variant restore
* Polish favorite model drag handle
* Fix Korean model picker locale
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
Show language selection in VS Code settings
Display context usage in the VS Code chat header
Keep VS Code header usage controls visible in expanded layouts
* fix: exclude file content from reverted prompt text
Revert and fork now restore only the user's original prompt, not server-injected file content
Uses existing isSyntheticPart helper for type-safe filtering
* fix: keep scrollbar visible when hovering over thumb
* fix: prevent ESC abort from triggering when terminal is focused
* fix: pass directory to permission/question reply calls so approvals actually resolve
* fix: default model selection not responding after Base UI migration
* fix: prevent modal content from shifting and clipping footer buttons
* fix: improve session switching performance and add sub-agent export with prompt collapse
Defer viewport anchor saving to eliminate ~800ms UI freeze when switching sessions
Add export dialog to include sub-agent tasks recursively in markdown export
Add collapse chevron button for expanded user prompts in sticky header
* fix: resolve sidebar scroll and TDZ crash in session sidebar
* perf: reduce CPU overhead and re-renders across chat, layout, and settings
* fix: position collapse button at top of message and prevent ESC abort in terminal
* fix: position collapse button at top and add padding only when expanded
* refactor: extract shared PATH utilities and mobile keyboard hook
* refactor: import shared path-utils in electron, use module-level style constants
- Electron now imports pathLooksUserConfigured/mergePathValues from
shared path-utils.js instead of inline duplication
- ToolPart collapsedCustomStyle moved from useMemo([]) to module const
* fix: resolve remaining merge conflicts and type errors
- Remove duplicate variable declarations in SessionNodeItem
- Remove orphaned export callback body from conflict resolution
- Fix HelpDialog description -> descriptionKey (i18n rename)
* fix: resolve type-check and lint errors in session-actions.test.ts
- Added missing bun:test type declarations (beforeEach, mock, mock.module)
- Removed unused State import
- Replaced 'as any' casts with proper OpencodeClient and ChildStoreManager types
- Added eslint-disable for unused _ parameter in mock function
* fix PR 1028 export and PATH edge cases
* fix startup retry exhaustion state
* remove opencode package lock change
* fix sub-session rename cancellation
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>