@blocksdiy/react-common 1.52.2 → 1.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,193 @@
1
+ // One conversation's row, live — the reader for a surface that shows a single conversation
2
+ // without the agent's index (the floating agent chat on a page). It reads from the agent's
3
+ // shared store: a row the index already walked costs nothing; otherwise one single-thread read,
4
+ // deferred while an index for the agent is still walking so the two never race for the same row.
5
+ // A draft (the new chat, nothing sent) reads nothing: the server has no row for it yet.
6
+ // The result wears the index result's shape (one row or none), so `useAgentConversation` and
7
+ // `AgentConversationProvider`'s root run on it unchanged. The decisions are pure and tested
8
+ // (`singleThreadMountPlan`, `singleThreadEventOutcome`); the hook is the React shell around them.
9
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
10
+ import { agentThreadKey, optimisticThreadAfterSend, overlayOptimisticThreads, retireOptimisticThreadsTakenOver, } from "./agent-thread-live-update.js";
11
+ import { acquireAgentThreadStore } from "./agent-thread-store.js";
12
+ import { agentThreadPath, AgentThreadsRequestError, postAgentThreadRename, useAgentThreadsFetchJson, } from "./use-agent-threads.js";
13
+ /**
14
+ * Mount: the store's row when the index already has it; a draft reads nothing; otherwise one
15
+ * read — deferred while an index is walking, so the two never race for the same row.
16
+ */
17
+ export const singleThreadMountPlan = (store, key, isDraft) => {
18
+ const cached = store.getRow(key);
19
+ if (cached) {
20
+ return { kind: "cached", thread: cached };
21
+ }
22
+ if (isDraft) {
23
+ return { kind: "draft" };
24
+ }
25
+ return store.isIndexLoading ? { kind: "await-index" } : { kind: "read" };
26
+ };
27
+ /**
28
+ * The cache is the row's source of truth: it keeps the newest row it has seen, so what it holds
29
+ * is what the surface shows — a stale wire event, or a page an index walked later, never
30
+ * regresses it. `awaitingIndex`: the mount deferred its read to a walking index.
31
+ */
32
+ export const singleThreadEventOutcome = (event, store, key, awaitingIndex) => {
33
+ if (event.kind === "primed") {
34
+ const primed = store.getRow(key);
35
+ if (primed) {
36
+ return { kind: "row", thread: primed };
37
+ }
38
+ return awaitingIndex && !store.isIndexLoading ? { kind: "read" } : { kind: "ignore" };
39
+ }
40
+ if (event.kind === "removed") {
41
+ return agentThreadKey(event.removed) === key ? { kind: "removed" } : { kind: "ignore" };
42
+ }
43
+ if (agentThreadKey(event.thread) !== key) {
44
+ return { kind: "ignore" };
45
+ }
46
+ return { kind: "row", thread: store.getRow(key) ?? event.thread };
47
+ };
48
+ /** The single-thread read's "no row" answer — a conversation the server has not seen — as opposed to a failure. */
49
+ export const isMissingRowError = (error) => error instanceof AgentThreadsRequestError && error.status === 404;
50
+ const noop = async () => undefined;
51
+ export const useAgentThread = ({ agentId, conversation, isDraft = false, token, headers, baseUrl, }) => {
52
+ const key = agentThreadKey(conversation);
53
+ // Keyed by identity, not by object: a caller handing a fresh `{ chatId, chatThreadId }` each
54
+ // render must not re-acquire the store and refetch each render.
55
+ const target = useMemo(() => ({
56
+ chatId: conversation.chatId,
57
+ chatThreadId: conversation.chatThreadId,
58
+ }), [conversation.chatId, conversation.chatThreadId]);
59
+ const [thread, setThread] = useState(null);
60
+ // The optimistic row after this client's own send, overlaid until the server's row shows the
61
+ // run — the index's rule, applied to a list of one (the same helpers).
62
+ const [optimistic, setOptimistic] = useState(null);
63
+ const [isLoading, setIsLoading] = useState(Boolean(agentId) && !isDraft);
64
+ const [error, setError] = useState(null);
65
+ const fetchJson = useAgentThreadsFetchJson({ baseUrl, token, headers });
66
+ // Read at call time, not a dependency: the draft turning into a thread must not re-acquire
67
+ // the store or re-read — its row comes over the wire.
68
+ const isDraftRef = useRef(isDraft);
69
+ isDraftRef.current = isDraft;
70
+ const fetchRow = useCallback((target) => fetchJson(agentThreadPath(agentId, target)), [agentId, fetchJson]);
71
+ const storeHandleRef = useRef(null);
72
+ const requestSeqRef = useRef(0);
73
+ /** Read the row from the server; a row the server has none for yet reads as null, not an error. A draft reads nothing. */
74
+ const refresh = useCallback(async () => {
75
+ if (!agentId || isDraftRef.current) {
76
+ setIsLoading(false);
77
+ return;
78
+ }
79
+ const requestSeq = ++requestSeqRef.current;
80
+ setIsLoading(true);
81
+ setError(null);
82
+ try {
83
+ const row = await fetchRow(target);
84
+ if (requestSeqRef.current !== requestSeq) {
85
+ return;
86
+ }
87
+ // Through the cache: a live update that landed while the read was in flight is newer
88
+ // than what the read returned, and the cache kept the newer of the two.
89
+ const store = storeHandleRef.current?.store;
90
+ store?.prime([row]);
91
+ setThread(store?.getRow(key) ?? row);
92
+ }
93
+ catch (fetchError) {
94
+ if (requestSeqRef.current !== requestSeq) {
95
+ return;
96
+ }
97
+ if (isMissingRowError(fetchError)) {
98
+ setThread(null);
99
+ }
100
+ else {
101
+ setError(fetchError instanceof Error ? fetchError : new Error(String(fetchError)));
102
+ }
103
+ }
104
+ finally {
105
+ if (requestSeqRef.current === requestSeq) {
106
+ setIsLoading(false);
107
+ }
108
+ }
109
+ }, [agentId, target, key, fetchRow]);
110
+ useEffect(() => {
111
+ if (!agentId) {
112
+ return;
113
+ }
114
+ const handle = acquireAgentThreadStore(agentId, { fetchThread: fetchRow });
115
+ storeHandleRef.current = handle;
116
+ const { store } = handle;
117
+ setThread(null);
118
+ setOptimistic(null);
119
+ const plan = singleThreadMountPlan(store, key, isDraftRef.current);
120
+ let awaitingIndex = plan.kind === "await-index";
121
+ if (plan.kind === "cached") {
122
+ setThread(plan.thread);
123
+ setIsLoading(false);
124
+ }
125
+ else if (plan.kind === "read") {
126
+ void refresh();
127
+ }
128
+ else {
129
+ setIsLoading(awaitingIndex);
130
+ }
131
+ const unsubscribe = store.subscribe((event) => {
132
+ const outcome = singleThreadEventOutcome(event, store, key, awaitingIndex);
133
+ if (outcome.kind === "row") {
134
+ awaitingIndex = false;
135
+ setThread(outcome.thread);
136
+ setIsLoading(false);
137
+ }
138
+ else if (outcome.kind === "read") {
139
+ awaitingIndex = false;
140
+ void refresh();
141
+ }
142
+ else if (outcome.kind === "removed") {
143
+ // A ghost thread (a run that ended before any turn) is gone; a conversation with turns stays.
144
+ setThread((current) => (current && current.turnCount > 0 ? current : null));
145
+ }
146
+ });
147
+ return () => {
148
+ requestSeqRef.current += 1;
149
+ unsubscribe();
150
+ storeHandleRef.current = null;
151
+ handle.release();
152
+ };
153
+ }, [agentId, key, fetchRow, refresh]);
154
+ const notifyThreadActivity = useCallback((row, prompt) => {
155
+ setOptimistic((previous) => optimisticThreadAfterSend(row, previous ?? undefined, prompt, new Date().toISOString()));
156
+ }, []);
157
+ const renameThread = useCallback(async (target, title) => {
158
+ const trimmed = title.trim();
159
+ if (!trimmed || agentThreadKey(target) !== key) {
160
+ return;
161
+ }
162
+ const previousTitle = thread?.title;
163
+ setThread((current) => (current ? { ...current, title: trimmed } : current));
164
+ try {
165
+ await postAgentThreadRename(fetchJson, agentId, target, trimmed, storeHandleRef.current?.store);
166
+ }
167
+ catch (renameError) {
168
+ setThread((current) => (current ? { ...current, title: previousTitle ?? null } : current));
169
+ throw renameError;
170
+ }
171
+ }, [agentId, fetchJson, key, thread?.title]);
172
+ const threads = useMemo(() => {
173
+ const listed = thread ? [thread] : [];
174
+ if (!optimistic) {
175
+ return listed;
176
+ }
177
+ // Retired for good once the server row shows the run; overlaid until then.
178
+ const remaining = retireOptimisticThreadsTakenOver(new Map([[key, optimistic]]), listed);
179
+ return overlayOptimisticThreads(listed, remaining);
180
+ }, [thread, optimistic, key]);
181
+ return {
182
+ thread: threads[0] ?? null,
183
+ threads,
184
+ notifyThreadActivity,
185
+ renameThread,
186
+ isLoading,
187
+ isLoadingMore: false,
188
+ error,
189
+ hasMore: false,
190
+ loadMore: noop,
191
+ refresh,
192
+ };
193
+ };
@@ -1,8 +1,40 @@
1
1
  import { type AgentThreadItem, type AgentThreadSource, type AgentThreadStatus } from "./agent-thread-live-update.js";
2
+ import { type AgentThreadStore } from "./agent-thread-store.js";
2
3
  export * from "./agent-thread-live-update.js";
3
4
  export { type AgentAdminCaller, BUILD_PERMISSION, isAgentAdmin, readAgentAdminRoles } from "../utils/agent-admin.js";
4
5
  export { type AgentConversationChat, type AgentConversationRef, type AgentConversationUrlTarget, agentThreadStandIn, nextRunStaleAt, RUN_STALE_MS, useAgentConversation, type UseAgentConversationOptions, type UseAgentConversationResult, useAgentConversationSelection, type UseAgentConversationSelectionOptions, type UseAgentConversationSelectionResult, } from "./use-agent-conversation.js";
5
- export { type AgentConversationContextValue, AgentConversationProvider, type AgentConversationProviderProps, type AgentThreadFilters, useAgentConversationContext, } from "./agent-conversation-context.js";
6
+ export { type AgentConversationContextValue, AgentConversationProvider, type AgentConversationProviderProps, AgentThreadConversationProvider, type AgentThreadConversationProviderProps, type AgentThreadFilters, useAgentConversationContext, } from "./agent-conversation-context.js";
7
+ export { useAgentThread, type UseAgentThreadOptions, type UseAgentThreadResult } from "./use-agent-thread.js";
8
+ export { useMediaQuery } from "./use-media-query.js";
9
+ export { type FloatingAgentChatExpandHref, type FloatingAgentChatHandle, type FloatingAgentChatOpenOptions, type FloatingAgentChatOpenRequest, type FloatingAgentChatState, type FloatingAgentChatTarget, floatingAgentChatAfterDraftSent, floatingAgentChatAfterOpen, isFloatingAgentChatDraft, useFloatingAgentChatController, type UseFloatingAgentChatControllerResult, } from "./floating-agent-chat.js";
10
+ export { clampPanelSize, type PanelResizeAxis, type PanelResizeBounds, type PanelResizeHandleProps, type PanelSize, parseStoredPanelSize, useResizablePanel, type UseResizablePanelOptions, type UseResizablePanelResult, } from "./use-resizable-panel.js";
11
+ /** The auth a reader carries — its SDK token and extra headers — read at call time. */
12
+ export interface AgentThreadsAuth {
13
+ token?: string;
14
+ headers?: Record<string, string>;
15
+ }
16
+ /** A non-2xx answer from the agent-threads API, with its status — a reader tells a 404 (no row) from a failure. */
17
+ export declare class AgentThreadsRequestError extends Error {
18
+ readonly status: number;
19
+ constructor(status: number);
20
+ }
21
+ /** One request against the agent-threads API; every reader (index, single thread) goes through it. */
22
+ export declare const agentThreadsFetchJson: <T>(baseUrl: string | undefined, auth: AgentThreadsAuth, path: string, init?: RequestInit) => Promise<T>;
23
+ /**
24
+ * The request function a reader holds. Its auth is read at call time: a token refresh changes
25
+ * nothing a reader depends on, so it never re-acquires a store or re-reads for it.
26
+ */
27
+ export declare const useAgentThreadsFetchJson: ({ baseUrl, token, headers, }: AgentThreadsAuth & {
28
+ baseUrl?: string;
29
+ }) => (<T>(path: string, init?: RequestInit) => Promise<T>);
30
+ /** The single-thread read: one row by its (chatId, chatThreadId) identity under the agent. */
31
+ export declare const agentThreadPath: (agentId: string, conversation: Pick<AgentThreadItem, "chatId" | "chatThreadId">) => string;
32
+ /**
33
+ * The rename, as the server applies it. The `x-socket-id` lets the publish skip this tab — the
34
+ * caller already shows the new title optimistically — so, that publish never coming back, the
35
+ * renamed row is handed to the agent's shared store here, for every other reader of it.
36
+ */
37
+ export declare const postAgentThreadRename: (fetchJson: <T>(path: string, init?: RequestInit) => Promise<T>, agentId: string, thread: Pick<AgentThreadItem, "chatId" | "chatThreadId">, title: string, store?: Pick<AgentThreadStore, "getRow" | "prime">) => Promise<void>;
6
38
  export interface AgentThreadsPage {
7
39
  items: AgentThreadItem[];
8
40
  nextCursor: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"use-agent-threads.d.ts","sourceRoot":"","sources":["../../src/hooks/use-agent-threads.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,eAAe,EAEpB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EAUvB,MAAM,4BAA4B,CAAC;AAEpC,cAAc,4BAA4B,CAAC;AAC3C,OAAO,EAAE,KAAK,gBAAgB,EAAE,gBAAgB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAClH,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,EAC/B,6BAA6B,EAC7B,KAAK,oCAAoC,EACzC,KAAK,mCAAmC,GACzC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,6BAA6B,EAClC,yBAAyB,EACzB,KAAK,8BAA8B,EACnC,KAAK,kBAAkB,EACvB,2BAA2B,GAC5B,MAAM,8BAA8B,CAAC;AAItC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,cAAc,GAAG,WAAW,CAAC;IACpC,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,oBAAoB,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACzE,YAAY,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,cAAc,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzG,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,eAAO,MAAM,uBAAuB,iBAAiB,CAAC;AACtD,eAAO,MAAM,gBAAgB,GAAI,SAAS,MAAM,KAAG,MAAgD,CAAC;AAEpG,eAAO,MAAM,qBAAqB,GAAI,QAAQ,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,cAAc,CAAC,KAAG,MAIhG,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,QAAQ,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,cAAc,GAAG,OAAO,CAAC,KAAG,MACpD,CAAC;AAuC1D,0FAA0F;AAC1F,eAAO,MAAM,eAAe,GAAI,SAAS,sBAAsB,KAAG,qBA6UjE,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"use-agent-threads.d.ts","sourceRoot":"","sources":["../../src/hooks/use-agent-threads.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,79 @@
1
1
  import { getApiHost } from "@blocksdiy/blocks-client-api/envService";
2
2
  import { websocketsService } from "@blocksdiy/blocks-client-api/websocketService";
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
- import { agentThreadKey, applyAgentThreadUpdate, isAgentThreadItem, matchesAgentThreadFilters, optimisticThreadAfterSend, overlayOptimisticThreads, reconcileWalkWithLive, removeAgentThread, retireOptimisticThreadsTakenOver, } from "./agent-thread-live-update.js";
4
+ import { agentThreadKey, applyAgentThreadUpdate, matchesAgentThreadFilters, optimisticThreadAfterSend, overlayOptimisticThreads, reconcileWalkWithLive, removeAgentThread, retireOptimisticThreadsTakenOver, } from "./agent-thread-live-update.js";
5
+ import { acquireAgentThreadStore } from "./agent-thread-store.js";
5
6
  export * from "./agent-thread-live-update.js";
6
7
  export { BUILD_PERMISSION, isAgentAdmin, readAgentAdminRoles } from "../utils/agent-admin.js";
7
8
  export { agentThreadStandIn, nextRunStaleAt, RUN_STALE_MS, useAgentConversation, useAgentConversationSelection, } from "./use-agent-conversation.js";
8
- export { AgentConversationProvider, useAgentConversationContext, } from "./agent-conversation-context.js";
9
- const AGENT_THREAD_UPDATED = "AGENT_THREAD_UPDATED";
9
+ export { AgentConversationProvider, AgentThreadConversationProvider, useAgentConversationContext, } from "./agent-conversation-context.js";
10
+ export { useAgentThread } from "./use-agent-thread.js";
11
+ export { useMediaQuery } from "./use-media-query.js";
12
+ export { floatingAgentChatAfterDraftSent, floatingAgentChatAfterOpen, isFloatingAgentChatDraft, useFloatingAgentChatController, } from "./floating-agent-chat.js";
13
+ export { clampPanelSize, parseStoredPanelSize, useResizablePanel, } from "./use-resizable-panel.js";
14
+ /** A non-2xx answer from the agent-threads API, with its status — a reader tells a 404 (no row) from a failure. */
15
+ export class AgentThreadsRequestError extends Error {
16
+ status;
17
+ constructor(status) {
18
+ super(`Agent threads request failed with status ${status}`);
19
+ this.status = status;
20
+ this.name = "AgentThreadsRequestError";
21
+ }
22
+ }
23
+ /** One request against the agent-threads API; every reader (index, single thread) goes through it. */
24
+ export const agentThreadsFetchJson = async (baseUrl, auth, path, init) => {
25
+ const response = await fetch(`${baseUrl ?? getApiHost()}${path}`, {
26
+ ...init,
27
+ headers: {
28
+ ...(auth.token ? { Authorization: `Bearer ${auth.token}` } : {}),
29
+ ...auth.headers,
30
+ ...init?.headers,
31
+ },
32
+ });
33
+ if (!response.ok) {
34
+ throw new AgentThreadsRequestError(response.status);
35
+ }
36
+ return (await response.json());
37
+ };
38
+ /**
39
+ * The request function a reader holds. Its auth is read at call time: a token refresh changes
40
+ * nothing a reader depends on, so it never re-acquires a store or re-reads for it.
41
+ */
42
+ export const useAgentThreadsFetchJson = ({ baseUrl, token, headers, }) => {
43
+ const authRef = useRef({ token, headers });
44
+ useEffect(() => {
45
+ authRef.current = { token, headers };
46
+ });
47
+ return useCallback((path, init) => agentThreadsFetchJson(baseUrl, authRef.current, path, init), [baseUrl]);
48
+ };
49
+ /** The single-thread read: one row by its (chatId, chatThreadId) identity under the agent. */
50
+ export const agentThreadPath = (agentId, conversation) => `/agent-threads/${encodeURIComponent(agentId)}/thread?${new URLSearchParams({
51
+ chatId: conversation.chatId,
52
+ chatThreadId: conversation.chatThreadId,
53
+ })}`;
54
+ /**
55
+ * The rename, as the server applies it. The `x-socket-id` lets the publish skip this tab — the
56
+ * caller already shows the new title optimistically — so, that publish never coming back, the
57
+ * renamed row is handed to the agent's shared store here, for every other reader of it.
58
+ */
59
+ export const postAgentThreadRename = async (fetchJson, agentId, thread, title, store) => {
60
+ const socketId = websocketsService.getSocketId();
61
+ const { updated } = await fetchJson(`/agent-threads/${encodeURIComponent(agentId)}/thread`, {
62
+ method: "POST",
63
+ headers: {
64
+ "Content-Type": "application/json",
65
+ ...(socketId ? { "x-socket-id": socketId } : {}),
66
+ },
67
+ body: JSON.stringify({ chatId: thread.chatId, chatThreadId: thread.chatThreadId, title }),
68
+ });
69
+ if (!updated) {
70
+ throw new Error("Thread rename was not applied");
71
+ }
72
+ const cached = store?.getRow(agentThreadKey(thread));
73
+ if (store && cached) {
74
+ store.prime([{ ...cached, title }]);
75
+ }
76
+ };
10
77
  export const AGENT_INBOX_CHAT_PREFIX = "agent-inbox-";
11
78
  export const agentInboxChatId = (agentId) => `${AGENT_INBOX_CHAT_PREFIX}${agentId}`;
12
79
  export const agentThreadShortLabel = (thread) => {
@@ -81,26 +148,17 @@ export const useAgentThreads = (options) => {
81
148
  next.set(agentThreadKey(thread), optimisticThreadAfterSend(thread, prev.get(agentThreadKey(thread)), prompt, new Date().toISOString()));
82
149
  applyOptimisticThreads(next);
83
150
  }, [applyOptimisticThreads]);
84
- const authRef = useRef({ token: options.token, headers: options.headers });
85
- authRef.current = { token: options.token, headers: options.headers };
151
+ const fetchJson = useAgentThreadsFetchJson({ baseUrl, token: options.token, headers: options.headers });
86
152
  const requestSeqRef = useRef(0);
87
- const fetchJson = useCallback(async (path, init) => {
88
- const { token, headers } = authRef.current;
89
- const response = await fetch(`${baseUrl ?? getApiHost()}${path}`, {
90
- ...init,
91
- headers: {
92
- ...(token ? { Authorization: `Bearer ${token}` } : {}),
93
- ...headers,
94
- ...init?.headers,
95
- },
96
- });
97
- if (!response.ok) {
98
- throw new Error(`Agent threads request failed with status ${response.status}`);
99
- }
100
- return (await response.json());
101
- }, [baseUrl]);
102
153
  const fetchPage = useCallback((cursor) => fetchJson(`/agent-threads/${encodeURIComponent(agentId)}${buildQuery({ status, source, chatId, limit, sort, order }, cursor)}`), [agentId, status, source, chatId, limit, sort, order, fetchJson]);
103
- const fetchThread = useCallback((threadChatId, threadChatThreadId) => fetchJson(`/agent-threads/${encodeURIComponent(agentId)}/thread?${new URLSearchParams({ chatId: threadChatId, chatThreadId: threadChatThreadId })}`), [agentId, fetchJson]);
154
+ // The agent's shared store: one live subscription and one row cache for every reader in the
155
+ // app. This index is its priming reader — its pages land in the cache, so a single-conversation
156
+ // reader beside it (the floating agent chat over a mini-app page) never fetches what the index
157
+ // already has. Acquired per agent; the handlers below read refs, so the handle outlives filter
158
+ // changes.
159
+ const storeHandleRef = useRef(null);
160
+ const liveUpdateOptionsRef = useRef(liveUpdateOptions);
161
+ liveUpdateOptionsRef.current = liveUpdateOptions;
104
162
  const renameThread = useCallback(async (thread, title) => {
105
163
  const trimmed = title.trim();
106
164
  if (!trimmed) {
@@ -123,18 +181,7 @@ export const useAgentThreads = (options) => {
123
181
  const previousEntryTitle = optimisticThreadsRef.current.get(key)?.title;
124
182
  setLocalTitle(trimmed, trimmed);
125
183
  try {
126
- const socketId = websocketsService.getSocketId();
127
- const { updated } = await fetchJson(`/agent-threads/${encodeURIComponent(agentId)}/thread`, {
128
- method: "POST",
129
- headers: {
130
- "Content-Type": "application/json",
131
- ...(socketId ? { "x-socket-id": socketId } : {}),
132
- },
133
- body: JSON.stringify({ chatId: thread.chatId, chatThreadId: thread.chatThreadId, title: trimmed }),
134
- });
135
- if (!updated) {
136
- throw new Error("Thread rename was not applied");
137
- }
184
+ await postAgentThreadRename(fetchJson, agentId, thread, trimmed, storeHandleRef.current?.store);
138
185
  }
139
186
  catch (error) {
140
187
  setLocalTitle(previousRowTitle, previousEntryTitle);
@@ -151,6 +198,7 @@ export const useAgentThreads = (options) => {
151
198
  const requestSeq = ++requestSeqRef.current;
152
199
  liveWhileWalkingRef.current = new Map();
153
200
  setIsLoading(true);
201
+ storeHandleRef.current?.setIndexLoading(true);
154
202
  setError(null);
155
203
  const run = (async () => {
156
204
  try {
@@ -181,6 +229,7 @@ export const useAgentThreads = (options) => {
181
229
  }
182
230
  applyThreads(items);
183
231
  applyCursor(cursor);
232
+ storeHandleRef.current?.store.prime(items);
184
233
  }
185
234
  catch (fetchError) {
186
235
  if (requestSeqRef.current !== requestSeq) {
@@ -191,6 +240,7 @@ export const useAgentThreads = (options) => {
191
240
  finally {
192
241
  if (requestSeqRef.current === requestSeq) {
193
242
  setIsLoading(false);
243
+ storeHandleRef.current?.setIndexLoading(false);
194
244
  liveWhileWalkingRef.current = null;
195
245
  }
196
246
  }
@@ -223,6 +273,7 @@ export const useAgentThreads = (options) => {
223
273
  }
224
274
  applyThreads(appendPage(threadsRef.current, page.items));
225
275
  applyCursor(page.nextCursor);
276
+ storeHandleRef.current?.store.prime(page.items);
226
277
  }
227
278
  catch (fetchError) {
228
279
  if (requestSeqRef.current !== requestSeq) {
@@ -235,69 +286,67 @@ export const useAgentThreads = (options) => {
235
286
  setIsLoadingMore(false);
236
287
  }
237
288
  }, [agentId, fetchPage, applyThreads, applyCursor]);
238
- useEffect(() => {
239
- applyThreads([]);
240
- applyCursor(null);
241
- applyOptimisticThreads(new Map());
242
- void loadFirstPage();
243
- return () => {
244
- requestSeqRef.current += 1;
245
- };
246
- }, [loadFirstPage, applyThreads, applyCursor, applyOptimisticThreads]);
289
+ // The store handle and this index's view of its events. Declared before the load effect below,
290
+ // so the first page's walk finds the handle (effects run in declaration order).
247
291
  useEffect(() => {
248
292
  if (!agentId) {
249
293
  return;
250
294
  }
251
- const onThreadUpdated = async (data) => {
252
- const requestSeq = requestSeqRef.current;
253
- try {
254
- const event = data;
255
- const raw = event?.thread;
256
- if (typeof raw?.chatId !== "string" || typeof raw.chatThreadId !== "string") {
257
- return;
258
- }
259
- const identity = { chatId: raw.chatId, chatThreadId: raw.chatThreadId };
260
- const key = agentThreadKey(identity);
261
- if (event?.removed) {
262
- const removed = { ...identity, runningRunId: raw.runningRunId };
263
- const removal = removeAgentThread(threadsRef.current, optimisticThreadsRef.current, removed);
264
- if (!removal) {
265
- return;
266
- }
267
- liveWhileWalkingRef.current?.set(key, { removed });
268
- if (removal.threads !== threadsRef.current) {
269
- applyThreads(removal.threads);
270
- }
271
- if (removal.optimistic !== optimisticThreadsRef.current) {
272
- applyOptimisticThreads(removal.optimistic);
273
- }
295
+ const handle = acquireAgentThreadStore(agentId, {
296
+ fetchThread: (conversation) => fetchJson(agentThreadPath(agentId, conversation)),
297
+ isIndex: true,
298
+ });
299
+ storeHandleRef.current = handle;
300
+ const onStoreEvent = (event) => {
301
+ if (event.kind === "primed") {
302
+ return;
303
+ }
304
+ const liveUpdateOptions = liveUpdateOptionsRef.current;
305
+ if (event.kind === "removed") {
306
+ const { removed } = event;
307
+ const removal = removeAgentThread(threadsRef.current, optimisticThreadsRef.current, removed);
308
+ if (!removal) {
274
309
  return;
275
310
  }
276
- const incoming = isAgentThreadItem(raw) ? raw : await fetchThread(identity.chatId, identity.chatThreadId);
277
- if (requestSeqRef.current !== requestSeq) {
278
- return;
311
+ liveWhileWalkingRef.current?.set(agentThreadKey(removed), { removed });
312
+ if (removal.threads !== threadsRef.current) {
313
+ applyThreads(removal.threads);
279
314
  }
280
- const entry = optimisticThreadsRef.current.get(key);
281
- if (entry && !matchesAgentThreadFilters(incoming, liveUpdateOptions) && incoming.turnCount >= entry.turnCount) {
282
- const next = new Map(optimisticThreadsRef.current);
283
- next.delete(key);
284
- applyOptimisticThreads(next);
315
+ if (removal.optimistic !== optimisticThreadsRef.current) {
316
+ applyOptimisticThreads(removal.optimistic);
285
317
  }
286
- liveWhileWalkingRef.current?.set(key, incoming);
287
- applyThreads(applyAgentThreadUpdate(threadsRef.current, incoming, {
288
- ...liveUpdateOptions,
289
- hasMore: nextCursorRef.current !== null,
290
- }));
318
+ return;
291
319
  }
292
- catch {
293
- // Best-effort: websocket handler must not reject.
320
+ const incoming = event.thread;
321
+ const key = agentThreadKey(incoming);
322
+ const entry = optimisticThreadsRef.current.get(key);
323
+ if (entry && !matchesAgentThreadFilters(incoming, liveUpdateOptions) && incoming.turnCount >= entry.turnCount) {
324
+ const next = new Map(optimisticThreadsRef.current);
325
+ next.delete(key);
326
+ applyOptimisticThreads(next);
294
327
  }
328
+ liveWhileWalkingRef.current?.set(key, incoming);
329
+ applyThreads(applyAgentThreadUpdate(threadsRef.current, incoming, {
330
+ ...liveUpdateOptions,
331
+ hasMore: nextCursorRef.current !== null,
332
+ }));
295
333
  };
296
- websocketsService.listen(AGENT_THREAD_UPDATED, onThreadUpdated, { filter: { agentId } });
334
+ const unsubscribe = handle.store.subscribe(onStoreEvent);
297
335
  return () => {
298
- websocketsService.unlisten(AGENT_THREAD_UPDATED, onThreadUpdated);
336
+ unsubscribe();
337
+ storeHandleRef.current = null;
338
+ handle.release();
299
339
  };
300
- }, [agentId, liveUpdateOptions, fetchThread, applyThreads, applyOptimisticThreads]);
340
+ }, [agentId, fetchJson, applyThreads, applyOptimisticThreads]);
341
+ useEffect(() => {
342
+ applyThreads([]);
343
+ applyCursor(null);
344
+ applyOptimisticThreads(new Map());
345
+ void loadFirstPage();
346
+ return () => {
347
+ requestSeqRef.current += 1;
348
+ };
349
+ }, [loadFirstPage, applyThreads, applyCursor, applyOptimisticThreads]);
301
350
  // Realtime has no replay — reconnect re-walks; last socket id lives in a ref so a filter switch while offline does not look like a first connect.
302
351
  const lastSocketIdRef = useRef(undefined);
303
352
  useEffect(() => websocketsService.onSocketIdChange((socketId) => {
@@ -0,0 +1,2 @@
1
+ export declare const useMediaQuery: (query: string) => boolean;
2
+ //# sourceMappingURL=use-media-query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-media-query.d.ts","sourceRoot":"","sources":["../../src/hooks/use-media-query.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,KAAG,OAc7C,CAAC"}
@@ -0,0 +1,11 @@
1
+ // A media query's current match, subscribed — for layout decisions that CSS alone cannot make
2
+ // (which surface a pick opens, not how it looks). False where there is no window.
3
+ import { useCallback, useSyncExternalStore } from "react";
4
+ export const useMediaQuery = (query) => {
5
+ const subscribe = useCallback((onChange) => {
6
+ const list = window.matchMedia(query);
7
+ list.addEventListener("change", onChange);
8
+ return () => list.removeEventListener("change", onChange);
9
+ }, [query]);
10
+ return useSyncExternalStore(subscribe, () => window.matchMedia(query).matches, () => false);
11
+ };
@@ -0,0 +1,66 @@
1
+ export interface PanelSize {
2
+ width: number;
3
+ height: number;
4
+ }
5
+ /** Which dimension a handle resizes: the top edge the height, the end edge the width, the corner both. */
6
+ export type PanelResizeAxis = "width" | "height" | "both";
7
+ export interface PanelResizeBounds {
8
+ /** Pixel floor: the smallest usable panel. */
9
+ minSize: PanelSize;
10
+ /** The gap the panel keeps from the viewport's edges, in px. */
11
+ inset: number;
12
+ }
13
+ /**
14
+ * A size the panel can take inside `viewport`: never below the floor, never past the viewport
15
+ * minus the inset on both sides — unless the viewport itself is smaller than the floor, where
16
+ * the floor wins (CSS still caps the box to the viewport).
17
+ */
18
+ export declare const clampPanelSize: (size: PanelSize, viewport: PanelSize, { minSize, inset }: PanelResizeBounds) => PanelSize;
19
+ /** A stored size, or null for anything but two finite positive numbers. */
20
+ export declare const parseStoredPanelSize: (raw: string | null | undefined) => PanelSize | null;
21
+ interface ResizeDrag {
22
+ pointerId: number;
23
+ axis: PanelResizeAxis;
24
+ startX: number;
25
+ startY: number;
26
+ startSize: PanelSize;
27
+ viewport: PanelSize;
28
+ isRtl: boolean;
29
+ }
30
+ /**
31
+ * The size a drag has reached. The panel is anchored at the viewport's bottom-start corner, so
32
+ * growth is toward the end edge and upward — the horizontal delta flips in RTL, and a drag up
33
+ * counts as more height.
34
+ */
35
+ export declare const panelSizeFromDrag: (drag: ResizeDrag, pointer: {
36
+ clientX: number;
37
+ clientY: number;
38
+ }, bounds: PanelResizeBounds) => PanelSize;
39
+ /** The key's step for a handle: toward the end edge and up grow; null for a key the axis ignores. */
40
+ export declare const panelKeyboardDelta: (key: string, axis: PanelResizeAxis, step: number, isRtl: boolean) => {
41
+ dx: number;
42
+ dy: number;
43
+ } | null;
44
+ export interface UseResizablePanelOptions extends PanelResizeBounds {
45
+ /** The size before anyone resizes. */
46
+ defaultSize: PanelSize;
47
+ /** Where the size is remembered (localStorage); omit for a size that forgets on unmount. */
48
+ storageKey?: string;
49
+ }
50
+ export interface PanelResizeHandleProps {
51
+ onPointerDown: (event: React.PointerEvent<HTMLElement>) => void;
52
+ onPointerMove: (event: React.PointerEvent<HTMLElement>) => void;
53
+ onPointerUp: (event: React.PointerEvent<HTMLElement>) => void;
54
+ onPointerCancel: () => void;
55
+ onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;
56
+ }
57
+ export interface UseResizablePanelResult {
58
+ size: PanelSize;
59
+ /** A pointer drag is in progress (the component disables text selection meanwhile). */
60
+ isResizing: boolean;
61
+ /** The handlers for a handle that resizes `axis`; spread them on the handle element. */
62
+ getHandleProps: (axis: PanelResizeAxis) => PanelResizeHandleProps;
63
+ }
64
+ export declare const useResizablePanel: (panelRef: React.RefObject<HTMLElement | null>, { defaultSize, minSize, inset, storageKey }: UseResizablePanelOptions) => UseResizablePanelResult;
65
+ export {};
66
+ //# sourceMappingURL=use-resizable-panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-resizable-panel.d.ts","sourceRoot":"","sources":["../../src/hooks/use-resizable-panel.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,0GAA0G;AAC1G,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,8CAA8C;IAC9C,OAAO,EAAE,SAAS,CAAC;IACnB,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;CACf;AAID;;;;GAIG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,SAAS,EACf,UAAU,SAAS,EACnB,oBAAoB,iBAAiB,KACpC,SAOF,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,oBAAoB,GAAI,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,SAAS,GAAG,IAwBjF,CAAC;AAEF,UAAU,UAAU;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,UAAU,EAChB,SAAS;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAC7C,QAAQ,iBAAiB,KACxB,SAWF,CAAC;AAEF,qGAAqG;AACrG,eAAO,MAAM,kBAAkB,GAC7B,KAAK,MAAM,EACX,MAAM,eAAe,EACrB,MAAM,MAAM,EACZ,OAAO,OAAO,KACb;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,IAgB/B,CAAC;AAqCF,MAAM,WAAW,wBAAyB,SAAQ,iBAAiB;IACjE,sCAAsC;IACtC,WAAW,EAAE,SAAS,CAAC;IACvB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAChE,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAChE,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAC9D,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,SAAS,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;CAC9D;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,SAAS,CAAC;IAChB,uFAAuF;IACvF,UAAU,EAAE,OAAO,CAAC;IACpB,wFAAwF;IACxF,cAAc,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,sBAAsB,CAAC;CACnE;AAED,eAAO,MAAM,iBAAiB,GAC5B,UAAU,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EAC7C,6CAA6C,wBAAwB,KACpE,uBAiFF,CAAC"}