mirror of
https://github.com/stablyai/orca.git
synced 2026-09-24 00:02:24 +00:00
* Revert "fix(daemon): stop killing live coding agents when the daemon can't report its sessions (#13928)" This reverts commit2e8cf589de. Reverted together with #13326: the 1.4.182-daily.202608131439 build carrying both loses every tab on an SSH disconnect/reconnect cycle. Reverting first so main stays releasable and the P0 fixes in the wild remain cherry-pickable, rather than fixing forward on a shipped regression. * Revert "fix(ssh): stop SSH reconnect from multiplying terminals and resuming agents twice (STA-3077)" (#13326) This reverts commit3ab8b6a117. Reported on 1.4.182-daily.202608131439: connect to an SSH worktree, disconnect the host from the hosts popup, reconnect — every tab is gone. That is worse than the behaviour this PR set out to fix, where most tabs were retained. Reverting rather than fixing forward, so main stays releasable and the P0 fixes already out in the wild stay cherry-pickable. STA-3077 stays open.
199 lines
6.0 KiB
TypeScript
199 lines
6.0 KiB
TypeScript
import {
|
|
execDockerSshRelayTargetCommand,
|
|
shellQuote,
|
|
type DockerSshRelayTarget
|
|
} from './docker-ssh-relay-target'
|
|
|
|
export type DockerSshRelayProcessSnapshot = {
|
|
relayPid: number
|
|
watcherPids: number[]
|
|
relayDir: string
|
|
}
|
|
|
|
export type DockerSshRelayArtifactState = {
|
|
installComplete: boolean
|
|
relayWatcher: boolean
|
|
}
|
|
|
|
type RelayProcessRow = {
|
|
type: 'relay' | 'watcher'
|
|
pid: number
|
|
parentPid: number
|
|
cwd: string
|
|
}
|
|
|
|
const LIST_RELAY_PROCESSES_COMMAND = `
|
|
for proc in /proc/[0-9]*; do
|
|
[ -r "$proc/cmdline" ] || continue
|
|
argv=()
|
|
mapfile -d '' -t argv < "$proc/cmdline" 2>/dev/null || continue
|
|
entry="\${argv[1]:-}"
|
|
base="\${entry##*/}"
|
|
type=
|
|
if [ "$base" = relay-watcher.js ]; then
|
|
type=watcher
|
|
elif [ "$base" = relay.js ]; then
|
|
for arg in "\${argv[@]:2}"; do
|
|
if [ "$arg" = --detached ]; then type=relay; break; fi
|
|
done
|
|
fi
|
|
[ -n "$type" ] || continue
|
|
pid="\${proc##*/}"
|
|
ppid="$(awk '/^PPid:/{print $2}' "$proc/status" 2>/dev/null)"
|
|
cwd="$(readlink "$proc/cwd" 2>/dev/null)"
|
|
printf '%s\\t%s\\t%s\\t%s\\n' "$type" "$pid" "$ppid" "$cwd"
|
|
done
|
|
`
|
|
|
|
function parseRelayProcessRows(output: string): RelayProcessRow[] {
|
|
if (!output) {
|
|
return []
|
|
}
|
|
return output.split('\n').map((line) => {
|
|
const [type, rawPid, rawParentPid, cwd] = line.split('\t')
|
|
// Why: Number('') is 0, so empty pid/ppid (e.g. vanished /proc status) must
|
|
// throw and let expect.poll retry instead of accepting parentPid: 0.
|
|
const pid = Number(rawPid)
|
|
const parentPid = Number(rawParentPid)
|
|
if (
|
|
(type !== 'relay' && type !== 'watcher') ||
|
|
rawPid === undefined ||
|
|
rawPid === '' ||
|
|
rawParentPid === undefined ||
|
|
rawParentPid === '' ||
|
|
!Number.isInteger(pid) ||
|
|
!Number.isInteger(parentPid) ||
|
|
!cwd
|
|
) {
|
|
throw new Error(`Unexpected Docker SSH relay process row: ${line}`)
|
|
}
|
|
return { type, pid, parentPid, cwd }
|
|
})
|
|
}
|
|
|
|
export function readDockerSshRelayProcessSnapshot(
|
|
target: DockerSshRelayTarget
|
|
): DockerSshRelayProcessSnapshot | null {
|
|
const groups = readDockerSshRelayProcessSnapshots(target)
|
|
if (groups.length > 1) {
|
|
throw new Error(`Expected one Docker SSH relay process group, found ${groups.length}`)
|
|
}
|
|
return groups[0] ?? null
|
|
}
|
|
|
|
export function readDockerSshRelayProcessSnapshots(
|
|
target: DockerSshRelayTarget
|
|
): DockerSshRelayProcessSnapshot[] {
|
|
const rows = parseRelayProcessRows(
|
|
execDockerSshRelayTargetCommand(target, LIST_RELAY_PROCESSES_COMMAND)
|
|
)
|
|
const relays = rows.filter((row) => row.type === 'relay')
|
|
const groups = relays.flatMap((relay) => {
|
|
const watcherPids = rows
|
|
.filter((row) => row.type === 'watcher' && row.parentPid === relay.pid)
|
|
.map((row) => row.pid)
|
|
.sort((left, right) => left - right)
|
|
return watcherPids.length > 0 ? [{ relayPid: relay.pid, watcherPids, relayDir: relay.cwd }] : []
|
|
})
|
|
return groups.sort((left, right) => left.relayPid - right.relayPid)
|
|
}
|
|
|
|
export function signalDockerSshRelayWatchers(
|
|
target: DockerSshRelayTarget,
|
|
snapshot: DockerSshRelayProcessSnapshot
|
|
): void {
|
|
const { relayPid, watcherPids } = snapshot
|
|
if (!Number.isInteger(relayPid) || watcherPids.some((pid) => !Number.isInteger(pid))) {
|
|
throw new Error('Docker SSH relay process IDs must be integers')
|
|
}
|
|
execDockerSshRelayTargetCommand(
|
|
target,
|
|
[
|
|
`relay_proc=/proc/${relayPid}`,
|
|
'[ -r "$relay_proc/cmdline" ]',
|
|
'relay_argv=()',
|
|
'mapfile -d \'\' -t relay_argv < "$relay_proc/cmdline"',
|
|
'[ "${relay_argv[1]##*/}" = relay.js ]',
|
|
'[[ " ${relay_argv[*]:2} " = *" --detached "* ]]',
|
|
...watcherPids.flatMap((watcherPid) => [
|
|
`proc=/proc/${watcherPid}`,
|
|
'[ -r "$proc/cmdline" ]',
|
|
'argv=()',
|
|
'mapfile -d \'\' -t argv < "$proc/cmdline"',
|
|
'[ "${argv[1]##*/}" = relay-watcher.js ]',
|
|
`ppid="$(awk '/^PPid:/{print $2}' "$proc/status")"`,
|
|
`[ "$ppid" = ${relayPid} ]`,
|
|
`kill -SEGV ${watcherPid}`
|
|
])
|
|
].join(' && ')
|
|
)
|
|
}
|
|
|
|
export function terminateDockerSshRelay(
|
|
target: DockerSshRelayTarget,
|
|
snapshot: DockerSshRelayProcessSnapshot
|
|
): void {
|
|
const { relayPid } = snapshot
|
|
if (!Number.isInteger(relayPid)) {
|
|
throw new Error('Docker SSH relay process ID must be an integer')
|
|
}
|
|
execDockerSshRelayTargetCommand(
|
|
target,
|
|
[
|
|
`proc=/proc/${relayPid}`,
|
|
'[ -r "$proc/cmdline" ]',
|
|
'argv=()',
|
|
'mapfile -d \'\' -t argv < "$proc/cmdline"',
|
|
'[ "${argv[1]##*/}" = relay.js ]',
|
|
'[[ " ${argv[*]:2} " = *" --detached "* ]]',
|
|
`kill -TERM ${relayPid}`
|
|
].join(' && ')
|
|
)
|
|
}
|
|
|
|
export function isDockerSshRelayPidRunning(
|
|
target: DockerSshRelayTarget,
|
|
relayPid: number
|
|
): boolean {
|
|
if (!Number.isInteger(relayPid)) {
|
|
return false
|
|
}
|
|
const result = execDockerSshRelayTargetCommand(
|
|
target,
|
|
`proc=/proc/${relayPid}; ` +
|
|
'if [ -r "$proc/cmdline" ]; then ' +
|
|
'argv=(); mapfile -d \'\' -t argv < "$proc/cmdline"; ' +
|
|
'if [ "${argv[1]##*/}" = relay.js ] && ' +
|
|
'[[ " ${argv[*]:2} " = *" --detached "* ]]; then echo ALIVE; else echo DEAD; fi; ' +
|
|
'else echo DEAD; fi'
|
|
)
|
|
return result === 'ALIVE'
|
|
}
|
|
|
|
export function readDockerSshRelayArtifactState(
|
|
target: DockerSshRelayTarget,
|
|
relayDir: string
|
|
): DockerSshRelayArtifactState {
|
|
const quotedDir = shellQuote(relayDir)
|
|
const output = execDockerSshRelayTargetCommand(
|
|
target,
|
|
`test -f ${quotedDir}/.install-complete && marker=yes || marker=no; ` +
|
|
`test -f ${quotedDir}/relay-watcher.js && watcher=yes || watcher=no; ` +
|
|
'printf "%s %s" "$marker" "$watcher"'
|
|
)
|
|
const [marker, watcher] = output.split(' ')
|
|
return { installComplete: marker === 'yes', relayWatcher: watcher === 'yes' }
|
|
}
|
|
|
|
export function removeDockerSshRelayWatcherArtifact(
|
|
target: DockerSshRelayTarget,
|
|
relayDir: string
|
|
): void {
|
|
const quotedDir = shellQuote(relayDir)
|
|
execDockerSshRelayTargetCommand(
|
|
target,
|
|
`test -f ${quotedDir}/.install-complete && rm -f ${quotedDir}/relay-watcher.js && ` +
|
|
`test -f ${quotedDir}/.install-complete && test ! -e ${quotedDir}/relay-watcher.js`
|
|
)
|
|
}
|