fix(terminal): commit the adopted-claim reattach grid and reject non-integer provider grids

Review follow-ups. The runtime spawn path's adopted-claim branch returned before the size
commit, so an adoption attaching to a live session kept whatever the caller requested; it now
commits and reflows like every other reattach. The grid validator requires integers so a
malformed provider grid falls through to the cached size instead of reaching xterm.
This commit is contained in:
Jinwoo-H
2026-09-04 17:14:29 -04:00
parent 6fdfa88d7c
commit 3937bce103
5 changed files with 91 additions and 11 deletions
@@ -98,6 +98,16 @@ describe('resolveCommittedPtySize', () => {
).toEqual(CACHED)
})
it('rejects a non-integer provider grid as unproven', () => {
expect(
resolveCommittedPtySize({
result: { isReattach: true, snapshotCols: 120.5, snapshotRows: 40 },
requested: REQUESTED,
cachedBeforeAttach: CACHED
})
).toEqual(CACHED)
})
it('takes the request only when nothing better exists', () => {
expect(
resolveCommittedPtySize({
@@ -6,8 +6,8 @@ export type PtyGrid = { cols: number; rows: number }
function positiveGrid(cols: unknown, rows: unknown): PtyGrid | undefined {
return typeof cols === 'number' &&
typeof rows === 'number' &&
Number.isFinite(cols) &&
Number.isFinite(rows) &&
Number.isInteger(cols) &&
Number.isInteger(rows) &&
cols > 0 &&
rows > 0
? { cols, rows }
@@ -0,0 +1,56 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { ptySizes } from '../delivery/visibility-state'
import { commitRuntimePtySpawn } from './spawn-commit'
import { createRuntimePtySpawnState, type RuntimePtySpawnArgs } from './spawn-state'
import type { PtyRuntimeControllerDeps } from './controller-deps'
const PTY_ID = 'orca-pty-adopted'
const LIVE_GRID = { cols: 211, rows: 57 }
function makeRuntime() {
return {
registerPreAllocatedHandleForPty: vi.fn(),
registerPty: vi.fn(),
reflowHeadlessTerminalToPtyGrid: vi.fn(),
seedHeadlessTerminal: vi.fn(),
noteTerminalSpawnCommand: vi.fn()
}
}
describe('runtime spawn commit: adopted agent-session claim', () => {
afterEach(() => {
ptySizes.delete(PTY_ID)
})
it('commits the live grid from the adoption reply before the early return', async () => {
const runtime = makeRuntime()
const deps = { runtime, store: undefined, options: {} } as unknown as PtyRuntimeControllerDeps
const args = { cols: 120, rows: 40, worktreeId: 'wt-1' } as unknown as RuntimePtySpawnArgs
const ctx = createRuntimePtySpawnState(deps, args)
ctx.result = {
id: PTY_ID,
isReattach: true,
snapshotCols: LIVE_GRID.cols,
snapshotRows: LIVE_GRID.rows,
agentSessionEnsure: {
disposition: 'adopted',
owner: {
claim: { kind: 'terminal' },
generation: 'g1',
phase: 'live',
ptyId: PTY_ID,
surface: { worktreeId: 'wt-1', tabId: 'tab-1', leafId: 'leaf-1', terminalHandle: 'h1' }
}
}
} as unknown as typeof ctx.result
await commitRuntimePtySpawn(ctx)
expect(ptySizes.get(PTY_ID)).toEqual(LIVE_GRID)
expect(runtime.reflowHeadlessTerminalToPtyGrid).toHaveBeenCalledWith(
PTY_ID,
LIVE_GRID.cols,
LIVE_GRID.rows
)
})
})
@@ -0,0 +1,18 @@
import { commitAttachedPtySize } from '../delivery/attached-pty-size'
import type { RuntimePtySpawnState } from './spawn-state'
/** Record the settled grid for a runtime-path spawn; `result` is passed explicitly because the
* adopted-claim branch commits with `isReattach` forced on before it returns early. */
export function commitRuntimePtySize(
ctx: RuntimePtySpawnState,
result: RuntimePtySpawnState['result']
): void {
commitAttachedPtySize({
result,
requested: { cols: ctx.args.cols, rows: ctx.args.rows },
cachedBeforeAttach: ctx.sessionSizeBeforeAttach,
reflowHeadlessTerminalToPtyGrid: ctx.deps.runtime?.reflowHeadlessTerminalToPtyGrid?.bind(
ctx.deps.runtime
)
})
}
+5 -9
View File
@@ -1,7 +1,7 @@
import { isValidTerminalTabId } from '../../../../shared/terminal-tab-id'
import { ptyOwnership, ptyIncarnationById, deletePtyOwnership } from '../provider/ownership-state'
import { ptySizes } from '../delivery/visibility-state'
import { commitAttachedPtySize } from '../delivery/attached-pty-size'
import { commitRuntimePtySize } from './spawn-commit-pty-size'
import {
shouldSkipCodexHomeEnvForWindowsShell,
recordCodexPaneAccountForSpawn,
@@ -87,6 +87,9 @@ export async function commitRuntimePtySpawn(ctx: RuntimePtySpawnState) {
...(ctx.env ? { launchEnv: ctx.env } : {})
})
}
// Why: an adoption attaches to a live session too, and this branch returns before the
// normal commit site; without this the cache keeps whatever the caller requested.
commitRuntimePtySize(ctx, { ...ctx.result, isReattach: true })
// Why: the adopted branch returns before the normal settle site, so the
// reservation must be resolved here or every later spawn for this pane
// awaits a promise that never settles.
@@ -126,14 +129,7 @@ export async function commitRuntimePtySpawn(ctx: RuntimePtySpawnState) {
if (!ctx.hostSessionBinding) {
persistSshLease()
}
commitAttachedPtySize({
result: ctx.result,
requested: { cols: args.cols, rows: args.rows },
cachedBeforeAttach: ctx.sessionSizeBeforeAttach,
reflowHeadlessTerminalToPtyGrid: ctx.deps.runtime?.reflowHeadlessTerminalToPtyGrid?.bind(
ctx.deps.runtime
)
})
commitRuntimePtySize(ctx, ctx.result)
if (ctx.effectiveSessionAppId !== undefined && ctx.effectiveSessionAppId !== ctx.result.id) {
ptySizes.delete(ctx.effectiveSessionAppId)
}