mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(automations): a slept server run opens its own tab from a paired desktop (#32132)
* fix(automations): a slept headless run opens from a paired desktop, and a half-failed sleep keeps its pane link A paired desktop now asks the server to open a run whose pane retention slept (its PTY id is dropped, its pane kept), instead of only opening the workspace. Retention finishes a sleep that stopped the run's PTY and then failed, as the host's sleep state still holds it, instead of forgetting the run's pane on the next sweep. * test: use typed automation retention drain hook * test(automations): fail the real resume-record write, and recover a slept run whose record failed to save * revert(automations): drop the half-failed sleep recovery, which no real path reaches A real headless serve keeps a slept run's pane resolving to its stopped PTY, so retention's retry already re-sleeps and keeps the pane link; a failed resume-record write is swallowed inside the sleep. Only a mocked null PTY lookup reached the runSleepHeld branch.
This commit is contained in:
@@ -105,6 +105,7 @@ export function resolveAutomationRunTerminalTarget(
|
||||
const mirroredTabId = toWebTerminalSurfaceTabId(tabId)
|
||||
const layout = terminals.terminalLayoutsByTabId[mirroredTabId]
|
||||
const mirroredPtyId = layout?.ptyIdsByLeafId?.[parsed.leafId]
|
||||
// Why a live PTY only: a slept run's pane is asked of the server, which knows if it still exists.
|
||||
if (
|
||||
!run.terminalPtyId ||
|
||||
!terminals.hasTerminalTab(mirroredTabId) ||
|
||||
|
||||
@@ -91,6 +91,21 @@ describe('automation run view state', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps View run for a paired server run whose pane slept, which only the server can open', () => {
|
||||
const slept = makeRun({ terminalPtyId: null })
|
||||
expect(
|
||||
getAutomationRunViewState({
|
||||
run: slept,
|
||||
workspaceExists: true,
|
||||
terminalTargetExists: false,
|
||||
terminalOnPairedServer: true
|
||||
})
|
||||
).toMatchObject({ availability: 'terminal', actionLabel: 'View run', canOpen: true })
|
||||
expect(
|
||||
getAutomationRunViewState({ run: slept, workspaceExists: true, terminalTargetExists: false })
|
||||
).toMatchObject({ availability: 'workspace', actionLabel: 'Open workspace' })
|
||||
})
|
||||
|
||||
it('resumes the workspace only when there is no exact terminal identity', () => {
|
||||
expect(
|
||||
getAutomationRunViewState({
|
||||
|
||||
@@ -61,6 +61,8 @@ export function getAutomationRunViewState({
|
||||
terminalOnPairedServer?: boolean
|
||||
}): AutomationRunViewState {
|
||||
const hasTerminalIdentity = Boolean(run.terminalPaneKey && run.terminalPtyId)
|
||||
// Why the pane alone: a slept run drops its PTY id but keeps its pane, which the server resumes.
|
||||
const hasPaneIdentity = Boolean(run.terminalPaneKey)
|
||||
if (run.workspaceId && workspaceExists && terminalTargetExists) {
|
||||
return {
|
||||
availability: 'terminal',
|
||||
@@ -70,7 +72,7 @@ export function getAutomationRunViewState({
|
||||
}
|
||||
}
|
||||
|
||||
if (run.workspaceId && workspaceExists && hasTerminalIdentity && terminalOnPairedServer) {
|
||||
if (run.workspaceId && workspaceExists && hasPaneIdentity && terminalOnPairedServer) {
|
||||
return {
|
||||
availability: 'terminal',
|
||||
actionLabel: 'View run',
|
||||
|
||||
+15
-2
@@ -95,13 +95,13 @@ function storeState(mirrored: boolean): Record<string, unknown> {
|
||||
}
|
||||
}
|
||||
|
||||
function openRun(): void {
|
||||
function openRun(target: AutomationRun = run): void {
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the action reads only these members.
|
||||
const context = {
|
||||
store: { repoForRow: () => undefined, worktreeForRow: () => worktree },
|
||||
list: { selectedRow: { key: 'row' } }
|
||||
} as unknown as AutomationsPageActionContext
|
||||
createAutomationRunWorkspaceAction(context)(run)
|
||||
createAutomationRunWorkspaceAction(context)(target)
|
||||
}
|
||||
|
||||
describe('opening a paired-server automation run', () => {
|
||||
@@ -142,4 +142,17 @@ describe('opening a paired-server automation run', () => {
|
||||
expect(mocks.toastError).toHaveBeenCalledWith('Run terminal is unavailable.')
|
||||
)
|
||||
})
|
||||
|
||||
it('asks the server to open a run whose pane it slept, PTY id dropped', async () => {
|
||||
mocks.state = storeState(false)
|
||||
|
||||
openRun({ ...run, terminalPtyId: null })
|
||||
await vi.waitFor(() => expect(mocks.activateWebRuntimeSessionTab).toHaveBeenCalled())
|
||||
|
||||
expect(mocks.activateAndRevealWorktree).toHaveBeenCalledWith(WT)
|
||||
expect(mocks.activateWebRuntimeSessionTab).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ worktreeId: WT, tabId: HOST_TAB, environmentId: ENV, leafId: LEAF })
|
||||
)
|
||||
expect(mocks.toastMessage).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user