import type { TerminalSession } from "../../domain/models"; export type SessionPwdProbe = ( sessionId: string, options?: { allowHomeFallback?: boolean; allowLoginShellFallback?: boolean; timeoutMs?: number; }, ) => Promise<{ success: boolean; cwd?: string }>; type CaptureSession = Pick; export interface CaptureInheritedCwdOptions { /** * The session's live tracked cwd (OSC 7), sourced from the terminal-state * cwd map rather than the session object. This is the freshest value and the * only one that reflects `cd`s in a running LOCAL terminal (whose live cwd is * never mirrored onto `TerminalSession.lastCwd`). */ liveCwd?: string; /** * Whether an SSH `/proc` probe is permitted. Callers pass `false` for network * devices (e.g. Huawei VRP), where the extra exec channel can drop the whole * session — mirrors `shouldProbeSessionCwd` in the terminal cwd-probe path. */ allowSshProbe?: boolean; /** Max time to wait on the probe before falling back. */ probeTimeoutMs?: number; } /** Max time to wait on the live SSH cwd probe before falling back to lastCwd. */ export const DEFAULT_INHERITED_CWD_PROBE_TIMEOUT_MS = 1500; /** * Resolve the working directory a clone/split should inherit from its source. * * Priority: live tracked cwd (OSC 7) -> live SSH `/proc` probe (when allowed) * -> tracked `lastCwd` snapshot -> local `localStartDir`. The probe is raced * against a short timeout so a slow/wedged connection can't block tab creation, * and is skipped entirely when `allowSshProbe` is false. Returns undefined when * nothing is known (caller then behaves as before: login dir). */ export async function captureInheritedCwd( session: CaptureSession, getSessionPwd: SessionPwdProbe, options: CaptureInheritedCwdOptions = {}, ): Promise { const { liveCwd, allowSshProbe = true, probeTimeoutMs = DEFAULT_INHERITED_CWD_PROBE_TIMEOUT_MS, } = options; const live = liveCwd?.trim(); if (live) return live; const protocol = session.protocol ?? "ssh"; const isRemoteSsh = protocol === "ssh" || protocol === undefined; if (isRemoteSsh && allowSshProbe && session.status === "connected") { // Never rejects: a failed/absent probe resolves to undefined so the race // below can't leave a dangling unhandled rejection when the timeout wins. const probePromise = getSessionPwd(session.id, { allowHomeFallback: false, // Keep the backend exec within the same budget as this UI-side timeout. timeoutMs: probeTimeoutMs, }) .then((res) => (res?.success ? res.cwd?.trim() : undefined)) .catch(() => undefined); let timer: ReturnType | undefined; const timeoutPromise = new Promise((resolve) => { timer = setTimeout(() => resolve(undefined), probeTimeoutMs); }); const probed = await Promise.race([probePromise, timeoutPromise]); if (timer) clearTimeout(timer); if (probed) return probed; } const tracked = session.lastCwd?.trim(); if (tracked) return tracked; if (protocol === "local") return session.localStartDir; return undefined; }