feat(agent): support Responses steering and project-aware tools
Use the dedicated TJWATER_AGENT_URL and Responses stream protocol. Keep running turns active while users steer or queue follow-ups and edit pending delivery. Refresh the model catalog before generation and preserve approval mode selection. Integrate scoped investigation messaging/presentation, geographic place selection, current Agent composer styles and supporting workbench contracts. Validation: 77 focused Vitest checks, 21 browser cases, lint, typecheck and production build. Preserve direct Server/Agent routing and project authentication.
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import type { AgentApiClient } from "../api/client";
|
||||
|
||||
export function useAgentMessageInbox(client: AgentApiClient, sessionId: string | null) {
|
||||
const inbox = useSWR(sessionId ? ["agent-message-inbox", sessionId] : null,
|
||||
([, id]) => client.listAgentMessages(id), { revalidateOnFocus: true, shouldRetryOnError: false });
|
||||
const [loadingMore, setLoadingMore] = useState(false);
|
||||
const mutate = inbox.mutate;
|
||||
const refresh = useCallback(() => { void mutate().catch(() => undefined); }, [mutate]);
|
||||
const refreshRef = useRef(refresh);
|
||||
useEffect(() => { refreshRef.current = refresh; }, [refresh]);
|
||||
useEffect(() => {
|
||||
if (!sessionId) return;
|
||||
const controller = new AbortController();
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
let retryDelay = 1500;
|
||||
function subscribe() {
|
||||
void client.streamAgentMessages(sessionId!, { signal: controller.signal, onMessage: () => { retryDelay = 1500; refreshRef.current(); } })
|
||||
.catch(() => { retryDelay = Math.min(retryDelay * 2, 30_000); })
|
||||
.finally(() => {
|
||||
if (!controller.signal.aborted) timer = setTimeout(subscribe, retryDelay);
|
||||
});
|
||||
}
|
||||
subscribe();
|
||||
return () => { controller.abort(); clearTimeout(timer); };
|
||||
}, [client, sessionId]);
|
||||
return {
|
||||
messages: inbox.data?.items ?? [],
|
||||
hasMore: Boolean(inbox.data?.next_cursor),
|
||||
loading: inbox.isLoading,
|
||||
error: inbox.error ? "任务消息暂时无法加载" : null,
|
||||
refresh,
|
||||
loadingMore,
|
||||
loadMore: async () => {
|
||||
if (!sessionId || !inbox.data?.next_cursor || loadingMore) return;
|
||||
setLoadingMore(true);
|
||||
try {
|
||||
const page = await client.listAgentMessages(sessionId, { cursor: inbox.data.next_cursor });
|
||||
await inbox.mutate((current) => current ? { items: [...current.items, ...page.items.filter((item) => !current.items.some((existing) => existing.message_id === item.message_id))], next_cursor: page.next_cursor } : page, { revalidate: false });
|
||||
} finally { setLoadingMore(false); }
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user