@@ -2,10 +2,11 @@ import { platform, arch } from 'node:os';
22import { loadConfig } from '../daemon/config.js' ;
33import { type AuthState , readAuth , saveAuth } from './auth.js' ;
44import { createHubClient , type HubClient } from './hub-client.js' ;
5+ import { createHubWs , type HubWs } from './hub-ws.js' ;
56import { createReconnector , type Reconnector } from './reconnection.js' ;
67import { logInfo , logWarn } from '../daemon/logger.js' ;
78import { getAgents } from '../daemon/routes.js' ;
8- import { getRuns } from '../daemon/run-manager.js' ;
9+ import { cancelRun , sendInput , getRuns } from '../daemon/run-manager.js' ;
910
1011const HEARTBEAT_INTERVAL_MS = 30_000 ;
1112const DAEMON_VERSION = '0.2.0' ;
@@ -18,11 +19,12 @@ export interface HubSync {
1819
1920export const createHubSync = ( ) : HubSync => {
2021 let hubClient : HubClient | null = null ;
22+ let hubWs : HubWs | null = null ;
2123 let heartbeatTimer : ReturnType < typeof setInterval > | null = null ;
2224 let reconnector : Reconnector | null = null ;
2325 let connected = false ;
2426
25- const register = async ( auth : AuthState ) : Promise < void > => {
27+ const connectAll = async ( auth : AuthState ) : Promise < void > => {
2628 const config = loadConfig ( ) ;
2729
2830 hubClient = createHubClient ( auth . hub . url , auth ) ;
@@ -39,15 +41,25 @@ export const createHubSync = (): HubSync => {
3941 auth . hub . machineId = result . machineId ;
4042 saveAuth ( auth ) ;
4143
42- connected = true ;
4344 logInfo ( `Registered with hub as machine ${ result . machineId } ` ) ;
45+
46+ // Connect WebSocket
47+ hubWs = createHubWs ( auth . hub . url , auth . session . access_token , auth . hub . machineId , ( ) => {
48+ // On disconnect — trigger reconnection
49+ connected = false ;
50+ logWarn ( '[hub-sync] WS disconnected, will reconnect via heartbeat' ) ;
51+ reconnector ?. start ( ) ;
52+ } ) ;
53+
54+ hubWs . connect ( ) ;
55+ connected = true ;
4456 } ;
4557
4658 const startHeartbeat = ( auth : AuthState ) : void => {
4759 if ( heartbeatTimer ) clearInterval ( heartbeatTimer ) ;
4860
4961 heartbeatTimer = setInterval ( async ( ) => {
50- if ( ! hubClient || ! connected ) return ;
62+ if ( ! hubClient ) return ;
5163
5264 try {
5365 const agents = getAgents ( ) . map ( ( a ) => ( {
@@ -66,10 +78,20 @@ export const createHubSync = (): HubSync => {
6678 activeRunIds,
6779 } ) ;
6880
69- // Process pending commands
81+ // Process pending commands from hub
7082 if ( response . pendingCommands && Array . isArray ( response . pendingCommands ) ) {
71- if ( response . pendingCommands . length > 0 ) {
72- logInfo ( `Received ${ response . pendingCommands . length } pending command(s) from hub` ) ;
83+ for ( const cmd of response . pendingCommands as Array < {
84+ type : string ;
85+ runId : string ;
86+ payload ?: string ;
87+ } > ) {
88+ if ( cmd . type === 'cancel' ) {
89+ cancelRun ( cmd . runId ) ;
90+ logInfo ( `Processed pending cancel for run ${ cmd . runId } ` ) ;
91+ } else if ( cmd . type === 'input' && cmd . payload ) {
92+ sendInput ( cmd . runId , cmd . payload ) ;
93+ logInfo ( `Processed pending input for run ${ cmd . runId } ` ) ;
94+ }
7395 }
7496 }
7597 } catch ( err ) {
@@ -88,7 +110,7 @@ export const createHubSync = (): HubSync => {
88110
89111 reconnector = createReconnector ( {
90112 onReconnect : async ( ) => {
91- await register ( auth ) ;
113+ await connectAll ( auth ) ;
92114 startHeartbeat ( auth ) ;
93115 } ,
94116 onError : ( err ) => {
@@ -99,7 +121,7 @@ export const createHubSync = (): HubSync => {
99121 } ) ;
100122
101123 try {
102- await register ( auth ) ;
124+ await connectAll ( auth ) ;
103125 startHeartbeat ( auth ) ;
104126 logInfo ( `Connected to hub at ${ auth . hub . url } ` ) ;
105127 } catch ( err ) {
@@ -122,6 +144,11 @@ export const createHubSync = (): HubSync => {
122144 reconnector = null ;
123145 }
124146
147+ if ( hubWs ) {
148+ hubWs . disconnect ( ) ;
149+ hubWs = null ;
150+ }
151+
125152 if ( hubClient && connected ) {
126153 const auth = readAuth ( ) ;
127154 if ( auth ) {
0 commit comments