fix(native-chat): a new chat in a twin folder, or a twin with no copy picked, refuses instead of starting on this computer (#30838)

This commit is contained in:
Neil
2026-10-19 13:58:12 -07:00
committed by GitHub
parent b0c55557f0
commit 7bff50b457
2 changed files with 52 additions and 3 deletions
@@ -7,7 +7,7 @@ import {
import type { Tab } from '../../../shared/tab-types'
import { getExecutionHostIdForWorktree } from '@/lib/worktree-runtime-owner'
import type { WorktreeRuntimeOwnerState } from '@/lib/worktree-runtime-owner-state'
import { resolveIndexedWorktreeOwner } from '@/lib/worktree-runtime-owner-index'
import { isTwinWorkspace, namedHostForOwnerRef } from '@/lib/resolve-owner'
import { LOCAL_STRUCTURED_SESSION_OWNER } from './local-structured-session-owner'
import { runtimeRpcTargetForAuthorityKey } from './runtime-client-target'
import type { RuntimeClientTarget } from '../../../shared/runtime-client-target'
@@ -33,9 +33,11 @@ export function resolveStructuredAgentSessionOwner(
state: WorktreeRuntimeOwnerState,
worktreeId: string
): ExecutionHostId | null {
// Why resolveOwner's twin rule: an id-only check missed folder twins and an active twin with no
// host picked, and the fallback below reads both as this machine.
if (
state.activeWorktreeId !== worktreeId &&
resolveIndexedWorktreeOwner(state.worktreesByRepo, worktreeId).kind === 'ambiguous'
!namedHostForOwnerRef(state, { workspaceId: worktreeId }) &&
isTwinWorkspace(state, worktreeId)
) {
return null
}
@@ -45,6 +45,8 @@ const { resolveNativeChatSkillDiscoveryContext, selectNativeChatSkillStateInputs
await import('@/components/native-chat/native-chat-skill-discovery-context')
const { mirroredRuntimeEnvironmentIdForWorkspace } =
await import('@/runtime/web-session-tabs-sync/mirrored-workspace-owner')
const { resolveStructuredAgentSessionOwner } =
await import('@/runtime/structured-agent-session-owner')
const RUNTIME_HOST: ExecutionHostId = `runtime:${TWIN_RUNTIME_ENV}`
@@ -213,6 +215,51 @@ describe('folder workspace twin', () => {
)
})
describe('a new structured chat', () => {
it.each<HostTwinKind>(['folder local+ssh', 'folder group local+ssh'])(
'refuses a %s twin instead of starting on this computer',
(kind) => {
const { store, workspaceKey } = bind(createHostTwinFixture(kind))
expect(resolveStructuredAgentSessionOwner(store.getState(), workspaceKey)).toBeNull()
store.setState({ activeWorktreeId: workspaceKey, activeWorkspaceExecutionHostId: null })
expect(resolveStructuredAgentSessionOwner(store.getState(), workspaceKey)).toBeNull()
}
)
it('refuses an active worktree twin with no copy picked, and starts on the picked one', () => {
const { store, workspaceKey } = bind(createHostTwinFixture('local+runtime'))
store.setState({ activeWorktreeId: workspaceKey, activeWorkspaceExecutionHostId: null })
expect(resolveStructuredAgentSessionOwner(store.getState(), workspaceKey)).toBeNull()
store.setState({ activeWorkspaceExecutionHostId: RUNTIME_HOST })
expect(resolveStructuredAgentSessionOwner(store.getState(), workspaceKey)).toBe(RUNTIME_HOST)
})
it("starts on the copy the user picked, and on a lone unstamped folder's computer", () => {
const { store, workspaceKey, projectId } = bind(createHostTwinFixture('folder local+ssh'))
store.setState({ activeWorktreeId: workspaceKey, activeWorkspaceExecutionHostId: 'local' })
expect(resolveStructuredAgentSessionOwner(store.getState(), workspaceKey)).toBe('local')
store.setState({
activeWorktreeId: null,
activeWorkspaceExecutionHostId: null,
projectGroups: [],
folderWorkspaces: [makeFolderWorkspace({ id: projectId, folderPath: '/work/twin' })]
})
expect(resolveStructuredAgentSessionOwner(store.getState(), workspaceKey)).toBe('local')
})
it('starts a lone unstamped worktree on this computer', () => {
const fixture = bind(createHostTwinFixture('local+ssh'))
const { store, workspaceKey, projectId } = fixture
store.setState({
repos: store.getState().repos.filter((repo) => !repo.connectionId),
worktreesByRepo: {
[projectId]: [makeWorktree({ id: workspaceKey, repoId: projectId, path: '/work/twin' })]
}
})
expect(resolveStructuredAgentSessionOwner(store.getState(), workspaceKey)).toBe('local')
})
})
describe('a workspace selected on the unresolved-owner sentinel', () => {
it('neither reopens a terminal locally nor uploads an attachment', () => {
const { store, workspaceKey } = runtimeOnly()