mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 16:02:56 +00:00
A relay answer of sourceRecovery.status === 'restoreRequired' is a SUCCESSFUL RPC that proves the PTY is alive - only its output delivery was retired. The spawn reattach path raised SSH_SESSION_EXPIRED for it, which is the one token that authorises replacing the pane's PTY: both spawn-execute sites then call markSshRemotePtyLease(..., 'expired') and deletePtyOwnership(), and the renderer cold-starts a fresh agent over the same worktree. A live remote agent was orphaned by a request that had just observed it running. Split the vocabulary so the two verdicts cannot be confused (docs/reference/ssh-execution-boundary.md), retry the attach first since a retired delivery is repairable, and give the token its own user-facing copy so it never inherits the "open a new terminal" advice. Second root cause, same symptom: the relay's delivery record was keyed on client id alone, so a re-bound primary channel matched a delivery bound to the dead sink and got 'existing' back - the pane repaints and never receives frames. Key it on the transport generation too, as the dispatcher's own publication ledger already does, and scope every release/retire path in activate() to a record the caller still owns.
40 lines
1.7 KiB
TypeScript
40 lines
1.7 KiB
TypeScript
import { SSH_PTY_IDENTITY_MISMATCH_ERROR } from '../../shared/ssh-pty-failure-tokens'
|
|
|
|
export {
|
|
SSH_PTY_IDENTITY_MISMATCH_ERROR,
|
|
SSH_SESSION_EXPIRED_ERROR,
|
|
SSH_SOURCE_RESTORE_REQUIRED_ERROR
|
|
} from '../../shared/ssh-pty-failure-tokens'
|
|
|
|
export function isSshPtyNotFoundError(error: unknown): boolean {
|
|
const message = error instanceof Error ? error.message : String(error)
|
|
return /PTY ".+" not found/i.test(message)
|
|
}
|
|
|
|
export function isSshPtyIdentityMismatchError(error: unknown): boolean {
|
|
const message = error instanceof Error ? error.message : String(error)
|
|
return message.includes(SSH_PTY_IDENTITY_MISMATCH_ERROR) || /identity mismatch/i.test(message)
|
|
}
|
|
|
|
/**
|
|
* A reachable relay answered for this exact PTY id and reported it absent — positive evidence of
|
|
* absence from the execution host, so `exited` rather than `unverifiable`
|
|
* (docs/reference/ssh-execution-boundary.md). Deliberately NOT raised for a transport failure, a
|
|
* request timeout, a disposed multiplexer, an identity mismatch (the id names a live PTY belonging
|
|
* to another pane), or `restoreRequired` (the PTY is live, only its source stream is not) — none of
|
|
* those observe the process, and treating them as absence orphans live remote work.
|
|
*
|
|
* Carries the same `SSH_SESSION_EXPIRED` message so message-based consumers are unaffected; only
|
|
* callers that can act on the stronger verdict test the class.
|
|
*/
|
|
export class SshPtyAbsentFromRelayError extends Error {
|
|
constructor(message: string) {
|
|
super(message)
|
|
this.name = 'SshPtyAbsentFromRelayError'
|
|
}
|
|
}
|
|
|
|
export function isSshPtyAbsentFromRelayError(error: unknown): boolean {
|
|
return error instanceof SshPtyAbsentFromRelayError
|
|
}
|