Update files
This commit is contained in:
@@ -59,6 +59,7 @@ interface DaemonRegistryContextValue {
|
||||
removeDaemon: (id: string) => Promise<void>;
|
||||
upsertDaemonFromOffer: (offer: ConnectionOfferV1) => Promise<HostProfile>;
|
||||
upsertDaemonFromOfferUrl: (offerUrlOrFragment: string) => Promise<HostProfile>;
|
||||
adoptDaemonServerId: (currentId: string, serverId: string) => Promise<string>;
|
||||
}
|
||||
|
||||
const DaemonRegistryContext = createContext<DaemonRegistryContextValue | null>(null);
|
||||
@@ -129,6 +130,72 @@ export function DaemonRegistryProvider({ children }: { children: ReactNode }) {
|
||||
await persist(remaining);
|
||||
}, [persist, readDaemons]);
|
||||
|
||||
const adoptDaemonServerId = useCallback(
|
||||
async (currentId: string, serverId: string) => {
|
||||
const trimmed = serverId.trim();
|
||||
if (!trimmed) {
|
||||
throw new Error("serverId is required");
|
||||
}
|
||||
if (currentId === trimmed) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
const existing = readDaemons();
|
||||
const now = new Date().toISOString();
|
||||
const idx = existing.findIndex((daemon) => daemon.id === currentId);
|
||||
if (idx === -1) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
const current = existing[idx];
|
||||
const otherIdx = existing.findIndex((daemon) => daemon.id === trimmed);
|
||||
|
||||
const addLegacyId = (metadata: Record<string, unknown> | null | undefined, legacyId: string) => {
|
||||
const prev = (metadata ?? {}) as Record<string, unknown>;
|
||||
const legacyIdsRaw = prev.legacyIds;
|
||||
const legacyIds = Array.isArray(legacyIdsRaw)
|
||||
? legacyIdsRaw.filter((value) => typeof value === "string")
|
||||
: [];
|
||||
if (!legacyIds.includes(legacyId)) {
|
||||
legacyIds.push(legacyId);
|
||||
}
|
||||
return { ...prev, legacyIds };
|
||||
};
|
||||
|
||||
// Merge into existing canonical entry if it exists.
|
||||
if (otherIdx !== -1) {
|
||||
const canonical = existing[otherIdx];
|
||||
const merged: HostProfile = {
|
||||
...canonical,
|
||||
label: canonical.label?.trim() ? canonical.label : current.label,
|
||||
endpoints: Array.from(new Set([...(canonical.endpoints ?? []), ...(current.endpoints ?? [])])),
|
||||
daemonPublicKeyB64: canonical.daemonPublicKeyB64 ?? current.daemonPublicKeyB64,
|
||||
relay: canonical.relay ?? current.relay ?? null,
|
||||
createdAt: canonical.createdAt ?? current.createdAt,
|
||||
updatedAt: now,
|
||||
metadata: addLegacyId(canonical.metadata, currentId),
|
||||
};
|
||||
const next = existing.filter((daemon) => daemon.id !== currentId);
|
||||
next[otherIdx > idx ? otherIdx - 1 : otherIdx] = merged;
|
||||
await persist(next);
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
const updated: HostProfile = {
|
||||
...current,
|
||||
id: trimmed,
|
||||
updatedAt: now,
|
||||
metadata: addLegacyId(current.metadata, currentId),
|
||||
};
|
||||
|
||||
const next = [...existing];
|
||||
next[idx] = updated;
|
||||
await persist(next);
|
||||
return trimmed;
|
||||
},
|
||||
[persist, readDaemons]
|
||||
);
|
||||
|
||||
const upsertDaemonFromOffer = useCallback(
|
||||
async (offer: ConnectionOfferV1) => {
|
||||
const existing = readDaemons();
|
||||
@@ -194,6 +261,7 @@ export function DaemonRegistryProvider({ children }: { children: ReactNode }) {
|
||||
addDaemon,
|
||||
updateDaemon,
|
||||
removeDaemon,
|
||||
adoptDaemonServerId,
|
||||
upsertDaemonFromOffer,
|
||||
upsertDaemonFromOfferUrl,
|
||||
};
|
||||
@@ -389,3 +457,16 @@ export function buildDirectDaemonWsUrl(profile: HostProfile): string {
|
||||
}
|
||||
return buildDaemonWebSocketUrl(endpoint);
|
||||
}
|
||||
|
||||
export function resolveCanonicalDaemonId(daemons: HostProfile[], id: string): string | null {
|
||||
const direct = daemons.find((daemon) => daemon.id === id);
|
||||
if (direct) return direct.id;
|
||||
|
||||
for (const daemon of daemons) {
|
||||
const legacyIds = (daemon.metadata as any)?.legacyIds;
|
||||
if (Array.isArray(legacyIds) && legacyIds.some((value) => value === id)) {
|
||||
return daemon.id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRef, ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { AppState, Platform } from "react-native";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useDaemonClient } from "@/hooks/use-daemon-client";
|
||||
import { useAudioPlayer } from "@/hooks/use-audio-player";
|
||||
import { useClientActivity } from "@/hooks/use-client-activity";
|
||||
@@ -23,6 +23,7 @@ import type { AgentPermissionRequest } from "@server/server/agent/agent-sdk-type
|
||||
import type { DaemonClient, ConnectionState } from "@server/client/daemon-client";
|
||||
import { File } from "expo-file-system";
|
||||
import { useDaemonConnections } from "./daemon-connections-context";
|
||||
import { useDaemonRegistry } from "./daemon-registry-context";
|
||||
import {
|
||||
useSessionStore,
|
||||
type SessionState,
|
||||
@@ -259,18 +260,23 @@ interface SessionProviderProps {
|
||||
serverId: string;
|
||||
}
|
||||
|
||||
const FORM_PREFERENCES_STORAGE_KEY = "@paseo:create-agent-preferences";
|
||||
const FORM_PREFERENCES_QUERY_KEY = ["form-preferences"];
|
||||
|
||||
// SessionProvider: Daemon client message handler that updates Zustand store
|
||||
export function SessionProvider({
|
||||
children,
|
||||
serverUrl,
|
||||
serverId,
|
||||
}: SessionProviderProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const client = useDaemonClient(serverUrl);
|
||||
const [connectionSnapshot, setConnectionSnapshot] =
|
||||
useState<DaemonConnectionSnapshot>(() =>
|
||||
mapConnectionState(client.getConnectionState(), client.lastError)
|
||||
);
|
||||
const { updateConnectionStatus } = useDaemonConnections();
|
||||
const { adoptDaemonServerId } = useDaemonRegistry();
|
||||
|
||||
// Zustand store actions
|
||||
const initializeSession = useSessionStore((state) => state.initializeSession);
|
||||
@@ -349,6 +355,7 @@ export function SessionProvider({
|
||||
);
|
||||
const attentionNotifiedRef = useRef<Map<string, number>>(new Map());
|
||||
const appStateRef = useRef(AppState.currentState);
|
||||
const hasAdoptedServerIdRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = AppState.addEventListener("change", (nextState) => {
|
||||
@@ -443,6 +450,47 @@ export function SessionProvider({
|
||||
return unsubscribe;
|
||||
}, [client]);
|
||||
|
||||
// Daemon identity handshake: adopt stable serverId from the daemon.
|
||||
useEffect(() => {
|
||||
return client.on("status", (message) => {
|
||||
if (message.type !== "status") return;
|
||||
const payload = message.payload as { status?: unknown; serverId?: unknown };
|
||||
if (payload?.status !== "server_info") return;
|
||||
|
||||
const remoteServerId = typeof payload.serverId === "string" ? payload.serverId.trim() : "";
|
||||
if (!remoteServerId) return;
|
||||
|
||||
// Avoid loops if multiple server_info messages arrive.
|
||||
if (hasAdoptedServerIdRef.current) return;
|
||||
hasAdoptedServerIdRef.current = true;
|
||||
|
||||
if (remoteServerId === serverId) {
|
||||
return;
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
const canonicalId = await adoptDaemonServerId(serverId, remoteServerId);
|
||||
|
||||
// Migrate persisted "create agent" preferences if they reference the old id.
|
||||
try {
|
||||
const stored = await AsyncStorage.getItem(FORM_PREFERENCES_STORAGE_KEY);
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored) as { serverId?: unknown } & Record<string, unknown>;
|
||||
if (typeof parsed.serverId === "string" && parsed.serverId === serverId) {
|
||||
const next = { ...parsed, serverId: canonicalId };
|
||||
await AsyncStorage.setItem(FORM_PREFERENCES_STORAGE_KEY, JSON.stringify(next));
|
||||
queryClient.setQueryData(FORM_PREFERENCES_QUERY_KEY, next);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("[Session] Failed to migrate form preferences serverId", { error });
|
||||
}
|
||||
})().catch((error) => {
|
||||
console.warn("[Session] Failed to adopt daemon serverId", { error });
|
||||
});
|
||||
});
|
||||
}, [adoptDaemonServerId, client, queryClient, serverId]);
|
||||
|
||||
useEffect(() => {
|
||||
updateSessionConnection(serverId, connectionSnapshot);
|
||||
}, [serverId, connectionSnapshot, updateSessionConnection]);
|
||||
|
||||
Reference in New Issue
Block a user