diff --git a/internal/cloud/code-agent-agentrun-adapter.ts b/internal/cloud/code-agent-agentrun-adapter.ts index 67742654..86cbf35d 100644 --- a/internal/cloud/code-agent-agentrun-adapter.ts +++ b/internal/cloud/code-agent-agentrun-adapter.ts @@ -2473,6 +2473,7 @@ function mapAgentRunEvent(event, mapping = {}) { function agentRunProviderRetryEvent({ base = {}, payload = {}, status = "retrying", label = "agentrun:provider-retry" } = {}) { const failureKind = normalizedFailureKind(payload.failureKind ?? payload.errorCode ?? "provider-stream-disconnected"); + const retryMessage = providerRetryMessage(textPayload(payload, "Provider stream disconnected; AgentRun will retry."), payload); return { ...base, type: "provider_retry", @@ -2491,11 +2492,28 @@ function agentRunProviderRetryEvent({ base = {}, payload = {}, status = "retryin nextRetryAt: firstNonEmpty(payload.nextRetryAt) || null, retryOf: payload.retryOf ?? null, terminalStatus: payload.terminalStatus ?? null, - message: textPayload(payload, "Provider stream disconnected; AgentRun will retry."), + message: retryMessage, valuesPrinted: false }; } +function providerRetryMessage(message, payload = {}) { + const attempt = numberOrNull(payload.retryAttempt); + const max = numberOrNull(payload.retryMax); + const delayMs = numberOrNull(payload.retryDelayMs); + const nextRetryAt = firstNonEmpty(payload.nextRetryAt) || null; + const parts = []; + if (attempt !== null && max !== null) parts.push(`retry ${attempt}/${max}`); + else if (attempt !== null) parts.push(`retry attempt ${attempt}`); + else if (max !== null) parts.push(`retry max ${max}`); + if (delayMs !== null) parts.push(`after ${delayMs}ms`); + if (nextRetryAt) parts.push(`next=${nextRetryAt}`); + if (parts.length === 0) return message; + const text = String(message || "Provider stream disconnected; AgentRun will retry.").trim(); + if (attempt !== null && max !== null && text.includes(`${attempt}/${max}`)) return text; + return text.replace(/[.。]\s*$/u, "") + `; ${parts.join(" ")}.`; +} + function isRetryableProviderInterruptionPayload(payload = {}, options = {}) { const kind = normalizedFailureKind(payload?.failureKind ?? payload?.errorCode ?? payload?.code); if (kind === "provider-stream-disconnected") return payload?.willRetry === true; diff --git a/internal/cloud/workbench-turn-projection.ts b/internal/cloud/workbench-turn-projection.ts index d81dd0a8..7d77b82f 100644 --- a/internal/cloud/workbench-turn-projection.ts +++ b/internal/cloud/workbench-turn-projection.ts @@ -117,12 +117,17 @@ export function projectionDiagnostics({ traceId = null, projection = null, resul const turn = projection ?? createWorkbenchTurnProjection({ traceId, result, trace }); const sealedCompleted = turn.terminal === true && turn.status === "completed" && Boolean(turn.finalResponse?.text); const source = sealedCompleted ? null : projectionDiagnosticSource(trace); - const blocker = sealedCompleted ? null : refreshError ?? source?.blocker ?? trace?.blocker ?? result?.blocker ?? result?.error ?? null; + const rawBlocker = sealedCompleted ? null : refreshError ?? source?.blocker ?? trace?.blocker ?? result?.blocker ?? result?.error ?? null; + const retryingProviderInterruption = !sealedCompleted && turn.running === true ? retryableProviderInterruptionEvidence(rawBlocker) : null; + const blocker = retryingProviderInterruption ? null : rawBlocker; const hasProjectionInput = hasTraceProjection(trace) || Boolean(result || result?.agentRun); const sourceStatus = sealedCompleted ? null : normalizeProjectionStatus(source?.projectionStatus ?? trace?.projectionStatus); - const status = sealedCompleted ? "caught-up" : sourceStatus ?? (blocker ? "blocked" : turn.terminal ? "caught-up" : hasProjectionInput ? "projecting" : "unknown"); + const effectiveSourceStatus = retryingProviderInterruption && sourceStatus === "blocked" ? "projecting" : sourceStatus; + const status = sealedCompleted ? "caught-up" : effectiveSourceStatus ?? (blocker ? "blocked" : turn.terminal ? "caught-up" : hasProjectionInput ? "projecting" : "unknown"); const diagnostic = blocker ? diagnosticBlocker(blocker) : null; - const projectionHealth = sealedCompleted ? "healthy" : normalizeProjectionHealth(source?.projectionHealth ?? trace?.projectionHealth) + const sourceHealth = sealedCompleted ? null : normalizeProjectionHealth(source?.projectionHealth ?? trace?.projectionHealth); + const effectiveSourceHealth = retryingProviderInterruption && (sourceHealth === "degraded" || sourceHealth === "unavailable" || sourceHealth === "stalled") ? "projecting" : sourceHealth; + const projectionHealth = sealedCompleted ? "healthy" : effectiveSourceHealth ?? projectionHealthFor({ status, turn, hasProjectionInput, blocker: diagnostic }); const staleMs = sealedCompleted ? null : projectionStaleMs(source?.staleMs ?? trace?.staleMs, turn.updatedAt ?? trace?.updatedAt ?? result?.updatedAt); return { @@ -133,6 +138,7 @@ export function projectionDiagnostics({ traceId = null, projection = null, resul sourceCommandId: turn.sourceCommandId ?? null, staleMs, blocker: diagnostic, + retryingProviderInterruption, updatedAt: turn.updatedAt ?? null, valuesRedacted: true };