[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,143 @@
import type { TransferTask } from "../../domain/models";
export const SFTP_TRANSFER_HISTORY_RETENTION_MS = 10 * 60 * 1000;
/** Terminal side-panel transfer owner id for a workspace/session tab. */
export function terminalSftpTransferOwnerId(tabId: string): string {
return `terminal:${tabId}`;
}
/**
* Tasks that must keep the hidden SFTP owner mounted (orchestration lives in
* useSftpState / useSftpTransfers). Matches activeTransfersCount semantics:
* everything except completed/cancelled top-level rows.
*/
export function isTransferRetainingSftpOwner(
task: Pick<TransferTask, "status" | "parentTaskId">,
): boolean {
if (task.parentTaskId) return false;
return task.status !== "completed" && task.status !== "cancelled";
}
export function countTransfersRetainingSftpOwner(
tasks: readonly Pick<TransferTask, "status" | "parentTaskId" | "ownerId">[],
ownerId: string,
): number {
return tasks.filter(
(task) => (task.ownerId ?? "") === ownerId && isTransferRetainingSftpOwner(task),
).length;
}
/**
* Prefer the live panel report, but never under-count unfinished work already
* published to the global transfer center (avoids close-before-layout-effect races).
*/
export function resolveSftpActiveTransfersCount(params: {
reportedCount: number;
storeTasks: readonly Pick<TransferTask, "status" | "parentTaskId" | "ownerId">[];
ownerId: string;
}): number {
const reported = Math.max(0, params.reportedCount);
const fromStore = countTransfersRetainingSftpOwner(params.storeTasks, params.ownerId);
return Math.max(reported, fromStore);
}
/** Tab ids whose terminal:* owner still has unfinished work in the store. */
export function listTerminalTabIdsWithRetainingTransfers(
tasks: readonly Pick<TransferTask, "status" | "parentTaskId" | "ownerId">[],
): string[] {
const tabIds = new Set<string>();
for (const task of tasks) {
if (!isTransferRetainingSftpOwner(task)) continue;
const ownerId = task.ownerId ?? "";
if (!ownerId.startsWith("terminal:")) continue;
tabIds.add(ownerId.slice("terminal:".length));
}
return [...tabIds];
}
export function shouldKeepSftpMountedAfterClose(params: {
activeTransfersCount: number;
/** External-editor temps still need the browse session (closeSftp deletes them). */
activeExternalEditCount?: number;
}): boolean {
return params.activeTransfersCount > 0
|| (params.activeExternalEditCount ?? 0) > 0;
}
/**
* A different side-panel tool keeps SFTP warm only when the SFTP owner was
* never closed. A retained-after-close mount is kept for transfers/editor
* cleanup, but its browse session must still be allowed to park.
*/
export function shouldKeepSftpBrowseSessionInteractive(params: {
sidePanelOpen: boolean;
retainedAfterClose: boolean;
sftpPaneClosed: boolean;
}): boolean {
return params.sidePanelOpen
&& !params.retainedAfterClose
&& !params.sftpPaneClosed;
}
export function shouldMarkSftpPaneClosed(params: {
closingPaneTool: string | null | undefined;
closesWholePanel: boolean;
}): boolean {
return !params.closesWholePanel && params.closingPaneTool === 'sftp';
}
export function shouldCloseSftpSidePanel(params: {
shouldKeepOpen: boolean;
isOpen: boolean;
isSameEndpoint: boolean;
paneCount: number;
}): boolean {
return !params.shouldKeepOpen
&& params.isOpen
&& params.isSameEndpoint
&& params.paneCount <= 1;
}
export function shouldClearSftpPanelAfterTransferChange(params: {
activeTransfersCount: number;
activeExternalEditCount?: number;
panelOpen: boolean;
retainedAfterClose: boolean;
}): boolean {
return params.activeTransfersCount <= 0
&& (params.activeExternalEditCount ?? 0) <= 0
&& !params.panelOpen
&& !params.retainedAfterClose;
}
export function shouldScheduleSftpRetainedPanelCleanup(params: {
activeTransfersCount: number;
activeExternalEditCount?: number;
retainedAfterClose: boolean;
}): boolean {
return params.activeTransfersCount <= 0
&& (params.activeExternalEditCount ?? 0) <= 0
&& params.retainedAfterClose;
}
export function listInvalidSftpPanelTabIds(params: {
mountedTabIds: Iterable<string>;
activeTransferTabIds: Iterable<string>;
retainedTabIds: Iterable<string>;
openingTabIds: Iterable<string>;
cleanupTimerTabIds: Iterable<string>;
validTabIds: ReadonlySet<string>;
}): string[] {
const activeTransferTabIds = new Set(params.activeTransferTabIds);
const trackedTabIds = new Set([
...params.mountedTabIds,
...activeTransferTabIds,
...params.retainedTabIds,
...params.openingTabIds,
...params.cleanupTimerTabIds,
]);
return [...trackedTabIds].filter((tabId) => (
!params.validTabIds.has(tabId) && !activeTransferTabIds.has(tabId)
));
}