Commit Graph
29 Commits
Author SHA1 Message Date
Neil f116d2ca2a test(ci): retry Windows teardown EPERM and restart evaluate misses (#17780)
Restart-survival polls treated a recycled renderer as a hard failure.
Wrap those evaluates so "Execution context was destroyed" is a pending
miss. Windows package-lane teardowns after a force-kill used rmSync
with force:true only, which does not absorb EPERM; put them on the
shared maxRetries:8 policy.
2026-08-31 18:53:01 -07:00
Neil a381b47437 test(cursor): widen Windows hook spawn budget to fix ETIMEDOUT flake (#17721)
* test(cursor): widen Windows hook spawn budget to fix ETIMEDOUT flake

`package (windows)` failed once on an unrelated packaging PR with
`spawnSync cmd.exe ETIMEDOUT` at hook-service.test.ts:78. This is an
infrastructure-timing flake, not a logic race: the assertion is
`expect(result.error).toBeUndefined()` and `ETIMEDOUT` only means the
spawnSync `timeout` elapsed.

Cursor is the heaviest of the hook-service suites on Windows. Its managed
command is the PowerShell encoded launcher, so one hook run is
cmd.exe -> powershell.exe -> cursor-hook.cmd -> curl.exe: four process
creations, one of them a CLR start that installer-utils.ts itself
documents as ~300ms warm and "visibly slow". The sibling suites (codex,
grok, agent-hooks/installer-utils) spawn the .cmd directly and set no
per-spawn timeout at all, so 15s here was a one-off, not a convention.

Raise the per-spawn budget 15s -> 30s to match
WINDOWS_PROCESS_TEST_TIMEOUT_MS in src/shared/setup-agent-sequencing*.test.ts
and the 30-90s used by the real-subprocess tests in src/main/browser. 30s
is ~30x the warm cost of the chain, which leaves room for CPU contention
and Defender scanning of the freshly written .cmd on a packaging runner.

The default vitest testTimeout is also 30s, which would have become the
new binding constraint (the protocol case runs 16 chains back to back), so
give the four spawning cases 120s. That keeps ETIMEDOUT - which names the
stuck process - as the failure you see, instead of an opaque case timeout.

No product behavior changes and no end-to-end coverage of the Windows
launcher is removed.

* fix: halve the case timeout and correct the contention rationale

Review found the stated cause wrong. pr.yml runs "Test Windows-specific
boundaries" before "Build package inputs", so electron-builder is not
running. The real contender is that vitest invocation itself: ~25 files at
maxWorkers 4, including five real-Electron suites and two node-pty tests.

120s was over-provisioned. windows-hook-payload-delivery.test.ts drives the
identical PowerShell chain on the same job with a 60s case budget; 60s gives
the same property here (16 warm spawns plus one 30s outlier) and halves
time-to-signal on a genuinely stuck chain.

Also record that 30s deliberately exceeds the product's own
MANAGED_HOOK_TIMEOUT_SECONDS (10s) — this test gates launcher correctness,
not user latency, so the SLA is not the right bound. Left
windows-hook-payload-delivery.test.ts at 15s: its value is deliberate, set
to mirror Claude Code abandoning a hook at 10s.
2026-08-31 13:22:12 -07:00
Brennan Benson 928d306b53 fix(agent-hooks): stop the Windows hook launcher spelling the AV-denied flag pair (STA-5237) (#16739)
* fix(agent-hooks): stop the Windows hook launcher spelling the AV-denied flag pair (STA-5237)

`-WindowStyle Hidden` + `-EncodedCommand` is denied at CreateProcess by
Kaspersky on Windows 11, whatever the payload decodes to. Bash reports it as
`Permission denied` and every managed hook event fails, so agent status never
arrives; the parent shell also briefly cannot spawn anything afterwards, so a
denied hook can take the user's next command down with it.

Measured on the reporting host (#16003), with a harmless `exit 0` payload:

  -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -EncodedCommand  126
  -NoProfile -WindowStyle Hidden -EncodedCommand                          126
  -WindowStyle Hidden -EncodedCommand                                     126
  -NoProfile -EncodedCommand                                              0 (5/5)

#16576 removed `-ExecutionPolicy Bypass`, which is the one flag of the three
NOT in the signature, so hooks kept failing after that fix. The pair that has
to stop being spelled is `-WindowStyle Hidden` + `-EncodedCommand`.

Because the change is to the shared switch constant, it covers every site that
spells the denied pair in one edit: Claude via `wrapWindowsPowerShellEncodedCommand`,
gemini/cursor/droid/command-code/copilot via `wrapWindowsHookCommand`, the
`runtime-home-hook-command` unsafe-HOME fallback, and the spaced-path fallback
for codex/grok/devin/antigravity. Only a flag is removed, so parser and payload
compatibility is unchanged for every executor: the string is still a PowerShell
command line, still one self-contained token, still base64-shielded.

The tradeoff, recorded rather than hidden: `-WindowStyle Hidden` was the shipped
fix for #14815 (+#14828, #15117, #15447, #15767), and this removes it. Its
suppression was never measured — #14825 confirmed it visually, #16576's author
stated it "remains unverified on a real box", and #15506's author argued it
cannot help a `.cmd` child with no console to inherit. The console is allocated
by the parent chain, not by this command line. A live window measurement is
still outstanding and is called out in the PR.

Also adds `windows-hook-payload-delivery.test.ts` to the PR CI Windows leg,
which had never run it.

* test(agent-hooks): keep launcher token out of source grep
2026-08-27 10:48:53 -07:00
Brennan Benson f352e3e27d fix(cursor): emit Cursor-contract JSON from managed hooks
Merge rebased conflict repair after exact-head tests, typecheck, lint, format, and all required GitHub checks passed.
2026-08-27 00:25:31 -07:00
Brennan Benson aaef5e8c9f fix(agent-hooks): deliver hook events that fire while Orca is restarting (STA-5329) (#16685)
* fix(agent-hooks): correct durable spool delivery

* fix(agent-hooks): spool curl failures after retries

* fix(agent-hooks): keep replay out of runtime observations

* test(agent-hooks): pin managed hooks inert outside an Orca terminal

* fix(agent-hooks): address review findings on the durable spool

- claude: pass the literal source; options.agent does not exist (typecheck)
- kimi: the windows-local ordering runs its guard pre-stdin and before the
  function exists, so it no longer spools there (printed command-not-found)
- writer: require a readable endpoint file before creating a spool tree
- antigravity: carry its out-of-band event name into the record and filter on it
- drain: truncate only the bytes consumed, preserving concurrent appends and a
  torn trailing line

* fix(agent-hooks): ignore spool events without pane attribution

* fix(agent-hooks): make spool replay and appends robust

* test(agent-hooks): type spool replay records

* fix(agent-hooks): defer unterminated spool records

* fix(agent-hooks): replay spool events through relays

* fix(agent-hooks): preserve Codex prompt across child replay

* fix(relay): keep startup alive when spool replay fails

* fix(relay): simplify spool replay startup guard
2026-08-26 23:17:34 -07:00
Neil c1a7748267 fix(agent-hooks): stop the Windows hook launcher spelling the AV-denied flag triple (#16576)
* fix(agent-hooks): stop the hook launcher spelling the AV-denied flag triple

Orca's Windows agent-hook launcher ran

  powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden \
    -EncodedCommand <base64>

That exact combination is the textbook "hidden encoded PowerShell" malware
shape, and endpoint security denies it at process creation whatever the
payload decodes to -- even `exit 0`. Every injected hook then failed with
`powershell.exe: Permission denied` (exit 126 from bash's execve, EACCES)
on every turn, for both Claude Code and Codex, with no AV exclusion that
re-enabled it.

Dropping any one of the three flags clears the signature. `-ExecutionPolicy
Bypass` is the one that can move: it sets the Process scope, and so does
`Set-ExecutionPolicy -Scope Process`, which now rides inside the encoded
payload. `-EncodedCommand` is never policy-gated, so the bypass always gets
to run before the managed script does -- which is what keeps Copilot's .ps1
hook working under a Restricted or AllSigned machine policy.

The hidden window and the encoding are unchanged, so nothing regresses for
#14815, #14818 or #6078.

Closes #16003

* fix(agent-hooks): ship the launcher shape #16003 actually measured as allowed

The previous revision of this branch dropped only `-ExecutionPolicy Bypass`
and kept `-WindowStyle Hidden -EncodedCommand`, on the reasoning that
"dropping any one of the three flags clears the signature". That sentence is
not in the bisect. The reporter ran exactly four command lines on the affected
Kaspersky/Windows 11 host:

  -NoProfile -WindowStyle Hidden -Command 'exit 0'                       -> 0
  -NoProfile -EncodedCommand <b64>                                       -> 0
  -NoProfile -ExecutionPolicy Bypass -Command 'exit 0'                   -> 0
  -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -EncodedCommand -> 126

Every passing row drops two flags. No row drops exactly one, so the shape the
branch was about to ship had never been executed on the machine that reports
the bug -- and it is `-WindowStyle Hidden -EncodedCommand`, which is the
"hidden encoded PowerShell" pair the denial is named for in our own comment.
Shipping it would have closed #16003 while leaving every hook on that host
dying at CreateProcess, with no tracking left open.

So emit the measured-passing encoded row instead: `-NoProfile -EncodedCommand`.
Of the two flags there was a choice between, `-EncodedCommand` is the one that
carries correctness -- it is what keeps paths and switches intact across
cmd.exe and MSYS (#6078, #14815). `-WindowStyle Hidden` costs at most a console
flash, and only where the parent has no console to inherit.

Second, the relocated bypass now runs inside try/catch. Under a MachinePolicy
or UserPolicy GPO scope, `Set-ExecutionPolicy -Scope Process` reports that the
process scope did not take. `-ErrorAction SilentlyContinue` covers only the
non-terminating half of that; the command-line switch it replaces was silent
either way. This file already documents that non-stdout PowerShell streams
corrupt consumers merging our output into JSON stdout, so a per-invocation
ErrorRecord on stderr is a regression we should not trade for the switch.

Refs #16003

* fix(agent-hooks): keep the hook console hidden while dropping the AV-denied flag

Round 2 of this PR widened the fix from "stop spelling -ExecutionPolicy Bypass"
to "stop spelling it and -WindowStyle Hidden", on the reasoning that the #16003
reporter never measured a shape that drops exactly one flag, so keeping the
hidden+encoded pair would be extrapolation.

That trades a reproduced regression for an unmeasured one. Window suppression is
the shipped fix for #14815 and its four duplicates (#14828, #15117, #15447,
#15767): a hook launched from a parent with no console gets a fresh console per
event, which takes foreground and eats whatever the user is typing into Orca,
and never closes at all on the stdin-blocking path hook-stdin-contract.ts exists
to guard. That fires on every prompt, tool call and stop of every managed agent.
The AV denial, by contrast, is measured only for the full triple; that the
remaining pair still trips it is a hypothesis. Between a certain regression and
a possible one, keep the certainty.

So the flag that leaves the command line is the policy bypass alone — the only
one of the three with an exact in-payload equivalent, hence the only one that
can move without losing behaviour. If the pair turns out to be denied too, the
answer is a different shape that still hides the window.

* fix(agent-hooks): silence progress before the policy bypass can autoload (#16621)

Hardware-measured on Windows 11 while exercising #16576.

Set-ExecutionPolicy autoloads Microsoft.PowerShell.Security, and that module's
"Preparing modules for first use." progress record is written before any later
assignment can suppress it. Running the bypass first therefore defeated the
silencer that runs immediately after it:

  bypass-first    stderr = 616 bytes, first merged line '#< CLIXML'
  silencer-first  stderr = 0 bytes,   first merged line '{"decision":"approve"}'

That is precisely the corruption HOOK_PROGRESS_SILENCER's own comment warns
about -- redirected progress becoming CLIXML that can corrupt merged JSON -- so
the PR reintroduced the hazard it documents, one line below documenting it.

Both existing tests asserted the broken order, so they enforced the bug rather
than catching it. Reordered them and added one that pins the ordering itself
rather than the literal string, since the string will drift again.
2026-08-26 14:08:58 -07:00
OrcaWinandOrcaWin 02ba70a847 fix(agent-hooks): make the Windows managed hook survive Claude-hooks-compat consumers (#14825)
* fix(agent-hooks): make the Windows managed hook survive Claude-hooks-compat consumers

`~/.claude/settings.json` is not read only by Claude Code. Third-party
Claude-hooks-compat layers (cursor-agent, Devin) import the same file and
reimplement hook execution, so Orca's entry has to survive consumers that
support strictly less than the documented schema. Three separate defects
came from assuming otherwise.

1. The entry depended on `args`, which a compat consumer ignores.
   `args` is valid Claude Code syntax, but cursor-agent spawns `command`
   alone -- so `conhost.exe` ran bare, which opens an interactive console
   that never closes. Hook payloads were typed into those stranded shells
   (#14815). The entry is now one self-contained `command` string that
   depends on nothing optional.

2. `conhost.exe --headless` never relayed anything. It implements the
   ConPTY server protocol, not a generic no-window wrapper: it does not
   wait for the hosted process and relays neither exit code nor stdout.
   Measured directly -- `conhost --headless cmd /c "echo X& exit /b 42"`
   yields empty stdout and no exit code, while the replacement returns
   both and waits. So every hook was fire-and-forget, and whatever it
   printed was discarded. Replaced with `-WindowStyle Hidden`, which
   suppresses the window and keeps wait/exit-code/stdout intact.

3. The hook never wrote anything to stdout. Guards exited silently and
   curl's output went to nul. Claude Code documents empty stdout as "no
   decision", but cursor-agent treats PreToolUse as a permission gate,
   fails to parse empty stdout as JSON, and blocks the tool call -- so
   every shell command in every cursor-agent session on Windows failed
   (#14818). The script now writes `{}` first, on both the Windows and
   POSIX branches, which is documented to be identical to writing nothing
   for real Claude Code. Gemini and Antigravity already did this.

Defects 2 and 3 are causally linked: `{}` cannot reach any consumer while
conhost is swallowing stdout, so neither fix works without the other.

Also fixed while establishing the contract:

- The launcher's own missing-script fallback returned empty stdout,
  reproducing #14818 whenever `~/.orca` was cleaned or an install was
  half-finished. It now emits `{}` too.
- PowerShell serializes progress records to stderr as CLIXML when stderr
  is redirected; a consumer merging stderr into stdout would see those
  bytes before the JSON. Every encoded payload now silences progress.
- `runtime-home-hook-command.ts` built its own launcher without window
  suppression -- exactly the drift #14815 asks to prevent. All launcher
  construction now goes through `windows-powershell-hook-launcher.ts`, so
  the switch list cannot be present in one installer and missing in
  another.
- Renamed `usesWindowsHeadlessHook` to `usesWindowsPowerShellLauncher`;
  nothing is headless anymore, and the flag selects a launcher.

Testing: the new regression test asserts the effect a consumer observes
-- it runs the exact `command` string from settings.json through both
cmd.exe and Git Bash, across the guard-exit, reached-curl, and
missing-script paths, and parses stdout. Verified it fails when
`conhost --headless` is reintroduced. The previous tests all asserted
installer intent, which is why they passed through all three defects.

* fix(agent-hooks): close hook launcher review gaps

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-16 20:48:26 -07:00
NeilandOrca 971d9548b5 docs(agent-status): make design references self-contained (#13902)
docs/design/agent-status-over-ssh.md was cited from ~10 source files but
does not exist in the repo. Replace each pointer with the invariant the
code actually relies on so the knowledge survives without the doc.

Renderer-side citations (useIpcEvents.ts, agent-status-types.ts) are left
for the concurrent batching change that owns those files.

Co-authored-by: Orca <help@stably.ai>
2026-08-11 22:13:35 -07:00
Brennan Benson 2ee43bfc0d fix(agent-hooks): refresh existing Orca launchers when agent CLIs are unavailable (#13378)
* fix(agent-hooks): refresh existing shared hook scripts when the CLI is no longer detected

A CLI that falls off PATH (moved npm prefix, relocated shim) keeps its user-wide
config invoking Orca's launcher script under ~/.orca/agent-hooks, but the
presence gate skips install() with no removal — freezing the script at whatever
Orca generated last. Anyone in that state kept the pre-#11568 more.com-leaking
.cmd forever, because no launcher script is ever deleted and Windows startup
deliberately skips shell PATH hydration.

Reconcile before gating: every existing shared launcher/statusline script is
rewritten to the current template on each install pass. Creating scripts stays
behind the presence gate — an existing file is proof of a prior install; a
missing one means the gate did its job. Amp and Hermes are deliberately absent:
they write provider-native plugin code with its own install lifecycle, not
shared launchers.

- refreshManagedScriptIfPresent() in installer-utils (no-op unless the file exists)
- refreshManagedScripts() on the 11 launcher-writing services (openclaude via
  the shared Claude class)
- reconcile pass in installManagedAgentHooks before presence detection,
  filtered by the agents option, best-effort per agent
- coverage gate: a launcher written to ~/.orca/agent-hooks without a matching
  refresher entry fails the suite, in both directions

* perf(agent-hooks): refresh launchers off the main thread

* test(agent-hooks): keep refresh mode assertion POSIX-only
2026-08-10 16:34:15 -07:00
Jinjing 05206046f6 chore: condense code comments (#12008)
* chore: condense code comments

* chore: shorten more code comments

* clarify PTY agent session descendant cleanup behavior

Refine the comment on ptyAgentSessionIds to more accurately describe
when agent sessions sweep their descendant process trees and note the
exception on immediate Windows shutdown.
2026-08-01 14:24:31 -07:00
nolainjin 17fc40eae6 fix(hooks): drain POSIX hook stdin without PATH (#10885)
Reviewed with an independent reproduction on macOS and Ubuntu 20.04. Fixed five test files that asserted the old literal strings, and switched the reader to `command -p cat` so it also survives hosts without /bin/cat.
2026-07-27 15:41:23 -07:00
NeilandOrca 190de8223e refactor(comments): slim verbose comments in main integrations (git/providers/…) (#9543)
Collapse multi-line explanatory comment blocks into single-line "why" statements
per AGENTS.md ("Document the Why, Briefly"): drop restatements of the code and
mechanism narration; keep the non-obvious reason, external refs, and directives.

Comments-only — verified no code changed via a Babel/esbuild comment-strip
token-equality gate against origin/main; typecheck and oxlint clean.

Area: main — git, source-control, providers & integrations. 40 files changed, 1432 insertions(+), 4473 deletions(-).

Co-authored-by: Orca <help@stably.ai>
2026-07-20 03:18:28 -07:00
Kaynan Sampaio de CamargoandJinjing c3ab805d12 fix(agent-hooks): drain stdin before hook script early exits so agents never hit EPIPE (#8430)
* Fix hook scripts to drain stdin before any early-exit path

Generated agent hook scripts and missing-script launchers could exit
successfully before consuming the payload written to their stdin,
leaving the writer with a broken pipe (EPIPE/ERROR_BROKEN_PIPE) once
the reader closed early. Capture stdin (or drain it via a shared
epilogue/fast-path guard) before any whole-script success exit across
all POSIX, batch, PowerShell, and Git Bash launcher variants, and add
a cross-agent lifecycle test suite plus a live Electron verification
script to guard the contract going forward.

* Harden hook scripts against unreadable managed scripts and add a Claude/

- Extend the POSIX launcher guard to also require `[ -r ]`, not just `-f`/`-x`,
  so an executable-but-unreadable managed script still drains stdin instead of
  erroring or silently misbehaving.
- Add a verifier case (`verifyClaudeDevinSkip`) that spins up a local HTTP
  server and confirms the Claude hook never forwards a request that Devin
  already imported, catching accidental double-forwarding.
- Update installer-utils tests and stdin-lifecycle docs to match the new
  readable-file guard and the added verification case.

* Fix hook-launcher verification to derive script paths from the installed

Extract the quoted path from the launcher's `if [ -f '...'` clause instead of
reconstructing it via join(home, ...), so missing/failing-script test cases
can't silently fall through to the real script if the install layout changes.

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-07-13 00:26:22 -07:00
4797cf81a4 fix(agent-hooks): stream hook payload to a temp file instead of inlining it on the curl command line (#4475)
The POSIX agent-hook script for every curl-based agent inlined the full
event payload via `curl --data-urlencode "payload=${payload}"`. Tool
output can be tens of KB, so the resulting process command line could be
multi-KB — which endpoint security tools (e.g. Microsoft Defender for
Endpoint) flag as an oversized/suspicious command line. That produced a
false-positive detection on Orca's own loopback (127.0.0.1) telemetry POST.

Stream the payload to an mktemp file and post it with
`--data-urlencode "payload@$payload_file"` instead. The urlencoded body on
the wire is byte-identical, so the agent-hook receiver is unchanged; the
payload simply never appears on a process command line. `trap ... EXIT`
removes the temp file on every exit path. Small bounded metadata fields
(paneKey/tabId/worktreeId/env/version) stay inline.

Applied to all curl-based agents: claude, codex, command-code, copilot,
cursor, droid, gemini, grok, antigravity. (amp/hermes/opencode post via
the HTTP request body and were never affected.) The Windows post-command
shares the same latent pattern and is flagged as follow-up.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 16:26:53 -07:00
NeilandOrca 46646d7ff1 chore(lint): upgrade oxlint to 1.71 + enable 7 new rules (autofixed backlog) (#6841)
* 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>
2026-06-29 22:38:29 -07:00
Brennan BensonandNeil b6ac7b9c92 Harden Windows hook command resolution (#6409)
* Keep spaced Windows Codex hook paths fast

* Harden Windows hook command resolution

---------

Co-authored-by: Neil <neil@stably.ai>
2026-06-25 22:43:25 -07:00
Brennan Benson e03a1cf769 Prevent managed agent hooks from hanging (#6148) 2026-06-23 17:22:10 -07:00
Jorge SilvaandJinwoo Hong 1fde7f553c fix(agent-hooks): wrap Windows hook commands in cmd.exe to survive spaces in profile path (#6078) (#6083)
* 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>
2026-06-22 16:04:01 -07:00
Brennan BensonandOrca fe8fe16c83 Preserve agent settings when sleeping sessions resume (#5916)
Co-authored-by: Orca <help@stably.ai>
2026-06-21 16:44:58 -07:00
0ec3882cb8 Add project Windows runtime selection (#5519)
* Add project Windows runtime selection

* Fix project Windows runtime selection

Co-authored-by: Orca <help@stably.ai>

* fix: preserve WSL shell variables

---------

Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Neil <neil@stably.ai>
2026-06-17 16:08:14 -07:00
Jinwoo Hong da98a93089 Fix cursor-agent notification spam on shell tool hooks (#4835) 2026-06-07 15:24:15 -07:00
Neil d1b9392b49 perf: cap generated hook posts (#4166)
Generated agent hook POSTs now use short curl and PowerShell request timeouts so best-effort status hooks cannot hold agent subprocesses open on a stalled local listener.
2026-05-31 06:40:02 -07:00
Neil 675b1b0cb6 Fix Windows agent hook Unicode prompts (#2100) 2026-05-16 13:32:23 -07:00
Neil 6a960a855e Add Cursor Agent hook installer coverage 2026-05-15 21:31:43 -07:00
Brennan BensonandOrca 2cadcbc4e1 Support agent status hooks over SSH (#1865)
Co-authored-by: Orca <help@stably.ai>
2026-05-14 16:06:23 -07:00
Brennan BensonandOrca 727226fed9 fix(agent-hooks): write managed hook script to shared ~/.orca path (#1546)
Co-authored-by: Orca <help@stably.ai>
2026-05-14 11:57:48 -07:00
Brennan BensonandOrca c375f403ef fix(agent-hooks): silence exit-127 spam from stale managed hook entries (#1536)
Co-authored-by: Orca <help@stably.ai>
2026-05-07 13:05:30 -07:00
Brennan BensonandOrca 4279fc8075 feat(agent-hooks): on-disk endpoint discovery for surviving PTYs (v2) (#1196)
Co-authored-by: Orca <help@stably.ai>
2026-04-27 21:49:18 -07:00
NeilandOrca ae9ddeab5c feat(cursor): first-class Cursor CLI agent status via hooks.json (#1108)
* feat(cursor): first-class Cursor CLI agent status via ~/.cursor/hooks.json

Give cursor-agent the same hook-driven status pipeline Claude/Codex/Gemini/
OpenCode already use so working/done/permission transitions show the proper
sidebar spinner instead of falling back to title heuristics — cursor-agent
only sets its OSC title to the literal string "Cursor Agent" during a turn,
so title-based detection cannot see working→done transitions on its own.

- New CursorHookService installs a managed shell script and registers it
  under ~/.cursor/hooks.json for beforeSubmitPrompt, preToolUse, postToolUse,
  postToolUseFailure, beforeShellExecution, beforeMCPExecution, stop, and
  afterAgentResponse (the subset that marks turn boundaries and surfaces
  in-flight tool context).
- AgentHookServer gains a /hook/cursor route and a normalizeCursorEvent
  mapping the camelCase cursor events to working/done/waiting, with tool
  previews for preToolUse/shell/MCP and lastAssistantMessage from
  afterAgentResponse. stop with status != "completed" surfaces as
  interrupted (matches Claude's is_interrupt behavior).
- cursorHookService joins the startup install loop alongside Claude/Codex/
  Gemini, and the IPC status handler is exposed via preload.
- 'cursor' added to WellKnownAgentType and to the renderer's
  WELL_KNOWN_LABELS so the dashboard prints "Cursor" rather than the raw
  'cursor' id.

Verified end-to-end against a real cursor-agent 2026.04.17-787b533 binary:
a mock hook receiver pointed at by ~/.cursor/hooks.json observes
beforeSubmitPrompt → stop for a live turn.

Co-authored-by: Orca <help@stably.ai>

* feat(cursor): wire hook events into sidebar spinner + unread pipeline

The initial hook wiring landed behind AGENT_DASHBOARD_ENABLED, which is
still false. That meant cursor-agent panes lit up no spinner and no
unread indicator — cursor's native OSC title stays literally "Cursor
Agent" across a turn, so title-based detection cannot transition.

Plumb cursor's hook stream into the existing, shipped title-tracker
pipeline (the one Claude/Codex/Pi drive working/idle/unread off) by
synthesizing OSC title sequences in the main-process hook listener:

  - `working`  → `\x1b]0;⠋ Cursor Agent\x07`   (braille prefix → working)
  - `waiting`  → `\x1b]0;Cursor - action required\x07\x07`
  - `done`     → `\x1b]0;Cursor ready\x07\x07`

The two trailing BELs on done/waiting are load-bearing: the unread badge
keys off BEL (0x07 outside any OSC), and cursor-agent emits none on its
own. The first BEL is consumed as the OSC terminator; the second fires
the bell detector.

Also treats the bare native "Cursor Agent" title as a no-op in
`detectAgentStatusFromTitle` so cursor's own per-turn re-emissions cannot
stomp our synthesized working state back to idle. `isClaudeAgent`
excludes cursor-bearing braille titles so the Claude prompt-cache timer
doesn't fire for cursor panes.

Scope: the hook server + cursor install run unconditionally now, but
Claude/Codex/Gemini installs stay gated behind AGENT_DASHBOARD_ENABLED,
so only cursor events flow through the pipeline. No dashboard surface
is turned on.

Verified end-to-end in Electron (dev build): launched cursor-agent via
the Cursor menu item, submitted three prompts (including a tool-use
turn reading package.json). Observed tab title flip to "Cursor ready"
on done, and the tab + worktree both transitioned to unread
(unreadTerminalTabs[tabId]=true, worktree.isUnread=true). Parallel
Claude Code pane untouched.

Co-authored-by: Orca <help@stably.ai>

* fix(cursor): animate spinner frames + filter bare native title so the spinner doesn't go solid mid-turn

cursor-agent re-emits its bare "Cursor Agent" OSC title on every internal
redraw, which was stomping the single synthesized "⠋ Cursor Agent" frame
in runtimePaneTitlesByTabId within milliseconds and flipping the sidebar
dot back to solid. Two-part fix:

- Main: drive an 80ms Pi-style braille spinner from the cursor hook
  channel, keyed by paneKey, torn down on pty exit via a new
  registerPaneKeyTeardownListener hook in ipc/pty.
- Renderer: drop bare "Cursor Agent" titles in pty-transport so cursor's
  native re-emissions cannot overwrite the synthesized working/idle
  titles.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-04-25 23:14:31 -07:00