* refactor: organize activity menu into sections and change toggle callbac
Restructure the activity thread options menu to use explicit boolean callbacks instead of toggle functions (rename onToggleUnread to onUnreadOnlyChange) and organize options into logical "Filters" and "View" sections. Remove descriptive tooltips for compact mode and unread filter. Rename ActivityScopeFilterMenuSections to ActivityScopeFilterMenuItems and shift layout responsibility to parent component.
* i18n
* fix issues
* i18n
* Hide empty Filters section in activity options menu
- Extract visibility logic into reusable hook `useActivityScopeFilterMenuItemsVisible` to avoid duplication
- Only render Filters label and items when filters are available, preventing empty section in dropdown
- Improves UX by not showing unused menu sections
* fix(pi): carry modal waits to mobile and stop losing the dialog close
Follow-ups to #18836, from its readiness review.
- Paint pi's `!` needs-input state marker while a dialog is open, so the
80ms spinner frame stops repainting a working title over a mid-turn
wait. Mobile and the CLI read the title, so they saw `working` where
the desktop already showed `waiting`.
- Keep the assistant reply that lands while a dialog is open. The modal
guard cleared tool fields and the `message_end` capture with them, so
a turn ending under a dialog left the preview on the previous message.
- Report `ui_prompt_end` even when `ctx.isIdle()` throws on a runner the
modal itself invalidated; the lost post stranded the pane on `waiting`.
- Declare the `esbuild` the runtime smoke tool imports.
* fix(pi): hold the needs-input marker until the dialog actually closes
From review of the previous commit.
- Settling under an open dialog no longer retires the marker. stopAnimation
painted the plain title unconditionally, so agent_settled, a resolved
agent_end, or an idle auto_compaction_end erased it mid-dialog — and
because that also cleared the timer, the close then painted the plain
title again and the wait was lost for good.
- Track the dialog as a boolean, not a depth counter. Pi does its own
nesting accounting and emits one pair per stack, which is what the status
extension already assumes; two files disagreeing on that would have let an
inner close release the outer wait.
- Reset the flag on agent_start in both extensions. A turn cannot begin under
a dialog holding input focus, so it is the one boundary that can recover a
close that never arrived instead of pinning the pane forever.
- Leave OMP to its approval events: it reports waits through those already,
and painting the marker there too would put title and hook in disagreement.
* fix(pi): do not ring the completion bell for a dialog that lost its close
From review of the previous commit.
- Report working, not done, when ui_prompt_end's isIdle() throws. done is
not cosmetic: it reaches dispatchCompletion and fires the pane's finished
notification, so a turn that is still running would announce itself. The
real done still arrives from agent_end/agent_settled.
- Keep the idle-maintenance frame cap accruing while a dialog holds the
title, so a dialog left open cannot suspend the guard that stops a
compaction spinner whose end event never came.
- Guard the dialog handlers against a ctx without ui. The source is
generated and untypechecked, and pi does not document the ctx it passes
these two events; a TypeError there would surface on every dialog.
* fix(pi): let a turn still complete after a dialog loses its runner
From review of the previous commit.
- Re-arm the completion report when ui_prompt_end's isIdle() throws. The
fallback posts working, but the finished turn had already reported its
end, so nothing further would ever fire and an idle pane sat spinning.
- Count dialog depth in both extensions instead of trusting pi to emit one
pair per stack. The guarantee is undocumented, and if it ever does emit a
pair per dialog, an inner close would release the wait the outer dialog
still holds. A counter costs nothing and drops the dependency.
* fix(pi): decide a dialog close from turn state, not from a guess
From review of the previous commit.
- Fall back to agentEndReported when ctx.isIdle is unavailable or throws.
The previous guess of working stranded the common case — a dialog opened
at idle — because no later event was coming to correct it, and the
agentEndReported re-arm it relied on could not fire either. A turn that
already reported its end is not still running, and that is knowledge this
process holds without needing ctx at all.
- Only suppress spinner frames once the marker is actually painted. Pi may
pass a ctx with no ui, and freezing the title on its last working frame
is the opposite of what the marker is for.
- Gate the titlebar dialog handlers on the OMP runtime too, not just the
installed kind: a bare-shell OMP launch runs inside a pi-kind pane, and
the status extension already defers there. Extracted that check so both
extensions share it rather than carrying two copies.
* fix(pi): treat a pane that never ran a turn as idle, not busy
From review of the previous commit.
- Track turn-in-flight separately from agentEndReported. That flag also
dedupes the completion post, so it starts false on a pane that has not
run a turn — which read as still-running and left a dialog opened before
the first prompt spinning forever.
- Retry the marker paint on each dialog open instead of only the outermost,
so an outer ctx without ui cannot decide the whole nested stack goes
unmarked.
- Fall back to the opening ctx when the close carries no ui. Nothing else
clears the needs-input marker, so the pane would have kept asking for
attention until the next turn.
* fix(pi): keep a dying dialog ctx from stranding the needs-input marker
The close path paints through the ctx captured at open time, which is the
one a session-switching modal is most likely to have invalidated. Guard
both paint sites so a throw cannot reject the handler and leave the title
on the needs-input marker, and make local turn state the floor for the
status extension's idleness verdict instead of a fallback.
* fix(pi): hold the dialog wait against pi's own title writes and lost closes
Reviewed against real Pi 0.85.1 source rather than inference:
- ctx.ui is a getter that calls assertActive() and throws once a session-
replacing dialog invalidates the runner, so optional chaining never
screened it out and the probe sat outside the try. A throw landed after
the depth decrement but before markerPainted cleared, stranding the
needs-input marker until the next turn.
- Pi writes the same terminal title from its own writers with no event we
observe, so the marker is now re-asserted rather than merely not
overwritten, on a slow timer that outlives the spinner and its cap.
- resetExtensionUI drops an open dialog without resolving its promise, so
a replaced or reloaded session never emits the matching ui_prompt_end.
Both extensions now release the wait on session_start and shutdown.
* fix(pi): build the title inside the guard, not as an argument to it
paintTitle caught the setTitle throw but not the two calls one argument to
its left: pi.getSessionName() asserts runner liveness the same way ctx.ui
does, and process.cwd() throws ENOENT once the worktree is unlinked under a
live pane. Four of the six call sites are timer callbacks, where an escape
is an uncaught exception and pi exits(1) through its own handler — so the
cwd route was reachable today. paintTitle now takes a builder and runs it
inside the existing try.
* fix(pi): let only the pane-owning process assert the needs-input marker
The spinner is harmlessly per-process, but the marker is status the pane
reports, and child agents inherit ORCA_PANE_KEY. Gate the two dialog
handlers on a PID claim, mirroring ORCA_PI_STATUS_OWNED in the status hook.
* Remove settled-worker automatic resume and hibernation fences
* test: retirement rollback case follows the no-fence policy
Case 4 seeded and asserted automaticResumeBlockedBy, which this branch
deletes. A rolled-back settled worker is now an ordinary done record that
wake clears as passive evidence, same as any finished agent pane.
* chore(i18n): regenerate the runtime-required catalog for the contrast floor strings
* test(orchestration): give the stopping-worker guard fixtures a Run
The 7 auto.components.settings.TerminalPane.minimumContrast.* entries added by #18126 have zero call sites; the shipped component reads settings.contrast.*. Because the runtime-required catalog classifies any key with no literal-default call site as required, the orphans broke 'Verify runtime-required localization catalog' on main and red-lit every PR in the repo.
Deleting them is the root-cause fix: regenerating would instead add dead strings to the boot bundle. On main+delete, --fix regenerates a byte-identical catalog and the CI step exits 0.
Merged with 'test / tests node 24 3/8' red: that failure is an unrelated main break from the #19542/#19551 collision, not from this change.
* feat(terminal): make the contrast floor user-configurable (#10754)
The xterm minimumContrastRatio floor was hardcoded (3 on dark backgrounds,
4.5 on light) and applied to every pane with no way out, so TUIs that use
deliberately low contrast were rewritten: Powerline separators drawn in the
neighbouring segment's background became visible seams, and dimmed secondary
text lost its hierarchy.
Adds an optional `terminalMinimumContrastRatio` setting under Settings ->
Terminal -> Rendering. Blank keeps today's automatic, background-luminance
gated floor; 1 disables correction entirely (matching VS Code's documented
`terminal.integrated.minimumContrastRatio` and iTerm2's off-by-default
Minimum Contrast); values are clamped to xterm's 1-21 range.
The floor is resolved in one place, so live panes, the Appearance preview
and the dashboard terminal preview all follow it, and the existing
value-gated write still avoids clearing xterm's contrast cache on no-op
re-applies. The clamp also lives at the persistence boundary that every
writer crosses, so a hand-edited profile or CLI write can never hand xterm
a non-finite option. Mobile mirrors the desktop gate, so the resolved floor
travels with the terminal theme payload as a new optional field; hosts that
omit it leave older and newer clients on the luminance gate.
Fixes#10754.
Co-authored-by: Nyanako <44753291+Nanako0129@users.noreply.github.com>
* fix(terminal): refresh mobile payload fixture and clarify contrast target
* feat(terminal): make contrast controls intent-based with custom tuning
---------
Co-authored-by: Nyanako <44753291+Nanako0129@users.noreply.github.com>
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Adds the missing removal path to the host-authoritative layout reconciler, so a pane the host has retired is unmounted once its PTY has cleared. Fixes#17770.
The removal planner, its retired-set gate, the null-PTY guard, the never-last-pane guard and their unit tests originate from #18387 by @ylcn91. This PR adds the recovery-state dependency that makes the deferred removal actually re-run, an e2e regression spec, and a hook-parity repin.
Co-authored-by: ylcn91 <7249450+ylcn91@users.noreply.github.com>
A Windows hook whose target file is missing fell back to reading stdin and throwing it away. That read never returns when the caller abandons the pipe, which is what happens outside an Orca pane — one stuck process and a visible console per hook event (#11549).
The rule 'check the Orca env before you own stdin' existed once in cmd syntax and was retyped by hand elsewhere, so the PowerShell and Git Bash launchers never got it. Derive all three dialects from one list of vars and apply them wherever a missing target makes the caller the stdin owner.
- wrapWindowsHookCommand and the runtime-home PowerShell branch guard before ReadToEnd, and emit the fallback answer before the guard so a gate event outside a pane is not answered with silence.
- The runtime-home Git Bash fallback picks its rule by platform: POSIX keeps capture-first (#8110), Windows answers, guards, then drains.
- The Antigravity wrapper disables delayed expansion like its core; with a '!' in the hooks path it was missing the core on every event (#9358/#9941).
Tests drive the wrapper through the production 'cmd /d /c' chain under both delayed-expansion states, and the cross-agent ratchet covers the launchers with an abandoned pipe rather than requiring the unguarded drain.
* perf: select highest usage totals without full sorting
* test(usage): pin first-inserted tie-break contract for highestUsageKey
Document why the strict > and the NaN sort fallback are load-bearing, and
cover the tie/re-set ordering the replaced stable sort guaranteed.
---------
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* perf: lazily index case-insensitive Windows environment keys
* test(windows): pin env expansion fallback against the per-miss lookup oracle
Adds zero-enumeration, first-case-variant-wins, prototype-chain and 4,000-case
randomized differential coverage, and groups the new cases under their describe.
---------
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
`extractPartialEscapeTail` broke its own fold invariant
(extract(a + b) === extract(extract(a) + b)) in the oscEsc/stringEsc states,
so a PTY read that split there produced a different pending tail than the
same bytes delivered whole — the tail snapshots append after a restore.
Two causes, both in the "ESC did not terminate the string" branch:
- CAN/SUB were routed through `stateAfterEscByte`, which maps them back to
`esc` instead of aborting to ground. `extractPartialEscapeTail('\x1bPx\x1b\x18X0abc')`
returned '\x1b\x18X0abc'; the chunk-split fold returned ''.
- A second ESC opened its new sequence at `i - 1` rather than at itself.
`extractPartialEscapeTail('\x1b] \x1b\x1b^')` returned '\x1b\x1b^' whole but
'\x1b^' folded. The fold was right — xterm starts the sequence at the second ESC.
The existing fuzz only asserted the fold as `advance(extract(pending), chunk)`,
which is a tautology because every PENDINGS entry is already a tail. Replaced
with a sweep that re-splits the combined stream at every code-unit boundary,
and extended the alphabet (NUL, 0x20 intermediate, CJK) and SEQUENCES with
CAN/SUB and doubled-ESC-inside-string cases. A 1.25M-split fold fuzz over a
VT alphabet goes from 421 failures to 0.
* perf(native-chat): preserve historical tool rows while streaming
* perf(native-chat): short-circuit identical rows and lock producer immutability
Most folded rows come back as the input object, so compare identity before
scanning fields and blocks. Add a regression test for the invariant the reuse
cache depends on: ordering and folding never rewrite producer-owned messages
or blocks, which reused rows alias.
* perf(native-chat): bound journal reads during paged catch-up
* perf(native-chat): reduce the journal once per catch-up run, not per page
Bounding the SQL read per page left the JS side still O(total items) per
page: every page re-reduced the whole timeline and rebuilt the live-item
map, and the byte-shrink loop rebuilt it again on each halving.
A catch-up run is a synchronous loop with no await between pages, so the
reduced timeline is loop-invariant. `createAgentSessionCatchUpReader`
holds one snapshot for the run and re-reduces only if the journal cursor
actually moved, and the projection's live-item / alias / submission-byte
indexes memoize on the snapshot arrays the reducer rebuilds on change.
Per catch-up over a 8,000-message backlog: 40 timeline reductions to 1,
reduce+project time 24.3ms to 3.5ms, end-to-end 134.6ms to 111.3ms.
* fix(remote): keep terminal tabs syncing after orphan recovery
* fix(remote): validate recovery snapshots (#19065)
Address CodeRabbit feedback discussion_r3943677920 by validating the complete session-tabs payload before orphan recovery can publish it. Reject malformed rows and metadata as a whole while preserving optional and unknown additive fields.
Add validation and recovery/mirror regressions proving invalid follow-up reads retain the previous inventory and retry successfully.
Validation: 805 tests passed across 49 files; all four changed files pass Oxlint 1.80.0.
Note: pre-existing web typecheck errors in psl/emojibase-data resolution and export-let-function-initializer-ban.test.ts are unchanged from upstream.
* fix(remote): test reachable pending recovery states (#19065)
Address Pullfrog feedback discussion_r3943701696 by removing the retirement guard the host projection cannot reach and validating both affected fixtures through real host finalization.
Cover exact retirement, ready rebinding, pending/no-proof retention, and newer pending rows surviving prior authoritative removal. Preserve the host wire format and retain-on-unverifiable policy.
Validation: 807 tests passed across 50 files; all five changed files pass Oxlint 1.80.0.
Note: pre-existing web typecheck errors in psl/emojibase-data resolution and export-let-function-initializer-ban.test.ts remain unchanged.
* fix(remote): keep recovery reads tolerant of newer hosts (#19065)
Narrow the post-adoption snapshot validator to the fields recovery and the
mirror's coordinate logic actually consume. The previous schema closed every
enum and discriminant on the session-tab channel, so a host that published an
unknown agent name, status state, or tab kind failed the whole parse and
recovery retained forever - the same permanently-invisible-terminal symptom
this PR fixes. Unknown labels now pass through; structural defects in consumed
fields (coordinates, handles, groups, layouts, active selection) still fail
closed, and the three adoption regressions pinning that keep passing.
Replace the cyclic-layout test, which assumed a zod v3 stack overflow that
zod v4 cycle-detects away, with a throwing-accessor case that exercises the
same fail-closed branch.
Thread expectedRuntimeId through refreshWebRuntimeSessionTabsSnapshot so the
fifth recovery call site fences its post-adoption read like the other four.
Takes over stablyai/orca#19065 from its original author.
Co-authored-by: Shahar Mor <shaharmor1@gmail.com>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* Update PR checks fix prompt to verify failure causality before fixing
Revise the prompt to classify failures as caused by this branch, not caused,
or uncertain before making changes. Only proceed autonomously for confirmed
failures; ask the user for guidance on uncertain or unrelated issues to avoid
fixing failures that weren't caused by the branch.
* Update PR checks fix prompt to verify failure causality before fixing
- Emphasize investigation phase by reframing prompt: "Investigate" rather than "Fix"
- Extend untrusted-data warning to all investigation sources (repository files, commit messages, diffs, CI output)
- Add test verifying injection safety: malicious input confined to JSON payloads, never as prompt instructions
* Refactor buildFixChecksPrompt test to focus on field mapping
The wrapper's only responsibility is renaming mobile PR fields onto the
shared prompt builder. Remove assertions about prompt wording, which are
already covered by the builder's own test suite. Simplify the test to
verify the field mapping contract and nothing else.
- Update automations API to use runtime.call pattern with automation.create
- Refactor browser creation flow to use state helpers instead of file explorer
- Simplify Playwright selectors and context menu interactions
- Remove fixture file creation from test setup
Monitoring turns emit no working event (4b2e3dded0), so the titlebar
badge must not count them either, or it lights with no unread row to
clear. The build cache's cached-events ternary could never take its
cached path because the early return above already covers it.
* Fix native PTY I/O failures disabling session termination
I/O failures on write or resize were incorrectly treated as exit evidence,
which disabled further termination attempts and producer flow control.
Separate ioFailed state from dead state; I/O errors suppress operations
but keep kill, forceKill, and signal available. Publish physical exit
before notifying listeners to prevent reentrant cleanup attempts from
accessing the retired native process.
* Fix PTY I/O cleanup tests and config syntax error
- Fixed missing closing brace and comma in reliability-gates.jsonc
- Added clear() operation to PTY mock fixture and test coverage
- Enhanced assertions to verify operation suppression during I/O failures
- Improved kill operation error handling with better promise-chain assertions
- Updated test result summaries in reliability gate documentation
* refactor(activity): rank status groups by attention level
Establishes consistent group ordering by introducing an attention-based ranking system, ensuring status groups maintain a fixed order regardless of thread recency. Consolidates thread status classification logic into `activityThreadStatusId` and simplifies group key naming.
* refactor(activity): emit working state for live agent turns
Activity events now emit working state for current turns,
enabling attention ranking above historical states.
* fix(activity): preserve working turns and count as unread
- Remove working-state events from cap logic so live turns stay visible
- Count fresh working/monitoring as unread in Activity badge
- Extract state-checking to activity-event-state module
- Use agentStatusEpoch for freshness-based invalidation
* fix(activity): subscribe only to epoch for unread count, not status map
The unread receipt is keyed on turn boundaries (stateStartedAt), not
heartbeats (updatedAt). Only the epoch matters; read the status map
directly via getState() to avoid wasteful re-renders on same-turn
heartbeats.
* fix(activity): prevent monitoring turns from emitting working events
Monitoring turns should surface only via the 'monitoring' snapshot in
the live state, not as separate working events that would contradict
the snapshot signal.
* Stabilize scrollbar gutter to prevent message list layout shift
- Add `scrollbar-gutter:stable` to prevent reflow when scrollbar appears
- Adjust scroll container padding to properly accommodate the scrollbar
- Add 5px horizontal inset to content for alignment with composer field
* Simplify message list padding and update scrollbar-gutter
emitPinnedGroup was the one section emitter that appended worktree rows
without hostContextLabelByWorktreeIdentity, and the mixed-host map it
would have received was computed over naturalWorktrees, which under the
default pinned policy has the pinned worktrees filtered out. Under that
policy a pinned worktree renders only in the Pinned section, so a pinned
remote workspace had no host badge anywhere.
Compute the mixed-host map over the full worktree set and thread it into
the Pinned emitter. Single-host sidebars still draw no badge.
Fixes#18472
A paired runtime host's first status publication counted as a connection
change, advancing the connection generation. Worktree scans already in
flight against that same connection were then discarded, so the sidebar
showed a strict subset of the host's worktrees until an unrelated refresh.
Two independent defects, both fixed:
- `connectionChanged` conflated "no entry yet" with "recorded unreachable".
Only the latter is a reconnect. The provider-session bump keeps the
broader predicate, since a first publication is a real session start for
integration-readiness caches.
- A stale-generation result was thrown away with no retry, so even a
genuine mid-flight reconnect silently dropped completed work. The scan is
now re-read once against the new generation.
* test: cover keyboard input in five simultaneously flooding SSH panes
* test: capture pane focus and buffers on flood input failure
* test: capture pane focus and buffers on flood input failure
* test: capture pane focus and buffers on flood input failure
* test: record replay input loss and application fix dependency
* test: record merged replay-input fix in the five-pane flood gate