mirror of
https://github.com/stablyai/orca.git
synced 2026-10-02 16:02:15 +00:00
fix(pty): preserve fence refusals across providers and UI
This commit is contained in:
@@ -19,7 +19,7 @@ import {
|
||||
|
||||
describe('daemon protocol version', () => {
|
||||
it('ships bounded history transfer after the 2031-unsubscribe fact', () => {
|
||||
expect(PROTOCOL_VERSION).toBe(36)
|
||||
expect(PROTOCOL_VERSION).toBe(37)
|
||||
expect(CONTENT_ADDRESSED_SHELL_WRAPPER_DAEMON_PROTOCOL_VERSION).toBe(36)
|
||||
expect(ASYNC_CWD_VALIDATION_DAEMON_PROTOCOL_VERSION).toBe(35)
|
||||
expect(CODEX_SHELL_LAUNCH_PREFLIGHT_DAEMON_PROTOCOL_VERSION).toBe(34)
|
||||
@@ -33,7 +33,7 @@ describe('daemon protocol version', () => {
|
||||
expect(AGENT_SESSION_CLAIM_DAEMON_PROTOCOL_VERSION).toBe(26)
|
||||
expect(AGENT_SESSION_CREATE_OPERATION_DAEMON_PROTOCOL_VERSION).toBe(26)
|
||||
expect(PREVIOUS_DAEMON_PROTOCOL_VERSIONS).toEqual(
|
||||
Array.from({ length: 35 }, (_, index) => index + 1)
|
||||
Array.from({ length: 36 }, (_, index) => index + 1)
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export const CLEAN_DISCONNECT_PROTOCOL_VERSION = 24
|
||||
export const MODE_2031_UNSUBSCRIBE_FACT_PROTOCOL_VERSION = 29
|
||||
export const PREVIOUS_DAEMON_PROTOCOL_VERSIONS = [
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
|
||||
28, 29, 30, 31, 32, 33, 34, 35
|
||||
28, 29, 30, 31, 32, 33, 34, 35, 36
|
||||
] as const
|
||||
|
||||
export function supportsPtyStartupIngress(protocolVersion: number): boolean {
|
||||
|
||||
@@ -72,6 +72,7 @@ export async function shutdownSinglePty(
|
||||
// A pending daemon teardown can reject a stale incarnation without throwing.
|
||||
// Keep client ownership intact until bulk verification classifies that refusal.
|
||||
if (result?.fenceUnavailable) {
|
||||
deps.runtime?.clearPtyStopRequested?.(id)
|
||||
return result
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -217,7 +217,7 @@ export async function shutdownLocalPty(
|
||||
): Promise<PtyShutdownResult | void> {
|
||||
const expectedIncarnationId = ptyIncarnations.get(id)
|
||||
if (opts.incarnationId !== undefined && opts.incarnationId !== expectedIncarnationId) {
|
||||
throw new Error(`PTY incarnation mismatch: ${id}`)
|
||||
return { fenceUnavailable: true }
|
||||
}
|
||||
cancelPendingLocalPtySpawns(id)
|
||||
const pending = ptyShutdownOperations.get(id)
|
||||
|
||||
@@ -20823,6 +20823,10 @@ export class OrcaRuntimeService {
|
||||
this.stopRequestedPtyIds.add(ptyId)
|
||||
}
|
||||
|
||||
clearPtyStopRequested(ptyId: string): void {
|
||||
this.stopRequestedPtyIds.delete(ptyId)
|
||||
}
|
||||
|
||||
isPtyStopRequested(ptyId: string): boolean {
|
||||
return this.stopRequestedPtyIds.has(ptyId)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import type {
|
||||
UnifiedWorktreeRow
|
||||
} from './resource-usage-merge-types'
|
||||
import { buildResourceSessionBindingIndex } from './resource-session-bindings'
|
||||
import { killVerdictFields } from './resource-session-kill-verdict-fields'
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────────────
|
||||
|
||||
@@ -134,6 +135,11 @@ function resolveDaemonSessionLabel(
|
||||
export const UNATTRIBUTED_REPO_ID = '__unattributed__'
|
||||
export const UNATTRIBUTED_REPO_NAME = 'Unattributed'
|
||||
|
||||
type SessionWithKillVerdict = DaemonSession & {
|
||||
killVerdict?: UnifiedSessionRow['killVerdict']
|
||||
killReason?: string
|
||||
}
|
||||
|
||||
export function mergeSnapshotAndSessions(
|
||||
snapshot: MemorySnapshot | null,
|
||||
daemonSessions: readonly DaemonSession[],
|
||||
@@ -152,6 +158,9 @@ export function mergeSnapshotAndSessions(
|
||||
const ownershipBySessionId = new Map(
|
||||
daemonSessions.map((session) => [session.id, session.agentOwnership])
|
||||
)
|
||||
const verdictBySessionId = new Map(
|
||||
daemonSessions.map((session) => [session.id, session as SessionWithKillVerdict])
|
||||
)
|
||||
|
||||
function isRepoRemote(repoId: string): boolean {
|
||||
// Why: missing entry === we don't know about this repo (typically the
|
||||
@@ -206,8 +215,10 @@ export function mergeSnapshotAndSessions(
|
||||
const sessions: UnifiedSessionRow[] = wt.sessions.map((s) => {
|
||||
seenSessionIds.add(s.sessionId)
|
||||
const tabId = index.ptyIdToTabId.get(s.sessionId) ?? null
|
||||
const verdict = verdictBySessionId.get(s.sessionId)
|
||||
return {
|
||||
sessionId: s.sessionId,
|
||||
...(verdict ? killVerdictFields(verdict) : {}),
|
||||
paneKey: s.paneKey,
|
||||
pid: s.pid,
|
||||
label: resolveSnapshotSessionLabel(s, wt.worktreeId, ctx),
|
||||
@@ -297,6 +308,7 @@ export function mergeSnapshotAndSessions(
|
||||
row.sessions.push({
|
||||
sessionId: session.id,
|
||||
...(session.incarnationId ? { incarnationId: session.incarnationId } : {}),
|
||||
...killVerdictFields(session as SessionWithKillVerdict),
|
||||
paneKey: null,
|
||||
pid: 0,
|
||||
label: resolveDaemonSessionLabel(session, worktreeId, tabId, ctx),
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { UnifiedSessionRow } from './resource-usage-merge-types'
|
||||
|
||||
type SessionWithKillVerdict = {
|
||||
killVerdict?: UnifiedSessionRow['killVerdict']
|
||||
killReason?: string
|
||||
}
|
||||
|
||||
export function killVerdictFields(
|
||||
session: SessionWithKillVerdict
|
||||
): Pick<UnifiedSessionRow, 'killVerdict' | 'killReason'> {
|
||||
return session.killVerdict
|
||||
? {
|
||||
killVerdict: session.killVerdict,
|
||||
...(session.killReason ? { killReason: session.killReason } : {})
|
||||
}
|
||||
: {}
|
||||
}
|
||||
Reference in New Issue
Block a user