Commit Graph
8908 Commits
Author SHA1 Message Date
Neil dc2c76ff30 fix(cli): accept agy alias for supervised worker starts 2026-09-19 05:12:43 -07:00
Neil 8bcb02cdde fix: keep direct SSH Antigravity chat on its execution host 2026-09-19 04:57:41 -07:00
Neilandhaoliangli e5dcac8fe4 feat: render Antigravity terminal sessions in native chat
Co-authored-by: haoliangli <haoliangli@users.noreply.github.com>
2026-09-19 04:53:05 -07:00
Neil 32a37f02b4 fix(antigravity): wait for host-confirmed composer before continuation paste 2026-09-19 04:21:57 -07:00
Neil 7ea751027b fix(onboarding): localize Antigravity installer terminal title 2026-09-19 04:06:58 -07:00
Neil db265b9059 feat(onboarding): offer Antigravity installer in a persistent setup dialog 2026-09-19 03:57:45 -07:00
NeilandOrcaWin 4ef37c1470 fix(orchestration): address Antigravity groups from the agent catalog
Adopt group routing and unknown-address diagnostics from PR #20549; retain current generated guide content and omit the documentation enumeration ratchet.

Co-authored-by: OrcaWin <orcawin@users.noreply.github.com>
2026-09-19 03:29:12 -07:00
NeilandAbhishek Tiwari b21303ad0e fix(antigravity): read native quota only on supported CLI versions
Adopts native usage parsing and display from PR #20797, adding a pre-invocation version guard and legacy group labels.

Co-authored-by: Abhishek Tiwari <69499+abti@users.noreply.github.com>
2026-09-19 03:21:22 -07:00
Neil 671490a7fb Allow custom agent commands when automatic detection misses them 2026-09-19 03:05:10 -07:00
Neil a88a2472ca Merge branch 'nwparker/agy-skill-discovery' into nwparker/agy-skill-install 2026-09-19 02:55:06 -07:00
Neil e01d07f8a8 Merge branch 'nwparker/agy-history-folders' into nwparker/agy-skill-discovery 2026-09-19 02:54:47 -07:00
Neil b88101e482 Regenerate runtime catalog after workspace label changes 2026-09-19 02:54:27 -07:00
Neil d8c326dbab Merge branch 'nwparker/agy-skill-discovery' into nwparker/agy-skill-install 2026-09-19 02:47:43 -07:00
Neil b50db87836 Merge branch 'nwparker/agy-history-folders' into nwparker/agy-skill-discovery 2026-09-19 02:47:32 -07:00
Neil 362b8077c9 Fix workspace history catalog and test fixtures 2026-09-19 02:47:09 -07:00
Neil 82348bd955 feat(skills): install for Antigravity with remote host capability checks 2026-09-19 02:40:16 -07:00
Neil aaca019edc fix(skills): discover the Antigravity CLI global skill root 2026-09-19 02:28:05 -07:00
Neil 87ff944c37 Merge branch 'nwparker/agy-history-workspace' into nwparker/agy-history-folders 2026-09-19 02:21:44 -07:00
Neil 2c38876be2 Merge branch 'nwparker/agy-escape-navigation' into nwparker/agy-history-workspace 2026-09-19 02:21:39 -07:00
Neil ae35a1324d Merge branch 'nwparker/agy-hook-exec' into nwparker/agy-escape-navigation 2026-09-19 02:21:30 -07:00
Neil 618571d1db Merge branch 'nwparker/agy-hook-stdin' into nwparker/agy-hook-exec 2026-09-19 02:21:24 -07:00
Neil 2fda296e32 Merge branch 'nwparker/agy-hook-health' into nwparker/agy-hook-stdin 2026-09-19 02:21:20 -07:00
Neil 53da98fdc6 Merge branch 'nwparker/agy-visible-readiness' into nwparker/agy-hook-health 2026-09-19 02:21:16 -07:00
Neil 7e85ea643a test: replay agy readiness captures at their live screen phase 2026-09-19 02:20:54 -07:00
Neil a45eb1c006 fix(history): recognize folder workspace session targets 2026-09-19 02:12:25 -07:00
Neil e783bc9b67 fix(antigravity): restore workspaces by exact conversation identity 2026-09-19 01:50:06 -07:00
Neil ca0844f325 fix(antigravity): do not infer interruption from navigation Escape 2026-09-19 01:43:17 -07:00
Neil 2a9c60ebf4 test(antigravity): use typed async process runner for hook launches 2026-09-19 01:42:12 -07:00
Neil 951d4a766c fix(antigravity): make POSIX hooks executable by ACP hosts 2026-09-19 01:36:52 -07:00
Neil 2f453bddf1 fix(antigravity): bound hook stdin without forwarding timed-out payloads 2026-09-19 01:27:35 -07:00
Neil eecd30bf0e fix: report missing Antigravity hook scripts 2026-09-19 01:09:54 -07:00
Neil 49a85bfad5 fix: refuse Antigravity readiness while host is unverifiable 2026-09-19 01:04:19 -07:00
Neil dfbb928816 fix: use current Antigravity screens for waits and delivery 2026-09-19 00:58:37 -07:00
Neil 9fc2c5bea6 fix(antigravity): validate the visible composer before resolving adopted waits 2026-09-19 00:38:41 -07:00
Neil cae60dafc2 fix(antigravity): gate configured models on remote runtime support 2026-09-19 00:26:21 -07:00
Neil a94b345313 fix(antigravity): discover current source-control models and use CLI defaults 2026-09-19 00:16:57 -07:00
OrcaWin 7063c2cbdd fix: read crash diagnostics without loading whole dumps into memory
Read crash diagnostics incrementally to avoid whole-dump memory spikes.
2026-09-18 23:39:52 -07:00
OrcaWin 8812534335 fix(claude): stream transcript ancestry proofs
Stream transcript ancestry proofs without loading whole histories into memory.
2026-09-18 23:39:16 -07:00
Neil d966927013 fix(omp): submit large prompts in one PTY frame (#21573)
* fix(omp): join prompt submit with large paste

* test(omp): cover joined submit timing

* ci: rerun PR checks after timing test fix

* test(omp): acknowledge joined submit activity
2026-09-18 23:35:45 -07:00
Neil e8a956e833 fix(remote): reassert terminal geometry after reveal (#21568)
* fix(remote): reassert terminal geometry after reveal

* test(remote): expose layout queues through fixture

* test(remote): narrow layout target assertion
2026-09-18 23:35:05 -07:00
Neil 2bf538a4e1 fix(runtime): detect a same-size terminal artifact swap the granted stat cannot see (#21436)
* fix(runtime): detect a same-size terminal artifact swap the granted stat cannot see

A local terminal-artifact grant pinned the file as `dev:ino:nlink:size:mtimeMs`.
On Linux every one of those can survive an unlink+recreate: ext4 reuses the
just-freed inode (measured: 100% of the time), nlink and size are unchanged for a
same-size replacement, and the mtime clock is tick-quantized to 1ms, so a swap
inside one tick produces a byte-identical identity string. The grant then served
the attacker's bytes as if nothing had changed.

Local grants now also pin a sha256 of the artifact's content, taken from the same
handle as the stat so nothing can swap the file between them, and every local
read, preview and write re-checks it before returning or committing content.

The stat identity string itself is unchanged: the relay recomputes it verbatim to
honour `expectedStatIdentity`, so its format is a wire contract. Remote grants
keep the stat-only check and are untouched.

This is also the mechanism behind the intermittent
`orca-runtime-files-terminal-artifact-io.test.ts` failure on
`rejects stale absolute terminal artifact previews before returning changed
content`: it replaces an 8-byte artifact with 8 different bytes, so whenever the
two writes shared a 1ms tick the product genuinely could not tell them apart.

* docs(runtime): record what the terminal artifact grant checks do not close

The digest makes the same-size swap detectable; it does not make the sequence
atomic. A reader arriving at the access module would reasonably assume otherwise,
so write down the measured limits of the stat identity, why the identity string
cannot change, and the four windows that stay open — the write path's surviving
rename() gap above all.
2026-09-18 23:33:21 -07:00
Jinwoo Hong e49b3aa0bd fix(relay): carry the signed-out reason when the broker's own renewal notices the session loss (#21562)
The relay control socket closes with the reason `signed-out` so the cell can
tell paired phones to sign in rather than reporting a bare "host offline".
Only the auth coordinator computed that reason. The broker's renewal tick
called `closeNow()` with no reason when its token refresh came back empty, and
`closeNow` is idempotent, so whenever that tick observed the lost session
first — the common case for a session revoked or expired while Orca runs — the
coordinator's later `closeNow(SIGNED_OUT)` was a no-op and the cause never left
the desktop.

`refreshAccessToken` now returns a refusal that carries the reason the
coordinator already derives, and both close paths read it from one place. A
refusal for any other cause still names nothing: a present-but-unentitled
desktop is signed in, and a superseded refresh leaves the close to whoever
superseded it.
2026-09-18 23:56:00 -04:00
Neil 289a8bf1ce feat(settings): confirm before stopping running terminals (#21569)
* feat(settings): confirm before stopping running terminals

* fix(settings): localize close confirmation search keywords

* ci: rerun PR checks after localization fix

* fix(settings): add search keyword translations
2026-09-18 20:52:22 -07:00
Neil b6f6122bcd fix(terminal): preserve idle Linux IME direct commits (#21567) 2026-09-18 20:51:30 -07:00
Neil b69bc6d5b1 fix(terminal): preserve escape tails in renderer snapshots (#21578) 2026-09-18 20:49:59 -07:00
c34b944136 feat(github): bind projects to a specific gh account (#13664)
* feat(github): bind projects to a specific gh account

Adds per-project `Repo.ghAccount` so repo-scoped gh calls (create-worktree
issue/PR search, work items, hosted-review reads and mutations) run as the bound
account via ephemeral child-env token injection instead of the globally active
gh login. Multi-account resolution is capability-gated (gh >= 2.40) and fails
closed when the bound account or host is unavailable; Project View stays
ambient by design.

Repository settings gains a section for selecting or clearing a keyring-backed
account (shadcn `Select`), with mixed-version "not enforced" handling for older
remote runtimes. Attached `-Rhost/owner/repo` forms are covered by the host-drift
guard and its tests; es/ja/ko/zh catalogs carry the section's strings.

`getLocalProjectGhExecOptions` centralizes the binding lookup so every gh
execution path picks it up, including the Electron `hostedReview:*` handlers
that previously stayed on the ambient login. `gh auth token` (a keyring read)
is exempt from the rate-limit breaker gate so a tripped bucket cannot turn a
bound-token resolve into a false "unavailable".

The `ghAccount` update field and the two binding RPC methods live in the shared
RPC params contract; the generated catalog is regenerated.

Fixes #13612

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012B3QEP5iP4WGGEpPLtkHqA

* fix(settings): make GitHub account refresh secondary

* fix(github): satisfy strict casting quality checks

* test(rpc): use runtime fixture for repo binding

* fix(github): preserve project account for PR worktree lookups

* test(rpc): avoid incomplete runtime settings fixture

* fix(i18n): add GitHub account refresh label

* fix(i18n): refresh runtime required catalog

* fix(windows): preserve mobile patch bytes

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
2026-09-18 20:39:52 -07:00
Neil 545f526c31 refactor(editor): land shared Markdown scanners on main (#21565)
* refactor(editor): introduce shared Markdown code scanners (#21554)

* refactor(editor): add shared Markdown scanners

* test(editor): verify standalone scanner boundaries

* fix(editor): preserve line endings and fenced code boundaries

* fix(editor): keep bare dash lines out of table scanning
2026-09-18 20:04:31 -07:00
Neil 2038376d8e fix(terminal): a park must not discard the only copy of a remote pane's scrollback (#21285)
* fix(terminal): keep a client copy of a parked remote pane's scrollback

A remote-runtime pty's bytes never transit the client's main process, so the pane's
xterm buffer is the only client-side copy. The ordinary cold-park unmounted that pane
without capturing it, licensed by TERMINAL_PAIRED_PARKING_RUNTIME_CAPABILITY — a static
build string that says nothing about whether the host retained this pty's buffer. On
reveal, a host that answers 'no-serializable-buffer' (or stays silent past the request
timeout) collapses to a null snapshot and the pane paints blank: tabs and splits survive,
the scrollback is gone.

Capture before every park, not only the retention-budget force-park, so the reveal has a
copy to replay when the host cannot answer. An unverifiable host answer is not proof the
pane was empty; keep the buffer, never discard it.

Adds ORCA_E2E_FORCE_REMOTE_TERMINAL_SNAPSHOT_UNAVAILABLE so an e2e can reproduce the
host-retains-nothing state, mirroring the existing forced-truncation lever.

* test(terminal): prove a parked remote pane survives a host that answers nothing

The oracle is a token the test types into the terminal before the park and the fixture
echoes back. Nothing replays stdin, so a respawned command cannot reproduce that line —
only the pre-park buffer can. An earlier argv marker passed vacuously for exactly that
reason.

The control ('host retains the buffer') is insensitive to the fix and fails if the harness
never parks, never reveals, or never echoed the token, so the regression case cannot be
green for a harness reason.

* refactor(terminal): validate the paired host terminal RPC shape instead of casting it

The merge-commit consistent-type-assertions gate flags every new `as`. Two were fixture
shapes that a type annotation states directly, and the third hid an unchecked RPC payload —
readCreatedTerminalTab now fails with the shape named rather than surfacing later as an
undefined surface id.

* fix(terminal): let a park capture survive an unhydrated repo catalog

Reading state.repos unguarded threw out of the cold-park effect whenever the catalog was
absent, which would break parking itself. Capture is best-effort evidence; an empty catalog
also fails open in shouldPreserveTerminalScrollbackBuffers, the safe direction for a park.

* docs(terminal): pin why the two unhydrated-catalog fallbacks point opposite ways

shouldPreserveTerminalScrollbackBuffers fails open toward 'remote' because a worktree wrongly
judged local parks with no copy at all. worktree-runtime-owner.ts resolves the same unhydrated
catalog to 'local', which is safe there and would be data loss here. A reader pattern-matching
'fail open' across the two gets one of them backwards.

* fix(terminal): keep a parked pane's scrollback across a reconnect merge

The direct-SSH pull replaces a replaced tab's layout wholesale, and a park capture does not
bump tab.generation — so a just-parked tab is not in locallyPreservedTabIds and the only
client-side copy of its remote scrollback went with the layout it replaced. That is the same
data loss this branch already fixes, one layer down, and it is the layer that decides whether
the fix survives the app update the user actually performed.

Carry the client's leaf-keyed scrollback into the host's layout, filtered to the host's own
root leaves. Structure stays the host's verbatim, so a split it added while we were away still
wins and a leaf it retired still drops its bytes. Local wins a conflict: neither copy is then
the only one, but remote-wins would overwrite the tail captured since the last upload and
propagate that backwards on the next replace-session patch.

Not a generation bump: the pane key is `${tab.id}-${tab.generation}`, so bumping would remount
the pane and destroy the very buffer the capture just serialized, lift the recovery-storm
ledger ceiling, and let a stale local ptyId win through preserveNewerLocalTerminalFields.

* fix(terminal): carry a parked pane's scrollback through the mirrored-layout rebuild

Found in review of this PR by rc-ssh-remoting. chooseRemoteTerminalLayout rebuilds a
mirrored tab's layout from the host's picture and never carried buffersByLeafId or
scrollbackRefsByLeafId forward, though it already receives existingLayout. The host
publishes no scrollback of its own, so ANY session-inventory frame landing between park and
reveal dropped the only client-side copy: the rebuild is bufferless, terminalLayoutEqual
compares buffers so the write is not bailed out, and apply-terminal-records assigns it
wholesale.

Measured before the fix: 336 bytes captured at park, 0 after one forced frame, blank pane on
reveal. After: 411 bytes survive the frame and the reveal repaints.

The e2e passed either way because no frame happened to land in its window, so it was not
covering the destroying event. It now forces one inside the park -> reveal window and asserts
the capture survives it.

An identical fix was written and reverted earlier in this branch as 'no measurable effect' —
that measurement ran on a harness deleting the client profile between launches, so nothing
downstream of persistence could register. It was never actually tested.

* feat(session): add a local-only home for ordinary-park scrollback

localOnlyScrollbackByTabId is a top-level session field, tabId -> leafId -> buffer, that never
rides the remote projection: exportRemoteWorkspaceSession is an explicit allowlist of named
top-level fields, so a new one is omitted for free, whereas anything added to
TerminalLayoutSnapshot is copied whole. It is also outside the two records the mirrored-tab apply
rewrites, so a host inventory frame cannot wipe it.

Registered in every exhaustive session registry ('tabKeyed'), hydrated and scoped like the layout
map, dropped with its tab on close/removal/purge/repo removal/mirrored retirement, copied on profile
transfer, emitted by the incremental patch builder, and capped by pruneLocalTerminalScrollbackBuffers
alongside the shared home — with a per-home test so an uncapped path cannot go unnoticed.

Known ceiling, not widened here: the field routes through the partition router that falls back to
'local' when the repo catalog is unknown at write time (#21295).

* fix(terminal): keep ordinary-park scrollback off the upload, and read both homes through one resolver

The ordinary cold park fires on every workspace hide. Its capture now splits: structure (root,
ptyIds, titles) stays in the shared layout, bytes go to localOnlyScrollbackByTabId. Force-park,
hibernate, sleep and shutdown keep writing buffersByLeafId, because that copy is what a second
desktop cold-restores from; a shared capture clears the local copy so the two homes never hold two
versions of one leaf.

resolveLeafScrollbackBuffers is the only read across the two homes (local wins a conflict: it is
the later write by construction). restoreTerminalPaneLayout no longer reads buffersByLeafId
directly, the capture's merge prior comes from the resolver, and the post-replay release covers
both homes.

Measured with the projection at 20 tabs x 2 panes at the per-leaf cap: the shared-layout shape
exports ~22 MiB per replace-session; the local-only shape exports the bufferless baseline.

* test(sync): pin that the mirrored rebuild carries the client scrollback refs

The carry-through added in f210dece83 keeps scrollbackRefsByLeafId for leaves the host still
names (a ref is the only pointer to a local scrollback file), so the rebuilt layout equals the
stored one and the write bails. The old assertion expected the refs to be dropped and has been red
on this branch since that commit.

* test(e2e): assert where a park's bytes land, and re-point the inventory-frame check at the force-park

Once ordinary parks stop writing buffersByLeafId, the existing survivedInventoryFrame assertion
passes trivially — there is nothing in the layout to wipe. The ordinary scenarios now assert the
store-level upload contract (bytes in the local-only home, shared home empty) and that the local
home is out of a host frame's reach; a third scenario reaches a force-park (host without paired
parking, client retention limit 1) and asserts the shared capture survives the forced frame, which
is where the mirrored-layout carry-through is load-bearing.

* test(e2e): leave the force-park reveal out of the assertion instead of matching it loosely

expect.any(Boolean) reads as a check but cannot fail. The force-park reveal is served by the host
tail on a host without paired parking, so it is logged for diagnosis and the assertion carries the
three fields that are deterministic on this topology.
2026-09-18 19:49:38 -07:00
Neil 56e5cd5e34 fix(editor): render CJK-adjacent emphasis in Markdown preview 2026-09-18 19:15:45 -07:00
Neil d8cfe374a3 refactor(usage): share attribution and scope filtering
Readiness checklist review: PASS. No proven release-blocking findings. Codex and usage tests: 116 passed; all required CI checks green.
2026-09-18 19:14:38 -07:00