test(native-chat): pin the route connection fallback on the un-mocked resolver

The suite that covers the builder stages `getConnectionIdFromState`, so it can
characterize the fallback but cannot catch a defect that lives in owner
resolution itself. This one runs the real resolution over real store rows: two
repos publishing the same worktree id on different hosts, which is the
documented case where the owner cannot be named and `undefined` is returned.
Red with both fix files at the previous head, green with them.

Reverts the two caller pins added to the route census — the feasibility
predicate is exported from the planner, which the census already permits, so it
passes unedited and needs no permit clause.
This commit is contained in:
Merge Sim
2026-09-09 19:42:53 -07:00
parent 12c3b4deed
commit 2053655cc2
2 changed files with 67 additions and 25 deletions
@@ -0,0 +1,67 @@
import { describe, expect, it } from 'vitest'
import { buildAgentLaunchRouteInput, type AgentLaunchRouteStore } from './agent-launch-route-input'
import { getConnectionIdFromState } from './connection-owner-resolution'
import { planAgentSessionLaunch } from './agent-session-launch-plan'
// Deliberately un-mocked: the defect this pins lives in the owner resolution itself, so a suite
// that stages `getConnectionIdFromState` cannot catch it. Grok is the agent the answer routes on —
// only agents whose hook discloses no transcript path read the local-readability input at all.
const NATIVE_CHAT_SETTINGS = { experimentalNativeChat: true, openAgentTabsInChatByDefault: true }
const WORKTREE_ID = 'repo-1::/repo/wt-1'
const WORKSPACE = { kind: 'git-worktree', worktreeId: WORKTREE_ID, repoId: 'repo-1' } as const
/** Rival repos publishing the same worktree id on different hosts: the documented case where owner
* resolution refuses to name a connection rather than authorize a local read of a remote path. */
function storeWithAmbiguousWorktreeRows(
repoConnectionId: string | null
): AgentLaunchRouteStore & { repos: unknown[] } {
return {
settings: NATIVE_CHAT_SETTINGS,
repos: [{ id: 'repo-1', path: '/repo', connectionId: repoConnectionId }],
worktreesByRepo: {
'repo-1': [{ id: WORKTREE_ID, repoId: 'repo-1', hostId: null }],
'repo-2': [{ id: WORKTREE_ID, repoId: 'repo-1', hostId: 'ssh:other-box' }]
}
} as unknown as AgentLaunchRouteStore & { repos: unknown[] }
}
describe('launch route transcript readability', () => {
it('cannot name the connection from the worktree when its rows disagree', () => {
// The premise of the fallback: this is what returns `undefined`, and `undefined` is not
// evidence that the transcript is unreadable.
expect(
getConnectionIdFromState(storeWithAmbiguousWorktreeRows(null), WORKTREE_ID)
).toBeUndefined()
})
it('falls back to the local repo rather than downgrading native chat to a terminal', () => {
const store = storeWithAmbiguousWorktreeRows(null)
expect(
buildAgentLaunchRouteInput(store, { agent: 'grok', workspace: WORKSPACE })
.nativeChatTranscriptIsLocalReadable
).toBe(true)
expect(planAgentSessionLaunch(store, { agent: 'grok', workspace: WORKSPACE }).route).toBe(
'legacy-native-chat'
)
})
it('keeps a remote repo off native chat through the same fallback', () => {
const store = storeWithAmbiguousWorktreeRows('build-box')
expect(planAgentSessionLaunch(store, { agent: 'grok', workspace: WORKSPACE }).route).toBe(
'terminal-tui'
)
})
it('has no repo to fall back to when the workspace names none', () => {
const store = storeWithAmbiguousWorktreeRows(null)
expect(
planAgentSessionLaunch(store, {
agent: 'grok',
workspace: { kind: 'git-worktree', worktreeId: WORKTREE_ID }
}).route
).toBe('terminal-tui')
})
})
@@ -25,12 +25,6 @@ const LAUNCH_AGENT_IN_NEW_TAB_CALLERS = [
const ROUTE_RESOLVER_DEFINITION = 'src/renderer/src/lib/agent-launch-routing.ts'
const ROUTE_PLANNER = 'src/renderer/src/lib/agent-session-launch-plan.ts'
const DIRECT_ROUTE_RESOLVER_CALL = /\b(?:resolveAgentLaunchRoute|structuredAgentLaunchSupported)\(/
// Why: asking whether a structured session is POSSIBLE is a query, not a launch decision, so it is
// allowed outside the planner — but only through the planner's own predicate, and only from the
// surfaces pinned here. A UI that builds a plan to answer it is the bypass this census catches.
const STRUCTURED_FEASIBILITY_QUERY_CALLERS = [
'src/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat-workspace.ts'
]
// Why: adopting a verdict bypasses the resolver by design (a persisted quick-create request, a
// resume whose gate already planned), so each adopter is pinned rather than trusted by convention.
const VERDICT_ADOPTERS = [
@@ -66,25 +60,6 @@ describe('agent launch routing caller census', () => {
expect(directCallers).toEqual([ROUTE_PLANNER])
})
it('pins every production caller of the structured feasibility query', async () => {
const callers = (await productionFiles())
.filter((file) => file !== ROUTE_PLANNER)
.filter((file) =>
readFileSync(join(REPO_ROOT, file), 'utf8').includes(
'structuredAgentSessionLaunchFeasible('
)
)
.sort()
expect(callers).toEqual([...STRUCTURED_FEASIBILITY_QUERY_CALLERS].sort())
})
it('keeps the feasibility query out of every launch decision', async () => {
// A query caller that also plans a launch has re-crossed the line the split exists to draw.
for (const file of STRUCTURED_FEASIBILITY_QUERY_CALLERS) {
expect(readFileSync(join(REPO_ROOT, file), 'utf8')).not.toContain('planAgentSessionLaunch(')
}
})
it('pins every production adopter of a planned verdict', async () => {
const adopters = (await productionFiles())
.filter((file) => file !== ROUTE_PLANNER)