Commit Graph
3741 Commits
Author SHA1 Message Date
Brennan BensonandOrca 748bced2e6 Fix Resource Manager going empty when a runtime server is selected or stale (#7275)
Co-authored-by: Orca <help@stably.ai>
2026-07-03 17:52:59 -07:00
Brennan BensonandOrca 4951168d4a Unify tab agent identity with the agent-status pipeline (#7059)
Co-authored-by: Orca <help@stably.ai>
2026-07-03 17:39:59 -07:00
727c7ae21b fix(runtime): render remote-server binary files (PDF/images) in the editor (#6606)
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-07-03 17:37:06 -07:00
f7a4100bc7 Preserve slash branch names from branch composer (#6748)
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Neil <charlie-eng@stably.ai>
2026-07-03 17:33:22 -07:00
mehmet turac ca36072295 Handle GitHub attachment image load failures (#6759) 2026-07-03 17:32:08 -07:00
8796261eed feat: allow custom worktree branch names (#6454)
* feat: allow custom worktree branch names

* refactor(ui): render custom branch name field unconditionally under advanced container

* fix(composer): hide manual branch field when a work-item source drives the branch

A tracked PR/issue/MR/Linear source derives the branch itself, and a linked
GitHub PR re-resolves the branch name at submit — so an override typed in the
Advanced branch field was silently ignored. Only render the field for the
typed-name and base-branch flows, where the manual override is honored.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 17:21:50 -07:00
2789a67604 feat(browser): add Copy to context menu when text is selected (#7159)
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-07-03 16:52:08 -07:00
473c4e8680 fix(markdown): restore TOC navigation for h4 and h5 (#7063)
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Neil <charlie-eng@stably.ai>
2026-07-03 16:51:26 -07:00
2c03133975 fix(i18n): apply persisted UI language at startup instead of OS locale (#7193)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: rubin <RubinCarter@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-07-03 16:44:56 -07:00
Jinwoo Hong 2f3c7e8660 Fix Windows slow startup and multi-minute UI freezes (#7225) (#7266) 2026-07-03 19:40:45 -04:00
a10e1d7584 fix(gitlab): recognize self-hosted GitLab on non-default ports over SSH connections; stop one project failing the whole issues panel (#5400)
* fix(gitlab): port-aware self-hosted host recognition

Use the URL host (including a non-default web/API port) as the GitLab
host identity instead of the port-less hostname, and match known hosts
port-aware:

- A known-host entry without a port matches any port of the same
  hostname (preserves legacy bare-host and gitlab.com recognition).
- A known-host entry WITH a port matches only that exact host:port, so
  two services sharing a hostname on different ports (e.g. a GitLab and
  a Gitea) are no longer conflated.
- For ssh/git remotes the port is a transport port (e.g. ssh :2222) and
  is dropped; for http(s) remotes the port is the endpoint and kept.
- Also capture an optional :port in parseGlabAuthStatusHosts so a
  self-hosted GitLab on a non-default port is discovered correctly.

* fix(gitlab): per-connection known-hosts cache + port-aware auth-status parsing

getGlabKnownHosts() was connection-blind and cached process-globally,
and on any failure it cached [gitlab.com] forever — so a repo on an SSH
connection never discovered its self-hosted host once a probe failed
before the tunnel was ready.

- getGlabKnownHosts(connectionId?) now caches per connection so a
  connected repo's authenticated hosts don't leak into the local
  context (or vice versa).
- The failure fallback (canonical default) is no longer cached, so a
  later probe can re-discover the real host once auth/tunnel is ready.
- parseGlabAuthStatusHosts captures an optional :port on both the
  'Logged in to <host>' and header-style lines, keeping two services on
  the same hostname distinct by port.

* fix(gitlab): isolate unresolvable projects instead of cwd-fallback that hits exit 128

listIssues/getIssue fell back to an unscoped 'glab issue list' / 'glab
issue view' that infers the project from cwd. For a repo on an SSH
connection cwd is not the repo dir, so glab runs git resolution in a
non-repo dir and fails with 'git: exit status 128'. In an 'All projects'
aggregate one such failure could sink the whole issues panel.

When a projectRef cannot be resolved, return a structured, isolated
per-project result (listIssues: { items: [], error: not_found };
getIssue: null) and spawn no glab subprocess. Behavior is unchanged when
a projectRef IS resolved (the scoped '-R' / 'api projects/...' path).

* fix(gitlab): recognize modern /-/work_items/<iid> issue URLs

Modern GitLab emits issue URLs as /-/work_items/<iid> in addition to the
legacy /-/issues/<iid>. The URL classifiers only matched /-/issues/, so
work-item-form issue links went unrecognized.

Extend the gitlab-links parsers (parseGitLabIssueOrMRNumber /
parseGitLabIssueOrMRLink, which also backs isWorkItemLookupText) and
isGitLabIssueUrl to accept /-/work_items/<iid>, mapping it to an issue
work item with the same project-path + iid extraction.

* fix(gitlab): thread connectionId into getGlabKnownHosts call sites

Follow the existing connectionId-threading pattern: pass the repo's
connectionId into every getGlabKnownHosts() call (client.ts,
work-item-details.ts, orca-runtime.ts) so the per-connection known-hosts
cache is keyed correctly and self-hosted hosts are discovered against
the right glab context.

* docs(gitlab): use generic example hosts in comments

* fix(gitlab): pass self-hosted host:port via GITLAB_HOST (glab --hostname rejects ports)

* polish: satisfy oxlint curly + oxfmt on merged gitlab port-recognition code

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Ptah-CT <auctor@xinfty.space>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 16:24:01 -07:00
Brennan BensonandOrca 6213f43016 Wake hibernated agent panes on reveal instead of leaving an inert ghost (#7145)
Co-authored-by: Orca <help@stably.ai>
2026-07-03 14:27:36 -07:00
332490e919 Fix stale GitHub repo avatars after owner changes (#6507)
* Fix stale GitHub repo avatars after owner changes

* Simplify avatar-refresh effect deps (repo covers its fields)

Co-authored-by: Orca <help@stably.ai>

* Preserve known fork identity when live upstream lookup is inconclusive

Co-authored-by: Orca <help@stably.ai>

* Remove dead avatar-icon wrapper; drop no-op update fallbacks

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 14:20:30 -07:00
3614cbc40c Add default browser action for terminal links (#6129)
* Add terminal link browser context action

* Polish terminal link default-browser action

- Route Open in Default Browser through the shared openHttpLink funnel
  (forceSystemBrowser) so loopback dev-server links get the same
  worktree-label rewrite as shift+click's system-browser path.
- Translate the new menu label in es/ja/ko/zh (was en-only).
- Extract TerminalQuickCommandMenuItem to keep TerminalContextMenu under
  the max-lines lint after adding the link item.
- Hit-test the link only when the menu actually opens (skip on Windows
  right-click copy/paste path).
- Add scheme-safety unit tests (file:/javascript:/smb:/vscode: rejected)
  and a menu-item render/callback test; fix TerminalContextMenu.test
  props for the new required fields.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 14:09:52 -07:00
42de074152 fix(browser): keep isolated profile storage on its partition (#6958)
* fix(browser): keep isolated profile storage on its partition

* fix(browser): keep cloned isolated tabs on their resolved partition

* fix(browser): thread resolved partition through UI profile switches

Keep the isolated-storage invariant universal: UI-initiated profile
switch/create now persist the resolved partition alongside the profile
id, matching the runtime path, so a tab stays on its partition even if
the renderer profile mirror is later stale (issue #6923).

Co-authored-by: Orca <help@stably.ai>

* fix(browser): persist resolved partition through session restore

Add sessionPartition to the workspace-session zod schema so the resolved
partition survives persist->load; without it zod strips the field and a
restored isolated tab whose profile mirror is stale at startup falls back
to the shared default partition, reopening the storage leak (#6923).

Fold the webview teardown branches (parent drift or partition mismatch)
into one and re-resolve the viewport container once, matching the
pre-refactor null-guard behavior.

Split the sleeping-agent parse tests into their own file to keep both
under the 800-line cap.

Co-authored-by: Orca <help@stably.ai>

* fix(browser): keep CLI-created tabs inheriting the default profile

browserTabCreate without an explicit profile was sending sessionProfileId:
null, which the renderer store treats as 'no inheritance' (its guard is
!== undefined). That forced CLI-created tabs onto the shared default
partition even when the user had configured a default browser session
profile, silently changing behavior a #6923 fix should not touch.

Leave sessionProfileId/sessionPartition undefined when no profile is named
so the renderer applies default-profile inheritance; thread the resolved
partition only when a profile is explicitly chosen.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 13:58:40 -07:00
Brennan BensonandOrca 133ebcee5d Update stale mobile Android download links to 0.0.21 (#7262)
Co-authored-by: Orca <help@stably.ai>
2026-07-03 13:42:02 -07:00
97c30c5ccc fix: drop stale repos fetches so deleted projects don't reappear (#7020) (#7024)
* fix: drop stale repos fetches so deleted projects don't reappear

Deleting a project group with "Remove contained projects" could leave the
removed projects as stale, unusable sidebar rows until an app restart (#7020).
`repos:changed` fires once per removal and the renderer starts an unsequenced
repos fetch per event; an earlier fetch that read pre-removal state could
resolve last and overwrite the newer result, reintroducing the deleted repos.

Guard fetchRepos and fetchReposForAllHosts with a monotonic token so a fetch
drops its own result once a newer repos fetch has superseded it — only the
latest fetch, which reads the final persisted state, applies. Add a regression
test that a stale fetch resolving after a newer one can't resurrect a removed
repo.

* test: isolate stale-fetch race in a dedicated file; scope guard to fetchRepos

Move the #7020 regression test out of repos.test.ts into a focused
repos-stale-fetch.test.ts and add a reject-path case (a superseding fetch
that later rejects must still block the older stale fetch). Scope the
monotonic guard to fetchRepos only: the original shared-counter guard on
fetchReposForAllHosts let an unrelated fetchRepos bump the counter and
abort an in-flight all-host load, dropping every host's repos.

Co-authored-by: Orca <help@stably.ai>

* chore: restore origin/main cdp-ws-proxy.test.ts (drop merge artifact)

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 13:40:17 -07:00
2c0be62df6 Fix empty nested import folder fallback (#6719)
* fix: allow empty nested import as folder

* Track open-as-folder recovery action and localize its strings

Completes the empty-nested-import fallback: adds the open_as_folder action to the existing add_repo_nested_import_action funnel (integer counts only, no paths) so adoption of the recovery path is measurable, and lands the five-locale catalog entries the new UI strings require.

Co-authored-by: Orca <help@stably.ai>

* Pin runtime-kind mock return to the literal union in nested import flow test

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 13:38:20 -07:00
Parker Rex 02dfb4b334 Fix Discord icon and CreateWorktree label spacing (#6505)
* Fix landing create worktree label

* Update Discord help menu icon
2026-07-03 13:12:14 -07:00
2153ef9456 Accept any hostname (and optional :port) in manual network address entry (#7223)
* Allow arbitrary hostnames in manual network address entry

parseManualNetworkAddress only accepted an IPv4 address or a Tailscale
MagicDNS (*.ts.net) hostname, so users behind a dynamic residential IP
who rely on a DDNS domain or self-hosted relay had no way to enter it
in the desktop UI short of bypassing validation via DevTools/IPC.

The main process already resolves any host: resolvePairingEndpoint
and parsePairingAddressOverride in src/main/runtime/runtime-rpc.ts
accept an arbitrary hostname and an optional host:port. This change
brings the renderer-side validation in line with what pairing already
supports: any RFC 1123 hostname (a superset that still covers
*.ts.net), optionally suffixed with :port (1-65535). IPv4 validation
is unchanged, including still rejecting malformed dotted-numeric input
instead of silently treating it as an all-digit hostname.

Updates the custom-address dialog copy in NetworkInterfacePicker.tsx
to describe the wider grammar.

* Polish manual-address takeover: fix bare-numeric guard, sync 5 locales, lint

- Require a dot in the IPv4-typo guard so a bare numeric label (`123`)
  validates as a legal RFC 1123 hostname, matching the code's own comment
  and the main-process resolver; add coverage.
- Update en.json + es/ja/ko/zh placeholder/hint to the broadened grammar
  (translate() reads en.json before the TSX fallback, so the copy change
  was previously inert; the other locales described the old ts.net-only rule).
- Replace indexOf(...)!==-1 with includes() to satisfy oxlint.

Co-authored-by: Orca <help@stably.ai>

* Keep validator a strict subset of the backend resolver

Review surfaced two ways the renderer could accept an address the main
process handles differently:
- All-numeric hosts (bare `123` and dotted `256.0.0.1`) are now rejected.
  The WHATWG URL host parser downstream reinterprets a numeric host as IPv4
  (`123` -> `0.0.0.123`), so accepting one would validate an address the
  pairing resolver silently dials as a different host.
- Ports with leading zeros are rejected. `^[0-9]+$` let an arbitrarily long
  zero-padded string past the range check and inflate the returned address
  beyond the hostname length cap that the old whole-string check enforced.

Co-authored-by: Orca <help@stably.ai>

* Reject any numeric final label, not just fully-numeric hosts

WHATWG URL host parsing treats a host whose last label is numeric
(`foo.123`, `foo.0x1`) as an IPv4 signal, so the pairing resolver would
fail to parse it and silently dial a fallback host. Widen the ambiguous-IP
guard to a single last-label check that subsumes the earlier all-numeric
case, keeping the renderer a strict subset of what the backend resolves
correctly. Normal hostnames whose last label merely contains digits
(`host2.example.com`) are unaffected.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 12:52:55 -07:00
492306f223 fix(sidebar): keep independent same-host checkouts of one repo as separate projects (#7237)
* fix(sidebar): keep independent same-host checkouts of one repo as separate projects

Re-regression of #5374: PR #6320 removed the per-host-surface separation
added by #5375, so independent local clones of the same repo (shared origin
=> same github:owner/repo identity) collapse into one sidebar project again.

Restore the surface-key separation in worktree-list-groups.ts, scoped by
ProjectHostSetup.setupMethod: 'provisioned' (ephemeral-VM runtime copies)
still nest under the project header (preserving #6320), while real user
checkouts (legacy-repo / imported-existing-folder / cloned) each get their
own header when duplicated on one host surface.

Revert the two tests #6320 inverted and add coverage for a provisioned copy
nesting alongside a same-host user checkout.

* test(cmd-j): update palette grouping tests for same-host checkout split

The Cmd+J palette shares getProjectGroupingForRepo (via
getProjectHeaderRevealTarget) with the sidebar, so restoring same-host
checkout separation also splits independent `cloned` checkouts into
per-setup palette entries. Update the multi-setup test to assert the split
and add coverage that a `provisioned` runtime copy still nests under one
project key.

* test(sidebar): cover split + provisioned-nest on one host together

Guard the intersection of #5374 and #6320: when a project has two real
same-host user checkouts AND a provisioned copy, the real checkouts each get
their own per-setup header while the provisioned copy stays under the plain
project key (never a path-scoped ::setup: header). Locks the two features'
combined behavior so a future change can't silently regress either half.

* refactor(sidebar): reuse shared Windows-path helper, single-pass surface count

Take-over polish of #7237:
- Use shared isWindowsAbsolutePathLike instead of an inline drive-letter
  regex, deduplicating the pattern and also bucketing UNC \\server\share
  paths as a Windows surface instead of falling through to default.
- Collapse buildProjectGroupingIndex's two passes over projectHostSetups into
  one, computing each surface key once and flagging ambiguous projects from
  the aggregated counts.
- Tighten why-comments to AGENTS.md's two-line rule.

No behavior change to the fix; all 123 sidebar/palette grouping tests pass.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 12:44:34 -07:00
37c8ff4935 fix: block resuming host-stored AI Vault sessions into SSH workspaces (#7130)
* fix: block resuming host-stored AI Vault sessions into SSH workspaces

AI Vault scans session history from this machine's disk only (host home
dirs plus local WSL homes), but since #6685 resume actions accepted any
SSH workspace as a target. Resuming a host-stored session there queues
cd '<local cwd>' && <agent> --resume <id> into a remote shell where
neither the cwd nor the session file exists, so it always fails.

Gate resume targets on where the session file lives: local targets stay
unrestricted, SSH targets now require a WSL-stored session (the
SSH-to-local-WSL setup #6685 was built for). Applies to the sidebar
resume buttons, direct resume, and pane drag-and-drop, with a toast
explaining the host mismatch. Catalog sync also picked up two
pre-existing drifts (missing failedUnnestWorkspace fallback, two
unreferenced SSH-timeout keys).

* i18n: translate host-session SSH-resume block message (es/ja/ko/zh)

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 12:44:31 -07:00
1fd9547501 fix(agent-completion): stop PermissionRequest hooks from firing false completion notifications (#6316)
* test(agent-completion): cover done after waiting pause

Co-authored-by: Orca <help@stably.ai>

* test(agent-completion): assert attention fires alongside done-after-waiting

Wire dispatchAttention into the done-after-waiting regression test so it
verifies both halves of the #5698 fix: the mid-turn permission pause
surfaces as attention exactly once, and the later genuine 'done' still
completes exactly once. Matches sibling tests that assert on both paths.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: brennanb2025 <brennankbenson@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-07-03 12:44:28 -07:00
Neil c8e571f40c fix(sidebar): prevent hovercard text selection drags (#7228) 2026-07-03 03:07:17 -07:00
Brennan Benson 653f349b5f Fix terminal theme selection persistence (#7227)
Fixes #7166.
2026-07-03 02:55:27 -07:00
Neil ce680071a2 Place terminal URL tooltip at pane corner (#7221) 2026-07-03 02:15:24 -07:00
Neil a84f737cf7 fix(terminals): restore pane detach tab placement (#7215)
* fix(tabs): restore deferred tab activation so dragging a terminal tab doesn't switch panes mid-gesture

PR #5927 shipped terminal-pane drag (reorder tabs, move a tab into another
pane, edge-split into a new column). It deliberately DEFERRED tab activation
to pointer-up and suppressed it during a drag, so pressing a tab to drag it
never switched the active tab or stole terminal focus mid-gesture.

PR #6395 (d32d62a395) deleted tab-strip-pointer-activation.ts and made
SortableTab/EditorFileTab/BrowserTab activate eagerly on pointerdown — fixing
click-to-switch-after-reorder but regressing the drag: grabbing a tab now
flips the active tab + focused pane and yanks xterm keyboard focus before the
drag is even recognized (aggravated by PR #6210 raising the dnd-kit threshold
5px->12px). The move itself still lands, but the gesture feels broken.

Restore the deferred-activation hook, but gate it on measured pointer
DISPLACEMENT instead of the drag-active context ref the old hook used — that
ref clears asynchronously relative to the drop's pointerup, which is exactly
what made #6395's click-after-reorder misfire. Displacement mirrors dnd-kit's
own activation threshold: a release within TAB_DRAG_ACTIVATION_DISTANCE_PX is
a click (activate); crossing it is a drag (suppress). Because each press
measures its own gesture, a click after a reorder always activates.

- Recreate src/renderer/src/components/tab-bar/tab-strip-pointer-activation.ts
  (displacement-gated) + unit tests, incl. an explicit #6395 regression case.
- Rewire onPointerDown in SortableTab, EditorFileTab, BrowserTab to use it.
- BrowserTab.test.tsx shallow-renders via mocked React hooks; add useRef/
  useCallback stubs so the new hook works under that harness.

Verified end-to-end in a dev build via CDP: pressing a tab no longer
activates/steals focus on pointerdown; a click still activates on release;
dragging a tab into another pane still moves it; clicking tabs after a drag
still switches (the #6395 guard in tests/e2e/tabs.spec.ts stays green because
it uses zero-displacement clicks). 233 unit tests pass; oxlint + renderer
typecheck clean.

* fix: restore pane detach tab placement
2026-07-03 01:45:15 -07:00
Neil 7c9059c4d4 Restore desktop terminal URL modifier clicks
Restore desktop terminal URL and OSC link activation to require Cmd/Ctrl while preserving mobile tap routing.
2026-07-03 01:32:22 -07:00
Dvitash 0ff2c09002 Fix typed OMP remote title flicker (#6954) 2026-07-03 00:26:37 -07:00
Jinwoo HongandOrca 10e8f86899 Coalesce PTY input write bursts (#7205)
Co-authored-by: Orca <help@stably.ai>
2026-07-02 23:43:35 -07:00
Mark Xian 2848c5daf4 Fix rename inputs committing on CJK IME Enter confirmation (#6980)
Fixes #6980.
2026-07-02 23:37:12 -07:00
Neil 0fd2d67c69 Allow manual sorting of Project Groups (#7191) 2026-07-02 22:23:18 -07:00
Neil b523c5311f fix: show Claude Fable usage in account switcher
Show Fable usage in inactive Claude account switcher previews by supplementing OAuth usage with scoped Claude CLI /usage data, with macOS scoped Keychain safety checks and compact preview layout coverage.

Validation:
- pnpm exec vitest run --config config/vitest.config.ts src/main/rate-limits/claude-fetcher.test.ts src/main/rate-limits/service.test.ts src/renderer/src/components/status-bar/inline-usage-bars.test.tsx src/renderer/src/components/status-bar/tooltip.test.ts
- pnpm run typecheck
- pnpm exec oxlint src/main/rate-limits/claude-fetcher.ts src/main/rate-limits/claude-fetcher.test.ts src/main/rate-limits/service.ts src/main/rate-limits/service.test.ts src/renderer/src/components/status-bar/StatusBar.tsx src/renderer/src/components/status-bar/inline-usage-bars.test.tsx
- git diff --check HEAD~1..HEAD
2026-07-02 22:18:12 -07:00
Neil 185b768609 Add rich Markdown spellcheck setting (#7103) 2026-07-02 21:59:41 -07:00
NeilandOrca 4c4cbf2d76 Fix Vim alternate-screen terminal redraw recovery (#7142)
* Fix Vim alternate-screen terminal redraw recovery

* Decide alternate-screen atlas recovery from parsed buffer state

Replace the raw-chunk scan for alternate-screen enter sequences with the
xterm parser's own verdict: in-place rewrite chunks check
buffer.active.type (plus a parse-time buffer-switch count) inside the
write callback, where the buffer is authoritative. Covers enter/exit
sequences split across PTY chunk boundaries and full enter-exit cycles
coalesced into one write, and deletes the hand-rolled CSI parser.

Adds regression tests from a real captured vim session (including a
CSI sequence split at a 1024-byte PTY read boundary) and a contract
pin against the real @xterm/headless parser.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-02 21:59:17 -07:00
Jinwoo HongandOrca 827d2bb6aa Refine TUI wheel scroll feel (#7179)
Co-authored-by: Orca <help@stably.ai>
2026-07-02 21:27:16 -07:00
Brennan BensonandOrca 3e4ace0562 Fix Codex pane output tearing after app occlusion (#7173)
Co-authored-by: Orca <help@stably.ai>
2026-07-02 21:00:03 -07:00
Brennan BensonandNeil 0ea354d454 Keep terminals responsive under agent output load (#7139)
Co-authored-by: Neil <neil@stably.ai>
2026-07-02 20:39:49 -07:00
Brennan BensonandOrca c06507ee30 Open terminal file links on mobile (#7134)
Co-authored-by: Orca <help@stably.ai>
2026-07-02 18:17:47 -07:00
Brennan BensonandOrca 11ca4955ae Run the repo's Custom GitHub Issue Command when starting a workspace from an issue (#6827)
Co-authored-by: Orca <help@stably.ai>
2026-07-02 18:09:14 -07:00
Jinwoo Hong 78d3a37b90 Revert WebGL context retention (#7169) 2026-07-02 17:55:37 -07:00
gatsby74 5da41d4ed6 [codex] Add file explorer open in terminal
Add an Open in Terminal action for file-explorer directories and preserve terminal startup cwd through restore/session paths.
2026-07-02 16:47:38 -07:00
Jinwoo Hong 2ce9314acb Improve Windows terminal performance: retain WebGL contexts, warm first ConPTY (#7085) 2026-07-02 18:41:23 -04:00
Brennan Benson e44daf37a3 Match main terminal mirror character widths to the renderer (#7148) 2026-07-02 14:50:15 -07:00
Brennan BensonandOrca 186f6c9e3d Fix stale terminal frames on worktree return: clear alt screen on snapshot restore + WebGL reveal hardening (#7133)
Co-authored-by: Orca <help@stably.ai>
2026-07-02 12:11:27 -07:00
aa1744e30e Fix cursor agent ime anchor (#7061)
* fix: correct IME cursor after terminal reattach

* fix: stabilize Cursor Agent IME anchor

* Fix Cursor Agent reattach IME detection

* Harden live-agent reattach detection and always restore cursor visibility

- POST_REPLAY_LIVE_AGENT_REATTACH_RESET now includes ?25h so a false-positive
  agent signal (or dead TUI leftovers) can never leave a shell with a
  permanently hidden cursor; only focus reporting (?1004h) is preserved.
- Replace the broad getAgentLabel token match (fires on titles like
  'ssh devin@host') with an exact cursor-agent native-title check.
- Anchor the replay-payload screen signal on the LAST 'Cursor Agent' header so
  scrollback from an earlier finished run does not classify the current screen.
- Make the payload-signal flag replay-shape-aware: incremental
  (clearBeforeReplay:false) frames can only add evidence; full-screen replays
  remain authoritative.

Co-authored-by: Orca <help@stably.ai>

* Fix anchor scan direction and drop non-decaying launch metadata from liveness

- Scan the visible screen bottom-up in resolveCursorAgentImeAnchor so a
  transcript line containing an arrow (e.g. a rename diff) cannot hijack the
  IME anchor away from the input box.
- Remove getAuthoritativePaneAgent() from hasLiveAgentReattachSignal:
  tab.launchAgent never decays after the agent exits, so it would preserve
  ?1004h and inject focus-in into the shell left behind by a dead agent.
- Clear a stale replay-payload agent signal on an authoritative empty clearing
  frame.

Co-authored-by: Orca <help@stably.ai>

* Veto scrollback-only Cursor Agent signal against the parsed viewport

The replay-byte screen signal also matches a dead run's screen sitting in
scrollback (daemon snapshots serialize full scrollback; the relay buffer is
never cleared). After xterm parses the replay, confirm a payload-only signal
against the visible screen via resolveCursorAgentImeAnchor; when the viewport
shows a plain shell, drop focus reporting (?1004l) and skip the focus-in so a
bare shell never inherits stale focus modes or receives stray focus bytes.

Co-authored-by: Orca <help@stably.ai>

* Make the reattach viewport veto shape-based and latest-frame-only

- parsedViewportShowsCursorAgentScreen now checks the screen shape (header +
  input row) via viewportShowsCursorAgentScreen instead of requiring the
  parked-cursor precondition, so a live agent whose cursor sits at the caret
  is not downgraded.
- Add a replay-signal generation counter so a stale post-parse callback stands
  down when a newer replay frame has updated the signal.

Co-authored-by: Orca <help@stably.ai>

* Require the parked cursor in the reattach viewport confirmation

A dead cursor-agent screen can still be painted in the viewport with the
shell prompt below it, which passes a shape-only check and would preserve
?1004h and inject a focus-in into the shell. Confirm a payload-only signal
with resolveCursorAgentImeAnchor (shape + parked cursor): a live agent that
needs the focus-in is by definition parked, and an unparked live agent only
falls back to the pre-fix reattach behavior.

Co-authored-by: Orca <help@stably.ai>

* Complete window stub in pty-connection tests for command-finished event

Main's onCommandFinished now dispatches a window CustomEvent synchronously,
but the node-env connectPanePty window stub omits dispatchEvent. These tests
only passed when another test file in the same vitest worker happened to leave
a window with dispatchEvent; this branch's added test files shift sharding and
expose the gap. Add dispatchEvent/addEventListener/removeEventListener to the
stub so the command-finished path is covered deterministically.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-02 02:41:07 -07:00
Neil 443d6efb50 Tune worktree card drag targets (#7095) 2026-07-02 02:34:10 -07:00
WONGILandJinjing 4f0a141951 Fix mobile terminal Korean IME composition on Android (#7011)
* Fix Korean IME composition by deferring live terminal preedit

The mobile terminal capture field previously sent and cleared every TextInput change, which can break Hangul composition on Android keyboards. Introduce a small commit model and extracted live-input hook so composed text is flushed deliberately while ASCII remains immediate.

Constraint: React Native TextInput has no portable composition event for this path; the fix uses a bounded commit delay for likely IME text.

Rejected: Native-module IME integration | unnecessary for the confirmed JS dispatch/clear failure and higher maintenance risk.

Confidence: high

Scope-risk: moderate

Directive: Keep terminal.send payload shape and buffered command input unchanged; do not claim physical Samsung Keyboard QA without device evidence.

Tested: cd mobile && pnpm exec vitest run src/terminal/terminal-live-text-commit.test.ts src/terminal/terminal-live-input.test.ts src/terminal/terminal-text-input-normalization.test.ts src/terminal/terminal-keyboard-type.test.ts --reporter=verbose

Tested: cd mobile && pnpm exec tsc --noEmit

Tested: cd mobile && pnpm exec oxlint src/terminal/terminal-live-text-commit.ts src/terminal/terminal-live-text-commit.test.ts src/terminal/use-terminal-live-input-commit.ts app/h/[hostId]/session/[worktreeId].tsx

Not-tested: Physical Galaxy Fold7/Samsung Keyboard and Android emulator/Gboard QA were unavailable; device probes recorded no attached Android device.

* Preserve pending Korean IME text before mobile accessory controls

Accessory keys share the same pending live-input commit gate as TextInput keypress and submit paths, so control bytes cannot race ahead of composed Hangul.

Constraint: React Native mobile input does not expose portable composition events for Samsung/Gboard IME paths.

Rejected: Let accessory buttons keep sending directly | Direct sends can drop pending Hangul before Tab/Esc/Enter/Backspace reaches the PTY.

Confidence: high

Scope-risk: narrow

Directive: Keep all terminal control-byte paths behind the pending live-input flush/local-edit decision before sending to the PTY.

Tested: pnpm --dir mobile test; pnpm --dir mobile lint; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile exec oxfmt --check src/terminal/terminal-live-text-commit.ts src/terminal/terminal-live-text-commit.test.ts src/terminal/use-terminal-live-input-commit.ts src/terminal/use-terminal-live-accessory-input-commit.ts app/h/[hostId]/session/[worktreeId].tsx; git diff --cached --check

Not-tested: Physical Galaxy Fold7 Samsung keyboard manual QA is still external-device only.

* Prevent stale IME timer flushes after mobile terminal teardown

Pending live-input timers now clear on hook unmount, and accessory Delete documents why it stays local without trimming pending IME text.

Constraint: React Native TextInput lacks a portable composition lifecycle, so pending IME text is guarded by a bounded timer that must not survive screen teardown.

Rejected: Use clearPendingLiveInputCommit during unmount | it would also touch React state/native props during teardown when only timer/ref cleanup is required.

Confidence: high

Scope-risk: narrow

Directive: Any delayed terminal input commit must have an owner-lifecycle cleanup path before sending to the PTY.

Tested: pnpm --dir mobile test; pnpm --dir mobile lint; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile exec vitest run src/terminal/terminal-live-text-commit.test.ts --reporter=verbose; pnpm --dir mobile exec oxfmt --check src/terminal/terminal-live-text-commit.ts src/terminal/use-terminal-live-input-commit.ts; git diff --check

Not-tested: Physical Galaxy Fold7 Samsung keyboard manual QA remains unavailable in this environment.

* Use semantic accessory edits for mobile IME commits

Accessory Backspace/Delete now carry semantic local-edit intent from built-in keys instead of inferring intent from raw bytes, and submit handling is reconnected to the pure submit-sequence model.

Constraint: Custom terminal accessory keys may produce the same bytes as built-ins but should still flush pending IME text before sending rather than being silently treated as hidden-input edits.

Rejected: Classify local accessory edits by raw bytes | That couples future custom controls to current built-in byte encodings.

Confidence: high

Scope-risk: narrow

Directive: Keep semantic input intent separate from terminal byte payloads when pending IME text is present.

Tested: pnpm --dir mobile test; pnpm --dir mobile lint; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile exec oxfmt --check src/terminal/terminal-live-text-commit.ts src/terminal/terminal-live-text-commit.test.ts src/terminal/use-terminal-live-input-commit.ts src/terminal/use-terminal-live-accessory-input-commit.ts app/h/[hostId]/session/[worktreeId].tsx; git diff --check

Not-tested: Physical Galaxy Fold7 Samsung keyboard manual QA remains unavailable in this environment.

* Respect IME flush failures before control input

Propagate terminal.send success from pending Korean IME text before sending Enter, Tab, or accessory bytes, while keeping custom no-pending accessory bytes on the original direct path.

Constraint: PR #7011 review required follow-up control bytes only after the pending composed text send actually succeeds.

Rejected: Treating send invocation as success | It can still reject or no-op when RPC state changed.

Confidence: high

Scope-risk: narrow

Directive: Keep pending IME flush paths async-success-aware before adding new terminal control inputs.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; targeted no-excuse clean for mobile/src/terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Serialize mobile IME flushes before live controls

Treat terminal.send as successful only when the RPC response is ok and the runtime send result is accepted, then route all live-input control sends through a shared in-flight pending-flush barrier.

Constraint: PR #7011 review found that resolved RPC promises and per-call sequencing were not enough to prove pending Hangul text reached the PTY before follow-up controls.

Rejected: Only awaiting each flush-then-send call | Repeatable accessory keys and no-pending sends can arrive while the first flush is still in flight.

Confidence: high

Scope-risk: moderate

Directive: Keep future mobile terminal control paths behind the pending-flush barrier whenever IME text may be in flight.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; no-excuse clean for terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Queue current IME snapshots behind active flushes

Drain the pending snapshot captured by a control action after any already-active terminal send, and make accessory commit handling explicit so raw fallback is not encoded as an inverted boolean.

Constraint: Architecture review found the previous single-slot barrier could wait for an older flush while skipping newly pending Hangul text.

Rejected: Reusing the prior in-flight promise as the current flush result | It proves only an older snapshot, not the current pending buffer.

Confidence: high

Scope-risk: narrow

Directive: New mobile terminal control paths must distinguish allow-raw, handled, and suppress-raw outcomes explicitly.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; no-excuse clean for terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Preserve accessory raw-send terminal targets

Capture the terminal handle at accessory keypress time and suppress raw fallback if the active live terminal changes while waiting for pending IME flushes.

Constraint: Independent review found raw accessory bytes could retarget to a different terminal after an async IME flush barrier.

Rejected: Re-reading activeHandleRef as the send target after await | It can point at a different terminal than the keypress belonged to.

Confidence: high

Scope-risk: narrow

Directive: Raw accessory fallback must use the keypress-time target and revalidate it after any await.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; no-excuse clean for terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Document accessory flush barrier intent

Make the non-obvious raw accessory wait/suppress behavior explicit so future changes preserve IME-before-control ordering.

Constraint: CodeRabbit requested a why-comment for the send-now accessory branch.

Rejected: Leaving the barrier semantics implicit | The branch can otherwise look like unnecessary async defensive code.

Confidence: high

Scope-risk: narrow

Directive: Keep comments focused on why raw accessory bytes wait behind IME flushes.

Tested: targeted terminal vitest suite; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt check for changed file.

Not-tested: Physical Galaxy Fold7 Samsung keyboard.

* Preserve buffered accessory raw sends

Keep the stale-handle guard focused on the captured active terminal instead of live-input opt-in state, so buffered mode keeps existing accessory key behavior while async live-input waits still cannot retarget to another terminal.

Constraint: Buffered command input behavior must remain unchanged while fixing mobile Korean IME live input ordering.
Rejected: Requiring live-input enabled handles for raw accessory fallback | suppresses valid buffered-mode accessory sends.
Confidence: high
Scope-risk: narrow
Directive: Do not use live-input opt-in state as terminal liveness for raw accessory sends; validate captured target, active terminal tab, connection, and client instead.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile terminal/session files; TypeScript no-excuse checker for changed terminal files.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Keep Hangul IME text pending until explicit flush

Avoid timer-driven PTY writes for Hangul candidates so paused Korean composition cannot leak intermediate jamo, while preserving the bounded settle timer for non-Hangul IME text. Also keep disabled live-input accessory fallback behind any existing pending flush barrier.

Constraint: React Native TextInput does not expose a portable composition lifecycle on this mobile surface.
Rejected: Fixed 150ms auto-flush for Hangul | can emit ㅎ or 하 if the user pauses mid-composition.
Confidence: high
Scope-risk: narrow
Directive: Treat Hangul candidates as pending until submit/control/accessory flush; do not reintroduce idle timer commits for Hangul without device-level composition evidence.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile terminal/session files; TypeScript no-excuse checker for changed terminal files.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Gate dictation toast on accepted live send

Honor the async live-input sender contract so the mobile UI reports dictation insertion only after terminal.send is accepted.

Constraint: sendLiveTerminalInput now returns false for stale, disconnected, oversized, or rejected terminal sends.
Rejected: Toasting immediately after dispatch | reports success for sends that never reached the PTY.
Confidence: high
Scope-risk: narrow
Directive: Treat live-input UI success as terminal.send acceptance, not request dispatch.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check app/h/[hostId]/session/[worktreeId].tsx.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Keep accessory edits on Hangul pending path

Make accessory local edits reuse the Hangul-aware defer policy so built-in Backspace/Delete cannot reintroduce timer-driven Hangul PTY writes.

Constraint: Hangul IME candidates must remain pending until explicit submit/control/accessory flush.
Rejected: Reusing the non-Hangul 150ms settle timer for accessory local edits | can leak pending Hangul after Backspace/Delete.
Confidence: high
Scope-risk: narrow
Directive: Any future pending-text reschedule must use getTerminalLiveDeferredTextDelayMs instead of a hardcoded timer.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile terminal/session files; TypeScript no-excuse checker for changed terminal files.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Prove Hangul live-input hook ordering

Add a direct hook-level regression so Android Korean IME fixes are covered at the orchestration boundary, not only by lower-level helpers.

Constraint: React Native mobile TextInput lacks portable composition lifecycle events in this path.

Rejected: Relying only on helper tests | misses hook-level pending flush and submit ordering.

Confidence: high

Scope-risk: narrow

Directive: Keep Hangul candidates pending until an explicit terminal action flushes them.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile files; no-excuse on terminal modules

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Keep accessory raw-send tests precise

Remove a duplicate raw-target assertion whose title implied disabled live-input behavior that is covered at the accessory commit boundary instead.

Constraint: Anti-slop cleanup must preserve existing Hangul/accessory behavior and stay within changed terminal tests.

Rejected: Keeping the duplicate disabled-input wording | it tests the same active-terminal predicate as the preceding case.

Confidence: high

Scope-risk: narrow

Directive: Test disabled live-input buffering in the accessory commit layer, not in the raw-target predicate helper.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Explain stale mobile terminal send gates

Document why async IME flush paths re-check terminal/client refs before sending raw bytes or reporting live-send success.

Constraint: CodeRabbit review requested short why comments for non-obvious stale-send safety gates.

Rejected: Leaving the gates undocumented | future edits could remove the stale-target suppression contract.

Confidence: high

Scope-risk: narrow

Directive: Keep async terminal sends guarded by current client, active handle, tab type, and connection state.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Run mobile IME hook tests through effects

Move the Hangul live-input hook regression from server rendering to react-test-renderer so effect cleanup and unmount timer cancellation are exercised.

Constraint: @testing-library/react-native imports React Native's Flow entry under this Vitest setup, so the narrow effect-running renderer is the compatible test surface.

Rejected: Keeping renderToString | it never runs useEffect cleanup and missed the pending timer cleanup path.

Rejected: Adding @testing-library/react-native directly | it failed before tests with React Native Flow syntax under the current Vitest transform.

Confidence: high

Scope-risk: narrow

Directive: Hook-level IME tests must use a renderer that runs effects when asserting pending flush cleanup.

Tested: vitest targeted terminal tests; pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Keep hook lifecycle tests quiet

Suppress only the react-test-renderer deprecation warning around the effect-running hook harness so real console errors still surface.

Constraint: CodeRabbit flagged React 19 renderer warning noise; @testing-library/react-native remains incompatible with the current Vitest/RN Flow transform path.

Rejected: Global console silencing | it would hide unrelated test failures.

Confidence: high

Scope-risk: narrow

Directive: Keep the renderer warning suppression scoped to this hook harness and pass all other console errors through.

Tested: vitest targeted terminal tests; pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* fix: flush pending mobile IME input before external sends

* fix: guard terminal command finished event dispatch

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-07-02 02:31:19 -07:00
Neil e59c04122d Fix Activity thread options trigger ref loop (#7096) 2026-07-02 02:17:38 -07:00
JinjingandOrca 6142ec1a06 Refresh AI Vault session list on window refocus (#7075)
* Refresh AI Vault session list when the window regains focus

Sessions started after the panel mounted never appeared until a manual
refresh, since the hook only scanned on mount and scope changes. Listen
for window focus and visibilitychange (to visible) while the panel is
mounted and trigger a non-force re-scan, letting the main process's 15s
scan cache rate-limit rapid focus flips. The manual refresh button keeps
its force (cache-bypassing) behavior.

Co-authored-by: Orca <help@stably.ai>

* Keep AI Vault refocus refresh render-free on cache hits

Refocus refreshes now run as background: the loading flag stays down (no
spinner flash on every alt-tab back), and when the main process replays
the cached snapshot (same scope key + scannedAt) the state updates are
skipped entirely so nothing re-renders. Fresh scans and the manual force
refresh apply results exactly as before.

Co-authored-by: Orca <help@stably.ai>

* Bypass AI Vault scan cache on panel entry and refocus, throttled

Non-force refreshes were served the 15s-old cached snapshot, so a
session started right before re-entering the panel or refocusing the
window still didn't appear — only the manual force refresh showed it.
Panel entry and refocus now request a force scan, throttled in module
scope to one forced scan per 5s (surviving panel remounts), so rapid
tab/focus flips still resolve from the main-process cache. Manual force
refreshes count against the throttle to avoid back-to-back full scans.

Co-authored-by: Orca <help@stably.ai>

* Deliver AI Vault refocus via main-process signal; calm in-app triggers

Renderer DOM focus/visibility events never fire on macOS app activation
(verified live: document.hasFocus() stays true and no focus/blur event
lands when the window loses/gains OS focus), so the refocus refresh was
inert. Main now broadcasts browser-window-focus to the renderer as
aiVault:windowFocused and the hook subscribes to that instead.

Sessions started inside Orca (window never blurs) get their own trigger
from agent hooks: re-scan only when an unseen provider session id
appears in agent status. State transitions and message pings on known
sessions are deliberately ignored — keying on them made the panel churn
on every AI message. Event-driven rescans that land inside the 5s
throttle window defer to one trailing scan instead of being dropped.

Verified end-to-end against a dev instance: a headless claude session
appeared in the panel on OS-level refocus and another on panel
re-entry, without the manual refresh button.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-02 01:45:03 -07:00