1515import type { ApiClient , Task } from "./api-client.js" ;
1616import type { AgentRunner } from "./runner.js" ;
1717import { callClaudeCli , createAuthHeaders , buildConversationHistory } from "./llm-client.js" ;
18+ import { PollLoop } from "./poll-loop.js" ;
1819import * as ui from "./ui.js" ;
1920
2021// ---------------------------------------------------------------------------
@@ -94,8 +95,7 @@ export class Manager {
9495 private runner : AgentRunner | null ;
9596 private api : ApiClient | null ;
9697 private lastSeenId : string | null = null ;
97- private timer : ReturnType < typeof setInterval > | null = null ;
98- private processing = false ;
98+ private poller : PollLoop ;
9999 private useClaudeCli : boolean ;
100100
101101 /** Callbacks for actions the Manager can't execute directly */
@@ -115,39 +115,35 @@ export class Manager {
115115 this . runner = options . runner ?? null ;
116116 this . api = options . api ?? null ;
117117 this . useClaudeCli = ! options . llmBaseUrl || ! options . llmApiKey ;
118+ this . poller = new PollLoop ( {
119+ name : "manager" ,
120+ intervalMs : this . pollIntervalMs ,
121+ onTick : ( ) => this . poll ( ) ,
122+ } ) ;
118123 }
119124
120125 // ── Lifecycle ────────────────────────────────────────────
121126
122127 start ( ) : void {
123128 ui . info ( `[manager] Started (poll every ${ this . pollIntervalMs / 1000 } s, model: ${ this . model } )` ) ;
124- this . timer = setInterval ( ( ) => this . poll ( ) , this . pollIntervalMs ) ;
125- this . poll ( ) ;
129+ this . poller . start ( ) ;
126130 }
127131
128132 stop ( ) : void {
129- if ( this . timer ) {
130- clearInterval ( this . timer ) ;
131- this . timer = null ;
132- }
133+ this . poller . stop ( ) ;
133134 ui . debug ( "manager" , "Manager stopped" ) ;
134135 }
135136
136137 /** Pause polling when WS clients are connected (messages come via WS) */
137138 pausePolling ( ) : void {
138- if ( this . timer ) {
139- clearInterval ( this . timer ) ;
140- this . timer = null ;
141- ui . info ( "[manager] Polling paused (WS connected)" ) ;
142- }
139+ ui . info ( "[manager] Polling paused (WS connected)" ) ;
140+ this . poller . pause ( ) ;
143141 }
144142
145143 /** Resume polling when all WS clients disconnected */
146144 resumePolling ( ) : void {
147- if ( ! this . timer ) {
148- this . timer = setInterval ( ( ) => this . poll ( ) , this . pollIntervalMs ) ;
149- ui . info ( "[manager] Polling resumed (no WS clients)" ) ;
150- }
145+ ui . info ( "[manager] Polling resumed (no WS clients)" ) ;
146+ this . poller . resume ( ) ;
151147 }
152148
153149 // ── WebSocket entry point ────────────────────────────────
@@ -183,52 +179,43 @@ export class Manager {
183179 // ── Polling ──────────────────────────────────────────────
184180
185181 private async poll ( ) : Promise < void > {
186- if ( this . processing ) return ;
187- this . processing = true ;
182+ await this . heartbeat ( ) ;
188183
189- try {
190- await this . heartbeat ( ) ;
191-
192- const messages = await this . fetchMessages ( ) ;
193- if ( ! messages || messages . length === 0 ) return ;
184+ const messages = await this . fetchMessages ( ) ;
185+ if ( ! messages || messages . length === 0 ) return ;
194186
195- const newMessages = this . findNewInboundMessages ( messages ) ;
196- if ( newMessages . length > 0 ) {
197- ui . debug ( "manager" , `${ newMessages . length } new message(s) to process` ) ;
198- }
187+ const newMessages = this . findNewInboundMessages ( messages ) ;
188+ if ( newMessages . length > 0 ) {
189+ ui . debug ( "manager" , `${ newMessages . length } new message(s) to process` ) ;
190+ }
199191
200- for ( const msg of newMessages ) {
201- const isFromUser = msg . from === "user" || msg . from . startsWith ( "user:" ) ;
192+ for ( const msg of newMessages ) {
193+ const isFromUser = msg . from === "user" || msg . from . startsWith ( "user:" ) ;
202194
203- try {
204- const senderContext = isFromUser
205- ? `[Message from user: ${ msg . from } ]`
206- : `[Message from agent: ${ msg . from } ]` ;
207- const enrichedContent = `${ senderContext } \n${ msg . content } ` ;
208-
209- const context = await this . fetchContext ( ) ;
210- const { reply, actions, proposals } = await this . think ( enrichedContent , context ) ;
211- await this . executeActions ( actions , context ) ;
212- await this . postReplyTo ( msg . from , reply ) ;
213- if ( isFromUser ) {
214- this . onReply ?.( reply ) ;
215- if ( proposals && proposals . length > 0 ) {
216- this . onProposals ?.( proposals ) ;
217- }
195+ try {
196+ const senderContext = isFromUser
197+ ? `[Message from user: ${ msg . from } ]`
198+ : `[Message from agent: ${ msg . from } ]` ;
199+ const enrichedContent = `${ senderContext } \n${ msg . content } ` ;
200+
201+ const context = await this . fetchContext ( ) ;
202+ const { reply, actions, proposals } = await this . think ( enrichedContent , context ) ;
203+ await this . executeActions ( actions , context ) ;
204+ await this . postReplyTo ( msg . from , reply ) ;
205+ if ( isFromUser ) {
206+ this . onReply ?.( reply ) ;
207+ if ( proposals && proposals . length > 0 ) {
208+ this . onProposals ?.( proposals ) ;
218209 }
219- // Compact log: inbound + reply on two lines
220- ui . chatExchange ( msg . from , msg . content , reply , actions . length ) ;
221- } catch ( err ) {
222- ui . chatExchange ( msg . from , msg . content , `Error: ${ err } ` , 0 ) ;
223- await this . postReplyTo ( msg . from , "Sorry, an error occurred while processing your message. Please try again." ) . catch ( ( ) => { } ) ;
224210 }
225-
226- this . lastSeenId = msg . id ;
211+ // Compact log: inbound + reply on two lines
212+ ui . chatExchange ( msg . from , msg . content , reply , actions . length ) ;
213+ } catch ( err ) {
214+ ui . chatExchange ( msg . from , msg . content , `Error: ${ err } ` , 0 ) ;
215+ await this . postReplyTo ( msg . from , "Sorry, an error occurred while processing your message. Please try again." ) . catch ( ( ) => { } ) ;
227216 }
228- } catch ( err ) {
229- ui . debug ( "manager" , `Poll error: ${ err } ` ) ;
230- } finally {
231- this . processing = false ;
217+
218+ this . lastSeenId = msg . id ;
232219 }
233220 }
234221
0 commit comments