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
88 lines
3.2 KiB
TypeScript
88 lines
3.2 KiB
TypeScript
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<TerminalSession, "id" | "protocol" | "status" | "lastCwd" | "localStartDir">;
|
|
|
|
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<string | undefined> {
|
|
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<typeof setTimeout> | undefined;
|
|
const timeoutPromise = new Promise<undefined>((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;
|
|
}
|