refactor: remove draft agent routes and consolidate terminal output into session model

This commit is contained in:
Mohamed Boudra
2026-03-04 14:17:39 +07:00
parent b21764fd40
commit 92991ffdb8
25 changed files with 634 additions and 317 deletions
+62 -85
View File
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useFocusEffect } from "@react-navigation/native";
import { useFocusEffect, useIsFocused } from "@react-navigation/native";
import {
ActivityIndicator,
Pressable,
@@ -29,10 +29,10 @@ import {
import { upsertTerminalListEntry } from "@/utils/terminal-list";
import { confirmDialog } from "@/utils/confirm-dialog";
import {
TerminalOutputPump,
type TerminalOutputChunk,
} from "@/terminal/runtime/terminal-output-pump";
import { TerminalOutputDeliveryQueue } from "@/terminal/runtime/terminal-output-delivery-queue";
getTerminalOutputSession,
releaseTerminalOutputSession,
retainTerminalOutputSession,
} from "@/terminal/runtime/terminal-output-session";
import {
TerminalStreamController,
type TerminalStreamControllerStatus,
@@ -82,12 +82,6 @@ type ModifierState = {
alt: boolean;
};
type TerminalOutputChunkState = {
sequence: number;
text: string;
replay: boolean;
};
type PendingTerminalInput =
| {
type: "data";
@@ -147,6 +141,7 @@ export function TerminalPane({
hideHeader = false,
manageTerminalDirectorySubscription = true,
}: TerminalPaneProps) {
const isScreenFocused = useIsFocused();
const { theme } = useUnistyles();
const xtermTheme = useMemo(() => toXtermTheme(theme.colors.terminal), [theme.colors.terminal]);
const isMobile =
@@ -167,14 +162,33 @@ export function TerminalPane({
const terminalsQueryKey = useMemo(() => ["terminals", serverId, cwd] as const, [cwd, serverId]);
const lastReportedSizeRef = useRef<{ rows: number; cols: number } | null>(null);
const streamControllerRef = useRef<TerminalStreamController | null>(null);
const outputPumpRef = useRef<TerminalOutputPump | null>(null);
const outputDeliveryQueueRef = useRef<TerminalOutputDeliveryQueue | null>(null);
const [selectedOutputChunk, setSelectedOutputChunk] = useState<TerminalOutputChunkState>({
sequence: 0,
text: "",
replay: false,
});
const [selectedOutputSnapshot, setSelectedOutputSnapshot] = useState("");
const outputSession = useMemo(
() =>
getTerminalOutputSession({
scopeKey,
maxOutputChars: MAX_OUTPUT_CHARS,
}),
[scopeKey]
);
const subscribeOutputSession = useCallback(
(listener: () => void) => outputSession.subscribe(listener),
[outputSession]
);
const getOutputSessionState = useCallback(
() => outputSession.getState(),
[outputSession]
);
useEffect(() => {
retainTerminalOutputSession({ scopeKey });
return () => {
releaseTerminalOutputSession({ scopeKey });
};
}, [scopeKey]);
const outputState = useSyncExternalStore(
subscribeOutputSession,
getOutputSessionState,
getOutputSessionState
);
const [activeStream, setActiveStream] = useState<{
terminalId: string;
streamId: number;
@@ -204,39 +218,6 @@ export function TerminalPane({
selectedTerminalIdRef.current = selectedTerminalId;
}, [selectedTerminalId]);
useEffect(() => {
const outputDeliveryQueue = new TerminalOutputDeliveryQueue({
onDeliver: (chunk) => {
setSelectedOutputChunk(chunk);
},
});
outputDeliveryQueueRef.current = outputDeliveryQueue;
return () => {
if (outputDeliveryQueueRef.current === outputDeliveryQueue) {
outputDeliveryQueueRef.current = null;
}
outputDeliveryQueue.reset();
};
}, []);
useEffect(() => {
const outputPump = new TerminalOutputPump({
maxOutputChars: MAX_OUTPUT_CHARS,
onSelectedOutputChunk: (chunk: TerminalOutputChunk) => {
outputDeliveryQueueRef.current?.enqueue(chunk);
},
});
outputPumpRef.current = outputPump;
return () => {
if (outputPumpRef.current === outputPump) {
outputPumpRef.current = null;
}
outputPump.dispose();
};
}, []);
const clearHoverOutTimeout = useCallback(() => {
if (!hoverOutTimeoutRef.current) {
return;
@@ -467,7 +448,7 @@ export function TerminalPane({
},
onSuccess: (_, terminalId) => {
setHoveredTerminalId((current) => (current === terminalId ? null : current));
outputPumpRef.current?.clearTerminal({ terminalId });
outputSession.clearTerminal({ terminalId });
if (selectedTerminalIdRef.current === terminalId) {
updateSelectedTerminalId(null);
setModifiers({ ...EMPTY_MODIFIERS });
@@ -488,9 +469,9 @@ export function TerminalPane({
useEffect(() => {
const terminalIds = terminals.map((terminal) => terminal.id);
outputPumpRef.current?.prune({ terminalIds });
outputSession.prune({ terminalIds });
streamControllerRef.current?.pruneResumeOffsets({ terminalIds });
}, [terminals]);
}, [outputSession, terminals]);
const handleStreamControllerStatus = useCallback(
(status: TerminalStreamControllerStatus) => {
@@ -519,28 +500,22 @@ export function TerminalPane({
return;
}
const outputPump = outputPumpRef.current;
if (!outputPump) {
return;
}
const controller = new TerminalStreamController({
client,
getPreferredSize: () => lastReportedSizeRef.current,
onChunk: ({ terminalId, text, replay }) => {
outputPump.append({ terminalId, text, replay });
outputSession.append({ terminalId, text, replay });
},
onReset: ({ terminalId }) => {
outputPump.clearTerminal({ terminalId });
if (selectedTerminalIdRef.current === terminalId) {
setSelectedOutputSnapshot("");
}
outputSession.clearTerminal({ terminalId });
},
onStatusChange: handleStreamControllerStatus,
});
streamControllerRef.current = controller;
controller.setTerminal({ terminalId: selectedTerminalIdRef.current });
controller.setTerminal({
terminalId: isScreenFocused ? selectedTerminalIdRef.current : null,
});
return () => {
controller.dispose();
@@ -548,24 +523,18 @@ export function TerminalPane({
streamControllerRef.current = null;
}
};
}, [client, handleStreamControllerStatus, isConnected]);
}, [client, handleStreamControllerStatus, isConnected, isScreenFocused, outputSession]);
useEffect(() => {
outputDeliveryQueueRef.current?.reset();
pendingTerminalInputRef.current = [];
setSelectedOutputChunk({ sequence: 0, text: "", replay: false });
outputPumpRef.current?.setSelectedTerminal({
terminalId: selectedTerminalId,
const nextSelectedTerminalId = isScreenFocused ? selectedTerminalId : null;
outputSession.setSelectedTerminal({
terminalId: nextSelectedTerminalId,
});
streamControllerRef.current?.setTerminal({
terminalId: selectedTerminalId,
terminalId: nextSelectedTerminalId,
});
setSelectedOutputSnapshot(
outputPumpRef.current?.readSnapshot({
terminalId: selectedTerminalId,
}) ?? ""
);
}, [selectedTerminalId]);
}, [isScreenFocused, outputSession, selectedTerminalId]);
const activeStreamId =
activeStream && activeStream.terminalId === selectedTerminalId
@@ -870,8 +839,15 @@ export function TerminalPane({
}, [clearPendingModifiers]);
const handleOutputChunkConsumed = useCallback((sequence: number) => {
outputDeliveryQueueRef.current?.consume({ sequence });
}, []);
terminalDebugLog({
scope: "terminal-pane",
event: "output:chunk:consumed",
details: {
sequence,
},
});
outputSession.consume({ sequence });
}, [outputSession]);
const toggleModifier = useCallback(
(modifier: keyof ModifierState) => {
@@ -1046,10 +1022,11 @@ export function TerminalPane({
contentInsetAdjustmentBehavior: "never",
}}
streamKey={`${scopeKey}:${selectedTerminal.id}`}
initialOutputText={selectedOutputSnapshot}
outputChunkText={selectedOutputChunk.text}
outputChunkSequence={selectedOutputChunk.sequence}
outputChunkReplay={selectedOutputChunk.replay}
initialOutputText={outputState.snapshotText}
initialOutputChunkSequence={outputState.snapshotSequence}
outputChunkText={outputState.chunkText}
outputChunkSequence={outputState.chunkSequence}
outputChunkReplay={outputState.chunkReplay}
testId="terminal-surface"
xtermTheme={xtermTheme}
swipeGesturesEnabled={swipeGesturesEnabled}