()
| 383 | } |
| 384 | |
| 385 | private async backfillMessages(): Promise<void> { |
| 386 | if (this.backfillInFlight) { |
| 387 | await this.backfillInFlight |
| 388 | return |
| 389 | } |
| 390 | |
| 391 | const startSeq = this.incomingFilter.cursorSeq() |
| 392 | if (startSeq === null) { |
| 393 | logger.debug('[API] Skipping backfill because no last-seen message sequence is available') |
| 394 | return |
| 395 | } |
| 396 | |
| 397 | const limit = 200 |
| 398 | const run = async () => { |
| 399 | let cursor = startSeq |
| 400 | while (true) { |
| 401 | const response = await axios.get( |
| 402 | `${configuration.apiUrl}/cli/sessions/${encodeURIComponent(this.sessionId)}/messages`, |
| 403 | { |
| 404 | params: { afterSeq: cursor, limit }, |
| 405 | headers: buildHubRequestHeaders({ |
| 406 | Authorization: `Bearer ${this.token}`, |
| 407 | 'Content-Type': 'application/json' |
| 408 | }), |
| 409 | timeout: 15_000 |
| 410 | } |
| 411 | ) |
| 412 | |
| 413 | const parsed = CliMessagesResponseSchema.safeParse(response.data) |
| 414 | if (!parsed.success) { |
| 415 | throw apiValidationError('Invalid /cli/sessions/:id/messages response', response) |
| 416 | } |
| 417 | |
| 418 | const messages = parsed.data.messages |
| 419 | if (messages.length === 0) { |
| 420 | break |
| 421 | } |
| 422 | |
| 423 | let maxSeq = cursor |
| 424 | for (const message of messages) { |
| 425 | if (typeof message.seq === 'number') { |
| 426 | if (message.seq > maxSeq) { |
| 427 | maxSeq = message.seq |
| 428 | } |
| 429 | } |
| 430 | this.handleIncomingMessage(message) |
| 431 | } |
| 432 | |
| 433 | const observedSeq = this.incomingFilter.cursorSeq() ?? maxSeq |
| 434 | const nextCursor = Math.max(maxSeq, observedSeq) |
| 435 | if (nextCursor <= cursor) { |
| 436 | logger.debug('[API] Backfill stopped due to non-advancing cursor', { |
| 437 | cursor, |
| 438 | maxSeq, |
| 439 | observedSeq |
| 440 | }) |
| 441 | break |
| 442 | } |
no test coverage detected