[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
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:
143
components/terminalLayer/sftpPanelLifecycle.ts
Normal file
143
components/terminalLayer/sftpPanelLifecycle.ts
Normal 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)
|
||||
));
|
||||
}
|
||||
Reference in New Issue
Block a user