[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,170 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { netcattyBridge } from '../../../infrastructure/services/netcattyBridge';
export interface ZmodemTransferEvent {
type: 'detect' | 'progress' | 'complete' | 'error';
sessionId: string;
transferType?: 'upload' | 'download';
filename?: string;
transferred?: number;
total?: number;
fileIndex?: number;
fileCount?: number;
finalizing?: boolean;
error?: string;
}
export interface ZmodemTransferState {
active: boolean;
transferType: 'upload' | 'download' | null;
filename: string | null;
transferred: number;
total: number;
fileIndex: number;
fileCount: number;
finalizing: boolean;
completed: boolean;
startedAt: number | null;
updatedAt: number | null;
bytesPerSecond: number | null;
error: string | null;
}
const initialState: ZmodemTransferState = {
active: false,
transferType: null,
filename: null,
transferred: 0,
total: 0,
fileIndex: 0,
fileCount: 0,
finalizing: false,
completed: false,
startedAt: null,
updatedAt: null,
bytesPerSecond: null,
error: null,
};
export function reduceZmodemTransferState(
prev: ZmodemTransferState,
event: ZmodemTransferEvent,
now: number = Date.now(),
): ZmodemTransferState {
switch (event.type) {
case 'detect':
return {
...initialState,
active: true,
transferType: event.transferType ?? null,
startedAt: now,
updatedAt: now,
};
case 'progress': {
const transferred = event.transferred ?? prev.transferred;
const fileChanged = (
prev.filename !== null
&& (
(typeof event.fileIndex === 'number' && event.fileIndex !== prev.fileIndex)
|| (typeof event.filename === 'string' && event.filename !== prev.filename)
)
);
const previousUpdatedAt = fileChanged ? now : (prev.updatedAt ?? now);
const elapsedSeconds = Math.max((now - previousUpdatedAt) / 1000, 0);
const deltaBytes = Math.max(transferred - prev.transferred, 0);
const bytesPerSecond = elapsedSeconds > 0 && deltaBytes > 0
? deltaBytes / elapsedSeconds
: fileChanged
? null
: prev.bytesPerSecond;
return {
...prev,
active: true,
transferType: event.transferType ?? prev.transferType,
filename: event.filename ?? prev.filename,
transferred,
total: event.total ?? prev.total,
fileIndex: event.fileIndex ?? prev.fileIndex,
fileCount: event.fileCount ?? prev.fileCount,
finalizing: !!event.finalizing,
completed: false,
startedAt: prev.startedAt ?? now,
updatedAt: now,
bytesPerSecond,
error: null,
};
}
case 'complete':
return {
...prev,
active: false,
finalizing: false,
completed: true,
updatedAt: now,
};
case 'error':
return {
...prev,
active: false,
finalizing: false,
completed: false,
updatedAt: now,
error: event.error ?? 'Unknown error',
};
}
}
export function useZmodemTransfer(sessionId: string | null) {
const [state, setState] = useState<ZmodemTransferState>(initialState);
const [overwriteRequest, setOverwriteRequest] = useState<{ requestId: string; filename: string } | null>(null);
const disposeRef = useRef<(() => void) | null>(null);
const disposeExitRef = useRef<(() => void) | null>(null);
useEffect(() => {
if (!sessionId) return;
const bridge = netcattyBridge.get();
if (!bridge?.onZmodemEvent) return;
disposeRef.current = bridge.onZmodemEvent(sessionId, (event) => {
setState((prev) => reduceZmodemTransferState(prev, event));
});
const disposeOverwrite = bridge.onZmodemOverwriteRequest?.(sessionId, (payload) => {
setOverwriteRequest({ requestId: payload.requestId, filename: payload.filename });
});
// If the session exits mid-transfer (disconnect, shell exit, etc.),
// reset state so the progress indicator doesn't stay stuck.
disposeExitRef.current = bridge.onSessionExit(sessionId, () => {
setState(initialState);
});
return () => {
disposeRef.current?.();
disposeRef.current = null;
disposeOverwrite?.();
disposeExitRef.current?.();
disposeExitRef.current = null;
setState(initialState);
setOverwriteRequest(null);
};
}, [sessionId]);
const cancel = useCallback(() => {
if (!sessionId) return;
const bridge = netcattyBridge.get();
bridge?.cancelZmodem?.(sessionId);
}, [sessionId]);
const respondOverwrite = useCallback((action: "overwrite" | "skip" | "cancel", applyToRest: boolean) => {
setOverwriteRequest((req) => {
if (req) netcattyBridge.get()?.respondZmodemOverwrite?.({ requestId: req.requestId, action, applyToRest });
return null;
});
}, []);
return { ...state, cancel, overwriteRequest, respondOverwrite };
}