Switch to double quotes and reformat codebase with Biome
This commit is contained in:
@@ -1,11 +1,6 @@
|
||||
import { createContext, useContext, useEffect, useRef, type ReactNode } from "react";
|
||||
import { useWindowDimensions } from "react-native";
|
||||
import {
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
Easing,
|
||||
type SharedValue,
|
||||
} from "react-native-reanimated";
|
||||
import { useSharedValue, withTiming, Easing, type SharedValue } from "react-native-reanimated";
|
||||
import { type GestureType } from "react-native-gesture-handler";
|
||||
import { UnistylesRuntime } from "react-native-unistyles";
|
||||
import { usePanelStore } from "@/stores/panel-store";
|
||||
@@ -22,12 +17,13 @@ interface ExplorerSidebarAnimationContextValue {
|
||||
closeGestureRef: React.MutableRefObject<GestureType | undefined>;
|
||||
}
|
||||
|
||||
const ExplorerSidebarAnimationContext = createContext<ExplorerSidebarAnimationContextValue | null>(null);
|
||||
const ExplorerSidebarAnimationContext = createContext<ExplorerSidebarAnimationContextValue | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
export function ExplorerSidebarAnimationProvider({ children }: { children: ReactNode }) {
|
||||
const { width: windowWidth } = useWindowDimensions();
|
||||
const isMobile =
|
||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||
const isMobile = UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||
const mobileView = usePanelStore((state) => state.mobileView);
|
||||
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
|
||||
|
||||
@@ -130,7 +126,9 @@ export function ExplorerSidebarAnimationProvider({ children }: { children: React
|
||||
export function useExplorerSidebarAnimation() {
|
||||
const context = useContext(ExplorerSidebarAnimationContext);
|
||||
if (!context) {
|
||||
throw new Error("useExplorerSidebarAnimation must be used within ExplorerSidebarAnimationProvider");
|
||||
throw new Error(
|
||||
"useExplorerSidebarAnimation must be used within ExplorerSidebarAnimationProvider",
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export function HorizontalScrollProvider({ children }: { children: ReactNode })
|
||||
scrollOffsetsRef.current.set(id, offset);
|
||||
updateIsAnyScrolled();
|
||||
},
|
||||
[updateIsAnyScrolled]
|
||||
[updateIsAnyScrolled],
|
||||
);
|
||||
|
||||
const unregisterScrollOffset = useCallback(
|
||||
@@ -39,7 +39,7 @@ export function HorizontalScrollProvider({ children }: { children: ReactNode })
|
||||
scrollOffsetsRef.current.delete(id);
|
||||
updateIsAnyScrolled();
|
||||
},
|
||||
[updateIsAnyScrolled]
|
||||
[updateIsAnyScrolled],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,10 +5,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useClientActivity } from "@/hooks/use-client-activity";
|
||||
import { usePushTokenRegistration } from "@/hooks/use-push-token-registration";
|
||||
import { clearArchiveAgentPending } from "@/hooks/use-archive-agent";
|
||||
import {
|
||||
generateMessageId,
|
||||
type StreamItem,
|
||||
} from "@/types/stream";
|
||||
import { generateMessageId, type StreamItem } from "@/types/stream";
|
||||
import {
|
||||
processTimelineResponse,
|
||||
processAgentStreamEvent,
|
||||
@@ -27,14 +24,8 @@ import {
|
||||
import type { AgentLifecycleStatus } from "@server/shared/agent-lifecycle";
|
||||
import type { DaemonClient } from "@server/client/daemon-client";
|
||||
import { File } from "expo-file-system";
|
||||
import {
|
||||
getHostRuntimeStore,
|
||||
useHostRuntimeIsConnected,
|
||||
} from "@/runtime/host-runtime";
|
||||
import {
|
||||
useVoiceAudioEngineOptional,
|
||||
useVoiceRuntimeOptional,
|
||||
} from "@/contexts/voice-context";
|
||||
import { getHostRuntimeStore, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
||||
import { useVoiceAudioEngineOptional, useVoiceRuntimeOptional } from "@/contexts/voice-context";
|
||||
import type { AudioPlaybackSource } from "@/voice/audio-engine-types";
|
||||
import {
|
||||
useSessionStore,
|
||||
@@ -54,10 +45,7 @@ import {
|
||||
rejectInitDeferred,
|
||||
} from "@/utils/agent-initialization";
|
||||
import { encodeImages } from "@/utils/encode-images";
|
||||
import {
|
||||
derivePendingPermissionKey,
|
||||
normalizeAgentSnapshot,
|
||||
} from "@/utils/agent-snapshots";
|
||||
import { derivePendingPermissionKey, normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
||||
import { resolveProjectPlacement } from "@/utils/project-placement";
|
||||
import { buildDraftStoreKey } from "@/stores/draft-keys";
|
||||
import type { AttachmentMetadata } from "@/attachments/types";
|
||||
@@ -78,10 +66,7 @@ export type {
|
||||
const HISTORY_STALE_AFTER_MS = 60_000;
|
||||
const AUTHORITATIVE_REVALIDATION_DEBOUNCE_MS = 300;
|
||||
|
||||
type AudioOutputPayload = Extract<
|
||||
SessionOutboundMessage,
|
||||
{ type: "audio_output" }
|
||||
>["payload"];
|
||||
type AudioOutputPayload = Extract<SessionOutboundMessage, { type: "audio_output" }>["payload"];
|
||||
|
||||
interface BufferedAudioChunk {
|
||||
chunkIndex: number;
|
||||
@@ -94,9 +79,7 @@ function decodeBase64Chunk(base64: string): Uint8Array {
|
||||
return Buffer.from(base64, "base64");
|
||||
}
|
||||
|
||||
function buildAudioPlaybackSource(
|
||||
chunks: BufferedAudioChunk[]
|
||||
): AudioPlaybackSource {
|
||||
function buildAudioPlaybackSource(chunks: BufferedAudioChunk[]): AudioPlaybackSource {
|
||||
const decodedChunks = chunks.map((chunk) => decodeBase64Chunk(chunk.audio));
|
||||
const totalSize = decodedChunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
||||
const output = new Uint8Array(totalSize);
|
||||
@@ -111,7 +94,7 @@ function buildAudioPlaybackSource(
|
||||
format === "pcm"
|
||||
? "audio/pcm;rate=24000;bits=16"
|
||||
: format === "mp3"
|
||||
? "audio/mpeg"
|
||||
? "audio/mpeg"
|
||||
: `audio/${format}`;
|
||||
|
||||
const bytes = output.slice();
|
||||
@@ -119,10 +102,7 @@ function buildAudioPlaybackSource(
|
||||
size: bytes.byteLength,
|
||||
type: mimeType,
|
||||
async arrayBuffer() {
|
||||
return bytes.buffer.slice(
|
||||
bytes.byteOffset,
|
||||
bytes.byteOffset + bytes.byteLength
|
||||
);
|
||||
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -139,7 +119,7 @@ const findLatestAssistantMessageText = (items: StreamItem[]): string | null => {
|
||||
|
||||
const getLatestPermissionRequest = (
|
||||
session: SessionState | undefined,
|
||||
agentId: string
|
||||
agentId: string,
|
||||
): NotificationPermissionRequest | null => {
|
||||
if (!session) {
|
||||
return null;
|
||||
@@ -173,10 +153,7 @@ type FileDownloadTokenPayload = Extract<
|
||||
{ type: "file_download_token_response" }
|
||||
>["payload"];
|
||||
|
||||
type AgentUpdatePayload = Extract<
|
||||
SessionOutboundMessage,
|
||||
{ type: "agent_update" }
|
||||
>["payload"];
|
||||
type AgentUpdatePayload = Extract<SessionOutboundMessage, { type: "agent_update" }>["payload"];
|
||||
type WorkspaceUpdatePayload = Extract<
|
||||
SessionOutboundMessage,
|
||||
{ type: "workspace_update" }
|
||||
@@ -197,14 +174,14 @@ function pendingKey(serverId: string, agentId: string): string {
|
||||
export function bufferPendingAgentUpdate(
|
||||
serverId: string,
|
||||
agentId: string,
|
||||
update: AgentUpdatePayload
|
||||
update: AgentUpdatePayload,
|
||||
): void {
|
||||
pendingAgentUpdates.set(pendingKey(serverId, agentId), update);
|
||||
}
|
||||
|
||||
export function flushPendingAgentUpdate(
|
||||
serverId: string,
|
||||
agentId: string
|
||||
agentId: string,
|
||||
): AgentUpdatePayload | undefined {
|
||||
const key = pendingKey(serverId, agentId);
|
||||
const update = pendingAgentUpdates.get(key);
|
||||
@@ -212,10 +189,7 @@ export function flushPendingAgentUpdate(
|
||||
return update;
|
||||
}
|
||||
|
||||
export function deletePendingAgentUpdate(
|
||||
serverId: string,
|
||||
agentId: string
|
||||
): void {
|
||||
export function deletePendingAgentUpdate(serverId: string, agentId: string): void {
|
||||
pendingAgentUpdates.delete(pendingKey(serverId, agentId));
|
||||
}
|
||||
|
||||
@@ -238,11 +212,7 @@ interface SessionProviderClientProps extends SessionProviderSharedProps {
|
||||
|
||||
export type SessionProviderProps = SessionProviderClientProps;
|
||||
|
||||
function SessionProviderWithClient({
|
||||
children,
|
||||
serverId,
|
||||
client,
|
||||
}: SessionProviderClientProps) {
|
||||
function SessionProviderWithClient({ children, serverId, client }: SessionProviderClientProps) {
|
||||
return (
|
||||
<SessionProviderInternal serverId={serverId} client={client}>
|
||||
{children}
|
||||
@@ -255,11 +225,7 @@ export function SessionProvider(props: SessionProviderProps) {
|
||||
return <SessionProviderWithClient {...props} />;
|
||||
}
|
||||
|
||||
function SessionProviderInternal({
|
||||
children,
|
||||
serverId,
|
||||
client,
|
||||
}: SessionProviderClientProps) {
|
||||
function SessionProviderInternal({ children, serverId, client }: SessionProviderClientProps) {
|
||||
const voiceRuntime = useVoiceRuntimeOptional();
|
||||
const voiceAudioEngine = useVoiceAudioEngineOptional();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -270,55 +236,29 @@ function SessionProviderInternal({
|
||||
const clearSession = useSessionStore((state) => state.clearSession);
|
||||
const setIsPlayingAudio = useSessionStore((state) => state.setIsPlayingAudio);
|
||||
const setMessages = useSessionStore((state) => state.setMessages);
|
||||
const setCurrentAssistantMessage = useSessionStore(
|
||||
(state) => state.setCurrentAssistantMessage
|
||||
);
|
||||
const setAgentStreamTail = useSessionStore(
|
||||
(state) => state.setAgentStreamTail
|
||||
);
|
||||
const setAgentStreamHead = useSessionStore(
|
||||
(state) => state.setAgentStreamHead
|
||||
);
|
||||
const setAgentStreamState = useSessionStore(
|
||||
(state) => state.setAgentStreamState
|
||||
);
|
||||
const clearAgentStreamHead = useSessionStore(
|
||||
(state) => state.clearAgentStreamHead
|
||||
);
|
||||
const setAgentTimelineCursor = useSessionStore(
|
||||
(state) => state.setAgentTimelineCursor
|
||||
);
|
||||
const setInitializingAgents = useSessionStore(
|
||||
(state) => state.setInitializingAgents
|
||||
);
|
||||
const bumpHistorySyncGeneration = useSessionStore(
|
||||
(state) => state.bumpHistorySyncGeneration
|
||||
);
|
||||
const setCurrentAssistantMessage = useSessionStore((state) => state.setCurrentAssistantMessage);
|
||||
const setAgentStreamTail = useSessionStore((state) => state.setAgentStreamTail);
|
||||
const setAgentStreamHead = useSessionStore((state) => state.setAgentStreamHead);
|
||||
const setAgentStreamState = useSessionStore((state) => state.setAgentStreamState);
|
||||
const clearAgentStreamHead = useSessionStore((state) => state.clearAgentStreamHead);
|
||||
const setAgentTimelineCursor = useSessionStore((state) => state.setAgentTimelineCursor);
|
||||
const setInitializingAgents = useSessionStore((state) => state.setInitializingAgents);
|
||||
const bumpHistorySyncGeneration = useSessionStore((state) => state.bumpHistorySyncGeneration);
|
||||
const markAgentHistorySynchronized = useSessionStore(
|
||||
(state) => state.markAgentHistorySynchronized
|
||||
(state) => state.markAgentHistorySynchronized,
|
||||
);
|
||||
const setAgentAuthoritativeHistoryApplied = useSessionStore(
|
||||
(state) => state.setAgentAuthoritativeHistoryApplied
|
||||
);
|
||||
const setHasHydratedAgents = useSessionStore(
|
||||
(state) => state.setHasHydratedAgents
|
||||
);
|
||||
const setHasHydratedWorkspaces = useSessionStore(
|
||||
(state) => state.setHasHydratedWorkspaces
|
||||
(state) => state.setAgentAuthoritativeHistoryApplied,
|
||||
);
|
||||
const setHasHydratedAgents = useSessionStore((state) => state.setHasHydratedAgents);
|
||||
const setHasHydratedWorkspaces = useSessionStore((state) => state.setHasHydratedWorkspaces);
|
||||
const setAgents = useSessionStore((state) => state.setAgents);
|
||||
const setWorkspaces = useSessionStore((state) => state.setWorkspaces);
|
||||
const mergeWorkspaces = useSessionStore((state) => state.mergeWorkspaces);
|
||||
const removeWorkspace = useSessionStore((state) => state.removeWorkspace);
|
||||
const setAgentLastActivity = useSessionStore(
|
||||
(state) => state.setAgentLastActivity
|
||||
);
|
||||
const flushAgentLastActivity = useSessionStore(
|
||||
(state) => state.flushAgentLastActivity
|
||||
);
|
||||
const setPendingPermissions = useSessionStore(
|
||||
(state) => state.setPendingPermissions
|
||||
);
|
||||
const setAgentLastActivity = useSessionStore((state) => state.setAgentLastActivity);
|
||||
const flushAgentLastActivity = useSessionStore((state) => state.flushAgentLastActivity);
|
||||
const setPendingPermissions = useSessionStore((state) => state.setPendingPermissions);
|
||||
const clearDraftInput = useDraftStore((state) => state.clearDraftInput);
|
||||
const setQueuedMessages = useSessionStore((state) => state.setQueuedMessages);
|
||||
const updateSessionClient = useSessionStore((state) => state.updateSessionClient);
|
||||
@@ -326,37 +266,22 @@ function SessionProviderInternal({
|
||||
|
||||
// Track focused agent for heartbeat
|
||||
const focusedAgentId = useSessionStore(
|
||||
(state) => state.sessions[serverId]?.focusedAgentId ?? null
|
||||
);
|
||||
const sessionAgents = useSessionStore(
|
||||
(state) => state.sessions[serverId]?.agents
|
||||
(state) => state.sessions[serverId]?.focusedAgentId ?? null,
|
||||
);
|
||||
const sessionAgents = useSessionStore((state) => state.sessions[serverId]?.agents);
|
||||
|
||||
const previousAgentStatusRef = useRef<Map<string, AgentLifecycleStatus>>(
|
||||
new Map()
|
||||
);
|
||||
const previousAgentStatusRef = useRef<Map<string, AgentLifecycleStatus>>(new Map());
|
||||
const sendAgentMessageRef = useRef<
|
||||
| ((
|
||||
agentId: string,
|
||||
message: string,
|
||||
images?: AttachmentMetadata[]
|
||||
) => Promise<void>)
|
||||
| null
|
||||
((agentId: string, message: string, images?: AttachmentMetadata[]) => Promise<void>) | null
|
||||
>(null);
|
||||
const sessionStateTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
|
||||
null
|
||||
);
|
||||
const sessionStateTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const attentionNotifiedRef = useRef<Map<string, number>>(new Map());
|
||||
const appStateRef = useRef(AppState.currentState);
|
||||
const revalidationTimerRef = useRef<ReturnType<typeof setTimeout> | null>(
|
||||
null
|
||||
);
|
||||
const revalidationTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const revalidationInFlightRef = useRef<Promise<void> | null>(null);
|
||||
const revalidationQueuedRef = useRef(false);
|
||||
const wasConnectedRef = useRef(isConnected);
|
||||
const audioOutputBuffersRef = useRef<Map<string, BufferedAudioChunk[]>>(
|
||||
new Map()
|
||||
);
|
||||
const audioOutputBuffersRef = useRef<Map<string, BufferedAudioChunk[]>>(new Map());
|
||||
const activeAudioGroupsRef = useRef<Set<string>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
@@ -420,7 +345,7 @@ function SessionProviderInternal({
|
||||
setWorkspaces(serverId, workspaces);
|
||||
setHasHydratedWorkspaces(serverId, true);
|
||||
},
|
||||
[client, isConnected, serverId, setHasHydratedWorkspaces, setWorkspaces]
|
||||
[client, isConnected, serverId, setHasHydratedWorkspaces, setWorkspaces],
|
||||
);
|
||||
|
||||
const applyAuthoritativeAgentSnapshot = useCallback(
|
||||
@@ -523,7 +448,7 @@ function SessionProviderInternal({
|
||||
setAgents,
|
||||
setPendingPermissions,
|
||||
setQueuedMessages,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
const runAuthoritativeRevalidation = useCallback(async () => {
|
||||
@@ -596,7 +521,7 @@ function SessionProviderInternal({
|
||||
}
|
||||
bumpHistorySyncGeneration(serverId);
|
||||
},
|
||||
[bumpHistorySyncGeneration, scheduleAuthoritativeRevalidation, serverId]
|
||||
[bumpHistorySyncGeneration, scheduleAuthoritativeRevalidation, serverId],
|
||||
);
|
||||
|
||||
// Client activity tracking (heartbeat, push token registration)
|
||||
@@ -617,8 +542,7 @@ function SessionProviderInternal({
|
||||
return;
|
||||
}
|
||||
const isActivelyVisible = getIsAppActivelyVisible(appState);
|
||||
const isAwayFromAgent =
|
||||
!isActivelyVisible || focusedAgentId !== params.agentId;
|
||||
const isAwayFromAgent = !isActivelyVisible || focusedAgentId !== params.agentId;
|
||||
if (!isAwayFromAgent) {
|
||||
return;
|
||||
}
|
||||
@@ -633,12 +557,8 @@ function SessionProviderInternal({
|
||||
const head = session?.agentStreamHead.get(params.agentId) ?? [];
|
||||
const tail = session?.agentStreamTail.get(params.agentId) ?? [];
|
||||
const assistantMessage =
|
||||
findLatestAssistantMessageText(head) ??
|
||||
findLatestAssistantMessageText(tail);
|
||||
const permissionRequest = getLatestPermissionRequest(
|
||||
session,
|
||||
params.agentId
|
||||
);
|
||||
findLatestAssistantMessageText(head) ?? findLatestAssistantMessageText(tail);
|
||||
const permissionRequest = getLatestPermissionRequest(session, params.agentId);
|
||||
|
||||
const notification =
|
||||
params.notification ??
|
||||
@@ -647,10 +567,7 @@ function SessionProviderInternal({
|
||||
serverId,
|
||||
agentId: params.agentId,
|
||||
assistantMessage: params.reason === "finished" ? assistantMessage : null,
|
||||
permissionRequest:
|
||||
params.reason === "permission"
|
||||
? permissionRequest
|
||||
: null,
|
||||
permissionRequest: params.reason === "permission" ? permissionRequest : null,
|
||||
});
|
||||
|
||||
void sendOsNotification({
|
||||
@@ -659,7 +576,7 @@ function SessionProviderInternal({
|
||||
data: notification.data,
|
||||
});
|
||||
},
|
||||
[serverId]
|
||||
[serverId],
|
||||
);
|
||||
|
||||
// Initialize session in store
|
||||
@@ -676,7 +593,7 @@ function SessionProviderInternal({
|
||||
return;
|
||||
}
|
||||
|
||||
return voiceRuntime.registerSession({
|
||||
return voiceRuntime.registerSession({
|
||||
serverId,
|
||||
setVoiceMode: async (enabled, agentId) => {
|
||||
if (!client) {
|
||||
@@ -706,12 +623,7 @@ function SessionProviderInternal({
|
||||
setIsPlayingAudio(serverId, isPlaying);
|
||||
},
|
||||
});
|
||||
}, [
|
||||
client,
|
||||
serverId,
|
||||
setIsPlayingAudio,
|
||||
voiceRuntime,
|
||||
]);
|
||||
}, [client, serverId, setIsPlayingAudio, voiceRuntime]);
|
||||
|
||||
useEffect(() => {
|
||||
voiceRuntime?.updateSessionConnection(serverId, isConnected);
|
||||
@@ -818,7 +730,7 @@ function SessionProviderInternal({
|
||||
setAgents,
|
||||
setPendingPermissions,
|
||||
setAgentTimelineCursor,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
const requestCanonicalCatchUp = useCallback(
|
||||
@@ -831,14 +743,10 @@ function SessionProviderInternal({
|
||||
projection: "canonical",
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn(
|
||||
"[Session] failed to fetch canonical catch-up timeline",
|
||||
agentId,
|
||||
error
|
||||
);
|
||||
console.warn("[Session] failed to fetch canonical catch-up timeline", agentId, error);
|
||||
});
|
||||
},
|
||||
[client]
|
||||
[client],
|
||||
);
|
||||
|
||||
const applyTimelineResponse = useCallback(
|
||||
@@ -846,7 +754,7 @@ function SessionProviderInternal({
|
||||
payload: Extract<
|
||||
SessionOutboundMessage,
|
||||
{ type: "fetch_agent_timeline_response" }
|
||||
>["payload"]
|
||||
>["payload"],
|
||||
) => {
|
||||
const agentId = payload.agentId;
|
||||
const initKey = getInitKey(serverId, agentId);
|
||||
@@ -992,7 +900,7 @@ function SessionProviderInternal({
|
||||
setAgentStreamTail,
|
||||
setAgentTimelineCursor,
|
||||
setInitializingAgents,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1027,8 +935,7 @@ function SessionProviderInternal({
|
||||
const initKey = getInitKey(serverId, agentId);
|
||||
const session = useSessionStore.getState().sessions[serverId];
|
||||
const isSyncingHistory =
|
||||
session?.initializingAgents.get(agentId) === true &&
|
||||
Boolean(getInitDeferred(initKey));
|
||||
session?.initializingAgents.get(agentId) === true && Boolean(getInitDeferred(initKey));
|
||||
|
||||
if (isSyncingHistory) {
|
||||
bufferPendingAgentUpdate(serverId, agentId, update);
|
||||
@@ -1160,13 +1067,10 @@ function SessionProviderInternal({
|
||||
// on status changes, which is sufficient for sorting and display purposes.
|
||||
});
|
||||
|
||||
const unsubAgentTimeline = client.on(
|
||||
"fetch_agent_timeline_response",
|
||||
(message) => {
|
||||
if (message.type !== "fetch_agent_timeline_response") return;
|
||||
applyTimelineResponse(message.payload);
|
||||
}
|
||||
);
|
||||
const unsubAgentTimeline = client.on("fetch_agent_timeline_response", (message) => {
|
||||
if (message.type !== "fetch_agent_timeline_response") return;
|
||||
applyTimelineResponse(message.payload);
|
||||
});
|
||||
|
||||
const unsubWorkspaceUpdate = client.on("workspace_update", (message) => {
|
||||
if (message.type !== "workspace_update") return;
|
||||
@@ -1174,9 +1078,7 @@ function SessionProviderInternal({
|
||||
removeWorkspace(serverId, message.payload.id);
|
||||
return;
|
||||
}
|
||||
mergeWorkspaces(serverId, [
|
||||
normalizeWorkspaceDescriptor(message.payload.workspace),
|
||||
]);
|
||||
mergeWorkspaces(serverId, [normalizeWorkspaceDescriptor(message.payload.workspace)]);
|
||||
});
|
||||
|
||||
const unsubStatus = client.on("status", (message) => {
|
||||
@@ -1187,53 +1089,42 @@ function SessionProviderInternal({
|
||||
serverId: serverInfo.serverId,
|
||||
hostname: serverInfo.hostname,
|
||||
version: serverInfo.version,
|
||||
...(serverInfo.capabilities
|
||||
? { capabilities: serverInfo.capabilities }
|
||||
: {}),
|
||||
...(serverInfo.capabilities ? { capabilities: serverInfo.capabilities } : {}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
const unsubPermissionRequest = client.on(
|
||||
"agent_permission_request",
|
||||
(message) => {
|
||||
if (message.type !== "agent_permission_request") return;
|
||||
const { agentId, request } = message.payload;
|
||||
const unsubPermissionRequest = client.on("agent_permission_request", (message) => {
|
||||
if (message.type !== "agent_permission_request") return;
|
||||
const { agentId, request } = message.payload;
|
||||
|
||||
setPendingPermissions(serverId, (prev) => {
|
||||
const next = new Map(prev);
|
||||
const key = derivePendingPermissionKey(agentId, request);
|
||||
next.set(key, { key, agentId, request });
|
||||
return next;
|
||||
});
|
||||
}
|
||||
);
|
||||
setPendingPermissions(serverId, (prev) => {
|
||||
const next = new Map(prev);
|
||||
const key = derivePendingPermissionKey(agentId, request);
|
||||
next.set(key, { key, agentId, request });
|
||||
return next;
|
||||
});
|
||||
});
|
||||
|
||||
const unsubPermissionResolved = client.on(
|
||||
"agent_permission_resolved",
|
||||
(message) => {
|
||||
if (message.type !== "agent_permission_resolved") return;
|
||||
const { requestId, agentId } = message.payload;
|
||||
const unsubPermissionResolved = client.on("agent_permission_resolved", (message) => {
|
||||
if (message.type !== "agent_permission_resolved") return;
|
||||
const { requestId, agentId } = message.payload;
|
||||
|
||||
setPendingPermissions(serverId, (prev) => {
|
||||
const next = new Map(prev);
|
||||
const derivedKey = `${agentId}:${requestId}`;
|
||||
if (!next.delete(derivedKey)) {
|
||||
for (const [key, pending] of next.entries()) {
|
||||
if (
|
||||
pending.agentId === agentId &&
|
||||
pending.request.id === requestId
|
||||
) {
|
||||
next.delete(key);
|
||||
break;
|
||||
}
|
||||
setPendingPermissions(serverId, (prev) => {
|
||||
const next = new Map(prev);
|
||||
const derivedKey = `${agentId}:${requestId}`;
|
||||
if (!next.delete(derivedKey)) {
|
||||
for (const [key, pending] of next.entries()) {
|
||||
if (pending.agentId === agentId && pending.request.id === requestId) {
|
||||
next.delete(key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
});
|
||||
|
||||
const unsubAudioOutput = client.on("audio_output", async (message) => {
|
||||
if (message.type !== "audio_output") return;
|
||||
@@ -1273,16 +1164,15 @@ function SessionProviderInternal({
|
||||
bufferedChunks.sort((left, right) => left.chunkIndex - right.chunkIndex);
|
||||
const chunkIds = bufferedChunks.map((chunk) => chunk.id);
|
||||
const shouldPlay =
|
||||
!payload.isVoiceMode ||
|
||||
(voiceRuntime?.shouldPlayVoiceAudio(serverId) ?? false);
|
||||
!payload.isVoiceMode || (voiceRuntime?.shouldPlayVoiceAudio(serverId) ?? false);
|
||||
const audioBlob = buildAudioPlaybackSource(bufferedChunks);
|
||||
const confirmAudioPlayed = async () => {
|
||||
await Promise.all(
|
||||
chunkIds.map((chunkId) =>
|
||||
client.audioPlayed(chunkId).catch((error) => {
|
||||
console.warn("[Session] Failed to confirm audio playback:", error);
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1352,17 +1242,13 @@ function SessionProviderInternal({
|
||||
prev.map((msg) =>
|
||||
msg.type === "tool_call" && msg.id === toolCallId
|
||||
? { ...msg, result, status: "completed" as const }
|
||||
: msg
|
||||
)
|
||||
: msg,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
data.type === "error" &&
|
||||
data.metadata &&
|
||||
"toolCallId" in data.metadata
|
||||
) {
|
||||
if (data.type === "error" && data.metadata && "toolCallId" in data.metadata) {
|
||||
const { toolCallId, error } = data.metadata as {
|
||||
toolCallId: string;
|
||||
error: unknown;
|
||||
@@ -1372,8 +1258,8 @@ function SessionProviderInternal({
|
||||
prev.map((msg) =>
|
||||
msg.type === "tool_call" && msg.id === toolCallId
|
||||
? { ...msg, error, status: "failed" as const }
|
||||
: msg
|
||||
)
|
||||
: msg,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1422,10 +1308,7 @@ function SessionProviderInternal({
|
||||
|
||||
const unsubChunk = client.on("assistant_chunk", (message) => {
|
||||
if (message.type !== "assistant_chunk") return;
|
||||
setCurrentAssistantMessage(
|
||||
serverId,
|
||||
(prev) => prev + message.payload.chunk
|
||||
);
|
||||
setCurrentAssistantMessage(serverId, (prev) => prev + message.payload.chunk);
|
||||
});
|
||||
|
||||
const unsubTranscription = client.on("transcription_result", (message) => {
|
||||
@@ -1442,10 +1325,7 @@ function SessionProviderInternal({
|
||||
|
||||
const unsubVoiceInputState = client.on("voice_input_state", (message) => {
|
||||
if (message.type !== "voice_input_state") return;
|
||||
voiceRuntime?.onServerSpeechStateChanged(
|
||||
serverId,
|
||||
message.payload.isSpeaking
|
||||
);
|
||||
voiceRuntime?.onServerSpeechStateChanged(serverId, message.payload.isSpeaking);
|
||||
});
|
||||
|
||||
const unsubAgentDeleted = client.on("agent_deleted", (message) => {
|
||||
@@ -1589,11 +1469,7 @@ function SessionProviderInternal({
|
||||
]);
|
||||
|
||||
const sendAgentMessage = useCallback(
|
||||
async (
|
||||
agentId: string,
|
||||
message: string,
|
||||
images?: AttachmentMetadata[]
|
||||
) => {
|
||||
async (agentId: string, message: string, images?: AttachmentMetadata[]) => {
|
||||
const messageId = generateMessageId();
|
||||
const userMessage: StreamItem = {
|
||||
kind: "user_message",
|
||||
@@ -1605,7 +1481,9 @@ function SessionProviderInternal({
|
||||
// Append to head if streaming (keeps the user message with the current
|
||||
// turn so late text_deltas still find the existing assistant_message).
|
||||
// Otherwise append to tail.
|
||||
const currentHead = useSessionStore.getState().sessions[serverId]?.agentStreamHead?.get(agentId);
|
||||
const currentHead = useSessionStore
|
||||
.getState()
|
||||
.sessions[serverId]?.agentStreamHead?.get(agentId);
|
||||
if (currentHead && currentHead.length > 0) {
|
||||
setAgentStreamHead(serverId, (prev) => {
|
||||
const head = prev.get(agentId) || [];
|
||||
@@ -1630,15 +1508,13 @@ function SessionProviderInternal({
|
||||
void client
|
||||
.sendAgentMessage(agentId, message, {
|
||||
messageId,
|
||||
...(imagesData && imagesData.length > 0
|
||||
? { images: imagesData }
|
||||
: {}),
|
||||
...(imagesData && imagesData.length > 0 ? { images: imagesData } : {}),
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("[Session] Failed to send agent message:", error);
|
||||
});
|
||||
},
|
||||
[encodeImages, serverId, client, setAgentStreamTail, setAgentStreamHead]
|
||||
[encodeImages, serverId, client, setAgentStreamTail, setAgentStreamHead],
|
||||
);
|
||||
|
||||
// Keep the ref updated so the agent_update handler can call it
|
||||
@@ -1654,7 +1530,7 @@ function SessionProviderInternal({
|
||||
console.error("[Session] Failed to cancel agent:", error);
|
||||
});
|
||||
},
|
||||
[client]
|
||||
[client],
|
||||
);
|
||||
|
||||
const deleteAgent = useCallback(
|
||||
@@ -1667,7 +1543,7 @@ function SessionProviderInternal({
|
||||
console.error("[Session] Failed to delete agent:", error);
|
||||
});
|
||||
},
|
||||
[client]
|
||||
[client],
|
||||
);
|
||||
|
||||
const archiveAgent = useCallback(
|
||||
@@ -1680,7 +1556,7 @@ function SessionProviderInternal({
|
||||
console.error("[Session] Failed to archive agent:", error);
|
||||
});
|
||||
},
|
||||
[client]
|
||||
[client],
|
||||
);
|
||||
|
||||
const restartServer = useCallback(
|
||||
@@ -1693,7 +1569,7 @@ function SessionProviderInternal({
|
||||
console.error("[Session] Failed to restart server:", error);
|
||||
});
|
||||
},
|
||||
[client]
|
||||
[client],
|
||||
);
|
||||
|
||||
const createAgent = useCallback(
|
||||
@@ -1721,10 +1597,7 @@ function SessionProviderInternal({
|
||||
try {
|
||||
imagesData = await encodeImages(images);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[Session] Failed to prepare images for agent creation:",
|
||||
error
|
||||
);
|
||||
console.error("[Session] Failed to prepare images for agent creation:", error);
|
||||
}
|
||||
return client.createAgent({
|
||||
config,
|
||||
@@ -1735,7 +1608,7 @@ function SessionProviderInternal({
|
||||
...(requestId ? { requestId } : {}),
|
||||
});
|
||||
},
|
||||
[encodeImages, client]
|
||||
[encodeImages, client],
|
||||
);
|
||||
|
||||
const setAgentMode = useCallback(
|
||||
@@ -1748,7 +1621,7 @@ function SessionProviderInternal({
|
||||
console.error("[Session] Failed to set agent mode:", error);
|
||||
});
|
||||
},
|
||||
[client]
|
||||
[client],
|
||||
);
|
||||
|
||||
const setAgentModel = useCallback(
|
||||
@@ -1761,7 +1634,7 @@ function SessionProviderInternal({
|
||||
console.error("[Session] Failed to set agent model:", error);
|
||||
});
|
||||
},
|
||||
[client]
|
||||
[client],
|
||||
);
|
||||
|
||||
const setAgentThinkingOption = useCallback(
|
||||
@@ -1770,13 +1643,11 @@ function SessionProviderInternal({
|
||||
console.warn("[Session] setAgentThinkingOption skipped: daemon unavailable");
|
||||
return;
|
||||
}
|
||||
void client
|
||||
.setAgentThinkingOption(agentId, thinkingOptionId)
|
||||
.catch((error) => {
|
||||
console.error("[Session] Failed to set agent thinking option:", error);
|
||||
});
|
||||
void client.setAgentThinkingOption(agentId, thinkingOptionId).catch((error) => {
|
||||
console.error("[Session] Failed to set agent thinking option:", error);
|
||||
});
|
||||
},
|
||||
[client]
|
||||
[client],
|
||||
);
|
||||
|
||||
const respondToPermission = useCallback(
|
||||
@@ -1785,13 +1656,11 @@ function SessionProviderInternal({
|
||||
console.warn("[Session] respondToPermission skipped: daemon unavailable");
|
||||
return;
|
||||
}
|
||||
void client
|
||||
.respondToPermission(agentId, requestId, response)
|
||||
.catch((error) => {
|
||||
console.error("[Session] Failed to respond to permission:", error);
|
||||
});
|
||||
void client.respondToPermission(agentId, requestId, response).catch((error) => {
|
||||
console.error("[Session] Failed to respond to permission:", error);
|
||||
});
|
||||
},
|
||||
[client]
|
||||
[client],
|
||||
);
|
||||
|
||||
// Cleanup on unmount
|
||||
|
||||
@@ -15,15 +15,9 @@ const turnFailedEvent: AgentStreamEventPayload = {
|
||||
|
||||
describe("session stream lifecycle helpers", () => {
|
||||
it("derives optimistic terminal lifecycle only when current status is running", () => {
|
||||
expect(deriveOptimisticLifecycleStatus("running", turnCompletedEvent)).toBe(
|
||||
"idle"
|
||||
);
|
||||
expect(deriveOptimisticLifecycleStatus("running", turnFailedEvent)).toBe(
|
||||
"error"
|
||||
);
|
||||
expect(deriveOptimisticLifecycleStatus("initializing", turnCompletedEvent)).toBe(
|
||||
null
|
||||
);
|
||||
expect(deriveOptimisticLifecycleStatus("running", turnCompletedEvent)).toBe("idle");
|
||||
expect(deriveOptimisticLifecycleStatus("running", turnFailedEvent)).toBe("error");
|
||||
expect(deriveOptimisticLifecycleStatus("initializing", turnCompletedEvent)).toBe(null);
|
||||
expect(deriveOptimisticLifecycleStatus("idle", turnFailedEvent)).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { AgentStreamEventPayload } from "@server/shared/messages";
|
||||
|
||||
export function deriveOptimisticLifecycleStatus(
|
||||
currentStatus: AgentLifecycleStatus,
|
||||
event: AgentStreamEventPayload
|
||||
event: AgentStreamEventPayload,
|
||||
): AgentLifecycleStatus | null {
|
||||
if (currentStatus !== "running") {
|
||||
return null;
|
||||
|
||||
@@ -13,11 +13,7 @@ import {
|
||||
// Test helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function makeTimelineEntry(
|
||||
seq: number,
|
||||
text: string,
|
||||
type: string = "assistant_message"
|
||||
) {
|
||||
function makeTimelineEntry(seq: number, text: string, type: string = "assistant_message") {
|
||||
return {
|
||||
seqStart: seq,
|
||||
provider: "claude",
|
||||
@@ -28,7 +24,7 @@ function makeTimelineEntry(
|
||||
|
||||
function makeTimelineEvent(
|
||||
text: string,
|
||||
type: string = "assistant_message"
|
||||
type: string = "assistant_message",
|
||||
): AgentStreamEventPayload {
|
||||
return {
|
||||
type: "timeline",
|
||||
@@ -161,9 +157,7 @@ describe("processTimelineResponse", () => {
|
||||
endSeq: 3,
|
||||
});
|
||||
expect(result.error).toBe(null);
|
||||
expect(
|
||||
result.sideEffects.some((e) => e.type === "flush_pending_updates")
|
||||
).toBe(true);
|
||||
expect(result.sideEffects.some((e) => e.type === "flush_pending_updates")).toBe(true);
|
||||
});
|
||||
|
||||
it("sets cursor to null when reset=true but no cursors in payload", () => {
|
||||
@@ -193,10 +187,7 @@ describe("processTimelineResponse", () => {
|
||||
epoch: "epoch-1",
|
||||
startCursor: { seq: 1 },
|
||||
endCursor: { seq: 5 },
|
||||
entries: [
|
||||
makeTimelineEntry(1, "first"),
|
||||
makeTimelineEntry(5, "last"),
|
||||
],
|
||||
entries: [makeTimelineEntry(1, "first"), makeTimelineEntry(5, "last")],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -232,10 +223,7 @@ describe("processTimelineResponse", () => {
|
||||
payload: {
|
||||
...baseTimelineInput.payload,
|
||||
epoch: "epoch-1",
|
||||
entries: [
|
||||
makeTimelineEntry(4, "next-1"),
|
||||
makeTimelineEntry(5, "next-2"),
|
||||
],
|
||||
entries: [makeTimelineEntry(4, "next-1"), makeTimelineEntry(5, "next-2")],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -262,9 +250,7 @@ describe("processTimelineResponse", () => {
|
||||
payload: {
|
||||
...baseTimelineInput.payload,
|
||||
epoch: "epoch-1",
|
||||
entries: [
|
||||
makeTimelineEntry(10, "far ahead"),
|
||||
],
|
||||
entries: [makeTimelineEntry(10, "far ahead")],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -290,10 +276,7 @@ describe("processTimelineResponse", () => {
|
||||
payload: {
|
||||
...baseTimelineInput.payload,
|
||||
epoch: "epoch-1",
|
||||
entries: [
|
||||
makeTimelineEntry(5, "old"),
|
||||
makeTimelineEntry(7, "also old"),
|
||||
],
|
||||
entries: [makeTimelineEntry(5, "old"), makeTimelineEntry(7, "also old")],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -315,9 +298,7 @@ describe("processTimelineResponse", () => {
|
||||
payload: {
|
||||
...baseTimelineInput.payload,
|
||||
epoch: "epoch-2",
|
||||
entries: [
|
||||
makeTimelineEntry(6, "different epoch"),
|
||||
],
|
||||
entries: [makeTimelineEntry(6, "different epoch")],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -386,9 +367,7 @@ describe("processTimelineResponse", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
result.sideEffects.some((e) => e.type === "flush_pending_updates")
|
||||
).toBe(true);
|
||||
expect(result.sideEffects.some((e) => e.type === "flush_pending_updates")).toBe(true);
|
||||
});
|
||||
|
||||
it("initializes cursor when no existing cursor on first entries", () => {
|
||||
@@ -398,10 +377,7 @@ describe("processTimelineResponse", () => {
|
||||
payload: {
|
||||
...baseTimelineInput.payload,
|
||||
epoch: "epoch-1",
|
||||
entries: [
|
||||
makeTimelineEntry(1, "first"),
|
||||
makeTimelineEntry(2, "second"),
|
||||
],
|
||||
entries: [makeTimelineEntry(1, "first"), makeTimelineEntry(2, "second")],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import type { AgentStreamEventPayload } from "@server/shared/messages";
|
||||
import type { AgentLifecycleStatus } from "@server/shared/agent-lifecycle";
|
||||
import type { StreamItem } from "@/types/stream";
|
||||
import {
|
||||
applyStreamEvent,
|
||||
hydrateStreamState,
|
||||
reduceStreamUpdate,
|
||||
} from "@/types/stream";
|
||||
import { applyStreamEvent, hydrateStreamState, reduceStreamUpdate } from "@/types/stream";
|
||||
import {
|
||||
classifySessionTimelineSeq,
|
||||
type SessionTimelineSeqDecision,
|
||||
@@ -84,7 +80,7 @@ export interface ProcessTimelineResponseOutput {
|
||||
}
|
||||
|
||||
export function processTimelineResponse(
|
||||
input: ProcessTimelineResponseInput
|
||||
input: ProcessTimelineResponseInput,
|
||||
): ProcessTimelineResponseOutput {
|
||||
const {
|
||||
payload,
|
||||
@@ -126,7 +122,7 @@ export function processTimelineResponse(
|
||||
}));
|
||||
|
||||
const toHydratedEvents = (
|
||||
units: typeof timelineUnits
|
||||
units: typeof timelineUnits,
|
||||
): Array<{ event: AgentStreamEventPayload; timestamp: Date }> =>
|
||||
units.map(({ event, timestamp }) => ({ event, timestamp }));
|
||||
|
||||
@@ -186,17 +182,13 @@ export function processTimelineResponse(
|
||||
|
||||
for (const unit of timelineUnits) {
|
||||
const decision: SessionTimelineSeqDecision = classifySessionTimelineSeq({
|
||||
cursor: cursor
|
||||
? { epoch: cursor.epoch, endSeq: cursor.endSeq }
|
||||
: null,
|
||||
cursor: cursor ? { epoch: cursor.epoch, endSeq: cursor.endSeq } : null,
|
||||
epoch: payload.epoch,
|
||||
seq: unit.seq,
|
||||
});
|
||||
|
||||
if (decision === "gap") {
|
||||
gapCursor = cursor
|
||||
? { epoch: cursor.epoch, endSeq: cursor.endSeq }
|
||||
: null;
|
||||
gapCursor = cursor ? { epoch: cursor.epoch, endSeq: cursor.endSeq } : null;
|
||||
break;
|
||||
}
|
||||
if (decision === "drop_stale" || decision === "drop_epoch") {
|
||||
@@ -227,7 +219,7 @@ export function processTimelineResponse(
|
||||
reduceStreamUpdate(state, event, timestamp, {
|
||||
source: "canonical",
|
||||
}),
|
||||
currentTail
|
||||
currentTail,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -262,12 +254,9 @@ export function processTimelineResponse(
|
||||
responseDirection: payload.direction,
|
||||
reset: payload.reset,
|
||||
});
|
||||
const clearInitializing =
|
||||
shouldResolveDeferredInit || (isInitializing && !hasActiveInitDeferred);
|
||||
const clearInitializing = shouldResolveDeferredInit || (isInitializing && !hasActiveInitDeferred);
|
||||
|
||||
const initResolution: "resolve" | "reject" | null = shouldResolveDeferredInit
|
||||
? "resolve"
|
||||
: null;
|
||||
const initResolution: "resolve" | "reject" | null = shouldResolveDeferredInit ? "resolve" : null;
|
||||
|
||||
return {
|
||||
tail: nextTail,
|
||||
@@ -319,18 +308,10 @@ export interface ProcessAgentStreamEventOutput {
|
||||
}
|
||||
|
||||
export function processAgentStreamEvent(
|
||||
input: ProcessAgentStreamEventInput
|
||||
input: ProcessAgentStreamEventInput,
|
||||
): ProcessAgentStreamEventOutput {
|
||||
const {
|
||||
event,
|
||||
seq,
|
||||
epoch,
|
||||
currentTail,
|
||||
currentHead,
|
||||
currentCursor,
|
||||
currentAgent,
|
||||
timestamp,
|
||||
} = input;
|
||||
const { event, seq, epoch, currentTail, currentHead, currentCursor, currentAgent, timestamp } =
|
||||
input;
|
||||
|
||||
let shouldApplyStreamEvent = true;
|
||||
let nextTimelineCursor: TimelineCursor | null = null;
|
||||
@@ -340,15 +321,9 @@ export function processAgentStreamEvent(
|
||||
// ------------------------------------------------------------------
|
||||
// Timeline sequencing gate
|
||||
// ------------------------------------------------------------------
|
||||
if (
|
||||
event.type === "timeline" &&
|
||||
typeof seq === "number" &&
|
||||
typeof epoch === "string"
|
||||
) {
|
||||
if (event.type === "timeline" && typeof seq === "number" && typeof epoch === "string") {
|
||||
const decision = classifySessionTimelineSeq({
|
||||
cursor: currentCursor
|
||||
? { epoch: currentCursor.epoch, endSeq: currentCursor.endSeq }
|
||||
: null,
|
||||
cursor: currentCursor ? { epoch: currentCursor.epoch, endSeq: currentCursor.endSeq } : null,
|
||||
epoch,
|
||||
seq,
|
||||
});
|
||||
@@ -410,18 +385,12 @@ export function processAgentStreamEvent(
|
||||
event.type === "turn_canceled" ||
|
||||
event.type === "turn_failed")
|
||||
) {
|
||||
const optimisticStatus = deriveOptimisticLifecycleStatus(
|
||||
currentAgent.status,
|
||||
event
|
||||
);
|
||||
const optimisticStatus = deriveOptimisticLifecycleStatus(currentAgent.status, event);
|
||||
if (optimisticStatus) {
|
||||
const nextUpdatedAtMs = Math.max(
|
||||
currentAgent.updatedAt.getTime(),
|
||||
timestamp.getTime()
|
||||
);
|
||||
const nextUpdatedAtMs = Math.max(currentAgent.updatedAt.getTime(), timestamp.getTime());
|
||||
const nextLastActivityAtMs = Math.max(
|
||||
currentAgent.lastActivityAt.getTime(),
|
||||
timestamp.getTime()
|
||||
timestamp.getTime(),
|
||||
);
|
||||
agentPatch = {
|
||||
status: optimisticStatus,
|
||||
|
||||
@@ -78,7 +78,7 @@ describe("shouldResolveTimelineInit", () => {
|
||||
initRequestDirection: "tail",
|
||||
responseDirection: "tail",
|
||||
reset: false,
|
||||
})
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
@@ -90,7 +90,7 @@ describe("shouldResolveTimelineInit", () => {
|
||||
initRequestDirection: "tail",
|
||||
responseDirection: "after",
|
||||
reset: false,
|
||||
})
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
@@ -102,7 +102,7 @@ describe("shouldResolveTimelineInit", () => {
|
||||
initRequestDirection: "after",
|
||||
responseDirection: "after",
|
||||
reset: false,
|
||||
})
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,8 +62,7 @@ export function deriveBootstrapTailTimelinePolicy({
|
||||
return { replace: true, catchUpCursor: null };
|
||||
}
|
||||
|
||||
const isBootstrapTailInit =
|
||||
direction === "tail" && isInitializing && hasActiveInitDeferred;
|
||||
const isBootstrapTailInit = direction === "tail" && isInitializing && hasActiveInitDeferred;
|
||||
if (!isBootstrapTailInit) {
|
||||
return { replace: false, catchUpCursor: null };
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ describe("classifySessionTimelineSeq", () => {
|
||||
cursor: { epoch: "epoch-1", endSeq: 4 },
|
||||
epoch: "epoch-1",
|
||||
seq: 5,
|
||||
})
|
||||
}),
|
||||
).toBe("accept");
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ describe("classifySessionTimelineSeq", () => {
|
||||
cursor: { epoch: "epoch-1", endSeq: 8 },
|
||||
epoch: "epoch-1",
|
||||
seq: 7,
|
||||
})
|
||||
}),
|
||||
).toBe("drop_stale");
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ describe("classifySessionTimelineSeq", () => {
|
||||
cursor: { epoch: "epoch-1", endSeq: 8 },
|
||||
epoch: "epoch-1",
|
||||
seq: 8,
|
||||
})
|
||||
}),
|
||||
).toBe("drop_stale");
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ describe("classifySessionTimelineSeq", () => {
|
||||
cursor: { epoch: "epoch-1", endSeq: 4 },
|
||||
epoch: "epoch-2",
|
||||
seq: 5,
|
||||
})
|
||||
}),
|
||||
).toBe("drop_epoch");
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@ describe("classifySessionTimelineSeq", () => {
|
||||
cursor: null,
|
||||
epoch: "epoch-1",
|
||||
seq: 1,
|
||||
})
|
||||
}),
|
||||
).toBe("init");
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ describe("classifySessionTimelineSeq", () => {
|
||||
cursor: { epoch: "epoch-1", endSeq: 4 },
|
||||
epoch: "epoch-1",
|
||||
seq: 9,
|
||||
})
|
||||
}),
|
||||
).toBe("gap");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,12 +6,7 @@ export type SessionTimelineSeqCursor =
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
export type SessionTimelineSeqDecision =
|
||||
| "accept"
|
||||
| "drop_stale"
|
||||
| "drop_epoch"
|
||||
| "gap"
|
||||
| "init";
|
||||
export type SessionTimelineSeqDecision = "accept" | "drop_stale" | "drop_epoch" | "gap" | "init";
|
||||
|
||||
export function classifySessionTimelineSeq({
|
||||
cursor,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, type ReactNode } from "react";
|
||||
import { useWindowDimensions } from "react-native";
|
||||
import {
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
Easing,
|
||||
type SharedValue,
|
||||
} from "react-native-reanimated";
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { useWindowDimensions } from "react-native";
|
||||
import { useSharedValue, withTiming, Easing, type SharedValue } from "react-native-reanimated";
|
||||
import { type GestureType } from "react-native-gesture-handler";
|
||||
import { UnistylesRuntime } from "react-native-unistyles";
|
||||
import { usePanelStore } from "@/stores/panel-store";
|
||||
@@ -27,8 +30,7 @@ const SidebarAnimationContext = createContext<SidebarAnimationContextValue | nul
|
||||
|
||||
export function SidebarAnimationProvider({ children }: { children: ReactNode }) {
|
||||
const { width: windowWidth } = useWindowDimensions();
|
||||
const isMobile =
|
||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||
const isMobile = UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||
const mobileView = usePanelStore((state) => state.mobileView);
|
||||
const desktopAgentListOpen = usePanelStore((state) => state.desktop.agentListOpen);
|
||||
|
||||
@@ -112,13 +114,19 @@ export function SidebarAnimationProvider({ children }: { children: ReactNode })
|
||||
isGesturing,
|
||||
closeGestureRef,
|
||||
}),
|
||||
[translateX, backdropOpacity, windowWidth, animateToOpen, animateToClose, isGesturing, closeGestureRef]
|
||||
[
|
||||
translateX,
|
||||
backdropOpacity,
|
||||
windowWidth,
|
||||
animateToOpen,
|
||||
animateToClose,
|
||||
isGesturing,
|
||||
closeGestureRef,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<SidebarAnimationContext.Provider value={value}>
|
||||
{children}
|
||||
</SidebarAnimationContext.Provider>
|
||||
<SidebarAnimationContext.Provider value={value}>{children}</SidebarAnimationContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { createContext, useContext, type ReactNode } from "react";
|
||||
import {
|
||||
ToastViewport,
|
||||
useToastHost,
|
||||
type ToastApi,
|
||||
} from "@/components/toast-host";
|
||||
import { ToastViewport, useToastHost, type ToastApi } from "@/components/toast-host";
|
||||
|
||||
const ToastContext = createContext<ToastApi | null>(null);
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ export function useVoiceOptional(): VoiceContextValue | null {
|
||||
const snapshot = useSyncExternalStore(
|
||||
runtime ? runtime.subscribe : noopSubscribe,
|
||||
runtime ? runtime.getSnapshot : getEmptySnapshot,
|
||||
runtime ? runtime.getSnapshot : getEmptySnapshot
|
||||
runtime ? runtime.getSnapshot : getEmptySnapshot,
|
||||
);
|
||||
|
||||
if (!runtime) {
|
||||
@@ -88,7 +88,7 @@ export function useVoiceTelemetryOptional(): VoiceRuntimeTelemetrySnapshot | nul
|
||||
const snapshot = useSyncExternalStore(
|
||||
runtime ? runtime.subscribeTelemetry : noopSubscribe,
|
||||
runtime ? runtime.getTelemetrySnapshot : getEmptyTelemetry,
|
||||
runtime ? runtime.getTelemetrySnapshot : getEmptyTelemetry
|
||||
runtime ? runtime.getTelemetrySnapshot : getEmptyTelemetry,
|
||||
);
|
||||
|
||||
return runtime ? snapshot : null;
|
||||
@@ -153,9 +153,7 @@ export function VoiceProvider({ children }: VoiceProviderProps) {
|
||||
|
||||
return (
|
||||
<VoiceAudioEngineContext.Provider value={engine}>
|
||||
<VoiceRuntimeContext.Provider value={runtime}>
|
||||
{children}
|
||||
</VoiceRuntimeContext.Provider>
|
||||
<VoiceRuntimeContext.Provider value={runtime}>{children}</VoiceRuntimeContext.Provider>
|
||||
</VoiceAudioEngineContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user