Files
orca/src/main/daemon/daemon-server.ts
T
NeilandOrca fddb19f697 snapshot: terminal session authority construction tree (unshippable, for salvage)
Preserves the full uncommitted construction tree from the prior agent's
comprehensive redesign so it can be mined during decomposition.
Not intended to ship: +60.9k net production LOC, 0/8 goalposts, 0/13 journeys.

Co-authored-by: Orca <help@stably.ai>
2026-08-07 17:32:53 -07:00

2259 lines
86 KiB
TypeScript

/* eslint-disable max-lines -- Why: one class owns the daemon socket protocol, routing, stream fanout, and session lifecycle. */
import { createServer, type Server, type Socket } from 'node:net'
import { createHash, randomUUID } from 'node:crypto'
import { performance } from 'node:perf_hooks'
import { writeFileSync, chmodSync, unlinkSync } from 'node:fs'
import { StringDecoder } from 'node:string_decoder'
import { encodeNdjson, createNdjsonParser } from './ndjson'
import { TerminalHost, type TerminalHostOptions } from './terminal-host'
import { DaemonStreamDataBatcher } from './daemon-stream-data-batcher'
import {
BackgroundTransientFactRelay,
BACKGROUND_STREAM_DROP_ENABLED
} from './daemon-background-transient-facts'
import { extractHiddenStartupRendererQueryData } from '../../shared/terminal-reply-query-extraction'
import {
recordDaemonStreamBacklogEvent,
startDaemonStreamBacklogProbe
} from './daemon-stream-backlog-probe'
import { readCurrentProcessMacSystemResolverHealth } from '../network/macos-system-resolver-health'
import type { SubprocessHandle } from './session'
import { checkPtySpawnHealth } from './pty-subprocess'
import { createNoopDaemonFileLog, type DaemonFileLog } from './daemon-file-log'
import { isTuiAgent } from '../../shared/tui-agent-config'
import { parsePtyStartupIngressIntent } from '../../shared/pty-startup-ingress'
import { unlinkOwnedDaemonPidFile, unlinkOwnedDaemonTokenFile } from './daemon-spawner'
import {
getDaemonSocketBindPath,
publishDaemonSocketPath,
readDaemonEndpointOwnershipState,
unlinkOwnedDaemonSocketPath,
type DaemonSocketIdentity
} from './daemon-endpoint-ownership'
import {
CLEAN_DISCONNECT_PROTOCOL_VERSION,
PROTOCOL_VERSION,
NOTIFY_PREFIX,
SessionNotFoundError,
TerminalAttachCanceledError,
type HelloMessage,
type DaemonRequest,
type DaemonTransientFact
} from './types'
import {
supportsExactHeldProducerPause,
supportsPtyStreamBinding,
supportsTerminalSessionAuthority
} from './daemon-protocol-version'
import { isPtyStreamBindingNonce } from '../../shared/pty-stream-binding-protocol'
import { isPtyIncarnationId } from '../../shared/pty-incarnation'
import { isPtyHeldProducerPauseToken } from '../providers/pty-provider-contract'
import {
isAgentSessionExecutionClaim,
isAgentSessionSurfaceBinding
} from '../../shared/agent-session-host-authority'
import { TerminalHistorySeedTransferRegistry } from './terminal-history-seed-transfer-registry'
import {
parseTerminalSessionAuthorityPtyAccess,
type TerminalSessionAuthorityPtyAccess
} from '../../shared/terminal-session-authority-pty-access'
import type { TerminalSessionAuthorityPtyOwner } from '../session-authority/terminal-session-authority-pty-owner'
import type {
TerminalAuthorityPolicyConsumerConnection,
TerminalAuthorityPolicyConsumerResolver,
TerminalAuthorityPolicyConsumerSource
} from '../session-authority/terminal-session-authority-policy-consumers'
import type {
TerminalAuthorityAuthenticatedNamespacePreparation,
TerminalAuthorityAuthenticatedNamespaceSession
} from '../session-authority/terminal-session-authority-authenticated-consumers'
import type { TerminalAuthorityAuthenticatedConsumerTransport } from '../session-authority/terminal-session-authority-consumer-admission'
import { joinTerminalAuthorityRollbackFailure } from '../session-authority/terminal-session-authority-consumer-rollback-failure'
import { TerminalSessionAuthorityBoundaryAcceptances } from '../session-authority/terminal-session-authority-boundary-acceptance'
import {
parseTerminalAuthorityNamespaceBoundaryAcceptance,
parseTerminalAuthorityNamespaceOutcomeAck,
sameTerminalAuthorityPolicyConsumer,
type TerminalAuthorityNamespaceOutcomeBoundary,
type TerminalAuthorityNamespaceOutcomePublication,
type TerminalAuthorityPolicyConsumerIdentity
} from '../../shared/terminal-session-authority-consumer-transport'
import {
parseDaemonHelloCapabilities,
sameDaemonHelloCapabilities,
TERMINAL_SESSION_AUTHORITY_HELLO_CAPABILITY,
type DaemonHelloCapabilities,
type DaemonTerminalAuthorityConsumerProofGrant
} from './daemon-hello-protocol'
import {
TERMINAL_AUTHORITY_CONSUMER_PROOF_CAPABILITY,
TERMINAL_AUTHORITY_CONSUMER_PROOF_VERSION,
parseTerminalAuthorityNamespaceAdmissionCancellation,
parseTerminalAuthorityNamespaceAdmissionProof
} from '../../shared/terminal-session-authority-consumer-proof'
import {
TERMINAL_AUTHORITY_CONSUMER_RETIREMENT_VERSION,
parseTerminalAuthorityConsumerRetirementProof,
parseTerminalAuthorityConsumerRetirementStart
} from '../../shared/terminal-session-authority-consumer-retirement'
import {
DAEMON_TERMINAL_AUTHORITY_CONSUMER_CHALLENGE_REQUEST,
DAEMON_TERMINAL_AUTHORITY_CONSUMER_CANCEL_REQUEST,
DAEMON_TERMINAL_AUTHORITY_CONSUMER_GRANT_REQUEST,
DAEMON_TERMINAL_AUTHORITY_CONSUMER_RETIREMENT_CHALLENGE_REQUEST,
DAEMON_TERMINAL_AUTHORITY_CONSUMER_RETIREMENT_REQUEST,
DAEMON_TERMINAL_AUTHORITY_CONSUMER_RESOLVE_NAMESPACE_REQUEST
} from './daemon-terminal-authority-consumer-requests'
import { terminalAuthorityHostAppConsumerId } from '../session-authority/terminal-session-authority-consumer-proof'
export type TerminalSessionAuthorityCapabilityReadiness = Readonly<{
hostEffectConsumerInstalled(): boolean
}>
export type DaemonServerOptions = {
socketPath: string
tokenPath: string
pidPath?: string
launchNonce?: string
startedAtMs?: number
publishEndpointOwnership?: () => void
/** Reported in the hello so a repaired PID record can carry the real owner's metadata. */
entryPath?: string
appVersion?: string
spawnerExecPath?: string
/** Direct-construction seam for protocol fixture tests; production never overrides it. */
protocolVersion?: number
onIdleShutdown?: () => void
onRpcShutdown?: () => void
/** Direct-construction-only controls; production uses the compiled initial-adoption timeout. */
initialAdoptionTestConfig?: {
timeoutMs: number
clock: {
setTimeout(callback: () => void, delayMs: number): unknown
clearTimeout(handle: unknown): void
now(): number
}
}
ptySpawnHealthCheck?: () => Promise<void>
preparePtySpawn?: () => Promise<void>
// Why: login-session death detection (#7936) probes on PTY-exit bursts and fresh app connections.
onPtySessionExit?: (sessionId: string) => void
onAuthenticatedClientPair?: () => void
log?: DaemonFileLog
spawnSubprocess: (opts: {
sessionId: string
cols: number
rows: number
cwd?: string
env?: Record<string, string>
command?: string
shellOverride?: string
}) => SubprocessHandle
terminalSessionAuthority?: TerminalHostOptions['terminalSessionAuthority']
terminalSessionAuthorityCapabilityReadiness?: TerminalSessionAuthorityCapabilityReadiness
onTerminalSessionAuthorityFailure?: TerminalHostOptions['onTerminalSessionAuthorityFailure']
}
function isDaemonConsumerProofGrant(
capability: DaemonHelloCapabilities['terminalAuthorityConsumerProof']
): capability is DaemonTerminalAuthorityConsumerProofGrant {
return Boolean(capability && 'version' in capability)
}
function authorityNamespaceKey(namespace: TerminalSessionAuthorityPtyAccess['namespace']): string {
return JSON.stringify([namespace.authorityHostId, namespace.namespaceId])
}
function sameAuthorityNamespace(
left: TerminalSessionAuthorityPtyAccess['namespace'],
right: TerminalSessionAuthorityPtyAccess['namespace']
): boolean {
return left.authorityHostId === right.authorityHostId && left.namespaceId === right.namespaceId
}
type ConnectedClient = {
clientId: string
controlSocket: Socket
streamSocket: Socket | null
authenticatedPairEstablished: boolean
capabilities?: DaemonHelloCapabilities
authorityConsumerTransport?: TerminalAuthorityAuthenticatedConsumerTransport
authorityNamespaceSessions?: Map<string, AuthenticatedAuthorityNamespaceSession>
authorityPendingPreparations?: Set<AuthenticatedAuthorityNamespacePreparation>
}
type AuthenticatedAuthorityNamespaceSession = Readonly<{
session: TerminalAuthorityAuthenticatedNamespaceSession
acceptances: TerminalSessionAuthorityBoundaryAcceptances
}>
type AuthenticatedAuthorityNamespacePreparation = {
active: boolean
preparation: TerminalAuthorityAuthenticatedNamespacePreparation | null
acceptances: TerminalSessionAuthorityBoundaryAcceptances
requestId: string
namespace: TerminalSessionAuthorityPtyAccess['namespace']
connectionGrantId: string
consumer: TerminalAuthorityPolicyConsumerIdentity
}
type PendingPtySpawnPreparation = {
canceled: boolean
// Why: preparations are keyed by sessionId, but a control-socket close must
// cancel only the disconnecting client's preps, not another client's (F4).
clientId: string
}
type PendingShutdownReply = {
start: () => void
}
type HeldProducerPauseLease = {
sessionId: string
incarnationId: string
token: string
}
type HeldProducerPausePayload =
| { mode: 'legacy' }
| { mode: 'invalid' }
| ({ mode: 'exact' } & HeldProducerPauseLease)
function parseHeldProducerPausePayload(payload: {
sessionId: string
incarnationId?: string
heldPauseToken?: string
}): HeldProducerPausePayload {
if (payload.incarnationId === undefined && payload.heldPauseToken === undefined) {
return { mode: 'legacy' }
}
if (
!isPtyIncarnationId(payload.incarnationId) ||
!isPtyHeldProducerPauseToken(payload.heldPauseToken)
) {
return { mode: 'invalid' }
}
return {
mode: 'exact',
sessionId: payload.sessionId,
incarnationId: payload.incarnationId,
token: payload.heldPauseToken
}
}
function heldProducerPauseLeaseKey(lease: HeldProducerPauseLease): string {
return JSON.stringify([lease.sessionId, lease.incarnationId, lease.token])
}
function hasTerminalSessionAuthorityCreateMetadata(payload: {
terminalSessionAuthorityVersion?: unknown
terminalSessionAuthorityOperationId?: unknown
terminalSessionAuthorityAccess?: unknown
worktreeId?: unknown
paneKey?: unknown
paneGeneration?: unknown
}): boolean {
return (
payload.terminalSessionAuthorityVersion !== undefined ||
payload.terminalSessionAuthorityOperationId !== undefined ||
payload.terminalSessionAuthorityAccess !== undefined ||
payload.worktreeId !== undefined ||
payload.paneKey !== undefined ||
payload.paneGeneration !== undefined
)
}
export class DaemonServer {
// Why: survive long enough to adopt a first client pair, but don't orphan forever if the parent crashes first.
private static readonly INITIAL_ADOPTION_TIMEOUT_MS = 2 * 60 * 1000
private static readonly SHUTDOWN_REPLY_FLUSH_TIMEOUT_MS = 1_000
private static readonly ENDPOINT_OWNERSHIP_POLL_MS = 30 * 1000
private static readonly ENDPOINT_OWNERSHIP_LOSS_CONFIRMATIONS = 2
private server: Server | null = null
private token: string
private host: TerminalHost
private socketPath: string
private tokenPath: string
private pidPath: string | null
private launchNonce: string | null
private startedAtMs: number | null
private publishEndpointOwnership: () => void
private entryPath: string | null
private appVersion: string | null
private spawnerExecPath: string | null
private ownedSocketIdentity: DaemonSocketIdentity | null = null
private endpointOwnershipTimer: ReturnType<typeof setInterval> | null = null
private endpointOwnershipLossStreak = 0
private protocolVersion: number
private onIdleShutdown: () => void
private onRpcShutdown: () => void
private onAuthenticatedClientPair: () => void
private ptySpawnHealthCheck: () => Promise<void>
private preparePtySpawn: () => Promise<void>
private log: DaemonFileLog
private transportSockets = new Set<Socket>()
private createOrAttachInFlight = 0
private idleShutdownState: 'running' | 'idle-shutdown-pending' | 'shutting-down' = 'running'
private initialAdoptionTimer: unknown | null = null
private initialAdoptionDeadlineMs: number | null = null
private retirementRequested = false
private shutdownPromise: Promise<void> | null = null
private ordinaryShutdownServerClose: Promise<void> | null = null
private pendingShutdownReplies = new Map<string, PendingShutdownReply>()
private initialAdoptionTimeoutMs: number
private lifecycleClock: NonNullable<DaemonServerOptions['initialAdoptionTestConfig']>['clock']
private clients = new Map<string, ConnectedClient>()
private streamDataBatcher = new DaemonStreamDataBatcher(
(clientId) => this.clients.get(clientId),
{
isSessionDroppable: (sessionId) =>
BACKGROUND_STREAM_DROP_ENABLED && this.transientFactRelay.isBackgrounded(sessionId),
salvageDroppedData: (dropped) => {
if (!dropped.includes('\x1b')) {
return ''
}
const extracted = extractHiddenStartupRendererQueryData(dropped, '')
return (
extracted.statelessQueryData + extracted.statefulQueryData + extracted.oscColorQueryData
)
}
}
)
private transientFactRelay = new BackgroundTransientFactRelay((sessionId, fact) => {
this.enqueueLegacyTransientFact(sessionId, fact)
})
private streamClientIdBySessionId = new Map<string, string>()
private lastInputAtBySessionId = new Map<string, number>()
private pendingPtySpawnPreparations = new Map<string, Set<PendingPtySpawnPreparation>>()
private pendingAuthoritySemanticOutcomes = new Set<Promise<boolean>>()
private heldProducerPauseLeasesByClient = new Map<string, Map<string, HeldProducerPauseLease>>()
private historySeedTransfers = new TerminalHistorySeedTransferRegistry()
private stopStreamBacklogProbe: () => void = () => {}
private readonly terminalSessionAuthorityEnabled: boolean
private readonly terminalSessionAuthorityPtyOwner: TerminalSessionAuthorityPtyOwner | null
private readonly terminalSessionAuthorityHostId: string | null
private readonly removeTerminalSessionAuthorityHostEffectApplier: () => void
private readonly terminalSessionAuthorityCapabilityReadiness:
| TerminalSessionAuthorityCapabilityReadiness
| undefined
private readonly onTerminalSessionAuthorityFailure:
| NonNullable<DaemonServerOptions['onTerminalSessionAuthorityFailure']>
| undefined
// Why: bypass batching within this window so keystroke echo/redraws skip the daemon's fixed batch delay.
private static readonly INTERACTIVE_OUTPUT_WINDOW_MS = 100
private static readonly INTERACTIVE_OUTPUT_MAX_CHARS = 1024
constructor(opts: DaemonServerOptions) {
this.socketPath = opts.socketPath
this.tokenPath = opts.tokenPath
this.pidPath = opts.pidPath ?? null
this.protocolVersion = opts.protocolVersion ?? PROTOCOL_VERSION
this.launchNonce =
opts.launchNonce ??
(this.protocolVersion >= CLEAN_DISCONNECT_PROTOCOL_VERSION ? randomUUID() : null)
this.startedAtMs =
opts.startedAtMs ??
(this.protocolVersion >= CLEAN_DISCONNECT_PROTOCOL_VERSION
? Date.now() - process.uptime() * 1000
: null)
this.publishEndpointOwnership = opts.publishEndpointOwnership ?? (() => {})
this.entryPath = opts.entryPath ?? null
this.appVersion = opts.appVersion ?? null
this.spawnerExecPath = opts.spawnerExecPath ?? null
this.onIdleShutdown = opts.onIdleShutdown ?? (() => {})
this.onRpcShutdown = opts.onRpcShutdown ?? (() => {})
this.initialAdoptionTimeoutMs =
opts.initialAdoptionTestConfig?.timeoutMs ?? DaemonServer.INITIAL_ADOPTION_TIMEOUT_MS
this.lifecycleClock = opts.initialAdoptionTestConfig?.clock ?? {
setTimeout: (callback, delayMs) => {
const timer = setTimeout(callback, delayMs)
timer.unref()
return timer
},
clearTimeout: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),
now: () => Date.now()
}
this.token = randomUUID()
this.terminalSessionAuthorityEnabled = opts.terminalSessionAuthority !== undefined
this.terminalSessionAuthorityPtyOwner = opts.terminalSessionAuthority?.ptyOwner ?? null
this.terminalSessionAuthorityHostId = opts.terminalSessionAuthority?.authorityHostId ?? null
this.terminalSessionAuthorityCapabilityReadiness =
opts.terminalSessionAuthorityCapabilityReadiness
this.onTerminalSessionAuthorityFailure = opts.onTerminalSessionAuthorityFailure
this.onAuthenticatedClientPair = opts.onAuthenticatedClientPair ?? (() => {})
this.host = new TerminalHost({
spawnSubprocess: opts.spawnSubprocess,
...(opts.onPtySessionExit ? { onSessionReaped: opts.onPtySessionExit } : {}),
...(opts.terminalSessionAuthority
? { terminalSessionAuthority: opts.terminalSessionAuthority }
: {}),
...(opts.onTerminalSessionAuthorityFailure
? { onTerminalSessionAuthorityFailure: opts.onTerminalSessionAuthorityFailure }
: {})
})
const installHostEffectApplier = this.terminalSessionAuthorityPtyOwner?.installHostEffectApplier
this.removeTerminalSessionAuthorityHostEffectApplier = installHostEffectApplier
? installHostEffectApplier.call(this.terminalSessionAuthorityPtyOwner, {
ensureBindingRetired: (access, reason) =>
this.host.ensureAuthorityBindingRetired(access, reason)
})
: () => {}
this.ptySpawnHealthCheck = opts.ptySpawnHealthCheck ?? checkPtySpawnHealth
this.preparePtySpawn = opts.preparePtySpawn ?? (() => Promise.resolve())
this.stopStreamBacklogProbe = startDaemonStreamBacklogProbe(() => ({
clients: Array.from(this.clients.values(), (client) => ({
clientId: client.clientId,
socketBufferedBytes: client.streamSocket?.writableLength ?? 0,
batcherQueuedChars: this.streamDataBatcher.queuedCharsForClient(client.clientId)
})),
backgroundedSessionIdSuffixes: this.transientFactRelay.backgroundedSessionIdSuffixes()
}))
this.log = opts.log ?? createNoopDaemonFileLog()
}
async start(): Promise<void> {
return new Promise((resolve, reject) => {
this.server = createServer((socket) => this.handleConnection(socket))
const onListenError = (err: Error): void => {
reject(err)
}
this.server.once('error', onListenError)
// Why: bind a private name and hard-link it into place, so libuv's close-time unlink
// can only ever remove our own bind name — never a replacement daemon's endpoint.
const bindPath =
process.platform === 'win32' ? this.socketPath : getDaemonSocketBindPath(this.socketPath)
this.server.listen(bindPath, () => {
// Why: drop the startup error listener after bind so it doesn't retain this closure.
this.server?.off('error', onListenError)
try {
// Why: tighten the mode on the private bind name so the endpoint is never reachable
// at the canonical path with default permissions, even briefly.
chmodSync(bindPath, 0o600)
} catch {
// Best-effort on platforms that support it
}
let publishedOwnership = false
try {
// Why: the exclusive link is the endpoint claim, and the PID/nonce record must
// exist before the token makes this listener adoptable.
this.ownedSocketIdentity = publishDaemonSocketPath(bindPath, this.socketPath)
this.publishEndpointOwnership()
publishedOwnership = true
writeFileSync(this.tokenPath, this.token, { mode: 0o600 })
} catch (error) {
// Why: roll back only a record we actually wrote. Losing the endpoint claim means
// the record at that path belongs to the incumbent daemon, and even the ownership-
// checked unlink briefly renames it aside — enough to strand a live daemon's record.
if (publishedOwnership && this.pidPath && this.launchNonce) {
unlinkOwnedDaemonPidFile(this.pidPath, process.pid, this.launchNonce)
}
unlinkOwnedDaemonSocketPath(this.socketPath, this.ownedSocketIdentity)
this.ownedSocketIdentity = null
const server = this.server
this.server = null
// Why: settle before close — an already-accepted connection defers the close
// callback indefinitely, and start() has no timeout of its own.
reject(error)
server?.close()
if (process.platform !== 'win32') {
try {
unlinkSync(bindPath)
} catch {
// Already consumed by a successful link, or never created.
}
}
return
}
if (this.protocolVersion >= CLEAN_DISCONNECT_PROTOCOL_VERSION) {
// Why: a parent crash before the first full client pair must not leave an empty daemon alive forever.
this.armInitialAdoptionTimeout()
}
this.startEndpointOwnershipWatch()
resolve()
})
})
}
async shutdown(): Promise<void> {
if (this.shutdownPromise) {
return this.shutdownPromise
}
const serverClose = this.beginOrdinaryShutdownFence()
this.shutdownPromise = this.finishOrdinaryShutdown(serverClose)
return this.shutdownPromise
}
private beginOrdinaryShutdownFence(): Promise<void> {
this.idleShutdownState = 'shutting-down'
this.cancelInitialAdoptionTimer()
this.ordinaryShutdownServerClose ??= this.beginServerClose()
return this.ordinaryShutdownServerClose
}
private async finishOrdinaryShutdown(serverClose: Promise<void>): Promise<void> {
this.unlinkOwnedEndpointArtifacts()
await this.disposeDaemonResources()
await serverClose
}
private async finishRpcShutdown(serverClose: Promise<void>): Promise<void> {
await this.finishOrdinaryShutdown(serverClose)
this.onRpcShutdown()
}
private unlinkOwnedEndpointArtifacts(): void {
// Why: ownership checks prevent removing a late replacement's token, PID record or endpoint.
unlinkOwnedDaemonTokenFile(this.tokenPath, this.token)
if (this.pidPath && this.launchNonce) {
unlinkOwnedDaemonPidFile(this.pidPath, process.pid, this.launchNonce)
}
// Why: we bound a private name, so libuv unlinks nothing at the canonical path — this
// is the only removal of our endpoint, and it is skipped once someone else owns it.
unlinkOwnedDaemonSocketPath(this.socketPath, this.ownedSocketIdentity)
this.ownedSocketIdentity = null
}
private startEndpointOwnershipWatch(): void {
if (process.platform === 'win32' || !this.ownedSocketIdentity) {
return
}
this.endpointOwnershipTimer = setInterval(
() => this.checkEndpointOwnership(),
DaemonServer.ENDPOINT_OWNERSHIP_POLL_MS
)
// Why: a liveness poll must never be the reason the process cannot exit.
this.endpointOwnershipTimer.unref()
}
private stopEndpointOwnershipWatch(): void {
if (this.endpointOwnershipTimer === null) {
return
}
clearInterval(this.endpointOwnershipTimer)
this.endpointOwnershipTimer = null
}
/**
* Retires a daemon that no longer owns the canonical endpoint.
*
* Why: a daemon whose endpoint name was taken over keeps hosting PTYs that no client can
* reach through the socket, which reads to the user as terminals that acknowledge input and
* never run it. Retirement drains rather than kills: live sessions finish, and the process
* exits once idle instead of surviving as an unreachable orphan.
*/
private checkEndpointOwnership(): void {
if (process.platform === 'win32' || !this.ownedSocketIdentity || this.shutdownPromise) {
return
}
const state = readDaemonEndpointOwnershipState(this.socketPath, this.ownedSocketIdentity)
if (state === 'owned') {
this.endpointOwnershipLossStreak = 0
return
}
if (state === 'indeterminate') {
// Why: an inconclusive stat proves nothing. Retiring on EACCES or EIO would take down a
// daemon that is still serving every terminal on the machine. Reset the streak too, so
// the confirmations we act on are consecutive rather than merely cumulative.
this.endpointOwnershipLossStreak = 0
return
}
this.endpointOwnershipLossStreak++
// Why: a replacement publishes by unlink-then-link, so a single observation can land in
// that gap. Require the loss to persist before acting on it.
if (this.endpointOwnershipLossStreak < DaemonServer.ENDPOINT_OWNERSHIP_LOSS_CONFIRMATIONS) {
return
}
if (this.retirementRequested) {
return
}
this.log.log('endpoint-ownership-lost', { socketPath: this.socketPath })
console.warn(
'[daemon] Endpoint ownership lost to another daemon — retiring once existing sessions end'
)
this.ownedSocketIdentity = null
this.retirementRequested = true
this.reevaluateIdleShutdown()
}
private async disposeDaemonResources(): Promise<void> {
this.stopEndpointOwnershipWatch()
this.stopStreamBacklogProbe()
this.transientFactRelay.dispose()
this.cancelAllPendingPtySpawnPreparations()
await Promise.allSettled(this.pendingAuthoritySemanticOutcomes)
let hostDisposeError: unknown
try {
await this.host.dispose()
} catch (err) {
// Why: an unreapable child must not block daemon exit — post-exit it reparents to init anyway.
this.log.log('shutdown-dispose-failed', {
error: err instanceof Error ? err.message : String(err)
})
hostDisposeError = err
}
this.removeTerminalSessionAuthorityHostEffectApplier()
this.streamDataBatcher.clear()
this.historySeedTransfers.dispose()
this.pendingShutdownReplies.clear()
this.heldProducerPauseLeasesByClient.clear()
this.pendingAuthoritySemanticOutcomes.clear()
for (const [, client] of this.clients) {
this.releaseAuthenticatedPolicyTransport(client)
client.controlSocket.destroy()
client.streamSocket?.destroy()
}
this.clients.clear()
for (const socket of this.transportSockets) {
socket.destroy()
}
this.transportSockets.clear()
if (hostDisposeError && this.terminalSessionAuthorityEnabled) {
throw hostDisposeError
}
}
private beginServerClose(): Promise<void> {
const server = this.server
this.server = null
if (!server) {
return Promise.resolve()
}
return new Promise<void>((resolve) => {
// Why: close synchronously before any awaited cleanup so no new transport enters after the empty proof.
server.close(() => {
// Why: libuv unlinks the path this server bound, which is our private bind name and
// is already gone. The canonical endpoint is removed by unlinkOwnedEndpointArtifacts,
// under an ownership check, so closing late cannot delete a replacement's endpoint.
resolve()
})
})
}
private isIdle(): boolean {
return (
this.transportSockets.size === 0 &&
this.clients.size === 0 &&
this.createOrAttachInFlight === 0 &&
this.host.listSessions().length === 0
)
}
private reevaluateIdleShutdown(): void {
if (this.idleShutdownState !== 'running') {
return
}
if (this.retirementRequested) {
this.cancelInitialAdoptionTimer()
if (this.isIdle()) {
this.beginIdleShutdown()
}
return
}
if (!this.isIdle() || this.initialAdoptionDeadlineMs === null) {
this.cancelInitialAdoptionTimer()
return
}
if (this.initialAdoptionTimer !== null) {
return
}
const remainingMs = Math.max(0, this.initialAdoptionDeadlineMs - this.lifecycleClock.now())
if (remainingMs === 0) {
this.initialAdoptionDeadlineMs = null
this.retirementRequested = true
this.beginIdleShutdown()
return
}
this.initialAdoptionTimer = this.lifecycleClock.setTimeout(() => {
this.initialAdoptionTimer = null
this.initialAdoptionDeadlineMs = null
this.retirementRequested = true
this.beginIdleShutdown()
}, remainingMs)
}
private armInitialAdoptionTimeout(): void {
this.initialAdoptionDeadlineMs = this.lifecycleClock.now() + this.initialAdoptionTimeoutMs
this.reevaluateIdleShutdown()
}
private cancelInitialAdoptionTimer(): void {
if (this.initialAdoptionTimer === null) {
return
}
this.lifecycleClock.clearTimeout(this.initialAdoptionTimer)
this.initialAdoptionTimer = null
}
private beginIdleShutdown(): void {
this.initialAdoptionTimer = null
if (this.idleShutdownState !== 'running') {
return
}
this.idleShutdownState = 'idle-shutdown-pending'
if (!this.isIdle()) {
// Why: work admitted before the fence wins; clear pending state to keep it usable.
this.idleShutdownState = 'running'
this.reevaluateIdleShutdown()
return
}
this.idleShutdownState = 'shutting-down'
// beginServerClose() runs synchronously up to server.close() before any yield to a racing connection.
const serverClose = this.beginServerClose()
this.shutdownPromise = this.finishIdleShutdown(serverClose)
}
private async finishIdleShutdown(serverClose: Promise<void>): Promise<void> {
this.unlinkOwnedEndpointArtifacts()
await this.disposeDaemonResources()
await serverClose
this.onIdleShutdown()
}
private handleConnection(socket: Socket): void {
this.cancelInitialAdoptionTimer()
this.transportSockets.add(socket)
const removeTransport = (): void => {
this.transportSockets.delete(socket)
this.reevaluateIdleShutdown()
}
socket.once('close', removeTransport)
socket.on('error', () => socket.destroy())
if (this.idleShutdownState !== 'running') {
// Why: a connection accepted just before close() gets an explicit retry signal instead of dying mid-auth.
socket.end(
encodeNdjson({
type: 'hello',
ok: false,
error: 'Daemon temporarily unavailable; reconnect',
retryable: true
})
)
return
}
// Why: keep UTF-8 sequences intact across socket chunks before NDJSON parsing.
const decoder = new StringDecoder('utf8')
const parser = createNdjsonParser(
(msg) => {
void this.handleFirstMessage(socket, msg, parser).catch((error) => {
this.log.log('client-hello-rejected', {
reason: error instanceof Error ? error.message : String(error)
})
socket.destroy()
})
},
() => {
socket.destroy()
}
)
socket.on('data', (chunk) => parser.feed(decoder.write(chunk)))
}
private async handleFirstMessage(
socket: Socket,
msg: unknown,
_parser: ReturnType<typeof createNdjsonParser>
): Promise<void> {
const hello = msg as HelloMessage
if (hello.type !== 'hello') {
this.log.log('client-hello-rejected', { reason: 'expected-hello' })
socket.write(encodeNdjson({ type: 'hello', ok: false, error: 'Expected hello' }))
socket.destroy()
return
}
if (hello.version !== this.protocolVersion) {
this.log.log('client-hello-rejected', {
reason: 'protocol-mismatch',
clientVersion: hello.version
})
socket.write(
encodeNdjson({
type: 'hello',
ok: false,
error: 'Protocol version mismatch'
})
)
socket.destroy()
return
}
if (hello.token !== this.token) {
this.log.log('client-hello-rejected', {
reason: 'invalid-token',
role: hello.role
})
socket.write(encodeNdjson({ type: 'hello', ok: false, error: 'Invalid token' }))
socket.destroy()
return
}
const capabilities = this.negotiateHelloCapabilities(hello.capabilities)
const existingControl = hello.role === 'stream' ? this.clients.get(hello.clientId) : undefined
const capabilitiesMatchControl =
existingControl === undefined ||
sameDaemonHelloCapabilities(existingControl.capabilities ?? {}, capabilities)
this.log.log('client-hello-accepted', {
role: hello.role,
clientId: hello.clientId
})
const response = encodeNdjson({
type: 'hello',
ok: true,
...(capabilities.terminalSessionAuthority !== undefined ||
capabilities.terminalAuthorityConsumerProof !== undefined
? { capabilities }
: {}),
...(this.launchNonce && this.startedAtMs
? {
daemonIdentity: {
pid: process.pid,
startedAtMs: this.startedAtMs,
launchNonce: this.launchNonce,
...(this.entryPath ? { entryPath: this.entryPath } : {}),
...(this.appVersion ? { appVersion: this.appVersion } : {}),
...(this.spawnerExecPath ? { spawnerExecPath: this.spawnerExecPath } : {})
}
}
: {})
})
if (hello.role === 'stream' && (!existingControl || !capabilitiesMatchControl)) {
socket.end(response)
return
}
if (hello.role === 'control') {
socket.write(response)
const previous = this.clients.get(hello.clientId)
if (previous) {
this.releaseHeldProducerPausesForClient(hello.clientId)
}
const client: ConnectedClient = {
clientId: hello.clientId,
controlSocket: socket,
streamSocket: null,
authenticatedPairEstablished: false,
capabilities
}
this.clients.set(hello.clientId, client)
this.setupControlSocket(socket, hello.clientId)
if (previous) {
// Why: reconnect reuses clientId before stale close fires; cancel the old owner's preflight at handoff.
this.cancelPendingPtySpawnPreparationsForClient(hello.clientId)
this.historySeedTransfers.clearOwner(hello.clientId)
this.recordFullyAuthenticatedDisconnect(previous.authenticatedPairEstablished)
this.releaseAuthenticatedPolicyTransport(previous)
// Why: tear down the old sockets after installing the new owner so a stale close can't delete the replacement.
previous.streamSocket?.destroy()
previous.controlSocket.destroy()
}
} else if (hello.role === 'stream') {
const client = this.clients.get(hello.clientId)
if (!client) {
// Why: a stream socket is meaningless without its control socket; drop the orphan.
socket.destroy()
return
}
this.setupStreamSocket(socket, client)
client.authenticatedPairEstablished = true
if (isDaemonConsumerProofGrant(client.capabilities?.terminalAuthorityConsumerProof)) {
this.installAuthenticatedPolicyTransport(client)
}
socket.write(response)
// Why: one-shot health probes authenticate only a control socket; they are not fresh app activity.
this.onAuthenticatedClientPair()
// A complete app connection (unlike a probe) re-owns the endpoint and cancels pending retirement.
this.initialAdoptionDeadlineMs = null
this.retirementRequested = false
this.cancelInitialAdoptionTimer()
}
}
private negotiateHelloCapabilities(requested: unknown): DaemonHelloCapabilities {
const parsed = parseDaemonHelloCapabilities(requested)
if (
parsed.terminalSessionAuthority !== TERMINAL_SESSION_AUTHORITY_HELLO_CAPABILITY ||
!this.terminalSessionAuthorityAdmissionReady()
) {
return {}
}
const proof = parsed.terminalAuthorityConsumerProof
if (proof) {
if (
'versions' in proof &&
proof.versions.includes(TERMINAL_AUTHORITY_CONSUMER_PROOF_VERSION) &&
this.terminalSessionAuthorityHostId
) {
return {
terminalSessionAuthority: TERMINAL_SESSION_AUTHORITY_HELLO_CAPABILITY,
terminalAuthorityConsumerProof: {
version: TERMINAL_AUTHORITY_CONSUMER_PROOF_VERSION,
authorityHostId: this.terminalSessionAuthorityHostId,
...(proof.retirementVersions?.includes(TERMINAL_AUTHORITY_CONSUMER_RETIREMENT_VERSION)
? { retirementVersion: TERMINAL_AUTHORITY_CONSUMER_RETIREMENT_VERSION }
: {})
}
}
}
return {}
}
// Why a proofless peer negotiates nothing: it would otherwise name its own consumer identity, and
// a client-chosen label must never reach a durable authority claim. It stays on the legacy path.
return {}
}
private terminalSessionAuthorityAdmissionReady(): boolean {
if (
!supportsTerminalSessionAuthority(this.protocolVersion) ||
!this.terminalSessionAuthorityEnabled ||
!this.terminalSessionAuthorityCapabilityReadiness
) {
return false
}
try {
return this.terminalSessionAuthorityCapabilityReadiness.hostEffectConsumerInstalled()
} catch {
return false
}
}
private assertAuthorityPolicyAdmission(
client: ConnectedClient | undefined,
value: unknown
): TerminalAuthorityPolicyConsumerConnection {
const access = parseTerminalSessionAuthorityPtyAccess(value)
const authenticated = access
? client?.authorityNamespaceSessions?.get(authorityNamespaceKey(access.namespace))
: undefined
if (
access &&
authenticated &&
client?.authenticatedPairEstablished &&
client.streamSocket &&
authenticated.session.policyConsumer.isInstalled(access.namespace)
) {
authenticated.session.policyConsumer.assertInstalled(access.namespace)
return authenticated.session.policyConsumer
}
throw new Error('terminal session authority policy admission is unavailable')
}
private installAuthenticatedPolicyTransport(client: ConnectedClient): void {
this.releaseAuthenticatedPolicyTransport(client)
const token = Object.freeze({})
client.authorityConsumerTransport = Object.freeze({
connectionGrantId: randomUUID(),
principal: `daemon-token:v1:${createHash('sha256').update(this.token).digest('base64url')}`,
capability: TERMINAL_AUTHORITY_CONSUMER_PROOF_CAPABILITY,
token
})
client.authorityNamespaceSessions = new Map()
client.authorityPendingPreparations = new Set()
}
private releaseAuthenticatedPolicyTransport(client: ConnectedClient): void {
const transport = client.authorityConsumerTransport
client.authorityConsumerTransport = undefined
for (const pending of client.authorityPendingPreparations ?? []) {
pending.active = false
pending.acceptances.close()
const preparation = pending.preparation
if (preparation) {
const cause = new Error('terminal authority consumer transport released during admission')
void joinTerminalAuthorityRollbackFailure(cause, () => preparation.rollback()).catch(
(failure) => {
if (failure !== cause) {
this.reportTerminalSessionAuthorityFailure(failure)
}
}
)
}
}
client.authorityPendingPreparations?.clear()
for (const installed of client.authorityNamespaceSessions?.values() ?? []) {
installed.acceptances.close()
installed.session.disconnect()
}
client.authorityNamespaceSessions?.clear()
client.authorityPendingPreparations = undefined
client.authorityNamespaceSessions = undefined
if (transport) {
this.terminalSessionAuthorityPtyOwner?.releaseAuthenticatedPolicyConsumerTransport(
transport.token
)
}
}
private reportTerminalSessionAuthorityFailure(error: unknown): void {
const failure = error instanceof Error ? error : new Error(String(error))
const causes =
failure instanceof AggregateError
? failure.errors.map((cause) => (cause instanceof Error ? cause.message : String(cause)))
: undefined
this.log.log('terminal-session-authority-rollback-failed', {
error: failure.message,
...(causes ? { causes } : {})
})
this.onTerminalSessionAuthorityFailure?.(failure)
}
private requireAuthenticatedConsumerTransport(
client: ConnectedClient | undefined
): TerminalAuthorityAuthenticatedConsumerTransport {
const capability = client?.capabilities?.terminalAuthorityConsumerProof
const transport = client?.authorityConsumerTransport
if (
!client?.authenticatedPairEstablished ||
!client.streamSocket ||
!isDaemonConsumerProofGrant(capability) ||
!transport
) {
throw new Error('terminal authority consumer proof transport is unavailable')
}
return transport
}
private requireAuthenticatedConsumerRetirementTransport(
client: ConnectedClient | undefined
): TerminalAuthorityAuthenticatedConsumerTransport {
const capability = client?.capabilities?.terminalAuthorityConsumerProof
if (
!isDaemonConsumerProofGrant(capability) ||
capability.retirementVersion !== TERMINAL_AUTHORITY_CONSUMER_RETIREMENT_VERSION
) {
throw new Error('terminal authority consumer retirement is unsupported')
}
return this.requireAuthenticatedConsumerTransport(client)
}
private authenticatedPolicyConsumerResolver(
client: ConnectedClient
): TerminalAuthorityPolicyConsumerResolver {
const transport = this.requireAuthenticatedConsumerTransport(client)
return Object.freeze({
forNamespace: (namespace: TerminalSessionAuthorityPtyAccess['namespace']) => {
if (
this.clients.get(client.clientId) !== client ||
client.authorityConsumerTransport?.token !== transport.token
) {
throw new Error('terminal authority consumer proof transport is stale')
}
const installed = client.authorityNamespaceSessions?.get(authorityNamespaceKey(namespace))
if (!installed) {
throw new Error('terminal authority namespace consumer is not installed')
}
installed.session.policyConsumer.assertInstalled(namespace)
return installed.session.policyConsumer
}
})
}
private publishAuthenticatedAuthorityEvent(
client: ConnectedClient,
namespaceId: string,
event: 'terminalAuthorityNamespaceOutcomeBoundary' | 'terminalAuthorityNamespaceOutcome',
payload:
| TerminalAuthorityNamespaceOutcomeBoundary
| TerminalAuthorityNamespaceOutcomePublication
): Promise<void> {
this.requireAuthenticatedConsumerTransport(client)
const sessionId = `authority-outcomes:${namespaceId}`
return event === 'terminalAuthorityNamespaceOutcomeBoundary'
? this.streamDataBatcher.enqueueSettledControlEvent(client.clientId, sessionId, {
type: 'event',
event,
sessionId,
payload: payload as TerminalAuthorityNamespaceOutcomeBoundary
})
: this.streamDataBatcher.enqueueSettledControlEvent(client.clientId, sessionId, {
type: 'event',
event,
sessionId,
payload: payload as TerminalAuthorityNamespaceOutcomePublication
})
}
private failAuthenticatedAuthorityNamespace(
client: ConnectedClient,
namespace: TerminalSessionAuthorityPtyAccess['namespace'],
_error: Error
): void {
const key = authorityNamespaceKey(namespace)
const installed = client.authorityNamespaceSessions?.get(key)
if (!installed) {
return
}
client.authorityNamespaceSessions?.delete(key)
installed.acceptances.close()
installed.session.disconnect()
}
private setupControlSocket(socket: Socket, clientId: string): void {
// Why: decode as a UTF-8 stream so emoji/Unicode split across chunks isn't corrupted.
const decoder = new StringDecoder('utf8')
const parser = createNdjsonParser(
(msg) => this.handleRequest(socket, clientId, msg as DaemonRequest),
() => {} // Ignore parse errors
)
// Remove the initial data listener and replace with the RPC parser
socket.removeAllListeners('data')
socket.on('data', (chunk) => parser.feed(decoder.write(chunk)))
socket.on('close', () => {
const client = this.clients.get(clientId)
if (client?.controlSocket !== socket) {
return
}
// Why: a client that disconnects mid-preflight would otherwise still create
// its daemon PTY, orphaning a durable, unattached session — cancel its preps (F4).
this.cancelPendingPtySpawnPreparationsForClient(clientId)
this.historySeedTransfers.clearOwner(clientId)
this.releaseHeldProducerPausesForClient(clientId)
this.releaseAuthenticatedPolicyTransport(client)
const wasFullyAuthenticated = client.authenticatedPairEstablished
this.streamDataBatcher.clear(clientId)
client.streamSocket?.destroy()
this.clients.delete(clientId)
this.recordFullyAuthenticatedDisconnect(wasFullyAuthenticated)
this.reevaluateIdleShutdown()
})
}
private recordFullyAuthenticatedDisconnect(wasFullyAuthenticated: boolean): void {
if (
!wasFullyAuthenticated ||
[...this.clients.values()].some((remaining) => remaining.authenticatedPairEstablished) ||
this.idleShutdownState !== 'running'
) {
return
}
// Why: once the last full client is gone, incomplete transports may block retirement but never erase it.
this.retirementRequested = true
}
private setupStreamSocket(socket: Socket, client: ConnectedClient): void {
const previous = client.streamSocket
socket.removeAllListeners('data')
client.streamSocket = socket
// Why: 'drain' is the wake-up for the batcher's shallow-gate held bulk.
socket.on('drain', () => {
this.streamDataBatcher.flush(client.clientId)
})
const cleanup = (): void => {
socket.removeListener('close', cleanup)
socket.removeListener('error', cleanup)
if (this.clients.get(client.clientId) !== client || client.streamSocket !== socket) {
return
}
// Why: a preflight that outlives its output channel would create an unattached daemon PTY.
this.cancelPendingPtySpawnPreparationsForClient(client.clientId)
this.streamDataBatcher.clear(client.clientId)
this.releaseHeldProducerPausesForClient(client.clientId)
this.releaseAuthenticatedPolicyTransport(client)
client.streamSocket = null
}
socket.on('close', cleanup)
socket.on('error', cleanup)
if (previous && previous !== socket) {
// Why: replacing a stream socket must not leave the old channel alive and untracked.
previous.destroy()
}
}
private async handleRequest(
socket: Socket,
clientId: string,
request: DaemonRequest
): Promise<void> {
const isNotify = request.id.startsWith(NOTIFY_PREFIX)
try {
const result = await this.routeRequest(clientId, request)
if (!isNotify) {
const pendingShutdown = this.pendingShutdownReplies.get(
this.shutdownReplyKey(clientId, request.id)
)
socket.write(encodeNdjson({ id: request.id, ok: true, payload: result }), () => {
pendingShutdown?.start()
})
}
} catch (err) {
if (!isNotify) {
socket.write(
encodeNdjson({
id: request.id,
ok: false,
error: err instanceof Error ? err.message : String(err)
})
)
}
}
}
private shutdownReplyKey(clientId: string, requestId: string): string {
return `${clientId}\u0000${requestId}`
}
private deferShutdownUntilReply(
clientId: string,
requestId: string,
socket: Socket,
finish: () => Promise<void>
): void {
const key = this.shutdownReplyKey(clientId, requestId)
let started = false
let timer: ReturnType<typeof setTimeout>
const start = (): void => {
if (started) {
return
}
started = true
clearTimeout(timer)
socket.off('close', start)
socket.off('error', start)
this.pendingShutdownReplies.delete(key)
if (!this.shutdownPromise) {
this.shutdownPromise = finish()
}
}
// Why: a non-reading peer must not pin a fenced daemon by holding its ack behind permanent socket backpressure.
timer = setTimeout(start, DaemonServer.SHUTDOWN_REPLY_FLUSH_TIMEOUT_MS)
timer.unref()
socket.once('close', start)
socket.once('error', start)
this.pendingShutdownReplies.set(key, { start })
}
private async preparePtySpawnUnlessCanceled(sessionId: string, clientId: string): Promise<void> {
const preparation: PendingPtySpawnPreparation = {
canceled: false,
clientId
}
const pending = this.pendingPtySpawnPreparations.get(sessionId) ?? new Set()
pending.add(preparation)
this.pendingPtySpawnPreparations.set(sessionId, pending)
try {
// Why: register before the async probe so a concurrent close can cancel this creation before a subprocess exists.
await this.preparePtySpawn()
if (preparation.canceled) {
throw new TerminalAttachCanceledError(sessionId)
}
} finally {
pending.delete(preparation)
if (pending.size === 0) {
this.pendingPtySpawnPreparations.delete(sessionId)
}
}
}
private cancelPendingPtySpawnPreparations(sessionId: string): boolean {
const pending = this.pendingPtySpawnPreparations.get(sessionId)
if (!pending) {
return false
}
for (const preparation of pending) {
preparation.canceled = true
}
return true
}
private cancelAllPendingPtySpawnPreparations(): void {
for (const sessionId of this.pendingPtySpawnPreparations.keys()) {
this.cancelPendingPtySpawnPreparations(sessionId)
}
}
private cancelPendingPtySpawnPreparationsForClient(clientId: string): void {
for (const pending of this.pendingPtySpawnPreparations.values()) {
for (const preparation of pending) {
if (preparation.clientId === clientId) {
preparation.canceled = true
}
}
}
}
private rememberHeldProducerPause(clientId: string, lease: HeldProducerPauseLease): void {
const leases = this.heldProducerPauseLeasesByClient.get(clientId) ?? new Map()
leases.set(heldProducerPauseLeaseKey(lease), lease)
this.heldProducerPauseLeasesByClient.set(clientId, leases)
}
private forgetHeldProducerPause(clientId: string, lease: HeldProducerPauseLease): void {
const leases = this.heldProducerPauseLeasesByClient.get(clientId)
if (!leases) {
return
}
leases.delete(heldProducerPauseLeaseKey(lease))
if (leases.size === 0) {
this.heldProducerPauseLeasesByClient.delete(clientId)
}
}
private releaseHeldProducerPausesForClient(clientId: string): void {
const leases = this.heldProducerPauseLeasesByClient.get(clientId)
this.heldProducerPauseLeasesByClient.delete(clientId)
if (!leases) {
return
}
const releasedSessions = new Set<string>()
for (const lease of leases.values()) {
const sessionKey = JSON.stringify([lease.sessionId, lease.incarnationId])
if (releasedSessions.has(sessionKey)) {
continue
}
releasedSessions.add(sessionKey)
this.host.releaseExactHeldProducerPauses(lease.sessionId, lease.incarnationId, clientId)
}
}
private forgetHeldProducerPausesForSession(sessionId: string, incarnationId: string): void {
for (const [clientId, leases] of this.heldProducerPauseLeasesByClient) {
for (const [key, lease] of leases) {
if (lease.sessionId === sessionId && lease.incarnationId === incarnationId) {
leases.delete(key)
}
}
if (leases.size === 0) {
this.heldProducerPauseLeasesByClient.delete(clientId)
}
}
}
private enqueueLegacyTransientFact(
sessionId: string,
fact: DaemonTransientFact,
exactClientId?: string
): void {
const clientId = exactClientId ?? this.streamClientIdBySessionId.get(sessionId)
if (!clientId) {
return
}
this.streamDataBatcher.enqueueControlEvent(clientId, sessionId, {
type: 'event',
event: 'transientFact',
sessionId,
payload: fact
})
}
private recordAuthorityTransientFact(
sessionId: string,
access: TerminalSessionAuthorityPtyAccess,
fact: DaemonTransientFact
): void {
const recording = this.host.recordSemanticOutcomeExact(sessionId, access, fact)
this.pendingAuthoritySemanticOutcomes.add(recording)
void recording.then(
() => this.pendingAuthoritySemanticOutcomes.delete(recording),
(error) => {
this.pendingAuthoritySemanticOutcomes.delete(recording)
this.log.log('terminal-session-authority-semantic-outcome-failed', {
sessionId,
error: error instanceof Error ? error.message : String(error)
})
}
)
}
private async routeRequest(clientId: string, request: DaemonRequest): Promise<unknown> {
const client = this.clients.get(clientId)
switch (request.type) {
case DAEMON_TERMINAL_AUTHORITY_CONSUMER_RESOLVE_NAMESPACE_REQUEST: {
this.requireAuthenticatedConsumerTransport(client)
const owner = this.terminalSessionAuthorityPtyOwner
const worktreeId = request.payload.worktreeId
if (!owner || typeof worktreeId !== 'string') {
throw new Error('terminal authority consumer namespace resolution is unavailable')
}
return await owner.resolvePolicyConsumerNamespace(worktreeId)
}
case DAEMON_TERMINAL_AUTHORITY_CONSUMER_CHALLENGE_REQUEST: {
const transport = this.requireAuthenticatedConsumerTransport(client)
const owner = this.terminalSessionAuthorityPtyOwner
if (!owner) {
throw new Error('terminal authority consumer proof is unavailable')
}
return await owner.issuePolicyConsumerChallenge(request.payload, transport)
}
case DAEMON_TERMINAL_AUTHORITY_CONSUMER_GRANT_REQUEST: {
const transport = this.requireAuthenticatedConsumerTransport(client)
const owner = this.terminalSessionAuthorityPtyOwner
const proof = parseTerminalAuthorityNamespaceAdmissionProof(request.payload)
if (!owner || !proof || !client) {
throw new Error('terminal authority consumer proof is unavailable')
}
const namespace = proof.challenge.namespace
const acceptances = new TerminalSessionAuthorityBoundaryAcceptances()
const pending: AuthenticatedAuthorityNamespacePreparation = {
active: true,
preparation: null,
acceptances,
requestId: proof.challenge.requestId,
namespace: Object.freeze({ ...namespace }),
connectionGrantId: proof.challenge.connectionGrantId,
consumer: Object.freeze({
consumerId: terminalAuthorityHostAppConsumerId(
namespace.authorityHostId,
Uint8Array.from(Buffer.from(proof.challenge.appPublicKeyB64, 'base64'))
),
consumerIncarnationId: proof.challenge.candidateProcessIncarnationId
})
}
client.authorityPendingPreparations?.add(pending)
try {
pending.preparation = await owner.prepareAuthenticatedPolicyConsumerNamespace(
proof,
transport,
{
publishBoundary: async (boundary) => {
const accepted = acceptances.wait(boundary)
try {
await this.publishAuthenticatedAuthorityEvent(
client,
namespace.namespaceId,
'terminalAuthorityNamespaceOutcomeBoundary',
boundary
)
await accepted
} catch (error) {
acceptances.close(error instanceof Error ? error : new Error(String(error)))
throw error
}
},
publishOutcome: (publication) =>
this.publishAuthenticatedAuthorityEvent(
client,
namespace.namespaceId,
'terminalAuthorityNamespaceOutcome',
publication
),
onFailure: (error) =>
this.failAuthenticatedAuthorityNamespace(client, namespace, error)
}
)
} catch (error) {
pending.active = false
client.authorityPendingPreparations?.delete(pending)
acceptances.close()
throw error
}
if (
!pending.active ||
this.clients.get(clientId) !== client ||
client.authorityConsumerTransport?.token !== transport.token
) {
pending.active = false
client.authorityPendingPreparations?.delete(pending)
acceptances.close()
await pending.preparation.rollback()
throw new Error('terminal authority consumer proof transport is unavailable')
}
const preparation = pending.preparation
try {
const session = await preparation.commit()
if (
!pending.active ||
this.clients.get(clientId) !== client ||
client.authorityConsumerTransport?.token !== transport.token
) {
session.disconnect()
throw new Error('terminal authority consumer proof transport is unavailable')
}
const key = authorityNamespaceKey(namespace)
const existing = client.authorityNamespaceSessions?.get(key)
if (existing?.session === session) {
acceptances.close()
} else {
existing?.acceptances.close()
existing?.session.disconnect()
client.authorityNamespaceSessions?.set(key, Object.freeze({ session, acceptances }))
}
return preparation.grant
} catch (error) {
acceptances.close()
return await joinTerminalAuthorityRollbackFailure(error, () => preparation.rollback())
} finally {
client.authorityPendingPreparations?.delete(pending)
pending.active = false
}
}
case DAEMON_TERMINAL_AUTHORITY_CONSUMER_RETIREMENT_CHALLENGE_REQUEST: {
const transport = this.requireAuthenticatedConsumerRetirementTransport(client)
const owner = this.terminalSessionAuthorityPtyOwner
const start = parseTerminalAuthorityConsumerRetirementStart(request.payload)
if (!owner || !start) {
throw new Error('terminal authority consumer retirement is unavailable')
}
return await owner.issuePolicyConsumerRetirementChallenge(start, transport)
}
case DAEMON_TERMINAL_AUTHORITY_CONSUMER_RETIREMENT_REQUEST: {
const transport = this.requireAuthenticatedConsumerRetirementTransport(client)
const owner = this.terminalSessionAuthorityPtyOwner
const proof = parseTerminalAuthorityConsumerRetirementProof(request.payload)
if (!owner || !proof || !client) {
throw new Error('terminal authority consumer retirement is unavailable')
}
const result = await owner.retireAuthenticatedPolicyConsumer(proof, transport)
const key = authorityNamespaceKey(result.namespace)
const installed = client.authorityNamespaceSessions?.get(key)
if (installed?.session.grant.consumer.consumerId === result.consumerId) {
client.authorityNamespaceSessions?.delete(key)
installed.acceptances.close()
installed.session.disconnect()
}
return result
}
case DAEMON_TERMINAL_AUTHORITY_CONSUMER_CANCEL_REQUEST: {
const transport = this.requireAuthenticatedConsumerTransport(client)
const cancellation = parseTerminalAuthorityNamespaceAdmissionCancellation(request.payload)
if (
!client ||
!cancellation ||
cancellation.connectionGrantId !== transport.connectionGrantId
) {
throw new Error('terminal authority namespace cancellation is unauthorized')
}
let canceled = false
for (const pending of client.authorityPendingPreparations ?? []) {
if (
sameAuthorityNamespace(pending.namespace, cancellation.namespace) &&
pending.requestId === cancellation.requestId &&
pending.connectionGrantId === cancellation.connectionGrantId &&
sameTerminalAuthorityPolicyConsumer(pending.consumer, cancellation.consumer)
) {
client.authorityPendingPreparations?.delete(pending)
pending.active = false
pending.acceptances.close()
await pending.preparation?.rollback()
canceled = true
}
}
const key = authorityNamespaceKey(cancellation.namespace)
const installed = client.authorityNamespaceSessions?.get(key)
if (
installed &&
installed.session.grant.requestId === cancellation.requestId &&
sameTerminalAuthorityPolicyConsumer(
installed.session.grant.consumer,
cancellation.consumer
)
) {
client.authorityNamespaceSessions?.delete(key)
installed.acceptances.close()
installed.session.disconnect()
canceled = true
}
return { canceled }
}
case 'acceptTerminalAuthorityNamespaceBoundary': {
const acceptance = parseTerminalAuthorityNamespaceBoundaryAcceptance(request.payload)
const authenticated = acceptance
? client?.authorityNamespaceSessions?.get(authorityNamespaceKey(acceptance.namespace))
: undefined
const pending = acceptance
? [...(client?.authorityPendingPreparations ?? [])].find(
(entry) =>
entry.active &&
sameAuthorityNamespace(entry.namespace, acceptance.namespace) &&
sameTerminalAuthorityPolicyConsumer(entry.consumer, acceptance.consumer)
)
: undefined
if (
acceptance &&
(pending ||
(authenticated &&
sameTerminalAuthorityPolicyConsumer(
authenticated.session.policyConsumer.identity,
acceptance.consumer
)))
) {
;(pending?.acceptances ?? authenticated!.acceptances).accept(acceptance)
return { acceptedBoundaryId: acceptance.boundaryId }
}
throw new Error('terminal authority namespace boundary acceptance is unauthorized')
}
case 'ackTerminalAuthorityNamespaceOutcome': {
const ack = parseTerminalAuthorityNamespaceOutcomeAck(request.payload)
const authenticated = ack
? client?.authorityNamespaceSessions?.get(authorityNamespaceKey(ack.namespace))
: undefined
if (
ack &&
authenticated &&
sameTerminalAuthorityPolicyConsumer(
authenticated.session.policyConsumer.identity,
ack.consumer
)
) {
return {
acknowledgedSequence: await authenticated.session.policyConsumer.acknowledge(ack)
}
}
throw new Error('terminal authority namespace outcome ACK is unauthorized')
}
case 'retireTerminalAuthorityPolicyConsumer': {
throw new Error('terminal authority policy consumer retirement is unsupported')
}
case 'startHistorySeedTransfer': {
if (!client?.authenticatedPairEstablished || client.streamSocket === null) {
throw new Error('Daemon client connection is incomplete; reconnect')
}
const transferId = this.historySeedTransfers.start(clientId, request.payload)
return { transferId }
}
case 'appendHistorySeedTransfer':
this.historySeedTransfers.append(
clientId,
request.payload.transferId,
request.payload.index,
request.payload.data
)
return {}
case 'finishHistorySeedTransfer':
this.historySeedTransfers.finish(clientId, request.payload.transferId)
return {}
case 'abortHistorySeedTransfer':
this.historySeedTransfers.abort(clientId, request.payload.transferId)
return {}
case 'createOrAttach': {
if (this.idleShutdownState !== 'running') {
throw new Error('Daemon temporarily unavailable; reconnect')
}
if (!client?.authenticatedPairEstablished || client.streamSocket === null) {
// Why: a control-only replacement can't own terminal admission or erase the prior client's retirement request.
throw new Error('Daemon client connection is incomplete; reconnect')
}
const p = request.payload
// Why proof is the only admission: a durable authority claim must resolve through an
// authenticated grant, never a consumer label the client picked for itself.
const authorityConsumer: TerminalAuthorityPolicyConsumerSource | null =
isDaemonConsumerProofGrant(client.capabilities?.terminalAuthorityConsumerProof) &&
client.authorityConsumerTransport !== undefined &&
this.terminalSessionAuthorityAdmissionReady()
? this.authenticatedPolicyConsumerResolver(client)
: null
const authorityAdmitted = authorityConsumer !== null
if (hasTerminalSessionAuthorityCreateMetadata(p) && !authorityAdmitted) {
throw new Error('terminal_session_authority_unavailable')
}
this.createOrAttachInFlight++
const attachOnly = p.attachOnly === true
const streamBindingNonce =
supportsPtyStreamBinding(this.protocolVersion) &&
isPtyStreamBindingNonce(p.streamBindingNonce)
? p.streamBindingNonce
: null
let routedSessionId = p.sessionId
let streamAuthorityAccess: TerminalSessionAuthorityPtyAccess | null | undefined
const routeTransientFact = (fact: DaemonTransientFact): void => {
const capturedAccess = streamAuthorityAccess
if (capturedAccess === undefined) {
throw new Error('terminal_session_authority_semantic_route_missing')
}
if (capturedAccess) {
this.recordAuthorityTransientFact(routedSessionId, capturedAccess, fact)
} else {
this.enqueueLegacyTransientFact(routedSessionId, fact, clientId)
}
}
let result: Awaited<ReturnType<TerminalHost['createOrAttach']>>
try {
if (
p.agentSessionEnsure !== undefined &&
(!isAgentSessionExecutionClaim(p.agentSessionEnsure.claim) ||
!isAgentSessionSurfaceBinding(p.agentSessionEnsure.surface))
) {
throw new Error('agent_session_identity_required')
}
if (!attachOnly) {
await this.preparePtySpawnUnlessCanceled(p.sessionId, clientId)
}
if (p.historySeed !== undefined && p.historySeedTransferId !== undefined) {
throw new Error('Multiple terminal history seed sources')
}
const historySeedChunks =
p.historySeedTransferId !== undefined
? this.historySeedTransfers.take(clientId, p.historySeedTransferId)
: p.historySeed !== undefined
? [p.historySeed]
: undefined
result = await this.host.createOrAttach({
sessionId: p.sessionId,
cols: p.cols,
rows: p.rows,
cwd: p.cwd,
env: p.env,
envToDelete: p.envToDelete,
command: p.command,
startupCommandDelivery: p.startupCommandDelivery,
...(attachOnly ? { attachOnly: true } : {}),
// Why: RPC payloads are untrusted JSON; persist only the allowlisted routing enum, never arbitrary identity.
...(isTuiAgent(p.launchAgent) ? { launchAgent: p.launchAgent } : {}),
shellOverride: p.shellOverride,
terminalWindowsWslDistro: p.terminalWindowsWslDistro,
terminalWindowsPowerShellImplementation: p.terminalWindowsPowerShellImplementation,
shellReadySupported: p.shellReadySupported,
historySeedChunks,
startupIngress: parsePtyStartupIngressIntent(p.startupIngress),
...(p.shellReadyTimeoutMs !== undefined
? { shellReadyTimeoutMs: p.shellReadyTimeoutMs }
: {}),
...(p.agentSessionEnsure ? { agentSessionEnsure: p.agentSessionEnsure } : {}),
...(p.terminalSessionAuthorityVersion !== undefined
? { terminalSessionAuthorityVersion: p.terminalSessionAuthorityVersion }
: {}),
...(p.terminalSessionAuthorityOperationId !== undefined
? { terminalSessionAuthorityOperationId: p.terminalSessionAuthorityOperationId }
: {}),
...(p.worktreeId !== undefined ? { worktreeId: p.worktreeId } : {}),
...(p.paneKey !== undefined ? { paneKey: p.paneKey } : {}),
...(p.paneGeneration !== undefined ? { paneGeneration: p.paneGeneration } : {}),
...(p.terminalSessionAuthorityAccess !== undefined
? { terminalSessionAuthorityAccess: p.terminalSessionAuthorityAccess }
: {}),
...(authorityAdmitted ? { terminalSessionAuthorityNegotiated: true as const } : {}),
...(authorityAdmitted
? {
terminalSessionAuthorityPolicyConsumer: authorityConsumer!
}
: {}),
onSessionResolved: (sessionId) => {
routedSessionId = sessionId
},
streamClient: {
onAuthorityAccess: (access) => {
streamAuthorityAccess = access
},
onIncarnation: (incarnationId) => {
this.streamClientIdBySessionId.set(routedSessionId, clientId)
this.streamDataBatcher.refreshSessionDroppability(routedSessionId)
if (streamBindingNonce) {
this.streamDataBatcher.establishSessionSource(clientId, routedSessionId, {
incarnationId,
streamBindingNonce
})
}
if (this.transientFactRelay.isBackgrounded(routedSessionId)) {
this.streamDataBatcher.enqueueControlEvent(clientId, routedSessionId, {
type: 'event',
event: 'sessionBackgroundMarker',
sessionId: routedSessionId,
payload: { background: true }
})
}
},
onData: (data, rawLength = data.length, transformed = false, seq) => {
this.transientFactRelay.onSessionData(routedSessionId, data, routeTransientFact)
const lastInputAt = this.lastInputAtBySessionId.get(routedSessionId)
const isInteractiveOutput =
data.length <= DaemonServer.INTERACTIVE_OUTPUT_MAX_CHARS &&
lastInputAt !== undefined &&
performance.now() - lastInputAt <= DaemonServer.INTERACTIVE_OUTPUT_WINDOW_MS
this.streamDataBatcher.enqueue(clientId, routedSessionId, data, {
flushImmediately: isInteractiveOutput,
flushMaxChars: DaemonServer.INTERACTIVE_OUTPUT_MAX_CHARS,
rawLength,
transformed,
seq
})
},
onExit: (code, incarnationId) => {
// Why: exit tears down renderer handlers, so it must ride the ordered queue behind final output.
this.log.log('session-exited', {
sessionId: routedSessionId,
code
})
this.streamDataBatcher.enqueueControlEvent(clientId, routedSessionId, {
type: 'event',
event: 'exit',
sessionId: routedSessionId,
payload: { code, incarnationId }
})
this.streamDataBatcher.flush(clientId)
recordDaemonStreamBacklogEvent('sessionExit', {
sessionIdSuffix: routedSessionId.slice(-10)
})
this.transientFactRelay.onSessionExit(routedSessionId)
this.forgetHeldProducerPausesForSession(routedSessionId, incarnationId)
this.streamDataBatcher.refreshSessionDroppability(routedSessionId)
this.streamClientIdBySessionId.delete(routedSessionId)
this.lastInputAtBySessionId.delete(routedSessionId)
this.reevaluateIdleShutdown()
}
}
})
} finally {
this.createOrAttachInFlight--
this.reevaluateIdleShutdown()
}
routedSessionId = result.agentSessionEnsure?.owner.ptyId ?? p.sessionId
this.log.log(result.isNew ? 'session-created' : 'session-attached', {
sessionId: routedSessionId,
pid: result.pid
})
return {
isNew: result.isNew,
snapshot: result.snapshot,
pid: result.pid,
shellState: result.shellState,
incarnationId: result.incarnationId,
...(streamBindingNonce ? { streamBindingNonce } : {}),
...(result.launchAgent ? { launchAgent: result.launchAgent } : {}),
wslDistro: result.wslDistro,
...(result.historySeeded !== undefined ? { historySeeded: result.historySeeded } : {}),
...(result.agentSessionEnsure ? { agentSessionEnsure: result.agentSessionEnsure } : {}),
...(result.terminalSessionAuthorityAccess
? { terminalSessionAuthorityAccess: result.terminalSessionAuthorityAccess }
: {})
}
}
case 'cancelCreateOrAttach':
this.cancelPendingPtySpawnPreparations(request.payload.sessionId)
return {}
case 'closeStartupQueryAuthority':
return {
appliedSeq: this.host.closeStartupQueryAuthority(request.payload.sessionId)
}
case 'write':
try {
this.lastInputAtBySessionId.set(request.payload.sessionId, performance.now())
this.host.write(request.payload.sessionId, request.payload.data)
} catch (err) {
this.lastInputAtBySessionId.delete(request.payload.sessionId)
if (err instanceof SessionNotFoundError) {
this.sendExitEvent(client, request.payload.sessionId, -1)
}
throw err
}
return {}
case 'writeExact': {
const accepted =
typeof request.payload.data === 'string' &&
this.host.writeExact(
request.payload.sessionId,
request.payload.incarnationId,
request.payload.data
)
if (accepted) {
this.lastInputAtBySessionId.set(request.payload.sessionId, performance.now())
}
return { accepted }
}
case 'writeAuthorityExact': {
this.assertAuthorityPolicyAdmission(client, request.payload.authorityAccess)
const accepted =
typeof request.payload.data === 'string' &&
this.host.writeAuthorityExact(
request.payload.sessionId,
request.payload.authorityAccess,
request.payload.data
)
if (accepted) {
this.lastInputAtBySessionId.set(request.payload.sessionId, performance.now())
}
return { accepted }
}
case 'resize':
try {
this.host.resize(request.payload.sessionId, request.payload.cols, request.payload.rows)
} catch (err) {
if (err instanceof SessionNotFoundError) {
this.sendExitEvent(client, request.payload.sessionId, -1)
}
throw err
}
return {}
case 'resizeExact':
return {
accepted: this.host.resizeExact(
request.payload.sessionId,
request.payload.incarnationId,
request.payload.cols,
request.payload.rows
)
}
case 'resizeAuthorityExact':
this.assertAuthorityPolicyAdmission(client, request.payload.authorityAccess)
return {
accepted: this.host.resizeAuthorityExact(
request.payload.sessionId,
request.payload.authorityAccess,
request.payload.cols,
request.payload.rows
)
}
case 'pausePty':
if (supportsExactHeldProducerPause(this.protocolVersion)) {
const pause = parseHeldProducerPausePayload(request.payload)
if (pause.mode === 'invalid') {
return { accepted: false }
}
if (pause.mode === 'exact') {
if (!client?.authenticatedPairEstablished || client.streamSocket === null) {
return { accepted: false }
}
const accepted = this.host.acquireExactHeldProducerPause(
pause.sessionId,
pause.incarnationId,
clientId,
pause.token
)
if (accepted) {
this.rememberHeldProducerPause(clientId, pause)
}
return { accepted }
}
}
this.host.pauseProducer(request.payload.sessionId)
return {}
case 'resumePty':
if (supportsExactHeldProducerPause(this.protocolVersion)) {
const pause = parseHeldProducerPausePayload(request.payload)
if (pause.mode === 'invalid') {
return { accepted: false }
}
if (pause.mode === 'exact') {
const accepted = this.host.releaseExactHeldProducerPause(
pause.sessionId,
pause.incarnationId,
clientId,
pause.token
)
if (accepted) {
this.forgetHeldProducerPause(clientId, pause)
}
return { accepted }
}
}
this.host.resumeProducer(request.payload.sessionId)
return {}
case 'setSessionBackground': {
const sessionId = request.payload.sessionId
const background = request.payload.background === true
recordDaemonStreamBacklogEvent('setSessionBackground', {
sessionIdSuffix: sessionId.slice(-10),
background
})
const backgroundChanged = this.transientFactRelay.setSessionBackground(
sessionId,
background
)
this.streamDataBatcher.refreshSessionDroppability(sessionId)
if (!backgroundChanged) {
return {}
}
if (background) {
// Seed the fresh relay tracker with the emulator's dangling escape so a handoff-split sequence still parses.
this.transientFactRelay.seedSessionScanState(
sessionId,
this.host.getPartialEscapeTailAnsi(sessionId)
)
}
const streamClientId = this.streamClientIdBySessionId.get(sessionId)
if (!streamClientId) {
// Not attached yet — the attach-time marker covers the handoff.
return {}
}
// Reveal intentionally keeps the queued tail: main needs those bytes, and the normal flush/drain delivers them in order ahead of the marker.
const mode2031State = this.transientFactRelay.getMode2031ReplyScanState(sessionId)
const scanSeedAnsi = background
? ''
: mode2031State.pendingSubscribe
? mode2031State.tail
: this.host.getPartialEscapeTailAnsi(sessionId)
this.streamDataBatcher.enqueueControlEvent(streamClientId, sessionId, {
type: 'event',
event: 'sessionBackgroundMarker',
sessionId,
payload: {
background,
...(scanSeedAnsi.length > 0 ? { scanSeedAnsi } : {}),
...(mode2031State.pendingSubscribe ? { mode2031PendingSubscribe: true as const } : {})
}
})
return {}
}
case 'kill': {
const canceledPendingSpawn = this.cancelPendingPtySpawnPreparations(
request.payload.sessionId
)
this.lastInputAtBySessionId.delete(request.payload.sessionId)
const attribution = {
sessionId: request.payload.sessionId,
immediate: request.payload.immediate === true,
// Daemon control identity, not the paired-device bearer credential.
clientId
}
try {
await this.host.kill(request.payload.sessionId, {
immediate: request.payload.immediate
})
} catch (error) {
// Why: a kill that wins before session registration already canceled the pending spawn, so its intent is done.
if (!(canceledPendingSpawn && error instanceof SessionNotFoundError)) {
this.log.log('session-kill-failed', attribution)
throw error
}
}
this.log.log('session-killed', attribution)
return {}
}
case 'killExact': {
const accepted = await this.host.killExact(
request.payload.sessionId,
request.payload.incarnationId,
{ immediate: request.payload.immediate }
)
if (accepted) {
this.lastInputAtBySessionId.delete(request.payload.sessionId)
}
return { accepted }
}
case 'killAuthorityExact': {
const policyConsumer = this.assertAuthorityPolicyAdmission(
client,
request.payload.authorityAccess
)
const accepted = await this.host.killAuthorityExact(
request.payload.sessionId,
request.payload.authorityAccess,
policyConsumer,
{ immediate: request.payload.immediate }
)
if (accepted) {
this.lastInputAtBySessionId.delete(request.payload.sessionId)
}
return { accepted }
}
case 'signal':
this.host.signal(request.payload.sessionId, request.payload.signal)
return {}
case 'signalExact':
return {
accepted: this.host.signalExact(
request.payload.sessionId,
request.payload.incarnationId,
request.payload.signal
)
}
case 'signalAuthorityExact':
this.assertAuthorityPolicyAdmission(client, request.payload.authorityAccess)
return {
accepted: this.host.signalAuthorityExact(
request.payload.sessionId,
request.payload.authorityAccess,
request.payload.signal
)
}
case 'detach':
// Note: detach token handling simplified — full impl would track tokens per client
this.log.log('session-detached', {
sessionId: request.payload.sessionId
})
return {}
case 'getCwd':
return { cwd: await this.host.getCwd(request.payload.sessionId) }
case 'getForegroundProcess':
return {
foregroundProcess: this.host.getForegroundProcess(request.payload.sessionId)
}
case 'inspectProcess':
return this.host.inspectProcess(request.payload.sessionId)
case 'confirmForegroundProcess':
return {
foregroundProcess: await this.host.confirmForegroundProcess(request.payload.sessionId)
}
case 'clearScrollback':
this.host.clearScrollback(request.payload.sessionId)
return {}
case 'clearBufferExact':
return {
accepted: this.host.clearScrollbackExact(
request.payload.sessionId,
request.payload.incarnationId
)
}
case 'clearBufferAuthorityExact':
this.assertAuthorityPolicyAdmission(client, request.payload.authorityAccess)
return {
accepted: this.host.clearScrollbackAuthorityExact(
request.payload.sessionId,
request.payload.authorityAccess
)
}
case 'listSessions':
return { sessions: this.host.listSessions() }
case 'shutdownIfIdle': {
const authenticatedClient = this.clients.get(clientId)
const retiring =
authenticatedClient !== undefined &&
authenticatedClient.streamSocket !== null &&
this.clients.size === 1 &&
this.createOrAttachInFlight === 0 &&
this.host.listSessions().length === 0 &&
[...this.transportSockets].every(
(transport) =>
transport === authenticatedClient.controlSocket ||
transport === authenticatedClient.streamSocket
)
if (!retiring) {
return { retiring: false }
}
this.idleShutdownState = 'shutting-down'
this.initialAdoptionDeadlineMs = null
this.retirementRequested = false
this.cancelInitialAdoptionTimer()
// Why: close before acknowledging retirement so no new terminal races between the empty proof and disposal.
const serverClose = this.beginServerClose()
this.deferShutdownUntilReply(clientId, request.id, authenticatedClient.controlSocket, () =>
this.finishIdleShutdown(serverClose)
)
return { retiring: true }
}
case 'getSnapshot': {
const snapshotStart = performance.now()
const requestedScrollbackRows = request.payload.scrollbackRows
const scrollbackRows =
typeof requestedScrollbackRows === 'number' && Number.isFinite(requestedScrollbackRows)
? Math.max(0, Math.min(50_000, Math.floor(requestedScrollbackRows)))
: undefined
const snapshot = this.host.getSnapshot(request.payload.sessionId, {
scrollbackRows
})
const snapshotMs = performance.now() - snapshotStart
if (snapshotMs >= 25) {
// Serialize stalls block the daemon's single thread; surface them to attribute field typing stalls (issue #5096 family).
recordDaemonStreamBacklogEvent('slowGetSnapshot', {
sessionIdSuffix: request.payload.sessionId.slice(-10),
snapshotMs: Math.round(snapshotMs)
})
}
return { snapshot }
}
case 'getSize':
return { size: this.host.getAppliedSize(request.payload.sessionId) }
case 'takePendingOutput':
// Why no await: with includeSnapshot, drain+serialize must share one sync turn or cold restore replays doubled PTY bytes.
return this.host.takePendingOutput(
request.payload.sessionId,
request.payload.includeSnapshot === true,
{ teardownSnapshot: request.payload.teardownSnapshot === true }
)
case 'ping':
return { pong: true }
case 'systemResolverHealth':
return { health: await readCurrentProcessMacSystemResolverHealth() }
case 'ptySpawnHealth':
await this.ptySpawnHealthCheck()
return { healthy: true }
case 'shutdown': {
this.log.log('shutdown', {
reason: 'rpc',
killSessions: request.payload.killSessions === true
})
const serverClose = this.beginOrdinaryShutdownFence()
if (request.payload.killSessions) {
try {
await this.host.dispose()
} catch (err) {
// Why: shutdown must always self-terminate; failed owners stay retryable for the follow-up shutdown() below.
this.log.log('shutdown-dispose-failed', {
error: err instanceof Error ? err.message : String(err)
})
}
}
const controlSocket = this.clients.get(clientId)?.controlSocket
if (controlSocket) {
this.deferShutdownUntilReply(clientId, request.id, controlSocket, () =>
this.finishRpcShutdown(serverClose)
)
} else if (!this.shutdownPromise) {
this.shutdownPromise = this.finishRpcShutdown(serverClose)
}
return {}
}
}
throw new Error(`Unknown request type: ${(request as { type: string }).type}`)
}
private sendExitEvent(
client: ConnectedClient | undefined,
sessionId: string,
code: number
): void {
if (!client?.streamSocket) {
return
}
// Why: write/resize don't wait for replies, so this synthetic exit is the renderer's only signal to clear stale pane bindings.
this.streamDataBatcher.enqueueControlEvent(client.clientId, sessionId, {
type: 'event',
event: 'exit',
sessionId,
payload: { code }
})
this.streamDataBatcher.flush(client.clientId)
}
}