Fix browser annotation sends to active Windows agents (#8309)

* fix: authorize guarded sends to active ConPTY agents

* Fix stale-handle rejection using controller-less status checks and contr

- Distinguish `terminal_handle_stale` (PTY rebind mid-check) from `terminal_exited`
  so callers get an accurate reason instead of a generic `terminal_not_writable`.
- Move the PTY binding assertion to always run after the fresh foreground read,
  even when no controller is present, closing a gap where a rebind during a
  controller-less check went undetected.
- Call `confirmForegroundProcess` through its owning controller instance so the
  method keeps its `this` receiver instead of being invoked unbound.
- Update reliability gates and design doc to reflect the new test coverage and
  corrected error semantics.

* rm design doc
This commit is contained in:
Jinjing
2026-07-11 14:32:55 -07:00
committed by GitHub
parent 800967278e
commit 2802f0419d
7 changed files with 652 additions and 24 deletions
+38 -1
View File
@@ -348,6 +348,31 @@ function isTerminalSendGuardNotWritable(error: unknown): boolean {
return message.includes('terminal_guard_not_writable')
}
function isTerminalAgentStatusNotWritable(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error)
return [
'terminal_not_writable',
'terminal_handle_stale',
'terminal_gone',
'terminal_exited'
].some((code) => message.includes(code))
}
function assertTerminalSendExactPtyBinding(
runtime: OrcaRuntimeService,
handle: string,
expectedPtyId: string | undefined
): void {
try {
if (expectedPtyId && runtime.resolveLiveLeafForHandle(handle)?.ptyId === expectedPtyId) {
return
}
} catch {
// Fall through to the stable guarded-send result below.
}
throw new Error('terminal_guard_not_writable')
}
function appendPendingMultiplexOutput(
stream: TerminalMultiplexStream,
data: string,
@@ -1148,10 +1173,22 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [
const assertSendPreconditions =
params.requireAgentStatus === 'sendable'
? async (ptyId?: string): Promise<void> => {
assertTerminalSendExactPtyBinding(runtime, params.terminal, ptyId)
if (ptyId && isTerminalInputLockedForClient(runtime, ptyId, params.client)) {
throw new Error('terminal_guard_not_writable')
}
const agentStatus = await runtime.getTerminalAgentStatus(params.terminal)
let agentStatus
try {
agentStatus = await runtime.getTerminalAgentStatus(params.terminal)
} catch (error) {
if (isTerminalAgentStatusNotWritable(error)) {
throw new Error('terminal_guard_not_writable')
}
throw error
}
// Why: a send callback can race a pane reconnect; status evidence
// must never authorize the callback's replacement PTY.
assertTerminalSendExactPtyBinding(runtime, params.terminal, ptyId)
if (!agentStatus.isRunningAgent) {
throw new Error('terminal_guard_no_agent')
}