feat(chat): unify session gateway with stable IDs and a single WS protocol

The frontend previously juggled placeholder IDs, provider-native IDs, and session_created handoffs, which caused race conditions and provider-specific branching. This introduces app-allocated session IDs, a chat run registry with event replay, delta sidebar updates, and one kind-based websocket contract so the UI can treat every provider the same while JSONL remains the source of truth.
This commit is contained in:
Haileyesus
2026-06-11 18:47:19 +03:00
parent 3d948217ef
commit f5eac2ec12
40 changed files with 2451 additions and 1226 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect, useRef } from 'react';
import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@@ -24,8 +24,7 @@ function AppContentInner() {
const { sessionId } = useParams<{ sessionId?: string }>();
const { t } = useTranslation('common');
const { isMobile } = useDeviceSettings({ trackPWA: false });
const { ws, sendMessage, latestMessage, isConnected } = useWebSocket();
const wasConnectedRef = useRef(false);
const { ws, sendMessage, subscribe } = useWebSocket();
const {
processingSessions,
@@ -52,7 +51,7 @@ function AppContentInner() {
} = useProjectsState({
sessionId,
navigate,
latestMessage,
subscribe,
isMobile,
activeSessions: processingSessions,
});
@@ -96,23 +95,9 @@ function AppContentInner() {
};
}, [navigate, refreshProjectsSilently, setActiveTab, setSidebarOpen]);
// Permission recovery: query pending permissions on WebSocket reconnect or session change
useEffect(() => {
const isReconnect = isConnected && !wasConnectedRef.current;
if (isReconnect) {
wasConnectedRef.current = true;
} else if (!isConnected) {
wasConnectedRef.current = false;
}
if (isConnected && selectedSession?.id) {
sendMessage({
type: 'get-pending-permissions',
sessionId: selectedSession.id
});
}
}, [isConnected, selectedSession?.id, sendMessage]);
// Pending tool permissions are recovered through the `chat.subscribe` flow:
// the `chat_subscribed` ack carries them on session open and on reconnect,
// so no separate permission-recovery message is needed here.
// Adjust the app container to stay above the virtual keyboard on iOS Safari.
// On Chrome for Android the layout viewport already shrinks when the keyboard opens,
@@ -177,7 +162,6 @@ function AppContentInner() {
setActiveTab={setActiveTab}
ws={ws}
sendMessage={sendMessage}
latestMessage={latestMessage}
isMobile={isMobile}
onMenuClick={() => setSidebarOpen(true)}
isLoading={isLoadingProjects}