@athenaintel/react 0.57.0 → 0.57.2

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 (48) hide show
  1. package/README.md +25 -2
  2. package/dist/chat/AthenaChat.cjs +61 -3
  3. package/dist/chat/AthenaChat.cjs.map +1 -1
  4. package/dist/chat/AthenaChat.js +62 -4
  5. package/dist/chat/AthenaChat.js.map +1 -1
  6. package/dist/chat-ui/index.cjs +1 -0
  7. package/dist/chat-ui/index.cjs.map +1 -1
  8. package/dist/chat-ui/index.d.ts +1 -1
  9. package/dist/chat-ui/index.js +2 -1
  10. package/dist/chat-ui/messages/inbound-channel-message.cjs +23 -0
  11. package/dist/chat-ui/messages/inbound-channel-message.cjs.map +1 -1
  12. package/dist/chat-ui/messages/inbound-channel-message.d.ts +13 -20
  13. package/dist/chat-ui/messages/inbound-channel-message.js +24 -1
  14. package/dist/chat-ui/messages/inbound-channel-message.js.map +1 -1
  15. package/dist/chat-ui/messages/message-components.cjs +14 -1
  16. package/dist/chat-ui/messages/message-components.cjs.map +1 -1
  17. package/dist/chat-ui/messages/message-components.js +15 -2
  18. package/dist/chat-ui/messages/message-components.js.map +1 -1
  19. package/dist/index.cjs +1 -0
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +2 -1
  23. package/dist/provider/AthenaProvider.cjs +7 -0
  24. package/dist/provider/AthenaProvider.cjs.map +1 -1
  25. package/dist/provider/AthenaProvider.js +9 -2
  26. package/dist/provider/AthenaProvider.js.map +1 -1
  27. package/dist/react/package.json.cjs +1 -1
  28. package/dist/react/package.json.js +1 -1
  29. package/dist/runtime/statewire-run-config.cjs +15 -0
  30. package/dist/runtime/statewire-run-config.cjs.map +1 -1
  31. package/dist/runtime/statewire-run-config.d.ts +13 -0
  32. package/dist/runtime/statewire-run-config.js +15 -0
  33. package/dist/runtime/statewire-run-config.js.map +1 -1
  34. package/dist/threads/SessionList.cjs +22 -19
  35. package/dist/threads/SessionList.cjs.map +1 -1
  36. package/dist/threads/SessionList.js +22 -19
  37. package/dist/threads/SessionList.js.map +1 -1
  38. package/dist/threads/live-session-rows.cjs +207 -0
  39. package/dist/threads/live-session-rows.cjs.map +1 -0
  40. package/dist/threads/live-session-rows.d.ts +175 -0
  41. package/dist/threads/live-session-rows.js +207 -0
  42. package/dist/threads/live-session-rows.js.map +1 -0
  43. package/dist/threads/statewire-thread-list.cjs +173 -20
  44. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  45. package/dist/threads/statewire-thread-list.d.ts +44 -1
  46. package/dist/threads/statewire-thread-list.js +174 -21
  47. package/dist/threads/statewire-thread-list.js.map +1 -1
  48. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-session-rows.js","sources":["../../src/threads/live-session-rows.ts"],"sourcesContent":["/**\n * The rows the session list shows between one server answer and the next.\n *\n * The list's rows come from the fetched REST pages, and the signed-in user's\n * live conversation index updates them in place. Two gaps are filled here, so\n * the list follows what just happened without waiting for a re-read:\n *\n * - **Sessions the index pushed that the page does not list yet.** A session\n * just started (in this tab or anywhere else) reaches the index in about a\n * second; the re-read the list runs for it takes several more (measured on\n * staging 2026-11-06: push at +1.8 s, row drawn at +5.5 s). The pushed entry\n * is drawn at once, while the re-read still fills in what the index never\n * carries (pull requests, who started it).\n * - **A message this tab just sent.** The session moves to the top as working\n * the moment it is sent (a chat the server does not know yet gets a pending\n * row titled with the message), until the server reports the send.\n *\n * Rows are kept newest first by `updatedAt`, the server's own order, so a\n * session that just became active moves up instead of staying where its last\n * fetched copy was.\n *\n * Pure functions over the shared contract rows: the provider composes them,\n * and they are tested on their own.\n */\nimport {\n type ConversationIndexEntry,\n type ConversationSummary,\n summaryFromConversationIndexEntry,\n} from '@athenaintel/agent-runtime';\n\n/**\n * Start channels of an agent's sessions that the server leaves out when it lists the conversations\n * with a home agent (`home_agent_id`): the agent's tasks and the sessions an orchestrating agent\n * starts.\n */\nconst AGENT_HOME_EXCLUDED_START_CHANNELS: ReadonlySet<string> = new Set([\n 'task',\n 'agent_orchestration',\n]);\n\n/** The REST thread list only lists this asset type (`sdk_thread_list.py`); the index carries more. */\nconst LISTED_ASSET_TYPE = 'session';\n\n/**\n * Whether a pushed index entry is one of the conversations with `homeAgentId`'s agent that the\n * server lists beside the app scope: the agent's, and not one of its tasks or orchestration\n * sessions. An entry from a pod that predates `collab_agent_id` is not.\n */\nexport function isAgentHomeIndexEntry(\n entry: { collab_agent_id?: string | null; start_channel?: string | null },\n homeAgentId: string | undefined,\n): boolean {\n if (!homeAgentId || entry.collab_agent_id !== homeAgentId) return false;\n return !AGENT_HOME_EXCLUDED_START_CHANNELS.has(entry.start_channel ?? '');\n}\n\n/** Which sessions a list shows: its app scope, as the server applies it to every read. */\nexport interface SessionListScope {\n /** The provider's `appId`; the app's own sessions. */\n appId: string | undefined;\n /** Other apps whose sessions the list shows too (a union with `appId`). */\n additionalAppIds: readonly string[];\n /** The collab agent whose conversations the list also asks for (`home_agent_id`). */\n homeAgentId: string | undefined;\n /**\n * Whether the list's latest first page said the server widened it to the home agent's\n * conversations. A server that did not never lists them.\n */\n homeAgentApplied: boolean;\n}\n\n/**\n * Whether the server lists a session with these stamps in a list of this scope — the app filter\n * and the home-agent union exactly as `sdk_thread_list.py` applies them. With no app ids the list\n * holds every app's sessions, unless it was widened to an agent, which then makes it that agent's.\n */\nexport function inSessionListScope(\n entry: { app_id: string | null; collab_agent_id?: string | null; start_channel?: string | null },\n scope: SessionListScope,\n): boolean {\n const appIds = [...(scope.appId ? [scope.appId] : []), ...scope.additionalAppIds];\n const agentId = scope.homeAgentApplied ? scope.homeAgentId : undefined;\n if (appIds.length === 0) return agentId ? isAgentHomeIndexEntry(entry, agentId) : true;\n return appIds.includes(entry.app_id ?? '') || isAgentHomeIndexEntry(entry, agentId);\n}\n\nfunction timestampMs(value: string): number {\n const parsed = Date.parse(value);\n return Number.isNaN(parsed) ? 0 : parsed;\n}\n\n/**\n * The listed rows plus every session the index pushed that the list should show and does not\n * list yet: in the list's scope, a listed asset type, and — while older pages remain unread — no\n * older than the oldest loaded row, so a row is never drawn below sessions that are not loaded\n * yet. Nothing is added while a search is active (the server matches it; the index cannot) or\n * before the first page has answered (the page decides what the list starts with). Identity is\n * kept when nothing is added. The rows are not re-sorted here ({@link sortNewestFirst}).\n */\nexport function withIndexedSessions({\n conversations,\n loadedConversations,\n entries,\n scope,\n searching,\n hasResolvedFirstPage,\n hasNextPage,\n}: {\n /** The rows the list shows (fetched pages, pinned rows, folded with the index). */\n conversations: readonly ConversationSummary[];\n /** The fetched pages' rows alone: the window older pages continue below. */\n loadedConversations: readonly ConversationSummary[];\n entries: readonly ConversationIndexEntry[] | null;\n scope: SessionListScope;\n searching: boolean;\n hasResolvedFirstPage: boolean;\n hasNextPage: boolean;\n}): readonly ConversationSummary[] {\n if (!entries || entries.length === 0 || searching || !hasResolvedFirstPage) return conversations;\n const listed = new Set(conversations.map((conversation) => conversation.threadId));\n let windowTailMs: number | null = null;\n if (hasNextPage) {\n for (const conversation of loadedConversations) {\n const ms = timestampMs(conversation.updatedAt);\n if (windowTailMs === null || ms < windowTailMs) windowTailMs = ms;\n }\n }\n const added: ConversationSummary[] = [];\n for (const entry of entries) {\n if (listed.has(entry.thread_id)) continue;\n if (entry.asset_type !== LISTED_ASSET_TYPE) continue;\n if (!inSessionListScope(entry, scope)) continue;\n if (windowTailMs !== null && timestampMs(entry.updated_at) < windowTailMs) continue;\n listed.add(entry.thread_id);\n added.push(summaryFromConversationIndexEntry(entry));\n }\n return added.length === 0 ? conversations : [...conversations, ...added];\n}\n\n/** How long a sent message holds its session's row before the server's own copy must take over. */\nexport const LOCAL_SEND_TTL_MS = 60_000;\n\n/** The longest provisional title kept from a sent message; the server's own title replaces it. */\nconst PENDING_TITLE_MAX_LENGTH = 200;\n\n/**\n * A message this tab sent that the server has not reported yet. Held by the provider; it\n * shapes the session's row until the server's copy of the session reflects the send.\n */\nexport interface LocalSessionSend {\n threadId: string;\n /** When this tab sent it (the client's clock, ms since the epoch). */\n sentAtMs: number;\n /**\n * The instant the row sorts at until the server reports the send: the send time, and never\n * older than the newest row the list showed when it was sent, so the session leads the list\n * even on a client whose clock runs behind the server's.\n */\n sortAt: string;\n /**\n * The session's `updatedAt` as the list showed it when the message was sent; `null` when the\n * list did not show the session (a new chat, or an older session the loaded pages do not\n * reach). The send is reported once the server's row differs.\n */\n baselineUpdatedAt: string | null;\n /**\n * Whether the session was a chat this tab started that the server had not listed yet. Only\n * such a send gets a pending row: a session missing from the loaded pages may well exist.\n */\n newChat: boolean;\n /** The message as one line, the pending title of a chat the server does not know yet. */\n title: string | null;\n /**\n * The provenance the session's first run stamps, when it is known here (the SDK's own `web`,\n * or a host's `extraRunConfig.start_channel`); `null` when the backend decides it (a collab\n * channel stamps its own kind). A new chat's pending row shows it as its origin.\n */\n startChannel: string | null;\n}\n\n/**\n * A sent message as the one-line provisional title of its session: markdown links reduced to\n * their text (a mention reads as its name), whitespace folded, and long messages cut. `null`\n * when nothing is left (an attachment sent alone).\n */\nexport function pendingSessionTitle(text: string | null | undefined): string | null {\n const line = (text ?? '')\n .replace(/!?\\[([^\\]]*)\\]\\([^)]*\\)/g, '$1')\n .replace(/\\s+/g, ' ')\n .trim();\n if (!line) return null;\n return line.length <= PENDING_TITLE_MAX_LENGTH\n ? line\n : `${line.slice(0, PENDING_TITLE_MAX_LENGTH - 1).trimEnd()}…`;\n}\n\n/**\n * Record a send against the rows the server last reported: the baseline the server must move\n * past, and the instant the row sorts at until it does.\n */\nexport function localSessionSend({\n threadId,\n text,\n sentAtMs,\n newChat,\n startChannel,\n serverConversations,\n}: {\n threadId: string;\n text: string | null | undefined;\n sentAtMs: number;\n /** The session was a chat this tab started that the server had not listed yet. */\n newChat: boolean;\n /** The `start_channel` the send stamps (`effectiveStatewireStartChannel`); `null` if unknown. */\n startChannel: string | null;\n /** The server's rows as the list shows them (fetched pages and pushed index entries). */\n serverConversations: readonly ConversationSummary[];\n}): LocalSessionSend {\n let newestMs = 0;\n let baselineUpdatedAt: string | null = null;\n for (const conversation of serverConversations) {\n const ms = timestampMs(conversation.updatedAt);\n if (ms > newestMs) newestMs = ms;\n if (conversation.threadId === threadId) baselineUpdatedAt = conversation.updatedAt;\n }\n return {\n threadId,\n sentAtMs,\n sortAt: new Date(Math.max(sentAtMs, newestMs + 1)).toISOString(),\n baselineUpdatedAt,\n newChat: newChat && baselineUpdatedAt === null,\n title: pendingSessionTitle(text),\n startChannel,\n };\n}\n\n/**\n * Whether the server's copy of the session reflects the send: a session the list did not show\n * is reported once the server lists it at all; a listed session once its `updatedAt` moved.\n */\nexport function isLocalSendReported(\n send: LocalSessionSend,\n serverRow: ConversationSummary | undefined,\n): boolean {\n if (!serverRow) return false;\n return send.baselineUpdatedAt === null || serverRow.updatedAt !== send.baselineUpdatedAt;\n}\n\n/** The sends without `threadIds`; the same map when it holds none of them. */\nexport function withoutLocalSends(\n sends: ReadonlyMap<string, LocalSessionSend>,\n threadIds: readonly string[],\n): ReadonlyMap<string, LocalSessionSend> {\n if (!threadIds.some((threadId) => sends.has(threadId))) return sends;\n const next = new Map(sends);\n for (const threadId of threadIds) next.delete(threadId);\n return next;\n}\n\n/** The sends to forget: reported by the server, or older than {@link LOCAL_SEND_TTL_MS}. */\nexport function settledLocalSends({\n sends,\n serverConversations,\n nowMs,\n ttlMs = LOCAL_SEND_TTL_MS,\n}: {\n sends: ReadonlyMap<string, LocalSessionSend>;\n serverConversations: readonly ConversationSummary[];\n nowMs: number;\n ttlMs?: number;\n}): string[] {\n if (sends.size === 0) return [];\n const byThread = new Map(serverConversations.map((row) => [row.threadId, row]));\n const settled: string[] = [];\n for (const send of sends.values()) {\n if (nowMs - send.sentAtMs >= ttlMs || isLocalSendReported(send, byThread.get(send.threadId))) {\n settled.push(send.threadId);\n }\n }\n return settled;\n}\n\n/** The session as working, sorted at the send, until the server reports the send. */\nfunction withSentMessage(row: ConversationSummary, send: LocalSessionSend): ConversationSummary {\n return {\n ...row,\n updatedAt: timestampMs(row.updatedAt) >= timestampMs(send.sortAt) ? row.updatedAt : send.sortAt,\n state: 'running',\n sessionStatus: 'active',\n runStatus: 'running',\n attention: 'working',\n nextAction: null,\n attentionSource: null,\n isUnread: false,\n };\n}\n\n/** The pending row of a chat the server does not know yet, as the server will first list it. */\nfunction pendingConversation(send: LocalSessionSend, appId: string | undefined): ConversationSummary {\n const sentAt = new Date(send.sentAtMs).toISOString();\n return {\n threadId: send.threadId,\n title: send.title ?? '',\n updatedAt: send.sortAt,\n createdAt: sentAt,\n state: 'running',\n runtime: null,\n // What the server stamps on the session's first run, when that is known here: the row's\n // origin badge reads as the server's row will, never as a label made from the app id.\n sourceChannel: send.startChannel,\n assetType: LISTED_ASSET_TYPE,\n assetId: null,\n appId: appId ?? null,\n triggerType: null,\n preview: send.title,\n messageCount: null,\n isUnread: false,\n sessionStatus: 'active',\n runStatus: 'running',\n attention: 'working',\n nextAction: null,\n attentionSource: null,\n isFavorited: false,\n createdByMe: true,\n };\n}\n\n/**\n * The rows with this tab's unreported sends applied: a listed session the server has not\n * reported the send for shows as working and sorts at the send; a new chat this tab started\n * gets a pending row, if the list's scope holds this app's new sessions and no search is\n * active. A session the loaded pages do not reach gets nothing here: it exists, and the\n * server's push (or re-read) brings it to the top. Returns the rows and the ids of the pending\n * rows (which the server does not know, so writes cannot be offered on them). Identity is kept\n * when no send applies.\n */\nexport function withLocalSends({\n conversations,\n sends,\n appId,\n showPendingSessions,\n}: {\n conversations: readonly ConversationSummary[];\n sends: ReadonlyMap<string, LocalSessionSend>;\n /** The app new sessions are tagged with (the provider's `appId`). */\n appId: string | undefined;\n /** Whether a new chat's pending row belongs in this list (its scope and search allow it). */\n showPendingSessions: boolean;\n}): { conversations: readonly ConversationSummary[]; pendingThreadIds: ReadonlySet<string> } {\n if (sends.size === 0) return { conversations, pendingThreadIds: EMPTY_IDS };\n const listed = new Set<string>();\n let changed = false;\n const rows = conversations.map((row) => {\n listed.add(row.threadId);\n const send = sends.get(row.threadId);\n if (!send || isLocalSendReported(send, row)) return row;\n changed = true;\n return withSentMessage(row, send);\n });\n const pendingThreadIds = new Set<string>();\n if (showPendingSessions) {\n for (const send of sends.values()) {\n if (!send.newChat || listed.has(send.threadId)) continue;\n pendingThreadIds.add(send.threadId);\n rows.push(pendingConversation(send, appId));\n changed = true;\n }\n }\n return {\n conversations: changed ? rows : conversations,\n pendingThreadIds: pendingThreadIds.size === 0 ? EMPTY_IDS : pendingThreadIds,\n };\n}\n\nconst EMPTY_IDS: ReadonlySet<string> = new Set();\n\n/**\n * The rows newest first by `updatedAt` (the order the server lists them in), keeping the given\n * order between rows of the same instant. Identity is kept when the rows are already in order.\n */\nexport function sortNewestFirst<T extends { updatedAt: string }>(rows: readonly T[]): readonly T[] {\n const keyed = rows.map((row, index) => ({ row, index, ms: timestampMs(row.updatedAt) }));\n let ordered = true;\n for (let i = 1; i < keyed.length; i++) {\n if ((keyed[i - 1]?.ms ?? 0) < (keyed[i]?.ms ?? 0)) {\n ordered = false;\n break;\n }\n }\n if (ordered) return rows;\n keyed.sort((a, b) => b.ms - a.ms || a.index - b.index);\n return keyed.map(({ row }) => row);\n}\n"],"names":[],"mappings":"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"}
@@ -15,6 +15,7 @@ const mintThreadId = require("./mint-thread-id.cjs");
15
15
  const threadIdContext = require("./thread-id-context.cjs");
16
16
  const threadPrefetcher = require("./thread-prefetcher.cjs");
17
17
  const indexTriggeredRefresh = require("./index-triggered-refresh.cjs");
18
+ const liveSessionRows = require("./live-session-rows.cjs");
18
19
  const sessionListTabShare = require("./session-list-tab-share.cjs");
19
20
  const title = require("./title.cjs");
20
21
  const authRefusalRestart = require("../runtime/auth-refusal-restart.cjs");
@@ -34,14 +35,6 @@ function useRefreshThreadList() {
34
35
  var _a;
35
36
  return ((_a = useAthenaStatewireThreadList()) == null ? void 0 : _a.refresh) ?? null;
36
37
  }
37
- const AGENT_HOME_EXCLUDED_START_CHANNELS = /* @__PURE__ */ new Set([
38
- "task",
39
- "agent_orchestration"
40
- ]);
41
- function isAgentHomeIndexEntry(entry, homeAgentId) {
42
- if (!homeAgentId || entry.collab_agent_id !== homeAgentId) return false;
43
- return !AGENT_HOME_EXCLUDED_START_CHANNELS.has(entry.start_channel ?? "");
44
- }
45
38
  function indexedThreadsToRefreshFor({
46
39
  entries,
47
40
  listedThreadIds,
@@ -54,7 +47,7 @@ function indexedThreadsToRefreshFor({
54
47
  const shownAppIds = appId || additionalAppIds.length > 0 ? /* @__PURE__ */ new Set([...appId ? [appId] : [], ...additionalAppIds]) : null;
55
48
  const listedAgentId = homeAgentApplied ? homeAgentId : void 0;
56
49
  return entries.filter(
57
- (entry) => !shownAppIds || shownAppIds.has(entry.app_id ?? "") || isAgentHomeIndexEntry(entry, listedAgentId)
50
+ (entry) => !shownAppIds || shownAppIds.has(entry.app_id ?? "") || liveSessionRows.isAgentHomeIndexEntry(entry, listedAgentId)
58
51
  ).map((entry) => entry.thread_id).filter((threadId) => !listedThreadIds.has(threadId) && !refreshedThreadIds.has(threadId));
59
52
  }
60
53
  function threadSummaryFromConversation(conversation) {
@@ -100,9 +93,16 @@ function withPinnedConversations({
100
93
  return unlisted.length === 0 ? conversations : [...conversations, ...unlisted];
101
94
  }
102
95
  const AthenaSessionsLiveContext = require$$0.createContext(null);
96
+ const AthenaThreadListSendContext = require$$0.createContext(null);
97
+ const POLL_REFRESH_AFTER_SEND_MS = [3e3, 1e4];
103
98
  const THREAD_LIST_PAGE_SIZE = 50;
104
99
  const EMPTY_THREAD_IDS = /* @__PURE__ */ new Set();
100
+ const EMPTY_LOCAL_SENDS = /* @__PURE__ */ new Map();
105
101
  const MAX_PINNED_PAGES = 10;
102
+ function withoutLocalSends(state, threadIds) {
103
+ const sends = liveSessionRows.withoutLocalSends(state.sends, threadIds);
104
+ return sends === state.sends ? state : { ...state, sends };
105
+ }
106
106
  function StatewireThreadListState({
107
107
  backendUrl,
108
108
  syncUrl,
@@ -251,6 +251,34 @@ function StatewireThreadListState({
251
251
  () => foldConversationIndex(listedConversations, indexByThread),
252
252
  [listedConversations, indexByThread]
253
253
  );
254
+ const homeAgentApplied = list.homeAgentApplied;
255
+ const scope = require$$0.useMemo(
256
+ () => ({ appId, additionalAppIds: listedAppIds, homeAgentId, homeAgentApplied }),
257
+ [appId, listedAppIds, homeAgentId, homeAgentApplied]
258
+ );
259
+ const searching = trimmedSearch.length > 0;
260
+ const serverConversations = require$$0.useMemo(
261
+ () => liveSessionRows.sortNewestFirst(
262
+ liveSessionRows.withIndexedSessions({
263
+ conversations: mergedConversations,
264
+ loadedConversations: list.conversations,
265
+ entries: index.entries,
266
+ scope,
267
+ searching,
268
+ hasResolvedFirstPage: list.hasResolvedFirstPage,
269
+ hasNextPage: list.hasNextPage
270
+ })
271
+ ),
272
+ [
273
+ mergedConversations,
274
+ list.conversations,
275
+ index.entries,
276
+ scope,
277
+ searching,
278
+ list.hasResolvedFirstPage,
279
+ list.hasNextPage
280
+ ]
281
+ );
254
282
  const queryClient = reactQuery.useQueryClient();
255
283
  const patchEveryList = require$$0.useCallback(
256
284
  (threadId, patch) => conversationList.patchConversationListCaches(queryClient, {
@@ -264,7 +292,7 @@ function StatewireThreadListState({
264
292
  backendUrl,
265
293
  principalId: principal,
266
294
  getAuth,
267
- rows: mergedConversations,
295
+ rows: serverConversations,
268
296
  onServerTruth: ({ threadId, isFavorited }) => {
269
297
  patchEveryList(threadId, { isFavorited });
270
298
  if (isFavorited) void pinnedListRefresh();
@@ -275,7 +303,7 @@ function StatewireThreadListState({
275
303
  backendUrl,
276
304
  principalId: principal,
277
305
  getAuth,
278
- rows: mergedConversations,
306
+ rows: serverConversations,
279
307
  onServerTruth: ({ threadId, title: title2 }) => patchEveryList(threadId, { title: title2 })
280
308
  });
281
309
  const titleOverrides = rename.overrides;
@@ -283,7 +311,7 @@ function StatewireThreadListState({
283
311
  backendUrl,
284
312
  principalId: principal,
285
313
  getAuth,
286
- rows: mergedConversations,
314
+ rows: serverConversations,
287
315
  onServerTruth: ({ threadId, isUnread }) => patchEveryList(threadId, { isUnread })
288
316
  });
289
317
  const readOverrides = readState.overrides;
@@ -316,9 +344,110 @@ function StatewireThreadListState({
316
344
  ).filter((conversation) => !hiddenThreadIds.has(conversation.threadId)),
317
345
  [favoriteOverrides, titleOverrides, readOverrides, readCoveredUpdatedAt, hiddenThreadIds]
318
346
  );
347
+ const sendsScopeKey = `${listScopeKey}::${credentialGeneration}::${conversationList.conversationListFilterKey({
348
+ ...appId ? { appId } : {},
349
+ ...listedAppIds.length > 0 ? { appIds: listedAppIds } : {},
350
+ ...homeAgentId ? { homeAgentId } : {}
351
+ })}`;
352
+ const [localSendState, setLocalSendState] = require$$0.useState(() => ({ scopeKey: sendsScopeKey, sends: EMPTY_LOCAL_SENDS }));
353
+ const localSends = localSendState.scopeKey === sendsScopeKey ? localSendState.sends : EMPTY_LOCAL_SENDS;
354
+ const serverConversationsRef = require$$0.useRef(serverConversations);
355
+ serverConversationsRef.current = serverConversations;
356
+ const sendsScopeKeyRef = require$$0.useRef(sendsScopeKey);
357
+ sendsScopeKeyRef.current = sendsScopeKey;
358
+ const activeRef = require$$0.useRef(active);
359
+ activeRef.current = active;
360
+ const indexLiveRef = require$$0.useRef(index.entries !== null);
361
+ indexLiveRef.current = index.entries !== null;
362
+ const refreshAfterSendRef = require$$0.useRef(() => void 0);
363
+ const pollRefreshTimersRef = require$$0.useRef(/* @__PURE__ */ new Set());
364
+ require$$0.useEffect(() => {
365
+ const timers = pollRefreshTimersRef.current;
366
+ return () => {
367
+ for (const timer of timers) clearTimeout(timer);
368
+ timers.clear();
369
+ };
370
+ }, []);
371
+ const noteSend = require$$0.useCallback(({ threadId, text, startChannel }) => {
372
+ if (!threadId) return;
373
+ const rows = serverConversationsRef.current;
374
+ const send = liveSessionRows.localSessionSend({
375
+ threadId,
376
+ text,
377
+ sentAtMs: Date.now(),
378
+ newChat: activeRef.current.id === threadId && activeRef.current.isNew,
379
+ startChannel,
380
+ serverConversations: rows
381
+ });
382
+ const scopeKey = sendsScopeKeyRef.current;
383
+ setLocalSendState((current) => {
384
+ const sends = new Map(current.scopeKey === scopeKey ? current.sends : EMPTY_LOCAL_SENDS);
385
+ const previous = sends.get(threadId);
386
+ const serverRow = rows.find((row) => row.threadId === threadId);
387
+ sends.set(
388
+ threadId,
389
+ previous && !liveSessionRows.isLocalSendReported(previous, serverRow) ? {
390
+ ...send,
391
+ baselineUpdatedAt: previous.baselineUpdatedAt,
392
+ newChat: previous.newChat,
393
+ title: previous.title ?? send.title,
394
+ startChannel: previous.startChannel
395
+ } : send
396
+ );
397
+ return { scopeKey, sends };
398
+ });
399
+ if (!indexLiveRef.current && !send.newChat) {
400
+ for (const delay of POLL_REFRESH_AFTER_SEND_MS) {
401
+ const timer = setTimeout(() => {
402
+ pollRefreshTimersRef.current.delete(timer);
403
+ refreshAfterSendRef.current();
404
+ }, delay);
405
+ pollRefreshTimersRef.current.add(timer);
406
+ }
407
+ }
408
+ }, []);
409
+ require$$0.useEffect(() => {
410
+ if (localSends.size === 0) return;
411
+ const settled = liveSessionRows.settledLocalSends({ sends: localSends, serverConversations, nowMs: Date.now() });
412
+ if (settled.length > 0) {
413
+ setLocalSendState((current) => withoutLocalSends(current, settled));
414
+ return;
415
+ }
416
+ let oldestMs = Number.POSITIVE_INFINITY;
417
+ for (const send of localSends.values()) oldestMs = Math.min(oldestMs, send.sentAtMs);
418
+ const timer = setTimeout(
419
+ () => setLocalSendState(
420
+ (current) => withoutLocalSends(
421
+ current,
422
+ [...current.sends.values()].filter((send) => Date.now() - send.sentAtMs >= liveSessionRows.LOCAL_SEND_TTL_MS).map((send) => send.threadId)
423
+ )
424
+ ),
425
+ Math.max(0, oldestMs + liveSessionRows.LOCAL_SEND_TTL_MS - Date.now()) + 1
426
+ );
427
+ return () => clearTimeout(timer);
428
+ }, [localSends, serverConversations]);
429
+ const showPendingSessions = require$$0.useMemo(
430
+ () => !searching && liveSessionRows.inSessionListScope(
431
+ { app_id: appId ?? null, collab_agent_id: homeAgentId ?? null, start_channel: null },
432
+ scope
433
+ ),
434
+ [searching, appId, homeAgentId, scope]
435
+ );
436
+ const liveRows = require$$0.useMemo(
437
+ () => liveSessionRows.withLocalSends({
438
+ conversations: serverConversations,
439
+ sends: localSends,
440
+ appId,
441
+ showPendingSessions
442
+ }),
443
+ [serverConversations, localSends, appId, showPendingSessions]
444
+ );
319
445
  const data = require$$0.useMemo(
320
- () => applyOverrides(mergedConversations).map(threadSummaryFromConversation),
321
- [applyOverrides, mergedConversations]
446
+ () => liveSessionRows.sortNewestFirst(applyOverrides(liveRows.conversations)).map((conversation) => {
447
+ const summary = threadSummaryFromConversation(conversation);
448
+ return liveRows.pendingThreadIds.has(conversation.threadId) ? { ...summary, isPending: true } : summary;
449
+ }),
450
+ [applyOverrides, liveRows]
322
451
  );
323
452
  const isLoading = list.isLoading;
324
453
  const lastActiveConversationRef = require$$0.useRef(null);
@@ -354,6 +483,7 @@ function StatewireThreadListState({
354
483
  void listRefresh();
355
484
  if (!pinnedFetchingRef.current) void pinnedListRefresh();
356
485
  }, [listRefresh, pinnedListRefresh]);
486
+ refreshAfterSendRef.current = refresh;
357
487
  const listFetchNextPage = list.fetchNextPage;
358
488
  const fetchNextPage = require$$0.useCallback(() => {
359
489
  void listFetchNextPage();
@@ -362,7 +492,6 @@ function StatewireThreadListState({
362
492
  () => new Set(list.conversations.map((conversation) => conversation.threadId)),
363
493
  [list.conversations]
364
494
  );
365
- const homeAgentApplied = list.homeAgentApplied;
366
495
  const unlistedIndexedThreads = require$$0.useMemo(
367
496
  () => index.entries === null ? [] : indexedThreadsToRefreshFor({
368
497
  entries: index.entries,
@@ -406,14 +535,14 @@ function StatewireThreadListState({
406
535
  prefetchScopeRef.current = prefetchScope;
407
536
  const prefetcher = require$$0.useMemo(() => {
408
537
  if (!snapshotCache) return null;
409
- const scope = prefetchScope;
538
+ const scope2 = prefetchScope;
410
539
  return threadPrefetcher.createThreadPrefetcher({
411
540
  cache: snapshotCache,
412
541
  fetchState: async (threadId) => credentialRenewal.withCredentialRenewal(
413
542
  getAuth,
414
543
  (auth) => api.getThreadState(backendUrl, auth, threadId, { skipCache: false })
415
544
  ),
416
- isCurrent: () => prefetchScopeRef.current === scope
545
+ isCurrent: () => prefetchScopeRef.current === scope2
417
546
  });
418
547
  }, [snapshotCache, prefetchScope, backendUrl, getAuth]);
419
548
  const activeIdRef = require$$0.useRef(active.id);
@@ -428,7 +557,7 @@ function StatewireThreadListState({
428
557
  const newThread = require$$0.useCallback(() => {
429
558
  setActive({ id: mintThreadId.mintThreadId(), isNew: true });
430
559
  }, []);
431
- const isPersisted = data.some((thread) => thread.id === active.id) || indexByThread.has(active.id);
560
+ const isPersisted = mergedConversations.some((conversation) => conversation.threadId === active.id) || indexByThread.has(active.id);
432
561
  require$$0.useEffect(() => {
433
562
  if (isPersisted) {
434
563
  setActive((current) => current.isNew ? { ...current, isNew: false } : current);
@@ -536,7 +665,7 @@ function StatewireThreadListState({
536
665
  tabShare
537
666
  ]
538
667
  );
539
- return /* @__PURE__ */ jsxRuntime.jsx(AthenaStatewireThreadListContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx(AthenaSessionsLiveContext.Provider, { value: live, children }) });
668
+ return /* @__PURE__ */ jsxRuntime.jsx(AthenaStatewireThreadListContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx(AthenaSessionsLiveContext.Provider, { value: live, children: /* @__PURE__ */ jsxRuntime.jsx(AthenaThreadListSendContext.Provider, { value: noteSend, children }) }) });
540
669
  }
541
670
  function AthenaStatewireThreadListProvider({
542
671
  backendUrl,
@@ -585,7 +714,7 @@ function StatewireThreadTitlePoller() {
585
714
  return (((_a2 = s.optional.thread) == null ? void 0 : _a2.messages.length) ?? 0) > 0;
586
715
  });
587
716
  const titleSource = (list == null ? void 0 : list.titleSource) ?? "poll";
588
- const entry = list == null ? void 0 : list.threads.find((thread) => thread.id === activeThreadId);
717
+ const entry = list == null ? void 0 : list.threads.find((thread) => thread.id === activeThreadId && !thread.isPending);
589
718
  const title$1 = ((_a = entry == null ? void 0 : entry.title) == null ? void 0 : _a.trim()) ?? "";
590
719
  const hasTitle = title$1.length > 0 && !title.isSerializedContentPartsTitle(title$1);
591
720
  const visitRef = require$$0.useRef(null);
@@ -620,9 +749,33 @@ function StatewireThreadTitlePoller() {
620
749
  }, [activeThreadId, hasMessages, hasTitle, titleSource]);
621
750
  return null;
622
751
  }
752
+ function StatewireThreadListSendSync({
753
+ startChannel = null
754
+ } = {}) {
755
+ const noteSend = require$$0.useContext(AthenaThreadListSendContext);
756
+ const threadId = threadIdContext.useAthenaStatewireThreadId();
757
+ const typedRef = require$$0.useRef(/* @__PURE__ */ new Map());
758
+ require$$0.useEffect(() => {
759
+ typedRef.current.clear();
760
+ }, [threadId]);
761
+ react.useAuiEvent({ scope: "thread", event: "composer.textChange" }, (event) => {
762
+ if (event.origin === "send") return;
763
+ typedRef.current.set(event.messageId ?? "", event.text ?? "");
764
+ });
765
+ react.useAuiEvent({ scope: "thread", event: "composer.send" }, (event) => {
766
+ const composer = event.messageId ?? "";
767
+ const typed = typedRef.current.get(composer) ?? null;
768
+ typedRef.current.delete(composer);
769
+ if (!noteSend || !threadId) return;
770
+ noteSend({ threadId, text: event.chars > 0 ? typed : null, startChannel });
771
+ });
772
+ return null;
773
+ }
623
774
  exports.AthenaSessionsLiveContext = AthenaSessionsLiveContext;
624
775
  exports.AthenaStatewireThreadListContext = AthenaStatewireThreadListContext;
625
776
  exports.AthenaStatewireThreadListProvider = AthenaStatewireThreadListProvider;
777
+ exports.AthenaThreadListSendContext = AthenaThreadListSendContext;
778
+ exports.StatewireThreadListSendSync = StatewireThreadListSendSync;
626
779
  exports.StatewireThreadTitlePoller = StatewireThreadTitlePoller;
627
780
  exports.TITLE_POLL_DELAY_MS = TITLE_POLL_DELAY_MS;
628
781
  exports.foldConversationIndex = foldConversationIndex;