(event: ErrorEvent)
| 8411 | } |
| 8412 | |
| 8413 | private async handleTaskStreamError(event: ErrorEvent): Promise<void> { |
| 8414 | if (await this.finalizeWorkspaceTurnFromStreamError(event)) { |
| 8415 | return; |
| 8416 | } |
| 8417 | const workspaceId = event.workspaceId; |
| 8418 | const cfg = this.config.loadConfigOrDefault(); |
| 8419 | const entry = findWorkspaceEntry(cfg, workspaceId); |
| 8420 | if (!entry?.workspace.parentWorkspaceId) { |
| 8421 | return; |
| 8422 | } |
| 8423 | |
| 8424 | const status = entry.workspace.taskStatus; |
| 8425 | // Stream errors only need settlement handling while the task is mid-run |
| 8426 | // (running) or waiting on its completion tool (awaiting_report). |
| 8427 | if (status !== "running" && status !== "awaiting_report") { |
| 8428 | return; |
| 8429 | } |
| 8430 | const taskIndex = this.buildAgentTaskIndex(cfg); |
| 8431 | |
| 8432 | if ( |
| 8433 | await this.interruptTaskRecoveryForInactiveWorkflowOwner( |
| 8434 | workspaceId, |
| 8435 | cfg, |
| 8436 | "stream-error", |
| 8437 | taskIndex |
| 8438 | ) |
| 8439 | ) { |
| 8440 | return; |
| 8441 | } |
| 8442 | |
| 8443 | if (await this.hasActiveTaskOwnedWork(workspaceId, taskIndex)) { |
| 8444 | return; |
| 8445 | } |
| 8446 | |
| 8447 | const isNonRetryable = |
| 8448 | event.errorType != null && isNonRetryableStreamError({ type: event.errorType }); |
| 8449 | |
| 8450 | // Terminal provider outcomes (e.g. model_refusal) settle the task even during its |
| 8451 | // first `running` turn — previously only awaiting_report settled, leaving the |
| 8452 | // parent's waitForAgentReport to block until timeout. Deliberately an allow-list |
| 8453 | // rather than "all non-retryable": |
| 8454 | // - `aborted` is a steerable user pause, not a terminal failure. |
| 8455 | // - `context_exceeded` has in-session recovery (compaction retry, post-compaction |
| 8456 | // retry, exec-subagent hard restart in AgentSession.handleStreamError) listening |
| 8457 | // on the same error event; settling here would race that recovery and interrupt |
| 8458 | // a child that was about to continue. |
| 8459 | const settlesRunningTask = |
| 8460 | event.errorType != null && RUNNING_TASK_TERMINAL_STREAM_ERRORS.has(event.errorType); |
| 8461 | |
| 8462 | if (isNonRetryable && (status === "awaiting_report" || settlesRunningTask)) { |
| 8463 | log.error("Task hit a non-retryable stream error; interrupting task", { |
| 8464 | workspaceId, |
| 8465 | taskStatus: status, |
| 8466 | errorType: event.errorType, |
| 8467 | error: event.error, |
| 8468 | }); |
| 8469 | await this.failAgentTaskTerminally(workspaceId, entry, { |
| 8470 | errorType: event.errorType ?? "unknown", |
no test coverage detected