Commit Graph
15 Commits
Author SHA1 Message Date
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 0926c25854 refactor: reuse canonical regex escaping (#16150) 2026-08-23 15:45:53 -07:00
Neil 77f23b013f refactor(shared): drop the shared/types barrel and import from the real modules (#14447)
#14397 split `shared/types.ts` into 46 per-domain modules but kept the path as
a re-export barrel so the import sites did not have to change. This removes
the barrel: every consumer now imports from the module that actually declares
the type, and `src/shared/types.ts` is deleted.

Barrels hide where a type lives, make every consumer look like it depends on
the whole domain, and let an unrelated edit invalidate a module that ~2,000
files transitively import.

2,323 import declarations across 2,321 files. Rewritten mechanically: each
specifier was resolved to an absolute path via the TypeScript AST and
recomputed, rather than string-substituted, so alias forms (`@/../../shared/
types`) and per-specifier `type` modifiers survive.

Four cases the mechanical pass had to handle, each found by a gate rather than
by reading the diff:

- Modules inside `src/shared` import the barrel as `./types`, not
  `shared/types`. A pre-filter on the latter string skipped 176 of them and
  left imports dangling at a deleted file, which surfaced as confusing
  `Property 'x' is optional in type 'Repo' but required in Pick<Repo, ...>`
  errors rather than "module not found".
- The barrel RENAMED one type on the way through
  (`WorkspaceSource as WorkspaceCreateTelemetrySource`), so the original name
  in the owning module has to be re-aliased at each consumer.
- Three test files put `;(globalThis as ...)` on the line after the import.
  TypeScript parses that `;` as the import statement's terminator, so
  replacing through `statement.getEnd()` deletes it and breaks ASI. The
  rewrite now stops at the module specifier.
- A file that already imported directly from a module got a SECOND import
  from it, because the barrel re-exported those same names — which trips
  `import/no-duplicates` under `--deny-warnings`. A post-pass merges
  declarations sharing a specifier and type-only-ness; the `import type` plus
  `import` pair from one module is left alone, since that form is allowed.

Splitting one barrel import into several genuinely adds lines, which pushed
`terminal-layout-pty-ownership.ts` to 301 counted lines: its 107-character
import must wrap, and neither local type collapses onto one line (101 and 116
characters). Rather than contort a type declaration to fit a line budget,
`collectLeafIds` and `pruneLeaves` move to `terminal-pane-layout-tree.ts` —
they are pure structural operations on the layout tree and independent of PTY
ownership. `visible-worktrees.ts` similarly loses its own mini-barrel
re-export of `isDefaultBranchWorkspace`, with the four real consumers
repointed at the declaring module. No `max-lines` bypass added.

Verified: cold `tsc --noEmit` green on node, cli, and web (buildinfo deleted
first — these projects are `composite: true` and reuse stale caches); the full
`pnpm lint` green, not just bare oxlint — the narrower local check is what let
the duplicate imports reach CI; max-lines ratchet OK at 344.
2026-08-13 22:48:24 -07:00
Brennan Benson 23cbe6dfe2 fix(agent-hooks): guard copilot and kimi Windows hooks before they own stdin (#13379)
Same hang class #11568 closed for .cmd: copilot-hook.ps1 ran
[Console]::In.ReadToEnd() and kimi-hook.sh captured stdin before checking the
Orca env, so a user-wide hook fired outside an Orca pane blocked forever when
the caller abandoned the pipe — one stranded powershell.exe or bash.exe per
hook event.

Move the env guard (after the endpoint refresh, which can supply PORT/TOKEN)
above the read on the Windows-local variants. The payload is discarded on the
missing-env path anyway; the broken writer is the trade #11568 established.
POSIX variants keep capture-first — those callers close stdin, and exiting
mid-write there surfaces as EPIPE the agent can see (#8110). Kimi's remote
install now asks for the posix variant explicitly.

Verified on a real Windows host: pre-fix copilot with an abandoning caller
never exits; post-fix both exit 0 immediately, and the env-present copilot
path still consumes stdin and delivers the POST (paneKey + payload received
by a local listener).
2026-08-11 17:06:55 -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
597c84f36c fix(rate-limits): read Kimi usage credentials from the configured WSL runtime (#12475)
* fix(rate-limits): read Kimi usage credentials from the configured WSL runtime

Kimi's usage fetch always read the Windows host's ~/.kimi-code, so a Kimi CLI running inside WSL rotated only the WSL-side token and the status bar was stuck on 'Run Kimi to refresh'.

Resolve the Kimi home from the local-account runtime target (mirroring Codex's getDefaultWslDistro()/getWslHome() UNC pattern), pinned to host off Windows, and keep KIMI_CODE_HOME host-only.

Fixes #12370

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

* fix(rate-limits): bound and offload the Kimi credentials read for WSL homes

Adopted from @cengiz-io's #12372: read credentials through
createAuthFilesystemOperation (async, per-path dedup, AbortSignal bound) so a
stopped distro degrades to an error instead of parking Electron main on a UNC
read. ENOTDIR joins ENOENT as "not signed in" to keep existsSync parity, and the
WSL runtime target is now probed with the async wsl.exe helpers.

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

* test(rate-limits): build Kimi credential-path expectations with path.join

The host-home assertions hardcoded POSIX separators, so they only passed on
a POSIX runner — on a Windows dev machine `join` emits backslashes and all
four cases failed (three assertion mismatches plus a WSL-suite fixture whose
map key never matched the path the fetcher read).

---------

Co-authored-by: Orca <help@stably.ai>
Co-authored-by: OrcaWin <alpha-eng@stably.ai>
2026-08-07 18:59:42 -07:00
NeilandOrca aab112933e Revert "fix(memory): bound OOM-prone accumulators (#10179)" (#10255)
Co-authored-by: Orca <help@stably.ai>
2026-07-23 18:35:31 -07:00
Neil 8f40ddf328 fix(memory): bound OOM-prone accumulators (#10179) 2026-07-23 06:22:56 -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 Benson e03a1cf769 Prevent managed agent hooks from hanging (#6148) 2026-06-23 17:22:10 -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
Prethish Kumar 21d0391881 fix(kimi): map AskUserQuestion to waiting and expose hook status
Maps Kimi AskUserQuestion PreToolUse events to waiting, exposes the Kimi hook status IPC/preload surface, and hardens Kimi session parsing plus remote hook path construction.
2026-06-19 16:45:34 -07:00
Neil 4eb3e75b9e feat(kimi): Kimi Code sessions in AI Vault + agent status hooks
Adds Kimi Code session parsing for AI Vault and managed Kimi agent status hooks.
2026-06-19 16:42:59 -07:00