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
144 lines
4.6 KiB
TypeScript
144 lines
4.6 KiB
TypeScript
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)
|
|
));
|
|
}
|