[Init] Initial commit - NetMesh terminal manager
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

This commit is contained in:
2026-09-13 18:24:01 +08:00
commit 3c72efcb7f
3255 changed files with 907009 additions and 0 deletions

View File

@@ -0,0 +1,155 @@
import type { Host } from "../../../domain/models";
import type { MutableRefObject } from "react";
import { isSessionError } from "./errors";
import type { SftpPane } from "./types";
export interface SftpSessionProbeBridge {
realpathSftp?: (sftpId: string, path: string) => Promise<string>;
}
/**
* Probe the SFTP protocol itself instead of using remote home discovery.
* Restricted/chroot servers can deny SSH exec or expose '/' as their virtual
* root while the SFTP session remains fully usable.
*/
export async function probeSftpSession(
bridge: SftpSessionProbeBridge | null | undefined,
sftpId: string,
): Promise<boolean> {
if (!bridge?.realpathSftp) return true;
await bridge.realpathSftp(sftpId, ".");
return true;
}
export interface EnsureRemoteSftpSessionParams {
side: "left" | "right";
getActivePane: (side: "left" | "right") => SftpPane | null;
sftpSessionsRef: MutableRefObject<Map<string, string>>;
lastConnectedHostRef: MutableRefObject<{ left: Host | "local" | null; right: Host | "local" | null }>;
connect: (
side: "left" | "right",
host: Host | "local",
options?: { initialPath?: string; ignoreSharedCache?: boolean; tabId?: string; sourceSessionId?: string },
) => Promise<void>;
/** Preferred already-authenticated SSH session for this reconnect. */
sourceSessionId?: string;
/** Resolve an SSH session after the reconnect host has been resolved. */
resolveSourceSessionId?: (hostId: string, host: Host) => string | undefined;
/**
* Per-tab connect-time host (includes session hostname/port/user overrides).
* Prefer this over the vault entry so upload reconnects keep the same endpoint.
*/
resolveConnectedHost?: (tabId: string) => Host | "local" | null | undefined;
/** Resolve vault host by id when per-tab connect-time host is unavailable. */
resolveHostById?: (hostId: string) => Host | null | undefined;
probeSession?: (sftpId: string) => Promise<boolean>;
/** Remove connection metadata and close the mapped backend session. */
releaseConnection: (connectionId: string) => Promise<void>;
forceReconnect?: boolean;
/** Stable tab identity — reconnect replaces connection ids, not tab ids. */
tabId?: string;
}
/**
* Return a live remote SFTP session id for the active pane, reconnecting the
* host when the mapping is missing or the backend session is gone.
*/
export async function ensureRemoteSftpSession(
params: EnsureRemoteSftpSessionParams,
): Promise<string> {
const {
side,
getActivePane,
sftpSessionsRef,
lastConnectedHostRef,
connect,
resolveConnectedHost,
resolveHostById,
probeSession,
releaseConnection,
forceReconnect = false,
tabId,
sourceSessionId,
resolveSourceSessionId,
} = params;
const resolveHost = (): Host => {
const pane = getActivePane(side);
const hostId = pane?.connection && !pane.connection.isLocal ? pane.connection.hostId : undefined;
const resolvedTabId = tabId ?? pane?.id;
// Prefer the full Host captured when this tab connected (session-time
// hostname/port/username overrides). Vault lookup by hostId alone would
// reconnect the base endpoint and can open the wrong server before the
// upload endpoint assertion aborts.
if (resolvedTabId && resolveConnectedHost) {
const fromTab = resolveConnectedHost(resolvedTabId);
if (fromTab && fromTab !== "local") return fromTab;
}
// Vault next — never prefer side-wide lastConnectedHost over vault when
// another tab on this side may hold different overrides for the same hostId.
if (hostId && resolveHostById) {
const fromVault = resolveHostById(hostId);
if (fromVault) return fromVault;
}
const lastHost = lastConnectedHostRef.current[side];
if (lastHost && lastHost !== "local" && (!hostId || lastHost.id === hostId)) {
return lastHost;
}
// Pane connection only stores hostId/label — inventing root@label:22 would
// open the wrong endpoint. Fail clearly so the caller can reconnect via
// vault host metadata instead of a synthetic identity.
if (pane?.connection && !pane.connection.isLocal) {
throw new Error(
`Cannot reconnect SFTP for "${pane.connection.hostLabel}": host credentials are unavailable. Reopen the host from the vault.`,
);
}
throw new Error("No remote host available to reconnect");
};
const readMappedId = (): string | undefined => {
const pane = getActivePane(side);
if (!pane?.connection || pane.connection.isLocal) {
throw new Error("No remote SFTP connection on this pane");
}
return sftpSessionsRef.current.get(pane.connection.id);
};
if (!forceReconnect) {
const existing = readMappedId();
if (existing) {
if (!probeSession) return existing;
try {
const ok = await probeSession(existing);
if (ok) return existing;
} catch (error) {
if (!isSessionError(error)) throw error;
}
const pane = getActivePane(side);
if (pane?.connection) {
await releaseConnection(pane.connection.id);
}
}
} else {
const pane = getActivePane(side);
if (pane?.connection) {
await releaseConnection(pane.connection.id);
}
}
const paneBefore = getActivePane(side);
const resumePath = paneBefore?.connection?.currentPath;
const host = resolveHost();
const resolvedSourceSessionId = sourceSessionId ?? resolveSourceSessionId?.(host.id, host);
await connect(side, host, {
initialPath: resumePath,
ignoreSharedCache: true,
...(tabId ? { tabId } : {}),
...(resolvedSourceSessionId ? { sourceSessionId: resolvedSourceSessionId } : {}),
});
const sftpId = readMappedId();
if (!sftpId) {
throw new Error("SFTP session not found after reconnect");
}
return sftpId;
}