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>
* fix(emulator): remove destroyed listener on stream stop to stop webContents leak
Both emulator stream IPC handlers register owner.once('destroyed', ...) per
start but never remove it on stop. .once only self-removes when the event
fires (window close), so every emulator tab show/hide cycle leaked a closure
on the long-lived main-window webContents — ~11 cycles trips Node's
MaxListenersExceededWarning and the closures grow unbounded until the window
dies. Store the handler on the session/subscription and removeListener on stop.
Co-authored-by: Orca <help@stably.ai>
* chore(emulator): trim why-comment to 2 lines, drop no-op afterEach
Review polish: honor AGENTS.md 1-2 line comment guidance and remove a
vi.clearAllTimers() that is a no-op without fake timers.
Co-authored-by: Orca <help@stably.ai>
---------
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.
* perf(runtime): memoize onPtyData tail wait scan to halve per-chunk work
onPtyData runs per raw PTY chunk (hundreds/sec during verbose builds and
agent token streaming). For any terminal past the 2000-line / 256KB tail
cap it built the full wait text (a map/trim/filter/join over the entire
retained tail) and lower-cased + scanned it twice per chunk — once for the
pre-append tail and once for the post-append tail — producing hundreds of
KB of transient string allocation per chunk and steady main-process GC/CPU
pressure under load.
Cache the post-append wait state (text + lower-cased blocked-signal scan)
on the pty/leaf record and reuse it as the next chunk's pre-append state.
The prior chunk's post-append tail *is* this chunk's pre-append tail, so
the cached scan is exact; reuse is gated on fromTail so the empty-tail
preview fallback (which depends on a value updated after append) is never
reused stale. This drops per-chunk full-tail scans from 2N to N+1.
Adds an equivalence test proving the memoized stamping is byte-for-byte
identical to the recompute-both-sides reference across split prompts,
partial lines, ready-after-blocked demotion, and tail eviction, plus a
count assertion (memoized N+1 vs reference 2N scans).
Co-authored-by: Orca <help@stably.ai>
* fix(runtime): clear memoized wait cache when a disconnected transcript is pruned
pruneDisconnectedPtyTranscript empties a disconnected PTY record's retained
tail but left the new tailWaitState memo untouched. If such a record resumed
output (adoption/reattach while a leaf keeps it alive), onPtyData would reuse
the stale pre-prune wait state (fromTail=true) as the next chunk's previous
state and could miss or mis-time the waitBlockedAt stamp on that first chunk.
Clear tailWaitState in the prune reset so the resumed chunk recomputes from the
emptied tail.
Adds a runtime guard (prune clears the cache) and a sim equivalence test
covering prune-then-resume stamping.
Co-authored-by: Orca <help@stably.ai>
* docs(runtime): reword wait-scan comment that named a removed function
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix: prefer Claude-generated titles in AI Vault
Agent Session History labeled Claude Code sessions with a truncated
first prompt even when the session already had a Claude-generated name
(the ai-title shown in /status and the tab title). Reserve the top
title slot for a user-set custom-title and rank the generated ai-title
above the first prompt: custom-title > ai-title > first prompt > meta.
New sessions still fall back to the first prompt until the ai-title is
written.
Also prune <session>/subagents/ during discovery via an injected
directoryPredicate so Task subagent transcripts, which share the parent
sessionId and are not independently resumable, stop appearing as
separate untitled history rows. Pruning at the directory level avoids
readdir'ing the excluded subtree and is cross-platform safe.
* Use latest generated Claude title in session scanner
Ensure the scanner updates the generated session title when Claude
revises it, rather than only keeping the first parsed 'ai-title'
record.
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
- Instruct workers to stop and idle or exit immediately after sending
`worker_done`, rather than running a 10-minute polling loop.
- Distinguish instructions based on worker kind: prompt-returning
agents should remain idle for re-engagement, while bare-shell
workers should exit.
- Prevent infinite polling overhead since the coordinator re-engages
workers via fresh terminal input instead of inbox polling.
Replace the hand-rolled `AbortController` + `setTimeout(() => controller.abort())`
+ `clearTimeout` in `finally` pattern with `AbortSignal.timeout(ms)` across the
main-process fetchers, updaters, and hosted-provider clients. This removes a
timer-leak footgun (a thrown/early-returned path that skips the finally leaks the
timer) and ~3-4 lines of bookkeeping per site. `AbortSignal.timeout` is Node
17.3+ (Electron main is Node 22+).
Two sites compose a caller-cancel signal with the timeout via `AbortSignal.any`
(Node 20.3+) instead of a manual abort listener:
- git/fork-sync.ts: also fixes a latent bug — the caller's `options.signal` was
spread into the git options then immediately clobbered by `signal:
controller.signal`, so caller cancellation was silently dropped. `AbortSignal.any`
restores it.
- rate-limits/claude-fetcher.ts (fetchViaOAuth external signal).
hosted-review-api-request.ts: `AbortSignal.timeout()` rejects with a
`TimeoutError`, not an `AbortError`, so the timeout-detection branch is updated
(otherwise `timedOut` would never be set).
minimax-fetcher.test.ts: its timeout test drove the abort with fake timers, which
cannot advance `AbortSignal.timeout`'s internal timer. Rewritten to fire the
timeout with an already-aborted signal so it genuinely exercises the abort path.
Deliberately NOT migrated:
- src/relay/git-handler.ts: the relay targets Node 18 (`build-relay.mjs`,
MIN_NODE_MAJOR = 18); `AbortSignal.any` needs Node 20.3+, and timeout-only would
drop the request context signal.
- ipc/feedback.ts: its timeout-driven fallback is verified with fake timers, which
can't advance `AbortSignal.timeout`; kept on the manual pattern.
getIssueComments loaded the issue, then its comments, then awaited c.user
inside a for-loop. Accessing .user on the Linear SDK's Comment model lazily
issues a fresh user(id) GraphQL query, so a comment-heavy issue did issue +
comments + N sequential user round-trips — a visible multi-second stall on
open, burning the complexity-based rate limit and holding one of only 4 shared
Linear concurrency slots (acquire/release) for the whole N*latency window.
Replace with a single rawRequest that fetches each comment's author inline
(first: 50, matching the SDK default page the code already relied on), the same
pattern the rest of this file uses. createdAt is passed through as the ISO
string rawRequest already returns (no re-serialization), and null avatarUrl is
normalized to undefined — output shape is unchanged.
Test asserts one request regardless of comment count and correct author
mapping (present user, null avatar, absent user).
Co-authored-by: Orca <help@stably.ai>
Every terminal.subscribe / terminal.multiplex slot registers a runtime
exit-waiter via waitForTerminal(condition:'exit'). With no AbortSignal that
waiter sits in waitersByHandle until the PTY actually exits — but agent
terminals routinely never exit for the life of a session. It is only ever
cleared by real exit or a desktop renderer graph reload (markRendererReloading
/ markGraphUnavailable), neither of which a remote/mobile WebSocket reconnect
triggers. So on long SSH/mobile sessions every reconnect and tab-switch
re-subscribe leaked a waiter, and each captured its closed-connection handler
context (including the dead ws), growing host-process memory monotonically.
- multiplex: give each stream an AbortController and abort it in detachStream
(the single teardown point, reached on slot unsubscribe, re-subscribe
pre-detach, and connection close via closeMultiplex). Passing its signal to
waitForTerminal removes the waiter at detach. The existing .catch no-ops
because the stream is already deleted (streams.get(streamId) !== stream).
- legacy json/binary subscribe: pass the per-connection dispatch signal so the
waiter is removed on socket close/error.
Regression test proves a signalled exit-waiter is released when its signal
aborts (and 25 reconnect churns leave zero waiters), while an unsignalled one
accumulates — the pre-fix behavior.
Co-authored-by: Orca <help@stably.ai>
* Fix terminal tab icon detection for foreground agents
- Register with bash-preexec's preexec_functions array to prevent its
DEBUG trap re-arming from silencing Orca's command-start signals.
- Serve the last-resolved identity past its cache TTL (stale-while-
revalidate) while the active foreground process is a wrapper.
- Reschedule confirming reads on duplicate OSC 133;D sequences to
prevent nested shells from prematurely clearing tab identities.
- Trigger self-limiting foreground sampling on visible PTY binding,
Enter keystrokes, and pane focus changes.
* Fix terminal shell integration and DEBUG trap chaining in bash
Chain external DEBUG traps (e.g., starship, bash-preexec) dynamically
in a prompt epilogue rather than using static hooks. This ensures our
own DEBUG trap survives re-arming by third-party frameworks and reliably
emits OSC 133 sequences.
Additionally:
- Print the shell-ready marker inside the precmd hook to avoid modifying
and displacing hooks at the end of PROMPT_COMMAND.
- Skip redundant foreground state checks in the PTY connection when a
shell prompt is already active and no background agent is expected.
* Fix terminal shell integration and DEBUG trap chaining in bash
Chain external DEBUG traps (e.g., starship, bash-preexec) dynamically
in a prompt epilogue rather than using static hooks. This ensures our
own DEBUG trap survives re-arming by third-party frameworks and reliably
emits OSC 133 sequences.
Additionally:
- Print the shell-ready marker inside the precmd hook to avoid modifying
and displacing hooks at the end of PROMPT_COMMAND.
- Skip redundant foreground state checks in the PTY connection when a
shell prompt is already active and no background agent is expected.
* Remove debug artifacts from codex icon investigation
Drop temporary diff, handoff notes, and screenshot evidence that were
accidentally committed during debugging.
* Mirror upstream bash-preexec so preexec dispatch test reflects real command
The naive $BASH_COMMAND imitation captured Orca's chained
__orca_osc133_epilogue instead of the user command, failing on CI.
Read the command from history like real bash-preexec does.
Co-authored-by: Orca <help@stably.ai>
* Fix promisify overload typecheck error in windowsHide wrapper test
The wrapper's static type (...args: unknown[]) => unknown makes
util.promisify resolve to its zero-arg callback overload, so calling the
promisified function with args tripped TS2554 under typecheck. Type the
promisified result to the variadic custom-symbol impl the wrapper copies
at runtime.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* Revert "Preload the daemon windowsHide shim via --require; wrap promisify custom (#7499)"
This reverts commit 8f396badaf.
* Revert "Hide console windows for children of the node.exe-hosted daemon (#7486)"
This reverts commit f0fdd3a716.
* Revert "fix(daemon): relocate daemon host image out of the install-dir kill zone (#7473)"
This reverts commit f4faafa987.
* Revert "fix(pty): keep runtime dirs a surviving daemon still uses (#7463)"
This reverts commit 3cd23a13a1.
* Revert "Relocate node-pty ConPTY runtime outside the Windows install dir (fixes update-time terminal loss) (#7421)"
This reverts commit 509c41e2bf.
* feat(native-chat): add runtime owner selector for panes (U1)
Co-authored-by: Orca <help@stably.ai>
* feat(native-chat): add session transport with runtime adapter (U2)
Co-authored-by: Orca <help@stably.ai>
* Add runtimeEnvironmentId to UseNativeChatLiveSessionArgs
Allows the native chat live hook to route read and subscribe requests
to a remote runtime host when a runtime environment ID is specified,
otherwise falling back to the local IPC path.
* feat(native-chat): route live-session hook through owner transport (U3)
Co-authored-by: Orca <help@stably.ai>
* feat(native-chat): select runtime owner in caller view (U4)
Co-authored-by: Orca <help@stably.ai>
* Improve native chat transport and add local Docker SSH VM scripts
- Clamp high pagination limits instead of rejecting to keep paging unstuck.
- Re-subscribe after mid-stream drops with a short backoff.
- Drop explicit unsubscribe RPCs as connection closure reaps watchers.
- Discard stale load-earlier resolves after transport or owner flips.
- Add Docker-SSH scripts and Dockerfile for local Orca VM workspaces.
* rm unrelated files
* Handle errors when loading older native chat history
- Catch and swallow rejected promises from IPC-backed history reads to
prevent unhandled rejections when a "load more" action fails.
- Document focus-containment check in the window-scoped paste bridge.
---------
Co-authored-by: Orca <help@stably.ai>
The rc.6 shim shipped broken twice over:
1. Bundler ordering: rollup's CJS output hoists chunk requires above
inlined module code, so daemon-entry's "first import" of the shim ran
AFTER sibling chunks evaluated `promisify(childProcess.execFile)` at
module scope. The shim is now its own self-contained bundle entry and
the daemon fork preloads it with `node --require`, which runs before
the module graph loads - immune to bundler ordering by construction.
2. promisify bypass: exec/execFile carry a util.promisify.custom
implementation that calls the ORIGINAL function internally; copying
the symbol verbatim onto the wrapper let every promisified call site
(exactly what the daemon's CIM probes use) skip the injection. The
wrapper now wraps each symbol-attached function with the same
windowsHide-default injection.
Verified on Windows against the staged production Node 24 binary with
canary-titled probes (attributable amid ambient rc.6 flashing):
plain execFile and promisify(execFile) both flash 100% of trials
without the preload and 0% with it; promisify(execFile) under the
preload resolves to the injection wrapper; built preload bundle has
zero chunk requires; daemon boots and serves getForegroundProcess RPCs
end-to-end under --require.
Removes 31 fully-orphaned source files with zero references anywhere in
the codebase, surfaced by knip static analysis and independently verified
(import-specifier grep across .ts/.tsx/.mjs/.cjs/.html/build configs,
transitive-cluster + basename-collision analysis).
Notable clusters:
- GitHub issue-comment composer + its close-reason dropdown/labels/popovers
(GitHubIssueCommentComposer and everything only it imported)
- Create-PR dialog components superseded by inline SourceControl logic
- right-sidebar Search/SearchHeader (unused search UI)
- two stale source-control-primary-* renderer duplicates (live logic moved
to src/shared/)
- CliAgentSkillSetup superseded by CliSection; its entry removed from the
AgentSkillSetupPanel governance test
Verified: typecheck (node/cli/web), oxlint, localization catalog+coverage,
full unit suite (24,581 tests), and electron-vite + web bundler builds all
pass with these files removed.
* 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.
Reject SSH relay PTY reattach when the persisted lease identity does not match the current relay PTY, and carry pane/tab identity through metadata so remote hook env stripping does not bypass the guard.
Also preserves live reused PTY ownership on identity mismatch so a stale lease cannot tombstone a fresh owner.
Since #7473 the terminal daemon runs under a standalone node.exe.
Electron's bundled Node defaults windowsHide to true; plain node.exe
defaults it to false, so every child_process call in the daemon that
does not pass the flag - the periodic PowerShell CIM process probes,
node-pty's kill-path conpty_console_list_agent fork - now allocates a
visible console, which opens and closes a Windows Terminal window on
the user's screen every few seconds.
Fix: daemon-entry installs a child_process shim (first import, before
any module captures bindings like promisify(execFile)) that defaults
windowsHide: true across spawn/exec/execFile/fork and their sync
variants, restoring the Electron default the daemon has always relied
on. Explicit windowsHide from a caller still wins. Also adds
windowsHide to node-pty's console-list agent fork in the existing
patch as defense in depth.
Verified on Windows: reproduced the flash with the rc.5 production
daemon (WindowsTerminal windows, ~3s cadence matching the CIM probe
interval, conhost spawned visible-capable "0x4"); with the shim, a
node.exe-hosted daemon's children (OpenConsole, powershell, node
helpers) all run without a visible-capable console and session kill
still works end to end.
The node-pty native runtime is relocated to a per-app-version dir under
userData so live terminals survive NSIS updates. On every launch,
removeStaleRuntimeVersions() deleted every *other* version's dir, gated
on a "renameSync succeeds => unused => safe to delete" heuristic.
That heuristic is false on Windows: running .exe images and mapped .dll
files are opened with FILE_SHARE_DELETE, so both the directory rename and
the recursive delete succeed while a daemon adopted across the update is
still using them. Existing PTYs keep streaming via deferred-delete
handles, but the next spawn re-loads conpty.dll from the now-unlinked
version dir and fails with ERROR_PATH_NOT_FOUND (code 3) -- the
"Cannot find conpty.dll ... error code: 3" new-tab failure.
Gate cleanup on real daemon liveness instead: collectInUseRuntimeVersions()
reads the daemon-v<N>.pid files under userData/daemon and, for each live
daemon, protects the appVersion (runtime dir) it recorded. Deletion now
skips the current version and any in-use version, dropping the rename
dance. Every failure mode leaks a dir rather than deleting one in use.
Rewrites the misleading cleanup test (which modeled no live daemon) and
adds coverage for surviving/dead daemons, appVersion:null pin files,
multiple protocol versions, and malformed/non-pid files.
Co-authored-by: Neil <neil@stably.ai>
* 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>
* Hide redundant current worktree info in workspace vault scope
When the AI Vault is scoped to the current workspace, showing the
"Current worktree" status line or badge is redundant since the sessions
are already filtered to this workspace.
- Add helper to hide the worktree line and status badge when the vault
is in 'workspace' scope and status is 'current'.
- Improve alignment and metadata layout in session rows using CSS grid.
- Update tests to verify worktree line and badge visibility rules.
* Indent AI vault session worktree line
Add left padding to the session worktree line to improve its visual
indentation inside the AI vault session row.
* 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>
* Harden rate-limit PTY cwd and abort background usage probes on stop
Resolve a runaway CPU incident where background Claude rate-limit usage
probes could inherit a root working directory (such as `/` or `C:\`) and
index the entire disk.
- Force hidden Claude and Codex usage PTYs to spawn inside a small,
dedicated directory under user data or temp.
- Thread AbortSignal through rate-limit fetchers to immediately kill
and reap in-flight PTY processes when RateLimitService stops.
- Harden daemon and local PTY defaults to reject root-like working
directories for automated agent startups.
* Consolidate PTY path safety checks to prevent runaway CPU
Centralize `isRootLikePath` into a dedicated PTY path safety module
to prevent terminal launches and hidden usage probes from executing
in filesystem or drive roots, avoiding unbounded file discovery.
- Centralize path validation supporting POSIX, Windows, and UNC roots.
- Fail loudly if no safe default working directory is available.
- Enforce bounded directories for hidden usage PTYs and WSL probes.
- Check for aborted signals during rate-limit CLI repairs.
* Gate agent startup on the effective PTY cwd after default fallback
Co-authored-by: Orca <help@stably.ai>
* test(repos): verify origin/HEAD target in SSH base-ref tests
Co-authored-by: Orca <help@stably.ai>
* Pin HOME to assert exact default PTY cwd in spawn test
Pin `process.env.HOME` in the fallback test for LocalPtyProvider to
assert the exact resolved candidate rather than just checking that it is
non-root-like. This catches potential regressions where an unintended
home directory is picked, and uses a try/finally block to safely restore
the environment afterwards.
---------
Co-authored-by: Orca <help@stably.ai>
* fix(worktrees): fall back from stale default base refs (#7312)
* docs(worktrees): explain base fallback policy
* fix(worktrees): harden stale base fallback edge cases
---------
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>
- Resolve true work item identity (issue vs. PR) using the URL path to
override stale or incorrect cached payload types.
- Prevent invalid PR start point resolution when launching an issue
misclassified as a PR.
- Validate and reject mismatched URL types in the worktree metadata
dialog fields to avoid incorrect associations.
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.
* feat(ghostty): import adjust-cell-height and dual-value window paddings
Two Ghostty config forms previously landed in unsupportedKeys:
- adjust-cell-height percentages (e.g. "35%") now map to
terminalLineHeight (1.35), the direct xterm equivalent. Pixel values
are still rejected because they depend on the rendered cell height.
- window-padding-x/y "top,bottom" / "left,right" pairs now import as
the rounded average, preserving the total padding along the axis.
parseStrictInt moves to numeric-config-values.ts alongside the new
pair parser to keep mapper.ts under the max-lines budget.
* fix(ghostty): tighten import edge cases
Co-authored-by: Orca <help@stably.ai>
* fix(settings): sentence-case account runtime copy
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* feat(ghostty): resolve theme references during config import
`theme = <name>` is how most Ghostty configs pick their colors, but the
importer treated the key as unsupported and silently dropped the entire
palette. Resolve the referenced theme file and merge its colors as
defaults, mirroring Ghostty's own precedence:
- Search user themes first (XDG, then the native macOS dir), then the
bundled themes inside the Ghostty install, and parse the file with the
existing parseGhosttyConfig (theme files are plain config subsets).
- Explicit config keys win over theme values; config palette entries are
appended after the theme's so per-index overrides apply naturally.
- Only color-bearing keys may flow from a theme into the import, a theme
file cannot smuggle font/window settings past the user's config.
- Theme names containing path separators are rejected (no traversal),
files over 256 KB are ignored, and light:/dark: pairs plus unresolvable
names surface as annotated unsupportedKeys instead of vanishing.
* fix(ghostty): align theme resolution with Ghostty lookup
Co-authored-by: Orca <help@stably.ai>
* test: stabilize ghostty import CI coverage
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Bare repo-backed terminal creates now reuse Settings agent defaults for exact agent commands while preserving raw commands, disabled agents, and already-managed launches.
Validation:
- PR Checks / verify passed
- Local runtime tests, typecheck, lint, CLI build, and Electron CLI/runtime smoke passed
* Seed WSL Codex runtime config through the fresh-mirror preparation
The WSL runtime home seed copied config.toml verbatim, so relative
path-valued settings (model_instructions_file etc.) resolved against the
runtime home inside the distro and aborted Codex config load with
'os error 2' -- the same failure class #7157 fixed for host runtime and
managed account homes. Seed now applies the shared fresh-mirror
preparation (deprecated codex_hooks normalization + relative-path rewrite
anchored to the Linux-side source home + system hook-trust strip).
Also fixes CRLF configs skipping deprecated codex_hooks normalization:
the feature-section header regexes did not tolerate the trailing \r.
Co-authored-by: Orca <help@stably.ai>
* Extract prepareWslRuntimeSeedConfig and pin UNC->Linux anchor in tests
The e2e seed test's mocked WSL home is a plain local path, so the
parseWslUncPath linuxPath branch was never exercised. Extract the seed
preparation as a pure exported function and cover both UNC spellings
(wsl.localhost and wsl$) with hardcoded literals.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
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(automations): start scheduler in headless serve mode
* Trigger initial catch-up check for headless scheduled automations
Headless serve never receives a renderer-ready IPC, which previously
prevented the initial catch-up pass of due scheduled automations from
running on startup. Checking for the presence of a headless dispatcher
allows starting the evaluation pass immediately.
Additionally, refactor the corresponding test to use fake timers for
better reliability and deterministic assertions.
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Creating a workspace hard-failed with "Could not refresh base ref …"
whenever the pre-create git fetch of a remote-tracking base (e.g.
origin/main) failed — offline, transient, or (common on Linux GUI) the
Electron process lacking SSH_AUTH_SOCK so `ssh -o BatchMode=yes` cannot
auth. This blocked creation even when a perfectly usable local
origin/main already existed and `git worktree add` from it would succeed.
Regression introduced by #2310 (5c93579e40), which replaced the prior
best-effort fetch + post-create reconcile with a hard network gate.
Fix: at all three create sites (local runtime, local IPC, SSH), only
throw when the refresh failed AND no usable local base ref exists;
otherwise create from the local ref (a possibly stale but valid base).
Also: move the SSH local-ref probe after session.registerRoot so relays
that gate generic git.exec don't false-negative and defeat the fallback.
Co-authored-by: Orca <help@stably.ai>
* fix: address pr-bug-scan validated finding from #6952
throwNodeNotFound() now re-raises AbortError when the shared signal is aborted, so a signal-cancelled node probe no longer launders into 'Node.js not found'; sequential fallback runs.
* fix(ssh): make session-limited (MaxSessions=1) relay deploys actually succeed
Review of #7180 verified the parent fallback end-to-end against a real
MaxSessions=1 sshd and found the connect still failed. Four gaps, in order
of discovery:
- isSshSessionLimitError missed stock OpenSSH, which refuses session
channels over MaxSessions with SSH2_OPEN_CONNECT_FAILED (2) and 'open
failed' — reason 4 never matched, so the fallback never triggered.
- execCommand settled aborted commands before the channel finished
closing, so the sequential fallback reissued execs while sshd still
counted the old session.
- SshConnection.waitForSshCallback rejected aborts mid-channel-open
immediately, leaking a confirmed-late channel that held the only
session slot; it now settles after the late channel closes (bounded)
and drains its streams so ssh2 emits 'close'.
- Session channel opens now retry transient session-limit refusals
(sshd frees the slot only after processing our close-ack, which the
next open can beat by microseconds), and the remote orca CLI shim
install is non-fatal like the managed-hook install — after the relay
bridge occupies the sole session slot, raw-connection extras must
degrade instead of failing the connection.
Verified live against Docker sshd (OpenSSH 9.2, MaxSessions=1): fresh
deploy (upload + native deps + launch), reconnect cycles, and a PTY
round-trip all succeed; unrestricted-sshd regression run also passes.
Co-authored-by: Orca <help@stably.ai>
* Handle ssh execution aborts immediately during retry backoff or hangs
- Cancel the session-limit retry delay immediately if the operation is
aborted during backoff.
- Limit the wait time to a 5-second grace period when aborted during a
channel open that is hung and never invokes its callback, rather than
waiting for the full connection timeout.
---------
Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
- Prevent the "Stage All" button from being enabled when only nested
submodule worktree changes are present.
- Remove the disabled stage button on submodule worktree-only rows,
replacing it with an explanatory tooltip.
- Fix WSL terminal environment assertion in daemon PTY tests to allow
inherited agent-hook environment variables.
Quality pass on main-process/build PRs merged 2026-07-03:
- ios-emulator-backend: resolve the serve-sim executable via a lazily-cached getter
instead of eagerly in the constructor. The bridge is built before the main window
is shown, so the one-time recursive copy + xattr subprocess (first launch after each
version bump) no longer blocks macOS startup for a feature that may go unused (#7174).
- index: collapse the two near-identical `{webContentsId, until}` reload flags
(expectedRendererReload / recoveryReloadInFlight) into one `createWebContentsTimedFlag`
primitive; behavior preserved, including consume-on-read for the recovery reload (#7290).
- check-reliability-gates: coerce gate.commands/testFiles/platforms/providers with an
`asArray` helper before `.includes`, so a hand-edited manifest with a missing/mistyped
field reports a validation failure instead of throwing an uncaught TypeError; extract
`hasCompleteRedGreenEvidence` for the duplicated status check (#7295).
- claude-pty: derive FABLE_WEEKLY_LABEL_RE from WEEKLY_RE.source so a future weekly-
wording change stays in one place and can't reopen the parsing gap it just closed.
- macos-tcc-login-shell: trim the 30-line flag-by-flag JSDoc to the two non-obvious whys
(TCC identity, env(1) SHELL re-assertion) per the repo comment guidance (#7003).
Typecheck, oxlint, oxfmt, `check:reliability-gates`, and touched unit suites all pass.