Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
106 lines
4.2 KiB
JavaScript
106 lines
4.2 KiB
JavaScript
"use strict";
|
|
|
|
const { executeBoundedSshCommand } = require("./boundedSshExec.cjs");
|
|
const { isSshChannelOpenRateLimitedError } = require("./boundedSshChannelOpen.cjs");
|
|
|
|
const SCAN_COMPLETE_MARKER = "__NETCATTY_SHELL_SCAN_COMPLETE__";
|
|
const discoveryDisabledConnections = new WeakSet();
|
|
|
|
function buildInteractiveShellPidCommand(quoteShellArg) {
|
|
const script = `SELF=$$
|
|
ps_output=$(ps -e -o pid=,ppid=,tty=,comm=,etimes= 2>/dev/null) || ps_output=$(ps -e -o pid=,ppid=,tty=,comm= 2>/dev/null) || exit 69
|
|
{
|
|
printf '%s\n' "$ps_output" | awk -v pp="$PPID" -v self="$SELF" '
|
|
function isshell(c) { sub(/^.*\\//, "", c); sub(/^-/, "", c); return c ~ /^(ba|z|fi|k|da|a|c|tc)?sh$/ }
|
|
$1 != self && $2 == pp && $3 !~ /^\\?+$/ && isshell($4) {
|
|
if (NF >= 5 && $5 ~ /^[0-9]+$/) print $1, $5+0
|
|
else print $1
|
|
}
|
|
'
|
|
if [ -r /proc/$SELF/environ ]; then
|
|
conn=$(tr '\\0' '\\n' < /proc/$SELF/environ 2>/dev/null | sed -n 's/^SSH_CONNECTION=//p' | head -n1)
|
|
if [ -n "$conn" ]; then
|
|
for d in /proc/[0-9]*; do
|
|
pid=$(basename "$d")
|
|
[ "$pid" = "$SELF" ] && continue
|
|
[ -r "$d/environ" ] || continue
|
|
conn2=$(tr '\\0' '\\n' < "$d/environ" 2>/dev/null | sed -n 's/^SSH_CONNECTION=//p' | head -n1)
|
|
[ "$conn2" = "$conn" ] || continue
|
|
comm=$(cat "$d/comm" 2>/dev/null)
|
|
case "$comm" in sh|bash|zsh|fish|ksh|dash|ash|csh|tcsh) ;; *) continue ;; esac
|
|
ppid=$(awk '{ print $4 }' "$d/stat" 2>/dev/null)
|
|
pcomm=$(cat "/proc/$ppid/comm" 2>/dev/null)
|
|
case "$pcomm" in sshd|dropbear|dropbearmulti) ;; *) continue ;; esac
|
|
tty=$(ps -p "$pid" -o tty= 2>/dev/null | tr -d '[:space:]')
|
|
[ -n "$tty" ] && [ "$tty" != "?" ] || continue
|
|
etimes=$(ps -p "$pid" -o etimes= 2>/dev/null | tr -d '[:space:]')
|
|
case "$etimes" in
|
|
''|*[!0-9]*) printf '%s\\n' "$pid" ;;
|
|
*) printf '%s %s\\n' "$pid" "$etimes" ;;
|
|
esac
|
|
done
|
|
fi
|
|
fi
|
|
} | awk '/^[0-9]+/ && !seen[$1]++ { print }'
|
|
printf '%s\n' '${SCAN_COMPLETE_MARKER}'`;
|
|
return `exec sh -c ${quoteShellArg(script)}`;
|
|
}
|
|
|
|
async function listInteractiveShellPids(conn, options = {}) {
|
|
if (!conn || typeof conn.exec !== "function" || typeof options.quoteShellArg !== "function") {
|
|
return { available: false, pids: [], ages: {} };
|
|
}
|
|
const invalidateOnOpenTimeout = options.invalidateOnOpenTimeout === true;
|
|
if (!invalidateOnOpenTimeout && discoveryDisabledConnections.has(conn)) {
|
|
return { available: false, disabled: true, pids: [], ages: {} };
|
|
}
|
|
// Shell PID discovery only improves cwd recovery for reused shells. A slow
|
|
// auxiliary exec open must not invalidate the physical transport and close
|
|
// every interactive shell sharing it. After one open timeout, stop probing
|
|
// this connection so ssh2 cannot retain another uncancellable callback on
|
|
// every later reuse.
|
|
try {
|
|
const result = await executeBoundedSshCommand(
|
|
conn,
|
|
buildInteractiveShellPidCommand(options.quoteShellArg),
|
|
{
|
|
openingTimeoutMs: options.openingTimeoutMs ?? 1500,
|
|
runTimeoutMs: options.runTimeoutMs ?? 1500,
|
|
maxOutputBytes: 1024 * 1024,
|
|
setTimeoutFn: options.setTimeoutFn,
|
|
clearTimeoutFn: options.clearTimeoutFn,
|
|
invalidateOnOpenTimeout,
|
|
},
|
|
);
|
|
const lines = result.stdout.split(/\r?\n/);
|
|
const available = lines.includes(SCAN_COMPLETE_MARKER)
|
|
&& (result.code === null || result.code === 0);
|
|
const pids = [];
|
|
const ages = {};
|
|
if (available) {
|
|
for (const line of lines) {
|
|
const match = /^(\d+)(?:\s+(\d+))?$/.exec(String(line || "").trim());
|
|
if (!match) continue;
|
|
const pid = match[1];
|
|
if (!pids.includes(pid)) pids.push(pid);
|
|
if (match[2] !== undefined) ages[pid] = Number(match[2]);
|
|
}
|
|
}
|
|
return { available, pids, ages };
|
|
} catch (error) {
|
|
const openTimedOut = error?.code === "SSH_EXEC_OPEN_TIMEOUT";
|
|
if (openTimedOut && !invalidateOnOpenTimeout) {
|
|
discoveryDisabledConnections.add(conn);
|
|
}
|
|
return {
|
|
available: false,
|
|
rateLimited: isSshChannelOpenRateLimitedError(error),
|
|
openTimedOut,
|
|
pids: [],
|
|
ages: {},
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = { buildInteractiveShellPidCommand, listInteractiveShellPids };
|