@athenaintel/react 0.48.0 → 0.50.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.
- package/README.md +48 -10
- package/dist/agent-runtime/src/call-recap.cjs +319 -0
- package/dist/agent-runtime/src/call-recap.cjs.map +1 -0
- package/dist/agent-runtime/src/call-recap.d.ts +142 -0
- package/dist/agent-runtime/src/call-recap.js +319 -0
- package/dist/agent-runtime/src/call-recap.js.map +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.cjs +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.cjs.map +1 -1
- package/dist/agent-runtime/src/chat-outbox-store.d.ts +7 -0
- package/dist/agent-runtime/src/chat-outbox-store.js +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.js.map +1 -1
- package/dist/agent-runtime/src/chat-outbox.cjs +46 -5
- package/dist/agent-runtime/src/chat-outbox.cjs.map +1 -1
- package/dist/agent-runtime/src/chat-outbox.d.ts +29 -0
- package/dist/agent-runtime/src/chat-outbox.js +46 -5
- package/dist/agent-runtime/src/chat-outbox.js.map +1 -1
- package/dist/agent-runtime/src/conversation-index.cjs.map +1 -1
- package/dist/agent-runtime/src/conversation-index.d.ts +7 -0
- package/dist/agent-runtime/src/conversation-index.js.map +1 -1
- package/dist/agent-runtime/src/conversation-list.cjs +2 -1
- package/dist/agent-runtime/src/conversation-list.cjs.map +1 -1
- package/dist/agent-runtime/src/conversation-list.d.ts +19 -3
- package/dist/agent-runtime/src/conversation-list.js +2 -1
- package/dist/agent-runtime/src/conversation-list.js.map +1 -1
- package/dist/agent-runtime/src/index.d.ts +6 -5
- package/dist/agent-runtime/src/interrupts.cjs +28 -1
- package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/agent-runtime/src/interrupts.d.ts +21 -1
- package/dist/agent-runtime/src/interrupts.js +28 -1
- package/dist/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/agent-runtime/src/model-context.cjs +3 -2
- package/dist/agent-runtime/src/model-context.cjs.map +1 -1
- package/dist/agent-runtime/src/model-context.d.ts +6 -1
- package/dist/agent-runtime/src/model-context.js +3 -2
- package/dist/agent-runtime/src/model-context.js.map +1 -1
- package/dist/agent-runtime/src/runtime.d.ts +3 -2
- package/dist/agent-runtime/src/send-recovery.cjs +37 -3
- package/dist/agent-runtime/src/send-recovery.cjs.map +1 -1
- package/dist/agent-runtime/src/send-recovery.d.ts +19 -0
- package/dist/agent-runtime/src/send-recovery.js +37 -3
- package/dist/agent-runtime/src/send-recovery.js.map +1 -1
- package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +8 -8
- package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +113 -1
- package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
- package/dist/agent-runtime/src/use-conversation-list.cjs +3 -2
- package/dist/agent-runtime/src/use-conversation-list.cjs.map +1 -1
- package/dist/agent-runtime/src/use-conversation-list.d.ts +6 -0
- package/dist/agent-runtime/src/use-conversation-list.js +3 -2
- package/dist/agent-runtime/src/use-conversation-list.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs +21 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.d.ts +8 -2
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js +21 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +24 -6
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +17 -0
- package/dist/agent-runtime/src/use-deep-agent-runtime.js +24 -6
- package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs +44 -9
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +34 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.js +44 -9
- package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
- package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +8 -2
- package/dist/agent-runtime/src/voice-earcon.cjs +139 -0
- package/dist/agent-runtime/src/voice-earcon.cjs.map +1 -0
- package/dist/agent-runtime/src/voice-earcon.d.ts +38 -0
- package/dist/agent-runtime/src/voice-earcon.js +139 -0
- package/dist/agent-runtime/src/voice-earcon.js.map +1 -0
- package/dist/agent-runtime/src/voice-media.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-media.d.ts +14 -0
- package/dist/agent-runtime/src/voice-media.js.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
- package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.js +2 -0
- package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
- package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
- package/dist/assets/AthenaAssetEmbed.d.ts +11 -5
- package/dist/assets/AthenaAssetEmbed.js.map +1 -1
- package/dist/assets/use-asset-embed.cjs.map +1 -1
- package/dist/assets/use-asset-embed.d.ts +12 -5
- package/dist/assets/use-asset-embed.js.map +1 -1
- package/dist/chat/StatewireApprovalCard.cjs +41 -6
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.d.ts +18 -1
- package/dist/chat/StatewireApprovalCard.js +42 -7
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat/StatewireQueuedMessages.cjs +56 -6
- package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
- package/dist/chat/StatewireQueuedMessages.d.ts +14 -0
- package/dist/chat/StatewireQueuedMessages.js +58 -8
- package/dist/chat/StatewireQueuedMessages.js.map +1 -1
- package/dist/chat/WaitingOnYouBanner.cjs +6 -2
- package/dist/chat/WaitingOnYouBanner.cjs.map +1 -1
- package/dist/chat/WaitingOnYouBanner.js +7 -3
- package/dist/chat/WaitingOnYouBanner.js.map +1 -1
- package/dist/chat/chat-voice-controls.cjs +3 -1
- package/dist/chat/chat-voice-controls.cjs.map +1 -1
- package/dist/chat/chat-voice-controls.d.ts +6 -1
- package/dist/chat/chat-voice-controls.js +3 -1
- package/dist/chat/chat-voice-controls.js.map +1 -1
- package/dist/chat-ui/index.cjs +3 -0
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.d.ts +1 -0
- package/dist/chat-ui/index.js +3 -0
- package/dist/chat-ui/index.js.map +1 -1
- package/dist/chat-ui/messages/from-the-call-card.cjs +124 -0
- package/dist/chat-ui/messages/from-the-call-card.cjs.map +1 -0
- package/dist/chat-ui/messages/from-the-call-card.d.ts +17 -0
- package/dist/chat-ui/messages/from-the-call-card.js +124 -0
- package/dist/chat-ui/messages/from-the-call-card.js.map +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-link.d.ts +4 -0
- package/dist/chat-ui/rich-text/chat-link.js +4 -3
- package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.d.ts +12 -0
- package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
- package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.cjs +2 -0
- package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.js +2 -0
- package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
- package/dist/collab/client.cjs +15 -3
- package/dist/collab/client.cjs.map +1 -1
- package/dist/collab/client.d.ts +4 -2
- package/dist/collab/client.js +16 -4
- package/dist/collab/client.js.map +1 -1
- package/dist/composer/index.d.ts +1 -1
- package/dist/composer/useReadComposerDraft.cjs +26 -7
- package/dist/composer/useReadComposerDraft.cjs.map +1 -1
- package/dist/composer/useReadComposerDraft.d.ts +23 -1
- package/dist/composer/useReadComposerDraft.js +27 -8
- package/dist/composer/useReadComposerDraft.js.map +1 -1
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +190 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +132 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js +190 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -0
- package/dist/keryx-http/src/update-attributions.cjs +34 -4
- package/dist/keryx-http/src/update-attributions.cjs.map +1 -1
- package/dist/keryx-http/src/update-attributions.d.ts +36 -4
- package/dist/keryx-http/src/update-attributions.js +35 -5
- package/dist/keryx-http/src/update-attributions.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +4 -0
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +11 -1
- package/dist/provider/AthenaProvider.js +4 -0
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/threads/api.cjs.map +1 -1
- package/dist/threads/api.d.ts +15 -0
- package/dist/threads/api.js.map +1 -1
- package/dist/threads/conversation-list-fetcher.cjs +13 -5
- package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
- package/dist/threads/conversation-list-fetcher.d.ts +4 -1
- package/dist/threads/conversation-list-fetcher.js +13 -5
- package/dist/threads/conversation-list-fetcher.js.map +1 -1
- package/dist/threads/statewire-thread-list.cjs +28 -6
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.d.ts +20 -8
- package/dist/threads/statewire-thread-list.js +28 -6
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/threads/use-athena-sessions.cjs +27 -5
- package/dist/threads/use-athena-sessions.cjs.map +1 -1
- package/dist/threads/use-athena-sessions.d.ts +10 -5
- package/dist/threads/use-athena-sessions.js +28 -6
- package/dist/threads/use-athena-sessions.js.map +1 -1
- package/dist/threads/use-rest-conversation-history.d.ts +1 -0
- package/package.json +1 -1
|
@@ -51,21 +51,26 @@ type IndexEntryLike = Parameters<typeof entryCouldMatch>[0] & {
|
|
|
51
51
|
thread_id: string;
|
|
52
52
|
updated_at: string;
|
|
53
53
|
app_id: string | null;
|
|
54
|
+
collab_agent_id?: string | null;
|
|
55
|
+
start_channel?: string | null;
|
|
54
56
|
};
|
|
55
57
|
/**
|
|
56
58
|
* The index entries that warrant one refresh of a filtered list, as
|
|
57
59
|
* `<thread_id>@<updated_at>` versions: entries the list does not show, of
|
|
58
|
-
* the apps it lists,
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
60
|
+
* the apps it lists (and, while `homeAgentApplied`, of the conversations with
|
|
61
|
+
* `homeAgentId`'s agent; see `indexedThreadsToRefreshFor`), that the query
|
|
62
|
+
* could match on what the entry carries, and whose version has not prompted a
|
|
63
|
+
* refresh before. An unchanged entry the server keeps leaving out refreshes
|
|
64
|
+
* once; any later change to it refreshes again. Exported for tests.
|
|
62
65
|
*/
|
|
63
|
-
export declare function indexEntryVersionsToRefresh({ entries, listedThreadIds, refreshedVersions, appId, additionalAppIds, query, }: {
|
|
66
|
+
export declare function indexEntryVersionsToRefresh({ entries, listedThreadIds, refreshedVersions, appId, additionalAppIds, homeAgentId, homeAgentApplied, query, }: {
|
|
64
67
|
entries: readonly IndexEntryLike[];
|
|
65
68
|
listedThreadIds: ReadonlySet<string>;
|
|
66
69
|
refreshedVersions: ReadonlySet<string>;
|
|
67
70
|
appId: string | undefined;
|
|
68
71
|
additionalAppIds: readonly string[];
|
|
72
|
+
homeAgentId?: string;
|
|
73
|
+
homeAgentApplied?: boolean;
|
|
69
74
|
query: AthenaSessionsQuery;
|
|
70
75
|
}): string[];
|
|
71
76
|
/**
|
|
@@ -9,7 +9,7 @@ import { sessionListShareKey } from "./session-list-tab-share.js";
|
|
|
9
9
|
import { supportedFiltersFromResponse, sessionsQueryKey, planSessionsQuery, sessionsQueryFromKey, unavailableSessionsQueryFields, matchesSessionsQuery, sortSessions } from "./sessions-query.js";
|
|
10
10
|
import { AthenaSessionsLiveContext, foldConversationIndex, threadSummaryFromConversation, indexedThreadsToRefreshFor } from "./statewire-thread-list.js";
|
|
11
11
|
import { useConversationList } from "../agent-runtime/src/use-conversation-list.js";
|
|
12
|
-
import {
|
|
12
|
+
import { conversationListFilterKey, conversationListQueryKeys } from "../agent-runtime/src/conversation-list.js";
|
|
13
13
|
const DEFAULT_PAGE_SIZE = 50;
|
|
14
14
|
const THREAD_LIST_CAPABILITIES_QUERY_KEY = [
|
|
15
15
|
"athena-sdk",
|
|
@@ -93,14 +93,15 @@ function useAthenaSessions({
|
|
|
93
93
|
enabled: active && capabilitiesSettled,
|
|
94
94
|
queryClient
|
|
95
95
|
});
|
|
96
|
+
const standInScope = `${identityScope}::${conversationListFilterKey(baseFilter)}`;
|
|
96
97
|
const lastResolvedRef = useRef({
|
|
97
98
|
scope: "",
|
|
98
99
|
conversations: []
|
|
99
100
|
});
|
|
100
101
|
if (list.hasResolvedFirstPage) {
|
|
101
|
-
lastResolvedRef.current = { scope:
|
|
102
|
+
lastResolvedRef.current = { scope: standInScope, conversations: list.conversations };
|
|
102
103
|
}
|
|
103
|
-
const standIn = !list.hasResolvedFirstPage && lastResolvedRef.current.scope ===
|
|
104
|
+
const standIn = !list.hasResolvedFirstPage && lastResolvedRef.current.scope === standInScope ? lastResolvedRef.current.conversations : null;
|
|
104
105
|
const source = standIn ?? list.conversations;
|
|
105
106
|
const isPlaceholderData = standIn !== null && standIn.length > 0;
|
|
106
107
|
const indexByThread = live == null ? void 0 : live.indexByThread;
|
|
@@ -116,6 +117,8 @@ function useAthenaSessions({
|
|
|
116
117
|
const listRefresh = list.refresh;
|
|
117
118
|
const baseAppId = baseFilter.appId;
|
|
118
119
|
const baseAppIds = baseFilter.appIds;
|
|
120
|
+
const baseHomeAgentId = baseFilter.homeAgentId;
|
|
121
|
+
const homeAgentApplied = list.homeAgentApplied;
|
|
119
122
|
const listedThreadIds = useMemo(
|
|
120
123
|
() => new Set(list.conversations.map((conversation) => conversation.threadId)),
|
|
121
124
|
[list.conversations]
|
|
@@ -127,9 +130,19 @@ function useAthenaSessions({
|
|
|
127
130
|
refreshedVersions: EMPTY_VERSIONS,
|
|
128
131
|
appId: baseAppId,
|
|
129
132
|
additionalAppIds: baseAppIds ?? [],
|
|
133
|
+
homeAgentId: baseHomeAgentId,
|
|
134
|
+
homeAgentApplied,
|
|
130
135
|
query: plan.local
|
|
131
136
|
}),
|
|
132
|
-
[
|
|
137
|
+
[
|
|
138
|
+
indexEntries,
|
|
139
|
+
listedThreadIds,
|
|
140
|
+
baseAppId,
|
|
141
|
+
baseAppIds,
|
|
142
|
+
baseHomeAgentId,
|
|
143
|
+
homeAgentApplied,
|
|
144
|
+
plan.local
|
|
145
|
+
]
|
|
133
146
|
);
|
|
134
147
|
useIndexTriggeredRefresh({
|
|
135
148
|
pending: unlistedVersions,
|
|
@@ -171,18 +184,27 @@ function indexEntryVersionsToRefresh({
|
|
|
171
184
|
refreshedVersions,
|
|
172
185
|
appId,
|
|
173
186
|
additionalAppIds,
|
|
187
|
+
homeAgentId,
|
|
188
|
+
homeAgentApplied,
|
|
174
189
|
query
|
|
175
190
|
}) {
|
|
176
191
|
const versionOf = (entry) => `${entry.thread_id}@${entry.updated_at}`;
|
|
177
192
|
const byVersion = new Map(entries.map((entry) => [versionOf(entry), entry]));
|
|
178
193
|
return indexedThreadsToRefreshFor({
|
|
179
|
-
entries: entries.map((entry) => ({
|
|
194
|
+
entries: entries.map((entry) => ({
|
|
195
|
+
thread_id: versionOf(entry),
|
|
196
|
+
app_id: entry.app_id,
|
|
197
|
+
collab_agent_id: entry.collab_agent_id,
|
|
198
|
+
start_channel: entry.start_channel
|
|
199
|
+
})),
|
|
180
200
|
listedThreadIds: new Set(
|
|
181
201
|
entries.filter((entry) => listedThreadIds.has(entry.thread_id)).map(versionOf)
|
|
182
202
|
),
|
|
183
203
|
refreshedThreadIds: refreshedVersions,
|
|
184
204
|
appId,
|
|
185
|
-
additionalAppIds
|
|
205
|
+
additionalAppIds,
|
|
206
|
+
homeAgentId,
|
|
207
|
+
homeAgentApplied
|
|
186
208
|
}).filter((version) => {
|
|
187
209
|
const entry = byVersion.get(version);
|
|
188
210
|
return entry === void 0 || entryCouldMatch(entry, query);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-athena-sessions.js","sources":["../../src/threads/use-athena-sessions.ts"],"sourcesContent":["/**\n * `useAthenaSessions(query)` — one filtered, sorted, live session list under\n * `<AthenaProvider enableThreadList>`: the data layer of `SessionsPage` and\n * `Inbox`, for hosts drawing their own.\n *\n * - **Server-side.** The query travels to `POST /api/conversations/threads/list`\n * as filters of the server's one page statement (`planSessionsQuery`), over\n * the shared two-tier pager (`useConversationList`: a live first page and\n * frozen history pages) in the thread-list provider's query client.\n * - **Live.** Rows fold the same conversation-index pushes the sidebar folds\n * (titles, lifecycle, attention flip in place), carry the provider's\n * in-flight writes (pin, rename, read state, archive), and a thread the\n * index learns about that the list does not show yet refreshes the list\n * once.\n * - **Degraded, not wrong.** A server that cannot apply part of the query is\n * detected from its `supported_filters` echo (one `limit: 1` probe per\n * identity). Fields the loaded rows can be tested on are applied to them\n * (`degradedFields`: exact per page, a page can come back short); fields\n * they cannot are reported in `unavailableFields` and not applied, so a\n * control for them can be disabled rather than lie.\n */\nimport {\n type ConversationListFilter,\n type ConversationSummary,\n conversationListFilterKey,\n conversationListQueryKeys,\n useConversationList,\n} from '@athenaintel/agent-runtime';\nimport { useQuery } from '@tanstack/react-query';\nimport { useContext, useMemo, useRef } from 'react';\nimport { useAthenaQueryClient } from '../lib/use-athena-rest-query';\nimport { withCredentialRenewal } from '../runtime/credential-renewal';\nimport { listThreads } from './api';\nimport { createRestConversationListFetcher } from './conversation-list-fetcher';\nimport { useIndexTriggeredRefresh } from './index-triggered-refresh';\nimport { sessionListShareKey } from './session-list-tab-share';\nimport {\n type AthenaSessionsQuery,\n type AthenaSessionsQueryField,\n matchesSessionsQuery,\n planSessionsQuery,\n sessionsQueryFromKey,\n sessionsQueryKey,\n sortSessions,\n supportedFiltersFromResponse,\n unavailableSessionsQueryFields,\n} from './sessions-query';\nimport {\n AthenaSessionsLiveContext,\n type AthenaStatewireThreadSummary,\n foldConversationIndex,\n indexedThreadsToRefreshFor,\n threadSummaryFromConversation,\n} from './statewire-thread-list';\n\nexport interface UseAthenaSessionsOptions {\n /** What to list. A new object with the same content does not refetch. */\n query: AthenaSessionsQuery;\n /** Rows per page (default 50). */\n pageSize?: number;\n /** `false` holds the list idle. Default `true`. */\n enabled?: boolean;\n}\n\nexport interface AthenaSessionsList {\n /** The rows, in the query's order, with live pushes and in-flight writes applied. */\n rows: AthenaStatewireThreadSummary[];\n /** `false` outside `enableThreadList` mode: nothing is fetched and `rows` stays empty. */\n isAvailable: boolean;\n /** True until the first page has answered (or while no credential has resolved). */\n isLoading: boolean;\n isFetching: boolean;\n hasResolvedFirstPage: boolean;\n /**\n * True while the rows are the previous query's (checked against this\n * query's local part) because this query's first page is still in flight.\n */\n isPlaceholderData: boolean;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n fetchNextPage: () => void;\n /** Refetch the first page; resolves once it has answered. */\n refresh: () => Promise<void>;\n /** True while the last \"load more\" request failed. */\n isFetchNextPageError: boolean;\n /**\n * The server fields this backend applies (`supported_filters`), `null`\n * while unknown or on a backend that predates the list.\n */\n supportedFilters: ReadonlySet<string> | null;\n /** Fields applied to the loaded rows because the backend cannot. */\n degradedFields: AthenaSessionsQueryField[];\n /** Fields of this query that cannot be applied on this backend at all (and are not). */\n unavailableFields: AthenaSessionsQueryField[];\n /**\n * Every field this backend cannot apply anywhere, selected or not — what a\n * filter UI disables. Empty until the capabilities probe has answered.\n */\n unsupportedFields: AthenaSessionsQueryField[];\n}\n\nconst DEFAULT_PAGE_SIZE = 50;\n\n/** The capabilities probe's key prefix. */\nexport const THREAD_LIST_CAPABILITIES_QUERY_KEY = [\n 'athena-sdk',\n 'thread-list-capabilities',\n] as const;\n\nconst EMPTY_ROWS: AthenaStatewireThreadSummary[] = [];\nconst EMPTY_VERSIONS: ReadonlySet<string> = new Set();\nconst NOOP = () => undefined;\nconst NOOP_ASYNC = async () => undefined;\n\nexport function useAthenaSessions({\n query,\n pageSize = DEFAULT_PAGE_SIZE,\n enabled = true,\n}: UseAthenaSessionsOptions): AthenaSessionsList {\n const live = useContext(AthenaSessionsLiveContext);\n const isAvailable = live !== null;\n const backendUrl = live?.backendUrl ?? '';\n const principal = live?.principal ?? '';\n const hasAuth = live?.hasAuth ?? false;\n const includePullRequests = live?.includePullRequests ?? false;\n const getAuth = live?.getAuth;\n const active = isAvailable && enabled && hasAuth;\n // Every cache below is keyed by WHO reads (principal) and WITH WHAT (the\n // credential generation): a scoped credential for another workspace under\n // the same principal must never read this one's cached rows.\n const identityScope = `${live?.scopeKey ?? 'unavailable'}::credential-${\n live?.credentialGeneration ?? 0\n }`;\n\n // Outside thread-list mode there may be no `QueryClientProvider`; every\n // query below is disabled there, on a client of this hook's own.\n const queryClient = useAthenaQueryClient();\n\n // Which filters this backend applies: one tiny page per identity. The\n // answer is a property of the deployed agora, so it never goes stale.\n const capabilities = useQuery(\n {\n queryKey: [\n ...THREAD_LIST_CAPABILITIES_QUERY_KEY,\n backendUrl,\n principal,\n live?.credentialGeneration ?? 0,\n ],\n queryFn: async ({ signal }) => {\n if (!getAuth) return null;\n const response = await withCredentialRenewal(getAuth, (auth) =>\n listThreads(backendUrl, auth, { limit: 1, signal }),\n );\n return supportedFiltersFromResponse(response.supported_filters);\n },\n enabled: active,\n staleTime: Number.POSITIVE_INFINITY,\n retry: 1,\n },\n queryClient,\n );\n // A failed probe is treated as an older backend: the query degrades\n // instead of the list staying empty.\n const capabilitiesSettled = capabilities.isSuccess || capabilities.isError;\n const supportedFilters = capabilities.data ?? null;\n\n // `query` may be a fresh object every render; its normalized key is what\n // identifies it, so the plan is built from the key and recomputed only\n // when the content changes.\n const queryKey = sessionsQueryKey(query);\n const plan = useMemo(\n () =>\n planSessionsQuery(sessionsQueryFromKey(queryKey), {\n supportedFilters,\n includePullRequests,\n }),\n [queryKey, supportedFilters, includePullRequests],\n );\n const planRef = useRef(plan);\n planRef.current = plan;\n const unsupportedFields = useMemo(\n () =>\n capabilitiesSettled\n ? unavailableSessionsQueryFields({ supportedFilters, includePullRequests })\n : [],\n [capabilitiesSettled, supportedFilters, includePullRequests],\n );\n\n const fetchPage = useMemo(\n () =>\n getAuth\n ? createRestConversationListFetcher({\n backendUrl,\n getAuth,\n includePullRequests,\n extraFilters: plan.body,\n keepRow: (row) => matchesSessionsQuery(row, planRef.current.local),\n })\n : async () => ({ items: [], hasMore: false, totalCount: null }),\n [backendUrl, getAuth, includePullRequests, plan.body],\n );\n const baseFilter: ConversationListFilter = live?.baseFilter ?? {};\n const capabilityKey = supportedFilters ? [...supportedFilters].sort().join(',') : 'legacy';\n // The query's server filters are not part of the shared filter contract,\n // so they — and what this backend applies of them — key the cache here.\n const listScopeKey = `${identityScope}::sessions::${queryKey}::${capabilityKey}`;\n const list = useConversationList({\n fetchPage,\n filter: baseFilter,\n pageSize,\n scopeKey: listScopeKey,\n enabled: active && capabilitiesSettled,\n queryClient,\n });\n\n // While a changed query's first page is in flight, the previous query's\n // rows stand in, checked against the new query's local part — the list\n // narrows at once instead of blanking to skeletons. Only rows read as the\n // same identity and credential ever stand in.\n const lastResolvedRef = useRef<{ scope: string; conversations: ConversationSummary[] }>({\n scope: '',\n conversations: [],\n });\n if (list.hasResolvedFirstPage) {\n lastResolvedRef.current = { scope: identityScope, conversations: list.conversations };\n }\n const standIn =\n !list.hasResolvedFirstPage && lastResolvedRef.current.scope === identityScope\n ? lastResolvedRef.current.conversations\n : null;\n const source = standIn ?? list.conversations;\n const isPlaceholderData = standIn !== null && standIn.length > 0;\n\n const indexByThread = live?.indexByThread;\n const applyOverrides = live?.applyOverrides;\n const rows = useMemo(() => {\n if (!indexByThread || !applyOverrides) return EMPTY_ROWS;\n const folded: ConversationSummary[] = applyOverrides(\n foldConversationIndex(source, indexByThread),\n ).filter((row) => matchesSessionsQuery(row, plan.local));\n return sortSessions(folded, plan.local).map(threadSummaryFromConversation);\n }, [source, indexByThread, applyOverrides, plan.local]);\n\n // A thread the index knows that this list does not show yet refreshes the\n // list — it may be new, or may have just come to match. Once per VERSION of\n // the entry (its id and `updated_at`): an unchanged entry the server keeps\n // leaving out never refreshes twice, and a later change to it (a PR opened,\n // a run now needing input) gets its own refresh. Coalesced, held while the\n // list is already being read, and a refused refresh is tried again\n // (`useIndexTriggeredRefresh`).\n const indexEntries = live?.indexEntries ?? null;\n const listRefresh = list.refresh;\n const baseAppId = baseFilter.appId;\n const baseAppIds = baseFilter.appIds;\n const listedThreadIds = useMemo(\n () => new Set(list.conversations.map((conversation) => conversation.threadId)),\n [list.conversations],\n );\n const unlistedVersions = useMemo(\n () =>\n indexEntries === null\n ? []\n : indexEntryVersionsToRefresh({\n entries: indexEntries,\n listedThreadIds,\n refreshedVersions: EMPTY_VERSIONS,\n appId: baseAppId,\n additionalAppIds: baseAppIds ?? [],\n query: plan.local,\n }),\n [indexEntries, listedThreadIds, baseAppId, baseAppIds, plan.local],\n );\n useIndexTriggeredRefresh({\n pending: unlistedVersions,\n refresh: listRefresh,\n // Held while the list is being read; a refused first page does not hold\n // it (the refresh is how the list recovers).\n enabled: active && capabilitiesSettled && indexEntries !== null && !list.isFetching,\n tabShare: live?.tabShare,\n shareKey: sessionListShareKey(\n conversationListQueryKeys.firstPage(\n listScopeKey,\n conversationListFilterKey(baseFilter),\n pageSize,\n ),\n ),\n });\n\n const listFetchNextPage = list.fetchNextPage;\n return {\n rows: isAvailable ? rows : EMPTY_ROWS,\n isAvailable,\n isLoading: active && (!capabilitiesSettled || list.isLoading),\n isFetching: list.isFetching || capabilities.isFetching,\n hasResolvedFirstPage: list.hasResolvedFirstPage,\n isPlaceholderData,\n hasNextPage: list.hasNextPage,\n isFetchingNextPage: list.isFetchingNextPage,\n fetchNextPage: isAvailable ? () => void listFetchNextPage() : NOOP,\n refresh: isAvailable ? async () => void (await listRefresh()) : NOOP_ASYNC,\n isFetchNextPageError: list.isFetchNextPageError,\n supportedFilters,\n degradedFields: plan.degraded,\n unavailableFields: plan.unavailable,\n unsupportedFields,\n };\n}\n\ntype IndexEntryLike = Parameters<typeof entryCouldMatch>[0] & {\n thread_id: string;\n updated_at: string;\n app_id: string | null;\n};\n\n/**\n * The index entries that warrant one refresh of a filtered list, as\n * `<thread_id>@<updated_at>` versions: entries the list does not show, of\n * the apps it lists, that the query could match on what the entry carries,\n * and whose version has not prompted a refresh before. An unchanged entry the\n * server keeps leaving out refreshes once; any later change to it refreshes\n * again. Exported for tests.\n */\nexport function indexEntryVersionsToRefresh({\n entries,\n listedThreadIds,\n refreshedVersions,\n appId,\n additionalAppIds,\n query,\n}: {\n entries: readonly IndexEntryLike[];\n listedThreadIds: ReadonlySet<string>;\n refreshedVersions: ReadonlySet<string>;\n appId: string | undefined;\n additionalAppIds: readonly string[];\n query: AthenaSessionsQuery;\n}): string[] {\n const versionOf = (entry: IndexEntryLike) => `${entry.thread_id}@${entry.updated_at}`;\n const byVersion = new Map(entries.map((entry) => [versionOf(entry), entry]));\n return indexedThreadsToRefreshFor({\n entries: entries.map((entry) => ({ thread_id: versionOf(entry), app_id: entry.app_id })),\n listedThreadIds: new Set(\n entries.filter((entry) => listedThreadIds.has(entry.thread_id)).map(versionOf),\n ),\n refreshedThreadIds: refreshedVersions,\n appId,\n additionalAppIds,\n }).filter((version) => {\n const entry = byVersion.get(version);\n return entry === undefined || entryCouldMatch(entry, query);\n });\n}\n\n/**\n * Whether a pushed index entry could belong to a list with this local query:\n * the fields the entry carries are checked, the rest pass.\n */\nexport function entryCouldMatch(\n entry: {\n is_unread?: boolean | null;\n is_favorited?: boolean | null;\n run_status?: string | null;\n session_status?: string | null;\n },\n query: AthenaSessionsQuery,\n): boolean {\n if (query.unreadOnly && entry.is_unread === false) return false;\n if (query.favoritesOnly && entry.is_favorited === false) return false;\n if (\n query.runStatuses &&\n typeof entry.run_status === 'string' &&\n !(query.runStatuses as readonly string[]).includes(entry.run_status)\n ) {\n return false;\n }\n if (\n query.sessionStatuses &&\n typeof entry.session_status === 'string' &&\n !(query.sessionStatuses as readonly string[]).includes(entry.session_status)\n ) {\n return false;\n }\n return true;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-athena-sessions.js","sources":["../../src/threads/use-athena-sessions.ts"],"sourcesContent":["/**\n * `useAthenaSessions(query)` — one filtered, sorted, live session list under\n * `<AthenaProvider enableThreadList>`: the data layer of `SessionsPage` and\n * `Inbox`, for hosts drawing their own.\n *\n * - **Server-side.** The query travels to `POST /api/conversations/threads/list`\n * as filters of the server's one page statement (`planSessionsQuery`), over\n * the shared two-tier pager (`useConversationList`: a live first page and\n * frozen history pages) in the thread-list provider's query client.\n * - **Live.** Rows fold the same conversation-index pushes the sidebar folds\n * (titles, lifecycle, attention flip in place), carry the provider's\n * in-flight writes (pin, rename, read state, archive), and a thread the\n * index learns about that the list does not show yet refreshes the list\n * once.\n * - **Degraded, not wrong.** A server that cannot apply part of the query is\n * detected from its `supported_filters` echo (one `limit: 1` probe per\n * identity). Fields the loaded rows can be tested on are applied to them\n * (`degradedFields`: exact per page, a page can come back short); fields\n * they cannot are reported in `unavailableFields` and not applied, so a\n * control for them can be disabled rather than lie.\n */\nimport {\n type ConversationListFilter,\n type ConversationSummary,\n conversationListFilterKey,\n conversationListQueryKeys,\n useConversationList,\n} from '@athenaintel/agent-runtime';\nimport { useQuery } from '@tanstack/react-query';\nimport { useContext, useMemo, useRef } from 'react';\nimport { useAthenaQueryClient } from '../lib/use-athena-rest-query';\nimport { withCredentialRenewal } from '../runtime/credential-renewal';\nimport { listThreads } from './api';\nimport { createRestConversationListFetcher } from './conversation-list-fetcher';\nimport { useIndexTriggeredRefresh } from './index-triggered-refresh';\nimport { sessionListShareKey } from './session-list-tab-share';\nimport {\n type AthenaSessionsQuery,\n type AthenaSessionsQueryField,\n matchesSessionsQuery,\n planSessionsQuery,\n sessionsQueryFromKey,\n sessionsQueryKey,\n sortSessions,\n supportedFiltersFromResponse,\n unavailableSessionsQueryFields,\n} from './sessions-query';\nimport {\n AthenaSessionsLiveContext,\n type AthenaStatewireThreadSummary,\n foldConversationIndex,\n indexedThreadsToRefreshFor,\n threadSummaryFromConversation,\n} from './statewire-thread-list';\n\nexport interface UseAthenaSessionsOptions {\n /** What to list. A new object with the same content does not refetch. */\n query: AthenaSessionsQuery;\n /** Rows per page (default 50). */\n pageSize?: number;\n /** `false` holds the list idle. Default `true`. */\n enabled?: boolean;\n}\n\nexport interface AthenaSessionsList {\n /** The rows, in the query's order, with live pushes and in-flight writes applied. */\n rows: AthenaStatewireThreadSummary[];\n /** `false` outside `enableThreadList` mode: nothing is fetched and `rows` stays empty. */\n isAvailable: boolean;\n /** True until the first page has answered (or while no credential has resolved). */\n isLoading: boolean;\n isFetching: boolean;\n hasResolvedFirstPage: boolean;\n /**\n * True while the rows are the previous query's (checked against this\n * query's local part) because this query's first page is still in flight.\n */\n isPlaceholderData: boolean;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n fetchNextPage: () => void;\n /** Refetch the first page; resolves once it has answered. */\n refresh: () => Promise<void>;\n /** True while the last \"load more\" request failed. */\n isFetchNextPageError: boolean;\n /**\n * The server fields this backend applies (`supported_filters`), `null`\n * while unknown or on a backend that predates the list.\n */\n supportedFilters: ReadonlySet<string> | null;\n /** Fields applied to the loaded rows because the backend cannot. */\n degradedFields: AthenaSessionsQueryField[];\n /** Fields of this query that cannot be applied on this backend at all (and are not). */\n unavailableFields: AthenaSessionsQueryField[];\n /**\n * Every field this backend cannot apply anywhere, selected or not — what a\n * filter UI disables. Empty until the capabilities probe has answered.\n */\n unsupportedFields: AthenaSessionsQueryField[];\n}\n\nconst DEFAULT_PAGE_SIZE = 50;\n\n/** The capabilities probe's key prefix. */\nexport const THREAD_LIST_CAPABILITIES_QUERY_KEY = [\n 'athena-sdk',\n 'thread-list-capabilities',\n] as const;\n\nconst EMPTY_ROWS: AthenaStatewireThreadSummary[] = [];\nconst EMPTY_VERSIONS: ReadonlySet<string> = new Set();\nconst NOOP = () => undefined;\nconst NOOP_ASYNC = async () => undefined;\n\nexport function useAthenaSessions({\n query,\n pageSize = DEFAULT_PAGE_SIZE,\n enabled = true,\n}: UseAthenaSessionsOptions): AthenaSessionsList {\n const live = useContext(AthenaSessionsLiveContext);\n const isAvailable = live !== null;\n const backendUrl = live?.backendUrl ?? '';\n const principal = live?.principal ?? '';\n const hasAuth = live?.hasAuth ?? false;\n const includePullRequests = live?.includePullRequests ?? false;\n const getAuth = live?.getAuth;\n const active = isAvailable && enabled && hasAuth;\n // Every cache below is keyed by WHO reads (principal) and WITH WHAT (the\n // credential generation): a scoped credential for another workspace under\n // the same principal must never read this one's cached rows.\n const identityScope = `${live?.scopeKey ?? 'unavailable'}::credential-${\n live?.credentialGeneration ?? 0\n }`;\n\n // Outside thread-list mode there may be no `QueryClientProvider`; every\n // query below is disabled there, on a client of this hook's own.\n const queryClient = useAthenaQueryClient();\n\n // Which filters this backend applies: one tiny page per identity. The\n // answer is a property of the deployed agora, so it never goes stale.\n const capabilities = useQuery(\n {\n queryKey: [\n ...THREAD_LIST_CAPABILITIES_QUERY_KEY,\n backendUrl,\n principal,\n live?.credentialGeneration ?? 0,\n ],\n queryFn: async ({ signal }) => {\n if (!getAuth) return null;\n const response = await withCredentialRenewal(getAuth, (auth) =>\n listThreads(backendUrl, auth, { limit: 1, signal }),\n );\n return supportedFiltersFromResponse(response.supported_filters);\n },\n enabled: active,\n staleTime: Number.POSITIVE_INFINITY,\n retry: 1,\n },\n queryClient,\n );\n // A failed probe is treated as an older backend: the query degrades\n // instead of the list staying empty.\n const capabilitiesSettled = capabilities.isSuccess || capabilities.isError;\n const supportedFilters = capabilities.data ?? null;\n\n // `query` may be a fresh object every render; its normalized key is what\n // identifies it, so the plan is built from the key and recomputed only\n // when the content changes.\n const queryKey = sessionsQueryKey(query);\n const plan = useMemo(\n () =>\n planSessionsQuery(sessionsQueryFromKey(queryKey), {\n supportedFilters,\n includePullRequests,\n }),\n [queryKey, supportedFilters, includePullRequests],\n );\n const planRef = useRef(plan);\n planRef.current = plan;\n const unsupportedFields = useMemo(\n () =>\n capabilitiesSettled\n ? unavailableSessionsQueryFields({ supportedFilters, includePullRequests })\n : [],\n [capabilitiesSettled, supportedFilters, includePullRequests],\n );\n\n const fetchPage = useMemo(\n () =>\n getAuth\n ? createRestConversationListFetcher({\n backendUrl,\n getAuth,\n includePullRequests,\n extraFilters: plan.body,\n keepRow: (row) => matchesSessionsQuery(row, planRef.current.local),\n })\n : async () => ({ items: [], hasMore: false, totalCount: null }),\n [backendUrl, getAuth, includePullRequests, plan.body],\n );\n const baseFilter: ConversationListFilter = live?.baseFilter ?? {};\n const capabilityKey = supportedFilters ? [...supportedFilters].sort().join(',') : 'legacy';\n // The query's server filters are not part of the shared filter contract,\n // so they — and what this backend applies of them — key the cache here.\n const listScopeKey = `${identityScope}::sessions::${queryKey}::${capabilityKey}`;\n const list = useConversationList({\n fetchPage,\n filter: baseFilter,\n pageSize,\n scopeKey: listScopeKey,\n enabled: active && capabilitiesSettled,\n queryClient,\n });\n\n // While a changed query's first page is in flight, the previous query's\n // rows stand in, checked against the new query's local part — the list\n // narrows at once instead of blanking to skeletons. Only rows read as the\n // same identity and credential, over the same app scope, ever stand in: a\n // changed scope (another app, or the home agent's conversations turned off)\n // cannot be narrowed on the rows.\n const standInScope = `${identityScope}::${conversationListFilterKey(baseFilter)}`;\n const lastResolvedRef = useRef<{ scope: string; conversations: ConversationSummary[] }>({\n scope: '',\n conversations: [],\n });\n if (list.hasResolvedFirstPage) {\n lastResolvedRef.current = { scope: standInScope, conversations: list.conversations };\n }\n const standIn =\n !list.hasResolvedFirstPage && lastResolvedRef.current.scope === standInScope\n ? lastResolvedRef.current.conversations\n : null;\n const source = standIn ?? list.conversations;\n const isPlaceholderData = standIn !== null && standIn.length > 0;\n\n const indexByThread = live?.indexByThread;\n const applyOverrides = live?.applyOverrides;\n const rows = useMemo(() => {\n if (!indexByThread || !applyOverrides) return EMPTY_ROWS;\n const folded: ConversationSummary[] = applyOverrides(\n foldConversationIndex(source, indexByThread),\n ).filter((row) => matchesSessionsQuery(row, plan.local));\n return sortSessions(folded, plan.local).map(threadSummaryFromConversation);\n }, [source, indexByThread, applyOverrides, plan.local]);\n\n // A thread the index knows that this list does not show yet refreshes the\n // list — it may be new, or may have just come to match. Once per VERSION of\n // the entry (its id and `updated_at`): an unchanged entry the server keeps\n // leaving out never refreshes twice, and a later change to it (a PR opened,\n // a run now needing input) gets its own refresh. Coalesced, held while the\n // list is already being read, and a refused refresh is tried again\n // (`useIndexTriggeredRefresh`).\n const indexEntries = live?.indexEntries ?? null;\n const listRefresh = list.refresh;\n const baseAppId = baseFilter.appId;\n const baseAppIds = baseFilter.appIds;\n const baseHomeAgentId = baseFilter.homeAgentId;\n const homeAgentApplied = list.homeAgentApplied;\n const listedThreadIds = useMemo(\n () => new Set(list.conversations.map((conversation) => conversation.threadId)),\n [list.conversations],\n );\n const unlistedVersions = useMemo(\n () =>\n indexEntries === null\n ? []\n : indexEntryVersionsToRefresh({\n entries: indexEntries,\n listedThreadIds,\n refreshedVersions: EMPTY_VERSIONS,\n appId: baseAppId,\n additionalAppIds: baseAppIds ?? [],\n homeAgentId: baseHomeAgentId,\n homeAgentApplied,\n query: plan.local,\n }),\n [\n indexEntries,\n listedThreadIds,\n baseAppId,\n baseAppIds,\n baseHomeAgentId,\n homeAgentApplied,\n plan.local,\n ],\n );\n useIndexTriggeredRefresh({\n pending: unlistedVersions,\n refresh: listRefresh,\n // Held while the list is being read; a refused first page does not hold\n // it (the refresh is how the list recovers).\n enabled: active && capabilitiesSettled && indexEntries !== null && !list.isFetching,\n tabShare: live?.tabShare,\n shareKey: sessionListShareKey(\n conversationListQueryKeys.firstPage(\n listScopeKey,\n conversationListFilterKey(baseFilter),\n pageSize,\n ),\n ),\n });\n\n const listFetchNextPage = list.fetchNextPage;\n return {\n rows: isAvailable ? rows : EMPTY_ROWS,\n isAvailable,\n isLoading: active && (!capabilitiesSettled || list.isLoading),\n isFetching: list.isFetching || capabilities.isFetching,\n hasResolvedFirstPage: list.hasResolvedFirstPage,\n isPlaceholderData,\n hasNextPage: list.hasNextPage,\n isFetchingNextPage: list.isFetchingNextPage,\n fetchNextPage: isAvailable ? () => void listFetchNextPage() : NOOP,\n refresh: isAvailable ? async () => void (await listRefresh()) : NOOP_ASYNC,\n isFetchNextPageError: list.isFetchNextPageError,\n supportedFilters,\n degradedFields: plan.degraded,\n unavailableFields: plan.unavailable,\n unsupportedFields,\n };\n}\n\ntype IndexEntryLike = Parameters<typeof entryCouldMatch>[0] & {\n thread_id: string;\n updated_at: string;\n app_id: string | null;\n collab_agent_id?: string | null;\n start_channel?: string | null;\n};\n\n/**\n * The index entries that warrant one refresh of a filtered list, as\n * `<thread_id>@<updated_at>` versions: entries the list does not show, of\n * the apps it lists (and, while `homeAgentApplied`, of the conversations with\n * `homeAgentId`'s agent; see `indexedThreadsToRefreshFor`), that the query\n * could match on what the entry carries, and whose version has not prompted a\n * refresh before. An unchanged entry the server keeps leaving out refreshes\n * once; any later change to it refreshes again. Exported for tests.\n */\nexport function indexEntryVersionsToRefresh({\n entries,\n listedThreadIds,\n refreshedVersions,\n appId,\n additionalAppIds,\n homeAgentId,\n homeAgentApplied,\n query,\n}: {\n entries: readonly IndexEntryLike[];\n listedThreadIds: ReadonlySet<string>;\n refreshedVersions: ReadonlySet<string>;\n appId: string | undefined;\n additionalAppIds: readonly string[];\n homeAgentId?: string;\n homeAgentApplied?: boolean;\n query: AthenaSessionsQuery;\n}): string[] {\n const versionOf = (entry: IndexEntryLike) => `${entry.thread_id}@${entry.updated_at}`;\n const byVersion = new Map(entries.map((entry) => [versionOf(entry), entry]));\n return indexedThreadsToRefreshFor({\n entries: entries.map((entry) => ({\n thread_id: versionOf(entry),\n app_id: entry.app_id,\n collab_agent_id: entry.collab_agent_id,\n start_channel: entry.start_channel,\n })),\n listedThreadIds: new Set(\n entries.filter((entry) => listedThreadIds.has(entry.thread_id)).map(versionOf),\n ),\n refreshedThreadIds: refreshedVersions,\n appId,\n additionalAppIds,\n homeAgentId,\n homeAgentApplied,\n }).filter((version) => {\n const entry = byVersion.get(version);\n return entry === undefined || entryCouldMatch(entry, query);\n });\n}\n\n/**\n * Whether a pushed index entry could belong to a list with this local query:\n * the fields the entry carries are checked, the rest pass.\n */\nexport function entryCouldMatch(\n entry: {\n is_unread?: boolean | null;\n is_favorited?: boolean | null;\n run_status?: string | null;\n session_status?: string | null;\n },\n query: AthenaSessionsQuery,\n): boolean {\n if (query.unreadOnly && entry.is_unread === false) return false;\n if (query.favoritesOnly && entry.is_favorited === false) return false;\n if (\n query.runStatuses &&\n typeof entry.run_status === 'string' &&\n !(query.runStatuses as readonly string[]).includes(entry.run_status)\n ) {\n return false;\n }\n if (\n query.sessionStatuses &&\n typeof entry.session_status === 'string' &&\n !(query.sessionStatuses as readonly string[]).includes(entry.session_status)\n ) {\n return false;\n }\n return true;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -21,6 +21,7 @@ export interface UseRestConversationHistoryOptions {
|
|
|
21
21
|
*/
|
|
22
22
|
export declare function useRestConversationHistory({ backendUrl, principalId, getAuth, search, enabled, pageSize, }: UseRestConversationHistoryOptions): {
|
|
23
23
|
totalCount: number | null;
|
|
24
|
+
homeAgentApplied: boolean;
|
|
24
25
|
isLoading: boolean;
|
|
25
26
|
isFetching: boolean;
|
|
26
27
|
hasResolvedFirstPage: boolean;
|