Commit Graph
441 Commits
Author SHA1 Message Date
Brennan Benson 62b26c58b9 fix(native-chat): serialize structured handoff recovery 2026-08-12 18:37:10 -07:00
Brennan Benson 7b3144c037 Merge remote-tracking branch 'origin/brennanb2025/native-chat-toggle' into brennanb2025/codex-structured-mobile-write 2026-08-11 22:57:35 -07:00
Brennan Benson 7316ac3926 fix(native-chat): complete mobile reverse after TUI exit 2026-08-11 20:35:12 -07:00
Brennan Benson c18e81c8d2 fix(native-chat): clear sticky handoff recovery 2026-08-11 19:46:33 -07:00
Brennan Benson 438d256ef5 Merge remote-tracking branch 'origin/main' into brennanb2025/codex-structured-mobile-write
# Conflicts:
#	mobile/app/h/[hostId]/session/[worktreeId].tsx
#	mobile/src/session/MobileMarkdownReader.tsx
#	src/renderer/src/components/native-chat/NativeChatView.tsx
#	src/renderer/src/components/native-chat/native-chat-view-types.ts
#	src/renderer/src/components/tab-bar/TabBar.tsx
#	src/renderer/src/runtime/web-session-tabs-sync.ts
2026-08-11 13:16:52 -07:00
Brennan Benson 1ab28b5fc0 fix: harden structured session handoffs 2026-08-11 04:24:54 -07:00
Brennan Benson 827e4a1c6c feat: toggle structured sessions between chat and TUI 2026-08-11 03:01:11 -07:00
Neil 383ae50a35 fix(utf8): stop reading a code point past the end of a sliced string (#13782)
Once V8 optimizes the calling function, `String.prototype.codePointAt` on a
sliced string pairs a trailing high surrogate with the code unit that follows
the SLICE inside its parent, returning a code point the string does not
contain. Every UTF-8 byte scan built on `codePointAt` therefore reported one
byte too many for a prefix slice cut mid-pair, but only after tier-up, which
is what made terminal-stream-byte-length.test.ts fail intermittently on the
same commit.

Read the units explicitly with `charCodeAt`, which stays bounds-correct in
every tier, via a shared `readUtf8CodePointAt`.
2026-08-11 02:04:10 -07:00
Jinwoo HongandJinwoo-H 5e3a2d25f7 Filter remote workspaces by creating device (#13718)
* Filter remote workspaces by creating device

* Fix workspace origin filter reconnect behavior

* Shorten workspace origin filter label

* Refine remote workspace filter UX

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-11 01:05:12 -07:00
Brennan Benson 03d5373071 fix: fence structured AI Vault resumes 2026-08-11 00:54:48 -07:00
Brennan Benson 07af7c9285 Merge remote-tracking branch 'origin/main' into brennanb2025/codex-structured-mobile-write 2026-08-10 23:37:18 -07:00
Brennan Benson e504fb44d3 Merge codex structured stack tip into desktop 2026-08-10 23:33:47 -07:00
Jinjing c1e75477f3 Fix static analysis page stuck in loading state (#13674)
* Fix static analysis page stuck in loading state

- Bound check-details requests with 30s timeout, matching remote RPC budget
- Track request IDs to discard stale responses when context changes
- Propagate githubRepository through store and components for proper routing
- Add retry button for failed check-details loads
- Improve accessibility with ARIA labels for loading and error states

* Fix static analysis page stuck in loading state

When an open check-details tab's repository is removed, the loading
state would continue indefinitely because the fetch was still being
triggered. Prevent the fetch call in this scenario to unblock the UI.
Also migrates translation keys to obfuscated identifiers.

* Fix static analysis page stuck in loading state

Add deadline-based timeouts and request ID tracking to prevent stale responses
from freezing the checks panel. Include abort signal propagation throughout the
request chain and provide retry UI for failed check details loads.

* fix(checks): prevent loading state from getting stuck on retry

- Consolidate mount checks into a helper function
- Details now clear when a new request begins
- Add i18n strings for retry status
2026-08-10 23:11:45 -07:00
Neil ee7e743813 perf(orchestration): skip redundant federation acknowledgments (#13671) 2026-08-10 22:51:33 -07:00
Brennan Benson 75b3a209e6 Merge remote-tracking branch 'origin/main' into brennanb2025/codex-structured-mobile-write 2026-08-10 22:03:55 -07:00
Jinwoo Hong 550eabe921 feat(workspaces): review preserved branches after bulk delete (#13693) 2026-08-10 18:37:45 -07:00
Brennan Benson 3a6eb1ee72 feat(native-chat): add desktop structured sessions 2026-08-10 14:45:48 -07:00
Brennan Benson c62f782554 Merge remote-tracking branch 'origin/main' into brennanb2025/codex-structured-mobile-write 2026-08-10 12:01:26 -07:00
Neil 6206945c07 fix(native-chat): harden paginated history metadata (#13573) 2026-08-10 05:55:29 -07:00
Brennan Benson 4185fd4cd3 fix: close structured session data-loss gaps 2026-08-10 05:18:01 -07:00
Brennan Benson 2689a9bbb9 fix: secure mobile structured image delivery 2026-08-10 04:56:26 -07:00
Brennan Benson ac82bc16f8 fix: keep scoped RPC tests native-free 2026-08-10 04:48:04 -07:00
OrcaWinandOrcaWin fb3a3c5643 perf(native-chat): probe WSL transcript paths asynchronously (#13265)
* perf(native-chat): probe WSL transcript paths asynchronously

The WSL transcript resolvers probed `\wsl.localhost` UNC candidates with
existsSync. Those paths are served over 9P, so a stopped or unreachable distro
blocked the Electron main thread instead of falling through to the next
candidate.

- host-readable-transcript-path: the `pathExists` dep is now async, defaulting
  to fs/promises.access. The per-distro loop stays sequential — the ranked order
  exists so the owning distro wins, and probing every distro at once would fan
  9P calls out to ones the user deliberately left stopped.
- session-file-resolver: dropped the existsSync guard outright rather than
  converting it. walkSessionFiles already yields [] for a missing or unreadable
  root, so the guard was redundant as well as blocking.

* perf(native-chat): stop the resolve poll mkdir-ing the Codex runtime home

codexSessionsDirs() only needs the managed home's path to scan it, but called
getOrcaManagedCodexHomePath(), which mkdirSyncs. That put a synchronous fs call
back on the 500ms-5s resolve poll the surrounding async probe work just cleared,
and materialized the runtime home as a side effect of a read-only lookup.

Use the resolve-only variant the module already exposes for this case. A missing
root walks to no matches, so behavior is unchanged.

* perf(native-chat): gate WSL transcript fs access behind a shared queue

Serializes and dedupes WSL 9P filesystem probes (access/readdir) so a
stopped or slow distro can't exhaust the shared libuv threadpool or
block unrelated local filesystem work. Routes Codex session-tree scans
and path-existence checks through the new wsl-transcript-fs-gate,
while sharing in-flight scans across concurrent callers.

* perf(native-chat): prioritize exact WSL transcript probes over scans

- Add cancellation (AbortSignal) throughout session resolution and
  directory walking so an unsubscribed transcript watch stops
  in-flight WSL filesystem work instead of leaking it.
- Split the WSL fs task gate into exact vs scan priority lanes with
  per-route concurrency, so a live transcript access probe is never
  queued behind a directory scan on another distro/provider.
- Extract Codex WSL session path scanning into its own module that
  shares one root snapshot across concurrent session-id lookups and
  refreshes on a shared miss to see post-start file creation.
- Skip the async WSL probe entirely for local paths via existsSync.

* fix(native-chat): harden WSL transcript cancellation

* fix(native-chat): finish transcript cancellation

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-10 02:19:16 -07:00
Brennan Benson 9202192f10 fix: restore structured chat parity 2026-08-10 01:37:59 -07:00
Jinwoo HongandJinwoo-H 158212b8b3 feat(github): add PR comment reactions (#13470)
* feat(github): add PR comment reactions

* fix(github): harden comment reaction updates

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-09 22:11:54 -07:00
Brennan Benson dcd26ef677 Close remaining structured mobile RC blockers 2026-08-09 21:35:24 -07:00
Brennan Benson bbc6aba927 Resolve structured mobile read review blockers 2026-08-09 19:58:23 -07:00
Neil ced2719b26 refactor: remove unreachable code (#13400) 2026-08-09 18:29:32 -07:00
Brennan Benson b4bcd20caa Add mobile structured session read flow 2026-08-09 18:22:23 -07:00
Brennan Benson b4ed53f7c6 Install the structured agent-session host on first use
The record store and the journals live under the profile's user-data path,
which is not final until Electron is ready, and a runtime that never serves a
structured session should not pay for a store it will never read. So the host
is built by the first `agentSession.create` or `.ensure` rather than at
startup, and the memoized slot clears on failure so the next call retries.

The capability gate runs first: building the store and spawning a child is an
observable side effect, and a client that cannot read structured sessions must
not be able to cause it.

Owner probing is scoped to this host. Checking another host's pid against this
machine's process table is exactly how a live owner gets declared dead, so a
foreign owner reports `indeterminate` and routes to manual recovery instead.
So does a reservation that named no process — freeing it needs positive proof
that nothing spawned under its token, and this host has no spawn-token process
scan yet.
2026-08-09 16:54:49 -07:00
Brennan Benson 5df2ddbc9c perf(ai-vault): isolate tab title resolution (#13377)
* perf(ai-vault): isolate tab title resolution

* fix(ai-vault): preserve background scan caches

* fix(ai-vault): resolve nested worker from chunks
2026-08-09 16:45:52 -07:00
Jinjing d23dda6d48 Move Linear issue view to localStorage (#13391)
Prevent older paired hosts from discarding the entire taskResumeState
when they encounter the strict linearIssueView field, which was causing
silent loss of github and jira queries during remote pairing. Layout,
grouping, ordering, and per-workspace filters are now device-local.
2026-08-09 16:35:24 -07:00
Jinwoo Hong 6397668271 Add manual artifact sharing from HTML and Markdown views (#13369) 2026-08-09 16:11:16 -07:00
Jinwoo HongandJinwoo-H 45c1cb979a fix(orchestration): release context-only dispatches (#13376)
* fix(orchestration): release context-only dispatches

Refs #13005

* test(orchestration): align PTY readiness timeout

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-09 14:49:36 -07:00
Brennan Benson 3e4fa050a1 Harden structured agent-session wire recovery 2026-08-09 14:12:33 -07:00
Brennan Benson 1a4cea1fd1 Merge remote-tracking branch 'origin/brennanb2025/native-chat-structured-base' into brennanb2025/native-chat-1d-wire 2026-08-09 13:35:34 -07:00
Brennan Benson 7924a339e0 Publish agentSession.* behind the structured capability
Ten methods on the runtime manifest, including a paged `agentSession.history`
over the journal cursor and a subscribe that streams cursor-qualified batches.
A declared client that did not advertise `agent-session.structured.v1` is told
the whole surface does not exist, so an old client never sees a session it
cannot render; an in-process caller negotiates nothing and is served.
2026-08-09 13:35:28 -07:00
JinjingandOrca 3ec48a74d5 Gate artifact publishing behind off-by-default capability (#13368)
* fix(artifacts): gate agent artifact publishing behind an off-by-default capability

Public artifact sharing was reachable by any agent through `orca artifacts
share`: the Artifacts settings toggle only controlled sidebar visibility, and
nothing in the main process checked a capability before minting a public URL.

Add `artifactSharingEnabled` (default off) and enforce it in
ArtifactCloudService.share/update — before auth, network, or the share-record
write — so the CLI, relay-forwarded remote CLI, and IPC paths are all denied.
The denial carries a stable `artifact_sharing_disabled` code plus next steps
through the RPC error allowlist, so the CLI prints actionable guidance.

list, unshare, and delete stay ungated: turning publishing off must not strand
already-published links. The capability is absent from the `settings.update`
RPC schema, so an agent cannot grant it to itself — only the desktop UI can.

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

* fix(artifacts): gate agent artifact publishing behind an off-by-default

Publishing is blocked until enabled in Settings → Artifacts. CLI preflights the capability before reading files to avoid unnecessary uploads. RPC surface rejects capability grants so callers cannot self-grant. UI shows opt-in workflow and recovery path when publishing is off. Web clients mirror the host's setting read-only.

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-09 13:19:08 -07:00
Brennan Benson c69b12b3dd Enforce the agent-session lease at every PTY write choke point
Route every byte that can reach a PTY through the single-writer lease
adjudicator, so a session owned by one runtime cannot be typed into by
another. Nothing binds a PTY to a durable session record yet, so the gate
short-circuits and today's shells, legacy agent terminals, and unbound
sessions are untouched.

Gated paths: terminal.send RPC, the renderer IPC write paths
(pty:write / pty:writeAccepted), plugin sendTerminalText, the agent-prompt
paste, and ptyController.write as a backstop for runtime writes that never
passed a typed gate. Every yield inside a write - inter-chunk, the
text/suffix pause, the agent-prompt submit delay, deferred size
measurement - re-checks the fence, so a handoff mid-paste stops the
remaining chunks instead of interleaving two owners.

Refusals are typed and additive on the wire: an optional
agentSessionRefusal on RuntimeTerminalSend and on the existing
pty:writeUnavailable payload, refusal codes drawn only from the existing
agent-session error codes, and stream-input refusals mapped onto the
WriteUnavailable frame old clients already decode.
2026-08-09 13:00:56 -07:00
Jinwoo Hong c991bb27d3 Add account-backed artifact sharing (#13012) 2026-08-07 23:02:29 -07:00
c3bf22b9a8 [P2] perf(windows): stop the capability poll respawning blocking wsl.exe probes (#11698)
* perf(windows): stop the capability poll respawning blocking wsl.exe probes

#11295 added a 30s renderer interval to `useWindowsTerminalCapabilities` whose
early-return only fires when WSL is available with at least one distro, so on the
common Windows host (no WSL) it re-ran a full capability read forever. Each read
IPCs four probes whose main-process handlers were synchronous `execFileSync` calls
to wsl.exe/pwsh.exe, blocking the Electron main event loop for up to 5s a time.

The un-latching intent is kept: a host that answers "no WSL" is still re-checked,
now on an exponential backoff (30s, +60s, +120s) that parks once the answer stops
moving, re-arms on window focus, is shared by all consumers of an owner key, and
stops entirely when the last consumer unmounts. The wsl/pwsh IPC handlers now use
async twins that share the existing caches and back off identically.

* fix(windows): classify async wsl/pwsh probe failures with the execFile error shape

The async twins feed `execFile` callback errors into classifiers written for
`execFileSync`: a non-zero exit lands on `error.code` as a number rather than
`error.status`, and a timeout is a SIGTERM kill rather than ETIMEDOUT.

So a Windows host without WSL (wsl.exe ships in System32, so it exits non-zero
instead of ENOENT) was cached as retryable, shrinking the shared window from
10min to 45s and making the still-sync callers re-pay their blocking spawn ~13x
more often; and a pwsh cold start past 5s cached "pwsh missing" for 30s,
demoting the user's PowerShell 7 preference — the exact case the ETIMEDOUT
branch exists to prevent.

Also drops a literal NUL byte from the new re-probe module's signature
separator, which made the file binary to git, and seeds `lastProbeAt` at
registration so focus churn right after mount cannot defer the first re-probe
indefinitely.

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

* perf(windows): route relay host-capability probes through the async wsl/pwsh twins

A paired web/mobile client resolves `useWindowsTerminalCapabilities` to a local
target (TabBar's `isWebClient` gate, and `useSettingsNavigationMetadata` forces
`{kind:'local'}`), so the new re-probe arms there too. But `window.api.wsl/pwsh`
on a web client is not the ipc/app.ts channel — it is `host.wsl.*`/`host.pwsh.*`
over the runtime RPC, which still ran the sync probes and blocked the desktop
main event loop on `execFileSync('wsl.exe' | 'pwsh.exe')` for up to 5s per call.

Switch those handlers and the relay preflight capability probe to the async
twins added here; they share the same caches, dedupe and backoff, so remote
callers see no behavior change.

* fix(windows): harden async capability reprobes

* fix(windows): dedupe PowerShell shell probes

---------

Co-authored-by: Orca <help@stably.ai>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-07 21:59:24 -07:00
ce20a109da Persist the Linear issue list view and per-workspace filters (#12710)
* Persist the Linear issue list view and per-workspace filters

Layout, grouping, ordering, columns, and attribute filters survive a restart.
Facet ids are workspace-scoped, so filters are kept per Linear workspace and the
active filter is *derived* from the selected workspace rather than reset by an
effect on switch — no ordering race can apply workspace A's facets to B, and an
unresolved or cross-workspace selection reads as unfiltered without erasing
anything.

A single shared catalog backs the renderer state, `TaskResumeState`, and the
strict `ui.set` schema, so a new view option cannot leave paired web/mobile/relay
clients rejecting the whole payload. Persisted values are normalized as untrusted
input: a corrupt preference or a single bad workspace entry is dropped without
taking the rest of the resume state with it.

Deriving the filter also removed the guard that used to make three neighbouring
behaviours safe, so they are re-scoped here:

- The primary-team facet reset now fires only on an in-workspace team change.
  A workspace switch also changes the primary team, and clearing there wiped the
  filter that had just been restored for the workspace being switched *to*.
- The list-read force check no longer fires on the session's first read, so a
  restored filter serves warm cache instead of forcing a network round trip
  behind a blocking spinner on every cold start.
- The filter dropdown derives "no single workspace" from `workspaceId` alone.
  With an unresolved workspace it previously rendered the statically populated
  priority section, whose clicks now have nowhere to be stored.

* Harden Linear view persistence against the failures review surfaced

Five issues, each found by a reviewer and reproduced before fixing:

- The filter dropdown's prune effect only ran when the user opened the popover,
  because the filter was always empty at startup. Restoration makes it run on
  mount, where `availableTeams` may still be the issue-scraped fallback rather
  than the real fetch. Metadata complete for a *partial* team set passes every
  R12 guard, so it pruned facets belonging to teams it simply hadn't seen — and
  the write persisted, deleting them permanently. Gated on `teamsSettled`.

- `canonicalize` dedupes but enforces none of the transport bounds; only the
  throwing parser does. So `serialize` could emit a 101-label filter that the
  strict `ui.set` schema rejects, which drops the WHOLE taskResumeState — github,
  jira and linear query included — on every subsequent write, since the renderer
  resends the merged object each time. Added `boundLinearIssueAttributeFilter`
  and a round-trip test built from serializer output rather than a literal, which
  is the only kind that can catch renderer/schema drift.

- `linearIssueView` now carries `.catch(undefined)`: value tolerance stops at the
  top level, so any future instance of the above is a cosmetic reset of the view
  instead of silent loss of every other resume field.

- A workspace switch forced an uncached list read in both directions. The switch
  is a later observation, so the null-baseline fix didn't cover it; the cache is
  already workspace-keyed, making the force pure cost.

- Recency for the 20-workspace cap came from object key order, which is wrong
  twice: re-filtering an existing workspace left it at the head (first evicted,
  though just used), and an array-index-like key enumerates first regardless of
  insertion, so a write could evict the very entry it added. Recency is now an
  explicit ordered key list.

Also adds the nested parity assertion — the top-level one compares only
TaskResumeState's own keys, so a field added to LinearIssueViewResumeState stayed
invisible to it, which is exactly what `.strict()` rejects.

The wiring test was blind: deleting the hydration guard outright left all four
assertions green. The gate is now `shouldPersistLinearIssueView`, unit-tested
directly, and the file is renamed to the repo's `*-boundary.test.ts` convention
with an assertion that fails on that mutation.

* Log discarded Linear views and fix empty-filter serialization

- Schema now logs when linearIssueView is discarded, making validation failures visible
- Fixed serialization: filters that become empty after bounding are now omitted
- Added AssertNoExtraKeys type check for bidirectional schema/type parity
- Refactored view option catalogs to use canonical constants, preventing UI/schema drift

* Remove workspace persistence limits and LRU eviction

Stop capping persisted Linear workspace filters at 20 and evicting
least-recently-used workspaces. Simplify persistence to store all
workspace filters, gate persistence only on resume state application,
and remove tests that pinned implementation details. Users can now
persist filters for all their workspaces without arbitrary limits.

* add test for linear persistence

* Improve Linear filter test clarity and fix e2e overlay dismissal for CI

- Convert parameterized filter-pruning test to sequential assertions
- Fix dismissOverlayChrome to toggle overlay triggers instead of
  force-clicking inert page elements in headless CI

* Prevent TaskPage from stealing Escape from Radix menus

- Add check to detect open Radix dropdown menus and popovers; return
  early from Escape handler to respect their capture-phase ownership
- Update overlay dismissal in e2e tests to use keyboard.press('Escape'),
  now that TaskPage no longer interferes

* The capture-phase Escape guard in TaskPage bailed out for open dropdown menus and popovers, but an open Radix Select matches none of those selectors: the shared SelectContent wrapper (src/renderer/src/components/ui/select.tsx:60) renders data-slot="select-content" and Radix gives its content role="listbox", not role="menu". So with a select open, the window-level capture handler ran first, called preventDefault() and closeTaskPage() — closing the whole task page instead of just the select. Added [data-slot="select-content"] to the guard, as suggested. I did not add [role="listbox"]; the reviewer explicitly notes it's too broad, and the data-slot selector covers every select rendered through the shared wrapper.

---------

Co-authored-by: m4air <m4air@MacBook-Air.localdomain>
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-08-07 20:41:59 -07:00
Avichal DwivediandJinwoo-H 96a6c93757 fix(orchestration): allow fresh agents to take over legacy runs (#12896)
* fix(orchestration): allow fresh agents to take over legacy runs

* test(orchestration): cover forged takeover evidence

* fix(orchestration): retain renderer launch authority

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-07 18:10:27 -07:00
Jinwoo HongandJinwoo-H ddf58d6d6a fix(terminal): restore preserved remote PTYs after host relaunch (#12990)
* fix(terminal): foreground preserved daemon PTYs

* fix(terminal): keep snapshot sequence domains distinct

* test(terminal): use active reconnect control

* test(terminal): await reconnect control activation

* test(terminal): validate reconnect with fresh control

* test(terminal): tighten host restart evidence

* fix(terminal): retry preserved PTY attach after inventory

* fix(terminal): retry attach after overlapping inventory

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-07 01:25:18 -07:00
Jinwoo HongandJinwoo-H c9485fdded fix(computer): fence macOS HID coordinate clicks (#12981)
Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-06 21:18:52 -07:00
BingZandJinwoo-H a025a71447 fix(orchestration): deliver pending mail to already-idle agents (#12584)
Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-06 17:08:28 -07:00
Brennan Benson 92f759bb51 perf: bound repeated watcher, Session History, and terminal work (#12828)
* perf: bound repeated watcher, vault, and terminal work

* fix(terminal): preserve redraw recovery while bounding fit retries

* fix(watcher): retain structural fallback after crash fuse

* fix(ai-vault): preserve forced scan budget
2026-08-06 10:37:52 -07:00
Brennan Benson cd8c66551a fix(agent-hooks): resumed Claude Code session gets its sidebar agent row at SessionStart (STA-3386) (#12859)
* fix(agent-hooks): give resumed Claude sessions a sidebar row at SessionStart (STA-3386)

Claude's hook set never registered SessionStart and normalizeClaudeEvent
dropped it at ingest, so a resumed session that idled produced zero hook
traffic and earned no sidebar agent row until the first prompt.

- Register SessionStart in CLAUDE_EVENTS (local + remote installs).
- Map lead SessionStart (startup/resume/clear) to an idle 'done' row,
  resetting stale roster/task/cron/tool/prompt state like the Codex path;
  compact restarts and child-attributed SessionStart stay dropped.
- Thread hookEventName through the agent-status IPC payload so the
  completion coordinator can tell a session connect from a turn result;
  a SessionStart 'done' no longer raises agent-task-complete.

* fix(agent-hooks): mark SessionStart rows as session boundaries, not completions (STA-3386)

Review follow-up: represent the idle connect as a first-class
sessionBoundary flag on the status payload instead of gating one
renderer consumer on hookEventName.

- sessionBoundary rides AgentStatusPayload/AgentStatusEntry (done-only,
  clamped like interrupted); drops the hookEventName IPC threading.
- Completion-reactive consumers ignore session boundaries: the
  completion coordinator (task-complete notifications), automation
  dispatch observers (a connecting agent no longer completes the run
  and closes its tab), activity unread counts, and the dashboard
  finished timestamp; the status slice keeps boundaries out of
  stateHistory and preserves the flag across done->done repaints.
- SessionStart sources are allowlisted (startup/resume/clear) so
  compact restarts or unknown sources fail closed mid-turn.
- A live SessionStart now un-retires a reusable pane like a fresh
  prompt, so resume-in-reused-pane earns its row too.

* fix(agent-hooks): keep session-boundary dones out of teardown and completion history (STA-3386)

Review round 2:
- A boundary done no longer deletes the pane's launch-config registry
  entry, so a resumed idle TUI keeps its registered-launch-agent
  identity evidence.
- A boundary landing on a REAL done pushes that completion into
  stateHistory so the finished timestamp and unread badge survive a
  resume//clear right after a finish.
- The done->done flag carry yields to turn evidence (assistant message
  or changed prompt) so a genuine completion can never be suppressed.
- Star-nag value-moment observer and the server's OSC-equivalence
  dedupe now discriminate the flag.

* fix(agent-hooks): keep a displaced completion unread in the sidebar badge (STA-3386)

Review round 3: sidebar-badge mode counts only the live entry, so a
session boundary landing on an unacknowledged completion silently
dropped the sidebar badge while the agent-events count kept it. Count
the displaced completion from history for boundary rows, and pin the
behavior with countActivityUnread tests.

* fix(agent-hooks): prevent SessionStart completion side effects (STA-3386)

* fix(agent-hooks): preserve SessionStart through renderer IPC (STA-3386)
2026-08-05 22:06:36 -07:00
Jinwoo Hong b0ba51831c Add per-worker model and effort overrides (#12851) 2026-08-05 21:17:45 -07:00
Jinwoo HongandJinwoo-H ff01fad4ad fix(runtime): stop a stalled tick from forgiving banked missed probes (#12841)
Post-merge review of #12790 demonstrated a real leak: the resume-from-pause
pardon cleared banked misses outright, so a host whose sweep stalls once every
three ticks reset the budget forever and a dead socket was never reaped. The
reviewer ran 300 sweeps against a permanently dead peer with a >1.5x gap every
third tick and observed zero terminate() calls.

Pre-#12790 that required a stall on *every* tick; the counter widened the
pathological window 3x, and the failure mode is permanent non-reaping — the
MAX_WS_CONNECTIONS leak the reaper exists to prevent.

A stalled tick now charges no miss, which is all the original rationale needed
(the client had no chance to answer that probe), but no longer forgives the
misses already banked. A live client still clears its own count by answering
the probe that is still sent on the stalled tick.

The tolerance test's pause case is rewritten to assert the new contract rather
than the old forgive-everything one, and a new test pins the leak directly: a
host stalling every third tick must still reap a dead socket.

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-05 20:28:10 -07:00