Improve daemon RPC resiliency and diff handling

This commit is contained in:
Mohamed Boudra
2026-02-04 10:21:36 +07:00
parent 10a2ef34a3
commit 79aec0069c
26 changed files with 467 additions and 109 deletions
@@ -45,7 +45,7 @@ import type { AgentPermissionResponse } from "@server/server/agent/agent-sdk-typ
import type { Agent } from "@/contexts/session-context";
import { useSessionStore } from "@/stores/session-store";
import { useFileExplorerActions } from "@/hooks/use-file-explorer-actions";
import type { DaemonClientV2 } from "@server/client/daemon-client-v2";
import type { DaemonClient } from "@server/client/daemon-client";
import { parseToolCallDisplay } from "@/utils/tool-call-parsers";
import { ToolCallDetailsContent } from "./tool-call-details";
import { ToolCallSheetProvider } from "./tool-call-sheet";
@@ -760,7 +760,7 @@ function PermissionRequestCard({
client,
}: {
permission: PendingPermission;
client: DaemonClientV2 | null;
client: DaemonClient | null;
}) {
const { theme } = useUnistyles();
const isMobile =
@@ -26,7 +26,7 @@ import Animated, {
} from "react-native-reanimated";
import { useDictation } from "@/hooks/use-dictation";
import { DictationOverlay } from "./dictation-controls";
import type { DaemonClientV2 } from "@server/client/daemon-client-v2";
import type { DaemonClient } from "@server/client/daemon-client";
import { usePanelStore } from "@/stores/panel-store";
import { useVoiceOptional } from "@/contexts/voice-context";
@@ -51,7 +51,7 @@ export interface MessageInputProps {
images?: ImageAttachment[];
onPickImages?: () => void;
onRemoveImage?: (index: number) => void;
client: DaemonClientV2 | null;
client: DaemonClient | null;
placeholder?: string;
autoFocus?: boolean;
disabled?: boolean;
@@ -20,7 +20,7 @@ import type {
} from "@server/shared/messages";
import type { AgentLifecycleStatus } from "@server/shared/agent-lifecycle";
import type { AgentPermissionRequest } from "@server/server/agent/agent-sdk-types";
import type { DaemonClientV2, ConnectionState } from "@server/client/daemon-client-v2";
import type { DaemonClient, ConnectionState } from "@server/client/daemon-client";
import { File } from "expo-file-system";
import { useDaemonConnections } from "./daemon-connections-context";
import {
@@ -1,8 +1,8 @@
import { generateMessageId } from "@/types/stream";
import type { DaemonClientV2 } from "@server/client/daemon-client-v2";
import type { DaemonClient } from "@server/client/daemon-client";
export type DictationStreamSenderParams = {
client: DaemonClientV2 | null;
client: DaemonClient | null;
format: string;
createDictationId?: () => string;
};
@@ -22,7 +22,7 @@ type DictationFinishResult = { dictationId: string; text: string };
* so enqueues can't "miss" a flush due to in-flight await/coalescing bugs.
*/
export class DictationStreamSender {
private client: DaemonClientV2 | null;
private client: DaemonClient | null;
private readonly format: string;
private readonly createDictationId: () => string;
@@ -40,7 +40,7 @@ export class DictationStreamSender {
this.createDictationId = params.createDictationId ?? generateMessageId;
}
setClient(client: DaemonClientV2 | null): void {
setClient(client: DaemonClient | null): void {
this.client = client;
}
@@ -1,12 +1,12 @@
import { useEffect, useRef, useCallback } from "react";
import { AppState, Platform } from "react-native";
import type { DaemonClientV2 } from "@server/client/daemon-client-v2";
import type { DaemonClient } from "@server/client/daemon-client";
const HEARTBEAT_INTERVAL_MS = 15_000;
const ACTIVITY_HEARTBEAT_THROTTLE_MS = 5_000;
interface ClientActivityOptions {
client: DaemonClientV2;
client: DaemonClient;
focusedAgentId: string | null;
}
+3 -3
View File
@@ -1,6 +1,6 @@
import { useEffect, useMemo } from "react";
import { AppState } from "react-native";
import { DaemonClientV2 } from "@server/client/daemon-client-v2";
import { DaemonClient } from "@server/client/daemon-client";
import { createTauriWebSocketTransportFactory } from "@/utils/tauri-daemon-transport";
function runDaemonRequest(label: string, promise: Promise<unknown>): void {
@@ -9,11 +9,11 @@ function runDaemonRequest(label: string, promise: Promise<unknown>): void {
});
}
export function useDaemonClient(url: string): DaemonClientV2 {
export function useDaemonClient(url: string): DaemonClient {
const client = useMemo(
() => {
const tauriTransportFactory = createTauriWebSocketTransportFactory();
return new DaemonClientV2({
return new DaemonClient({
url,
suppressSendErrors: true,
...(tauriTransportFactory
@@ -1,7 +1,7 @@
export type DictationStatus = "idle" | "recording" | "uploading" | "failed";
export type UseDictationOptions = {
client: import("@server/client/daemon-client-v2").DaemonClientV2 | null;
client: import("@server/client/daemon-client").DaemonClient | null;
onTranscript: (text: string, meta: { requestId: string }) => void;
onPartialTranscript?: (text: string, meta: { requestId: string }) => void;
onError?: (error: Error) => void;
@@ -3,7 +3,7 @@ import { Platform } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as Notifications from "expo-notifications";
import Constants from "expo-constants";
import type { DaemonClientV2 } from "@server/client/daemon-client-v2";
import type { DaemonClient } from "@server/client/daemon-client";
const STORAGE_PREFIX = "@paseo:expo-push-token:";
@@ -26,7 +26,7 @@ async function ensurePushPermission(): Promise<boolean> {
}
export function usePushTokenRegistration(params: {
client: DaemonClientV2;
client: DaemonClient;
serverId: string;
}): void {
const { client, serverId } = params;
+6 -6
View File
@@ -1,6 +1,6 @@
import { create } from "zustand";
import { subscribeWithSelector } from "zustand/middleware";
import type { DaemonClientV2 } from "@server/client/daemon-client-v2";
import type { DaemonClient } from "@server/client/daemon-client";
import type { useAudioPlayer } from "@/hooks/use-audio-player";
import type { AgentDirectoryEntry } from "@/types/agent-directory";
import type { StreamItem } from "@/types/stream";
@@ -150,7 +150,7 @@ export interface SessionState {
serverId: string;
// Daemon client (immutable reference)
client: DaemonClientV2 | null;
client: DaemonClient | null;
// Connection snapshot (mutable)
connection: DaemonConnectionSnapshot;
@@ -205,10 +205,10 @@ interface SessionStoreState {
// Action types
interface SessionStoreActions {
// Session management
initializeSession: (serverId: string, client: DaemonClientV2, audioPlayer: ReturnType<typeof useAudioPlayer>) => void;
initializeSession: (serverId: string, client: DaemonClient, audioPlayer: ReturnType<typeof useAudioPlayer>) => void;
clearSession: (serverId: string) => void;
getSession: (serverId: string) => SessionState | undefined;
updateSessionClient: (serverId: string, client: DaemonClientV2) => void;
updateSessionClient: (serverId: string, client: DaemonClient) => void;
updateSessionConnection: (serverId: string, connection: DaemonConnectionSnapshot) => void;
// Audio state
@@ -280,7 +280,7 @@ function logSessionStoreUpdate(
}
function createDefaultConnectionSnapshot(client?: DaemonClientV2 | null): DaemonConnectionSnapshot {
function createDefaultConnectionSnapshot(client?: DaemonClient | null): DaemonConnectionSnapshot {
if (!client) {
return { isConnected: false, isConnecting: false, lastError: null };
}
@@ -293,7 +293,7 @@ function createDefaultConnectionSnapshot(client?: DaemonClientV2 | null): Daemon
}
// Helper to create initial session state
function createInitialSessionState(serverId: string, client: DaemonClientV2, audioPlayer: ReturnType<typeof useAudioPlayer>): SessionState {
function createInitialSessionState(serverId: string, client: DaemonClient, audioPlayer: ReturnType<typeof useAudioPlayer>): SessionState {
return {
serverId,
client,
@@ -1,4 +1,4 @@
import type { DaemonTransport, DaemonTransportFactory } from "@server/client/daemon-client-v2";
import type { DaemonTransport, DaemonTransportFactory } from "@server/client/daemon-client";
type TauriWebSocketMessage =
| { type: "Text"; data: string }
@@ -1,5 +1,5 @@
import { DaemonClientV2 } from "@server/client/daemon-client-v2";
import type { ConnectionState } from "@server/client/daemon-client-v2";
import { DaemonClient } from "@server/client/daemon-client";
import type { ConnectionState } from "@server/client/daemon-client";
import { buildDaemonWebSocketUrl } from "./daemon-endpoints";
function normalizeNonEmptyString(value: unknown): string | null {
@@ -45,7 +45,7 @@ export async function testDaemonEndpointConnection(
const timeoutMs = options?.timeoutMs ?? 6000;
const url = buildDaemonWebSocketUrl(endpoint);
const client = new DaemonClientV2({
const client = new DaemonClient({
url,
suppressSendErrors: true,
});