MCPcopy
hub / github.com/coder/mux / drainTerminalAttention

Method drainTerminalAttention

src/node/services/taskService.ts:4508–4665  ·  view source on GitHub ↗

* 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)

Source from the content-addressed store, hash-verified

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 );

Callers 2

Tested by

no test coverage detected