* fix(terminal): reveal Markdown links at target lines
* fix(terminal): scope line reveals to opened tabs
---------
Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
* Normalize branch prefixes and flag invalid ones in settings
A custom branch prefix ending in a slash (e.g. "team/") produced a
double-slashed branch name like "team//feature" that git rejects, and
the raw check-ref-format error gave no hint that the prefix caused it.
- Normalize the configured prefix (trim whitespace, strip leading/
trailing and duplicate slashes) in the shared branch-name builder so
the common trailing-slash case just works, for local and SSH worktrees.
- Validate the prefix on the worktree-create path (computeValidatedBranchName)
so a genuinely invalid prefix fails fast with a clear
"update it in Settings -> Git" message instead of an opaque git error.
- Add a live BranchPrefixFeedback under the Branch Prefix setting: previews
the resulting branch name, warns on invalid characters, and notes when a
prefix collapses to none.
- Keep the background first-work rename on the non-throwing builder since the
prefix is already validated at create time.
* Keep caret in place when editing the branch prefix
The custom branch prefix input was directly controlled by settings, but
updateSettings persists through an async IPC round-trip, so the value
updated a tick late and React re-assigned it, snapping the caret to the
end on mid-string edits. Drive the input from a local draft and only
adopt genuine external settings changes so the caret stays put (and fast
typing survives slow SSH round-trips).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Return ReactNode from BranchPrefixFeedback
JSX.Element needlessly excludes null/string/number returns; ReactNode
keeps the component's return type from over-constraining future changes.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Preserve OMP session identity and exact resume paths across cold restoration, AI Vault, mobile, WSL/SSH, and host-authority routes. Add mixed-version fallback and completed-session recovery coverage.
* fix(worktree): stop terminal removal fence error flashing on delete
Deleting a worktree kills its PTYs for the filesystem teardown, then runs
git worktree remove (~1s). During that window a doomed pane races a fresh
respawn that main correctly fences with TerminalRemovalInProgressError, but
the renderer surfaced that internal fence verbatim as a pane error banner
until the worktree unmounted.
- startFreshSpawn: skip the respawn when the pane's own worktree is being
deleted (isDeleting) — no shell should spawn into a directory being removed
and the pane is about to unmount.
- reportError: swallow the removal fence at the single pane-error sink so it
never reaches the banner, covering the parent-removal-fences-child case the
own-worktree skip cannot see.
- Share the fence messages between main (thrown) and renderer (recognized) via
worktree-removal-fence-error.ts so the thrown text and predicate can't drift.
* test(worktree): assert onError callback captured before invoking
Optional invocation let the fence-suppression test false-pass if the
transport onError wiring broke; require the callback so a broken wire
fails loudly. Addresses CodeRabbit review on #10240.
* fix(app): bound the wake/quit paths implicated in the phone-session-ended freeze (#9447)
- relay-transport: waitForClose now times out (5s) so a half-open post-sleep
socket can't wedge runtimeRpc.stop()
- will-quit: race teardown against a 20s deadline so app.quit() always runs
(Force Quit was the only escape when any teardown member never settled)
- terminal-fit-restore: local restoreTerminalFit invoke gets the same 15s
bound as the remote path so the held-fit modal buttons can't pin disabled
* fix(app): close wake recovery timeout gaps
* fix(relay): drop late frames after forced teardown
* fix(relay): fence detached socket callbacks
* fix(app): close timeout resource gaps
* fix(relay): detach retired mobile transports
* fix(types): exclude absent stat overloads
* fix(runtime): expire wedged terminal restore dedupe
* fix(runtime): keep restore retries on one reclaim
* chore(skills): refresh bundled skill manifests
* fix(window): fence quit acknowledgements by request
* fix(relay): bound revoked device socket cleanup
* feat(agent-rows): opt-in conversation-name labels for agent rows
Sidebar worktree-card and dashboard agent rows always show the last
message sent to the agent, so rows relabel on every turn and a 'continue'
prompt becomes the row's name. Add an opt-in Agents setting that labels
rows with the conversation name instead, resolved with the tab bar's
precedence: manual rename, quick-command label, OpenCode session title,
generated title (behind its existing setting), then the agent-set live
title. Live titles count only when they carry a real name - status
decoration is stripped, and pure status, identity-echo, spinner+cwd, and
placeholder titles fall back to the last-message label. Subagent child
rows keep their own descriptions.
Locale note: sync:localization-catalog also restored parity for keys
already missing on main (add-host, sleep-worktree copy).
* fix(agent-rows): read the live tab so late renames and titles surface
Row data patches live entries in place and keeps the tab snapshot from
row creation, so a rename or agent-set title landing after the row was
built never reached the conversation-name resolver. Select the current
tab from the store in the hook; retained rows without a live tab keep
the snapshot fallback.
* fix(agent-rows): reject status labels and bound lookup work
Reject native provider/status titles and Windows/UNC cwd frames so conversation-name mode falls back to the user's last message instead of relabeling rows with identity or path text. Keep default-off and subagent rows off the tab map, and share a WeakMap-backed tab index across mounted rows to avoid repeated linear scans on store writes.
* feat(agent-rows): make conversation names the default
Always prefer a usable conversation name for sidebar and dashboard agent rows, falling back to the last message when the resolver rejects a title. Remove the preference, settings UI, search copy, and catalog entries. Keep subagent labels unchanged and reject generic Terminal N placeholders so partial tab snapshots cannot hide meaningful prompts.
* chore(i18n): sync catalog after main merge
* fix(agent-rows): preserve same-tab child labels
* feat(diagnostics): name what grew in renderer OOM crash reports
Renderer OOMs are the dominant crash class (heap pinned at the ~3.5GB V8
ceiling in crash-channel reports) but renderer_memory breadcrumbs carry only
heap totals, so reports say "it grew" without saying what.
Add a one-shot renderer_memory_highwater breadcrumb at 60%/80% of the heap
limit carrying leak-diagnosis counts: DOM node census, terminal element
count, and per-subsystem counts from a new contributor registry. The store
registers the first contributor, reporting its 20 largest top-level
collections. Counts only, capped per contributor; zero work on the 60s
sample path while below threshold.
* fix(diagnostics): retain renderer heap profiles
* test(agent-status): remove subagent row order race
* fix(diagnostics): bound aggregate heap profile work
* fix(diagnostics): bound heap profile contributor calls
* fix(types): avoid overloaded stat return inference
* fix(diagnostics): fully bound heap profile registry
* chore(skills): refresh release snapshot manifest
* fix(diagnostics): retain browser counts in heap profiles
* fix(quick-open): guide rg install after fallback budget errors
* fix(quick-open): show local host wording for local install guidance
The install-rg guidance component hardcoded 'on the remote', so the new
local fallback path told local users to install ripgrep 'on the remote'
— wrong for the exact case #9627 targets. Parse the location out of the
message and render the matching wording; add the local locale string and
a render test that guards against the 'on the remote' regression. Also
harden the reason capture against a stray ')' in the error text.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
Credits remote terminal output only after parse or intentional discard, with bounded adaptive windows, fair draining, recovery cleanup, and RTT/parser benchmarks.
Adds a keyboard command that opens the "Send notes to an agent" picker for
the active worktree's AI diff-review notes, enabling a fully keyboard-driven
review flow. Unbound by default; users assign it in Settings → Keyboard
Shortcuts.
- New `sourceControl.sendReviewNotes` command (scope global, unbound). Set
`conflictGroup: 'editor'` so Settings warns on collisions with editor chords
(e.g. Add Review Note), not just global ones.
- Dispatched from App.tsx's existing global capture handler so it respects the
terminal-shortcut policy, the shortcut-recorder guard, and defaultPrevented.
- Store thunk `openDiffNotesSendMenuForActiveWorktree` reveals Source Control
and requests the notes send menu open; no-op when there are no unsent notes.
- Menu opens via a nonce-based store request consumed on mount, TTL-bounded so
a request the menu never consumed can't reopen it on a later remount.
Co-authored-by: Orca <help@stably.ai>
Allow the existing "Open in" entries to launch a configured VS Code
launcher against an SSH-backed worktree via Remote-SSH:
code --remote ssh-remote+<authority> <remote-path>
- Split the blanket SSH/runtime block into a capability model: file
managers and non-VS Code launchers stay local-only (disabled with
"Local only" metadata); a recognized VS Code command is enabled and
forwarded with connectionId over a typed object IPC.
- Main process stays authoritative: rejects active/owned runtimes,
resolves the SshTarget from the persisted Store, derives the authority
(config alias, or username@host on port 22, or ssh-alias-required on a
non-default port), validates POSIX/Windows absolute remote paths without
local stat/normalize, and rejects non-VS Code and compound commands
before spawn.
- Authority and remote path are passed as separate argv; getSpawnArgsForWindows
remains the cmd/bat shim boundary and fails closed on metacharacters.
- Same capability rules across the worktree menu, Explorer overflow, and
the source-control entry context menu.
Refs STA-2386
Closes#9999
Production crash diagnostics measured ~128 `git worktree list` execs/min
(9,400 in one 80-minute session, ~16% of wall-clock in git subprocesses):
the resolved-worktree scan fans out over every registered repo on a 30s
cache TTL, and most registered repos on the affected installs were
agent-CLI scratch repos (~/.codex-tmp capsules, vendor imports, skill
checkouts) that need no freshness.
Classify agent-scratch repo roots with a curated shared matcher and stamp
their scan-cache entries with a 5-minute TTL instead of 30s. Orca-driven
mutations still bypass the TTL via the per-repo generation bump, so only
passive pickup of external changes slows for scratch repos. Expected
steady-state reduction on the measured install: ~82% fewer git spawns.
* fix(mobile): persist per-device tab selection so worktree return restores the last open tab
A phone's tab selection lived only in the host's in-memory
ClientSessionTabSelectionStore. Any host restart wiped it, and the
per-device projection then fell back to deterministic topology, so
returning to a worktree on mobile always landed on the first tab
instead of the tab last opened on the phone.
Persist the per-device selections in the Store (keyed deviceId ->
worktreeId), hydrate them when the runtime constructs, and guard
projection so an early empty snapshot after restart cannot wipe a
hydrated selection before tabs arrive. Selections are pruned with the
worktree/repo and on device revoke, and malformed persisted payloads
degrade to empty instead of throwing.
* fix(mobile): harden persisted tab selection cleanup
* fix(mobile): preserve tab selection across worktree rename
* fix(agent-status): map codex request_user_input questions to waiting
Codex 0.145 asks user questions via the auto-allowed request_user_input
tool (experimental default_mode_request_user_input): PreToolUse fires
while blocked on the answer with no Stop, so Orca showed the pane as
working/idle instead of Needs You. Map that PreToolUse to waiting
(mirrors grok's ask_user_question), exempt question waits from the codex
yolo auto-approval suppressor, and deliver native-chat answers to the
digit-commit selector by option number (typed labels are ignored and
Enter commits the highlighted first option). Older codex versions emit
no such event and are unchanged.
* fix(native-chat): preserve codex question answer semantics
* fix(agent-status): keep Claude in-process teammates visible as idle sidebar rows
Claude Code 2.1.21x runs named Agent-tool agents as turn-based in-process
teammates: SubagentStop and TeammateIdle fire at every TURN end while the
teammate stays alive awaiting mail (verified live on 2.1.217). Treating
those events as finish signals deleted the child row seconds after each
burst, so the sidebar showed no subagents for most of a teammate's life.
Root-cause fix: the roster now tracks a working/idle state per child.
- One-shot children (hyphen-free ids) keep remove-on-stop: their
SubagentStop is a true finish.
- Teammate-shaped rows park as idle on SubagentStop/TeammateIdle and
revive to working via the next SubagentStart (same lifecycle id,
first-observed startedAt preserved).
- Idle rows never gate the pane 'working' (#8825's done-gate rule).
- Only TeammateIdle-confirmed idle rows survive a complete lead-Stop
fold; a stopped workflow lane wearing a teammate-shaped id is reaped
there (or immediately, once a fold tagged it listedAsSubagentTask), so
the pre-#8825 idle pile cannot rebuild.
- At the wire cap, the oldest idle row is evicted to admit a working
spawn; working children are never displaced.
- Hydrate keeps pruning idle snapshots: idle-teammate liveness cannot be
proven across a restart, and a live teammate re-earns its row.
* fix(agent-status): restore inventory-confirmed workflow lanes
* perf(rate-limits): throttle statusline usage posts to one curl per pane per 15s
The managed Claude statusline script posted on every statusLine tick
carrying rate_limits — ~3 curl spawns/sec per streaming pane, multiplied
across concurrent panes. The service drops same-value posts inside its
30s dedupe window anyway, so most spawns bought nothing.
Gate the post on a per-pane stamp file: POSIX compares date +%s against
the stamp; Windows uses an all-builtin seconds-of-day parse of %TIME%
(octal-safe, no extra process). Both fail open — unparseable time,
garbage stamp, or midnight wrap posts rather than darkening the live
usage feed — and the stamp only advances when a post actually fires, so
skipped ticks never defer the next one.
Measured: 30 rapid rate-limit ticks spawn 1 curl (was 30).
* perf(rate-limits): keep throttled statusline ticks process-free
Use Claude's monotonic session duration for the POSIX throttle so skipped ticks do not replace curl churn with date churn, while retaining a fail-open date fallback. Key temp files by the stable leaf UUID so path-like or long host tab IDs cannot disable the throttle.
* fix(rate-limits): preserve throttle isolation across upgrades
* fix(rate-limits): reject leading-zero stamp values before arithmetic
All-digits validation still admitted values like 008, which are invalid
octal inside $(( )) — and dash treats that expansion error as fatal, so
the script died before rewriting the stamp and the pane's live usage
feed stayed dark until the file was deleted. Allow-list canonical
decimals (same pattern as the duration parse) on both the stamp and the
computed clock so malformed values fail open to posting.
Verified under dash: the old digits-only check aborts at the arithmetic
(Illegal number: 008); the allow-list survives and posts.
* perf(rate-limits): eliminate POSIX statusline cat churn
* test(rate-limits): exercise overlapping statusline ticks
The stamp check/write is deliberately lock-free (a lock could wedge the
feed closed; fail-open is the contract), so a truly concurrent burst may
post more than once, bounded by overlap width — an exact at-most-one
assertion would be flaky by design. Assert the invariants that do hold:
every overlapping run exits 0, the raced stamp lands valid, and it
throttles the following ticks.
* fix(naming): remove identifier-first name post-processing
Workspace display names and tab titles were being rewritten after
generation by prompt-scanning heuristics from #8238 — a stray "#1" in
prose became a workspace named "#1 - Fix", and the rewrite ran
downstream of generation so user naming instructions couldn't override
it. Per the same principle as #9088, naming defaults stay minimal and
user overrides own the style.
- Delete work-item-reference.ts and display-name-from-work.ts (+tests).
- Auto-rename display names return to the humanized branch slug; tab
titles return to the cleaned first prompt clause.
- Explicit create-from-work-item naming returns to action-first
("Review PR 1234").
- Keep #8238's URL-before-markdown strip-order bugfix in tab titles,
with regression tests adjusted to the natural expectations.
* test(naming): pin incidental marker regression
* feat: continue agent work in a new session
* fix: harden new-session continuation
* fix: source last prompt from provider-authenticated transcript records
Preview user entries can be tool results or harness-injected skill text,
so the continuation prompt's last-prompt hint now comes from the vault
scanner's provider-authenticated lastUserPrompt. Also softens the
continuation instructions for already-complete tasks and adds a cost
warning to the full-transcript option.
* fix: move Continue in New Session row action into the hover group
Edge-usage action; keep the resting row at three icons and reveal it with
the other session actions on hover, matching Resume's gating.
---------
Co-authored-by: jz.feng <jz.feng@aftership.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(dashboard-popout): zoom the pop-out window, not the main window behind it
The View menu's zoom handlers always sent terminal:zoom to the main
window, so zooming while the dashboard pop-out was focused zoomed the
window behind it. The pop-out also never applied the persisted
uiZoomLevel, so it always rendered at 100% in a zoomed app.
The pop-out now applies uiZoomLevel on dom-ready and follows app-zoom
changes while open (main-window zoom, settings control, mobile ui.set).
Menu zoom routes to the pop-out when it is the focused window, stepping
its own webContents zoom, and a narrow before-input-event handler
resolves the zoom.in/out/reset chords (honoring keybinding overrides)
since the pop-out has no renderer-side shortcut plumbing. The step/clamp
constants move to shared/ui-zoom-level.ts so main and renderer share one
definition.
* fix(dashboard-popout): isolate window zoom
* fix(dashboard-popout): deny unused permissions
* docs(dashboard-popout): clarify wheel zoom path
* feat(linear): add MCP-style save issue
* fix(linear): harden save issue parity
* fix(linear): close save issue contract gaps
* docs(linear): bundle project discovery with save issue
* fix(rate-limits): unstick Claude "Limited" usage and feed live usage from session statuslines
The OAuth usage endpoint's 429 Retry-After (~50 min) was ignored, so the
30s-15min automated retry lanes kept landing inside the throttle window and
the status bar stayed on a bare "Limited" indefinitely while Claude itself
worked fine.
- Respect Retry-After on 429: carry it through usageMetadata.retryAtMs and
gate automated refetches (activation lane, poll cycles) until it expires;
user-directed refreshes still bypass.
- Keep the last-known usage snapshot visible through rate-limited windows
(24h) instead of dropping it after the generic 30-minute stale threshold.
- Add a managed Claude statusLine command that forwards each session's
rate_limits (Claude Code >=2.1.80) to a new /statusline/claude loopback
route, feeding live usage windows with zero usage-endpoint calls; OAuth
polling pauses while the live feed is fresh. User-owned statusLine
settings are never overwritten.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(rate-limits): keep last-known window when a statusline post carries only one
Statusline payloads may report five_hour and seven_day independently; a
partial post must not wipe the other bar to null. Also document the
seconds-vs-ms epoch heuristic.
Addresses CodeRabbit review on #9617.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(rate-limits): unstick Claude usage with live statusline feed
The OAuth polling endpoint is rate-limited; Claude's status often shows
"Limited" until the next poll cycle, even when quota remains. Live posts
from the statusline command update usage within 100ms, eliminating false
"Limited" displays during active sessions.
Manages install lifecycle via marker to respect user deletions. Handles
Windows payload buffering and guards before curl spawn. Protects against
live-post/OAuth-fetch races and cross-attribution during account switches.
Gracefully tolerates schema drift in statusline parsing.
* test(rate-limits): assert stale outgoing post doesn't affect incoming
Capture usedPercent before ingesting and assert it remains unchanged,
rather than checking for a specific value. This is more precise and less
brittle when testing session switch isolation.
---------
Co-authored-by: Dzmitry Bachko <dbachko@users.noreply.github.com>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* feat(dashboard): open-worktree dialog control + finished-time parity
Pop-out agent dashboard polish:
- Terminal dialog: replace the footer Close button with the standard top-right
X, rename the primary action from Focus worktree to Open worktree, and prefix
it with an open-in-window icon.
- Card time column: finished agents now read time-since-finish (matching the
left worktree sidebar) instead of time-since-start. Extracts the sidebar's
finished-timestamp logic into a shared helper so the two surfaces cannot
drift, and threads finishedAt through the dashboard snapshot contract.
* fix(dashboard): thread finishedAt through payload validator + test fixture
CI typecheck caught a DashboardCard fixture in dashboard-payload-validation.test.ts
missing the new finishedAt field; also validate finishedAt in the relay guard.
* feat(dashboard): swap pending-question emoji for MessageCircleQuestion icon
The amber pending-question chip used a raw hand emoji (renders differently
per-OS and off the design system); use the lucide chat-with-question icon.
The code editor was hard-wired to the terminal font, so Nerd Font 'Mono' CJK glyphs overlapped in the editor. Adds an opt-in editorFontFamily setting; default is empty so the resolved value is byte-identical to today for every existing user.
Closes#9628
* Access the Floating Workspace from mobile (#8405)
Surface the desktop Floating Workspace (the global, repo-less scratchpad
of terminal tabs under the synthetic `global-floating-terminal` id) on the
mobile app so a Claude session left running there is reachable from a phone.
Adds a terminal-icon button to the mobile host header (phone + tablet
sidebar) that opens the existing Session screen for the floating id. The
sentinel already had host-side RPC support (#5946: local runtime, homedir
cwd, explicit-id fast paths in session.tabs.*); this wires up the mobile
surface and gates it on a new `floatingWorkspaceEnabled` status flag so the
entry hides on hosts that predate it or where the feature is disabled.
The Session screen learns an `isFloatingWorkspaceRoute` flag (mirroring the
existing `folder:` route pattern) that hides repo-backed surfaces — Files,
Source Control, PR/checks, agent history — skips the diff-comment and GitHub
probes, routes terminal URL taps to the phone browser, and limits the New
Tab drawer to terminals + agents (browser/markdown creation resolves a real
worktree host-side and stays desktop-only). useLiveWorktreeName
short-circuits for the sentinel so it no longer polls worktree.show forever.
Extracted the host status.get gating into a useHostStatusGates hook to keep
the host screen under the max-lines ratchet.
* Harden mobile Floating Workspace routing
* Fix mobile host gate reuse race
* Harden floating mobile session polling
* fix(mobile): harden floating workspace route reuse
* fix(mobile): skip floating workspace repo lookup
* fix(mobile): clarify floating workspace header action