Deleting a config-sourced SSH target had no lasting effect: the Manage-SSH pane
re-imports ~/.ssh/config on open, and the import was a pure upsert with no record
of deletions, so the just-deleted host was re-inserted verbatim from the config
that still exists on disk.
Persist a `deletedSshConfigAliases` tombstone set:
- Deleting a config-managed target (source 'ssh-config', or an adopted legacy
import) records its alias; manual targets are never tombstoned.
- The passive on-open sync skips tombstoned aliases, so a deleted host stays
deleted.
- Re-adding or editing a target reclaims its alias, and the explicit Import
action (`reAdopt`) clears all tombstones to deliberately re-adopt config.
This also fixes the edit-then-reappear case: editing a config host to `manual`
already reserved its current alias, and reclaim covers alias changes.
* feat(ai-vault): add OMP sessions to the AI Vault session browser
Adds OMP ("Oh My Pi") to the AI Vault/Agents catalog so historical
.omp/agent/sessions/**/*.jsonl transcripts are discovered, parsed, and
resumable from the right-sidebar session browser — locally and over SSH.
- Discovery mirrors Pi (OMP_CODING_AGENT_DIR env, WSL home roots, per-agent
limit) in both the local scanner and the remote/SSH scanner.
- Parses OMP's message-graph JSONL via the shared graph parser (new
MessageGraphAgent type), capturing the model from model_change.model (OMP's
key, not Pi's modelId) or the assistant message, and tokens from usage.
- Routes OMP through the incremental parse cache so ~5s rescans resume from
the last byte instead of re-reading whole transcripts.
- Resumes by absolute transcript path (`omp --resume <path>`) so it resolves
regardless of which session-dir root (custom OMP_CODING_AGENT_DIR / WSL
store) the file was discovered under; threaded through both the scanner and
the renderer's local resume/copy rebuild.
- Renderer reuses the existing OmpIcon/catalog/grouping; adds overflow-x-hidden
so long worktree chips never widen the sidebar.
Generalizes normalizePiSessionsDir -> normalizeAgentSessionsDir. Verified
end-to-end against 10 real ~/.omp transcripts and rendered in the app.
Co-authored-by: gatsby74 <166927047+gatsby74@users.noreply.github.com>
* fix: make AI Vault parse-cache agent switch explicit
---------
Co-authored-by: gatsby74 <166927047+gatsby74@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
110 files carried an eslint/oxlint-disable max-lines directive but are
already under the default max-lines budget (300 .ts / 400 .tsx / 600 .mjs
/ 800 test), so the suppression is dead. Removing it restores real
max-lines coverage on these files with zero behavior change.
Each removed directive had max-lines as its only rule; verified via a
full oxlint run (0 max-lines violations, 0 new errors). Diff is pure
deletions (200 lines, 0 additions) — no code touched.
Co-authored-by: Orca <help@stably.ai>
Ensure agent CLI startup and draft launch commands use the correct quoting
format based on the user's configured local Windows shell (e.g., cmd.exe).
This avoids using host settings for remote/SSH targets where local shell
preferences do not apply.
getProcessTableSnapshot deduped the ps fork (#6288/#6667) but cached only the
raw stdout string on POSIX, so every concurrent agent pane re-ran parsePsRows
over the identical output within each 500ms TTL window — O(M*P) redundant
tokenization + row allocation. The Windows reader already caches parsed rows;
this makes the POSIX default reader do the same by parsing inside the deduped
scan and returning ProcessTableRow[]. Collapses the duplicate parsePsRows in
the main and relay foreground resolvers into one shared parseProcessTableRows.
Co-authored-by: Orca <help@stably.ai>
Enable three unicorn rules — one correctness, two performance — and fix every
existing violation repo-wide so the rules pass as errors.
prefer-number-properties (76 sites)
- parseInt/parseFloat/NaN -> Number.* : safe aliases (autofixed).
- isNaN -> Number.isNaN (12 sites, hand-converted): global isNaN coerces its
argument, Number.isNaN does not. Verified every call site already passes a
number (Number.parseInt results, number-typed fields, Date.getTime()), so the
conversion is behavior-preserving today and guards against a future non-numeric
argument silently coercing.
prefer-array-find (26 sites)
- .filter(pred)[0] -> .find(pred); .filter(pred).at(-1) / .pop() -> .findLast(pred).
Drops the intermediate array and short-circuits.
prefer-array-index-of (5 sites)
- .findIndex(x => x === v) -> .indexOf(v).
Verified: typecheck (node/cli/web) clean, 53 affected suites pass (1679 tests),
oxlint clean repo-wide. mobile/ uses findLast safely (already ships ES2023
.toReversed()); config scripts and e2e helpers run on Node 24.
* fix(pty): deliver multiline agent-launch prompts via bracketed paste
Multiline agent-launch prompts (claude/codex/opencode argv injection) were
mangled when Orca typed the startup command into the interactive shell. The
command is single-quoted, but its literal embedded newlines survive quoting;
bash readline / zsh zle read every raw LF as accept-line (Enter), so the first
newline submits an unterminated single-quoted command, drops the shell into PS2
(>) continuation, and the rest executes piecemeal — backticks/$ evaluate, quotes
go unbalanced, and the agent never receives the intact prompt. Short single-line
prompts worked because they have no embedded newline.
Fix: when a startup command contains a newline, wrap the payload in
bracketed-paste markers (ESC[200~ … ESC[201~) before the trailing submit CR/LF
so the line editor inserts the multiline text literally and only the trailing
byte submits it. The single-line fast path is unchanged. Gated on the target
line editor having bracketed-paste mode active (Orca-wrapped bash/zsh) so shells
without it never echo the markers as garbage; Orca's bash rc wrappers now force
`enable-bracketed-paste on` (zsh has it on by default).
Applied consistently across every startup-command delivery path:
- src/main/providers/local-pty-shell-ready.ts (in-process / degraded local)
- src/main/daemon/terminal-host.ts (daemon host — primary local)
- src/relay/pty-handler.ts (SSH relay, remote host)
- src/renderer/src/lib/ssh-background-startup-delivery.ts (hidden SSH tab)
All share src/shared/startup-command-submission.ts. Windows cmd.exe and other
shells keep the current CR submit path (no regression); PSReadLine/POSIX
bash/zsh get the fix.
* Fix multiline detection for CRLF-terminated startup commands
Strip the entire CRLF terminator (or lone CR/LF) from startup commands
before checking if the body contains newline characters.
Previously, slicing off only the last character of a CRLF-terminated
command left a trailing CR in the body. This caused a single-line
command to be incorrectly categorized as multiline and wrapped in
bracketed paste.
Prevent enabling auto-merge when a PR is in an UNSTABLE merge state.
GitHub auto-merge mutations reject UNSTABLE PRs directly instead of
allowing them to wait, so we should suppress the option.
* Add MiniMax rate-limit tracking and secure cookie storage
* Securely store MiniMax session cookies using an encrypted envelope format and local file hardening.
* Fetch rate limits in an isolated session partition and clear the cookie jar before and after requests to prevent leakage.
* Add a default-on "minimax" status bar item to display subscription usage.
* Expose minimax configuration settings (group ID and models) in settings panes and sync them via the runtime client.
* Isolate MiniMax config resolver and decryption failures from affecting other rate-limit providers.
* Redact MiniMax secrets with whitespace around colons
Update redactMiniMaxSecret to allow and match optional whitespace
surrounding the colon when redacting quoted cookie values. This matches
the spacing tolerance used during parsing.
* Address PR review: harden cookie read, validate IPC, add tests
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(ai-vault): scan sessions by execution host
* fix(ai-vault): route history resume by host
* test(e2e): cover SSH AI Vault history
* Generalize remote session scanning for all AI Vault agents
Replace the Codex-only remote SSH session history scanner with a
unified scanner supporting all registered agents. This ensures remote
transcripts for Claude, Gemini, Devin, Droid, and others are scanned
and listed alongside local history.
- Propagate host metadata (host ID and platform) to scanned sessions
- Scope remote actions by host, disabling local OS path actions on
remote session logs
- Resolve ambiguous project/worktree matching for overlapping paths
by verifying matching host setup IDs
- Update tests and E2E specs to validate multi-agent remote scanning
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* perf(windows): dedupe per-pane process-table scans in agent inspection
Windows agent foreground-process inspection forks a whole-process-table
PowerShell/CIM scan per pane on the same 750ms/2000ms cadence the POSIX path
uses. The POSIX side routes through getProcessTableSnapshot (500ms TTL + single
in-flight, #6288/#6667), collapsing N concurrent panes to ~2 scans/sec. The
Windows path (queryWindowsProcessDescendants) had no such dedup: K concurrent
agent panes forked K powershell.exe cold-starts, each enumerating the ENTIRE
process table then filtering per-pid in JS — ~10-40x heavier than `ps` (a
powershell cold start is ~150-400ms CPU + tens of MB RSS). The degraded/local
PTY provider path calls it with no per-pane throttle at all. This is the
Windows analogue of the idle-CPU churn #6288 fixed for POSIX.
Generalize the existing createProcessTableSnapshotReader factory to be generic
over its scan result (default T = string, so the POSIX path and its test are
byte-identical) and add a Windows singleton reader that caches parsed
WindowsProcessRow[]. queryWindowsProcessDescendants now reads the shared
snapshot and runs its own descendant walk; runWindowsProcessRows throws on total
enumeration failure so the miss is not cached and the prior null-fallback
contract (callers fall through to node-pty's name) is preserved.
Windows scan-volume regression test (mirrors the POSIX #6288 guard) drives
PANE_COUNT concurrent panes over the cadence window and asserts powershell.exe
spawns are bounded by ticks, not pane count, while every pane still resolves its
descendant. Reverting the dedup fails both cases. POSIX snapshot + volume tests
unchanged and green; node/web/cli typecheck clean.
Co-authored-by: Orca <help@stably.ai>
* test: reset windows process-rows snapshot between agent-foreground cases
The new module-level Windows rows reader caches for 500ms with real
Date.now(), so one case's mocked process table was served to the next
case's assertions (7 CI failures in agent-foreground-process.test.ts).
Mirror the suite's existing POSIX resetProcessTableSnapshotForTests()
with the Windows reset in beforeEach.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Store POSIX mode bits in secure-file hardening cache entries so permission drift is detected even when ctime granularity is coarse.
Also clears inherited HISTFILE in the local PTY test harness for hermetic WSL history assertions, and adds a deterministic coarse-ctime regression test for directory and credential-file mode drift.
Resolve terminal CWD and worktree paths to their canonical form using
realpath before validating containment. This blocks symlink escape
routes for local terminal spawns, returning the default workspace
directory if a requested path attempts to escape the worktree.
- Local terminal paths are canonicalized using native realpath logic
- WSL UNC paths and SSH/remote terminals skip canonicalization
- Floating terminal startup directories bypass containment checks
- Missing or unresolvable workspace directories default safely
* Fix notes send targets for manual agents
* Split agent title merging into manual and launch-agent paths
Separate the merging logic for terminal titles depending on whether the
tab has a launch agent or is a manually started CLI.
- Launch-agent tabs carry an owner bit, allowing their live titles to
promote a stale status row on the same pane.
- Manually started agents have no owner bit, so they only ever add a
row and must not override existing status evidence.
- Remove the temporary TitleHintAgentTarget type and the need to strip
metadata when pushing targets.
Quality pass on renderer/shared PRs merged 2026-07-03:
- WorktreeTitleInlineRename: skip the truncation measure + ResizeObserver in `wrapTitle`
mode, where wrapped titles never truncate — it could only churn unused state (#7307).
- editor slice: reuse the `removeEditorStateForReplacedPreview` helper this PR added
instead of a hand-rolled copy of the same six-field eviction (drops ~50 lines) (#6476).
- useFileExplorerTree: extract `readWorktreeDirectory` so the connectionId/settings
assembly for `readRuntimeDirectory` lives in one place, not three (#6321).
- comment-markdown-github-attachment-media: extract a shared `AttachmentFallbackLink`
for the image/video error-fallback link (#6759).
- repository-icon-github: fold the two near-identical live resolvers into one
parameterized `resolveRepositoryIdentityLive`; trim a 3-line comment to 2 (#6507).
- resource-usage-open-slices: delete the `shouldReadPopoverSlices` identity wrapper and
inline `open` at the four call sites (#7275).
- BrowserPane: drop the pointerEvents assignment already applied inside
`ensureBrowserPageWebview` for the reused-webview path (#6958).
- github slice: fix two garbled "…a commit main confirmed…" comments (#7277).
- runtime-file-client: trim the binary-file fallback comment to its whys (#6606).
- composer-branch-selection: drop the inline comment that restated the JSDoc (#6748).
- TabBarQuickCommandsButton: correct the stale "+ Command" comment (button shows no +).
No behavior change (the editor-helper reuse is behavior-equivalent, only more
conservative on an edge case); typecheck, oxlint, react-doctor, oxfmt, and touched
unit suites all pass.
* 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>
* 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>
* 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>
* 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>
* Support Claude weekly Fable usage meter
* Reference Claude weekly usage research
* Add distinct Claude Fable weekly meter
* Tighten Claude Fable usage parsing
* feat(ssh): add ControlMaster multiplexing for system SSH transport
System SSH transport spawns a new OpenSSH process per exec command
(platform detect, relay install check, node resolution, relay launch,
socket probe). Each process pays the full SSH handshake cost — ~9s on
Uber devpods — making a typical relay connect take 54s+ and reliably
exceeding the 15s startup reconnect budget.
Add SSH ControlMaster multiplexing via a per-target socket in
$TMPDIR/orca-ssh-ctl/<hash>.sock. The first command establishes the
master; subsequent commands reuse it at ~100ms per exec instead of ~9s.
ControlPersist=300 keeps the master alive after commands exit so rapid
reconnects (e.g. on tab focus) also benefit. Windows is excluded since
OpenSSH's ControlMaster support there is limited.
* fix(ssh): address ControlMaster key collision and directory permission risks
- Use target.id in the socket key so distinct SSH targets can never
collide even when configHost/port/user happen to match
- Switch from SHA1 to SHA256 and extend hash slice from 12 to 16 chars
- Stat the control-socket directory after mkdirSync to reject pre-existing
dirs that are symlinks, foreign-owned, or have group/other write bits
(mkdirSync mode is ignored on pre-existing dirs)
- Update two tests that used exact spawn-arg arrays; replace with
ordering assertions (forward flags before --) that stay correct
regardless of which extra ControlMaster options are injected
* fix(ssh): bind ControlPath identity to route and reject symlinked ctl dir
Fold proxyCommand/jumpHost/identity fields into the ControlPath hash so a
target whose route is edited no longer reuses a still-alive master built on
the old route. Switch the control-socket dir check from statSync to lstatSync
so a planted symlink fails the directory validation outright.
* test(ssh): drop tautological argv re-assertion in spawn checks
The toHaveBeenCalledWith re-passed the args array extracted from the same
mock call, making that argument position always pass. argv content is
already verified by the index-ordering assertions above; use expect.any(Array)
so the spawn check only claims what it actually verifies (binary path, stdio).
* fix(ssh): harden system ssh connection reuse
Co-authored-by: Orca <help@stably.ai>
* test(ssh): isolate control socket runtime dir
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Test <test@example.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* fix: reordered default columns in kanban board
* Introduce dedicated flag to repair reversed default workspace statuses
- Add `_workspaceStatusesReorderedDefaultRepaired` to decouple the
one-shot repair from the initial status order migration.
- Ensure the repair runs for users who saved the reversed default
payload (with "Done" on the left) during a short-lived broken build.
- Support both "Completed" and "Done" labels when identifying default
status shapes to migrate or repair.
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* Add keyboard shortcut to toggle the Quick Commands menu
- New `tab.openQuickCommandsMenu` keybinding action (no default binding)
- TabBarQuickCommandsMenu listens for the binding and toggles open/closed
- Scoped to the active tab group naturally since the component only mounts when its group is focused
* Show keyboard shortcut in Quick Commands menu trigger tooltip
* Add tests
* expand tests
* Expand keyboard toggle to call handleOpenChange and skip repeated keys
- Replace `setMenuOpen` toggle with `handleOpenChange(!menuOpen)` so closing
via keyboard runs the same reset logic (query, focus frame, value override)
- Guard against key-repeat events to prevent rapid toggling on held key
- Wrap `handleOpenChange` in `useCallback` so it's stable enough to include
in the `useEffect` dependency array without causing spurious re-registrations
- Update tests to reflect that re-running the effect between presses is
required for the close path, and add a repeat-event test
* Add docstring to withShortcutHint func
* review: harden quick commands menu shortcut
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Keep the synthetic floating workspace local while a remote runtime is active, including terminal/browser creation, activation, close, and remote snapshot handling.
Maintainer follow-ups:
- require worktreeId for runtime-session terminal create payloads
- add renderer-backed terminal create reply sender regression coverage
- merge current main and keep the WSL readDir breadcrumb test aligned with main's Windows-only handler coverage