mirror of
https://github.com/stablyai/orca.git
synced 2026-10-13 00:02:54 +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>
270 lines
9.0 KiB
TypeScript
270 lines
9.0 KiB
TypeScript
import {
|
|
AiVaultSearchRequestSchema,
|
|
AiVaultSearchStatusRequestSchema
|
|
} from '../../shared/ai-vault-search-contract'
|
|
import { ipcMain, type BrowserWindow } from 'electron'
|
|
import type { Store } from '../persistence'
|
|
import { SshConnectionStore } from '../ssh/ssh-connection-store'
|
|
import { SshConnectionManager } from '../ssh/ssh-connection-manager'
|
|
import type { SshRelayAiVaultHostInfo } from '../ssh/ssh-relay-session'
|
|
import type {
|
|
SshAiVaultRelayListParams,
|
|
SshAiVaultRelayTitleParams
|
|
} from '../../shared/ssh-ai-vault-relay'
|
|
import { SshPortForwardManager } from '../ssh/ssh-port-forward'
|
|
import { isRuntimeOwnedSshTargetId } from '../../shared/execution-host'
|
|
import { quitTeardownStartGate } from '../quit-teardown-start-gate'
|
|
import {
|
|
getSshTargetRegistryStore,
|
|
setSshConnectionManagerResolver,
|
|
setSshTargetRegistryHandlers,
|
|
setSshTargetRegistryStore
|
|
} from '../ssh/ssh-target-registry'
|
|
|
|
// Why re-exported: the registry moved to ../ssh/ssh-target-registry so the runtime can
|
|
// read it without pulling ipcMain in, but many existing importers reference these from
|
|
// here. Re-exporting keeps them working without a repo-wide rename.
|
|
export {
|
|
connectRegisteredSshTarget,
|
|
getActiveMultiplexer,
|
|
getRegisteredSshState,
|
|
getSshConnectionManager,
|
|
listRegisteredRemovedSshTargetLabels,
|
|
listRegisteredSshTargets
|
|
} from '../ssh/ssh-target-registry'
|
|
import { registerSshBrowseHandler } from './ssh-browse'
|
|
import { registerCredentialHandler } from './ssh-passphrase'
|
|
import type { OrcaRuntimeService } from '../runtime/orca-runtime'
|
|
import {
|
|
initializeSshConnectionGenerationSession,
|
|
resetSshConnectionGenerations
|
|
} from '../ssh/ssh-connection-generation'
|
|
import { resetSshProviderAuthorities } from '../ssh/ssh-provider-authority'
|
|
import { activeSessions } from './ssh-active-relay-sessions'
|
|
import {
|
|
registerAdvertisedUrlRefresh,
|
|
unregisterAdvertisedUrlRefresh
|
|
} from './ssh-advertised-url-refresh'
|
|
import {
|
|
connectInFlight,
|
|
credentialRequestedForTarget,
|
|
pendingTransportReconnects,
|
|
resetRelayInFlight,
|
|
testConnectionProbes,
|
|
testingTargets
|
|
} from './ssh-connect-attempt-registry'
|
|
import { createSshConnectionCallbacks } from './ssh-connection-state-callbacks'
|
|
import { registerSshConnectionHandlers } from './ssh-connection-handlers'
|
|
import {
|
|
registerPowerMonitorReconnect,
|
|
unregisterPowerMonitorReconnect
|
|
} from './ssh-host-sleep-reconnect'
|
|
import {
|
|
connectionManager,
|
|
getCurrentMainWindow,
|
|
portForwardManager,
|
|
setConnectionManager,
|
|
setCurrentGetMainWindow,
|
|
setCurrentRuntime,
|
|
setPersistedStore,
|
|
setPortForwardManager
|
|
} from './ssh-ipc-context'
|
|
import { registerSshPortForwardHandlers } from './ssh-port-forward-handlers'
|
|
import { persistPortForwardsWithUnrestored } from './ssh-port-forward-persistence'
|
|
import { clearRelayLostBackoff, relayLostBackoff } from './ssh-relay-lost-backoff'
|
|
import { refreshActiveRelaySessions } from './ssh-relay-session-callbacks'
|
|
import { broadcastPortForwards, relayStateOverrides } from './ssh-renderer-broadcast'
|
|
import { resetSshShutdownDrain } from './ssh-shutdown-drain'
|
|
import { registerSshTargetCrudHandlers } from './ssh-target-crud-handlers'
|
|
import { targetLifecycleInFlight } from './ssh-target-lifecycle-queue'
|
|
|
|
const SSH_IPC_CHANNELS = [
|
|
'ssh:listTargets',
|
|
'ssh:listRemovedTargetLabels',
|
|
'ssh:addTarget',
|
|
'ssh:updateTarget',
|
|
'ssh:removeTarget',
|
|
'ssh:importConfig',
|
|
'ssh:listConfigHosts',
|
|
'ssh:resolveConfigHost',
|
|
'ssh:connect',
|
|
'ssh:disconnect',
|
|
'ssh:terminateSessions',
|
|
'ssh:resetRelay',
|
|
'ssh:getState',
|
|
'ssh:needsPassphrasePrompt',
|
|
'ssh:testConnection',
|
|
'ssh:addPortForward',
|
|
'ssh:updatePortForward',
|
|
'ssh:removePortForward',
|
|
'ssh:listPortForwards',
|
|
'ssh:listDetectedPorts'
|
|
] as const
|
|
|
|
export function getActiveSshAiVaultHostInfo(targetId: string): SshRelayAiVaultHostInfo | null {
|
|
if (isRuntimeOwnedSshTargetId(targetId)) {
|
|
return null
|
|
}
|
|
return activeSessions.get(targetId)?.getAiVaultHostInfo() ?? null
|
|
}
|
|
|
|
export function getActiveSshAiVaultHostInfos(): SshRelayAiVaultHostInfo[] {
|
|
return [...activeSessions.values()].flatMap((session) => {
|
|
if (isRuntimeOwnedSshTargetId(session.targetId)) {
|
|
return []
|
|
}
|
|
const info = session.getAiVaultHostInfo()
|
|
return info ? [info] : []
|
|
})
|
|
}
|
|
|
|
export async function requestActiveSshSessionSearch(
|
|
targetId: string,
|
|
method: string,
|
|
params: unknown
|
|
): Promise<unknown> {
|
|
if (isRuntimeOwnedSshTargetId(targetId)) {
|
|
throw new Error('SSH target belongs to another runtime')
|
|
}
|
|
const session = activeSessions.get(targetId)
|
|
if (!session) {
|
|
throw new Error('SSH relay is not ready')
|
|
}
|
|
if (method === 'aiVault.searchSessions') {
|
|
return session.requestSessionSearch(method, AiVaultSearchRequestSchema.parse(params))
|
|
}
|
|
if (method === 'aiVault.searchStatus') {
|
|
return session.requestSessionSearch(method, AiVaultSearchStatusRequestSchema.parse(params))
|
|
}
|
|
throw new Error('Unknown session search method')
|
|
}
|
|
|
|
export async function requestActiveSshAiVaultSessionList(
|
|
targetId: string,
|
|
params: SshAiVaultRelayListParams,
|
|
options: { signal?: AbortSignal; timeoutMs?: number } = {}
|
|
): Promise<unknown> {
|
|
if (isRuntimeOwnedSshTargetId(targetId)) {
|
|
return null
|
|
}
|
|
const session = activeSessions.get(targetId)
|
|
if (!session) {
|
|
throw new Error('SSH relay is not ready')
|
|
}
|
|
return session.requestAiVaultSessionList(params, options)
|
|
}
|
|
|
|
export async function requestActiveSshAiVaultSessionTitles(
|
|
targetId: string,
|
|
params: SshAiVaultRelayTitleParams,
|
|
options: { signal?: AbortSignal; timeoutMs?: number } = {}
|
|
): Promise<unknown> {
|
|
if (isRuntimeOwnedSshTargetId(targetId)) {
|
|
return null
|
|
}
|
|
const session = activeSessions.get(targetId)
|
|
if (!session) {
|
|
throw new Error('SSH relay is not ready')
|
|
}
|
|
return session.requestAiVaultSessionTitles(params, options)
|
|
}
|
|
|
|
export function registerSshHandlers(
|
|
store: Store,
|
|
getMainWindow: () => BrowserWindow | null,
|
|
runtime?: OrcaRuntimeService
|
|
): { connectionManager: SshConnectionManager; sshStore: SshConnectionStore } {
|
|
initializeSshConnectionGenerationSession()
|
|
// Why: macOS re-activation re-calls this with a new BrowserWindow; ipcMain.handle() throws on a duplicate channel, so remove prior handlers first.
|
|
for (const ch of SSH_IPC_CHANNELS) {
|
|
ipcMain.removeHandler(ch)
|
|
}
|
|
|
|
setCurrentGetMainWindow(getMainWindow)
|
|
setCurrentRuntime(runtime)
|
|
setSshTargetRegistryStore(new SshConnectionStore(store))
|
|
setPersistedStore(store)
|
|
registerAdvertisedUrlRefresh(getCurrentMainWindow)
|
|
|
|
registerCredentialHandler()
|
|
|
|
const callbacks = createSshConnectionCallbacks()
|
|
if (connectionManager) {
|
|
connectionManager.setCallbacks(callbacks)
|
|
} else {
|
|
setConnectionManager(new SshConnectionManager(callbacks))
|
|
}
|
|
setPortForwardManager(portForwardManager ?? new SshPortForwardManager())
|
|
portForwardManager!.setCallbacks({
|
|
onForwardClosed: (entry, reason) => {
|
|
if (reason.kind === 'unexpected-exit') {
|
|
console.warn(
|
|
`[ssh] Port forward ${entry.localPort} → ${entry.remoteHost}:${entry.remotePort} closed unexpectedly${
|
|
reason.detail ? `: ${reason.detail}` : ''
|
|
}`
|
|
)
|
|
}
|
|
persistPortForwardsWithUnrestored(entry.connectionId)
|
|
broadcastPortForwards(getCurrentMainWindow, entry.connectionId)
|
|
}
|
|
})
|
|
refreshActiveRelaySessions()
|
|
registerPowerMonitorReconnect()
|
|
registerSshBrowseHandler(() => connectionManager)
|
|
setSshConnectionManagerResolver(() => connectionManager)
|
|
|
|
registerSshTargetCrudHandlers()
|
|
registerSshConnectionHandlers()
|
|
registerSshPortForwardHandlers()
|
|
|
|
return {
|
|
connectionManager: connectionManager!,
|
|
sshStore: getSshTargetRegistryStore() as SshConnectionStore
|
|
}
|
|
}
|
|
|
|
export async function resetSshHandlerStateForTests(): Promise<void> {
|
|
unregisterAdvertisedUrlRefresh()
|
|
unregisterPowerMonitorReconnect()
|
|
for (const ch of SSH_IPC_CHANNELS) {
|
|
ipcMain.removeHandler(ch)
|
|
}
|
|
ipcMain.removeHandler('ssh:submitCredential')
|
|
|
|
// Why: allSettled — a rejected disposal write must not abort the rest of the reset and leak state into the next test.
|
|
await Promise.allSettled(
|
|
[...activeSessions.values()].map((session) => session.disposeAndPersist())
|
|
)
|
|
activeSessions.clear()
|
|
for (const targetId of relayLostBackoff.keys()) {
|
|
clearRelayLostBackoff(targetId)
|
|
}
|
|
relayStateOverrides.clear()
|
|
connectInFlight.clear()
|
|
targetLifecycleInFlight.clear()
|
|
pendingTransportReconnects.clear()
|
|
resetSshConnectionGenerations()
|
|
resetSshProviderAuthorities()
|
|
resetRelayInFlight.clear()
|
|
testingTargets.clear()
|
|
testConnectionProbes.clear()
|
|
credentialRequestedForTarget.clear()
|
|
quitTeardownStartGate.resetForTests()
|
|
resetSshShutdownDrain()
|
|
|
|
await connectionManager?.disconnectAll()
|
|
portForwardManager?.dispose()
|
|
setConnectionManager(null)
|
|
setSshConnectionManagerResolver(null)
|
|
setPortForwardManager(null)
|
|
setSshTargetRegistryStore(null)
|
|
setPersistedStore(null)
|
|
setSshTargetRegistryHandlers({ connect: null, getState: null })
|
|
setCurrentGetMainWindow(() => null)
|
|
setCurrentRuntime(undefined)
|
|
}
|
|
|
|
export function getSshConnectionStore(): SshConnectionStore | null {
|
|
return getSshTargetRegistryStore()
|
|
}
|