fix(mcp-client): distinguish failure from loss

The reconnect supervisor used connection lost for every transition into backoff, including an initial startup attempt that never established a connection and later retry attempts that also failed. That wording implied a previously healthy generation and obscured whether any tools had ever been registered.

Capture whether the generation had reached the established state before scheduling recovery. Established disconnects retain connection lost/reconnecting; startup and retry failures now report connection failed/retrying. The reconnect-disabled diagnostic uses the same distinction while preserving its concrete manual-recovery guidance.

Unit assertions cover established loss, initial failure, retry failure, and both reconnect-disabled branches. Focused package coverage remains 100%, and the bilingual Agent Note records the observable state vocabulary.
This commit is contained in:
Tianyi Cui
2026-08-11 00:05:06 +08:00
parent 0e01036a2a
commit e2556c51bf
5 changed files with 18 additions and 12 deletions
+7 -5
View File
@@ -190,11 +190,12 @@ export function startConnection(ctx: Context, config: Config, policy: ResolvedRe
}
function scheduleReconnect(): void {
const lostEstablishedConnection = connectedAt !== undefined
if (!policy.enabled) {
const detail = connectedAt !== undefined
? 'registered tools will fail until an HMR reload or Host restart'
: 'no tools were registered; reload the plugin or restart the Host to connect'
ctx.logger.error(`${label}: connection lost and reconnect is disabled — ${detail}`)
const message = lostEstablishedConnection
? 'connection lost and reconnect is disabled — registered tools will fail until an HMR reload or Host restart'
: 'connection failed and reconnect is disabled — no tools were registered; reload the plugin or restart the Host to connect'
ctx.logger.error(`${label}: ${message}`)
return
}
// A connection that stayed up past the stability window (= maxDelayMs, the
@@ -213,7 +214,8 @@ export function startConnection(ctx: Context, config: Config, policy: ResolvedRe
return
}
const delayMs = Math.min(policy.maxDelayMs, policy.initialDelayMs * 2 ** (failedAttempts - 1))
ctx.logger.warn(`${label}: connection lost; reconnecting in ${delayMs}ms (attempt ${failedAttempts}/${policy.maxAttempts})`)
const action = lostEstablishedConnection ? 'connection lost; reconnecting' : 'connection failed; retrying'
ctx.logger.warn(`${label}: ${action} in ${delayMs}ms (attempt ${failedAttempts}/${policy.maxAttempts})`)
reconnectTimer = setTimeout(() => {
reconnectTimer = undefined
settling = connectGeneration(false)