[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,111 @@
import type { Terminal as XTerm } from "@xterm/xterm";
import type React from "react";
import { useEffect } from "react";
import { netcattyBridge } from "../../../infrastructure/services/netcattyBridge";
import { logger } from "../../../lib/logger";
import type { TerminalSession } from "../../../types";
import type { RemoteClipboardImageUploadResult } from "../clipboardImagePaste";
import { handleTerminalClipboardPaste } from "../terminalClipboardPaste";
interface UseTerminalFilePasteOptions {
isLocalConnection: boolean;
status: TerminalSession["status"];
termRef: React.MutableRefObject<XTerm | null>;
sessionRef: React.MutableRefObject<string | null>;
terminalBackend: {
writeToSession: (sessionId: string, data: string, options?: { automated?: boolean; sensitive?: boolean }) => void;
};
isSensitiveInput?: () => boolean;
scrollOnPasteRef?: React.RefObject<boolean>;
onPasteData?: (data: string) => boolean | void;
scrollToBottomAfterProgrammaticInput: (data: string) => void;
containerRef: React.RefObject<HTMLDivElement | null>;
/** Remote sessions only: auto-upload a clipboard image on paste. */
autoUploadClipboardImage?: boolean;
getRemoteCwd?: () => Promise<string | null | undefined>;
onClipboardImageUploadResult?: (result: RemoteClipboardImageUploadResult) => void;
}
export function useTerminalFilePaste({
isLocalConnection,
status,
termRef,
sessionRef,
terminalBackend,
isSensitiveInput,
scrollOnPasteRef,
onPasteData,
scrollToBottomAfterProgrammaticInput,
containerRef,
autoUploadClipboardImage = false,
getRemoteCwd,
onClipboardImageUploadResult,
}: UseTerminalFilePasteOptions) {
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const handlePaste = (event: ClipboardEvent) => {
if (status !== "connected") return;
const bridge = netcattyBridge.get();
const wantsImageUpload =
autoUploadClipboardImage && !isLocalConnection && !!bridge?.readClipboardImage;
const canHandleLocalPaste =
isLocalConnection && !!(bridge?.readClipboardFiles || bridge?.hasClipboardImage);
if (!wantsImageUpload && !canHandleLocalPaste) return;
// ⚡ Must call preventDefault SYNCHRONOUSLY — the event lifecycle
// is synchronous; calling it after an await is too late and the
// browser will have already performed the default paste action.
event.preventDefault();
event.stopPropagation();
void (async () => {
try {
const term = termRef.current;
if (!term) return;
await handleTerminalClipboardPaste({
bridge,
autoUploadClipboardImage: wantsImageUpload,
clipboardImageBridge: bridge ?? undefined,
getRemoteCwd,
isLocalConnection,
isSensitiveInput,
onClipboardImageUploadResult,
readClipboardText: () => navigator.clipboard.readText(),
scrollOnPaste: scrollOnPasteRef?.current ?? false,
onPasteData,
sessionId: sessionRef.current,
terminalBackend,
term,
scrollToBottomAfterProgrammaticInput,
});
} catch (error) {
logger.error("Failed to handle file paste", error);
}
})();
};
container.addEventListener("paste", handlePaste, true);
return () => {
container.removeEventListener("paste", handlePaste, true);
};
}, [
autoUploadClipboardImage,
containerRef,
getRemoteCwd,
isLocalConnection,
isSensitiveInput,
onClipboardImageUploadResult,
onPasteData,
scrollOnPasteRef,
scrollToBottomAfterProgrammaticInput,
sessionRef,
status,
terminalBackend,
termRef,
]);
}