Commit Graph
762 Commits
Author SHA1 Message Date
Jinjing e361da7fb7 Deleting skill (#16357)
* Add skill deletion with cross-platform transaction safety

Implements end-to-end skill removal with placement enumeration, dependency guards, and transactional recovery. Covers native, WSL, and remote hosts; users can delete canonical directories and alias placements (symlinked directories or files) in a single atomic batch. Includes UI selection flow, preview, confirmation, and results band. Block reasons (bundled, plugin, unowned, stale) gate deletions that would fail or contradict user intent.

* Organize IPC handlers into module subdirectories

Move register-core-handlers and skill-delete-ipc-handlers into
dedicated subdirectories for improved code organization and to
reduce the flat structure in src/main/ipc/.

* Make skill deletion recovery transactions idempotent

Defer journal cleanup until both staging removal and receipt cleanup succeed, leaving the journal in place for startup to retry if either operation fails. This ensures the recovery process is safe to run multiple times without leaving partially-deleted skills.

* Consolidate skill-delete files into dedicated module

Reorganize skill deletion functionality into a modular structure under
`src/main/skills/skill-delete/` with simplified file names. Remove the
redundant `skill-delete-` prefix from file names since they now live in
the dedicated directory. Update all import paths throughout the codebase
to reflect the new structure, including imports from IPC handlers and
RPC methods.

* Fix broken import paths and add deletion robustness improvements

Import paths using `..//'` were invalid and broken. Replace with explicit
module names (`skill-discovery-sources`, `skill-install-filesystem`, etc.)
to clarify dependencies.

- Bind WSL filesystem methods to preserve `this` context
- Keep recovery journal when rollback rename fails, so startup can retry
- Skip symlink-based tests on Windows where they cannot run
- Only treat ENOENT/ENOTDIR as empty directories; propagate other errors
- Fix cross-platform path parent calculation to handle drive roots
- Replace shared constant with localized string for user-facing message
- Use `runProcess` for WSL integration test instead of bare `execFile`

* Add batch limit for skill deletion and improve host availability checkin

- Limit concurrent deletions to prevent remote host overload
- Add retry logic for capability probing to handle transient unavailability
- Add reprobe() method to recheck capability after errors or user refresh
- Fix status logic: receipt cleanup is best-effort, completion depends only on content removal
- Improve error message for unreachable hosts
2026-08-25 03:58:48 -07:00
NeilandNeil 822087c8ec refactor(git): split runner.ts into focused command-runner modules (#16395)
* refactor(git): split runner.ts into focused command-runner modules

* chore(ratchets): repoint child_process and wsl.exe allowlists at the split modules

---------

Co-authored-by: Neil <n@example.com>
2026-08-25 02:34:11 -07:00
Neil 1f39c93b01 refactor(ipc): split ssh.ts into focused modules (#16394) 2026-08-25 02:32:21 -07:00
Neil 33587fb77d refactor(git): split status.ts into source-control modules (#16393) 2026-08-25 02:24:11 -07:00
Neil 127fa7fae0 refactor(ipc): split repos.ts into focused modules (#16392)
* refactor(ipc): split repos.ts into focused modules

* test: point repo notification mocks at the extracted module

* fix(ipc): repoint the child-process allowlists after the repos split

The type-only `import type { ChildProcess }` moved from repos.ts to
repos/repo-clone-lifecycle.ts, so the import-boundary entry follows it and the
windows-console entry (now stale, and that list only shrinks) is dropped.
Fixture-only; the base file had no runtime child_process use at all.
2026-08-25 02:23:53 -07:00
Neil 8217e6838f refactor runtime contracts and web transports (#16197)
* refactor runtime contracts and transports

* test(web): repoint two-phase timeout seam at the transport that now owns call()
2026-08-25 00:59:29 -07:00
Neil d456549c22 refactor runtime Git bridges (#16189)
* refactor runtime git bridges

* fix(test): avoid computed namespace import access in runtime Git client contract test
2026-08-25 00:54:43 -07:00
Neil f482725355 refactor terminal coordination modules (#16335)
* Refactor terminal coordination modules

* preserve terminal completion and stale-connect guards

* restore pre-spawn E2E barrier and stale-connect check order in ipc-pty-connect

* restore merge-base title-working replay and stamped-tail delete semantics

* fix(terminal): merge the duplicated shortcut-matching import

Two adjacent imports of the same module tripped oxlint's
no-duplicate-imports under --deny-warnings. Import-only; no behavior change.
2026-08-25 00:53:54 -07:00
Neil fc0d947983 refactor(rate-limits): split Codex and Claude fetchers (#16140)
* refactor(rate-limits): split Codex and Claude fetchers

* refactor(rate-limits): restore base error-message defaulting

The split moved the 'Unknown error' fallback from inside String() to the call site, which changed behavior for an Error with an empty .message: base surfaced '', head surfaced 'Unknown error'. Restore the base form.
2026-08-25 00:53:44 -07:00
Neil 5ebc4bb2bc refactor(renderer): split repos store slice (#16191)
* refactor(renderer): split repos store slice

* test(store): align repos/folder-workspace characterization tests with main's owner-scoped delete + host-qualified visit recency

* fix(composer-state): repoint RepoUpdate import to repos/repo-state after split
2026-08-25 00:42:59 -07:00
Neil a782935ab3 refactor(checks): split panel responsibilities (#16186)
* refactor(checks): split panel responsibilities

* fix(checks): consolidate comment audience import

* fix(checks): handle open review state exhaustively

* fix(checks): restore merge-base check presentation values

The split into check-presentation.tsx silently changed icon, opacity, and
PR-state token values. Restore them verbatim.
2026-08-25 00:33:36 -07:00
Neil 83ffc0df24 refactor Electron facilities modules (#16333)
* refactor oversized Electron facilities

* fix interactive process timeout and shortcut repeat guard

* chore(child-process): drop stale cli-installer allowlist entry

cli-installer.ts now routes privileged spawns through runProcess via
cli-privileged-processes.ts, so the shrink-only ratchet flags it as stale.

* refactor(child-process): extract the bounded output sink

runProcess's timeoutMs opt-out (required to preserve the unbounded osascript
admin prompt) pushed run-process.ts past the 300-line cap. Move createOutputSink
to its own module rather than add a max-lines bypass, which AGENTS.md forbids.
Moved verbatim; no behavior change.
2026-08-25 00:31:31 -07:00
Neil 1cf562deea refactor: split source control AI modules (#16179) 2026-08-25 00:31:06 -07:00
Neil e0bc4e1126 refactor(editor): split notebook, editor, and external-watch surfaces (#16143)
* refactor(editor): split editor and watch surfaces

* fix(editor): revert behavior changes smuggled into the surface split

Restore merge-base React keys in IpynbCellOutputs: the content-identity keys
JSON.stringify'd every output value, including raw base64 image payloads, on
every keystroke.

Collapse the duplicated lazy() declarations into editor-lazy-views so each
viewer keeps a single React.lazy identity across the extracted surfaces.
2026-08-25 00:30:47 -07:00
Neil 5c3df831b7 refactor composer and jira slice modules (#16196) 2026-08-24 23:49:08 -07:00
Neil 6faaf3af74 fix(lint): match moved code by ordered near-match, not strict contiguity (#16385)
A diagnostic's span often reaches past the block a split moved — most commonly
to a hook dependency array, which legitimately grows when closure variables
become props. Requiring every line of the span to match contiguously reported
the moved body as new.

The block must still start at the same line in the base and appear in order,
and >=90% of it must be present. Genuinely new code shares neither the anchor
nor the ordering.
2026-08-24 23:17:04 -07:00
Neil 48e63c015f refactor agent config and auth services (#16195)
* refactor: split agent config and auth services

* chore: repoint wsl and global-fetch guards at split module paths

* fix: restore merge-base Claude CLI error propagation

Drop the secret-redaction rewriting added to Claude CLI error paths in the
refactor: spawn errors again reject with the original Error (preserving
.code/.errno/.syscall/.stack) and command output/auth-status logs are no
longer rewritten.
2026-08-24 23:15:01 -07:00
Neil 32ffdd2b9a refactor(renderer): split composer state (#16182)
* refactor(renderer): split composer state

* fix(renderer): satisfy composer static analysis

* test(renderer): migrate composer boundary contracts

* fix(composer): restore project group reset effect

Revert read-side mask back to the merge-base state clear so a momentarily
unavailable host permanently drops the folder group instead of silently
retargeting Create when the host reappears.
2026-08-24 23:14:42 -07:00
Neil 20c1a61401 fix(orcad): answer host paths honestly and ship the watcher child (#16369)
orcad's AppEnvironment implemented three of seven AppPathNames and returned the
userData directory for the rest — including 'exe', where a data directory is not
an executable. Every name now has a Node answer: 'appData' is the platform's
per-user application-data root, 'logs' lives inside the data root so a headless
deployment stays one removable directory, 'downloads' honours XDG_DOWNLOAD_DIR,
and 'exe' is the Node binary. getAppPath() is the directory orcad was launched
from rather than cwd, so children resolve against the bundle instead of wherever
the supervisor happened to be.

The watcher child was the load-bearing consequence: resolveWatcherProcessEntryPath
probed for the adjacent entry only when !isPackaged, so orcad resolved a desktop
out/main path that no deployment has — and build-orcad never emitted the child
anyway. isPackaged stays true (consumers read it as "production, not a dev
checkout" and it gates HTTPS-only skill downloads); the resolver now asks whether
the app root is an asar archive, which is the question it actually meant. The
child ships beside orcad.js, and the build forks it to prove it runs.
2026-08-24 23:01:17 -07:00
Neil c4988fa81c refactor workspace lifecycle modules (#16334)
* refactor(workspaces): split lifecycle modules

* preserve workspace cleanup consent contract

* restore workspace delete shortcut hint in context menu view

* restore host-qualified visit recency and viewed-candidate predicate

* test(cleanup): pin the viewed-mark upgrade path and host-qualified visit reads

Two invariants a refactor broke in this PR, both silent:

- viewed marks are persisted, so gating `shouldPreserveCleanupInspection` on any
  newer field voids the grace period for every entry written by an older build
- visits are stamped under `${hostId}|${worktreeId}` whenever the host is known
  (the normal case, including 'local'), so a bare map[worktreeId] read misses
  every modern entry and yields 0, disabling the recent-visible-context blocker

Verified discriminating: reintroducing each bug fails exactly its own test.
2026-08-24 22:44:08 -07:00
Neil 33c9353f29 fix(lint): exempt verbatim-moved code from the changed-lines quality gate (#16359)
A file-splitting refactor makes every line of the new module an added line, so
pre-existing lint debt in code that merely moved starts failing the gate. The
only way to satisfy it is to edit the moved code, which is what a
behavior-preserving refactor must not do. Exempt a diagnostic when its
highlighted lines already existed verbatim and contiguous in the base revision.
2026-08-24 22:43:24 -07:00
Neil e217fdd10f build(orcad): gate orcad's own graph, and prove it loads under plain Node (#16368)
* fix(orcad): close the browser-provider gaps

The providers landed without enforced coverage, so a regression in either path
would have landed silently.

- CI: the external-Chromium integration test was gated on ORCA_BROWSER_EXECUTABLE
  and nothing ever set it, so it skipped forever. It now runs in its own job
  against the runner's Chrome and FAILS when Chrome is absent rather than
  skipping, because an unset variable is exactly how it went uncovered. Timeout
  raised to 120s: a warm run is ~7s but the first launch against an unseeded
  profile took 30s and hit Vitest's default, and CI is always that cold case.
- Electron provider had no test at all. It is the path anyone with the desktop
  app hits.
- Browser unavailability reported one message for four causes, including telling
  an operator to set a variable they had already set.

Fixes a live defect found while covering it: the runtime advertises
browser.tabCreate.known-id.v1 unconditionally, so a web client sends a
provisional page id for a page that does not exist yet — and the sidecar's
generic requestedPageId branch ran require() on it first and threw. Every
known-id create against the Electron provider failed. The adoption logic was
already there; only the ordering was wrong.

Also updates the workflow-parallelism guard, which correctly caught the new job
missing from verify's required-check list, and asserts verify actually reads it.

* build(orcad): gate orcad's own graph, and prove it loads under plain Node

Two gaps the artifact's own comment asked for.

The ratchet measured only orca-runtime + runtime-rpc, but orcad imports ipc/pty
directly to install the PTY controller, so its graph is strictly larger. The gate
could read zero while the shipped artifact regressed. orcad's entry is now a
ratchet entry point, and the baseline stays empty with it included.

orcad cannot join plain-node-entry-guard — that is a rollup plugin keyed on
electron-vite input names, and orcad is an esbuild artifact. But the half that
matters here is the guard's smoke-load: scanning the metafile proves no module
NAMES electron, not that the graph resolves under plain Node. A dynamic require,
a missing native or a top-level throw all pass the scan and fail at runtime.
build-orcad now runs the bundle with a bogus flag and requires the argv rejection
that only a fully loaded graph can produce.

Verified: a bundle that builds but throws on load fails the gate.
2026-08-24 22:21:26 -07:00
Neil f7033e0e70 build(windows): drop the packaged node-pty prebuild that can silently replace the patch (#16350)
* build(windows): drop the packaged node-pty prebuild that can silently replace the patch

node-pty's loader tries build/Release, then build/Debug, then
prebuilds/<platform>-<arch>, and swallows every failure in between. Windows
packaging ships both the source build and the prebuild, and only the source
build carries Orca's job-object exports (listJobProcessIds, terminateJob,
assignCurrentProcessToJob).

So an ABI mismatch, a truncated file, or an AV quarantine of
build/Release/conpty.node degrades the shipped app to the UNPATCHED prebuild:
PTY teardown silently falls back to guessing by PID ancestry, with no error
anywhere. That is the failure mode that made #16059 hard to see -- an install
that looks fine and quietly cannot own a PTY tree.

Removing the fallback turns a silent downgrade into a loud load failure.

Scoped narrowly: only win32, and only when the source build is actually
present, so a build that legitimately has no build/Release keeps something
loadable. macOS and Linux prebuilds are untouched -- they have no patched
export to lose.

Refs #16059.

* fix: delete only the stale conpty fallback, not the whole prebuilds tree

Review caught a P0 in the first version of this change, and it was the same
defect the PR exists to prevent, pointed at a different target.

Orca's own patch removes the `conpty_console_list` and winpty `pty` gyp
targets, so a Windows source build emits conpty.node and nothing else.
conpty_console_list.node, pty.node, winpty.dll and winpty-agent.exe therefore
exist ONLY in prebuilds/. Deleting the tree removed them:

- the forked console-list agent throws at require, and its caller resolves null
  with silent: true, so console-membership probing dies with no log anywhere --
  a new silent degradation, in a PR whose thesis is "make it loud";
- node-pty still selects winpty below Windows build 18309, so PTY spawn would
  fail outright on Server 2019 / Win10 LTSC 2019.

Now removes only prebuilds/win32-<arch>/conpty{.node,.pdb}, and only when
electronArch matches the host arch -- a cross-arch package copies the host's
build/Release, so its presence does not mean it matches the target, and
deleting the target-arch prebuild would remove the only loadable binary.

The old fixture wrote just conpty.node, so it could not see any of this. It now
seeds a realistic prebuilds directory, and four tests assert each sibling
survives; all four fail against the broad delete.

Credit: review counsel.
2026-08-24 21:43:22 -07:00
Neil 8043b5f705 Refactor worktrees IPC into cohesive modules (#16190)
* refactor worktrees IPC into cohesive modules

* test update PTY waiver invariant paths

* revert unrelated oxfmt reflow from merge commit
2026-08-24 21:19:11 -07:00
Neil d8807801ff Refactor GitHub Project surfaces and mutations (#16166)
* refactor github project modules

* restore dropped issue #4756 guard comments
2026-08-24 21:18:55 -07:00
Neil 087b895524 refactor(daemon): split oversized PTY services (#16160)
* refactor(daemon): split oversized PTY services

* revert(daemon): restore merge-base session listing and canceled-spawn behavior

Two behavior changes rode along with the file-splitting refactor:

- listLiveTerminalHostSessions dropped sessions with isTerminating, not just
  dead ones, hiding sessions the merge base still advertised.
- spawnAndPublishSession called session.beginTermination() before publishing a
  canceled spawn into the host map.

Both hunks are reverted to the merge base; the refactor is untouched.
2026-08-24 21:18:11 -07:00
Neil 09048c63d4 feat(orcad): add headless browser providers (#16193)
* feat(orcad): add headless browser providers

* fix(orcad): merge the duplicate runtime-browser type import
2026-08-24 21:11:45 -07:00
Neil 5869e6d39e refactor Linear workspace surfaces (#16332)
* refactor Linear workspace surfaces

* refactor(linear): restore merge-base behavior in split modules

The Linear surface split smuggled in three behavior changes; revert them
so the refactor is a pure move.

- detail-state: drop 'project' from EDITED_LINEAR_ISSUE_FIELDS. List
  issues never carry `project` (only getIssue maps it), so preserving it
  across hydration permanently blanked the hydrated project whenever an
  edit landed while linearGetIssue was in flight.
- detail-state: handleProjectChanged no longer sets hasEditedRef.
- project-selector: remove the mountedRef/requestId guards around the
  global patchLinearIssue write and the success/error toasts.
- sub-issues: remove the added isComposing guard on the title Enter key.

The detail-state test asserted the smuggled project-preservation; updated
to assert hydration owns `project`.
2026-08-24 20:57:53 -07:00
Neil ec12a0e442 refactor settings maintenance modules (#16169)
* refactor settings maintenance modules

* revert behavior changes smuggled into settings split

- hoist isAdvancedOpen state back into RepositoryHooksSection so it survives
  SearchableSetting unmount during settings search
- drop isComposing guards absent from the merge-base AgentsPane handlers
- restore merge-base JSX for the 'when one exists.' fragment (no separator)
2026-08-24 20:57:37 -07:00
Neil d74f469379 refactor(feature-wall): split animated browser and terminal stories (#16142)
* refactor feature wall animated visuals

* fix(feature-wall): restore merge-base render behavior in split visuals

- Hoist workbench reduced-motion state to module constants so cursorTarget
  identity is stable and the cursor layout effect stops re-firing per render.
- Render one frame component and branch on the state source so toggling
  reducedMotion re-renders the storyboard instead of remounting its DOM.
2026-08-24 20:57:21 -07:00
Neil b6a24ecac1 refactor: split GitHub backend modules (#16194) 2026-08-24 20:46:07 -07:00
Neil 9bcaf09869 refactor loading store into cohesive domains (#16192) 2026-08-24 20:45:53 -07:00
Neil b516300b8c refactor agent hook listener modules (#16187) 2026-08-24 20:45:38 -07:00
Neil 2c77f71c4f refactor(renderer): split IPC event bridges (#16185)
* refactor(renderer): split IPC event bridges

* test(renderer): follow extracted IPC shortcut bridge
2026-08-24 20:45:23 -07:00
Neil f57114a106 refactor(renderer): split terminal store slice (#16184) 2026-08-24 20:45:08 -07:00
Neil d3c37b8e40 refactor(renderer): split web preload API (#16181) 2026-08-24 20:44:53 -07:00
Neil 07b13c8468 refactor git repository host boundaries (#16177) 2026-08-24 20:44:34 -07:00
Neil 2960fe9193 Split relay entrypoints into focused modules (#16151) 2026-08-24 19:55:57 -07:00
Neil 1c436a8084 refactor(rpc): split terminal methods into cohesive modules (#16188) 2026-08-24 19:52:12 -07:00
Neil 8776b6e49a refactor(renderer): split GitHub store slice (#16183) 2026-08-24 19:52:04 -07:00
Neil 4371aaf722 refactor provider clients into domain modules (#16168) 2026-08-24 19:51:57 -07:00
Neil 0b66daffcc refactor(cli): split orchestration handlers (#16139) 2026-08-24 19:51:40 -07:00
Neil 2d500278b4 build(windows): refuse unpatched node-pty prebuilds
Merged after clean CI, Windows packaging verification, and readiness review.
2026-08-24 15:48:47 -07:00
Jinjing 3bc13f7b8c Split monolithic PTY IPC module into organized submodules (#15172)
* rm unused files

* remove unused files

* Refactor PTY IPC and add host environment paths

- Split PTY handlers out of inline baseline checks
- Rename local PTY shell provider for clarity
- Pass userDataPath and resourcesPath to host environment

* Establish PTY daemon identity before first await in spawn flow

Move identity setup, session ID minting, and hidden delivery state to
the beginning of preflight, ensuring these complete synchronously
before any awaited operations. Defer async operations like folder
workspace validation; add liveness tracking for SSH provider failures.
Refactor pane spawn reservation to prevent concurrent spawns from
creating duplicate providers.

* Add incarnationId tracking throughout PTY exit lifecycle

Track PTY incarnation IDs in exit messages sent to renderer, and add cause tracking for exit events. This enables proper lifecycle state management when PTYs can be respawned or have multiple concurrent instances. Also adds deadline support to process listing operations and stop-request tracking for better shutdown observability.

* Use fake timers in SFTP namespace tests for deterministic abort handling

Tests now use `vi.useFakeTimers()` to control time during abort scenarios,
advancing timers explicitly instead of waiting on real async delays. Ensures
more reliable test execution without flakiness from timing-dependent behavior.

* Fix PTY spawn lifecycle: handle concurrent races and cleanup abandoned a

Properly release Agent Teams leader handles when spawns are abandoned or fail,
restore provisional PTY sizes on reattachment, and settle concurrent spawn races
for the same pane. Add validation guards for destroyed renderers and improve
handler re-registration to reset delivery state before bridging a new window.

* Move PTY cleanup to localized error boundaries

Restore provisional PTY size when build-options fails and guard pre-allocated handle registration. This ensures cleanup happens at the point of error, not deferred to the general catch block.

* Replace Promise.resolve() with vi.waitFor in PTY claim test

Wait explicitly for the providerSpawn call to be made using vi.waitFor()
instead of relying on event-loop yielding. This makes the test more
deterministic and reduces flakiness from timing assumptions.

* Redact PTY IDs in pending data drop diagnostics

Prevent workspace paths embedded in session IDs from leaking through
diagnostic logs by using redactPtyIdForDiagnostics.

* Mark PTY exit events as observed by provider

Exit handlers now receive `providerExitObserved: true` to
distinguish definitive provider-witnessed exits from inferred
state changes. Preserves optional exit cause when present.

* Add defensive input validation to PTY IPC handlers

Validate that IPC arguments are present and the correct type before
passing them to handler logic. Uses optional chaining and type checks
to safely handle malformed requests from the renderer process.

* Replace direct Electron imports with PTY host bindings

Abstract app, ipcMain, and powerMonitor access through getter functions
to support multiple host environments and improve testability.

* Defend against transient PTY setup failures with state cleanup

Host-env setup failures now trigger cleanup of runtime-allocated PTY state. Cached PTY geometry is preserved after transient reattach failures but cleared when the provider reports the PTY exited before the spawn reply—preventing stale geometry from corrupting future operations. Error handling now distinguishes expired SSH sessions and early-exit conditions to preserve geometry appropriately.
2026-08-24 15:30:16 -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 fe6f942d1f Fix Ubuntu release dependency lockfile gate (STA-5109) (#16055) 2026-08-23 15:18:08 -07:00
Jinjing 7a72f341f7 Split pty-connection.ts into focused modules under 400 lines (#15166)
* refactor: split pty-connection.ts under 400 lines

* rm design doc

* refactor(pty-connection): extract reattach payload handlers as factories

- Replace bindApplyReattachPayload with createReattachPayloadHandlers factory that returns handlers instead of mutating session directly, enabling better composability and testing
- Extract waitForUserInitiatedSshConnect as standalone function for reuse across deferred session attach flows
- Create ReattachPayloadSession type to document and isolate required session capabilities
- Add test coverage for overlapping reattach payload attempts
- Clean up comments to remove redundant prefixes (session.pane → pane, session.transport → transport)

* fix(pty-connection): correct sequencing and state bugs in spawn and reat

- Fix terminal tail slice to take prefix instead of suffix, preserving escape
  sequence markers needed by next scan
- Clear pending pane serializer when direct SSH retry PTY is unclaimed
- Initialize interrupt status baseline to undefined so first input advances
  sequence counter
- Bump reattach generation only after confirming current attempt owns the stream,
  preventing superseded results from canceling in-flight prepaint

* fix(pty-connection): correct sequencing and state bugs in spawn and reat

- Fix terminal tail slice to take prefix instead of suffix, preserving escape
  sequence markers needed by next scan
- Clear pending pane serializer when direct SSH retry PTY is unclaimed
- Initialize interrupt status baseline to undefined so first input advances
  sequence counter
- Bump reattach generation only after confirming current attempt owns the stream,
  preventing superseded results from canceling in-flight prepaint

* fix(test): increase poll iterations to prevent Node 26 test leakage

Increase event loop turns from 40 to 200 in the timer settlement loop.
Node 26's libuv poll phase can briefly starve when concurrent workers
transform tests, causing cleanup to leak into the next test. The higher
iteration count ensures async operations complete before returning.

* fix(foreground-output-budgets): use >= for budget window boundary check

At the exact window boundary, the budget should roll over. Change the
comparison from > to >= so the window resets when now equals
windowStart + FOREGROUND_BUDGET_WINDOW_MS, not just after. Add tests
to verify budget rejection and rollover behavior.

* refactor(pty-connection): add status observations and routing improvemen

- Track agent status observations with origin and transition metadata
- Separate interactive redraw input timing from general terminal input
- Restore pane authority on bind and reattach
- Refine routing trust and confirmation state handling
- Invoke queued startup callbacks when PTY is bound
- Resolve Windows shell overrides with user settings

* refactor: extract resolveLaunchAgentCandidate helper

Consolidate duplicated launch-agent resolution logic into a shared helper to prevent future divergence between paneExpectsLaunchAgent and resolveExpectedLaunchTuiAgent.

* refactor(pty-connection): use model snapshot for direct SSH reconnects

Direct SSH reconnects now restore from the full SSH model snapshot (complete scrollback) when dimensions are compatible, instead of the bounded relay tail. Falls back gracefully when incompatible or alternate-screen was exited.

* refactor(pty): retry unverifiable SSH reattaches via preserved bindings

Preserve deferred SSH session IDs longer when they serve as the only retry binding,
allowing the system to attempt recovery through direct SSH retries or PTY remounts
when reattach fails in an unverifiable way. Simplify reconnect model restoration
by removing the conditional model snapshot probe and using relay replay directly.

* test: poll terminal readiness in expectSingleOwningPty

Retry the terminal list assertion with polling to account for timing
delays in PTY state reporting from the runtime.
2026-08-23 13:55:20 -07:00
Neil 838f5bfb75 fix(secrets): tell Linux users when their secrets are only obfuscated (#16033)
On Linux with no keyring, Electron falls back to the `basic_text` backend, which
"encrypts" with a hardcoded password. `isEncryptionAvailable()` returns true for
it, so Orca reported those secrets as sealed. They are not.

The obvious fix — returning false for basic_text — is wrong and would have been a
credential regression: `decryptWithStatus()` skips decryption entirely when
encryption is unavailable, so every already-stored secret would read back empty.
Sealing genuinely works on basic_text and must keep working.

So capability and trust are now separate questions. `isEncryptionAvailable()`
still answers "can this host seal and unseal", and `describeProtectionGap()`
(renamed from `describeUnavailable`) answers "is my data actually protected",
covering both no-sealing and weak-sealing.

That method had no production caller — the port documented a promise nothing
kept. `reportSecretProtectionGap()` now reads it at startup. A user-visible
surface is follow-up; this at least stops the silence.

Adds a bootstrap wiring guard over all nine host port installs. The no-op
defaults are correct for a renderer-less host and silently wrong for the desktop,
and a dropped or reordered install fails no existing test. Verified in both
directions: it fails when an install is removed, and when one moves after the
runtime is constructed.
2026-08-22 22:30:11 -07:00