Commit Graph
178 Commits
Author SHA1 Message Date
Neil 70df6f0224 fix(terminal): mask the agent composer's dim placeholder during a preedit (#17377)
Split out of #17170, which now carries only the xterm composition-overlay work.

Codex and Claude draw an all-dim, full-row ghost placeholder. The opaque preedit
overlay reproduces the committed row tail it covers, so without this the ghost is
repeated to the right of the composing syllable instead of staying masked. The
binding keys off the `.xterm-composition-remainder` class that #17170 adds and
hides it through CSS while a composition owns a structurally verified placeholder
row — bold prompt glyph plus a dimmed model footer below a blank gap for Codex, a
frame line above the prompt for Claude. Arbitrary dim output, shell lookalikes,
and any row carrying typed text keep their tail visible.

readTerminalCursorLineContext moves from src/main/daemon to src/shared because the
renderer now needs the same reader the daemon uses; the move is import-only.

Depends on #17170.
2026-08-30 03:12:22 -07:00
Neil 7f822a73e3 fix(terminal): render the IME caret and give the candidate anchor one owner (#17170)
* fix(terminal): render IME caret without placeholder overlap

* fix(terminal): preserve dim mid-line composition tails

* fix(terminal): keep IME caret visible at row edge

* fix(terminal): harden IME overlay lifecycle and layout

* test(terminal): type final-cell layout mock

* fix(terminal): keep final-cell IME anchor on-screen

* fix(terminal): bind IME masking to composer ownership

* fix(terminal): bound IME placeholder session ownership

* fix(terminal): track latest IME placeholder session

* test(terminal): share IME session event fixture

* fix(terminal): keep both writers of the IME candidate anchor in agreement

`textarea.style.left` has two writers: xterm's patched CompositionHelper and
Orca's terminal-ime-candidate-anchor.ts. The anchor module listens on
terminal.element, so within a composition event it writes after xterm's textarea
listener and reverted the final-column clamp the patch had just applied.

Moving the clamp into the anchor module and dropping the patch hunk does not fix
it, and the rendered e2e caught that: CoreBrowserTerminal.ts:444 drives
updateCompositionElements from onRender as well, so xterm re-asserts the textarea
position on every repaint, with no composition event for that module to hear. The
anchor survived only when no render happened to follow — measured as a flake at the
final column, 1561.28px against a 1557px screen edge, the fully unclamped value.

So both writers now compute the same clamp. The patch keeps it, because it is the
writer on the render path and already holds cursorLeft, maxWidth and the preedit
bounds. The anchor module applies the same one, so its composition-event write no
longer reverts the correction in the window before the next render. Both halves are
individually necessary and both are mutation-tested.

Also restores _getRowRemainderText's expression from main: translateToString(true,
x, line.length) and translateToString(false, x, getTrimmedLength()) are the same
call, since upstream does endCol = min(endCol, getTrimmedLength()) under trimRight.

Adds the two missing tests — one installing both anchor writers in a single rig, one
driving a render under an open composition — plus disposal cleanup and clamp-bound
coverage, and moves the Codex/Claude placeholder mask to a follow-up PR.
2026-08-30 02:23:04 -07:00
Jinwoo Hong 252dbd60ea fix(terminal): restore lossy initial remote snapshots (#17113)
* fix(terminal): restore lossy initial remote snapshots

* test(terminal): strengthen lossy snapshot causal oracle
2026-08-30 03:11:55 -04:00
Jinwoo Hong f572ba34bc feat(browser): address-bar convergence — previews and browser tabs convert in place (STA-5681) (#16998) 2026-08-29 22:38:59 -07:00
Jinjing 73ff003147 test(e2e): cover session upgrade and Windows terminal recovery (#17289)
* coverage report

* rm test coverage

* test(e2e): cover session upgrade and Windows terminal recovery

* fix stub
2026-08-29 22:37:05 -07:00
Neil 7655d20f04 fix(terminal): complete OMP stale cwd recovery (#17154) 2026-08-29 14:34:50 -07:00
Neil 9db319dc06 fix(terminal): recover OMP from stale working directories (#17128)
* fix(terminal): recover OMP from stale cwd

* fix(terminal): harden OMP cwd recovery
2026-08-29 02:27:44 -07:00
Jinwoo Hong 6256f3d137 Fix lost session-tab changes during initial census (#17064) 2026-08-28 14:57:48 -07:00
Neil 7bbb8adc61 fix(ssh): replay an undelivered remote PTY stop on the next handshake (#12447 item 1) (#17011)
* fix(ssh): replay an undelivered remote PTY stop on the next handshake

A pty.shutdown that dies on the transport left the remote shell running
forever: kill.ts marked liveness unverifiable and nothing retried.

Record the undelivered stop on the existing durable SshRemotePtyLease and
replay it against the authoritative host on the next handshake to that same
target, fenced by the host-minted PTY incarnation so a replay cannot kill a
later PTY that reused a recycled pty-N id. Retire the record on confirmed
delivery, on the host reporting the PTY absent, and on a bounded TTL.

No wire change: the fence reads incarnationId, already published on
pty.listProcesses. A host that does not publish it degrades to no replay.

* fix(ssh): do not leave a replayable kill order behind a reversible stop

Worktree sleep stops through stopAndWait and marks those stops reversible;
when one does not land the pane stays live and the user keeps using it. An
order recorded there would come back on a later handshake and kill that
terminal. Only killPtyFromRuntimeController — where the client gives the PTY
up for good — records one, and it skips any PTY a reversible stop owns.

* fix(ssh): cover the renderer kill route and harden the replay's evidence

pty:kill is a separate implementation from killPtyFromRuntimeController and
is the one an ordinary tab close reaches, so the record was never written on
the path #12447 describes. Extracted it out of inspect.ts (which was over the
line budget and was not what the file is named for) and wired both branches.

Also:
- finishPtyShutdown no longer retires the order. It runs on paths that asked
  the host and on paths that never did, so retiring there was a contract every
  caller had to know, and the one that forgot silently dropped a kill order.
  Retirement is the replay's, on inventory evidence only.
- A recycled relay id now expires its lease. Declining to kill was only half:
  reattach fences on paneKey/tabId, never incarnation, so an untouched lease
  bound the user's old pane to whatever now holds the id.
- Dropped isPtyAlreadyGoneError from the tombstone path. It matches message
  text a transport failure could wear; every tombstone now traces to a listing.
- TTL is owned by a durable prune that actually deletes, not by a branch that
  was unreachable behind the read filter and only looked tested.
- The replay re-reads the inventory per wave and re-checks the fence next to
  each shutdown, and can never reject into the connect path.
2026-08-28 04:08:15 -07:00
Jinjing c4b39295c1 style: format codebase (#16935)
* style: format codebase

* style: format codebase

* refactor: extract skill install dialog footer and content

Extract footer and content sections from SkillInstallDialog and
SkillInstallManagementDialog into separate components for improved
maintainability and clarity of component responsibilities.
2026-08-28 00:59:21 -07:00
Jinwoo Hong cb848647e5 fix(browser-preview): require explicit preview capabilities (STA-5758) (#16921)
* fix(browser-preview): require explicit preview capabilities (STA-5758)

Scope document reads to approved directories, confirm external links before opening them, revoke grants with tab lifecycle, and keep document-preview session state rollback-safe across mixed client/runtime versions.

* Harden document preview lifecycle and permissions

* Document preview DNS prefetch residual

* Make preview E2E guest focus explicit

* fix(browser-preview): entry-file-only authority for root-level docs, contained chip layout, re-issued gate paths (STA-5758)

A grant whose document directory is its own request base — a doc at the
workspace root, or outside any workspace — now reads nothing but the entry
file until the reader approves a directory, at both the lexical and the
canonical containment pass. The DNS-prefetch residual can only beacon what
the page can read, and a root-level document could previously read the
whole worktree silently.

The identity chip's host badge overflowed the chip's layout box under
squeeze (Linux CI): every row member can now shrink and truncate, verified
by a width sweep in isolated Chromium down to ~120px chips.

The Allow banner says what it grants: 'Allow folder', reading files in the
named directory, for the life of the preview.

The reliability-gate manifest command, testFiles entry, assertion refs and
dated evidence naming the deleted doc-preview-external-link-bridge.test.ts
are re-issued at doc-preview-external-link-confirmation.test.ts with a
fresh 189/189 run; the focus-gate assertion text follows the shipped gate.

* fix(browser-preview): hide the chip identity row below 24rem instead of clipping it, ellipsize the host badge, catalog the new i18n keys (STA-5758)

CI's preview pane leaves the chip ~40px: no truncation shows anything
there, so the Workspace-file label and host badge now hide whole below a
24rem container threshold sized so that visible implies contained. The
badge text gains an inner text box — text directly inside the flex pill
clipped both ends with no ellipsis. The e2e geometry oracle asserts
containment when the row shows and the threshold when it does not.

verify:localization-catalog: the hardening's new preview keys (and the
renamed allowDirectory) join en.json via sync:localization-catalog.

* feat(browser-preview): batch blocked folders into one access decision (STA-5758)

Sequential per-folder banners trained the allow reflex without adding
judgment — a reader cannot weigh assets/ against data/. The banner now
accumulates every folder a load surfaces, names them (three, then a
count, full list in the title), and grants exactly that set with one
Allow-N-folders click and one reload. Dismiss fences the whole named
set. The map lives behind a ref with a version tick so a dismissal
fences an offer landing in the same event batch.
2026-08-28 00:42:07 -04:00
Jinwoo Hong b19a397d3e feat(browser-preview): reland remote HTML document previews (STA-5758) (#16920)
Reapply the reverted remote HTML document preview implementation so remote workspace files render locally over the orca-preview scheme.
2026-08-28 00:27:01 -04:00
Brennan Benson 9a0a2b1c31 fix(orchestration): settle worker release without web layout state (#16842)
* fix(orchestration): tolerate missing terminal layout partitions

* fix(orchestration): handle legacy release without layouts
2026-08-27 16:39:19 -07:00
Jinjing 551fbb9ac7 Revert "feat(browser-preview): render remote HTML docs locally over an orca-preview scheme (STA-5557) (#16679)"
This reverts commit 249d93bc5d.
2026-08-27 16:32:35 -07:00
Jinwoo Hong 249d93bc5d feat(browser-preview): render remote HTML docs locally over an orca-preview scheme (STA-5557) (#16679) 2026-08-27 14:34:21 -07:00
Jinwoo Hong 0f522c35e5 fix(remote): gate empty session inventory on host authority (#16546) 2026-08-26 22:30:48 -07:00
Jinwoo Hong d60a3c900b Reset stale terminal modes after dead TUI replay (#16379) 2026-08-26 19:49:26 -07:00
Jinwoo Hong 0e10fc5925 fix(browser): retire helpers with page owners (#16564) 2026-08-26 15:09:22 -07:00
Neil d1a11b3299 fix(pty): match the echo shapes a real tty actually produces (#16542)
Reply echo suppression modelled two echo shapes from the spec rather than from
a tty. Captured under node-pty against real bash, at a readline prompt and
under `read`:

  - Readline mangles CSI replies, not just OSC: `ESC [ ?` becomes BEL and the
    residue echoes. The projection was gated on an OSC introducer, so a private
    DSR echo was never matched at a readline prompt. This is the reachable one:
    a mode-2031 theme push (`CSI ?997;1n`) left latched by an exited TUI paints
    `997;1n` on a bash prompt (#9993's scenario).
  - ECHOCTL carets EVERY control, not just ESC. A BEL-terminated OSC reply
    echoes as `^G`, but the needle kept a literal BEL — a string no tty
    produces. Hardening only: every in-tree OSC reply is ST-terminated
    (terminal-osc-color-reply.ts:112, xterm's own reply), so the changed byte
    is unreachable except from a foreign or older emulator.

Why this is not the CSI projection #13160 review dropped: that one was the
identity (`replaceAll('\x1b]', …)` is a no-op on a CSI reply), so it was
ESC-led and 500ms-held bare-ESC tails away from the query parser. This one is
BEL-led. The rule is now asserted for every shape rather than implied by the
gate: holdPartial iff the needle does not start with ESC.

The readline branch is keyed on the private-DSR grammar with a non-empty
parameter list, plus a floor on needle length. The containment grammar admits
`CSI ? n`, and `answerLiveQueryReply` takes client-supplied bytes on the relay
path, so a peer could otherwise arm a two-byte `BEL n` needle and delete the
first bell-then-`n` in ordinary output. #61c65151129 proved this system can eat
real output when a needle outlives its budget; a length floor is cheap.

Live coverage: pty-reply-echo-shapes.node-pty.test.ts writes a reply to a real
bash master and feeds back what it echoes, so a shell or libc change fails the
suite instead of silently disarming suppression. Registered in the
shell-contracts lane. The transcript tests and the caretEcho helpers that
encoded the same ESC-only assumption are corrected alongside.

Suppression is display-only. This does not change what reaches the child's
stdin — the reply is written to the master either way, in call order.
2026-08-26 14:36:45 -07:00
Jinwoo Hong 868fc39d32 fix(worktrees): refresh paired clients after external discovery (#16557) 2026-08-25 23:18:30 -07:00
Jinwoo Hong 4ff428f763 fix(mobile): retain relay during brief backgrounding (#16543) 2026-08-25 22:33:59 -07:00
Neil a1ec0479e2 fix(windows): revalidate PTY liveness from the job object, not a forked helper (#16419)
* fix(windows): answer console membership from the job object, not a forked helper

node-pty answers "which processes are attached to this pane's console?" by
FORKING a helper, because GetConsoleProcessList must run from a process
attached to that console. Orca asked on a foreground poll, per pane, so each
read spawned a conpty_console_list_agent -- hundreds of hidden processes
exhausting RAM within minutes, respawning as fast as they were killed (#10857).

QueryInformationJobObject has no console-attachment constraint: any process
holding the job handle can ask. Orca already creates that job per PTY, and
listPtyJobProcessIds has exposed it since the W1/W2 work with zero callers.
One syscall, no children.

Semantics the three call sites rely on are preserved: a root-only set still
proves the shell is alone (so a stale agent can be retired), and size > 1 still
proves something is running under it. The single difference is that a
descendant detached from the console stays in the job -- which widens the set,
the conservative direction for every caller.

Also fixes the third call site, which returned { available: false } whenever
membership was unavailable AND a recognized agent existed -- i.e. exactly while
an agent was running. Membership only ever narrowed the candidate list, so an
unavailable answer now leaves it unfiltered instead of failing the whole
resolution.

The no-fork test is asserted through a module-level vi.mock of
node:child_process. A vi.spyOn of a require()'d child_process does not
intercept the module's own import binding: the first version of that test
passed with a fork() deliberately reintroduced.

* fix(windows): keep console attachment for the candidate filter

Readiness review caught that this PR changed two different questions as if they
were one, and the repo's own plan doc had already said so:

  "The job is the wrong set here -- it would re-admit precisely the detached
   process the filter exists to drop."  (windows-wsl-root-cause-plan.html, Use B)

The two uses:

- Use A, `size > 1` at local-pty-provider and the daemon tracker -- "is anything
  in this pane besides the shell?". The job answers this, in-process and with no
  fork. Unchanged from the previous commit.
- Use B, the candidate filter -- "which of these are ATTACHED TO THIS CONSOLE?".
  Its whole job is dropping a descendant that detached, and the job object keeps
  those, so answering it from the job makes the filter a no-op in its motivating
  case: a detached `Start-Process droid` would be granted byte authority, and a
  detached sibling would make an attached agent look ambiguous.

Use B goes back to GetConsoleProcessList, in its own module named for what it
answers, with its fail-closed null restored. That path is not the #10857 storm:
it runs only when a recognized agent candidate already exists, not on every
foreground poll. Bounding it to one pooled supervised helper is the remaining
half, and per the plan doc either half alone takes #10857 from unbounded to one.

My earlier claim that widening membership is "the conservative direction for
every caller" was wrong -- true for Use A, backwards for Use B. The hardware run
did not catch it because I measured a WSL pane, where the superset is harmless,
and never a detached GUI child, which is the divergence.

* fix: restore the coverage and ratchets the module split dropped

Round 2 of review. Two blockers, both from moving the forking code to a new
file without moving what guarded it.

- The child_process import ratchet was RED: windows-console-attached-processes.ts
  imports node:child_process and was unlisted, and the old entry was stale. I
  never ran that suite -- lint and the providers/daemon tests both pass without
  it, which is exactly the gap the ratchet exists to close. Entry repointed;
  count unchanged at 159.
- The forking module had ZERO tests. Its 11 assertions -- bounded timeout,
  single kill, spawn error, malformed message, helper-pid removal -- were in the
  file that now answers a different question, so the module that actually caused
  #10857 was shipping untested. Moved with the code.

Also: nothing pinned the round-1 fix itself. No test drove console attachment to
null and asserted the fail-closed result, so re-deleting that branch would have
gone green. Now covered, and verified to fail when the branch is removed.

Cleanups the split left behind: `consoleMembershipUnavailable`/`consoleProcessIds`
renamed to `pane*` where they now hold job membership, the duplicated
`WindowsConptyMembershipDeps` type name, comments still describing the console
on the job path, and eight reliability-gate paths pointing at the moved tests.

* fix(windows): let a superset job answer expire instead of vetoing retirement

Round 3. The job read had reintroduced #9258's bug by a new mechanism.

`size > 1` returned unconditionally, so any pane holding a console-detached
descendant never retired its cached agent. A WSL pane always holds some: the
measurement in this PR's own test recorded job [40980,104068,4888,69908] against
console [69908,40980], i.e. console said "shell alone, retire" while the job said
"three others alive, keep". #9258's third commit describes the identical failure
from the other direction -- a bare shell reading as [helper, shell] "looked like
it still had a child ... the foreground refresh held the exited agent's identity
indefinitely" -- and that is what came back.

It bites because the read branch that serves the cached name across a Windows
shell fallback is deliberately untimed: #9258 made it so on the stated assumption
that "the background refresh authoritatively retires it". Removing the retire
authority left the identity with no bound at all. Second-order: a non-null cache
makes idleNoEvidenceShell false, which pins the refresh at the 1s TTL, so an idle
WSL pane also scanned the process table every second forever.

A TTL on the read would have been the wrong fix -- untimed is deliberate, because
on Windows the fallback name is structurally uninformative. Instead the job answer
is treated as what it is: a SUPERSET of the console, which cannot tell a working
agent from a leftover. Proof of absence retires immediately (size 1, unchanged);
an inconclusive answer ages out at 30s; unverifiable (null) still holds forever
per ssh-execution-boundary.md. Only successful scans that found no agent advance
the clock -- a degraded scan returns before this -- so the fix cannot expire an
agent it simply failed to see.

Also from review:
- Restore the root requirement the forked probe had. Without it a set of one
  non-root pid -- shell gone, descendant alive -- read as "shell alone, retire",
  inverting the truth.
- Rename to windows-pty-job-membership.ts / readWindowsPtyJobProcessIds. The old
  name still said ConPTY console while reading the job, and conflating those two
  sets is precisely the bug aee07c24aa reverted. Same for
  windows-console-foreground.ts, which guards a job read now.
- Gate the two files that had no coverage: the job read and the retire path.

* fix(windows): bound the provider's job short-circuit too

The previous commit fixed the daemon retire path and left the identical bug in
the local provider, which I found while asking the reviewer to check for it.

local-pty-provider.ts returned the cached agent early on `size > 1` and that
early return skips the scan at the bottom of getForegroundProcess -- the ONLY
code that can delete ptyLastRecognizedForeground. So on a WSL pane, whose job
always holds console-detached plumbing, the short-circuit was permanent and the
identity could never be cleared. Same failure, second location, and the daemon
fix did nothing for it because this path never calls retireStaleForegroundIdentity.

The cache was a bare Map<id, name> with no timestamp, so bounding it needs one.
Added ptyLastRecognizedForegroundAt, stamped only when the recognized name
actually changes, and paired with every existing delete including pane teardown
so the new map cannot outlive the old one.

The 30s threshold now lives in windows-cached-agent-revalidation.ts rather than
being duplicated: that module already answers "can we revalidate this cached
agent without a scan", and the max age is the other half of that question.

Also renamed two tests that still said "ConPTY console presence" while driving a
job read. Re-conflating those two sets by name is how this PR got its first two
review rounds wrong.

* fix(windows): stamp the provider cache on every confirmation, not on change

My own previous commit was wrong, and wrong in the direction #9258 exists to
prevent. Review caught it; the test in this commit reproduces it first.

I stamped ptyLastRecognizedForegroundAt only when the recognized name CHANGED.
That makes the value the time of first recognition, so the age measures how long
the agent has been running rather than how long since we last confirmed it. For
a live agent recognized as the same name every cycle the stamp never moved, the
age crossed 30s and stayed there, and the short-circuit died permanently.

Two consequences, the second serious:
- every getForegroundProcess call on a >30s-old agent pane ran the whole-table
  scan, defeating the exact optimization the branch exists for;
- with the short-circuit off, one available-but-agentless snapshot was enough to
  delete a LIVE agent's identity, because paneMembershipUnavailable is false in
  this state so the degraded-scan substitution does not engage. That is the false
  "agent done" this code's own comment warns about.

The daemon path was already right -- it re-stamps refreshedAt on every positive
recognition -- so the same constant meant two different things in the two files.
Now both mean "time since we last saw the agent", which turns the bound from
"disable the short-circuit after 30s" into "force one revalidating scan every
30s": ~16-31ms per pane per 30s via the native process table.

Test asserts the scan count stops incrementing after the revalidation, and fails
against the stamp-on-change form.

Also correct the shared docstring, which had dropped the invariant the whole
design rests on, and stop calling this a WSL bug: the trigger is a persistent
console-detached job member plus a fallback that reads as a shell. wsl.exe is
not in SHELL_NAMES, so a plain WSL pane does not even reach this code -- WSL is
just where it was measured.

* refactor(windows): shrink the job-membership path

Elegance pass. No behaviour change -- all three mutation checks still bind
(restoring the size>1 veto, stamping only on name change, dropping the root
requirement each turn their tests red).

- windows-pty-job-membership.ts 54 -> 31 lines. A deps object carrying one
  optional function became a defaulted parameter, the accumulate loop became a
  filter, and the docblock lost two thirds of its bulk.

  It also lost a claim that was simply false: it said a widened set "is the
  conservative direction for every caller: it keeps a live agent rather than
  retiring it early". For the retire caller, never retiring IS the failure --
  that is the bug this stack just fixed, still being described as a feature
  three commits later.

- One local `identityOlderThan(ms)` in the tracker replaces two hand-rolled
  `Date.now() - refreshedAt` comparisons, one of which I had added.

- The provider's two parallel maps collapse into one Map<id, {name, at}>.
  Parallel maps meant every delete site had to remember its sibling, in three
  places; the reviewer flagged the leak risk and I fixed it by pairing them,
  which leaves the hazard for the next person. One map removes the class.

Comments trimmed to the load-bearing sentence throughout, per AGENTS.md.

* fix(windows): preserve foreground cache age evidence

* fix(windows): anchor cached agent identity to the pid that proved it

The job short-circuit and retirement veto only knew 'something besides the
shell is alive', so a detached leftover pinned a dead agent's name for the
30s age bound, and 30s of incomplete-but-successful scans could retire a
live one. The scan already knows which row proved the name: carry that pid
through the resolution, and judge the cache against the job with it --
membership of a known pid in a complete, inescapable job list is proof of
life (restamp, never expire), and its absence is proof of exit (retire now,
leftovers notwithstanding). Unanchored identities keep the age-bound
superset behavior.

* fix(windows): anchor the reported process, and let a scan refute a recycled pid

Review findings on the pid anchor:

1. The anchor followed the LEAF that proved a collapsed name: 'omp' reported,
   pi's pid stored. Pi exiting or restarting under a live OMP then read as the
   wrapper's exit -- retiring the identity before a scan that (degraded) may
   miss OMP, a false 'agent done'. resolveOuterWrapperForegroundIdentity now
   carries the pid of the process the name belongs to.

2. A bare numeric pid can be recycled inside the pane's job, making membership
   falsely confirm a dead identity indefinitely. Command lines are immutable,
   so a scan row holding the anchor pid without recognizing as an agent proves
   a different process: the resolution reports it (anchorPidForeign) and both
   consumers retire immediately. A query-denied row (command falls back to the
   image name) stays inconclusive -- never grounds to drop a live agent.

* fix(windows): find a recycled anchor pid in the full table, not the ppid walk

A squatter that inherited the pane job from a leftover whose creator then
exited is orphaned out of the shell-rooted descendant projection, so the
foreign-anchor refutation never saw its row. Pluck the anchor pid's row from
the same whole-table snapshot instead; a job member holding the pid is in the
table even when no ppid chain reaches it.

* fix(windows): survive an agent restart, and refute a squatter by name

Two review findings on the exit verdicts:

1. 'exited' deleted the cache before the scan, so an agent restarting under a
   new pid plus a degraded scan at that instant reported the shell -- a false
   'agent done'. Only the shell standing alone is decisive now; an anchor
   leaving a job that still has members downgrades to unanchored, age-bounded
   evidence and lets the scan decide. The daemon tracker keeps immediate
   retirement: its verdict path only runs after an available scan already
   found no agent.

2. The foreign-anchor refutation treated any recognized row as 'ours'. A pid
   recycled by a DIFFERENT agent now compares against the cached name the
   anchor is supposed to prove.
2026-08-25 20:32:54 -07:00
Jinwoo HongandJinwoo-H a9781a4118 STA-4150: client-hosted remote browser (consolidated) (#15448)
Co-authored-by: Jinwoo-H <jinwoo@stably.ai>
2026-08-25 15:36:51 -07:00
Brennan Benson 1921ba2250 fix(agent-status): clear the pane when a Claude compact finishes (STA-2915, STA-4613) (#15202)
* fix(agent-status): clear the pane when a Claude compact finishes (STA-2915, STA-4613)

A manual /compact ends at an idle prompt without emitting Stop, so nothing in the
compact window could ever clear the pane. A worktree that entered the compact
`working` stayed `working` until the 30-minute stale sweep -- and the summarizer's
start-less SubagentStop kept republishing the row, resetting that clock each time.

The correlation added by #12332 was supposed to own this, but it could never run:
PreCompact and PostCompact were never added to CLAUDE_EVENTS, so they were never
registered with Claude. compactTrigger was always undefined, and the transition
guard, the ownership cache, the relay wire field and the ingest branch were all
unreachable. Five test files exercised the logic by injecting events past the
registration boundary, so the suite stayed green over code that could not execute.

Register PostCompact -- and deliberately NOT PreCompact. Measured on Claude Code
2.1.227, a successful manual compact emits PreCompact, a start-less SubagentStop,
SessionStart(source=compact), then PostCompact; an ABORTED compact ("Not enough
messages to compact") emits PreCompact ALONE. Mapping PreCompact to `working`
would strand the pane on every aborted compact, which is the bug being fixed, so
the abort guard is structural: Orca never subscribes to the pre-validation event.

PostCompact carries its own trigger, so no anchor is needed to tell manual from
auto and the correlation machinery is deleted rather than repaired. Manual becomes
a `done` with sessionBoundary set -- a finished compact is a session-shaped
boundary, not a completed turn, so completion notifications, unread counts and
automation-run evidence stay out of it. Auto claims nothing: it runs inside a turn
that resumes and emits its own Stop.

The source-blind early return that dropped compact events for EVERY provider
before its normalizer ran is narrowed to Claude, so it keeps failing closed on a
malformed payload without pre-empting other providers.

Ownership is kept where the deleted guard had it: a valid provider prompt id is
required, a completion clears a row but never creates one (a retired pane must not
be resurrected), and a hydrated row is matched on provider session only -- it
carries the previous session's connectionId, and older rows carry no session at
all, so a strict check would reject the restart case this fixes. A consumed
prompt id keeps relay duplicates from refreshing the row.

Mixed versions: no new wire field and no new opcode. An older relay normalizes
with its own shipped mapping and forwards the event, so ingest drops `auto`
envelopes and stamps the boundary on `manual` ones; its replay strips the trigger
entirely, so payload state stands in for it while ownership is still enforced. The
relay now caches a completion with its compact identity removed, so a client that
was offline during the compact still receives the clearing row on reconnect.

Tests go red before this change and green after: 6 of 12 in the new
registration-gated suite and 5 of 8 in the relay/ingest suite. The harness delivers
only events present in CLAUDE_EVENTS, so a fix that is never registered cannot
pass -- the failure mode that let the original correlation ship unreachable.

* test(agent-status): restate the compact reliability gate around the new invariant

The gate pinned a test file this change deletes, so the manifest check failed.
Repointing the path alone would have left the gate describing an invariant that
no longer exists: it required a manual PostCompact to match its exact PreCompact
generation, and PreCompact is no longer consumed at all.

Restate it. The invariant is now that PreCompact never moves a pane, that only a
manual PostCompact marks done and does so as a session boundary, that a
completion clears an existing row but never creates one, and that a relay
predating the contract has its automatic envelopes dropped and its trigger-
stripped replays classified by payload state under the same ownership checks.

Evidence runs are the real ones: the 105-test suite from this branch, and the
Claude Code 2.1.227 PTY capture that measured PreCompact arriving alone on an
aborted compact.

* fix(agent-status): clear the restart-stuck pane a compact was meant to clear

Review found the completion did not clear the pane STA-2915 actually reports, and
that republishing it was a strict regression.

- A manual completion now retires a subagent that exists only as a disk snapshot:
  a /compact only completes at an idle prompt, so a restored child is proof of
  nothing. Live evidence -- a child observed in this runtime, an unclassifiable
  running background task, a registered session cron -- still holds the pane.
- A completion that cannot clear now publishes nothing instead of restating the
  row, which was stripping restoredUnconfirmed off a hydrated row and restarting
  the staleness clock for work the compact never observed.
- The relay defers compact ownership to the client that owns pane identity, so a
  cold relay cache can no longer swallow the one event that clears a remote pane.
- claudeConsumedCompactPromptIdByPaneKey joins all three pane-scoped teardown
  routes, and an auto compact no longer spends the pane's consumed-compact slot.
- The promptless completion keeps the summarized turn's label with or without a
  trigger on the envelope.

Tests: the two restart cases now deliver the completion while the hydrated row is
still cached, so they exercise the restored-row branch instead of passing through
the strict one; the triggerless working replay is asserted from a FINISHED pane so
it can fail. Reverting the four source files turns 12 of 21 registration-gated and
8 of 12 relay/ingest tests red, and 18 of 18 targeted mutations are caught.

* fix(agent-hooks): preserve compact identity across relay replay

* docs(reliability): describe compact replay ownership
2026-08-24 13:06:44 -07:00
Jinwoo Hong c618ec7393 test(reliability): protect recent P0 regression invariants (#16163) 2026-08-24 09:38:46 -07:00
Neil afd76a4df9 fix(terminal): preserve synchronized frames on reveal (#16026) 2026-08-24 00:01:53 -07:00
Jinwoo Hong 1354ff534f fix(cmd-j): host-qualify browser and simulator tab candidates (STA-4965) (#15686) 2026-08-21 23:17:54 -07:00
Jinwoo Hong da6b9d8065 fix(terminal): stop orphaning live agent terminals across host restarts and graph syncs (#15644) 2026-08-21 17:11:17 -07:00
Jinwoo Hong 1ce2e562b3 fix(skills): isolate concurrent upload staging (#15693) 2026-08-21 13:15:46 -07:00
NeilandBrennan c92f394cde fix(pty): delete the reply-withholding scheduler (#15578)
* fix(pty): answer a terminal colour query in its own turn

Root-cause follow-up to #15559, which stopped a CPR overtaking a deferred
colour reply but left the deferral itself in place.

Orca answers terminal queries by writing to the PTY master, which a line
discipline in ECHO copies straight back out as junk on a cooked prompt
(#12112). The guard was to withhold the write until an `stty` subprocess
proved ECHO clear — and forking is what forced the decision to be async.
Any deferral, however short, lets a reply written later in the same turn
overtake this one, so the async probe was the bug's root cause.

Read the bit synchronously instead. Linux and the BSDs redirect a
master's mode ioctls to the slave, so a `tcgetattr` on the master fd
node-pty already owns answers for the slave with no fork: measured 0.26us
against 2403us for the subprocess. With a verdict available inline, a
querying program that already cleared ECHO — every raw-mode prober,
including the colour probe behind the `gh auth login` report — is
answered in its own turn and can never be reordered.

The deferral stays for the genuinely cooked case, and the ordering
guarantee stays underneath it: hosts whose node-pty predates this patch
get no sync probe and fall back to the deferred path, which mixed
client/host versions make a live production path.

Reply routing is all-or-nothing: a payload needing neither containment
nor ordering stays on the host's own path, so a CPR answered during shell
startup cannot pass the daemon's post-ready flush gate and splice into
the buffered startup command.

Native side is fail-safe: a kernel that did not redirect would answer
from the master's own termios, whose ECHO defaults set, so the degraded
verdict is "echoing" — never a false "quiet". The JS half ships in the
pnpm patch while the binding needs a source build, so
ORCA_REQUIRE_NODE_PTY_ECHO_STATE=1 makes CI fail rather than silently
skip when it is handed an upstream prebuild.

Co-authored-by: Brennan <brennanb2025@users.noreply.github.com>

* fix(pty): keep the flush ordered under synchronous re-entry

Three defects found in external review of the reply-ordering work.

node-pty delivers onData inside the master write, so a query can be
answered while the queue is mid-flush. `flushPendingWrites` spliced the
array off before writing, so that reply saw an empty queue, took the
same-turn path, and landed ahead of entries the loop had not written yet
— reproduced as 01, 99, 02, 03. It now shifts one entry at a time so a
re-entrant reply queues behind the rest, bounded by the length at entry
so a re-entrant push cannot spin the loop.

An overflow flush can re-enter as far as teardown. `answer` did not
re-check `closed` afterwards, so it queued behind a closed delivery,
returned true, and the reply was never written and never reported.

The payload router's ownership comment overstated its guarantee. The
`any` semantics are deliberate — returning false after a constituent was
already written would have the caller re-write the whole payload and
duplicate it into the child's stdin — so the residual mixed-failure drop
is now documented rather than implied away.

* fix(pty): delete the reply-withholding scheduler

Orca answered a terminal query by withholding the write until a probe
proved the slave's ECHO bit was clear. That was the wrong mechanism, and
it is now gone: replies are written in the caller's turn and their echo
is contained on the output side, where it always was.

Withholding never removed an echo. The wait was bounded and always ended
in a write, so the output-side projections were doing the work the whole
time — including the readline rewrite, which happens with the tty already
raw and which therefore no reading of the ECHO bit can predict. What
withholding did add was an asynchronous write path, and that is what let
one reply overtake another and land in the next program's stdin (#15559),
what produced a re-entrancy inversion inside its own flush, and what four
rounds of regressions have lived in.

The last thing it covered was the verbatim echo of a `stty -echoctl` tty.
That shape is now projected directly. It starts with ESC, so it is
matched only when complete and never held as a partial: holding it would
take a bare trailing ESC from the query parser and an expired hold would
release it raw, so a query torn at its own ESC would never be answered.
Complete-match-only is what makes the shape safe to project at all.

Measured on a real pty: a cooked-mode master write is both echoed AND
delivered — ECHO copies the bytes without consuming them from the slave's
input queue, so a program arming raw mode with TCSANOW/TCSADRAIN (libuv's
setRawMode, hence every Node agent) still reads them. Only a TCSAFLUSH
switcher discards it, which it does on every terminal, none of which
gates a reply on termios state.

Deletes the pending-write queue, the async stty probe, the poll budget
and probe rate limit, the deadline-driven flush, and the answer/
answerInOrder split. Replies now leave in call order by construction.
No packaging, native or CI surface is touched.

* test(pty): restore stty-probe coverage and pin the duplicate-query retry

Archaeology on how withholding got here, and what its tests were really
protecting.

Deleting the ECHO probe took four tests with it that were not about the
probe at all: they cover createSttyProbe, which the shell-readiness
line-editor probe still uses — in-flight sharing, the per-platform stty
flag, and transient-versus-permanent failure latching. Restored against
the line-editor probe, which is now their only caller.

Also pins the property that answers the one case an immediate write
cannot serve. A program that queries while cooked and then arms raw mode
with TCSAFLUSH discards the reply with the rest of its input queue.
Nothing can prevent that from the terminal side, and no terminal tries.
What matters is that such a program re-queries after its own timeout: the
ingress declines to answer an already-answered slot but forwards the
duplicate downstream, so the renderer's emulator answers the retry, by
which point the program is raw. The retry path is the recovery, not
withholding.

* ci(pty): keep the fish real-PTY test in the shell-contracts lane only

Reverting pr.yml to main dropped the exclusion for the fish query-reply
test, which this branch keeps, so it would have run in the sharded lane
as well. Restores it to the shell-contracts include list and the shard
exclude list, and drops the parallelism expectations for the deleted
cooked-querier suite and the echo-state env guard.

---------

Co-authored-by: Brennan <brennanb2025@users.noreply.github.com>
2026-08-20 02:15:42 -07:00
Jinwoo Hong 9b5538d786 fix(runtime): scope create-with-activate navigation to the requesting client (STA-2802) (#15407) 2026-08-18 21:37:35 -07:00
Jinwoo Hong bb09dc1749 fix(mobile): escalate a persistently rejected Relay pairing to re-pair (STA-4681) (#15237) 2026-08-17 22:36:54 -07:00
Jinjing a3a2c44edf Split browser pane (#14861)
* refactor: split BrowserPane.tsx under 400 lines

* rm plan

* refactor(browser-pane): reorganize into lifecycle folders

Cut/paste + import rewrites only; no intentional behavior change.

- annotate/, assemble-chrome/, host-guest/, navigate/, stream-remote/,
  describe-page/ (foundation sink, zero outgoing edges)
- BrowserPane.tsx is now a pure re-export barrel; its component body moved
  verbatim to assemble-chrome/browser-workspace-pane.tsx so no dest file
  imports the barrel
- browser-runtime.ts -> describe-page/live-browser-url-registry.ts (banned
  name; relocating the contract collapsed the host-guest/navigate mutual pair)
- repath browser-pane test paths in config/reliability-gates.jsonc

* refactor: sync addressBarValueRef with useEffect

Move ref synchronization into useEffect hook with proper dependency
tracking to ensure the ref updates are handled through React's
lifecycle. Consolidate related imports from browser-page-types.

* refactor(browser-pane): fix React lifecycle and external store patterns

- Replace local state + effects with useSyncExternalStore for external subscriptions (draw hint, address bar, slot viewport)
- Fix React StrictMode double-invoke issues in pointer handlers and state updates
- Add keyboard navigation to context menu (arrows, Home, End, Escape) with focus management
- Improve error handling for mobile driver reclaim and grab action IPC failures
- Add test coverage for BrowserFind session flags, keyboard behavior, viewport lifecycle
- Remove react-doctor/no-adjust-state-on-prop-change lint disables (root causes now fixed)

* i18n: extract grab and download UI messages

Move hardcoded toast notifications and error messages to translation
system for both grab annotations and file drop handling. Also apply
lazy initialization to address bar value and remove duplicate event
recording.

* fix(browser-pane): stop mutating refs during render

React Doctor fails static analysis when refs are written in render.
Mirror latest values in useLayoutEffect, and read the current page id
from the latest grab callbacks.

* fix(browser-pane): drop unused grab-mode exit dependency

exit already reads the page id from a ref, so listing browserPageId
trips the changed-code exhaustive-deps gate.

* test(e2e): hide the window when Linux minimize is a no-op

Xvfb has no window manager, so BrowserWindow.minimize() never sets
isMinimized() on the frameless Linux CI window. Hide still occludes
the guest compositor so restore coverage can run.
2026-08-17 14:53:19 -07:00
Jinwoo Hong b0e27354b5 fix(mobile): escalate continuous Relay outages (STA-4587) (#15071) 2026-08-17 02:14:42 -07:00
Jinwoo Hong b6d5972ec4 fix(mobile): reland truthful Relay recovery status (#14986) 2026-08-16 19:09:15 -07:00
Jinjing 1e63cfef06 Revert "fix(mobile): present pending Relay fallback accurately (#14922)" (#14976)
This reverts commit 3811881410.
2026-08-16 16:30:50 -07:00
Neil 9f3a912c1e fix(terminal): type Option-composed ASCII instead of reporting it as a chord (#14743)
* fix(terminal): preserve Option-composed ASCII input

* fix(terminal): preserve Option keyboard protocol semantics

* fix(terminal): complete Option keyboard event encoding

* fix(terminal): harden Option input encoding

* fix(terminal): close keyboard protocol fallback gaps

* test(terminal): prove Option-composed ASCII reaches the pty end to end

The Option-compose fix had unit coverage only. This drives a live Electron
pane whose kitty flags are armed by the application's own CSI > 1 u and
asserts the bytes at the pty boundary: composed `@` and Shift-layer `\`
arrive as text, configured Option-as-Alt still reports the layout-resolved
chord, and a non-ASCII glyph still reaches the app as its alt hotkey.
Restoring the pre-fix policy fails exactly the two composed-text scenarios.

Also records the ASCII rule's rationale where the rule lives, not only in a
test comment.

* refactor(terminal): drop the unread Option layers from the layout snapshot

The native helper computed an Option and Option+Shift character for every
key, shipped both over IPC, validated them in the parser and cached them in
the renderer — but no production caller ever asked for them. Only the base
and Shift layers are read, and Shift is the one the web layout map cannot
supply, which is why the helper exists at all.

Removing them halves the helper's UCKeyTranslate work per key and drops the
option parameter that six signatures were threading through for nobody.
2026-08-16 12:49:02 -07:00
Jinwoo HongandOliver Mee 5e9159af16 fix(shell-ready): preserve Bash PROMPT_COMMAND composition (#14619)
Co-authored-by: Oliver Mee <102673257+oliver-mee@users.noreply.github.com>
2026-08-16 12:48:53 -07:00
Jinwoo Hong 3811881410 fix(mobile): present pending Relay fallback accurately (#14922) 2026-08-16 12:38:26 -07:00
Jinwoo Hong d2ffe1f362 fix(terminal): settle CLI prompts for Claude and Codex (#14608) 2026-08-15 15:45:17 -07:00
Jinjing 7aaa7c6f5b refactor(sidebar): group worktree-list files by domain (#14486)
* refactor(sidebar): group worktree-list files by domain

Follow-up to #14465 / #14467. Keep the landed extract and reorganize the
flat worktree-list dump into drag/, headers/, reveal/, rows/, scroll/,
and viewport/. Fold tiny modules into their owners, move leftover
sidebar-root files into the module, and retarget imports and source-path
tests. Layout-only; no behavior change.

* fix(sidebar): merge duplicate virtual-rows imports

Inlining virtual-row-dom-attributes left a second import from the same
module, which fails audit:code-quality:native --deny-warnings.

* refactor(sidebar): condense indentation comments

Shorten explanations to focus on the essential why, removing redundant
detail and improving readability without changing functionality.

* refactor: organize worktree-list into lifecycle dest folders

* fix react doctor

* fix: update reliability-gates path after worktree-list reorg

host-filtering.test.ts moved from viewport/ to listing/; keep the
runtime-routing.active-server-preference gate pointing at the real file.

* Extract workspace status colors to design tokens

Define theme-aware color tokens for workspace PR-state indicators (done, in-review, in-progress) to ensure consistent identity across theme switches. Update references to use the new tokens and refactor EmptyState button to use the Button component.

* fix(sidebar): stop mutating refs during worktree-list render

React Doctor fails static analysis when refs are written in render.
Commit reused array identity and the Smart live-signal latch after
paint, and return the attention map from the sort memo instead of
stashing it on a render-time ref.
2026-08-15 13:40:09 -07:00
Jinwoo Hong 2eb3e11327 fix(terminal): make close and handles incarnation-stable (STA-4327) (#14590) 2026-08-15 01:25:09 -07:00
Neil 9367169888 refactor(tests): split every oversized test file off the max-lines suppression list (#14728)
* refactor(tests): split oversized test files off the max-lines suppression list

Every `*.test.ts`/`*.spec.ts` that carried an `eslint/oxlint-disable max-lines`
directive is now split into focused, behavior-scoped suites that fit the 800-line
test budget, with shared setup extracted into co-located `*-test-harness.ts` /
`*-test-fixtures.ts` modules (300-line budget). 83 files became ~930; the largest
output is 797 effective lines. `orca-runtime.test.ts` is intentionally untouched.

Test bodies were moved by scripted line-range slicing rather than retyped, so
assertions are byte-identical. The only permitted body edits were mechanical
rebinding where a shared value moved into a harness (e.g. `tmpHome` ->
`homes.tmpHome`).

Registries that enumerate test files were updated in lockstep:
- config/max-lines-baseline.txt: pruned 341 -> 258 entries (all 83 removed).
- config/reliability-gates.jsonc: 33 gates repointed at the split files, with
  assertionRefs split per file where a gate's coverage now spans several.
- .github/workflows/pr.yml: the real-zsh lane now lists the 4 split files that
  actually exercise zsh, so they keep running in the dedicated shell lane.

Also renamed agent-hooks `server-test-fixtures.ts` to `server.test-fixtures.ts`
so the global-fetch call-site audit keeps skipping it, and added `.js` extensions
to the CLI suites' dynamic harness imports (node16 resolution) to unbreak
`build:cli`.

Verification: full suite 52,449 passing vs 52,448 at baseline with zero
assertions lost; `pnpm lint`, `pnpm typecheck`, and `pnpm build:cli` all exit 0;
the terminal-pane e2e spec runs 31/31 headless.

* refactor(tests): split hook-idle arbitration suite that oxfmt pushed over budget

The pre-commit oxfmt pass reflowed pty-connection-hook-idle-arbitration.test.ts
to 811 effective lines, 11 over the test budget. Split the hook-completion side
effect and replacement-agent veto cases into their own suite; both files now sit
well under the cap and the 15 tests are unchanged.

* test: port upstream test changes into the split files after rebase

Rebasing onto main surfaced 27 tests that main had added to files this branch
deleted, plus edits to tests that had already moved. Taking the deletion side of
those modify/delete conflicts would have dropped that coverage silently, so each
upstream change is ported into the split file that now owns the behavior — for
example main's six orchestration mailbox tests land across orchestration-runs,
-send, and -check.

Also repoints `orchestration.notification-mailbox-consistency`, a gate main added
after this branch's gate remap, at those same three split files, and re-prunes
the max-lines baseline against main's (257 entries).

Verified: all 27 upstream test titles present; full suite 52,761 passing with the
only diff vs baseline being 12 tests main itself removed and 3 that moved from
skipped to passing; lint and typecheck exit 0.

* fix(test): flush pending continuations before tearing down terminal test globals

CI shard 5/16 failed on both Node 24 and 26 with `ReferenceError: window is not
defined` from pty-connection.ts, surfacing through
pty-connection-daemon-snapshot-replay.test.ts.

The reattach/settle chains `await` a real promise and then touch `window.api`.
Under fake timers those continuations cannot run, so they only become schedulable
once restoreTerminalTestGlobals() switches back to real timers — which previously
happened immediately before `delete globalThis.window`, so a late continuation
threw and failed the whole file. Flush async ticks in that window instead.

This is latent in the source rather than new: the pre-split 25k-line file kept
running other tests after these, which gave the chains time to settle before
teardown. Splitting the file moved teardown directly behind them.

* fix(test): keep an inert window after terminal test teardown instead of deleting it

The async-tick flush was not enough: the reattach/settle chain can resolve after
teardown regardless of how long we drain, so CI shard 5/16 still failed with
`ReferenceError: window is not defined` from pty-connection.ts.

A real renderer never loses `window`, so deleting it was the artificial part.
Swap in an inert proxy whose properties resolve to callables and whose calls
resolve to undefined, making a late `window.api.pty.*` call a harmless no-op.
The next test replaces it wholesale via installTerminalTestGlobals(), and no test
asserts that `window` is absent.
2026-08-15 00:54:20 -07:00
Jinwoo HongandJinwoo-H 1d8eaa81c7 fix(orchestration): preserve mailbox delivery identity (#13717)
* test(orchestration): reproduce mailbox pointer mismatch

* fix(orchestration): align pointers with actionable mailboxes

* fix(orchestration): harden pointer reservation lifecycle

* fix(orchestration): bound mailbox reconciliation

* fix(orchestration): make pointer staging restart-safe

* test(orchestration): expect scoped dispatch index

* fix(orchestration): guard skewed inbox indexes

* refactor(orchestration): extract mailbox notification lifecycle

* fix(orchestration): settle mailbox pointer writes

* fix(orchestration): bound mailbox recovery work

* fix(orchestration): fence inactive mailbox snapshots

* test(orchestration): cover mailbox notification boundary

* test(orchestration): pin STA-4325 delivery identity

* fix(orchestration): preserve mailbox delivery identity

* fix(orchestration): harden mailbox settlement

* test(orchestration): make mailbox gates self-contained

* fix(orchestration): preserve paged mailbox ownership

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-14 18:36:51 -07:00
Jinwoo Hong 500b72d8ef fix(vm): harden provisioned root ownership and cleanup (#14477)
* fix(vm): verify provisioned root ownership

* test(vm): retry transient removal menu

* test(vm): stabilize provisioned root teardown

* fix(vm): clarify recipe-owned cleanup

* fix(vm): pin provisioned root source commit

* fix(vm): make runtime cleanup user-cancellable
2026-08-14 19:04:55 -04:00
Jinwoo Hong 32f46f9a24 fix(vm): keep failed cleanup retryable (#14476) 2026-08-14 19:04:54 -04:00
Jinwoo Hong 8b22f044f5 fix(vm): preserve runtime sidecar rollback compatibility (#14444)
* test(vm): reproduce runtime store rollback poisoning

* fix(vm): keep runtime sidecar rollback-readable

* fix(vm): harden rollback-compatible runtime persistence

* fix(vm): publish rollback lifecycle authority first

* test(vm): harden rollback compatibility coverage
2026-08-14 19:04:54 -04:00
Jinwoo Hong ff9bc0f079 fix(orchestration): wait for Codex composer render (#14575) 2026-08-14 12:56:35 -07:00
Brennan Benson b3c4ba9ed3 fix(daemon): harden checkpoint admission after per-session isolation (#14385)
* fix(daemon): bound checkpoint overlay fanout

* fix(daemon): retain checkpoint admission through deadlines

* docs(reliability): refresh checkpoint gate evidence

* fix(daemon): prevent checkpoint admission starvation

* docs(reliability): clarify checkpoint bounds

* fix(daemon): distinguish checkpoint admission warnings

* fix(daemon): bound checkpoint admission diagnostics
2026-08-14 12:21:55 -07:00
Langning ZhangandJinwoo-H 92b6ffd17d Terminate renderer graph reload generations and contain disposed-frame notifications (#14070)
* fix(runtime): terminate renderer graph reload generations

* fix(runtime): harden renderer reload teardown

* fix(runtime): fence renderer graph publication ownership

* test(runtime): register renderer graph reload gate

* test(runtime): record live reload validation

* fix(runtime): ignore cancelled renderer navigations

* chore: preserve main formatting during branch sync

* chore: satisfy changed-code quality gate

* fix(runtime): restore cancelled renderer reloads

* fix(runtime): preserve committed reload fencing

* test(runtime): prove cancelled reload timeout

* docs(reliability): record reload cancellation oracle

---------

Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
2026-08-14 01:21:00 -07:00