mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(desktop): dispose SSH terminals on connection flip
teardownSshConnection cancelled the forward and closed the control master but
left any interim ssh -tt terminals riding that master alive — after the flip
they were pointed at a dead control socket. Quit disposed them globally, but a
live A->B connection switch did not.
- Tag each SSH terminal session with its backing SSH scope at spawn
(activeSshTerminalTarget now returns { ssh, scope }).
- teardownSshConnection disposes terminalSessions whose sshScope matches the
scope being torn down, BEFORE closing the master (so the PTY dies while its
socket is still valid). Local and other-scope terminals are untouched.
Honors the connection-flip teardown invariant (dispose terminal sessions on the
connection being torn down).
This commit is contained in:
parent
f0693a3232
commit
97b44e5401
1 changed files with 24 additions and 8 deletions
|
|
@ -4694,6 +4694,16 @@ async function teardownSshConnection(profile) {
|
|||
const state = sshConnections.get(scope)
|
||||
if (!state) return
|
||||
sshConnections.delete(scope)
|
||||
// Dispose any interim ssh -tt terminals riding this scope's master FIRST —
|
||||
// once the master closes a leftover PTY is pointed at a dead control socket.
|
||||
// Spec component 4 invariant: a connection flip tears down terminal sessions
|
||||
// on the connection (mirrors desktop-remote-terminal.md). Local/other-scope
|
||||
// terminals are untagged or tagged with a different scope and are left alone.
|
||||
for (const [id, info] of [...terminalSessions.entries()]) {
|
||||
if (info.sshScope === scope) {
|
||||
disposeTerminalSession(id)
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (state.localPort && state.remotePort) {
|
||||
await state.ssh.cancelForward(state.localPort, state.remotePort)
|
||||
|
|
@ -4712,16 +4722,19 @@ async function teardownSshConnection(profile) {
|
|||
// null when the active connection is not SSH. Used by the interim ssh -tt
|
||||
// terminal so a remote terminal lands on the SSH host — and ONLY in SSH mode
|
||||
// (it must never leak into token/oauth remotes, whose trust boundary is a
|
||||
// token/cookie, not a shell credential).
|
||||
// token/cookie, not a shell credential). Returns { ssh, scope } so the spawned
|
||||
// terminal can be tagged with its backing scope and disposed on a flip.
|
||||
function activeSshTerminalTarget() {
|
||||
const scope = sshScopeKey(primaryProfileKey())
|
||||
const primaryScope = sshScopeKey(primaryProfileKey())
|
||||
// Try the primary scope first, then the global scope (one of them backs the
|
||||
// window depending on whether a per-profile SSH override is in play).
|
||||
const state = sshConnections.get(scope) || sshConnections.get('')
|
||||
if (!state || !state.ssh) {
|
||||
return null
|
||||
for (const scope of [primaryScope, '']) {
|
||||
const state = sshConnections.get(scope)
|
||||
if (state && state.ssh) {
|
||||
return { ssh: state.ssh, scope }
|
||||
}
|
||||
}
|
||||
return state.ssh
|
||||
return null
|
||||
}
|
||||
|
||||
// Bring up (or reuse) the SSH-tunneled dashboard for one scope and return a
|
||||
|
|
@ -6675,7 +6688,7 @@ ipcMain.handle('hermes:terminal:start', async (event, payload = {}) => {
|
|||
const sshTarget = activeSshTerminalTarget()
|
||||
if (sshTarget) {
|
||||
const remoteCwd = String(payload?.cwd || '').trim()
|
||||
const sshArgs = buildInteractiveSshArgs(sshTarget, remoteCwd)
|
||||
const sshArgs = buildInteractiveSshArgs(sshTarget.ssh, remoteCwd)
|
||||
const sshPty = nodePty.spawn('ssh', sshArgs, {
|
||||
cols,
|
||||
cwd: app.getPath('home'),
|
||||
|
|
@ -6684,7 +6697,10 @@ ipcMain.handle('hermes:terminal:start', async (event, payload = {}) => {
|
|||
rows
|
||||
})
|
||||
|
||||
terminalSessions.set(id, { pty: sshPty, webContentsId: event.sender.id })
|
||||
// Tag the session with its backing SSH scope so a connection flip can
|
||||
// dispose the PTYs riding the master it tears down (the master goes away;
|
||||
// a leftover ssh -tt would be pointed at a dead socket).
|
||||
terminalSessions.set(id, { pty: sshPty, webContentsId: event.sender.id, sshScope: sshTarget.scope })
|
||||
|
||||
const sshSend = (suffix, data) => {
|
||||
if (event.sender.isDestroyed()) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue