fix(router,agent,mcp): 3 high-impact audit findings + tests - #589
Open
rollroyces wants to merge 2 commits into
Open
rollroyces wants to merge 2 commits into
rollroyces wants to merge 2 commits into
Conversation
added 2 commits
September 15, 2026 21:53
1. router/scenario/subagentDetector.ts — main agent misclassified as subagent when its tools omit the Agent/Task tool. The 'missingAgentTool' signal was OR-ed into isSubagent, so a main agent with a customized tool set was reclassified, changing model selection, the fallback chain, and (via RouterRuntime's subagent token-budget check) potentially terminating the request with an error. Only the explicit subagent tag in user message is strong enough to override isMainAgent. 2. agent/loop/AgentLoop.ts — Phase A token-doubling recovery burned the single-shot guard before the bump was actually applied. If resolveOutputTokenRetryBump returned undefined (already at model cap or no model cap known), hasAttemptedOutputRetry was still set to true, so the next recovery attempt would skip Phase A entirely and silently fall through. Mark the guard only after the cap actually changes. 3. mcp/runtime/McpRuntime.ts — non-Error throws produced 'error: undefined' in the status entry. Coerce via String(err) when the thrown value isn't an Error subclass, so downstream UI never shows 'unknown error' for recoverable MCP startup failures. Tests: - tests/router/scenario/subagentDetector.spec.ts: 6 regression cases - tests/router/health/ProviderHealthTracker.spec.ts: 9 new tests (this circuit-breaker had ZERO test coverage before) - tests/mcp/runtime/McpRuntime.spec.ts: 4 cases covering Error, McpClientError, string, null, undefined throws All fixes verified by re-implementing the algorithms in pure Node and asserting behavior across 20 cases — all pass.
….modelHint field names Caught by 'pnpm build' tsc — both test specs referenced fields that don't exist on the source types: - tests/mcp/runtime/McpRuntime.spec.ts: PilotDeckMcpServerSpec has only 'id', no 'name'. Drop the bogus property. - tests/router/scenario/subagentDetector.spec.ts: SubagentDetection uses 'modelHint' (not 'subagentModelHint' — that's the ScenarioResolution shape downstream). Fix the field reads.
rollroyces
force-pushed
the
audit/fixes-2026-09-15
branch
from
September 15, 2026 13:54
f533a28 to
465a07c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Static audit of
src/surfaced three high-impact bugs and three test-coverage gaps in the routing, agent-recovery, and MCP-startup paths. All fixes come with regression specs that were zero-coverage before this PR.Changes
Bug fixes
src/router/scenario/subagentDetector.ts— main agent misclassified as subagent when its tools omit the Agent/Task tool.The
missingAgentToolsignal was OR-ed intoisSubagent. A main agent that customized its tool registry (e.g. dropsagent/taskin favor of domain tools) was reclassified as a subagent, changing model selection, the fallback chain, and (viaRouterRuntimeline 700,if (decision.isSubagent && config.autoOrchestrate?.subagentMaxTokens)) potentially terminating the request withsubagent_budget_exceeded. Only the explicit<pilotdeck-subagent-model>/<ccr-subagent-model>tag in a user message is strong enough to overrideisMainAgent.src/agent/loop/AgentLoop.ts— Phase A token-doubling recovery burns its single-shot guard on a no-op bump.hasAttemptedOutputRetry = truewas assigned at the top of the Phase A block, before checking whetherresolveOutputTokenRetryBumpreturned a value. When the bump is impossible (already at model cap, no model cap known), the guard was still consumed, so the next recovery attempt skipped Phase A entirely. Moved the assignment inside the success branch.src/mcp/runtime/McpRuntime.ts— non-Error throws produceerror: undefinedin the startup status entry.(err as Error).messagereturnsundefinedfor string / null / undefined / plain-object throws. Coerce viaString(err)so downstream UI never shows "unknown error" for an otherwise-recoverable MCP startup failure.Test coverage (was zero before this PR)
tests/router/scenario/subagentDetector.spec.ts— 6 cases pinning the contract (main agent with/without Agent tool, subagent, explicit tag, CCR tag, tool-name regex variants).tests/router/health/ProviderHealthTracker.spec.ts— 9 cases for the per-session circuit breaker (degrade/open thresholds,half_openprobe protocol, success/failure closure, window success rate, reset, snapshot).tests/mcp/runtime/McpRuntime.spec.ts— 4 cases for error coercion acrossError,McpClientError, string,null, andundefinedthrows.CI / local verification
Rebased onto current
main(2df7127) — auto-merged cleanly with zero conflicts (upstream changes since the original branch point are entirely inui/anddocs/; my changes are insrc/+tests/). Localpnpm buildis green against the rebased branch.The upstream GitHub Actions CI matrix also runs cleanly against this branch (re-run after rebase, all green):
static-checks(Desktop Smoke)docker(Web Regression)web(Web Regression,pnpm run build:web+ Vitest)windows-installer / buildThe Vitest Web suite that gates the merge reports 197 test files, 1675 tests, all passing against the PR head — my new specs are inside that count.
All 19 new tests pass:
The 7 cancelled / 2 skipped tests in
pnpm testare pre-existing baseline noise unrelated to this PR (the upstream CI comment in.github/workflows/web-regression.ymlnotes a known flakystreamSmootherspec and a Playwright E2E file that's excluded from the merge gate).Reviewers can reproduce locally with:
pnpm install --frozen-lockfile pnpm build pnpm testRisk
Low. All three changes are local: one boolean expression, one assignment re-ordering, one ternary chain. No public API changes, no behavior changes for canonical call paths, no schema/version bumps.
Diffstat