mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 09:27:29 +00:00
* fix(native-chat): refuse a stale startup-dialog card choice and add a card for the multi-server MCP dialog * fix(native-chat): drop casts from the startup-dialog guard * fix(native-chat): refuse a startup card choice until the screen model has applied all received output * fix(native-chat): refuse a clipped MCP server list and guard restored startup cards * fix(native-chat): allow one startup card choice per screen so a second client's keys can't hit the next dialog
253 lines
11 KiB
TypeScript
253 lines
11 KiB
TypeScript
import type { RuntimeTerminalAttention } from '../../shared/runtime-terminal-contracts'
|
|
import type { PaintedRestorePane } from '../../shared/painted-restore-ack'
|
|
import type {
|
|
PtyFencedTerminationRequest,
|
|
PtyFencedTerminationOutcome
|
|
} from '../../shared/terminal/pty/lifecycle/pty-fenced-termination'
|
|
import type { ResourceViewAdmission } from '../../shared/resource-view-admission'
|
|
import type { SpawnIntent } from '../../shared/terminal/pty/admission/spawn-admission-contract'
|
|
import { ipcRenderer } from 'electron'
|
|
import type { ProjectExecutionRuntimeResolution } from '../../shared/execution-host/project-execution-runtime'
|
|
import type { StartupCommandDelivery } from '../../shared/agent-launch/startup/codex-startup-delivery'
|
|
import type { TerminalInputKind } from '../../shared/terminal/input/terminal-input-kind'
|
|
import type {
|
|
TerminalLeafMoveRequest,
|
|
TerminalLeafMoveResult
|
|
} from '../../shared/workspace-layout/terminal-leaf-move'
|
|
import type {
|
|
SavedResumeDecision,
|
|
TerminalLaunchResult,
|
|
AgentProviderSessionMetadata,
|
|
SleepingAgentLaunchConfig
|
|
} from '../../shared/agent-session/lifecycle/agent-session-resume'
|
|
import type { TuiAgent } from '../../shared/agent-launch/catalog/tui-agent'
|
|
import type {
|
|
PtyListedSession,
|
|
PtySessionListScope
|
|
} from '../../shared/terminal/pty/lifecycle/pty-listed-session'
|
|
import type {
|
|
PtyRendererDeliveryHealthReply,
|
|
PtyRendererDeliveryStateReport
|
|
} from '../../shared/terminal/pty/diagnostics/pty-renderer-delivery-health'
|
|
import type { TerminalViewAttributes } from '../../shared/terminal/protocol/queries/terminal-view-attributes'
|
|
import type { PtyMainDeliveryDiagnostics } from '../../shared/terminal/pty/diagnostics/pty-delivery-diagnostics'
|
|
import type { AgentKind, LaunchSource, RequestKind } from '../../shared/telemetry-events'
|
|
import type { PreloadApi } from '../api-types'
|
|
import type { TerminalPanePlacement } from '../../shared/workspace-layout/terminal-pane-placement'
|
|
import type { AgentPaneLaunchRequest } from '../../shared/agent-launch/contracts/agent-pane-launch-request'
|
|
|
|
export const ptySessionControlApi = {
|
|
spawn: (opts: {
|
|
cols: number
|
|
rows: number
|
|
cwd?: string
|
|
env?: Record<string, string>
|
|
envToDelete?: string[]
|
|
command?: string
|
|
commandDelivery?: 'renderer' | 'provider'
|
|
launchConfig?: SleepingAgentLaunchConfig
|
|
resumeProviderSession?: AgentProviderSessionMetadata
|
|
resumeOrigin?: 'saved'
|
|
launchToken?: string
|
|
launchAgent?: TuiAgent
|
|
trustWorktreeId?: string
|
|
startupCommandDelivery?: StartupCommandDelivery
|
|
connectionId?: string | null
|
|
worktreeId?: string
|
|
sessionId?: string
|
|
shellOverride?: string
|
|
projectRuntime?: ProjectExecutionRuntimeResolution
|
|
terminalKittyKeyboardProtocol?: boolean
|
|
terminalColorQueryReplies?: { foreground?: string; background?: string }
|
|
initiallyHidden?: boolean
|
|
tabId?: string
|
|
leafId?: string
|
|
replacesPtyId?: string
|
|
placement?: TerminalPanePlacement
|
|
spawnIntent?: SpawnIntent
|
|
wslPassiveRestore?: true
|
|
agentLaunch?: AgentPaneLaunchRequest
|
|
telemetry?: { agent_kind: AgentKind; launch_source: LaunchSource; request_kind: RequestKind }
|
|
}): Promise<{
|
|
id: string
|
|
incarnationId?: string
|
|
launchConfig?: SleepingAgentLaunchConfig
|
|
launchAgent?: TuiAgent
|
|
snapshot?: string
|
|
snapshotCols?: number
|
|
snapshotRows?: number
|
|
snapshotPrefixAnsi?: string
|
|
snapshotFrameAnsi?: string
|
|
snapshotFrameRestoreAnsi?: string
|
|
snapshotKittyKeyboardFlags?: number
|
|
snapshotTerminalOwner?: 'shell'
|
|
snapshotSeq?: number
|
|
isReattach?: boolean
|
|
isAlternateScreen?: boolean
|
|
replay?: string
|
|
sessionExpired?: boolean
|
|
coldRestore?: { scrollback: string; cwd: string; cols?: number; rows?: number }
|
|
startupCwdFallback?: { kind: 'worktree'; cwd: string }
|
|
agentResumeUnavailable?: true
|
|
agentResumeRefused?: 'chat-home' | 'chat-writing' | 'unverifiable'
|
|
savedResumeDecision?: SavedResumeDecision
|
|
refusedResume?: TerminalLaunchResult['refusedResume']
|
|
launchToken?: string
|
|
resumeProviderSession?: TerminalLaunchResult['resumeProviderSession']
|
|
shellReadyArmed?: boolean
|
|
}> => ipcRenderer.invoke('pty:spawn', opts),
|
|
write: (id: string, data: string, inputKind: TerminalInputKind): void => {
|
|
ipcRenderer.send('pty:write', { id, data, inputKind })
|
|
},
|
|
noteUserInput: (): void => ipcRenderer.send('pty:noteUserInput'),
|
|
writeAccepted: (
|
|
id: string,
|
|
data: string,
|
|
inputKind: TerminalInputKind,
|
|
options?: { requireWriteSettlement?: true; requireStartupDialog?: string }
|
|
): Promise<boolean> =>
|
|
ipcRenderer.invoke('pty:writeAccepted', { id, data, inputKind, ...options }),
|
|
onWriteUnavailable: (callback: (payload: { id: string }) => void): (() => void) => {
|
|
const handler = (_event: Electron.IpcRendererEvent, payload: { id: string }): void =>
|
|
callback(payload)
|
|
ipcRenderer.on('pty:writeUnavailable', handler)
|
|
return () => ipcRenderer.removeListener('pty:writeUnavailable', handler)
|
|
},
|
|
resize: (id: string, cols: number, rows: number): void => {
|
|
ipcRenderer.send('pty:resize', { id, cols, rows })
|
|
},
|
|
claimViewport: (id: string, cols: number, rows: number): void => {
|
|
ipcRenderer.send('pty:claimViewport', { id, cols, rows })
|
|
},
|
|
getTerminalAttention: (ids: string[]): Promise<Record<string, RuntimeTerminalAttention>> =>
|
|
ipcRenderer.invoke('pty:getTerminalAttention', ids),
|
|
reportGeometry: (id: string, cols: number, rows: number): void => {
|
|
ipcRenderer.send('pty:reportGeometry', { id, cols, rows })
|
|
},
|
|
signal: (id: string, signal: string): void => {
|
|
ipcRenderer.send('pty:signal', { id, signal })
|
|
},
|
|
clearBuffer: (id: string): void => {
|
|
ipcRenderer.send('pty:clearBuffer', { id })
|
|
},
|
|
resetInputModes: (id: string): void => {
|
|
ipcRenderer.send('pty:resetInputModes', { id })
|
|
},
|
|
ackColdRestore: (id: string, pane?: PaintedRestorePane): void => {
|
|
ipcRenderer.send('pty:ackColdRestore', pane ? { id, pane } : { id })
|
|
},
|
|
ackHiddenOutputRestoreApplied: (id: string, hiddenDropEpisode?: number): void => {
|
|
ipcRenderer.send('pty:hiddenOutputRestoreApplied', { id, hiddenDropEpisode })
|
|
},
|
|
ackData: (id: string, charCount: number, processedChars?: number): void => {
|
|
ipcRenderer.send('pty:ackData', {
|
|
id,
|
|
charCount,
|
|
...(typeof processedChars === 'number' ? { processedChars } : {})
|
|
})
|
|
},
|
|
onDeliveryResyncRequest: (callback: (payload: { requestId: number }) => void): (() => void) => {
|
|
const listener = (_event: Electron.IpcRendererEvent, payload: { requestId: number }) =>
|
|
callback(payload)
|
|
ipcRenderer.on('pty:requestDeliveryResync', listener)
|
|
return () => ipcRenderer.removeListener('pty:requestDeliveryResync', listener)
|
|
},
|
|
respondDeliveryResync: (payload: {
|
|
requestId: number
|
|
processedCharsByPty: Record<string, number>
|
|
}): void => {
|
|
ipcRenderer.send('pty:deliveryResyncResponse', payload)
|
|
},
|
|
reportRendererDeliveryState: (
|
|
report: PtyRendererDeliveryStateReport
|
|
): Promise<PtyRendererDeliveryHealthReply> =>
|
|
ipcRenderer.invoke('pty:reportRendererDeliveryState', report),
|
|
getPtyDataListenerCount: (): number => ipcRenderer.listenerCount('pty:data'),
|
|
rendererDispatcherReady: (): void => {
|
|
ipcRenderer.send('pty:rendererDispatcherReady')
|
|
},
|
|
setActiveRendererPty: (id: string, active: boolean): void => {
|
|
ipcRenderer.send('pty:setActiveRendererPty', { id, active })
|
|
},
|
|
setRendererPtyVisible: (id: string, visible: boolean): void => {
|
|
ipcRenderer.send('pty:setRendererPtyVisible', { id, visible })
|
|
},
|
|
setHiddenRendererPty: (id: string, hidden: boolean): void => {
|
|
ipcRenderer.send('pty:setHiddenRendererPty', { id, hidden })
|
|
},
|
|
setPtyDeliveryInterest: (id: string, interested: boolean): void => {
|
|
ipcRenderer.send('pty:setPtyDeliveryInterest', { id, interested })
|
|
},
|
|
publishTerminalViewAttributes: (attributes: TerminalViewAttributes): void => {
|
|
ipcRenderer.send('pty:terminalViewAttributes', attributes)
|
|
},
|
|
killFenced: (request: PtyFencedTerminationRequest): Promise<PtyFencedTerminationOutcome> =>
|
|
ipcRenderer.invoke('pty:killFenced', request),
|
|
kill: (id: string, opts?: { keepHistory?: boolean }): Promise<void> =>
|
|
ipcRenderer.invoke('pty:kill', { id, keepHistory: opts?.keepHistory ?? false }),
|
|
listSessions: (
|
|
scope?: PtySessionListScope,
|
|
admission?: ResourceViewAdmission
|
|
): Promise<PtyListedSession[]> => ipcRenderer.invoke('pty:listSessions', scope, admission),
|
|
getAuthoritativeBufferSnapshotCapabilities: (
|
|
ids: string[]
|
|
): Promise<{ id: string; authoritative: boolean | null }[]> =>
|
|
ipcRenderer.invoke('pty:getAuthoritativeBufferSnapshotCapabilities', { ids }),
|
|
getGoneSessionIds: (ids: string[]): Promise<string[]> =>
|
|
ipcRenderer.invoke('pty:getGoneSessionIds', { ids }),
|
|
hasPty: (id: string): Promise<boolean | null> => ipcRenderer.invoke('pty:hasPty', { id }),
|
|
moveLeafToNewTab: (request: TerminalLeafMoveRequest): Promise<TerminalLeafMoveResult> =>
|
|
ipcRenderer.invoke('pty:moveLeafToNewTab', request),
|
|
getMainBufferSnapshot: (
|
|
id: string,
|
|
opts?: { scrollbackRows?: number }
|
|
): Promise<{
|
|
data: string
|
|
frameRestoreAnsi?: string
|
|
cols: number
|
|
rows: number
|
|
cwd?: string | null
|
|
seq?: number
|
|
pendingDeliveryStartSeq?: number
|
|
source?: 'headless' | 'renderer'
|
|
alternateScreen?: boolean
|
|
scrollbackAnsi?: string
|
|
pendingEscapeTailAnsi?: string
|
|
kittyKeyboardFlags?: number
|
|
terminalOwner?: 'shell'
|
|
} | null> => ipcRenderer.invoke('pty:getMainBufferSnapshot', { id, opts }),
|
|
getRendererDeliveryDebugSnapshot: (): Promise<{
|
|
pendingPtyCount: number
|
|
pendingChars: number
|
|
maxPendingCharsByPty: number
|
|
rendererInFlightPtyCount: number
|
|
rendererInFlightChars: number
|
|
maxRendererInFlightCharsByPty: number
|
|
activeRendererPtyCount: number
|
|
flushScheduled: boolean
|
|
peakPendingChars: number
|
|
peakMaxPendingCharsByPty: number
|
|
peakRendererInFlightChars: number
|
|
peakMaxRendererInFlightCharsByPty: number
|
|
ackGatedFlushSkipCount: number
|
|
hiddenDeliveryGatedPtyCount: number
|
|
hiddenDeliveryGatedVisiblePtyCount: number
|
|
hiddenDeliveryGatedActivePtyCount: number
|
|
deliveryInterestPtyCount: number
|
|
hiddenDeliveryDroppedChars: number
|
|
hiddenDeliveryDroppedChunks: number
|
|
pendingDroppedChars: number
|
|
diagnostics: PtyMainDeliveryDiagnostics
|
|
rendererLifecycleResetCount: number
|
|
lastLifecycleResetClearedChars: number
|
|
rendererPtyDispatcherReady: boolean
|
|
rendererDispatcherReadyForcedCount: number
|
|
}> => ipcRenderer.invoke('pty:getRendererDeliveryDebugSnapshot'),
|
|
resetRendererDeliveryDebug: (): Promise<void> =>
|
|
ipcRenderer.invoke('pty:resetRendererDeliveryDebug'),
|
|
hasChildProcesses: (id: string): Promise<boolean> =>
|
|
ipcRenderer.invoke('pty:hasChildProcesses', { id }),
|
|
getForegroundProcess: (id: string): Promise<string | null> =>
|
|
ipcRenderer.invoke('pty:getForegroundProcess', { id })
|
|
} satisfies Partial<PreloadApi['pty']>
|