Introduce support for detecting and activating 'task_' prefixed links
in terminal panes.
Because orchestration tasks are stable records but can be retried
onto new dispatches, clicking a task link queries the runtime database
via 'orchestration.dispatchShow' to retrieve the latest assigned
terminal handle, then navigates to that terminal.
Avoid triggering unnecessary terminal PTY resizes and SIGWINCH signals
when restoring terminal tabs or replaying hidden snapshot backlogs. This
prevents alternate-screen TUIs from resetting their viewports or
scrolling to the top when switching tabs.
- Track hidden startup measurement as state and clear it after first visibility
- Suppress forwarding terminal resizes while a pane is hidden
- Avoid resizing or sending SIGWINCH on snapshot replay when dimensions are unchanged
* Keep repos visible with missing project groups
* Preserve shared project metadata on host refresh
* Prune stale source repos in projects and fix sidebar group rendering
- Filter sourceRepoIds against currently known repo IDs to prevent
removed or stale remote repos from lingering in shared project metadata.
- Prevent project-group assigned repos from rendering as missing metadata
fallback rows if the group exists.
* Preserve multi-host project metadata and source repos on refresh
- Keep source repos owned by other hosts during a single-host refresh
- Ensure client-local Windows runtime preferences are not overwritten
by remote refreshes, while allowing local refreshes to clear them
- Prevent sidebar key errors when repo groups have not yet loaded
- Apply extra left-padding pullback to experimental cards when the status lane is shown, keeping title text aligned to tree steps.
- Use negative left margin on shallow rows when the minimum content inset clamps the padding adjustment.
- Resolve experimental card style setting dynamically in the folder workspace panel instead of hardcoding it.
Remove the logic that defers activation of slept worktrees behind the
input-quiet scheduler when clicked in the sidebar.
Since the underlying state transition and terminal preparation are
already properly deferred where needed, sidebar clicks can update the
active workspace immediately. This simplifies the activation flow and
eliminates complex cancellation race conditions.
* Speed up Markdown preview local images
Implement fast local image prewarming for Markdown preview as described in docs/markdown-preview-fast-local-images.md.
* Encode markdown image paths to support spaces on insertion
Unescaped spaces and delimiters in markdown image destinations can make
screenshot filenames with spaces render as literal text or broken partial
paths.
- Encode the image basename using encodeURIComponent when inserting.
- Preserve encoded local image paths during markdown round trips.
- Remove outdated markdown-preview-fast-local-images design doc.
* fix: address review findings
* Improve accessibility and refactor GitHub work item status helpers
- Add aria-label and aria-hidden attributes to the task ID pill and icons to make them screen-reader friendly.
- Refactor getTaskPageGitHubPRIconTone to use an exhaustive switch statement with explicit type-safety fallbacks.
- Add comprehensive test coverage for draft checks, state labels, and icon tone edge cases.
* Don't enqueue local PR refresh for remote-host repos (fix renderer OOM)
The renderer enqueues GitHub PR refreshes for worktrees. Remote/SSH/runtime
worktrees are meant to refresh through the runtime route (getRuntimeRepoTarget),
but when that route is unavailable — host not the active environment, or
disconnected — the call falls through to the local `gh:enqueuePRRefresh` IPC.
The local handler only resolves repos registered in the local store, so it
rejects every such call with "Access denied: unknown repository path".
With a remote worktree active (e.g. a runtime "Project server" workspace), this
fires on a loop (worktree activation + SWR polling), flooding the renderer with
failed invokes and unhandled rejections. Observed hundreds of these per session;
the renderer JS heap climbs to the V8 ceiling (~3.5GB) and V8 aborts the process
(crash-reports.json: renderer crash, exitCode 5, usedHeapMB == heapLimitMB).
Add isLocalHostPRRefreshCandidate and gate all four enqueue sites on it, so the
local handler only ever receives local-host candidates. Remote candidates with
no available runtime route are skipped (their PR status refreshes once the host
is active/reconnected) instead of spamming a handler that can't serve them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: cover local executionHostId in PR-refresh host guard
Address CodeRabbit nitpick on #6094: add a positive case where a local repo
carries an explicit executionHostId === LOCAL_EXECUTION_HOST_ID, completing
predicate coverage for isLocalHostPRRefreshCandidate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: route PR refreshes by repo host
Co-authored-by: Orca <help@stably.ai>
* fix: skip disconnected SSH PR refreshes
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* defer committing workspace directory path changes until blur or enter
Introduce draft state for the workspace directory path input, only
committing the changes on blur, Enter, or Browse completion. This
prevents the backend from creating incomplete directory path prefixes on
disk with `mkdir` during intermediate typing states.
* Fix agent-hooks test mock and clean up unused imports
* Update the RuntimeClient mock in agent-hooks test to return a fully
typed instance with the correct schema, eliminating type assertions.
* Remove an unused React import in WorkspaceDirectorySetting test.
* Detect agents in the serve user's package-manager bins; don't pin empty remote-detect results
Two defects prevent the desktop client from detecting agents installed on a
remote `orca serve`:
D1 (src/relay/relay-command-env.ts): the relay probes agents with a login shell
(`/bin/sh -lc`), which sources ~/.profile but not the interactive ~/.bashrc that
real PTY sessions source. Per-user package-manager bins added to PATH only in
~/.bashrc are therefore invisible to detection. Resolve the serve user's
package-manager bins from $HOME (.local/bin, .npm-global/bin, .cargo/bin,
.bun/bin, go/bin, .deno/bin, .local/share/pnpm), honor npm_config_prefix, append
to the POSIX fallback set, dedupe against inherited PATH; win32 unchanged.
D2 (src/renderer/src/store/slices/detected-agents.ts): an empty detection result
([], truthy in JS) was pinned by the `if (existing)` short-circuit, so detection
never retried after the server env was fixed. Changed to `if (existing?.length)`;
in-flight dedup still prevents re-detect storms; failures stay uncached.
No new transport, no PowerShell/bash bridge. D1 and D2 are independent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Also patch the runtime (orca serve) detection path against the empty-pin bug
The D2 fix guarded only ensureRemoteDetectedAgents (the SSH path). The
desktop client's headline scenario — pairing to a remote `orca serve` —
routes through ensureRuntimeDetectedAgents (kind: 'runtime'), which had
the identical bug: an empty detection result ([], truthy in JS) was pinned
by the `if (existing)` short-circuit, so a later install / PATH fix was
never picked up without a reconnect.
Change the runtime guard to `if (existing?.length)`, mirroring the remote
path. In-flight dedup still collapses concurrent re-detects; failed
requests stay uncached. Add a runtime regression test proving detection
re-runs after an empty result (fails on the pre-fix guard).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Honor package-manager relocation env vars in relay PATH detection
Extend the existing npm_config_prefix precedence to cargo, bun, deno, go,
and pnpm so relocated installs (CARGO_HOME, BUN_INSTALL, DENO_INSTALL,
GOBIN/GOPATH, PNPM_HOME/XDG_DATA_HOME) are detected, falling back to the
$HOME defaults. Mirrors the remote-node probe's NVM_DIR handling (#6037).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Tighten remote agent detection retries
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: LesleyMurfin <260182349+LesleyMurfin@users.noreply.github.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Apply styling via data attributes and custom CSS in main.css instead
of basic utility classes. This provides a more distinct background color
for selected files in both light and dark modes, and adds a subtle border
to ensure legibility against panel backgrounds.
* Load all hosts' repos at startup so cold start doesn't hide local repos
On a cold start, the renderer restores the last-active workspace. If that
workspace lived on a remote runtime environment, settingsForExecutionHostOwner
re-activates that environment, and startup's fetchRepos() then loads only the
active host's repos. The result: every local repo disappears from the sidebar
(even under the "All hosts" scope, which can only filter already-loaded repos),
and adding a local folder fails with "Not a valid git repository" because the
path is validated against the remote host. This reproduces on any cold start
regardless of launcher; warm activations skip restore and appear unaffected.
Add fetchReposForAllHosts(), which loads the local host plus every configured
runtime environment and merges them into the unified repos array (each host
fails soft so an unreachable/disconnected host is skipped without blocking the
others). Call it at startup instead of fetchRepos() so the sidebar shows local
+ remote together no matter which environment is active.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Schedule safe-auto fork sync per host in fetchReposForAllHosts
Codex review caught that the new all-host startup fetch dropped the
scheduleSafeAutoForkSync side effect that both fetchRepos and
fetchRuntimeEnvironmentRepos run after merging a host. Without it, repos with
forkSyncMode 'safe-auto' would stop syncing on cold start. Schedule the sync
for each fetched host's repos after applying its result.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Load startup project data across all hosts
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* Fix remote Node.js detection for nvm, mise, asdf, and volta
Remote Node resolution failed when node was installed via a version
manager (nvm with custom NVM_DIR, mise, asdf, volta) or when the user's
login shell was zsh/fish rather than bash.
Root cause: the SSH exec transport runs every command under /bin/sh,
which never sources shell init files. The only init-aware path was a
hardcoded `bash -lc` fallback that missed zsh/fish users and was never
reached for the newer version managers. nvm was handled by guessing
~/.nvm (breaking custom NVM_DIR), and mise/asdf/volta had no probes at
all. There was also no version gate, so nvm's highest-version glob
could return Node 8/10/12 and crash the relay on launch.
Fix: resolve via the user's own $SHELL as a login shell first (the only
path that runs nvm.sh / mise activate / asdf.sh init hooks), then fall
back to direct path probes for all major managers (nvm respecting
$NVM_DIR, fnm, mise, asdf, volta, n) plus system locations. Every
candidate is version-checked against the relay's Node 18+ requirement
before being accepted.
* Address CodeRabbit review: probes-first, no || short-circuit
- Reorder to path-probes first (deterministic, doesn't depend on shell
rc-file semantics where bash -lc skips .bashrc and zsh -lc skips
.zshrc — exactly where nvm/mise/asdf hooks live).
- Join probes with newlines instead of || so an empty
`ls | sort -V | tail -1` (exit 0) doesn't mask later probes.
- Deduplicate candidate paths before version-checking.
- Drop unreachable mock and fix misleading $SHELL-unset test name.
- Login shell is now a fallback for custom ~/.profile PATH setups.
* Fix remote Node path probing portability
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* Fix blank/unclosable mobile emulator tab in floating workspace
FloatingTerminalPanel only handled terminal/browser/editor content types, so simulator tabs rendered no pane and routed close through closeFile (a no-op for simulator tabs). Treat simulator as its own content type: render EmulatorPane for the active simulator tab, wire activeSimulatorTabId into TabBar, and close via closeUnifiedTab.
* Add floating Mobile Emulator tab E2E smoke test
Adds a deterministic Electron/Playwright spec that seeds a simulator unified tab in the floating workspace, asserts the emulator pane renders, and closes it through the real tab-strip X. Adds stable data-emulator-pane selectors and a data-tab-close-button hook on the simulator tab chrome, plus a targeted package script. No live iOS Simulator or Orca Computer/AX dependency.
* Exclude simulator tabs from floating Close All Files
Close All Files filtered out only terminal/browser tabs, so after the simulator render/close fix it would also close the Mobile Emulator. Simulator tabs are not files; exclude contentType 'simulator' to match terminal/browser behavior, and add a regression test asserting Close All Files closes the editor tab but leaves the simulator open.
* Keep floating simulator tabs mounted
---------
Co-authored-by: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
* fix(codex): wrap Windows hook command in cmd.exe to survive spaces in profile path (#6078)
Windows splits raw hook commands on whitespace, so a user profile path
like `C:\Users\Jane Doe` made Codex hooks exit with code 1. Add a
wrapWindowsHookCommand helper that invokes the .cmd through
`cmd.exe /d /c call "..."` and use it in getManagedCommand.
* fix(agent-hooks): wrap Windows hook command in cmd.exe for all agents with raw .cmd path (#6078)
Apply the wrapWindowsHookCommand helper to cursor, command-code, gemini,
grok, and droid, which shared the same raw-scriptPath-on-Windows pattern
as codex. A user profile path with a space (e.g. `C:\Users\Jane Doe`)
used to split at the space and fail with exit code 1.
Agents that already handle spaces correctly are left untouched:
- claude/openclaude (Git Bash + forward slashes)
- copilot (PowerShell with quoted path)
- kimi (Git Bash + forward slashes)
- antigravity (event-specific wrapper .cmd files)
- devin (already wraps via `cmd /d /s /c ""...""`)
Each fixed agent gets a Windows-only test asserting the cmd.exe wrapping
survives spaces in the profile path.
* fix(claude): wrap Windows hook command in cmd.exe to survive spaces in profile path (#6078)
Claude Code runs hooks through Git Bash on Windows. The previous
forward-slash trick only works when the path has no spaces — Git Bash
splits `C:/Users/Jane Doe/...` at the space and tries to execute
`C:/Users/Jane` as a command. Use wrapWindowsHookCommand so the .cmd is
invoked through `cmd.exe /d /c call "..."`, which Git Bash treats as one
argument. Applies to both Claude and OpenClaude (shared getManagedCommand).
* Harden Windows agent hook launcher
---------
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
Fixes#5906
The divider line on the workspace tab strip used full opacity
border-border, while other divider lines in the UI use varied
opacities. Changed to border-border/70 to match the standard
divider opacity used throughout the application.
Signed-off-by: Noah Khomer <108771853+noahkhomer18@users.noreply.github.com>