@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.
Files changed (179) hide show
  1. package/README.md +48 -10
  2. package/dist/agent-runtime/src/call-recap.cjs +319 -0
  3. package/dist/agent-runtime/src/call-recap.cjs.map +1 -0
  4. package/dist/agent-runtime/src/call-recap.d.ts +142 -0
  5. package/dist/agent-runtime/src/call-recap.js +319 -0
  6. package/dist/agent-runtime/src/call-recap.js.map +1 -0
  7. package/dist/agent-runtime/src/chat-outbox-store.cjs +1 -0
  8. package/dist/agent-runtime/src/chat-outbox-store.cjs.map +1 -1
  9. package/dist/agent-runtime/src/chat-outbox-store.d.ts +7 -0
  10. package/dist/agent-runtime/src/chat-outbox-store.js +1 -0
  11. package/dist/agent-runtime/src/chat-outbox-store.js.map +1 -1
  12. package/dist/agent-runtime/src/chat-outbox.cjs +46 -5
  13. package/dist/agent-runtime/src/chat-outbox.cjs.map +1 -1
  14. package/dist/agent-runtime/src/chat-outbox.d.ts +29 -0
  15. package/dist/agent-runtime/src/chat-outbox.js +46 -5
  16. package/dist/agent-runtime/src/chat-outbox.js.map +1 -1
  17. package/dist/agent-runtime/src/conversation-index.cjs.map +1 -1
  18. package/dist/agent-runtime/src/conversation-index.d.ts +7 -0
  19. package/dist/agent-runtime/src/conversation-index.js.map +1 -1
  20. package/dist/agent-runtime/src/conversation-list.cjs +2 -1
  21. package/dist/agent-runtime/src/conversation-list.cjs.map +1 -1
  22. package/dist/agent-runtime/src/conversation-list.d.ts +19 -3
  23. package/dist/agent-runtime/src/conversation-list.js +2 -1
  24. package/dist/agent-runtime/src/conversation-list.js.map +1 -1
  25. package/dist/agent-runtime/src/index.d.ts +6 -5
  26. package/dist/agent-runtime/src/interrupts.cjs +28 -1
  27. package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
  28. package/dist/agent-runtime/src/interrupts.d.ts +21 -1
  29. package/dist/agent-runtime/src/interrupts.js +28 -1
  30. package/dist/agent-runtime/src/interrupts.js.map +1 -1
  31. package/dist/agent-runtime/src/model-context.cjs +3 -2
  32. package/dist/agent-runtime/src/model-context.cjs.map +1 -1
  33. package/dist/agent-runtime/src/model-context.d.ts +6 -1
  34. package/dist/agent-runtime/src/model-context.js +3 -2
  35. package/dist/agent-runtime/src/model-context.js.map +1 -1
  36. package/dist/agent-runtime/src/runtime.d.ts +3 -2
  37. package/dist/agent-runtime/src/send-recovery.cjs +37 -3
  38. package/dist/agent-runtime/src/send-recovery.cjs.map +1 -1
  39. package/dist/agent-runtime/src/send-recovery.d.ts +19 -0
  40. package/dist/agent-runtime/src/send-recovery.js +37 -3
  41. package/dist/agent-runtime/src/send-recovery.js.map +1 -1
  42. package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +8 -8
  43. package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +113 -1
  44. package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
  45. package/dist/agent-runtime/src/use-conversation-list.cjs +3 -2
  46. package/dist/agent-runtime/src/use-conversation-list.cjs.map +1 -1
  47. package/dist/agent-runtime/src/use-conversation-list.d.ts +6 -0
  48. package/dist/agent-runtime/src/use-conversation-list.js +3 -2
  49. package/dist/agent-runtime/src/use-conversation-list.js.map +1 -1
  50. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs +21 -1
  51. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs.map +1 -1
  52. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.d.ts +8 -2
  53. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js +21 -1
  54. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js.map +1 -1
  55. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +24 -6
  56. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
  57. package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +17 -0
  58. package/dist/agent-runtime/src/use-deep-agent-runtime.js +24 -6
  59. package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
  60. package/dist/agent-runtime/src/use-deep-agent-voice.cjs +44 -9
  61. package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
  62. package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +34 -1
  63. package/dist/agent-runtime/src/use-deep-agent-voice.js +44 -9
  64. package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
  65. package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +8 -2
  66. package/dist/agent-runtime/src/voice-earcon.cjs +139 -0
  67. package/dist/agent-runtime/src/voice-earcon.cjs.map +1 -0
  68. package/dist/agent-runtime/src/voice-earcon.d.ts +38 -0
  69. package/dist/agent-runtime/src/voice-earcon.js +139 -0
  70. package/dist/agent-runtime/src/voice-earcon.js.map +1 -0
  71. package/dist/agent-runtime/src/voice-media.cjs.map +1 -1
  72. package/dist/agent-runtime/src/voice-media.d.ts +14 -0
  73. package/dist/agent-runtime/src/voice-media.js.map +1 -1
  74. package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
  75. package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
  76. package/dist/agent-runtime/src/voice-presentation.js +2 -0
  77. package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
  78. package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
  79. package/dist/assets/AthenaAssetEmbed.d.ts +11 -5
  80. package/dist/assets/AthenaAssetEmbed.js.map +1 -1
  81. package/dist/assets/use-asset-embed.cjs.map +1 -1
  82. package/dist/assets/use-asset-embed.d.ts +12 -5
  83. package/dist/assets/use-asset-embed.js.map +1 -1
  84. package/dist/chat/StatewireApprovalCard.cjs +41 -6
  85. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  86. package/dist/chat/StatewireApprovalCard.d.ts +18 -1
  87. package/dist/chat/StatewireApprovalCard.js +42 -7
  88. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  89. package/dist/chat/StatewireQueuedMessages.cjs +56 -6
  90. package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
  91. package/dist/chat/StatewireQueuedMessages.d.ts +14 -0
  92. package/dist/chat/StatewireQueuedMessages.js +58 -8
  93. package/dist/chat/StatewireQueuedMessages.js.map +1 -1
  94. package/dist/chat/WaitingOnYouBanner.cjs +6 -2
  95. package/dist/chat/WaitingOnYouBanner.cjs.map +1 -1
  96. package/dist/chat/WaitingOnYouBanner.js +7 -3
  97. package/dist/chat/WaitingOnYouBanner.js.map +1 -1
  98. package/dist/chat/chat-voice-controls.cjs +3 -1
  99. package/dist/chat/chat-voice-controls.cjs.map +1 -1
  100. package/dist/chat/chat-voice-controls.d.ts +6 -1
  101. package/dist/chat/chat-voice-controls.js +3 -1
  102. package/dist/chat/chat-voice-controls.js.map +1 -1
  103. package/dist/chat-ui/index.cjs +3 -0
  104. package/dist/chat-ui/index.cjs.map +1 -1
  105. package/dist/chat-ui/index.d.ts +1 -0
  106. package/dist/chat-ui/index.js +3 -0
  107. package/dist/chat-ui/index.js.map +1 -1
  108. package/dist/chat-ui/messages/from-the-call-card.cjs +124 -0
  109. package/dist/chat-ui/messages/from-the-call-card.cjs.map +1 -0
  110. package/dist/chat-ui/messages/from-the-call-card.d.ts +17 -0
  111. package/dist/chat-ui/messages/from-the-call-card.js +124 -0
  112. package/dist/chat-ui/messages/from-the-call-card.js.map +1 -0
  113. package/dist/chat-ui/rich-text/chat-link.cjs +1 -0
  114. package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
  115. package/dist/chat-ui/rich-text/chat-link.d.ts +4 -0
  116. package/dist/chat-ui/rich-text/chat-link.js +4 -3
  117. package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
  118. package/dist/chat-ui/rich-text/chat-message-body.cjs +1 -0
  119. package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
  120. package/dist/chat-ui/rich-text/chat-message-body.d.ts +12 -0
  121. package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
  122. package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
  123. package/dist/chat-ui/thread/chat-thread-shell.cjs +2 -0
  124. package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
  125. package/dist/chat-ui/thread/chat-thread-shell.js +2 -0
  126. package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
  127. package/dist/collab/client.cjs +15 -3
  128. package/dist/collab/client.cjs.map +1 -1
  129. package/dist/collab/client.d.ts +4 -2
  130. package/dist/collab/client.js +16 -4
  131. package/dist/collab/client.js.map +1 -1
  132. package/dist/composer/index.d.ts +1 -1
  133. package/dist/composer/useReadComposerDraft.cjs +26 -7
  134. package/dist/composer/useReadComposerDraft.cjs.map +1 -1
  135. package/dist/composer/useReadComposerDraft.d.ts +23 -1
  136. package/dist/composer/useReadComposerDraft.js +27 -8
  137. package/dist/composer/useReadComposerDraft.js.map +1 -1
  138. package/dist/index.cjs +3 -0
  139. package/dist/index.cjs.map +1 -1
  140. package/dist/index.d.ts +3 -1
  141. package/dist/index.js +4 -1
  142. package/dist/index.js.map +1 -1
  143. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +190 -0
  144. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -0
  145. package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +132 -0
  146. package/dist/keryx-http/src/unconfirmed-update-attributions.js +190 -0
  147. package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -0
  148. package/dist/keryx-http/src/update-attributions.cjs +34 -4
  149. package/dist/keryx-http/src/update-attributions.cjs.map +1 -1
  150. package/dist/keryx-http/src/update-attributions.d.ts +36 -4
  151. package/dist/keryx-http/src/update-attributions.js +35 -5
  152. package/dist/keryx-http/src/update-attributions.js.map +1 -1
  153. package/dist/provider/AthenaProvider.cjs +4 -0
  154. package/dist/provider/AthenaProvider.cjs.map +1 -1
  155. package/dist/provider/AthenaProvider.d.ts +11 -1
  156. package/dist/provider/AthenaProvider.js +4 -0
  157. package/dist/provider/AthenaProvider.js.map +1 -1
  158. package/dist/react/package.json.cjs +1 -1
  159. package/dist/react/package.json.js +1 -1
  160. package/dist/threads/api.cjs.map +1 -1
  161. package/dist/threads/api.d.ts +15 -0
  162. package/dist/threads/api.js.map +1 -1
  163. package/dist/threads/conversation-list-fetcher.cjs +13 -5
  164. package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
  165. package/dist/threads/conversation-list-fetcher.d.ts +4 -1
  166. package/dist/threads/conversation-list-fetcher.js +13 -5
  167. package/dist/threads/conversation-list-fetcher.js.map +1 -1
  168. package/dist/threads/statewire-thread-list.cjs +28 -6
  169. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  170. package/dist/threads/statewire-thread-list.d.ts +20 -8
  171. package/dist/threads/statewire-thread-list.js +28 -6
  172. package/dist/threads/statewire-thread-list.js.map +1 -1
  173. package/dist/threads/use-athena-sessions.cjs +27 -5
  174. package/dist/threads/use-athena-sessions.cjs.map +1 -1
  175. package/dist/threads/use-athena-sessions.d.ts +10 -5
  176. package/dist/threads/use-athena-sessions.js +28 -6
  177. package/dist/threads/use-athena-sessions.js.map +1 -1
  178. package/dist/threads/use-rest-conversation-history.d.ts +1 -0
  179. 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, that the query could match on what the entry carries,
59
- * and whose version has not prompted a refresh before. An unchanged entry the
60
- * server keeps leaving out refreshes once; any later change to it refreshes
61
- * again. Exported for tests.
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 { conversationListQueryKeys, conversationListFilterKey } from "../agent-runtime/src/conversation-list.js";
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: identityScope, conversations: list.conversations };
102
+ lastResolvedRef.current = { scope: standInScope, conversations: list.conversations };
102
103
  }
103
- const standIn = !list.hasResolvedFirstPage && lastResolvedRef.current.scope === identityScope ? lastResolvedRef.current.conversations : null;
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
- [indexEntries, listedThreadIds, baseAppId, baseAppIds, plan.local]
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) => ({ thread_id: versionOf(entry), app_id: entry.app_id })),
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@athenaintel/react",
3
- "version": "0.48.0",
3
+ "version": "0.50.0",
4
4
  "description": "Athena React SDK — Build AI-powered chat applications with the Athena platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",