mirror of
https://github.com/stablyai/orca.git
synced 2026-10-29 16:17:51 +00:00
* Revert "feat(orcad): source-side dormant export of a relay-hosted SSH target (#16741 T6-8) (#24519)" This reverts commit783101b304. * Revert "feat(ssh): update, roll back, recover and stop a managed orcad server (#16741 T6-5 follow-up) (#24463)" This reverts commit38c2d1dcb9. * Revert "feat(ssh): deploy and pair an empty managed orcad server over SSH (#16741 T6-5) (#24453)" This reverts commit8b76683b40. * Revert "fix(ssh): orcad GC honors the activation journal; readiness requires proven daemon coverage (#16741 T6 follow-up) (#24451)" This reverts commitd3f8c5063b. * Revert "feat(ssh): remote orcad stop by request file and journaled decommission (#16741 T6-4) (#24449)" This reverts commit43d9b43d3f. * Revert "feat(orcad): supervisable server: stop requests, managed stop receipts and a lifetime that keeps its lock on failed teardown (#16741 T6-3) (#24433)" This reverts commitb093d3ab20. * Revert "feat(ssh): crash-safe orcad activation, rollback and recovery (#16741 T6-2) (#24423)" This reverts commit1a9ac0e955. * Revert "feat(runtime): SSH access links for paired servers in a downgrade-safe sidecar (#16741 T5-1+T5-2) (#24420)" This reverts commit99db2bfae4. * Revert "feat(relay): capability-gated owner reset with a durable preparation journal (#16741 T3 R1) (#24418)" This reverts commit34a582bd39. * Revert "feat(ssh): track connection-manager drains, test probes and provider continuations (#16741 T2 P3+P8a) (#24407)" This reverts commitd53063d2b1. * Revert "feat(daemon): idle retirement, session census and recovery-only provider (#16741 T2 P4b) (#24409)" This reverts commitff212dbbef. * Revert "feat(ssh): add pty.resumeClient and split SSH PTY process listing (#16741 T2 P5+P6) (#24414)" This reverts commit92cb71765e. * Revert "feat(relay): await owned watcher and agent children on shutdown (#16741 T2 P1) (#24400)" This reverts commit6b36e4f85b. * Revert "feat(session): retry failed renderer session writes and verify local folder PTYs (#16741 T2 P9) (#24406)" This reverts commitd23ecef301. * Revert "feat(ssh): remote orcad primitives on the pinned Node runtime (#16741 T6-1) (#24419)" This reverts commitdd87ae578d. * Revert "fix(runtime): fence runtime-environment subscriptions and status probes by identity (#16741 T5-3) (#24421)" This reverts commitece9e4d2e3. * Revert "feat(orcad): migration manifest and dormant-state contracts (#16741 T6-7) (#24422)" This reverts commit3fbdaba262. * Revert "feat(ssh): wire SshConnection through the work and transport close ledgers (#16741 T2 P2) (#24401)" This reverts commit4e8edc8872. * Revert "feat(profiles): carry markdown frontmatter visibility in project transfers (#16741 T2 P7) (#24405)" This reverts commit60c93263cc. * Revert "fix(runtime): project the PTY incarnation onto mobile session tabs (#24413)" This reverts commit99e0303572. * Revert "feat(daemon): tag daemon stream data with the PTY incarnation id (#16741 T2 P4a) (#24402)" This reverts commit817af768b0. * Revert "feat(ssh): port the SSH connection work ledger and transport close ledger (#16741 T2) (#24210)" This reverts commitc9918931c8. * Revert "feat(relay): fence and drain file and git response streams on shutdown (#24185)" This reverts commitdc08ffeba9. * Revert "refactor(runtime-rpc): extract the Node WebSocket lifecycle; opt-in pinned port (#24186)" This reverts commita789233bbb. * Revert "feat(relay): route relay handlers through work admission; producer publication drain (#24181)" This reverts commit0b812bd698. * Revert "feat(relay): land the #16741 T1 seam (work drain, publication drain, release gate) (#24156)" This reverts commit3aa2d3af7c. --------- Co-authored-by: m4air <m4air@Mac.localdomain>
296 lines
12 KiB
TypeScript
296 lines
12 KiB
TypeScript
import { app, ipcMain } from 'electron'
|
|
import { randomUUID } from 'node:crypto'
|
|
import { listEnvironments, resolveEnvironment } from '../../shared/runtime-environment-store'
|
|
import type { RemoteRuntimeSubscription } from '../../shared/remote-runtime-client'
|
|
import type { Store } from '../persistence'
|
|
import {
|
|
isRuntimeEnvironmentManuallyDisconnected,
|
|
registerRuntimeEnvironmentConnectivityHandlers,
|
|
registerRuntimeEnvironmentPassiveHandlers
|
|
} from './runtime-environment-connectivity-handlers'
|
|
import {
|
|
closeRemoteRuntimeRequestConnection,
|
|
getRuntimeEnvironmentStatusOwner
|
|
} from './runtime-environment-request-connections'
|
|
import { registerRuntimeEnvironmentRecoveryHandler } from './runtime-environment-recovery-handler'
|
|
import {
|
|
advanceRuntimeEnvironmentTransportGeneration,
|
|
getRuntimeEnvironmentTransportGeneration
|
|
} from './runtime-environment-transport-generation'
|
|
import {
|
|
resetSharedControlSupport,
|
|
subscribeRuntimeEnvironment
|
|
} from './runtime-environment-transport-routing'
|
|
import { RUNTIME_ENVIRONMENT_HANDLER_CHANNELS } from './runtime-environment-handler-channels'
|
|
import { retirePairedRuntimeBrowserClientHostEnvironment } from '../browser/paired-runtime-browser-client-host-runtime'
|
|
import { registerRuntimeEnvironmentBrowserClientHostHandler } from './runtime-environment-browser-client-host-handler'
|
|
import { advanceRuntimeEnvironmentCapabilityIncarnation } from './runtime-environment-capability-evidence'
|
|
|
|
type RetainedRemoteRuntimeSubscription = RemoteRuntimeSubscription & {
|
|
environmentId: string
|
|
ownerWebContentsId: number
|
|
removeDestroyedListener: () => void
|
|
notifyClosed: () => void
|
|
}
|
|
const remoteRuntimeSubscriptions = new Map<string, RetainedRemoteRuntimeSubscription>()
|
|
const getUserDataPath = (): string => app.getPath('userData')
|
|
|
|
function closeSubscriptionsForEnvironment(environmentId: string): void {
|
|
// Why: removed runtimes must not retain terminal/browser WebSockets until renderer teardown.
|
|
for (const [subscriptionId, subscription] of remoteRuntimeSubscriptions) {
|
|
if (subscription.environmentId !== environmentId) {
|
|
continue
|
|
}
|
|
remoteRuntimeSubscriptions.delete(subscriptionId)
|
|
// Why: one failing teardown must not abandon this environment's other
|
|
// sockets -- that strands exactly the dead handles this sweep exists to
|
|
// retire. Guard the two steps independently so neither can skip the other,
|
|
// and so the isolation stays structural rather than resting on a claim that
|
|
// nothing inside notifyClosed will ever throw.
|
|
try {
|
|
subscription.close()
|
|
} catch (error) {
|
|
console.warn('[runtime-environments] subscription close failed during retirement:', error)
|
|
}
|
|
try {
|
|
// Why: a shared-control logical close never calls back, so notify directly.
|
|
subscription.notifyClosed()
|
|
} catch (error) {
|
|
console.warn('[runtime-environments] subscription close notice failed:', error)
|
|
}
|
|
}
|
|
}
|
|
/** Returns once the environment's client-hosted browser pages have been released. */
|
|
export function invalidateRuntimeEnvironmentTransport(environmentId: string): Promise<void> {
|
|
// Why: a same-id re-pair must retire every transport that still authenticates as the old peer.
|
|
advanceRuntimeEnvironmentCapabilityIncarnation(environmentId)
|
|
advanceRuntimeEnvironmentTransportGeneration(environmentId)
|
|
closeRemoteRuntimeRequestConnection(environmentId)
|
|
closeSubscriptionsForEnvironment(environmentId)
|
|
return retirePairedRuntimeBrowserClientHostEnvironment(
|
|
environmentId,
|
|
new Error('Runtime environment transport was invalidated')
|
|
).then(
|
|
() => undefined,
|
|
(error) => {
|
|
console.warn('[runtime-environments] browser client host retirement failed:', error)
|
|
}
|
|
)
|
|
}
|
|
|
|
export function registerRuntimeEnvironmentHandlers(store: Store): void {
|
|
// Why: keep direct re-registration safe even though register-core-handlers
|
|
// normally guards this path; otherwise the binary send listener can stack.
|
|
resetSharedControlSupport()
|
|
for (const channel of RUNTIME_ENVIRONMENT_HANDLER_CHANNELS) {
|
|
ipcMain.removeHandler(channel)
|
|
}
|
|
ipcMain.removeAllListeners('runtimeEnvironments:subscriptionBinary')
|
|
|
|
registerRuntimeEnvironmentConnectivityHandlers({
|
|
store,
|
|
getUserDataPath,
|
|
invalidateTransport: invalidateRuntimeEnvironmentTransport
|
|
})
|
|
registerRuntimeEnvironmentBrowserClientHostHandler({
|
|
getUserDataPath,
|
|
getSettings: () => store.getSettings()
|
|
})
|
|
registerRuntimeEnvironmentRecoveryHandler()
|
|
registerRuntimeEnvironmentPassiveHandlers(getUserDataPath)
|
|
for (const environment of listEnvironments(getUserDataPath())) {
|
|
if (!isRuntimeEnvironmentManuallyDisconnected(environment.id)) {
|
|
getRuntimeEnvironmentStatusOwner(getUserDataPath(), environment.id).activate()
|
|
}
|
|
}
|
|
ipcMain.handle(
|
|
'runtimeEnvironments:subscribe',
|
|
async (
|
|
event,
|
|
args: {
|
|
selector: string
|
|
method: string
|
|
params?: unknown
|
|
timeoutMs?: number
|
|
subscriptionId?: string
|
|
expectedEnvironmentPairingRevision?: number
|
|
expectedEnvironmentRuntimeId?: string
|
|
}
|
|
): Promise<{ subscriptionId: string; requestId: string }> => {
|
|
const subscriptionId =
|
|
typeof args.subscriptionId === 'string' && args.subscriptionId.length > 0
|
|
? args.subscriptionId
|
|
: randomUUID()
|
|
if (remoteRuntimeSubscriptions.has(subscriptionId)) {
|
|
throw new Error('Runtime environment subscription id already exists')
|
|
}
|
|
const environment = resolveEnvironment(getUserDataPath(), args.selector)
|
|
if (isRuntimeEnvironmentManuallyDisconnected(environment.id)) {
|
|
throw new Error('runtime_manually_disconnected')
|
|
}
|
|
const pairingRevision = environment.pairingRevision ?? environment.createdAt
|
|
if (
|
|
args.expectedEnvironmentPairingRevision !== undefined &&
|
|
pairingRevision !== args.expectedEnvironmentPairingRevision
|
|
) {
|
|
throw new Error('Runtime environment pairing changed; refresh and try again')
|
|
}
|
|
if (
|
|
args.expectedEnvironmentRuntimeId !== undefined &&
|
|
environment.runtimeId !== args.expectedEnvironmentRuntimeId
|
|
) {
|
|
throw new Error('Runtime environment identity changed; refresh and try again')
|
|
}
|
|
const transportGeneration = getRuntimeEnvironmentTransportGeneration(environment.id)
|
|
const transportIsCurrent = (): boolean =>
|
|
getRuntimeEnvironmentTransportGeneration(environment.id) === transportGeneration
|
|
const sender = event.sender
|
|
const ownerWebContentsId = sender.id
|
|
let senderDestroyed = sender.isDestroyed()
|
|
let subscription: RemoteRuntimeSubscription | null = null
|
|
let destroyedListenerAttached = false
|
|
const removeDestroyedListener = (): void => {
|
|
if (!destroyedListenerAttached) {
|
|
return
|
|
}
|
|
destroyedListenerAttached = false
|
|
sender.removeListener('destroyed', closeSubscription)
|
|
}
|
|
const closeSubscription = (): void => {
|
|
senderDestroyed = true
|
|
const retained = remoteRuntimeSubscriptions.get(subscriptionId) ?? null
|
|
remoteRuntimeSubscriptions.delete(subscriptionId)
|
|
if (retained) {
|
|
retained.close()
|
|
return
|
|
}
|
|
removeDestroyedListener()
|
|
subscription?.close()
|
|
}
|
|
// Why: the renderer treats close as terminal and drops its handle, so send it once.
|
|
// Latch before sending so a re-entrant call cannot duplicate it, and never
|
|
// throw: a dying renderer must not abort its siblings' retirement.
|
|
let closeNotified = false
|
|
const notifyClosed = (): void => {
|
|
if (closeNotified || sender.isDestroyed()) {
|
|
return
|
|
}
|
|
closeNotified = true
|
|
try {
|
|
sender.send('runtimeEnvironments:subscriptionEvent', { subscriptionId, type: 'close' })
|
|
} catch {
|
|
// The renderer is gone; there is no one left to tell.
|
|
}
|
|
}
|
|
sender.once('destroyed', closeSubscription)
|
|
destroyedListenerAttached = true
|
|
try {
|
|
subscription = await subscribeRuntimeEnvironment(
|
|
getUserDataPath(),
|
|
environment.id,
|
|
args.method,
|
|
args.params,
|
|
args.timeoutMs,
|
|
{
|
|
onEvent: (payload) => {
|
|
if (payload.type === 'close') {
|
|
// Why: retirement advances the generation before closing, so gating
|
|
// close on it stranded the renderer with a dead subscription.
|
|
notifyClosed()
|
|
return
|
|
}
|
|
if (transportIsCurrent() && !sender.isDestroyed()) {
|
|
sender.send('runtimeEnvironments:subscriptionEvent', {
|
|
subscriptionId,
|
|
...payload
|
|
})
|
|
}
|
|
},
|
|
onClose: () => {
|
|
const retained = remoteRuntimeSubscriptions.get(subscriptionId) ?? null
|
|
retained?.removeDestroyedListener()
|
|
remoteRuntimeSubscriptions.delete(subscriptionId)
|
|
}
|
|
},
|
|
transportIsCurrent
|
|
)
|
|
} catch (error) {
|
|
removeDestroyedListener()
|
|
throw error
|
|
}
|
|
let pairingIsCurrent = false
|
|
try {
|
|
const currentEnvironment = resolveEnvironment(getUserDataPath(), environment.id)
|
|
pairingIsCurrent =
|
|
(currentEnvironment.pairingRevision ?? currentEnvironment.createdAt) === pairingRevision
|
|
} catch {
|
|
pairingIsCurrent = false
|
|
}
|
|
if (!transportIsCurrent() || !pairingIsCurrent) {
|
|
removeDestroyedListener()
|
|
subscription.close()
|
|
throw new Error('Runtime environment pairing changed; refresh and try again')
|
|
}
|
|
if (senderDestroyed || sender.isDestroyed()) {
|
|
removeDestroyedListener()
|
|
subscription.close()
|
|
return { subscriptionId, requestId: subscription.requestId }
|
|
}
|
|
remoteRuntimeSubscriptions.set(subscriptionId, {
|
|
requestId: subscription.requestId,
|
|
environmentId: environment.id,
|
|
ownerWebContentsId,
|
|
removeDestroyedListener,
|
|
notifyClosed,
|
|
sendBinary: (bytes) => subscription?.sendBinary(bytes) ?? false,
|
|
close: () => {
|
|
removeDestroyedListener()
|
|
subscription?.close()
|
|
}
|
|
})
|
|
return { subscriptionId, requestId: subscription.requestId }
|
|
}
|
|
)
|
|
ipcMain.handle(
|
|
'runtimeEnvironments:unsubscribe',
|
|
(event, args: { subscriptionId: string }): { unsubscribed: boolean } => {
|
|
const subscription = remoteRuntimeSubscriptions.get(args.subscriptionId)
|
|
if (!subscription || subscription.ownerWebContentsId !== event.sender.id) {
|
|
return { unsubscribed: false }
|
|
}
|
|
remoteRuntimeSubscriptions.delete(args.subscriptionId)
|
|
subscription.close()
|
|
return { unsubscribed: true }
|
|
}
|
|
)
|
|
ipcMain.on(
|
|
'runtimeEnvironments:subscriptionBinary',
|
|
(event, args: { subscriptionId?: unknown; bytes?: unknown }) => {
|
|
if (typeof args.subscriptionId !== 'string') {
|
|
return
|
|
}
|
|
const bytes = toBinaryPayload(args.bytes)
|
|
if (!bytes) {
|
|
return
|
|
}
|
|
const subscription = remoteRuntimeSubscriptions.get(args.subscriptionId)
|
|
if (subscription?.ownerWebContentsId === event.sender.id) {
|
|
subscription.sendBinary(bytes)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
function toBinaryPayload(value: unknown): Uint8Array<ArrayBufferLike> | null {
|
|
if (value instanceof Uint8Array) {
|
|
return value
|
|
}
|
|
if (value instanceof ArrayBuffer) {
|
|
return new Uint8Array(value)
|
|
}
|
|
if (ArrayBuffer.isView(value)) {
|
|
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
|
|
}
|
|
return null
|
|
}
|