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.
The supervisor selected strict startup registration with a shared isFirstSync flag. Because the MCP SDK may deliver tools/list_changed before connect() resolves, that notification could enter the sync queue first, consume the strict option inside its contained handler, and leave the actual activation sync non-fatal.
Pass startup intent explicitly to connectGeneration(). Only the plugin activation attempt receives the failOnStartupError registration policy; notification-driven syncs and later reconnect generations always use contained runtime semantics. Queue arrival order can no longer redefine startup behavior.
A regression test injects list_changed from inside connect(), keeps a foreign namespace squatter in place, and proves activation still rejects after the notification's contained sync. Focused package coverage remains 100%, and the bilingual reconnect note records the ownership rule.
The MCP SDK starts a fire-and-forget close when initialization fails. Its stdio transport clears its process field before that close finishes, so our second Client.close() could return immediately and the reconnect timer could launch a replacement while the original child was still alive.
Track the transport onclose signal for every client generation and gate failed-attempt backoff on both Client.close() settlement and that signal. Use the same barrier during plugin disposal. If the SDK's bounded stdio termination window expires without onclose, fail closed and report incomplete shutdown instead of risking overlapping server processes.
Regression coverage models the SDK's early-returning second close, delayed and missing close signals, pending-connect disposal, close rejection, and the terminal timeout path. The reconnect Agent Note and Chinese counterpart now record the quiescence contract.
Integrate master at 8b4ef532 before addressing review feedback so each bot issue remains isolated in its own descendant commit. Resolve the vendor package rescope by moving the PR's new MCP supervisor imports to @deepseek-ai/cordis and @deepseek-ai/schemastery, preserve the reconnect implementation and e2e coverage, and regenerate the merged config and module catalogs with fresh bilingual pairing records.
A per-instance connection supervisor restarts the original server config
with exponential backoff when the transport closes, re-runs tool discovery
on success, and atomically replaces the previous generation. Default policy
retries for ~2.5 minutes (10 attempts, 500ms→30s doubling) before giving up
and unregistering the server's tools.
New config block reconnect { enabled, initialDelayMs, maxDelayMs, maxAttempts }
on both transports; misconfiguration fails plugin load. A connection that
survives past the stability window (maxDelayMs) resets the attempt budget,
so occasional crashes recover indefinitely while a crash loop still exhausts
the cap.
Integrates with the upstream failOnStartupError: the initial sync uses
registrationFailure:'throw' when that flag is set so a squatted namespace
still rejects activation.
Fixes#1746