()
| 550 | // at end of run; scanning only the delta (response.newEvents) is O(new). |
| 551 | let cachedReviewContent: string | null = null; |
| 552 | const poll = async (): Promise<void> => { |
| 553 | if (!isRunning) return; |
| 554 | try { |
| 555 | const appState = context.getAppState(); |
| 556 | const task = appState.tasks?.[taskId] as RemoteAgentTaskState | undefined; |
| 557 | if (!task || task.status !== 'running') { |
| 558 | // Task was killed externally (TaskStopTool) or already terminal. |
| 559 | // Session left alive so the claude.ai URL stays valid — the run_hunt.sh |
| 560 | // post_stage() calls land as assistant events there, and the user may |
| 561 | // want to revisit them after closing the terminal. TTL reaps it. |
| 562 | return; |
| 563 | } |
| 564 | const response = await pollRemoteSessionEvents(task.sessionId, lastEventId); |
| 565 | lastEventId = response.lastEventId; |
| 566 | const logGrew = response.newEvents.length > 0; |
| 567 | if (logGrew) { |
| 568 | accumulatedLog = [...accumulatedLog, ...response.newEvents]; |
| 569 | const deltaText = response.newEvents.map(msg => { |
| 570 | if (msg.type === 'assistant') { |
| 571 | return msg.message.content.filter(block => block.type === 'text').map(block => 'text' in block ? block.text : '').join('\n'); |
| 572 | } |
| 573 | return jsonStringify(msg); |
| 574 | }).join('\n'); |
| 575 | if (deltaText) { |
| 576 | appendTaskOutput(taskId, deltaText + '\n'); |
| 577 | } |
| 578 | } |
| 579 | if (response.sessionStatus === 'archived') { |
| 580 | updateTaskState<RemoteAgentTaskState>(taskId, context.setAppState, t => t.status === 'running' ? { |
| 581 | ...t, |
| 582 | status: 'completed', |
| 583 | endTime: Date.now() |
| 584 | } : t); |
| 585 | enqueueRemoteNotification(taskId, task.title, 'completed', context.setAppState, task.toolUseId); |
| 586 | void evictTaskOutput(taskId); |
| 587 | void removeRemoteAgentMetadata(taskId); |
| 588 | return; |
| 589 | } |
| 590 | const checker = completionCheckers.get(task.remoteTaskType); |
| 591 | if (checker) { |
| 592 | const completionResult = await checker(task.remoteTaskMetadata); |
| 593 | if (completionResult !== null) { |
| 594 | updateTaskState<RemoteAgentTaskState>(taskId, context.setAppState, t => t.status === 'running' ? { |
| 595 | ...t, |
| 596 | status: 'completed', |
| 597 | endTime: Date.now() |
| 598 | } : t); |
| 599 | enqueueRemoteNotification(taskId, completionResult, 'completed', context.setAppState, task.toolUseId); |
| 600 | void evictTaskOutput(taskId); |
| 601 | void removeRemoteAgentMetadata(taskId); |
| 602 | return; |
| 603 | } |
| 604 | } |
| 605 | |
| 606 | // Ultraplan: result(success) fires after every CCR turn, so it must not |
| 607 | // drive completion — startDetachedPoll owns that via ExitPlanMode scan. |
| 608 | // Long-running monitors (autofix-pr) emit result per notification cycle, |
| 609 | // so the same skip applies. |
no test coverage detected