Commit Graph
4 Commits
Author SHA1 Message Date
NeilandOrca b86880a9c1 Keep finished and interrupted agent sessions resumable after sleeping a workspace (#12214)
* fix(agent-sleep): keep finished and interrupted agent sessions resumable after sleeping a workspace

Manual workspace sleep ran a liveness filter over the panes it was about to kill
(isValidManualSleepLiveAgentEntry), then wiped every pre-existing sleeping record
in the worktree. A done, interrupted, typed-into, or >30-min-idle pane therefore
lost its only resume handle and woke as a bare shell. A second filter at wake
discarded any record carrying `interrupted: true`, which also killed interrupted
sessions across an app restart.

Capture now records every resumable pane, normalizing only `updatedAt` and
`interrupted` and preserving the entry's real `state` so a finished pane keeps
its passive record and resumes in place when its tab is opened instead of
spawning a duplicate tab. The wake-side interrupted check is gone. A
legacy-orchestration-worker block is carried onto the replacement record, and
the Pi-compatible promoted checkpoint is no longer overwritten by a re-derived
live record.

Closes #11598

* fix(agent-sleep): keep durable slept records a repeat sleep cannot re-derive

removeSleepingRecordsReplacedByManualWorktreeSleep wiped every record in the
worktree, and only the freshly captured set was merged back. A slept `done`
pane stays passive until its tab is opened, so a second sleep found no live
status row to rebuild its record from and deleted the pane's only
`--resume` handle with nothing written back — the original loss of #11598,
one wake/sleep cycle later.

The wipe now skips a record with no replacement in the new capture set when
it is a durable capture (`origin` `worktree-sleep` or `quit`). Provisional
`live`/legacy checkpoints are still cleared, so an unresumable Pi row does
not survive a sleep it cannot back.

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

* fix(agent-sleep): keep a slept workspace's finished panes out of the mobile wake fan-out

A manual sleep now records every finished pane, and those passive records fed
wakeSleepingAgentsForWorktreeInBackground step (b), which background-mounts one
tab per passive record. A phone opening a slept 12-tab workspace would have
cold-restored 12 agents at once, undoing the process shedding the sleep was for.

Manual-sleep captures of finished panes carry restoreOnTabOpenOnly; step (b)
skips them and the pane resumes in place when its own tab is opened, which is
what desktop activation already did and what the phone's per-tab mount provides.

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

* fix(agent-sleep): close the retained-row and shared-claim gaps in slept-session capture

Three narrow holes left by the manual-sleep capture rewrite, all in the same
"a slept session must stay resumable" contract:

- The retained pass captured `retained.entry` verbatim, so a retained row -
  stale by construction, since it exists only after the pane's pty died -
  produced a `working` record that wake then discarded on the >30min staleness
  rule. It now takes the same `updatedAt`/`interrupted` normalization and the
  same `automaticResumeBlockedBy` carry-over as the live pass.
- The retained pass also ran after the `origin: 'live'` promotion loop without
  its guard, so a pane holding both a promoted checkpoint and a retained row
  had the checkpoint (connectionId, transcript identity, active `working`
  class) overwritten by a re-derived passive record.
- The mobile wake filtered `restoreOnTabOpenOnly` records *after*
  canonicalization, so a lazy record sharing a provider-session claim with an
  eligible hibernated record could win the claim, delete the hibernated record
  as a duplicate, and then be skipped - stranding the session with nothing
  mounted.

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-04 01:01:19 -07:00
Mark Xian e3adb20917 fix(agents): include OMP terminals in cold session restoration (#8991)
Preserve OMP session identity and exact resume paths across cold restoration, AI Vault, mobile, WSL/SSH, and host-authority routes. Add mixed-version fallback and completed-session recovery coverage.
2026-07-23 19:05:22 -07:00
eisen0419andJinjing 3f335efdb9 feat(agents): pi session resume support (#8876)
* feat(agents): pi session resume support

* fix(pi): require persisted session files for resume

* test(sleeping-agent): use non-resumable sentinel in malformed-record fixture

The 'drops malformed sleeping agent resume records' test used agent:'pi' as
its example of an unknown/non-resumable agent, expecting the record to be
dropped. This PR added 'pi' to RESUMABLE_TUI_AGENTS, making that fixture
valid and retained, so the toBeUndefined assertion broke. Switch the
malformed-case fixture to a genuinely non-resumable sentinel
('definitely-not-an-agent') so the drop-malformed path is still exercised;
no other assertions changed.

* Add durable resume identity for Pi sessions without fabricating turn sta

Pi's `session_start` hook now carries the session file needed to resume
a sleeping pane, but until now Orca either discarded it or treated it
as a fake status transition. Thread a `providerSessionOnly` envelope
through the hook listener, relay, main-process server, and renderer
store so resume identity (and its session-file-scoped equality/claim
key) can be persisted and replayed without emitting prompt telemetry
or a visible working/done row.

* Add durable resume identity for completed Pi sessions

Pi's agent_end hook marks a turn done, but the underlying TUI session
stays alive and resumable. Previously a `done` status wiped sleeping
records and launch config as if the session ended, so hibernation,
manual worktree sleep, and quit-capture all lost Pi's resume identity.

- Track a "live recovery" record for done-but-still-resumable Pi
  sessions, exempting it from the usual done-state cleanup paths in
  agent-status.ts and agent-hibernation-planner.ts
- Gate providerSessionOnly rows and sleeping-agent schema records on
  actual resumability (getAgentResumeArgv) instead of trusting the
  presence of a provider session
- Wait for Pi to persist its session file before advertising resume
  metadata, and treat `/reload` as a non-terminal event so it doesn't
  clobber visible status
- Extend SSH relay envelopes to carry providerSessionOnly so remote
  hosts get the same behavior

* Add explicit periodic/quit mode to sleeping-agent session capture

Split captureAllSleepingAgentSessions into 'periodic' and 'quit' modes
so a background checkpoint can no longer downgrade a confirmed-quit
record or promote a completed Pi session without an authoritative
transcript path. Updates all call sites and tests accordingly.

* Use normalizeAgentStatusPayload for default pi status

Remove unnecessary JSON.stringify wrapper and call the appropriate normalization function directly.

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-07-17 17:29:04 -07:00
42de074152 fix(browser): keep isolated profile storage on its partition (#6958)
* fix(browser): keep isolated profile storage on its partition

* fix(browser): keep cloned isolated tabs on their resolved partition

* fix(browser): thread resolved partition through UI profile switches

Keep the isolated-storage invariant universal: UI-initiated profile
switch/create now persist the resolved partition alongside the profile
id, matching the runtime path, so a tab stays on its partition even if
the renderer profile mirror is later stale (issue #6923).

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

* fix(browser): persist resolved partition through session restore

Add sessionPartition to the workspace-session zod schema so the resolved
partition survives persist->load; without it zod strips the field and a
restored isolated tab whose profile mirror is stale at startup falls back
to the shared default partition, reopening the storage leak (#6923).

Fold the webview teardown branches (parent drift or partition mismatch)
into one and re-resolve the viewport container once, matching the
pre-refactor null-guard behavior.

Split the sleeping-agent parse tests into their own file to keep both
under the 800-line cap.

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

* fix(browser): keep CLI-created tabs inheriting the default profile

browserTabCreate without an explicit profile was sending sessionProfileId:
null, which the renderer store treats as 'no inheritance' (its guard is
!== undefined). That forced CLI-created tabs onto the shared default
partition even when the user had configured a default browser session
profile, silently changing behavior a #6923 fix should not touch.

Leave sessionProfileId/sessionPartition undefined when no profile is named
so the renderer applies default-profile inheritance; thread the resolved
partition only when a profile is explicitly chosen.

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

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 13:58:40 -07:00