* feat(ssh): support file transfers over system ssh
* fix(ssh): harden system file transfers
* test(runtime): stub getRepo in headless mobile tab cwd test
The mobile-session selector validator (getValidatedExplicitWorktreeIdSelector,
from main) calls this.store?.getRepo to reject repo ids passed as worktree ids.
The store stub only implemented getWorkspaceSession, so the guarded call threw
'getRepo is not a function' once main merged into this branch. Add a getRepo
that returns null (wt-1 is a worktree, not a repo).
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* 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>
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules
Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day
minimum-release-age supply-chain guard; nothing here needs it). The
bump is a no-op on the existing config.
Enable 3 error rules (backlog autofixed to zero in this commit) and
4 warn rules (surface signal without gating CI):
error (autofixed, behavior-preserving):
- unicorn/prefer-node-protocol (~1531 sites: bare builtin -> node:)
- typescript/no-import-type-side-effects (~36: all-inline-type -> import type)
- unicorn/no-array-reverse (19: copy-then-reverse -> toReversed)
warn (real signal, current fires are test-only/correct):
- unicorn/no-array-fill-with-reference-type (aliasing footgun guard)
- typescript/no-unsafe-function-type (bans bare Function type)
- unicorn/prefer-array-flat-map (map().flat() -> flatMap())
- unicorn/prefer-regexp-test (.match() in bool ctx -> .test())
mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix
ran from root and covered mobile/ too.
Verification (all green): oxlint 0 errors (root+mobile+aux configs),
oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed,
builds (electron-vite + web + cli) succeed. node: rewrites confirmed to
skip embedded SSH/CLI string payloads (AST-only); all toReversed sites
verified to operate on fresh copies or write-once locals.
* chore(lint): bump mobile oxlint to 1.71 so inherited rules parse
mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which
lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since
mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile &&
oxlint' failed to parse the new rule. Bump mobile to match root (1.71).
Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit
pass, vitest 978 passed / 0 failed.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* feat: add windows ssh relay base support
* feat: support windows ssh relay runtime services
* fix: default windows ssh pty cwd to user profile
* fix: support windows hosts over system ssh
* fix: preserve degraded windows relay native deps
* fix: gate windows shell args by relay platform
* fix: preserve windows relay fallback pipes
* test: align windows native deps relay fixture
* fix: build valid windows install lock command
* fix: address windows SSH relay review findings
Resolve correctness, efficiency, and reuse issues found reviewing the
Windows SSH native-host support:
- GC liveness on Windows now probes the actual named pipe (via node
net.connect against markers + deterministic candidates) instead of
substring-matching Win32_Process command lines, which could remove a
live relay dir. Reports ALIVE conservatively only when there is no
liveness signal at all (no markers and no seed pipes).
- Resolve the remote node path once per deploy and thread it through
install/repair/launch instead of re-resolving 3-7x.
- Replace the 200ms node -e poll loop with a single long-lived remote
wait process during Windows relay startup.
- Skip the no-op executable command on Windows in uploadRelay.
- Make the Windows fallback pipe name deterministic and recoverable
(drop the global counter), with an extra reconnect attempt.
- Normalize the prepended node bin dir to backslashes on Windows PATH.
- Batch the system-SSH Windows directory upload into a single streamed
JSON package instead of one ssh process per file.
- Extract relay endpoint/marker helpers into ssh-relay-endpoints.ts and
consolidate the PowerShell EncodedCommand encoding into the shared
powershell-command-encoding module.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Support cancellation and timeouts in Windows port scanning
- Propagate the request AbortSignal and a 5-second timeout to both
PowerShell and netstat child processes during Windows port scanning.
- Avoid spawning the netstat fallback process if the port scan has
already been aborted.
- Wrap the .NET OSArchitecture check in a try/catch block during SSH
Windows platform detection to robustly fall back to environment
variables if needed.
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>