mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(sleep): reopen a slept agent whose tab is gone in a fresh tab instead of stranding it (#33598)
A slept agent whose tab disappeared now resumes in a fresh tab (with --resume, through the existing duplicate/host guards) when its workspace opens, and main's stranded workspace-sleep record is then dropped (only if no tab still lists that pane), instead of the record sitting forever while the workspace opens a bare shell. Fixes #22657
This commit is contained in:
@@ -37,6 +37,7 @@ const WORKTREE_HANDLER_CHANNELS = [
|
||||
'worktrees:sleep',
|
||||
'worktrees:sleepReopened',
|
||||
'worktrees:resumeSleptPane',
|
||||
'worktrees:retireStrandedSleepRecord',
|
||||
'worktrees:previewNestedRemoval',
|
||||
'worktrees:forgetLocal',
|
||||
'worktrees:forceDeletePreservedBranch',
|
||||
|
||||
@@ -42,6 +42,7 @@ const WORKTREE_HANDLER_CHANNELS = [
|
||||
'worktrees:sleep',
|
||||
'worktrees:sleepReopened',
|
||||
'worktrees:resumeSleptPane',
|
||||
'worktrees:retireStrandedSleepRecord',
|
||||
'worktrees:previewNestedRemoval',
|
||||
'worktrees:forgetLocal',
|
||||
'worktrees:forceDeletePreservedBranch',
|
||||
|
||||
@@ -34,4 +34,13 @@ export function registerWorktreeSleepHandlers(context: WorktreeIpcContext): void
|
||||
}
|
||||
return context.runtime.resumeSleptPane(pane)
|
||||
})
|
||||
ipcMain.handle(
|
||||
'worktrees:retireStrandedSleepRecord',
|
||||
(_event, args: { worktreeId?: unknown; paneKey?: unknown } | undefined) => {
|
||||
if (typeof args?.worktreeId !== 'string' || typeof args.paneKey !== 'string') {
|
||||
throw new Error('invalid_sleep_record')
|
||||
}
|
||||
return context.runtime.retireStrandedSleepRecord(args.worktreeId, args.paneKey)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { SleepingAgentSessionRecord } from '../../../../shared/agent-session-resume'
|
||||
import type { WorkspaceSessionState } from '../../../../shared/workspace-session-state-types'
|
||||
import { dropStrandedSleepRecord } from './worktree-sleep-resume-records'
|
||||
|
||||
const slept: SleepingAgentSessionRecord = {
|
||||
paneKey: 'tab-1:leaf-1',
|
||||
tabId: 'tab-1',
|
||||
worktreeId: 'wt-1',
|
||||
agent: 'claude',
|
||||
providerSession: { key: 'session_id', id: 'sess-1' },
|
||||
prompt: '',
|
||||
state: 'done',
|
||||
capturedAt: 1,
|
||||
updatedAt: 1,
|
||||
origin: 'worktree-sleep',
|
||||
restoreOnTabOpenOnly: true
|
||||
}
|
||||
|
||||
function session(
|
||||
tabIds: string[],
|
||||
record: SleepingAgentSessionRecord = slept
|
||||
): WorkspaceSessionState {
|
||||
const tab = (id: string) => ({
|
||||
id,
|
||||
ptyId: null,
|
||||
worktreeId: 'wt-1',
|
||||
title: 'Terminal',
|
||||
customTitle: null,
|
||||
color: null,
|
||||
sortOrder: 0,
|
||||
createdAt: 1
|
||||
})
|
||||
return {
|
||||
activeRepoId: null,
|
||||
activeWorktreeId: null,
|
||||
activeTabId: null,
|
||||
tabsByWorktree: { 'wt-1': tabIds.map(tab) },
|
||||
terminalLayoutsByTabId: {},
|
||||
sleepingAgentSessionsByPaneKey: { [record.paneKey]: record }
|
||||
}
|
||||
}
|
||||
|
||||
describe('dropStrandedSleepRecord', () => {
|
||||
it("drops a workspace sleep's record once no tab holds its pane", () => {
|
||||
expect(
|
||||
dropStrandedSleepRecord(session([]), slept.paneKey)?.sleepingAgentSessionsByPaneKey
|
||||
).toEqual({})
|
||||
})
|
||||
|
||||
// A window must not discard a record main still resumes when its tab opens.
|
||||
it('keeps a record whose tab is still listed, and window-owned records', () => {
|
||||
expect(dropStrandedSleepRecord(session(['tab-1']), slept.paneKey)).toBeNull()
|
||||
const quit = { ...slept, origin: 'quit' as const }
|
||||
expect(dropStrandedSleepRecord(session([], quit), quit.paneKey)).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -10,6 +10,7 @@ import type { AgentProcessVerdict } from '../../../../shared/agent-process-prese
|
||||
import { parsePaneKey } from '../../../../shared/stable-pane-id'
|
||||
import { isLocalHookConnectionId } from '../../../../shared/wsl-hook-relay-contract'
|
||||
import type { WorkspaceSessionState } from '../../../../shared/workspace-session-state-types'
|
||||
import { isWorkspaceSleepRecord } from '../../../../shared/workspace-sleep'
|
||||
|
||||
/** Per slept PTY: the resume records captured, and the panes the hook store proves moved on. */
|
||||
export type WorktreeSleepResumeCapture = Map<
|
||||
@@ -158,3 +159,20 @@ export function clearClosedWorktreeResumeRecords(
|
||||
? { ...session, sleepingAgentSessionsByPaneKey, terminalPtyIncarnationsByPaneKey }
|
||||
: null
|
||||
}
|
||||
|
||||
/** The session without a workspace-sleep record whose tab it no longer lists, or null when the record is still placed. */
|
||||
export function dropStrandedSleepRecord(
|
||||
session: WorkspaceSessionState,
|
||||
paneKey: string
|
||||
): WorkspaceSessionState | null {
|
||||
const record = session.sleepingAgentSessionsByPaneKey?.[paneKey]
|
||||
const tabId = record?.tabId ?? parsePaneKey(paneKey)?.tabId
|
||||
const tabListed = Object.values(session.tabsByWorktree).some((tabs) =>
|
||||
tabs.some((tab) => tab.id === tabId)
|
||||
)
|
||||
if (!record || !isWorkspaceSleepRecord(record) || tabListed) {
|
||||
return null
|
||||
}
|
||||
const { [paneKey]: _dropped, ...rest } = session.sleepingAgentSessionsByPaneKey ?? {}
|
||||
return { ...session, sleepingAgentSessionsByPaneKey: rest }
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import type {
|
||||
WorkspaceSleepRequest
|
||||
} from '../../../shared/workspace-sleep'
|
||||
import { WorkspaceSleepCoordinator } from './workspace-sleep-runtime'
|
||||
import { dropStrandedSleepRecord } from '../terminal/stop/worktree-sleep-resume-records'
|
||||
import type { RestoredPane } from '../terminal/recovery/restored-pane-start'
|
||||
import type { SleepingAgentSessionRecord } from '../../../shared/agent-session-resume'
|
||||
|
||||
@@ -265,6 +266,14 @@ export abstract class OrcaRuntimeWithListManagedWorktrees extends OrcaRuntimeWit
|
||||
return this.workspaceSleep.resumePane(pane)
|
||||
}
|
||||
|
||||
/** The window reopened a slept agent whose tab is gone in a fresh tab; this record is spent. */
|
||||
async retireStrandedSleepRecord(worktreeId: string, paneKey: string): Promise<void> {
|
||||
await this.commitWorkspaceSessionEdit(
|
||||
this.getWorkspaceSessionHostIdForWorktree(worktreeId),
|
||||
(session) => dropStrandedSleepRecord(session, paneKey)
|
||||
)
|
||||
}
|
||||
|
||||
wakeWindowHibernatedAgents(worktreeId: string): void {
|
||||
this.notifier?.resumeSleepingAgents?.(worktreeId)
|
||||
}
|
||||
|
||||
@@ -89,6 +89,8 @@ export type WorktreeApi = {
|
||||
sleepReopened: (worktreeId: string) => Promise<void>
|
||||
/** True once running, so the renderer's subsequent spawn reattaches. */
|
||||
resumeSleptPane: (pane: { worktreeId: string; tabId: string; leafId: string }) => Promise<boolean>
|
||||
/** Drops main's workspace-sleep record only while no tab holds its pane. */
|
||||
retireStrandedSleepRecord: (args: { worktreeId: string; paneKey: string }) => Promise<void>
|
||||
remove: (args: {
|
||||
worktreeId: string
|
||||
hostId?: ExecutionHostId
|
||||
|
||||
@@ -45,6 +45,8 @@ export const worktreesApi = {
|
||||
sleep: (request) => ipcRenderer.invoke('worktrees:sleep', request),
|
||||
sleepReopened: (worktreeId) => ipcRenderer.invoke('worktrees:sleepReopened', worktreeId),
|
||||
resumeSleptPane: (pane) => ipcRenderer.invoke('worktrees:resumeSleptPane', pane),
|
||||
retireStrandedSleepRecord: (args) =>
|
||||
ipcRenderer.invoke('worktrees:retireStrandedSleepRecord', args),
|
||||
|
||||
remove: (args) => ipcRenderer.invoke('worktrees:remove', args),
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { afterEach, expect, it, vi } from 'vitest'
|
||||
import type { SleepingAgentSessionRecord } from '../../../shared/agent-session-resume'
|
||||
import { makePaneKey } from '../../../shared/stable-pane-id'
|
||||
import { useAppStore } from '@/store'
|
||||
import { resumeSleepingAgentSessionsForWorktree } from './resume-sleeping-agent-session'
|
||||
|
||||
vi.mock('@/runtime/runtime-rpc-client', async (importOriginal) =>
|
||||
(
|
||||
await import('@/runtime/layout-apply-test-transport.test-fixture')
|
||||
).runtimeRpcClientWithLayoutApply(await importOriginal())
|
||||
)
|
||||
|
||||
const initialAppStoreState = useAppStore.getState()
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
useAppStore.setState(initialAppStoreState, true)
|
||||
})
|
||||
|
||||
// #22657: deleting or ignoring it would leave the session with no resume handle at all.
|
||||
it('reopens a slept agent whose tab is gone in a fresh tab and retires main’s record', async () => {
|
||||
const retireStrandedSleepRecord = vi.fn().mockResolvedValue(undefined)
|
||||
vi.stubGlobal('window', { api: { worktrees: { retireStrandedSleepRecord } } })
|
||||
const record: SleepingAgentSessionRecord = {
|
||||
paneKey: makePaneKey('tab-1', '11111111-1111-4111-8111-111111111111'),
|
||||
tabId: 'tab-1',
|
||||
worktreeId: 'wt-1',
|
||||
agent: 'claude',
|
||||
providerSession: { key: 'session_id', id: 'sess-1' },
|
||||
prompt: '',
|
||||
state: 'done',
|
||||
capturedAt: 1,
|
||||
updatedAt: 1,
|
||||
origin: 'worktree-sleep',
|
||||
restoreOnTabOpenOnly: true
|
||||
}
|
||||
useAppStore.setState({
|
||||
tabsByWorktree: { 'wt-1': [] },
|
||||
sleepingAgentSessionsByPaneKey: { [record.paneKey]: record }
|
||||
} as never)
|
||||
|
||||
expect(resumeSleepingAgentSessionsForWorktree('wt-1')).toBe(1)
|
||||
const state = useAppStore.getState()
|
||||
const resumedTab = state.tabsByWorktree['wt-1']?.[0]
|
||||
expect(state.pendingStartupByTabId[resumedTab!.id]?.resumeProviderSession).toEqual(
|
||||
record.providerSession
|
||||
)
|
||||
expect(state.sleepingAgentSessionsByPaneKey[record.paneKey]).toBeUndefined()
|
||||
await vi.waitFor(() =>
|
||||
expect(retireStrandedSleepRecord).toHaveBeenCalledWith({
|
||||
worktreeId: 'wt-1',
|
||||
paneKey: record.paneKey
|
||||
})
|
||||
)
|
||||
})
|
||||
@@ -498,16 +498,16 @@ describe('resumeSleepingAgentSessionsForWorktree', () => {
|
||||
expect(state.sleepingAgentSessionsByPaneKey[second.paneKey]).toBeUndefined()
|
||||
})
|
||||
|
||||
it("leaves a workspace sleep's records to main: no fresh tab, record kept", () => {
|
||||
it("leaves a workspace sleep's records to main while their tab remains", () => {
|
||||
const record = makeRecord({ origin: 'worktree-sleep' })
|
||||
useAppStore.setState({
|
||||
tabsByWorktree: { 'wt-1': [] },
|
||||
tabsByWorktree: { 'wt-1': [makeTerminalTab('tab-1', 'wt-1')] },
|
||||
sleepingAgentSessionsByPaneKey: { [record.paneKey]: record }
|
||||
} as never)
|
||||
|
||||
expect(resumeSleepingAgentSessionsForWorktree('wt-1')).toBe(0)
|
||||
const state = useAppStore.getState()
|
||||
expect(state.tabsByWorktree['wt-1']).toEqual([])
|
||||
expect(state.tabsByWorktree['wt-1']).toHaveLength(1)
|
||||
expect(state.sleepingAgentSessionsByPaneKey[record.paneKey]).toBe(record)
|
||||
})
|
||||
|
||||
|
||||
@@ -9,10 +9,12 @@ import { parsePaneKey } from '../../../shared/stable-pane-id'
|
||||
import {
|
||||
getProviderSessionClaimKey,
|
||||
activationTreatsNoteAsFinished,
|
||||
isStrandedWorkspaceSleepRecord,
|
||||
recordPaneIsOwnedByPreservedPane,
|
||||
stablePaneHasLivePty
|
||||
} from './sleeping-agent-pane-ownership'
|
||||
import { isPaneSpawnDeferred } from './pane-spawn-deferral-registry'
|
||||
import { settleSweptRecord, sweepTreatsAsFinished } from './swept-sleep-record-settlement'
|
||||
import {
|
||||
launchSleepingAgentSession,
|
||||
type ResumeSleepingAgentSessionsOptions
|
||||
@@ -41,7 +43,7 @@ function clearPassiveCompletedRecordsForClaimKey(
|
||||
): void {
|
||||
const state = useAppStore.getState()
|
||||
for (const record of records) {
|
||||
if (record.paneKey === keepPaneKey || !activationTreatsNoteAsFinished(record)) {
|
||||
if (record.paneKey === keepPaneKey || !sweepTreatsAsFinished(record)) {
|
||||
continue
|
||||
}
|
||||
if (getProviderSessionClaimKey(record) === claimKey) {
|
||||
@@ -57,7 +59,7 @@ function getCurrentPaneOwnedClaimKeys(records: readonly SleepingAgentSessionReco
|
||||
if (
|
||||
state.sleepingAgentSessionsByPaneKey[record.paneKey] !== record ||
|
||||
isInvalidWorktreeActivationRecord(record) ||
|
||||
activationTreatsNoteAsFinished(record)
|
||||
sweepTreatsAsFinished(record)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
@@ -220,15 +222,19 @@ export function resumeSleepingAgentSessionsForWorktree(
|
||||
if (hostAuthority === 'unverifiable') {
|
||||
return 0
|
||||
}
|
||||
// Main owns workspace-sleep resumes in their original panes; this sweep must not create fresh tabs.
|
||||
// Main resumes workspace sleep records in their own panes; only stranded ones get a fresh tab here.
|
||||
const worktreeRecords = Object.values(state.sleepingAgentSessionsByPaneKey)
|
||||
.filter((record) => record.worktreeId === worktreeId && !isWorkspaceSleepRecord(record))
|
||||
.filter(
|
||||
(record) =>
|
||||
record.worktreeId === worktreeId &&
|
||||
(!isWorkspaceSleepRecord(record) || isStrandedWorkspaceSleepRecord(record, state))
|
||||
)
|
||||
.sort((a, b) => a.capturedAt - b.capturedAt || a.updatedAt - b.updatedAt)
|
||||
const validWorktreeRecords = worktreeRecords.filter(
|
||||
(record) => !isInvalidWorktreeActivationRecord(record)
|
||||
)
|
||||
const activeWorktreeRecords = validWorktreeRecords.filter(
|
||||
(record) => !activationTreatsNoteAsFinished(record)
|
||||
(record) => !sweepTreatsAsFinished(record)
|
||||
)
|
||||
const activeClaimKeys = new Set(activeWorktreeRecords.map(getProviderSessionClaimKey))
|
||||
// Why: a record this host may not resume must not outrank, and so delete, one it may.
|
||||
@@ -255,7 +261,7 @@ export function resumeSleepingAgentSessionsForWorktree(
|
||||
continue
|
||||
}
|
||||
if (isInvalidWorktreeActivationRecord(record)) {
|
||||
state.clearSleepingAgentSession(record.paneKey)
|
||||
settleSweptRecord(record)
|
||||
continue
|
||||
}
|
||||
// Host disagreement does not invalidate this machine-scoped locator. Retain it for manual retry; deleting loses the transcript handle, while wrong-host resume can fail or fork.
|
||||
@@ -269,31 +275,31 @@ export function resumeSleepingAgentSessionsForWorktree(
|
||||
continue
|
||||
}
|
||||
const isPaneOwned = recordPaneIsOwnedByPreservedPane(record, currentState)
|
||||
if (activationTreatsNoteAsFinished(record)) {
|
||||
if (sweepTreatsAsFinished(record)) {
|
||||
// Completed hibernated agents are passive history; activation preserves display evidence without starting work.
|
||||
if (!isPaneOwned || activeClaimKeys.has(claimKey)) {
|
||||
state.clearSleepingAgentSession(record.paneKey)
|
||||
settleSweptRecord(record)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (activeOrQueuedResumeClaimsProviderSession(record, currentState, isPaneOwned)) {
|
||||
// Clear stale main wake replays once this provider session is already queued in a fresh tab.
|
||||
state.clearSleepingAgentSession(record.paneKey)
|
||||
settleSweptRecord(record)
|
||||
continue
|
||||
}
|
||||
const paneOwnedClaimKeys = getCurrentPaneOwnedClaimKeys(activeWorktreeRecords)
|
||||
if (paneOwnedClaimKeys.has(claimKey)) {
|
||||
if (!isPaneOwned) {
|
||||
state.clearSleepingAgentSession(record.paneKey)
|
||||
settleSweptRecord(record)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (freshlyLaunchedClaimKeys.has(claimKey)) {
|
||||
state.clearSleepingAgentSession(record.paneKey)
|
||||
settleSweptRecord(record)
|
||||
continue
|
||||
}
|
||||
if (claimRepresentativeByKey.get(claimKey) !== record) {
|
||||
state.clearSleepingAgentSession(record.paneKey)
|
||||
settleSweptRecord(record)
|
||||
continue
|
||||
}
|
||||
if (isPaneOwned) {
|
||||
@@ -305,10 +311,11 @@ export function resumeSleepingAgentSessionsForWorktree(
|
||||
}
|
||||
// Provider sessions span workspaces; stale record IDs must not relaunch a transcript owned by another record’s pane.
|
||||
if (providerSessionOwnedElsewhere(record, currentState)) {
|
||||
state.clearSleepingAgentSession(record.paneKey)
|
||||
settleSweptRecord(record)
|
||||
continue
|
||||
}
|
||||
if (launchSleepingAgentSession(record, options)) {
|
||||
settleSweptRecord(record)
|
||||
launched += 1
|
||||
freshlyLaunchedClaimKeys.add(claimKey)
|
||||
clearPassiveCompletedRecordsForClaimKey(worktreeRecords, claimKey, record.paneKey)
|
||||
|
||||
@@ -11,6 +11,7 @@ import { parseLegacyNumericPaneKey, parsePaneKey } from '../../../shared/stable-
|
||||
import { getProviderSessionClaimKey } from '../../../shared/sleeping-record-legacy-pane-key'
|
||||
import { isWebTerminalSurfaceTabId } from '../../../shared/terminal-surface-id'
|
||||
import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../../shared/layout-owner-switch'
|
||||
import { isWorkspaceSleepRecord } from '../../../shared/workspace-sleep'
|
||||
import { isPaneResumingInPlace, isPaneSpawnDeferred } from './pane-spawn-deferral-registry'
|
||||
|
||||
export { getProviderSessionClaimKey }
|
||||
@@ -138,6 +139,19 @@ function findPreservedTabOwner(
|
||||
return null
|
||||
}
|
||||
|
||||
/** A workspace sleep's record whose tab is gone: main has no pane to resume it in, so the window reopens it in a fresh tab (#22657). */
|
||||
export function isStrandedWorkspaceSleepRecord(
|
||||
record: SleepingAgentSessionRecord,
|
||||
state: AppStoreState
|
||||
): boolean {
|
||||
const tabId = record.tabId ?? parsePaneKey(record.paneKey)?.tabId
|
||||
return (
|
||||
isWorkspaceSleepRecord(record) &&
|
||||
tabId !== undefined &&
|
||||
!findPreservedTabOwner(state, record.worktreeId, tabId)
|
||||
)
|
||||
}
|
||||
|
||||
export function recordPaneIsOwnedByPreservedPane(
|
||||
record: SleepingAgentSessionRecord,
|
||||
state: AppStoreState
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useAppStore } from '@/store'
|
||||
import type { SleepingAgentSessionRecord } from '../../../shared/agent-session-resume'
|
||||
import { isWorkspaceSleepRecord } from '../../../shared/workspace-sleep'
|
||||
import { activationTreatsNoteAsFinished } from './sleeping-agent-pane-ownership'
|
||||
|
||||
// A stranded workspace sleep record is the only handle to an unfinished session, never passive history.
|
||||
export function sweepTreatsAsFinished(record: SleepingAgentSessionRecord): boolean {
|
||||
return activationTreatsNoteAsFinished(record) && !isWorkspaceSleepRecord(record)
|
||||
}
|
||||
|
||||
// Main keeps workspace sleep records, so one this sweep settles must leave main's session too.
|
||||
export function settleSweptRecord(record: SleepingAgentSessionRecord): void {
|
||||
useAppStore.getState().clearSleepingAgentSession(record.paneKey)
|
||||
if (!isWorkspaceSleepRecord(record)) {
|
||||
return
|
||||
}
|
||||
const { worktreeId, paneKey } = record
|
||||
void Promise.resolve()
|
||||
.then(() => window.api.worktrees.retireStrandedSleepRecord({ worktreeId, paneKey }))
|
||||
.catch((error: unknown) =>
|
||||
console.warn('[sleep-resume] could not retire a stranded sleep record', error)
|
||||
)
|
||||
}
|
||||
@@ -26,6 +26,7 @@ const isPassiveSpy = vi.fn()
|
||||
vi.mock('./sleeping-agent-pane-ownership', () => ({
|
||||
activationTreatsNoteAsFinished: (record: unknown) => isPassiveSpy(record),
|
||||
recordPaneIsOwnedByPreservedPane: () => false,
|
||||
isStrandedWorkspaceSleepRecord: () => false,
|
||||
getProviderSessionClaimKey: (record: {
|
||||
worktreeId: string
|
||||
agent?: string
|
||||
|
||||
@@ -13,6 +13,7 @@ import { gateWorktreeAgentActivation } from './worktree-agent-activation-gate'
|
||||
import {
|
||||
getProviderSessionClaimKey,
|
||||
activationTreatsNoteAsFinished,
|
||||
isStrandedWorkspaceSleepRecord,
|
||||
recordPaneIsOwnedByPreservedPane
|
||||
} from './sleeping-agent-pane-ownership'
|
||||
|
||||
@@ -146,8 +147,11 @@ export function wakeSleepingAgentsForWorktreeInBackground(worktreeId: string): v
|
||||
detail: { worktreeId, wokenClaimKeys }
|
||||
})
|
||||
)
|
||||
// Why: main resumes a workspace sleep's records; the steps below are the window's own hibernation.
|
||||
const worktreeRecords = sleptRecords.filter((record) => !isWorkspaceSleepRecord(record))
|
||||
// Why: main resumes a workspace sleep's placed records; the steps below are the window's own hibernation and stranded sleeps.
|
||||
const state = useAppStore.getState()
|
||||
const worktreeRecords = sleptRecords.filter(
|
||||
(record) => !isWorkspaceSleepRecord(record) || isStrandedWorkspaceSleepRecord(record, state)
|
||||
)
|
||||
if (worktreeRecords.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -176,6 +176,7 @@ export function createWorktreesApi(): NonNullable<Partial<PreloadApi>['worktrees
|
||||
// A web client runs no VM, so a reopen during a sleep changes nothing.
|
||||
sleepReopened: async () => {},
|
||||
resumeSleptPane: async () => false,
|
||||
retireStrandedSleepRecord: async () => {},
|
||||
remove: async ({ worktreeId, hostId, force, allowUnverifiedPtyStop, skipArchive }) => {
|
||||
invalidateRuntimeWorktreeCaches()
|
||||
return callRuntimeResult<RemoveWorktreeResult>(
|
||||
|
||||
Reference in New Issue
Block a user