* Drain pending terminal notifications for one owner workspace: defer (leave pending) when the * owner is busy/queued/preparing, otherwise send one coalesced synthetic wake-up and mark the * drained notifications delivered. Stale (deleted-workspace) notifications are marked superseded.
(ownerWorkspaceId: string)
| 4506 | * drained notifications delivered. Stale (deleted-workspace) notifications are marked superseded. |
| 4507 | */ |
| 4508 | private async drainTerminalAttention(ownerWorkspaceId: string): Promise<void> { |
| 4509 | const pending = await this.terminalAttentionStore.listPending(ownerWorkspaceId); |
| 4510 | if (pending.length === 0) { |
| 4511 | return; |
| 4512 | } |
| 4513 | |
| 4514 | const cfg = this.config.loadConfigOrDefault(); |
| 4515 | const entry = findWorkspaceEntry(cfg, ownerWorkspaceId); |
| 4516 | if (entry == null) { |
| 4517 | // Owner workspace no longer exists: the terminal artifacts remain retrievable elsewhere. |
| 4518 | for (const notification of pending) { |
| 4519 | await this.terminalAttentionStore.markSuperseded(ownerWorkspaceId, notification.id); |
| 4520 | } |
| 4521 | return; |
| 4522 | } |
| 4523 | |
| 4524 | // Defer-until-idle: never inject ahead of an active stream or a queued/preparing user turn. |
| 4525 | const ownerHasPendingQueuedPreparingOrRetry = |
| 4526 | this.workspaceService.hasPendingQueuedOrPreparingTurn(ownerWorkspaceId); |
| 4527 | const ownerHasBusyQueuedOrRetry = |
| 4528 | this.workspaceService.isBusyForMessage(ownerWorkspaceId) || |
| 4529 | this.workspaceService.hasQueuedMessages(ownerWorkspaceId) || |
| 4530 | ownerHasPendingQueuedPreparingOrRetry; |
| 4531 | if ( |
| 4532 | this.aiService.isStreaming(ownerWorkspaceId) || |
| 4533 | ownerHasPendingQueuedPreparingOrRetry || |
| 4534 | this.interruptedParentWorkspaceIds.has(ownerWorkspaceId) |
| 4535 | ) { |
| 4536 | if (ownerHasBusyQueuedOrRetry && !this.interruptedParentWorkspaceIds.has(ownerWorkspaceId)) { |
| 4537 | this.scheduleTerminalAttentionDrainAfterIdle(ownerWorkspaceId); |
| 4538 | } |
| 4539 | return; |
| 4540 | } |
| 4541 | |
| 4542 | const taskIndex = this.buildAgentTaskIndex(cfg); |
| 4543 | if (await this.hasBlockingActiveWorkForTerminalDrain(ownerWorkspaceId, taskIndex)) { |
| 4544 | return; |
| 4545 | } |
| 4546 | |
| 4547 | const injectedNotifications = pending.filter((n) => n.outputDelivery === "already_injected"); |
| 4548 | const injectedTaskIds = injectedNotifications.map((n) => n.sourceId); |
| 4549 | const awaitHandleIds = pending |
| 4550 | .filter((n) => n.outputDelivery === "requires_task_await") |
| 4551 | .map((n) => n.sourceId); |
| 4552 | const workflowNotifications = pending.filter( |
| 4553 | (n) => n.outputDelivery === "workflow_result_context" |
| 4554 | ); |
| 4555 | const anyInjectedFailure = injectedNotifications.some( |
| 4556 | (n) => n.terminalOutcome === "failed" || n.terminalOutcome === "error" |
| 4557 | ); |
| 4558 | |
| 4559 | const promptSections: string[] = []; |
| 4560 | if (injectedTaskIds.length > 0) { |
| 4561 | promptSections.push( |
| 4562 | anyInjectedFailure |
| 4563 | ? FAILED_BACKGROUND_SUBAGENT_HANDOFF_PROMPT |
| 4564 | : COMPLETED_BACKGROUND_SUBAGENT_HANDOFF_PROMPT |
| 4565 | ); |
no test coverage detected