import * as plugins from './plugins.js'; import { commitinfo } from './00_commitinfo_data.js'; import { FlexFramedTransport } from './classes.flexframedtransport.js'; import { GitReversionConflictError, GitReversionDirtyWorktreeError, GitReversionFencedError, } from './classes.gitreversion.js'; import { createSanitizedRuntimeEnvironment } from './functions.runtimeenvironment.js'; import { FlexServiceError, type IFlexIpcHostRequestMessage, type IFlexServiceInit, type IFlexServiceStatus, type TFlexChildEvent, type TFlexChildMessage, type TFlexHostRequestMethod, type TFlexHostRequest, type TFlexHostResponse, type TFlexParentMessage, type TFlexRequest, type TFlexRequestMethod, type TFlexResponse, flexIpcCancellationGraceTimeoutMs, flexIpcControlTimeoutMs, flexIpcDisposeTimeoutMs, flexIpcLifecycleTimeoutMs, flexIpcMaximumBrowserChannels, flexIpcMaximumPendingBrowserFrameBytes, flexIpcMaximumPendingBrowserFrames, flexIpcMaximumPendingRequests, flexIpcProtocolVersion, flexIpcSlashTimeoutMs, flexIpcStartupTimeoutMs, hasFlexExactKeys, isFlexPlainObject, isFlexHostResponseResult, isFlexRetainedHostSuccessMethod, isFlexResponseResult, parseFlexChildMessage, parseFlexParentMessage, } from './interfaces.flexipc.js'; export interface IFlexSupervisorStatus { state: IFlexServiceStatus['state']; ready: boolean; code?: IFlexServiceStatus['code']; startedAt?: string; pid?: number; lastExit?: IFlexSupervisorExit; } export interface IFlexSupervisorExit { type: 'exit' | 'error'; timestamp: number; code?: number | null; signal?: NodeJS.Signals | null; errorMessage?: string; } export interface IFlexSupervisorLogEntry { timestamp: number; stream: 'stdout' | 'stderr'; line: string; truncated: boolean; } export interface IFlexSupervisorOptions { onChildExitObserved?: (exitArg: IFlexSupervisorExit) => void; onChildExit?: (exitArg: IFlexSupervisorExit) => void; cancellationGraceTimeoutMs?: number; childTaskDrainTimeoutMs?: number; handleHostRequest?( methodArg: TMethod, payloadArg: TFlexHostRequest, peerIdArg: string, signalArg: AbortSignal, ): Promise>; handleUndeliveredHostSuccess?( methodArg: TMethod, payloadArg: TFlexHostRequest, resultArg: TFlexHostResponse, peerIdArg: string, ): Promise; handleBrowserFrame?(channelIdArg: string, bytesArg: Buffer, peerIdArg: string): Promise; } interface IFlexPendingRequest { kind: 'request' | 'dispose'; method?: TFlexRequestMethod; resolve: (valueArg: unknown) => void; reject: (errorArg: unknown) => void; timeout?: NodeJS.Timeout; signal?: AbortSignal; abortListener?: () => void; cancellationGraceTimeout?: NodeJS.Timeout; cancelled?: true; dispatched?: true; } interface IFlexChildLifecycle { type: 'exit' | 'error'; code?: number | null; signal?: NodeJS.Signals | null; error?: Error; } interface IFlexPendingHostSuccessAcknowledgement { peerId: string; acknowledge(): boolean; } interface IFlexBrowserChannelGeneration { sessionGenerationId: string; sessionGenerationSequence: number; notificationOperation?: Promise; } type TFlexHostSuccessDeliveryState = | 'retained' | 'acknowledged' | 'compensating' | 'compensated'; interface ILogAccumulator { decoder: TextDecoder; partial: string; dropping: boolean; } const supervisorLogEntries = 200; const supervisorLogLineBytes = 8 * 1024; const supervisorTermTimeoutMs = 5_000; const supervisorKillTimeoutMs = 2_000; const logSuffix = '... [truncated]'; const supervisorExitMessageBytes = 1024; export class FlexBrowserChannelNotificationUnavailableError extends Error { constructor() { super('The Flex child generation is unavailable.'); this.name = 'FlexBrowserChannelNotificationUnavailableError'; } } const readOnlyFlexRequestMethods: ReadonlySet = new Set([ 'service.status', 'intelligence.get', 'session.list', 'session.get', 'session.reversion.info', 'slash.list', 'message.page', 'message.get', 'prompt.get', 'prompt.list', 'permission.list', 'provider.list', 'provider.login.status', 'provider.connection.list', 'provider.connection.opencode-auth.get', 'provider.connection.ratelimits.get', 'model.choice.get', 'model.choice.validate', ]); const outcomeMayBeUnknown = (methodArg?: TFlexRequestMethod): boolean => methodArg !== undefined && !readOnlyFlexRequestMethods.has(methodArg); const isReadyOnlyHostRequest = (methodArg: TFlexHostRequestMethod): boolean => ( methodArg === 'delegated-run-admission.acquire' || methodArg === 'crossharness.chats.list' || methodArg === 'crossharness.chat.read' || methodArg === 'browser.resources.resolve' || methodArg === 'browser.channel.open' || methodArg === 'browser.channel.close' ); const sanitizeExitMessage = (messageArg: string): string => { const sanitized = messageArg.replace(/[\u0000-\u001f\u007f]/gu, ' ').trim(); if (Buffer.byteLength(sanitized, 'utf8') <= supervisorExitMessageBytes) return sanitized; return Buffer.from(sanitized, 'utf8') .subarray(0, supervisorExitMessageBytes) .toString('utf8') .replace(/\uFFFD$/u, ''); }; const isMissingProcess = (errorArg: unknown): boolean => typeof errorArg === 'object' && errorArg !== null && 'code' in errorArg && errorArg.code === 'ESRCH'; const isProcessGroupAlive = (processGroupIdArg: number): boolean => { try { process.kill(-processGroupIdArg, 0); return true; } catch (errorArg) { if (isMissingProcess(errorArg)) return false; throw errorArg; } }; const signalProcessGroup = (processGroupIdArg: number, signalArg: NodeJS.Signals): void => { try { process.kill(-processGroupIdArg, signalArg); } catch (errorArg) { if (!isMissingProcess(errorArg)) throw errorArg; } }; const waitFor = async (predicateArg: () => boolean, timeoutMsArg: number): Promise => { const deadline = Date.now() + timeoutMsArg; while (predicateArg()) { const remaining = deadline - Date.now(); if (remaining <= 0) return false; await new Promise((resolve) => setTimeout(resolve, Math.min(20, remaining))); } return true; }; const waitBounded = async (promiseArg: Promise, timeoutMsArg: number): Promise => { let timer: NodeJS.Timeout | undefined; try { return await Promise.race([ promiseArg, new Promise((resolve) => { timer = setTimeout(() => resolve(undefined), timeoutMsArg); }), ]); } finally { if (timer) clearTimeout(timer); } }; const truncateLog = (lineArg: string): { line: string; truncated: boolean } => { if (Buffer.byteLength(lineArg, 'utf8') <= supervisorLogLineBytes) { return { line: lineArg, truncated: false }; } const contentBytes = supervisorLogLineBytes - Buffer.byteLength(logSuffix, 'utf8'); const content = Buffer.from(lineArg, 'utf8') .subarray(0, contentBytes) .toString('utf8') .replace(/\uFFFD$/u, ''); return { line: `${content}${logSuffix}`, truncated: true }; }; export const resolveFlexChildEntryPath = (): string => { const modulePath = plugins.url.fileURLToPath(import.meta.url); const sourceDirectory = plugins.path.dirname(modulePath); const packageDirectory = plugins.fs.realpathSync.native(plugins.path.dirname(sourceDirectory)); const packageJsonPath = plugins.fs.realpathSync.native( plugins.path.join(packageDirectory, 'package.json'), ); const packageJson = JSON.parse(plugins.fs.readFileSync(packageJsonPath, 'utf8')) as unknown; if ( !packageJson || typeof packageJson !== 'object' || !('name' in packageJson) || packageJson.name !== commitinfo.name ) throw new Error('The Flex child package identity is invalid.'); const childPath = plugins.fs.realpathSync.native( plugins.path.join(packageDirectory, 'dist_ts', 'flexharness.child.js'), ); const relative = plugins.path.relative(packageDirectory, childPath); if ( relative === '..' || relative.startsWith(`..${plugins.path.sep}`) || plugins.path.isAbsolute(relative) || relative !== plugins.path.join('dist_ts', 'flexharness.child.js') ) throw new Error('The Flex child entry escaped its package.'); if (!plugins.fs.statSync(childPath).isFile()) { throw new Error('The package-owned Flex child entry is not a file.'); } return childPath; }; export class FlexSupervisor { private status: IFlexSupervisorStatus = { state: 'stopped', ready: false }; private child?: plugins.childProcess.ChildProcess; private lifecycle?: Promise; private transport?: FlexFramedTransport; private ownedProcessGroupId?: number; private orphanReapTask?: Promise; private childTaskDrainTask?: Promise; private childExitCleanupPending = false; private pendingChildExit?: IFlexSupervisorExit; private lastExit?: IFlexSupervisorExit; private browserPeerId = ''; private startPromise?: Promise; private stopPromise?: Promise; private startupResolve?: (statusArg: IFlexServiceStatus) => void; private startupReject?: (errorArg: Error) => void; private readonly pendingRequests = new Map(); private readonly activeHostRequests = new Map(); private readonly hostRequestTasks = new Set>(); private readonly pendingHostSuccessAcknowledgements = new Map< string, IFlexPendingHostSuccessAcknowledgement >(); private readonly activeReadyHostRequests = new Set>(); private readonly browserFrameTasks = new Set>(); private droppedBrowserFrames = 0; private browserFrameTaskBytes = 0; private readonly browserChannelGenerations = new Map(); private readonly settledHostRequestIds = new Set(); private readonly eventListeners = new Set<(eventArg: TFlexChildEvent) => void>(); private readonly logs: IFlexSupervisorLogEntry[] = []; private readonly stdoutAccumulator: ILogAccumulator = { decoder: new TextDecoder(), partial: '', dropping: false, }; private readonly stderrAccumulator: ILogAccumulator = { decoder: new TextDecoder(), partial: '', dropping: false, }; private readonly cancellationGraceTimeoutMs: number; private readonly childTaskDrainTimeoutMs: number; constructor(private readonly options: IFlexSupervisorOptions = {}) { this.cancellationGraceTimeoutMs = this.normalizeDeadline( options.cancellationGraceTimeoutMs, flexIpcCancellationGraceTimeoutMs, 'cancellationGraceTimeoutMs', ); this.childTaskDrainTimeoutMs = this.normalizeDeadline( options.childTaskDrainTimeoutMs, flexIpcControlTimeoutMs, 'childTaskDrainTimeoutMs', ); } public getStatus(): IFlexSupervisorStatus { return { ...this.status, ...(this.child?.pid ? { pid: this.child.pid } : {}), ...(this.lastExit ? { lastExit: { ...this.lastExit } } : {}), }; } public getLogSnapshot(): IFlexSupervisorLogEntry[] { return this.logs.map((entry) => ({ ...entry })); } public ownsChildPeer(peerIdArg: string): boolean { return this.browserPeerId !== '' && this.browserPeerId === peerIdArg && this.child?.exitCode === null && this.child.signalCode === null; } public ownsBrowserPeer(peerIdArg: string): boolean { return this.status.ready && this.ownsChildPeer(peerIdArg); } public ownsHostPeer(peerIdArg: string): boolean { return this.ownsChildPeer(peerIdArg) && (this.status.state === 'starting' || this.status.state === 'ready'); } public getActiveHostPeerId(): string | undefined { return this.ownsHostPeer(this.browserPeerId) ? this.browserPeerId : undefined; } public ownsDelegatedRunClosePeer(peerIdArg: string): boolean { return this.ownsChildPeer(peerIdArg) && ( this.status.state === 'starting' || this.status.state === 'ready' || this.status.state === 'stopping' ); } private ownsHostRequestPeer(methodArg: TFlexHostRequestMethod, peerIdArg: string): boolean { return methodArg === 'delegated-run-admission.close' ? this.ownsDelegatedRunClosePeer(peerIdArg) : this.ownsHostPeer(peerIdArg); } public subscribe(listenerArg: (eventArg: TFlexChildEvent) => void): () => void { this.eventListeners.add(listenerArg); return () => this.eventListeners.delete(listenerArg); } public async settleReadyHostRequests(): Promise { while (this.activeReadyHostRequests.size > 0) { const settled = await waitBounded( Promise.allSettled([...this.activeReadyHostRequests]).then(() => true), flexIpcDisposeTimeoutMs, ); if (settled !== true) { throw new Error('Prior ready-only Flex host requests did not settle after cleanup.'); } } } public async start( initArg: IFlexServiceInit, signalArg?: AbortSignal, ): Promise { if (this.startPromise) return this.startPromise; if (this.hostRequestTasks.size > 0) { throw new Error('Prior Flex child host requests must settle before another child can start.'); } if (this.child || this.stopPromise || this.status.state === 'stopping') { throw new Error('A prior Flex child must stop before another can start.'); } const startPromise = this.performStart(initArg, signalArg); this.startPromise = startPromise; try { return await startPromise; } finally { if (this.startPromise === startPromise) this.startPromise = undefined; } } public async request( methodArg: TMethod, payloadArg: TFlexRequest, signalArg?: AbortSignal, ): Promise> { if (methodArg !== 'service.status' && !this.status.ready) { throw new FlexServiceError(this.status.state === 'unsupported' ? 'UNSUPPORTED_RUNTIME' : 'NOT_READY'); } try { return await this.sendRequest( methodArg, payloadArg, methodArg === 'slash.execute' ? flexIpcSlashTimeoutMs : methodArg === 'session.delete' || methodArg === 'project.remove' ? flexIpcLifecycleTimeoutMs : flexIpcControlTimeoutMs, signalArg, ) as TFlexResponse; } catch (errorArg) { if ( errorArg instanceof FlexServiceError && errorArg.code === 'OUTCOME_UNKNOWN' ) { try { await this.stop(); } catch (stopErrorArg) { throw new AggregateError( [errorArg, stopErrorArg], 'The timed-out Flex request could not stop its child.', ); } } throw errorArg; } } public async requestToCompletion( methodArg: TMethod, payloadArg: TFlexRequest, signalArg?: AbortSignal, ): Promise> { if (methodArg !== 'service.status' && !this.status.ready) { throw new FlexServiceError(this.status.state === 'unsupported' ? 'UNSUPPORTED_RUNTIME' : 'NOT_READY'); } return await this.sendRequest( methodArg, payloadArg, undefined, signalArg, ) as TFlexResponse; } public async stop(): Promise { if (this.stopPromise) return this.stopPromise; const stopPromise = this.performStop(); this.stopPromise = stopPromise; try { await stopPromise; } finally { if (this.stopPromise === stopPromise) this.stopPromise = undefined; } } private async performStart( initArg: IFlexServiceInit, signalArg?: AbortSignal, ): Promise { this.status = { state: 'starting', ready: false }; this.settledHostRequestIds.clear(); this.browserChannelGenerations.clear(); signalArg?.throwIfAborted(); const childEntry = resolveFlexChildEntryPath(); const child = plugins.childProcess.spawn(process.execPath, [childEntry], { cwd: plugins.path.dirname(plugins.path.dirname(childEntry)), detached: process.platform !== 'win32', shell: false, windowsHide: true, env: createSanitizedRuntimeEnvironment(), stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'], }); this.child = child; this.lifecycle = this.observeChild(child); if (!child.pid) { await this.stop().catch(() => undefined); throw new Error('The Flex child did not provide a process ID.'); } this.browserPeerId = `flex-child:${child.pid}:${plugins.crypto.randomBytes(12).toString('base64url')}`; if (process.platform !== 'win32') this.ownedProcessGroupId = child.pid; const parentWriter = child.stdio[3]; const parentReader = child.stdio[4]; if (!(parentWriter instanceof plugins.stream.Writable) || !(parentReader instanceof plugins.stream.Readable)) { await this.stop().catch(() => undefined); throw new Error('The Flex child control pipes were not created.'); } const transport = new FlexFramedTransport({ readable: parentReader, writable: parentWriter, parseIncoming: parseFlexChildMessage, validateOutgoing: parseFlexParentMessage, }); this.transport = transport; const browserPeerId = this.browserPeerId; transport.onMessage((message) => this.handleChildMessage(message, browserPeerId)); transport.onError(() => { if (this.transport === transport && this.ownsChildPeer(browserPeerId)) { this.handleProtocolFailure(); } }); transport.onClose(() => { if ( this.transport === transport && this.status.state !== 'stopping' && this.status.state !== 'stopped' ) this.handleProtocolFailure(); }); const startup = new Promise((resolve, reject) => { this.startupResolve = resolve; this.startupReject = reject; }); try { await transport.send({ version: flexIpcProtocolVersion, type: 'init', init: initArg, }); const result = await this.waitForStartup(startup, this.lifecycle, signalArg); if (result.state === 'failed') throw new Error('The Flex child failed during initialization.'); if (result.state === 'unsupported') { await this.stop(); this.status = { ...result }; return this.getStatus(); } this.status = { ...result, pid: child.pid }; return this.getStatus(); } catch (errorArg) { this.startupResolve = undefined; this.startupReject = undefined; await this.stop().catch(() => undefined); this.status = { state: 'failed', ready: false }; throw new Error('Flex child startup failed.', { cause: errorArg }); } finally { this.startupResolve = undefined; this.startupReject = undefined; } } private async waitForStartup( startupArg: Promise, lifecycleArg: Promise, signalArg?: AbortSignal, ): Promise { let timeout: NodeJS.Timeout | undefined; let abortListener: (() => void) | undefined; try { const outcomes: Array> = [ startupArg, lifecycleArg.then(() => { throw new Error('The Flex child exited during startup.'); }), new Promise((_, reject) => { timeout = setTimeout( () => reject(new Error('The Flex child exceeded its startup deadline.')), flexIpcStartupTimeoutMs, ); }), ]; if (signalArg) { outcomes.push(new Promise((_, reject) => { if (signalArg.aborted) { reject(signalArg.reason); return; } abortListener = () => reject(signalArg.reason); signalArg.addEventListener('abort', abortListener, { once: true }); })); } return await Promise.race(outcomes); } finally { if (timeout) clearTimeout(timeout); if (abortListener) signalArg?.removeEventListener('abort', abortListener); } } private handleChildMessage(messageArg: TFlexChildMessage, peerIdArg: string): void { if (!this.ownsChildPeer(peerIdArg)) return; if (messageArg.type === 'status') { const wasReady = this.status.state === 'ready' && this.status.ready; this.status = { ...messageArg.status, ...(this.child?.pid ? { pid: this.child.pid } : {}), }; if ( messageArg.status.state === 'ready' || messageArg.status.state === 'unsupported' || messageArg.status.state === 'failed' ) this.startupResolve?.(messageArg.status); if (wasReady && messageArg.status.state === 'failed') this.handleProtocolFailure(); return; } if (messageArg.type === 'event') { for (const listener of [...this.eventListeners]) { try { listener(messageArg.event); } catch { // Event listeners are isolated from the child transport. } } return; } if (messageArg.type === 'host.request') { if ( !this.ownsHostRequestPeer(messageArg.method, peerIdArg) || this.activeHostRequests.has(messageArg.requestId) || this.settledHostRequestIds.has(messageArg.requestId) || this.hostRequestTasks.size >= flexIpcMaximumBrowserChannels ) { this.handleProtocolFailure(); return; } const operation = this.handleHostRequest(messageArg, peerIdArg); this.hostRequestTasks.add(operation); if (isReadyOnlyHostRequest(messageArg.method)) { this.activeReadyHostRequests.add(operation); } void operation.finally(() => { this.hostRequestTasks.delete(operation); this.activeReadyHostRequests.delete(operation); this.notifyChildExitCleanup(); }).catch(() => undefined); return; } if (messageArg.type === 'host.response.acknowledge') { const pending = this.pendingHostSuccessAcknowledgements.get(messageArg.requestId); if (!pending || pending.peerId !== peerIdArg) { this.handleProtocolFailure(); return; } this.pendingHostSuccessAcknowledgements.delete(messageArg.requestId); if (!pending.acknowledge()) this.handleProtocolFailure(); return; } if (messageArg.type === 'host.cancel') { const active = this.activeHostRequests.get(messageArg.requestId); if (active) { active.abort(new FlexServiceError('ABORTED')); return; } if (!this.settledHostRequestIds.has(messageArg.requestId)) { this.handleProtocolFailure(); } return; } if (messageArg.type === 'browser.channel.frame') { const handleBrowserFrame = this.options.handleBrowserFrame; if (!this.ownsBrowserPeer(peerIdArg) || !handleBrowserFrame) { this.handleProtocolFailure(); return; } const transport = this.transport; if (!transport) { this.handleProtocolFailure(); return; } const bytes = Buffer.from(messageArg.dataBase64, 'base64'); if ( this.browserFrameTasks.size >= flexIpcMaximumPendingBrowserFrames || this.browserFrameTaskBytes + bytes.byteLength > flexIpcMaximumPendingBrowserFrameBytes ) { // Backpressure on the host side drops the frame; a live screencast can // legitimately outrun the host for a moment and must not stop the child. this.droppedBrowserFrames += 1; return; } this.browserFrameTaskBytes += bytes.byteLength; let operation!: Promise; operation = Promise.resolve() .then(() => handleBrowserFrame(messageArg.channelId, bytes, peerIdArg)) .finally(() => { this.browserFrameTaskBytes -= bytes.byteLength; this.browserFrameTasks.delete(operation); this.notifyChildExitCleanup(); }); this.browserFrameTasks.add(operation); // A frame the host cannot deliver (channel closing, already closed, or a // failed inbound write) is a data-path outcome, not a protocol violation: // the host closes and notifies the channel itself. Stopping the child here // would take down every Flex session because a frame raced a channel close. void operation.catch(() => { this.droppedBrowserFrames += 1; }); return; } const pending = this.pendingRequests.get(messageArg.requestId); if (!pending) { this.handleProtocolFailure(); return; } this.clearPendingRequest(messageArg.requestId, pending); if (pending.cancelled) return; if (!messageArg.ok) { pending.reject(new FlexServiceError(messageArg.error.code)); return; } if ( pending.kind === 'request' && pending.method && !isFlexResponseResult(pending.method, messageArg.result) ) { pending.reject(new Error('The Flex child returned an invalid method result.')); this.handleProtocolFailure(); return; } if ( pending.kind === 'dispose' && (!isFlexPlainObject(messageArg.result) || !hasFlexExactKeys(messageArg.result, ['disposed']) || messageArg.result.disposed !== true) ) { pending.reject(new Error('The Flex child returned an invalid dispose result.')); this.handleProtocolFailure(); return; } pending.resolve(messageArg.result); } public async sendBrowserFrame( channelIdArg: string, bytesArg: Buffer, peerIdArg: string, ): Promise { if (!this.ownsBrowserPeer(peerIdArg)) throw new Error('The Flex child generation is unavailable.'); const transport = this.transport; if (!transport) throw new Error('The Flex child transport is unavailable.'); await transport.send({ version: flexIpcProtocolVersion, type: 'browser.channel.frame', channelId: channelIdArg, dataBase64: bytesArg.toString('base64'), }); } public async notifyBrowserChannelClosed(channelIdArg: string, peerIdArg: string): Promise { if (!this.ownsBrowserPeer(peerIdArg)) { if (!this.ownsChildPeer(peerIdArg)) { throw new FlexBrowserChannelNotificationUnavailableError(); } throw new Error('The Flex child generation is unavailable.'); } const transport = this.transport; if (!transport) throw new Error('The Flex child transport is unavailable.'); const generation = this.browserChannelGenerations.get(channelIdArg); if (!generation) throw new Error('The Flex browser channel generation is unavailable.'); if (!generation.notificationOperation) { let operation!: Promise; operation = transport.send({ version: flexIpcProtocolVersion, type: 'browser.channel.closed', channelId: channelIdArg, sessionGenerationId: generation.sessionGenerationId, sessionGenerationSequence: generation.sessionGenerationSequence, }).then(() => { if (this.browserChannelGenerations.get(channelIdArg) === generation) { this.browserChannelGenerations.delete(channelIdArg); } }).finally(() => { if (generation.notificationOperation === operation) { generation.notificationOperation = undefined; } }); generation.notificationOperation = operation; } await generation.notificationOperation; } private async handleHostRequest( messageArg: IFlexIpcHostRequestMessage, peerIdArg: string, ): Promise { const transport = this.transport; const child = this.child; if ( !transport || !this.options.handleHostRequest || !this.ownsHostRequestPeer(messageArg.method, peerIdArg) ) { this.handleProtocolFailure(); return; } if ( isFlexRetainedHostSuccessMethod(messageArg.method) && !this.options.handleUndeliveredHostSuccess ) { this.handleProtocolFailure(); return; } const controller = new AbortController(); this.activeHostRequests.set(messageArg.requestId, controller); const timeout = setTimeout( () => controller.abort(new FlexServiceError('TIMEOUT')), messageArg.timeoutMs, ); timeout.unref(); let completedResult: TFlexHostResponse | undefined; let deliveryState: TFlexHostSuccessDeliveryState | undefined; let compensationTask: Promise | undefined; let responseSendAttempted = false; let compensationFailed = false; let acknowledgementTimeout: NodeJS.Timeout | undefined; let acknowledgementAbortListener: (() => void) | undefined; let acknowledgementResolve: (() => void) | undefined; let acknowledgementReject: ((errorArg: unknown) => void) | undefined; let pendingAcknowledgement: IFlexPendingHostSuccessAcknowledgement | undefined; let acknowledgementPromise: Promise | undefined; const browserOpenPayload = messageArg.method === 'browser.channel.open' ? messageArg.payload as TFlexHostRequest<'browser.channel.open'> : undefined; const browserClosePayload = messageArg.method === 'browser.channel.close' ? messageArg.payload as TFlexHostRequest<'browser.channel.close'> : undefined; const browserCloseGeneration = browserClosePayload === undefined ? undefined : this.browserChannelGenerations.get(browserClosePayload.channelId); if (browserOpenPayload) { this.browserChannelGenerations.set(browserOpenPayload.channelId, { sessionGenerationId: browserOpenPayload.sessionGenerationId, sessionGenerationSequence: browserOpenPayload.sessionGenerationSequence, }); } const compensateUndeliveredSuccess = (): Promise => { if (deliveryState === 'acknowledged') return Promise.resolve(); if (compensationTask) return compensationTask; const result = completedResult; if (!result) return Promise.resolve(); deliveryState = 'compensating'; if ( pendingAcknowledgement && this.pendingHostSuccessAcknowledgements.get(messageArg.requestId) === pendingAcknowledgement ) { this.pendingHostSuccessAcknowledgements.delete(messageArg.requestId); } compensationTask = (async () => { try { await this.options.handleUndeliveredHostSuccess?.( messageArg.method, messageArg.payload as TFlexHostRequest, result, peerIdArg, ); deliveryState = 'compensated'; completedResult = undefined; } catch (errorArg) { compensationFailed = true; deliveryState = 'compensated'; completedResult = undefined; throw new GitReversionFencedError( 'Undelivered Flex host success compensation failed.', { cause: errorArg }, ); } })(); return compensationTask; }; const rejectAcknowledgementAfterCompensation = (errorArg: unknown): void => { if (!acknowledgementReject || deliveryState !== 'retained') return; const task = compensateUndeliveredSuccess(); void task.then( () => acknowledgementReject?.(errorArg), (compensationError) => acknowledgementReject?.(compensationError), ); }; try { if ( browserClosePayload && ( !browserCloseGeneration || browserCloseGeneration.sessionGenerationId !== browserClosePayload.sessionGenerationId || browserCloseGeneration.sessionGenerationSequence !== browserClosePayload.sessionGenerationSequence ) ) throw new Error('The Flex browser channel close generation is stale.'); const result = await this.options.handleHostRequest( messageArg.method, messageArg.payload as TFlexHostRequest, peerIdArg, controller.signal, ); if (!isFlexHostResponseResult(messageArg.method, result)) { throw new Error('The Flex host returned an invalid resource result.'); } completedResult = result; deliveryState = 'retained'; if (controller.signal.aborted) { await compensateUndeliveredSuccess(); controller.signal.throwIfAborted(); } if (!this.ownsChildPeer(peerIdArg) || this.transport !== transport) { await compensateUndeliveredSuccess(); return; } if (isFlexRetainedHostSuccessMethod(messageArg.method)) { acknowledgementPromise = new Promise((resolve, reject) => { acknowledgementResolve = resolve; acknowledgementReject = reject; }); void acknowledgementPromise.catch(() => undefined); pendingAcknowledgement = { peerId: peerIdArg, acknowledge: () => { if (deliveryState !== 'retained') return false; deliveryState = 'acknowledged'; completedResult = undefined; acknowledgementResolve?.(); return true; }, }; this.pendingHostSuccessAcknowledgements.set( messageArg.requestId, pendingAcknowledgement, ); acknowledgementAbortListener = () => { rejectAcknowledgementAfterCompensation( controller.signal.reason ?? new FlexServiceError('ABORTED'), ); }; controller.signal.addEventListener('abort', acknowledgementAbortListener, { once: true }); if (controller.signal.aborted) acknowledgementAbortListener(); if (!this.ownsChildPeer(peerIdArg) || this.transport !== transport) { await compensateUndeliveredSuccess(); return; } } responseSendAttempted = true; try { await transport.send({ version: flexIpcProtocolVersion, type: 'host.response', requestId: messageArg.requestId, ok: true, result, }); } catch (errorArg) { await compensateUndeliveredSuccess(); throw errorArg; } if ( browserClosePayload && this.browserChannelGenerations.get(browserClosePayload.channelId) === browserCloseGeneration ) this.browserChannelGenerations.delete(browserClosePayload.channelId); clearTimeout(timeout); if (acknowledgementPromise) { if (deliveryState === 'retained') { acknowledgementTimeout = setTimeout(() => { rejectAcknowledgementAfterCompensation(new FlexServiceError('TIMEOUT')); }, this.cancellationGraceTimeoutMs); acknowledgementTimeout.unref(); } await acknowledgementPromise; } else { deliveryState = 'acknowledged'; completedResult = undefined; } this.rememberSettledHostRequest(messageArg.requestId); } catch (caughtError) { let errorArg = caughtError; if (completedResult) { try { await compensateUndeliveredSuccess(); } catch (compensationError) { errorArg = compensationError; } } if (responseSendAttempted) { if (deliveryState === 'acknowledged') { this.rememberSettledHostRequest(messageArg.requestId); } else { this.failClosedChildGeneration( errorArg instanceof Error ? errorArg : new Error('Flex host success delivery failed.'), transport, child, ); } return; } if (!this.ownsChildPeer(peerIdArg) || this.transport !== transport) return; const authoritativeError = errorArg instanceof GitReversionFencedError ? errorArg : controller.signal.aborted ? controller.signal.reason : errorArg; const code = authoritativeError instanceof FlexServiceError ? authoritativeError.code : authoritativeError instanceof GitReversionFencedError ? 'OUTCOME_UNKNOWN' : authoritativeError instanceof GitReversionConflictError || authoritativeError instanceof GitReversionDirtyWorktreeError ? 'CONFLICT' : 'INTERNAL'; await transport.send({ version: flexIpcProtocolVersion, type: 'host.response', requestId: messageArg.requestId, ok: false, error: { name: 'FlexServiceError', code, message: code === 'OUTCOME_UNKNOWN' ? 'The Flex operation outcome is unknown.' : code === 'CONFLICT' ? 'The Flex operation conflicted with a concurrent change.' : authoritativeError instanceof FlexServiceError ? authoritativeError.message : 'The Flex operation failed.', }, }).then( () => { this.rememberSettledHostRequest(messageArg.requestId); if (compensationFailed) this.handleProtocolFailure(); }, () => { if (this.ownsChildPeer(peerIdArg) && this.transport === transport) { this.handleProtocolFailure(); } }, ); } finally { if (browserOpenPayload && deliveryState !== 'acknowledged') { this.browserChannelGenerations.delete(browserOpenPayload.channelId); } clearTimeout(timeout); if (acknowledgementTimeout) clearTimeout(acknowledgementTimeout); if (acknowledgementAbortListener) { controller.signal.removeEventListener('abort', acknowledgementAbortListener); } if ( pendingAcknowledgement && this.pendingHostSuccessAcknowledgements.get(messageArg.requestId) === pendingAcknowledgement ) { this.pendingHostSuccessAcknowledgements.delete(messageArg.requestId); } if (this.activeHostRequests.get(messageArg.requestId) === controller) { this.activeHostRequests.delete(messageArg.requestId); } } } private sendRequest( methodArg: TFlexRequestMethod, payloadArg: unknown, timeoutMsArg: number | undefined, signalArg?: AbortSignal, ): Promise { return this.sendPending('request', timeoutMsArg, (requestId) => ({ version: flexIpcProtocolVersion, type: 'request', requestId, method: methodArg, payload: payloadArg, }), methodArg, signalArg); } private sendDispose(timeoutMsArg: number): Promise { return this.sendPending('dispose', timeoutMsArg, (requestId) => ({ version: flexIpcProtocolVersion, type: 'dispose', requestId, })); } private sendPending( kindArg: IFlexPendingRequest['kind'], timeoutMsArg: number | undefined, createMessageArg: (requestIdArg: string) => TFlexParentMessage, methodArg?: TFlexRequestMethod, signalArg?: AbortSignal, ): Promise { const transport = this.transport; if (!transport) return Promise.reject(new Error('The Flex child transport is unavailable.')); if (this.pendingRequests.size >= flexIpcMaximumPendingRequests) { return Promise.reject(new FlexServiceError('LIMIT_EXCEEDED')); } const child = this.child; const requestId = plugins.crypto.randomBytes(16).toString('base64url'); return new Promise((resolve, reject) => { if (signalArg?.aborted) { reject(signalArg.reason ?? new DOMException('The operation was aborted.', 'AbortError')); return; } const timeout = timeoutMsArg === undefined ? undefined : setTimeout(() => { const pending = this.pendingRequests.get(requestId); if (!pending || pending.cancelled) return; this.cancelPendingRequest( requestId, pending, new FlexServiceError( pending.dispatched && outcomeMayBeUnknown(pending.method) ? 'OUTCOME_UNKNOWN' : 'TIMEOUT', ), transport, child, ); }, timeoutMsArg); timeout?.unref(); const pending: IFlexPendingRequest = { kind: kindArg, ...(methodArg ? { method: methodArg } : {}), resolve, reject, ...(timeout ? { timeout } : {}), ...(signalArg === undefined ? {} : { signal: signalArg }), }; this.pendingRequests.set(requestId, pending); if (signalArg) { const abortListener = (): void => { if (this.pendingRequests.get(requestId) !== pending) return; this.cancelPendingRequest( requestId, pending, pending.dispatched && outcomeMayBeUnknown(pending.method) ? new FlexServiceError('OUTCOME_UNKNOWN') : signalArg.reason ?? new DOMException('The operation was aborted.', 'AbortError'), transport, child, ); }; pending.abortListener = abortListener; signalArg.addEventListener('abort', abortListener, { once: true }); if (signalArg.aborted) abortListener(); } if (this.pendingRequests.get(requestId) !== pending || pending.cancelled) return; pending.dispatched = true; void transport.send(createMessageArg(requestId)).catch(() => { const pending = this.pendingRequests.get(requestId); if (!pending) return; this.clearPendingRequest(requestId, pending); if (!pending.cancelled) { pending.reject( outcomeMayBeUnknown(pending.method) ? new FlexServiceError('OUTCOME_UNKNOWN') : new Error('The Flex child request could not be sent.'), ); } this.failClosedChildGeneration( new Error('The Flex child request could not be sent.'), transport, child, ); }); }); } private cancelPendingRequest( requestIdArg: string, pendingArg: IFlexPendingRequest, rejectionArg: unknown, transportArg: FlexFramedTransport, childArg?: plugins.childProcess.ChildProcess, ): void { if (this.pendingRequests.get(requestIdArg) !== pendingArg || pendingArg.cancelled) return; if (pendingArg.timeout) clearTimeout(pendingArg.timeout); if (pendingArg.signal && pendingArg.abortListener) { pendingArg.signal.removeEventListener('abort', pendingArg.abortListener); pendingArg.signal = undefined; pendingArg.abortListener = undefined; } pendingArg.cancelled = true; pendingArg.reject(rejectionArg); if (!pendingArg.dispatched) { this.clearPendingRequest(requestIdArg, pendingArg); return; } pendingArg.cancellationGraceTimeout = setTimeout(() => { if (this.pendingRequests.get(requestIdArg) !== pendingArg) return; this.clearPendingRequest(requestIdArg, pendingArg); this.failClosedChildGeneration( new Error('The Flex child did not settle a cancelled request.'), transportArg, childArg, ); }, this.cancellationGraceTimeoutMs); pendingArg.cancellationGraceTimeout.unref(); void transportArg.send({ version: flexIpcProtocolVersion, type: 'request.cancel', requestId: requestIdArg, }).catch(() => { if (this.pendingRequests.get(requestIdArg) !== pendingArg) return; this.clearPendingRequest(requestIdArg, pendingArg); this.failClosedChildGeneration( new Error('The Flex request cancellation could not be sent.'), transportArg, childArg, ); }); } private clearPendingRequest(requestIdArg: string, pendingArg: IFlexPendingRequest): void { if (this.pendingRequests.get(requestIdArg) === pendingArg) { this.pendingRequests.delete(requestIdArg); } if (pendingArg.timeout) clearTimeout(pendingArg.timeout); if (pendingArg.cancellationGraceTimeout) { clearTimeout(pendingArg.cancellationGraceTimeout); pendingArg.cancellationGraceTimeout = undefined; } if (pendingArg.signal && pendingArg.abortListener) { pendingArg.signal.removeEventListener('abort', pendingArg.abortListener); pendingArg.signal = undefined; pendingArg.abortListener = undefined; } } private async performStop(): Promise { const child = this.child; const lifecycle = this.lifecycle; if (!child || !lifecycle) { this.rejectPending(new Error('The Flex child stopped.')); await this.awaitChildTaskDrain(); await this.reapRetainedProcessGroup(); this.notifyChildExitCleanup(); this.status = { state: 'stopped', ready: false }; return; } this.status = { state: 'stopping', ready: false, pid: child.pid }; this.clearCancellationGraceTimeouts(); const deadline = Date.now() + flexIpcDisposeTimeoutMs; if (this.transport && child.exitCode === null && child.signalCode === null) { await this.sendDispose(flexIpcDisposeTimeoutMs).catch(() => undefined); const remaining = Math.max(1, deadline - Date.now()); await waitBounded(lifecycle, remaining); } if (child.exitCode === null && child.signalCode === null) { await this.terminateOwnedChild(child, lifecycle); } const orphanTask = this.orphanReapTask; if (orphanTask) await orphanTask; await this.reapRetainedProcessGroup(); const confirmed = await waitBounded(lifecycle, supervisorKillTimeoutMs); if (!confirmed && child.exitCode === null && child.signalCode === null) { this.status = { state: 'failed', ready: false }; throw new Error('The Flex child exit was not confirmed.'); } await this.transport?.close().catch(() => undefined); this.transport = undefined; this.rejectPending(new Error('The Flex child stopped.')); await this.awaitChildTaskDrain(); this.notifyChildExitCleanup(); if (this.child === child) this.child = undefined; if (this.lifecycle === lifecycle) this.lifecycle = undefined; this.browserPeerId = ''; this.browserChannelGenerations.clear(); this.status = { state: 'stopped', ready: false }; } private async terminateOwnedChild( childArg: plugins.childProcess.ChildProcess, lifecycleArg: Promise, ): Promise { const processGroupId = this.ownedProcessGroupId; if (processGroupId !== undefined && process.platform !== 'win32') { if (isProcessGroupAlive(processGroupId)) signalProcessGroup(processGroupId, 'SIGTERM'); let exited = await waitFor(() => isProcessGroupAlive(processGroupId), supervisorTermTimeoutMs); if (!exited) { signalProcessGroup(processGroupId, 'SIGKILL'); exited = await waitFor(() => isProcessGroupAlive(processGroupId), supervisorKillTimeoutMs); } if (!exited) throw new Error('The owned Flex process group survived SIGKILL.'); this.ownedProcessGroupId = undefined; } else { childArg.kill('SIGTERM'); let exited = await waitBounded(lifecycleArg, supervisorTermTimeoutMs); if (!exited && childArg.exitCode === null && childArg.signalCode === null) { childArg.kill('SIGKILL'); exited = await waitBounded(lifecycleArg, supervisorKillTimeoutMs); } if (!exited && childArg.exitCode === null && childArg.signalCode === null) { throw new Error('The owned Flex child survived SIGKILL.'); } } } private observeChild(childArg: plugins.childProcess.ChildProcess): Promise { childArg.stdout?.on('data', (chunk) => this.consumeLog('stdout', chunk, this.stdoutAccumulator)); childArg.stderr?.on('data', (chunk) => this.consumeLog('stderr', chunk, this.stderrAccumulator)); return new Promise((resolve) => { let settled = false; const finish = (outcomeArg: IFlexChildLifecycle): void => { if (settled) return; settled = true; this.flushLog('stdout', this.stdoutAccumulator); this.flushLog('stderr', this.stderrAccumulator); this.status = { state: this.status.state === 'stopping' ? 'stopped' : 'failed', ready: false }; this.browserPeerId = ''; this.browserChannelGenerations.clear(); this.startupReject?.(new Error('The Flex child exited during startup.')); this.rejectPending(new Error('The Flex child exited.')); const processGroupId = this.ownedProcessGroupId; const terminal: IFlexSupervisorExit = { type: outcomeArg.type, timestamp: Date.now(), ...(outcomeArg.code === undefined ? {} : { code: outcomeArg.code }), ...(outcomeArg.signal === undefined ? {} : { signal: outcomeArg.signal }), ...(outcomeArg.error?.message ? { errorMessage: sanitizeExitMessage(outcomeArg.error.message) } : {}), }; this.lastExit = terminal; try { this.options.onChildExitObserved?.({ ...terminal }); } catch { // Immediate authority fencing must not prevent lifecycle settlement. } this.pendingChildExit = terminal; this.childExitCleanupPending = true; this.startChildTaskDrain(); if (processGroupId !== undefined && process.platform !== 'win32') { const reap = this.reapOrphanedGroup(processGroupId).then(() => { if (this.ownedProcessGroupId === processGroupId) { this.ownedProcessGroupId = undefined; } }).finally(() => { if (this.orphanReapTask === reap) this.orphanReapTask = undefined; this.notifyChildExitCleanup(); }); this.orphanReapTask = reap; void reap.catch(() => undefined); } else { this.notifyChildExitCleanup(); } resolve(outcomeArg); }; childArg.once('exit', (code, signal) => finish({ type: 'exit', code, signal })); childArg.once('error', (error) => { if (!childArg.pid) finish({ type: 'error', error }); }); }); } private async reapOrphanedGroup(processGroupIdArg: number): Promise { if (!isProcessGroupAlive(processGroupIdArg)) return; signalProcessGroup(processGroupIdArg, 'SIGTERM'); let exited = await waitFor( () => isProcessGroupAlive(processGroupIdArg), supervisorTermTimeoutMs, ); if (!exited) { signalProcessGroup(processGroupIdArg, 'SIGKILL'); exited = await waitFor( () => isProcessGroupAlive(processGroupIdArg), supervisorKillTimeoutMs, ); } if (!exited) throw new Error('An owned Flex process-group member survived SIGKILL.'); } private async reapRetainedProcessGroup(): Promise { const processGroupId = this.ownedProcessGroupId; if (processGroupId === undefined || process.platform === 'win32') return; await this.reapOrphanedGroup(processGroupId); if (this.ownedProcessGroupId === processGroupId) this.ownedProcessGroupId = undefined; } private notifyChildExitCleanup(): void { if (!this.childExitCleanupPending) return; if (this.orphanReapTask) return; if (this.childTaskDrainTask) return; if ( this.hostRequestTasks.size > 0 || this.browserFrameTasks.size > 0 || this.activeHostRequests.size > 0 ) return; this.childExitCleanupPending = false; const terminal = this.pendingChildExit; this.pendingChildExit = undefined; if (!terminal) return; try { this.options.onChildExit?.({ ...terminal }); } catch { // Child lifecycle cleanup must not prevent lifecycle settlement. } } private handleProtocolFailure(): void { this.rejectPending(new Error('The Flex child protocol failed.')); this.startupReject?.(new Error('The Flex child protocol failed.')); void this.stop().catch(() => undefined); } private rejectPending(errorArg: Error): void { for (const [requestId, pending] of this.pendingRequests) { this.clearPendingRequest(requestId, pending); pending.reject( pending.dispatched && outcomeMayBeUnknown(pending.method) ? new FlexServiceError('OUTCOME_UNKNOWN') : errorArg, ); } for (const controller of this.activeHostRequests.values()) controller.abort(errorArg); this.settledHostRequestIds.clear(); } private clearCancellationGraceTimeouts(): void { for (const pending of this.pendingRequests.values()) { if (!pending.cancellationGraceTimeout) continue; clearTimeout(pending.cancellationGraceTimeout); pending.cancellationGraceTimeout = undefined; } } private failClosedChildGeneration( errorArg: Error, transportArg: FlexFramedTransport, childArg?: plugins.childProcess.ChildProcess, ): void { if ( !childArg || this.child !== childArg || this.transport !== transportArg || childArg.exitCode !== null || childArg.signalCode !== null ) return; this.status = { state: 'failed', ready: false }; this.browserPeerId = ''; this.browserChannelGenerations.clear(); this.transport = undefined; this.startupReject?.(errorArg); this.rejectPending(errorArg); void transportArg.close().catch(() => undefined); void this.stop().catch(() => undefined); } private startChildTaskDrain(): void { if (this.childTaskDrainTask) return; let task!: Promise; task = this.drainChildTasks().finally(() => { if (this.childTaskDrainTask === task) this.childTaskDrainTask = undefined; this.notifyChildExitCleanup(); }); this.childTaskDrainTask = task; void task.catch(() => undefined); } private async awaitChildTaskDrain(): Promise { const task = this.childTaskDrainTask ?? this.drainChildTasks(); await task; } private async drainChildTasks(): Promise { if (this.hostRequestTasks.size === 0 && this.browserFrameTasks.size === 0) return; const settled = await waitBounded( Promise.allSettled([...this.hostRequestTasks, ...this.browserFrameTasks]), this.childTaskDrainTimeoutMs, ); if ( !settled || this.hostRequestTasks.size > 0 || this.browserFrameTasks.size > 0 || this.activeHostRequests.size > 0 ) { this.status = { state: 'failed', ready: false }; throw new Error('Flex child tasks did not settle after child shutdown.'); } } private rememberSettledHostRequest(requestIdArg: string): void { this.settledHostRequestIds.delete(requestIdArg); this.settledHostRequestIds.add(requestIdArg); while (this.settledHostRequestIds.size > flexIpcMaximumPendingRequests * 4) { const oldest = this.settledHostRequestIds.values().next().value; if (oldest === undefined) break; this.settledHostRequestIds.delete(oldest); } } private normalizeDeadline( selectedArg: number | undefined, defaultArg: number, nameArg: string, ): number { const selected = selectedArg ?? defaultArg; if (!Number.isSafeInteger(selected) || selected < 1 || selected > defaultArg) { throw new Error(`${nameArg} must be a safe integer between 1 and ${defaultArg}.`); } return selected; } private consumeLog( streamArg: IFlexSupervisorLogEntry['stream'], chunkArg: unknown, accumulatorArg: ILogAccumulator, ): void { const bytes = typeof chunkArg === 'string' ? Buffer.from(chunkArg, 'utf8') : chunkArg instanceof Uint8Array ? chunkArg : undefined; if (!bytes) return; let text = accumulatorArg.decoder.decode(bytes, { stream: true }); while (text.length > 0) { if (accumulatorArg.dropping) { const newline = text.indexOf('\n'); if (newline < 0) return; accumulatorArg.dropping = false; text = text.slice(newline + 1); continue; } const newline = text.indexOf('\n'); if (newline >= 0) { const complete = `${accumulatorArg.partial}${text.slice(0, newline)}`.replace(/\r$/u, ''); accumulatorArg.partial = ''; this.addLog(streamArg, complete); text = text.slice(newline + 1); continue; } const complete = `${accumulatorArg.partial}${text}`; if (Buffer.byteLength(complete, 'utf8') > supervisorLogLineBytes) { this.addLog(streamArg, complete); accumulatorArg.partial = ''; accumulatorArg.dropping = true; } else { accumulatorArg.partial = complete; } return; } } private flushLog( streamArg: IFlexSupervisorLogEntry['stream'], accumulatorArg: ILogAccumulator, ): void { const final = accumulatorArg.decoder.decode(); if (final) this.consumeLog(streamArg, final, accumulatorArg); if (accumulatorArg.partial && !accumulatorArg.dropping) { this.addLog(streamArg, accumulatorArg.partial); } accumulatorArg.partial = ''; accumulatorArg.dropping = false; } private addLog(streamArg: IFlexSupervisorLogEntry['stream'], lineArg: string): void { const bounded = truncateLog(lineArg); this.logs.push({ timestamp: Date.now(), stream: streamArg, line: bounded.line, truncated: bounded.truncated, }); if (this.logs.length > supervisorLogEntries) { this.logs.splice(0, this.logs.length - supervisorLogEntries); } } }