fix(skills): trust SSH PTY pane mirrors

This commit is contained in:
Brennan Benson
2026-08-05 18:50:14 -07:00
parent 3e342302ac
commit 272bb1a8e6
2 changed files with 87 additions and 8 deletions
@@ -38,10 +38,14 @@ function worktreeMeta(hostId: WorktreeMeta['hostId']): WorktreeMeta {
}
}
function session(worktreeId: string, startupCwd?: string): WorkspaceSessionState {
function session(
worktreeId: string,
startupCwd?: string,
ptyId: string | null = null
): WorkspaceSessionState {
return {
tabsByWorktree: {
[worktreeId]: [{ id: 'tab-1', startupCwd }]
[worktreeId]: [{ id: 'tab-1', ptyId, startupCwd }]
}
} as WorkspaceSessionState
}
@@ -83,6 +87,74 @@ describe('resolvePaneSkillDiscoveryWorkspace', () => {
).toEqual({ connectionId: 'target-1', cwd: '/work/demo-project' })
})
it('accepts a legacy local SSH PTY mirror that omits its startup directory', () => {
const worktreeId = 'repo-1::/work/demo-project'
const ptyId = 'ssh:target-1@@pty-1'
expect(
resolvePaneSkillDiscoveryWorkspace({
worktreeId,
terminalTabId: 'tab-1',
repos: [repo({ path: '/work/demo-project' })],
projectGroups: [],
folderWorkspaces: [],
worktreeMeta: worktreeMeta('ssh:target-1'),
sessions: [
{ hostId: 'local', session: session(worktreeId, undefined, ptyId) },
{
hostId: 'ssh:target-1',
session: session(worktreeId, '/work/demo-project', ptyId)
}
]
})
).toEqual({ connectionId: 'target-1', cwd: '/work/demo-project' })
})
it('rejects missing startup-directory mirrors with different PTY identities', () => {
const worktreeId = 'repo-1::/work/demo-project'
expect(() =>
resolvePaneSkillDiscoveryWorkspace({
worktreeId,
terminalTabId: 'tab-1',
repos: [repo({ path: '/work/demo-project' })],
projectGroups: [],
folderWorkspaces: [],
worktreeMeta: worktreeMeta('ssh:target-1'),
sessions: [
{ hostId: 'local', session: session(worktreeId, undefined, 'ssh:target-1@@pty-1') },
{
hostId: 'ssh:target-1',
session: session(worktreeId, '/work/demo-project', 'ssh:target-1@@pty-2')
}
]
})
).toThrow('pane_skill_discovery_owner_ambiguous')
})
it('rejects a matching PTY identity from a different SSH target', () => {
const worktreeId = 'repo-1::/work/demo-project'
const ptyId = 'ssh:target-2@@pty-1'
expect(() =>
resolvePaneSkillDiscoveryWorkspace({
worktreeId,
terminalTabId: 'tab-1',
repos: [repo({ path: '/work/demo-project' })],
projectGroups: [],
folderWorkspaces: [],
worktreeMeta: worktreeMeta('ssh:target-1'),
sessions: [
{ hostId: 'local', session: session(worktreeId, undefined, ptyId) },
{
hostId: 'ssh:target-1',
session: session(worktreeId, '/work/demo-project', ptyId)
}
]
})
).toThrow('pane_skill_discovery_owner_ambiguous')
})
it('accepts normalized Windows variants of a mirrored SSH startup directory', () => {
const worktreeId = 'repo-1::C:\\Repo'
@@ -5,6 +5,7 @@ import {
toSshExecutionHostId,
type ExecutionHostId
} from '../../shared/execution-host'
import { parseAppSshPtyId } from '../../shared/ssh-pty-id'
import type {
FolderWorkspace,
ProjectGroup,
@@ -174,7 +175,7 @@ function findPaneSession(
worktreeId: string,
terminalTabId: string | undefined,
authoritativeHostId?: ExecutionHostId | null
): { hostId: ExecutionHostId; tab: { startupCwd?: string } } | null {
): { hostId: ExecutionHostId; tab: { ptyId: string | null; startupCwd?: string } } | null {
if (!terminalTabId) {
return null
}
@@ -183,7 +184,7 @@ function findPaneSession(
worktreeIdsEqual(candidateWorktreeId, worktreeId)
? tabs
.filter((tab) => tab.id === terminalTabId)
.map((tab) => ({ hostId, tab: { startupCwd: tab.startupCwd } }))
.map((tab) => ({ hostId, tab: { ptyId: tab.ptyId, startupCwd: tab.startupCwd } }))
: []
)
)
@@ -200,7 +201,7 @@ function findPaneSession(
(match) =>
match === authoritativeMatch ||
(match.hostId === LOCAL_EXECUTION_HOST_ID &&
paneSessionsMirror(match.tab, authoritativeMatch.tab))
paneSessionsMirror(match.tab, authoritativeMatch.tab, authoritativeMatch.hostId))
)
) {
return authoritativeMatch
@@ -211,13 +212,19 @@ function findPaneSession(
}
function paneSessionsMirror(
left: { startupCwd?: string },
right: { startupCwd?: string }
left: { ptyId: string | null; startupCwd?: string },
right: { ptyId: string | null; startupCwd?: string },
authoritativeHostId: ExecutionHostId
): boolean {
const leftCwd = left.startupCwd?.trim() || null
const rightCwd = right.startupCwd?.trim() || null
if (!leftCwd || !rightCwd) {
return leftCwd === rightCwd
if (leftCwd === rightCwd) {
return true
}
const host = parseExecutionHostId(authoritativeHostId)
const pty = left.ptyId && left.ptyId === right.ptyId ? parseAppSshPtyId(left.ptyId) : null
return host?.kind === 'ssh' && pty?.connectionId === host.targetId
}
return normalizeRuntimePathForComparison(leftCwd) === normalizeRuntimePathForComparison(rightCwd)
}