@athenaintel/react 0.42.1 → 0.43.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 (147) hide show
  1. package/README.md +46 -7
  2. package/dist/agent-runtime/src/conversation-list-groups.d.ts +74 -0
  3. package/dist/agent-runtime/src/index.d.ts +3 -2
  4. package/dist/agent-runtime/src/messages.cjs +38 -7
  5. package/dist/agent-runtime/src/messages.cjs.map +1 -1
  6. package/dist/agent-runtime/src/messages.d.ts +26 -8
  7. package/dist/agent-runtime/src/messages.js +38 -7
  8. package/dist/agent-runtime/src/messages.js.map +1 -1
  9. package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs +10 -0
  10. package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
  11. package/dist/agent-runtime/src/quota-safe-local-storage-session.d.ts +7 -0
  12. package/dist/agent-runtime/src/quota-safe-local-storage-session.js +10 -0
  13. package/dist/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
  14. package/dist/agent-runtime/src/session-lane-scope.cjs +25 -0
  15. package/dist/agent-runtime/src/session-lane-scope.cjs.map +1 -0
  16. package/dist/agent-runtime/src/session-lane-scope.d.ts +59 -0
  17. package/dist/agent-runtime/src/session-lane-scope.js +25 -0
  18. package/dist/agent-runtime/src/session-lane-scope.js.map +1 -0
  19. package/dist/agent-runtime/src/subagent-transcripts.cjs +2 -2
  20. package/dist/agent-runtime/src/subagent-transcripts.cjs.map +1 -1
  21. package/dist/agent-runtime/src/subagent-transcripts.d.ts +2 -2
  22. package/dist/agent-runtime/src/subagent-transcripts.js +3 -3
  23. package/dist/agent-runtime/src/subagent-transcripts.js.map +1 -1
  24. package/dist/agent-runtime/src/tool-metadata.cjs +0 -8
  25. package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
  26. package/dist/agent-runtime/src/tool-metadata.js +0 -8
  27. package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
  28. package/dist/agent-runtime/src/tool-names.cjs +4 -0
  29. package/dist/agent-runtime/src/tool-names.cjs.map +1 -1
  30. package/dist/agent-runtime/src/tool-names.d.ts +7 -0
  31. package/dist/agent-runtime/src/tool-names.js +4 -0
  32. package/dist/agent-runtime/src/tool-names.js.map +1 -1
  33. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +18 -1
  34. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
  35. package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +19 -10
  36. package/dist/agent-runtime/src/use-deep-agent-runtime.js +19 -2
  37. package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
  38. package/dist/agent-runtime/src/use-deep-agent-thread.cjs +28 -10
  39. package/dist/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
  40. package/dist/agent-runtime/src/use-deep-agent-thread.d.ts +35 -3
  41. package/dist/agent-runtime/src/use-deep-agent-thread.js +28 -10
  42. package/dist/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
  43. package/dist/agent-runtime/src/use-deep-agent-voice.cjs +50 -15
  44. package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
  45. package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +8 -0
  46. package/dist/agent-runtime/src/use-deep-agent-voice.js +50 -15
  47. package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
  48. package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
  49. package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
  50. package/dist/agent-runtime/src/voice-presentation.js +2 -0
  51. package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
  52. package/dist/catalog/runtime-catalog.js +1 -1
  53. package/dist/chat/AthenaChat.cjs +125 -103
  54. package/dist/chat/AthenaChat.cjs.map +1 -1
  55. package/dist/chat/AthenaChat.d.ts +9 -0
  56. package/dist/chat/AthenaChat.js +126 -104
  57. package/dist/chat/AthenaChat.js.map +1 -1
  58. package/dist/chat/chat-voice-controls.cjs +128 -0
  59. package/dist/chat/chat-voice-controls.cjs.map +1 -0
  60. package/dist/chat/chat-voice-controls.d.ts +19 -0
  61. package/dist/chat/chat-voice-controls.js +128 -0
  62. package/dist/chat/chat-voice-controls.js.map +1 -0
  63. package/dist/chat/super-grouping/SuperGroupingCard.cjs +8 -2
  64. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  65. package/dist/chat/super-grouping/SuperGroupingCard.d.ts +6 -1
  66. package/dist/chat/super-grouping/SuperGroupingCard.js +8 -2
  67. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  68. package/dist/chat/super-grouping/ToolTimeline.cjs +27 -0
  69. package/dist/chat/super-grouping/ToolTimeline.cjs.map +1 -1
  70. package/dist/chat/super-grouping/ToolTimeline.d.ts +5 -1
  71. package/dist/chat/super-grouping/ToolTimeline.js +27 -0
  72. package/dist/chat/super-grouping/ToolTimeline.js.map +1 -1
  73. package/dist/chat/super-grouping/index.d.ts +1 -0
  74. package/dist/chat/super-grouping/timeline-step.cjs +61 -2
  75. package/dist/chat/super-grouping/timeline-step.cjs.map +1 -1
  76. package/dist/chat/super-grouping/timeline-step.d.ts +13 -1
  77. package/dist/chat/super-grouping/timeline-step.js +61 -2
  78. package/dist/chat/super-grouping/timeline-step.js.map +1 -1
  79. package/dist/chat/use-statewire-approval-request.js +1 -1
  80. package/dist/chat-ui/composer/tiptap-composer.cjs +42 -22
  81. package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
  82. package/dist/chat-ui/composer/tiptap-composer.d.ts +1 -0
  83. package/dist/chat-ui/composer/tiptap-composer.js +43 -23
  84. package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
  85. package/dist/chat-ui/mentions/mention-chip.cjs +144 -35
  86. package/dist/chat-ui/mentions/mention-chip.cjs.map +1 -1
  87. package/dist/chat-ui/mentions/mention-chip.d.ts +17 -1
  88. package/dist/chat-ui/mentions/mention-chip.js +146 -37
  89. package/dist/chat-ui/mentions/mention-chip.js.map +1 -1
  90. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +2 -1
  91. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
  92. package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +3 -1
  93. package/dist/chat-ui/mentions/suggestions/data/api-client.js +2 -1
  94. package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
  95. package/dist/chat-ui/mentions/use-account-workspace-id.cjs +29 -51
  96. package/dist/chat-ui/mentions/use-account-workspace-id.cjs.map +1 -1
  97. package/dist/chat-ui/mentions/use-account-workspace-id.d.ts +2 -2
  98. package/dist/chat-ui/mentions/use-account-workspace-id.js +30 -52
  99. package/dist/chat-ui/mentions/use-account-workspace-id.js.map +1 -1
  100. package/dist/chat-ui/mentions/use-mention-asset.cjs +140 -0
  101. package/dist/chat-ui/mentions/use-mention-asset.cjs.map +1 -0
  102. package/dist/chat-ui/mentions/use-mention-asset.d.ts +16 -0
  103. package/dist/chat-ui/mentions/use-mention-asset.js +140 -0
  104. package/dist/chat-ui/mentions/use-mention-asset.js.map +1 -0
  105. package/dist/chat-ui/rich-text/chat-link.cjs +5 -4
  106. package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
  107. package/dist/chat-ui/rich-text/chat-link.d.ts +2 -3
  108. package/dist/chat-ui/rich-text/chat-link.js +6 -5
  109. package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
  110. package/dist/chat-ui/rich-text/chat-node-views.cjs +1 -0
  111. package/dist/chat-ui/rich-text/chat-node-views.cjs.map +1 -1
  112. package/dist/chat-ui/rich-text/chat-node-views.js +1 -0
  113. package/dist/chat-ui/rich-text/chat-node-views.js.map +1 -1
  114. package/dist/chat-ui/thread/composer-send-or-stop.cjs +2 -1
  115. package/dist/chat-ui/thread/composer-send-or-stop.cjs.map +1 -1
  116. package/dist/chat-ui/thread/composer-send-or-stop.d.ts +2 -0
  117. package/dist/chat-ui/thread/composer-send-or-stop.js +2 -1
  118. package/dist/chat-ui/thread/composer-send-or-stop.js.map +1 -1
  119. package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
  120. package/dist/diagnostics/athena-diagnostics.d.ts +1 -1
  121. package/dist/diagnostics/athena-diagnostics.js.map +1 -1
  122. package/dist/index.d.ts +1 -1
  123. package/dist/react/package.json.cjs +1 -1
  124. package/dist/react/package.json.js +1 -1
  125. package/dist/references/src/tool-option-schemas.d.ts +0 -34
  126. package/dist/runtime/statewire-session-key.cjs.map +1 -1
  127. package/dist/runtime/statewire-session-key.d.ts +8 -5
  128. package/dist/runtime/statewire-session-key.js.map +1 -1
  129. package/dist/runtime/useAthenaStatewireRuntime.cjs +5 -2
  130. package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
  131. package/dist/runtime/useAthenaStatewireRuntime.js +5 -2
  132. package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
  133. package/dist/styles.css +1 -1
  134. package/dist/threads/statewire-thread-list.cjs +6 -2
  135. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  136. package/dist/threads/statewire-thread-list.js +6 -2
  137. package/dist/threads/statewire-thread-list.js.map +1 -1
  138. package/dist/tools/tool-summary.cjs +0 -2
  139. package/dist/tools/tool-summary.cjs.map +1 -1
  140. package/dist/tools/tool-summary.js +0 -2
  141. package/dist/tools/tool-summary.js.map +1 -1
  142. package/dist/voice/use-chat-voice-enabled.cjs +43 -0
  143. package/dist/voice/use-chat-voice-enabled.cjs.map +1 -0
  144. package/dist/voice/use-chat-voice-enabled.d.ts +9 -0
  145. package/dist/voice/use-chat-voice-enabled.js +43 -0
  146. package/dist/voice/use-chat-voice-enabled.js.map +1 -0
  147. package/package.json +4 -8
@@ -1 +1 @@
1
- {"version":3,"file":"statewire-thread-list.js","sources":["../../src/threads/statewire-thread-list.tsx"],"sourcesContent":["import {\n type ConversationAttention,\n type ConversationAttentionSource,\n type ConversationIndexEntry,\n type ConversationListFilter,\n type ConversationPullRequest,\n type ConversationSummary,\n type ConversationWake,\n conversationIndexUrlFromStatewireUrl,\n conversationListFilterKey,\n conversationListQueryKeys,\n type DeepAgentHeadersResolver,\n type DeepAgentThreadSnapshotCache,\n deriveConversationAttention,\n patchConversationListCaches,\n removeConversationFromListCaches,\n summaryFromConversationIndexEntry,\n type ThreadSnapshotMirror,\n useConversationIndex,\n useConversationList,\n} from '@athenaintel/agent-runtime';\nimport { useOfflineSessionList } from './offline-session-list';\nimport { QueryClient, QueryClientProvider, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { useAuiState } from '@assistant-ui/react';\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\nimport { AthenaAuthContext } from '../auth/AthenaAuthContext';\nimport {\n type AthenaStatewireTokenGetter,\n resolveStatewireAuthHeaders,\n resolveStatewireAuthToken,\n} from '../runtime/statewire-auth-headers';\nimport { fetchAccountIdentity, getThreadState } from './api';\nimport { createRestConversationListFetcher } from './conversation-list-fetcher';\nimport { credentialShape, sameCredentialShape } from './credential-shape';\nimport { mintThreadId } from './mint-thread-id';\nimport { useAthenaStatewireThreadId } from './thread-id-context';\nimport { createThreadPrefetcher } from './thread-prefetcher';\nimport { useIndexTriggeredRefresh } from './index-triggered-refresh';\nimport {\n type SessionListTabShare,\n sessionListShareKey,\n useSessionListTabShare,\n} from './session-list-tab-share';\nimport { isSerializedContentPartsTitle, normalizeThreadTitle } from './title';\nimport { useRestartAfterAuthRefusal } from '../runtime/auth-refusal-restart';\nimport { type CredentialResolver, withCredentialRenewal } from '../runtime/credential-renewal';\nimport { useThreadArchive } from './use-thread-archive';\nimport {\n type ThreadFavoriteTarget,\n useThreadFavorite,\n withFavoriteOverride,\n withFavoriteOverrides,\n} from './use-thread-favorite';\nimport {\n type ThreadReadStateTarget,\n useThreadReadState,\n withReadStateOverrides,\n} from './use-thread-read-state';\nimport {\n type ThreadRenameTarget,\n useThreadRename,\n withTitleOverride,\n withTitleOverrides,\n} from './use-thread-rename';\n\n/**\n * One row of the thread list — the fields a Devin-style session list renders\n * (`SessionList`), all read from the shared conversation-list contract. The\n * attention fields are `null` on a server that does not derive them; the\n * pull-request state is `null` until the list has fetched it (or when the\n * provider opted out of it).\n */\nexport interface AthenaStatewireThreadSummary {\n id: string;\n title: string | null;\n /**\n * What the session asks of the viewer: the server-derived state when\n * reported, else the client fallback from the status planes\n * (`deriveConversationAttention`).\n */\n attention: ConversationAttention | null;\n /** One line (≤ 80 chars) of what the viewer should do next, when the server derives one. */\n nextAction: string | null;\n attentionSource: ConversationAttentionSource | null;\n /**\n * Whether the viewer pinned the session. Toggled through\n * `AthenaStatewireThreadListState.setFavorited`; a toggle in flight is\n * reflected here at once and held until the list sources report it.\n */\n isFavorited: boolean;\n isUnread: boolean;\n /** Canonical status_v2 planes, or `null` on an older server. */\n sessionStatus: string | null;\n runStatus: string | null;\n /** Channel the session was started from (e.g. `web`, `github`, `api`). */\n startChannel: string | null;\n /** Client app that created the session (SESSION_APP_ID_GUIDE). */\n appId: string | null;\n /** Trigger marker (`manual` remains user-initiated; other values are automated). */\n triggerType: string | null;\n /** Pull requests the session opened with their live GitHub state. */\n pullRequests: ConversationPullRequest[] | null;\n /** The last wake of a resting session. */\n lastWake: ConversationWake | null;\n /** ISO 8601 timestamp of the last update (the list's sort key). */\n updatedAt: string;\n /** ISO 8601 creation time; `null` when the list did not report it. */\n createdAt?: string | null;\n /** The session's last message, for a preview line (the inbox excerpt). */\n preview?: string | null;\n /** The collab agent the session started as, and its channel (`null`: the default channel). */\n collabAgentId?: string | null;\n collabChannelId?: string | null;\n /** Whether the viewer started the session (`false`: shared with them); `null` when not reported. */\n createdByMe?: boolean | null;\n}\n\n/**\n * Thread-list state. The list is the persisted Agora session list; the ACTIVE\n * thread is a plain id the statewire runtime attaches to — switching threads\n * remounts the statewire session against the new `…/v2/threads/{id}` URL\n * (the same model the mobile app uses).\n */\nexport interface AthenaStatewireThreadListState {\n /** The rows the current `search` lists, newest first. */\n threads: AthenaStatewireThreadSummary[];\n isLoading: boolean;\n /** True while the live first page (or a history page) is being fetched. */\n isFetching: boolean;\n /**\n * True once the live first page has answered at least once for the current\n * search and identity. Until then an empty `threads` means \"not known\n * yet\" (no credential resolved, first page in flight), not \"no sessions\":\n * `SessionList` shows skeleton rows, never the empty state.\n */\n hasResolvedFirstPage: boolean;\n activeThreadId: string;\n /**\n * The active thread's row, independent of what `threads` currently lists:\n * a search that hides the row, or a page that has not fetched it, must not\n * make the active chat's attention disappear. Resolved from the listed row,\n * else the pushed index entry folded over the last row seen, else that\n * last row; `null` for a never-started chat the backend does not know yet.\n */\n activeThread: AthenaStatewireThreadSummary | null;\n /** True while the active thread is a locally-minted chat with no server history. */\n isNewChat: boolean;\n /**\n * Switch to `threadId`; `isNew` for a chat this page minted that never started (it loads as a\n * new chat until the list has it), e.g. one holding messages that wait in the chat outbox.\n */\n selectThread: (threadId: string, options?: { isNew?: boolean }) => void;\n /**\n * Warm the provider's snapshot cache with a thread's REST state ahead of a\n * `selectThread`, so the switch paints the transcript on its first frame\n * instead of the loading scrim while the statewire attach lands.\n * `SessionList` calls it after a 120 ms hover dwell and on row focus.\n * Fetches once per thread per identity, deduplicates requests in flight,\n * runs at most two at a time and ignores failures; a no-op for the active\n * thread, a thread the cache already holds, or a provider without a cache.\n */\n prefetchThread: (threadId: string) => void;\n newThread: () => void;\n refresh: () => void;\n /**\n * Server-side title search (`search` on the REST thread list). An empty\n * string lists everything; the search covers the viewer's whole authorized\n * history, not just the live page.\n */\n search: string;\n setSearch: (search: string) => void;\n /** Whether another page of older sessions can be fetched. */\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n /** Fetch the next page of older sessions (frozen history pages, never replayed). */\n fetchNextPage: () => void;\n /**\n * Pin or unpin a session (`POST /api/conversations/threads/favorite`). The\n * row flips at once and moves between the `Pinned` and `Recent` groups; the\n * server's answer is written back, and a failure restores the previous\n * state. Writes for one thread are serialized: a toggle while that\n * thread's write is in flight records the value and sends at most one\n * follow-up write once the first has answered.\n */\n setFavorited: (target: ThreadFavoriteTarget) => void;\n /** True while any pin toggle is in flight. */\n favoritePending: boolean;\n /** The sessions whose pin toggle is in flight; `SessionList` disables their pin control. */\n favoritePendingThreadIds: ReadonlySet<string>;\n /**\n * Whether the backend offers the favorite route. `false` once it answered\n * 404 / 405 (an agora that predates the route): `SessionList` then shows\n * the read-only pin and no toggle, and `setFavorited` keeps failing.\n */\n favoriteToggleAvailable: boolean;\n /**\n * Rename a session for every viewer (`POST /api/conversations/threads/rename`).\n * The row shows the new title at once; the server's stored title is\n * written back, and a failure restores the previous one. Same-thread\n * writes are serialized like the pin's.\n */\n renameThread: (target: ThreadRenameTarget) => void;\n /** The sessions whose rename is in flight; `SessionList` keeps their editor closed. */\n renamePendingThreadIds: ReadonlySet<string>;\n /**\n * Whether the backend offers the rename route. `false` once it answered\n * 404 / 405 (an agora that predates the route): `SessionList` then offers\n * no Rename action, and `renameThread` keeps failing.\n */\n renameAvailable: boolean;\n /**\n * Archive a session (`POST /api/conversations/threads/archive`). The row\n * leaves the list at once and stays hidden until both list sources stop\n * reporting it; a failure brings it back. Archiving the active session\n * moves the chat to a new one once the server has confirmed.\n */\n archiveThread: (threadId: string) => void;\n /** The sessions whose archive is in flight. */\n archivePendingThreadIds: ReadonlySet<string>;\n /** True while any archive is in flight. */\n archivePending: boolean;\n /**\n * Whether the backend offers the archive route. `false` once it answered\n * 404 / 405: `SessionList` then offers no Archive action.\n */\n archiveAvailable: boolean;\n /**\n * Mark a session read or unread for the viewer\n * (`POST /api/conversations/threads/read-state`). The row flips at once in\n * every list under the provider (`SessionList`, `SessionsPage`, `Inbox`);\n * the server's answer is written back, and a failure restores the previous\n * state. Same-thread writes are serialized like the pin's.\n */\n setReadState: (target: ThreadReadStateTarget) => void;\n /** The sessions whose read-state write is in flight. */\n readStatePendingThreadIds: ReadonlySet<string>;\n /**\n * Where titles come from. `'index'` once the signed-in user's live\n * conversation index has replicated — titles and newly created threads then\n * arrive as pushed updates; `'poll'` while it has not (no signed-in\n * principal, e.g. API-key mode, or before its snapshot), when a\n * freshly-started thread's title is picked up by the bounded REST poll.\n */\n titleSource: 'index' | 'poll';\n}\n\nexport const AthenaStatewireThreadListContext =\n createContext<AthenaStatewireThreadListState | null>(null);\n\n/** Read the statewire thread-list state, or null outside that mode. */\nexport function useAthenaStatewireThreadList(): AthenaStatewireThreadListState | null {\n return useContext(AthenaStatewireThreadListContext);\n}\n\n/** Trigger a re-fetch of the thread list. `null` outside `enableThreadList` mode. */\nexport function useRefreshThreadList(): (() => void) | null {\n return useAthenaStatewireThreadList()?.refresh ?? null;\n}\n\n/**\n * Project one contract row onto the thread-list row. Exported for hosts (and\n * tests) that build rows from a `ConversationSummary` of their own.\n */\n/**\n * The threads the conversation index knows that the fetched list does not show yet, and that have\n * not prompted a refresh before: each warrants one refresh of the list. Only threads of the apps\n * the list shows count (its `appId` and `additionalAppIds`); with neither, every app's do.\n * Exported for tests.\n */\nexport function indexedThreadsToRefreshFor({\n entries,\n listedThreadIds,\n refreshedThreadIds,\n appId,\n additionalAppIds,\n}: {\n entries: ReadonlyArray<{ thread_id: string; app_id: string | null }>;\n listedThreadIds: ReadonlySet<string>;\n refreshedThreadIds: ReadonlySet<string>;\n appId: string | undefined;\n additionalAppIds: readonly string[];\n}): string[] {\n const shownAppIds =\n appId || additionalAppIds.length > 0\n ? new Set([...(appId ? [appId] : []), ...additionalAppIds])\n : null;\n return entries\n .filter((entry) => !shownAppIds || shownAppIds.has(entry.app_id ?? ''))\n .map((entry) => entry.thread_id)\n .filter((threadId) => !listedThreadIds.has(threadId) && !refreshedThreadIds.has(threadId));\n}\n\nexport function threadSummaryFromConversation(\n conversation: ConversationSummary,\n): AthenaStatewireThreadSummary {\n return {\n id: conversation.threadId,\n title: normalizeThreadTitle(conversation.title) ?? null,\n attention: deriveConversationAttention(conversation),\n nextAction: conversation.nextAction ?? null,\n attentionSource: conversation.attentionSource ?? null,\n isFavorited: conversation.isFavorited ?? false,\n isUnread: conversation.isUnread,\n sessionStatus: conversation.sessionStatus,\n runStatus: conversation.runStatus,\n startChannel: conversation.sourceChannel,\n appId: conversation.appId,\n triggerType: conversation.triggerType,\n pullRequests: conversation.pullRequests ?? null,\n lastWake: conversation.lastWake ?? null,\n updatedAt: conversation.updatedAt,\n createdAt: conversation.createdAt,\n preview: conversation.preview,\n collabAgentId: conversation.collabAgentId ?? null,\n collabChannelId: conversation.collabChannelId ?? null,\n createdByMe: conversation.createdByMe ?? null,\n };\n}\n\n/**\n * Fold the pushed conversation index into fetched rows: each row with an\n * index entry takes its pushed fields through the shared\n * `summaryFromConversationIndexEntry` (titles, lifecycle, attention), while\n * what the index never carries (pull requests, the last wake, who started\n * it) keeps the fetched value. A pushed title that normalizes to nothing\n * falls back to the fetched one. Exported for tests.\n */\nexport function foldConversationIndex(\n conversations: readonly ConversationSummary[],\n indexByThread: ReadonlyMap<string, ConversationIndexEntry>,\n): ConversationSummary[] {\n return conversations.map((conversation) => {\n const entry = indexByThread.get(conversation.threadId);\n if (!entry) return conversation;\n const merged = summaryFromConversationIndexEntry(entry, conversation);\n return normalizeThreadTitle(merged.title) === undefined\n ? { ...merged, title: conversation.title }\n : merged;\n });\n}\n\n/**\n * The listed rows plus the pinned rows they do not include, appended in the\n * pinned list's own newest-first order. A pinned row the list does not\n * include is older than the list's last row, so appending keeps the merged\n * rows newest-first. A row whose pin was just undone (`isFavorited: false`,\n * patched into every cached list) is not added back. Exported for tests.\n */\nexport function withPinnedConversations({\n conversations,\n pinned,\n}: {\n conversations: readonly ConversationSummary[];\n pinned: readonly ConversationSummary[];\n}): readonly ConversationSummary[] {\n const listed = new Set(conversations.map((conversation) => conversation.threadId));\n const unlisted = pinned.filter(\n (conversation) => conversation.isFavorited !== false && !listed.has(conversation.threadId),\n );\n return unlisted.length === 0 ? conversations : [...conversations, ...unlisted];\n}\n\n/**\n * What the thread-list provider shares with the other lists mounted under it\n * (`SessionsPage`, `Inbox`): the credential, the live conversation index and\n * the optimistic overrides (pin, title, read state, archived rows), so every\n * list folds the same pushes and shows the same in-flight writes. Internal —\n * hosts use `useAthenaSessions`.\n */\nexport interface AthenaSessionsLiveState {\n backendUrl: string;\n getAuth: CredentialResolver<{ apiKey?: string; token?: string | null }>;\n principal: string;\n hasAuth: boolean;\n includePullRequests: boolean;\n /** The provider list's cache scope (backend, principal, auth, PR opt-in). */\n scopeKey: string;\n /**\n * Advances whenever the host's credential changes (a swapped API key or\n * token, a scoped credential for another workspace under the same\n * principal), so caches keyed on it are never read with another credential.\n */\n credentialGeneration: number;\n /** The app scope the provider lists (`appId` / `additionalListAppIds`). */\n baseFilter: ConversationListFilter;\n indexByThread: ReadonlyMap<string, ConversationIndexEntry>;\n /** `null` until the index has replicated (or without one). */\n indexEntries: readonly ConversationIndexEntry[] | null;\n /** Layer the in-flight writes over rows and drop archived ones. */\n applyOverrides: (rows: readonly ConversationSummary[]) => ConversationSummary[];\n /** The user's other open tabs, which share the lists they read. */\n tabShare: SessionListTabShare;\n}\n\nexport const AthenaSessionsLiveContext = createContext<AthenaSessionsLiveState | null>(null);\n\n/**\n * The statewire thread list reads one live page through the shared\n * conversation-list contract; the REST default page size predates the shared\n * pager and is preserved here.\n */\nconst THREAD_LIST_PAGE_SIZE = 50;\nconst EMPTY_THREAD_IDS: ReadonlySet<string> = new Set();\n/** The most pinned pages the sidebar reads: 500 pinned sessions. */\nconst MAX_PINNED_PAGES = 10;\n\nfunction StatewireThreadListState({\n backendUrl,\n syncUrl,\n apiKey,\n token,\n getToken,\n appId,\n additionalListAppIds,\n initialThreadId,\n includePullRequests,\n snapshotCache,\n children,\n}: {\n backendUrl: string;\n syncUrl?: string;\n apiKey?: string;\n token?: string | null;\n getToken?: AthenaStatewireTokenGetter;\n appId?: string;\n additionalListAppIds?: readonly string[];\n initialThreadId?: string;\n includePullRequests: boolean;\n snapshotCache?: DeepAgentThreadSnapshotCache;\n children: ReactNode;\n}) {\n const [active, setActive] = useState<{ id: string; isNew: boolean }>(() =>\n initialThreadId\n ? { id: initialThreadId, isNew: false }\n : { id: mintThreadId(), isNew: true },\n );\n const [search, setSearch] = useState('');\n\n const tokenRef = useRef(token);\n tokenRef.current = token;\n const apiKeyRef = useRef(apiKey);\n apiKeyRef.current = apiKey;\n const getTokenRef = useRef(getToken);\n getTokenRef.current = getToken;\n\n // The cache must be keyed by WHO is asking. Keying on `backendUrl`/`appId`\n // alone let a token swap to a different user read the previous user's thread\n // titles out of the cache. The principal is a non-secret user id, so no\n // credential enters the key (or React Query's devtools). The shared pager\n // never serves placeholder rows across keys, so another principal's list is\n // never shown while the new one loads.\n const authContext = useContext(AthenaAuthContext);\n const principal = authContext?.user?.userId ?? '';\n // A live token getter is a credential too: a `getToken`-only host\n // authenticates its chat, so its list must not stay empty.\n const hasAuth = !!(apiKey || token || getToken);\n\n // Resolved per request with the attach's token semantics (getter first,\n // host-held token as the fallback) so a rotated token is never stale;\n // `force` renews a token the host just refused (`withCredentialRenewal`).\n const getAuth = useCallback(\n async (options?: { force?: boolean }) => ({\n apiKey: apiKeyRef.current,\n token: await resolveStatewireAuthToken(\n { apiKey: apiKeyRef.current, token: tokenRef.current, getToken: getTokenRef.current },\n { staleAuth: options?.force ?? false },\n ),\n }),\n [],\n );\n const fetchPage = useMemo(\n () => createRestConversationListFetcher({ backendUrl, includePullRequests, getAuth }),\n [backendUrl, includePullRequests, getAuth],\n );\n const trimmedSearch = search.trim();\n // A stable key, so a host passing a fresh array each render does not refetch; JSON keeps each\n // id exact (no separator an id could contain).\n const additionalListAppIdsKey = JSON.stringify(additionalListAppIds ?? []);\n const listedAppIds = useMemo<readonly string[]>(\n () => JSON.parse(additionalListAppIdsKey) as string[],\n [additionalListAppIdsKey],\n );\n const filter = useMemo<ConversationListFilter>(\n () => ({\n ...(appId ? { appId } : {}),\n ...(listedAppIds.length > 0 ? { appIds: listedAppIds } : {}),\n ...(trimmedSearch ? { search: trimmedSearch } : {}),\n }),\n [appId, listedAppIds, trimmedSearch],\n );\n // Pull-request opt-in changes what a cached row holds, so it is part of\n // the cache identity.\n const listScopeKey = `${backendUrl}::${principal}::${hasAuth ? 'auth' : 'anon'}::${\n includePullRequests ? 'prs' : 'no-prs'\n }`;\n const list = useConversationList({\n fetchPage,\n filter,\n pageSize: THREAD_LIST_PAGE_SIZE,\n scopeKey: listScopeKey,\n enabled: hasAuth,\n });\n // The pinned sessions are read on their own: `list` holds the newest\n // sessions only, so a session pinned long ago would leave the Pinned group\n // as soon as that many newer sessions exist. Same filter (app scope,\n // search), so a pinned row shows exactly where a listed one would.\n const fetchPinnedPage = useMemo(\n () =>\n createRestConversationListFetcher({\n backendUrl,\n includePullRequests,\n getAuth,\n extraFilters: { favorites_only: true },\n }),\n [backendUrl, includePullRequests, getAuth],\n );\n const pinnedScopeKey = `${listScopeKey}::pinned`;\n const pinnedList = useConversationList({\n fetchPage: fetchPinnedPage,\n filter,\n pageSize: THREAD_LIST_PAGE_SIZE,\n scopeKey: pinnedScopeKey,\n enabled: hasAuth,\n });\n // Every pin is listed, not only the first page of them: the later pinned\n // pages load one after another. Counting requests rather than rows bounds\n // the read even when a page repeats rows the pager already holds.\n const pinnedPagesRequested = useRef({ key: '', count: 0 });\n const pinnedRequestKey = `${pinnedScopeKey}::${JSON.stringify(filter)}`;\n if (pinnedPagesRequested.current.key !== pinnedRequestKey) {\n pinnedPagesRequested.current = { key: pinnedRequestKey, count: 0 };\n }\n const pinnedHasNextPage = pinnedList.hasNextPage;\n const pinnedIsFetchingNextPage = pinnedList.isFetchingNextPage;\n const pinnedNextPageFailed = pinnedList.isFetchNextPageError;\n const pinnedFetchNextPage = pinnedList.fetchNextPage;\n useEffect(() => {\n if (!pinnedHasNextPage || pinnedIsFetchingNextPage || pinnedNextPageFailed) return;\n if (pinnedPagesRequested.current.count >= MAX_PINNED_PAGES - 1) return;\n pinnedPagesRequested.current.count += 1;\n void pinnedFetchNextPage();\n }, [pinnedHasNextPage, pinnedIsFetchingNextPage, pinnedNextPageFailed, pinnedFetchNextPage]);\n const listedConversations = useMemo(\n () =>\n withPinnedConversations({\n conversations: list.conversations,\n pinned: pinnedList.conversations,\n }),\n [list.conversations, pinnedList.conversations],\n );\n\n // Live rows ride the signed-in user's conversation index — the same push\n // channel the conversation-history panel renders from: the host replicates\n // title, lifecycle, attention and creation changes as ops, so a\n // freshly-started thread's server-generated title (or a session flipping to\n // \"needs you\") lands without polling. The index is one instance per\n // `(workspace, user)` in agora's ids, so the pair is resolved from the\n // backend once a principal is known and the index only attaches once both\n // halves are; API-key-only hosts keep the bounded REST poll\n // (`titleSource: 'poll'`).\n const indexHeaders = useCallback<DeepAgentHeadersResolver>(\n (ctx) =>\n resolveStatewireAuthHeaders(\n { apiKey: apiKeyRef.current, token: tokenRef.current, getToken: getTokenRef.current },\n ctx,\n ),\n [],\n );\n // The workspace half is the CREDENTIAL's: a scoped credential (sandbox,\n // preview, Office session) pins its own workspace, so swapping one for\n // another under the same principal must re-resolve the pair. The query is\n // therefore keyed by a credential generation — a counter that advances\n // whenever the host's credentials change — so no secret enters the key.\n const credentialsRef = useRef(credentialShape({ apiKey, token, getToken }));\n const credentialGenerationRef = useRef(0);\n const currentCredentials = credentialShape({ apiKey, token, getToken });\n if (!sameCredentialShape(credentialsRef.current, currentCredentials)) {\n credentialsRef.current = currentCredentials;\n credentialGenerationRef.current += 1;\n }\n const credentialGeneration = credentialGenerationRef.current;\n const identityEnabled = syncUrl !== undefined && principal.length > 0 && hasAuth;\n const identity = useQuery({\n // Keyed by the non-secret principal and credential generation, never the\n // credential itself.\n queryKey: ['athena-sdk', 'account-identity', backendUrl, principal, credentialGeneration],\n queryFn: ({ signal }) =>\n withCredentialRenewal(getAuth, (auth) => fetchAccountIdentity(backendUrl, auth, { signal })),\n enabled: identityEnabled,\n staleTime: Number.POSITIVE_INFINITY,\n });\n const indexWorkspaceId = identity.data?.workspaceId ?? '';\n const indexUserId = identity.data?.userId ?? '';\n const indexEnabled = identityEnabled && indexWorkspaceId.length > 0 && indexUserId.length > 0;\n const index = useConversationIndex({\n baseUrl: syncUrl === undefined ? '' : conversationIndexUrlFromStatewireUrl(syncUrl),\n workspaceId: indexWorkspaceId || 'pending-workspace',\n userId: indexUserId || 'pending-user',\n headers: indexHeaders,\n enabled: indexEnabled,\n });\n // The index parks after repeated credential refusals; re-arm it as soon as\n // a new token exists so live rows resume without a reload.\n useRestartAfterAuthRefusal({\n connection: index.connection,\n reconnect: index.connection.reconnect,\n });\n const indexByThread = useMemo(() => {\n const entries = new Map<string, ConversationIndexEntry>();\n for (const entry of index.entries ?? []) entries.set(entry.thread_id, entry);\n return entries;\n }, [index.entries]);\n\n // Each fetched row folds its pushed index entry through the shared\n // `summaryFromConversationIndexEntry`, so titles, lifecycle and attention\n // update live while the fields the index never carries (pull requests,\n // the last wake) keep their fetched values. A pushed title that normalizes\n // to nothing falls back to the fetched one.\n const mergedConversations = useMemo(\n () => foldConversationIndex(listedConversations, indexByThread),\n [listedConversations, indexByThread],\n );\n // The server's answer to a write is patched into EVERY conversation list\n // cached under this provider (its own page, and any `SessionsPage` /\n // `Inbox` view) — the client is the provider's own, so nothing outside it\n // is touched.\n const queryClient = useQueryClient();\n const patchEveryList = useCallback(\n (threadId: string, patch: Partial<ConversationSummary>) =>\n patchConversationListCaches(queryClient, {\n match: (item) => item.threadId === threadId,\n patch,\n }),\n [queryClient],\n );\n // A pin toggle sits over the fold: the row flips at once, and a pushed\n // index entry that predates the write cannot clobber it. The override drops\n // once both sources report the toggled value.\n const pinnedListRefresh = pinnedList.refresh;\n const favorite = useThreadFavorite({\n backendUrl,\n principalId: principal,\n getAuth,\n rows: mergedConversations,\n onServerTruth: ({ threadId, isFavorited }) => {\n patchEveryList(threadId, { isFavorited });\n // A newly pinned session joins the pinned read, so it stays listed\n // once newer sessions push it out of `list`.\n if (isFavorited) void pinnedListRefresh();\n },\n });\n const favoriteOverrides = favorite.overrides;\n // A rename sits over the fold the same way: the row shows the new title\n // at once, and a pushed index entry that predates the write cannot restore\n // the old one. The override drops once both sources report the new title.\n const rename = useThreadRename({\n backendUrl,\n principalId: principal,\n getAuth,\n rows: mergedConversations,\n onServerTruth: ({ threadId, title }) => patchEveryList(threadId, { title }),\n });\n const titleOverrides = rename.overrides;\n // Read state likewise; the read-state route publishes no index update, so\n // the override is what keeps a stale pushed `is_unread` from flipping the\n // row back until the index's next rebuild agrees.\n const readState = useThreadReadState({\n backendUrl,\n principalId: principal,\n getAuth,\n rows: mergedConversations,\n onServerTruth: ({ threadId, isUnread }) => patchEveryList(threadId, { isUnread }),\n });\n const readOverrides = readState.overrides;\n const readCoveredUpdatedAt = readState.coveredUpdatedAt;\n\n // An archive hides the row at once and keeps it hidden until neither list\n // source reports it any more — the cached page is patched on the server's\n // answer, but the pushed index may still carry the row until the publish\n // that follows the commit lands. A failure drops the id and the row comes\n // back from the still-cached page: no refetch needed. Scoping, the\n // one-write-per-thread guard and the route-missing fallback live in the hook.\n const archive = useThreadArchive({\n backendUrl,\n principalId: principal,\n getAuth,\n onArchived: (threadId) => {\n removeConversationFromListCaches(queryClient, { threadId });\n // The archived session cannot stay the active chat: the server no\n // longer lists it, so the chat moves to a fresh one.\n setActive((current) =>\n current.id === threadId ? { id: mintThreadId(), isNew: true } : current,\n );\n },\n });\n const hiddenThreadIds = archive.hiddenThreadIds;\n const archiveRelease = archive.release;\n // A hidden row is released once both sources have forgotten it, so a\n // session archived elsewhere and then restored is never masked here.\n useEffect(() => {\n if (hiddenThreadIds.size === 0) return;\n const forgotten = [...hiddenThreadIds].filter(\n (threadId) =>\n !mergedConversations.some((conversation) => conversation.threadId === threadId) &&\n !indexByThread.has(threadId),\n );\n if (forgotten.length > 0) archiveRelease(forgotten);\n }, [hiddenThreadIds, mergedConversations, indexByThread, archiveRelease]);\n\n const applyOverrides = useCallback(\n (rows: readonly ConversationSummary[]) =>\n withReadStateOverrides(\n withTitleOverrides(withFavoriteOverrides(rows, favoriteOverrides), titleOverrides),\n readOverrides,\n readCoveredUpdatedAt,\n ).filter((conversation) => !hiddenThreadIds.has(conversation.threadId)),\n [favoriteOverrides, titleOverrides, readOverrides, readCoveredUpdatedAt, hiddenThreadIds],\n );\n const data = useMemo(\n () => applyOverrides(mergedConversations).map(threadSummaryFromConversation),\n [applyOverrides, mergedConversations],\n );\n const isLoading = list.isLoading;\n\n // The active thread's row must survive a search that filters it out of\n // the page (the banner above the chat reads it): the listed row when there\n // is one, else the pushed index entry folded over the last row seen for\n // this thread (so pull requests survive the fold), else that last row.\n const lastActiveConversationRef = useRef<ConversationSummary | null>(null);\n const activeConversation = useMemo<ConversationSummary | null>(() => {\n const listed = mergedConversations.find((conversation) => conversation.threadId === active.id);\n if (listed) return listed;\n const remembered =\n lastActiveConversationRef.current?.threadId === active.id\n ? lastActiveConversationRef.current\n : undefined;\n const entry = indexByThread.get(active.id);\n if (entry) return summaryFromConversationIndexEntry(entry, remembered);\n return remembered ?? null;\n }, [mergedConversations, indexByThread, active.id]);\n if (activeConversation !== null) lastActiveConversationRef.current = activeConversation;\n const activeThread = useMemo(\n () =>\n activeConversation\n ? threadSummaryFromConversation(\n withReadStateOverrides(\n [\n withTitleOverride(\n withFavoriteOverride(activeConversation, favoriteOverrides),\n titleOverrides,\n ),\n ],\n readOverrides,\n readCoveredUpdatedAt,\n )[0] ?? activeConversation,\n )\n : null,\n [activeConversation, favoriteOverrides, titleOverrides, readOverrides, readCoveredUpdatedAt],\n );\n\n const listRefresh = list.refresh;\n const refresh = useCallback(() => {\n void listRefresh();\n void pinnedListRefresh();\n }, [listRefresh, pinnedListRefresh]);\n const listFetchNextPage = list.fetchNextPage;\n const fetchNextPage = useCallback(() => {\n void listFetchNextPage();\n }, [listFetchNextPage]);\n\n // A thread the index knows — created here on first send, or elsewhere —\n // that the REST page does not list yet refreshes the page so the row\n // appears without the user pressing refresh. Rows for apps this list does\n // not show (its `appId` and `additionalListAppIds`) never trigger one. The\n // refreshes are coalesced, held while the page is already being read, and\n // a refused one is tried again (`useIndexTriggeredRefresh`).\n const listedThreadIds = useMemo(\n () => new Set(list.conversations.map((conversation) => conversation.threadId)),\n [list.conversations],\n );\n const unlistedIndexedThreads = useMemo(\n () =>\n index.entries === null\n ? []\n : indexedThreadsToRefreshFor({\n entries: index.entries,\n listedThreadIds,\n refreshedThreadIds: EMPTY_THREAD_IDS,\n appId,\n additionalAppIds: listedAppIds,\n }),\n [index.entries, listedThreadIds, appId, listedAppIds],\n );\n // The user's other open tabs heard the same push: one of them reads the\n // page and shares it with the rest (`session-list-tab-share.ts`).\n const tabShare = useSessionListTabShare({\n queryClient,\n identity:\n indexWorkspaceId && indexUserId\n ? { backendUrl, userId: indexUserId, workspaceId: indexWorkspaceId }\n : null,\n });\n const listShareKey = sessionListShareKey(\n conversationListQueryKeys.firstPage(\n listScopeKey,\n conversationListFilterKey(filter),\n THREAD_LIST_PAGE_SIZE,\n ),\n );\n useIndexTriggeredRefresh({\n pending: unlistedIndexedThreads,\n refresh: listRefresh,\n // Held only while the page is being read (its first read and that read's\n // own retries included). A first page that was refused does not hold it:\n // the index-triggered refresh is then how the list recovers.\n enabled: hasAuth && index.entries !== null && !list.isFetching,\n tabShare,\n shareKey: listShareKey,\n });\n\n const selectThread = useCallback((threadId: string, options?: { isNew?: boolean }) => {\n if (!threadId) return;\n const isNew = options?.isNew === true;\n setActive((current) => (current.id === threadId ? current : { id: threadId, isNew }));\n }, []);\n\n // The hover prefetcher is one per (backend, principal, credential\n // generation): a credential swap clears the provider's snapshot cache, so\n // the attempt ledger resets with it and a late answer from the previous\n // identity is never remembered under the new one.\n const prefetchScope = `${backendUrl}::${principal}::${credentialGeneration}`;\n const prefetchScopeRef = useRef(prefetchScope);\n prefetchScopeRef.current = prefetchScope;\n const prefetcher = useMemo(() => {\n if (!snapshotCache) return null;\n const scope = prefetchScope;\n return createThreadPrefetcher({\n cache: snapshotCache,\n fetchState: async (threadId) =>\n withCredentialRenewal(getAuth, (auth) =>\n getThreadState(backendUrl, auth, threadId, { skipCache: false }),\n ),\n isCurrent: () => prefetchScopeRef.current === scope,\n });\n }, [snapshotCache, prefetchScope, backendUrl, getAuth]);\n const activeIdRef = useRef(active.id);\n activeIdRef.current = active.id;\n const prefetchThread = useCallback(\n (threadId: string) => {\n if (!prefetcher || !threadId || threadId === activeIdRef.current) return;\n prefetcher.prefetch(threadId);\n },\n [prefetcher],\n );\n\n const newThread = useCallback(() => {\n setActive({ id: mintThreadId(), isNew: true });\n }, []);\n\n // A minted thread stops being \"new\" once the backend lists it — on the\n // fetched page or in the pushed index (a search may keep it off the page):\n // until then `useAthenaThreadManager` reports a null id, so URL sync cannot\n // name it.\n const isPersisted =\n data.some((thread) => thread.id === active.id) || indexByThread.has(active.id);\n useEffect(() => {\n if (isPersisted) {\n setActive((current) => (current.isNew ? { ...current, isNew: false } : current));\n }\n }, [isPersisted]);\n\n const titleSource: AthenaStatewireThreadListState['titleSource'] =\n index.entries !== null ? 'index' : 'poll';\n\n const value = useMemo<AthenaStatewireThreadListState>(\n () => ({\n threads: data,\n isLoading,\n isFetching: list.isFetching,\n hasResolvedFirstPage: list.hasResolvedFirstPage,\n activeThreadId: active.id,\n activeThread,\n isNewChat: active.isNew,\n selectThread,\n prefetchThread,\n newThread,\n refresh,\n search,\n setSearch,\n hasNextPage: list.hasNextPage,\n isFetchingNextPage: list.isFetchingNextPage,\n fetchNextPage,\n setFavorited: favorite.setFavorited,\n favoritePending: favorite.isPending,\n favoritePendingThreadIds: favorite.pendingThreadIds,\n favoriteToggleAvailable: favorite.isAvailable,\n renameThread: rename.rename,\n renamePendingThreadIds: rename.pendingThreadIds,\n renameAvailable: rename.isAvailable,\n archiveThread: archive.archive,\n archivePendingThreadIds: archive.pendingThreadIds,\n archivePending: archive.isPending,\n archiveAvailable: archive.isAvailable,\n setReadState: readState.setReadState,\n readStatePendingThreadIds: readState.pendingThreadIds,\n titleSource,\n }),\n [\n data,\n isLoading,\n list.isFetching,\n list.hasResolvedFirstPage,\n active.id,\n activeThread,\n active.isNew,\n selectThread,\n prefetchThread,\n newThread,\n refresh,\n search,\n list.hasNextPage,\n list.isFetchingNextPage,\n fetchNextPage,\n favorite.setFavorited,\n favorite.isPending,\n favorite.pendingThreadIds,\n favorite.isAvailable,\n rename.rename,\n rename.pendingThreadIds,\n rename.isAvailable,\n archive.archive,\n archive.pendingThreadIds,\n archive.isPending,\n archive.isAvailable,\n readState.setReadState,\n readState.pendingThreadIds,\n titleSource,\n ],\n );\n\n const live = useMemo<AthenaSessionsLiveState>(\n () => ({\n backendUrl,\n getAuth,\n principal,\n hasAuth,\n includePullRequests,\n scopeKey: listScopeKey,\n credentialGeneration,\n baseFilter: {\n ...(appId ? { appId } : {}),\n ...(listedAppIds.length > 0 ? { appIds: listedAppIds } : {}),\n },\n indexByThread,\n indexEntries: index.entries,\n applyOverrides,\n tabShare,\n }),\n [\n backendUrl,\n getAuth,\n principal,\n hasAuth,\n includePullRequests,\n listScopeKey,\n credentialGeneration,\n appId,\n listedAppIds,\n indexByThread,\n index.entries,\n applyOverrides,\n tabShare,\n ],\n );\n\n return (\n <AthenaStatewireThreadListContext.Provider value={value}>\n <AthenaSessionsLiveContext.Provider value={live}>{children}</AthenaSessionsLiveContext.Provider>\n </AthenaStatewireThreadListContext.Provider>\n );\n}\n\n/**\n * Provides the statewire thread-list state, with an SDK-scoped QueryClient so\n * consumers do not need their own QueryClientProvider. `syncUrl` (the\n * `…/v2/threads` mount) locates the conversation index beside it; without it\n * titles fall back to the REST poll. `includePullRequests` (default `true`)\n * asks the list for live pull-request state so `SessionList` can render its\n * chips; a host that never shows them turns it off to spare the server the\n * lookup. `snapshotCache` is the provider's thread snapshot cache — the one\n * the runtime seeds a switched-to thread from — which `prefetchThread` warms;\n * without it prefetching is a no-op.\n */\nexport function AthenaStatewireThreadListProvider({\n backendUrl,\n syncUrl,\n apiKey,\n token,\n getToken,\n appId,\n additionalListAppIds,\n initialThreadId,\n includePullRequests = true,\n snapshotCache,\n offlineList,\n children,\n}: {\n backendUrl: string;\n syncUrl?: string;\n apiKey?: string;\n token?: string | null;\n getToken?: AthenaStatewireTokenGetter;\n appId?: string;\n /** Other client apps whose sessions the list shows too (a union with `appId`). */\n additionalListAppIds?: readonly string[];\n initialThreadId?: string;\n includePullRequests?: boolean;\n snapshotCache?: DeepAgentThreadSnapshotCache;\n /**\n * Keep the list's first pages on this device, sealed (`AthenaProvider`'s `offlineHistory`): they\n * paint at once and read while Athena can't be reached.\n */\n offlineList?: { mirror: ThreadSnapshotMirror; ownerId: string };\n children: ReactNode;\n}) {\n const [queryClient] = useState(() => new QueryClient());\n useOfflineSessionList({ queryClient, offlineList });\n return (\n <QueryClientProvider client={queryClient}>\n <StatewireThreadListState\n backendUrl={backendUrl}\n syncUrl={syncUrl}\n apiKey={apiKey}\n token={token}\n getToken={getToken}\n appId={appId}\n additionalListAppIds={additionalListAppIds}\n initialThreadId={initialThreadId}\n includePullRequests={includePullRequests}\n snapshotCache={snapshotCache}\n >\n {children}\n </StatewireThreadListState>\n </QueryClientProvider>\n );\n}\n\n/** How long after a thread's first message the title poll starts (exported for tests). */\nexport const TITLE_POLL_DELAY_MS = 5000;\nconst TITLE_POLL_INTERVAL_MS = 2000;\nconst TITLE_POLL_MAX_DURATION_MS = 60_000;\n\n/**\n * Fallback for hosts without a live conversation index (`titleSource: 'poll'`,\n * i.e. no signed-in principal): refreshes the thread list until a\n * freshly-started thread's server-generated title appears. Idle while the\n * index carries titles. Mount inside the statewire runtime scope (needs aui\n * state).\n *\n * The thread is read from the statewire scope, not the list: the scope\n * publishes the id together with the client it belongs to, so the messages\n * observed here are always the named thread's (the list's selection moves\n * one commit ahead of the client during a switch).\n */\nexport function StatewireThreadTitlePoller(): null {\n const list = useAthenaStatewireThreadList();\n const activeThreadId = useAthenaStatewireThreadId();\n const hasMessages = useAuiState((s) => (s.optional.thread?.messages.length ?? 0) > 0);\n\n const titleSource = list?.titleSource ?? 'poll';\n const entry = list?.threads.find((thread) => thread.id === activeThreadId);\n const title = entry?.title?.trim() ?? '';\n const hasTitle = title.length > 0 && !isSerializedContentPartsTitle(title);\n\n // The poller stays mounted across thread switches, so the visit is tracked\n // by hand: one poll per visit — no restart when the visited thread's\n // messages or title flip underneath it — and a re-selected thread polls\n // again, as it did when every switch remounted the poller. The record is\n // replaced whenever the attached thread changes, whether or not a poll\n // started for it, so a visit to a thread that needs no poll can never make\n // the next visit to a previous thread look already polled.\n const visitRef = useRef<{ threadId: string; polled: boolean } | null>(null);\n const refreshRef = useRef(list?.refresh);\n refreshRef.current = list?.refresh;\n\n useEffect(() => {\n if (visitRef.current?.threadId !== activeThreadId) {\n visitRef.current = { threadId: activeThreadId, polled: false };\n }\n const visit = visitRef.current;\n if (!hasMessages || hasTitle || titleSource === 'index') return;\n if (visit.polled) return;\n visit.polled = true;\n\n let intervalId: ReturnType<typeof setInterval> | null = null;\n const startId = setTimeout(() => {\n intervalId = setInterval(() => refreshRef.current?.(), TITLE_POLL_INTERVAL_MS);\n }, TITLE_POLL_DELAY_MS);\n const stopId = setTimeout(() => {\n if (intervalId !== null) clearInterval(intervalId);\n }, TITLE_POLL_MAX_DURATION_MS);\n\n return () => {\n clearTimeout(startId);\n clearTimeout(stopId);\n if (intervalId !== null) clearInterval(intervalId);\n };\n }, [activeThreadId, hasMessages, hasTitle, titleSource]);\n\n return null;\n}\n"],"names":["_a"],"mappings":"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"}
1
+ {"version":3,"file":"statewire-thread-list.js","sources":["../../src/threads/statewire-thread-list.tsx"],"sourcesContent":["import {\n type ConversationAttention,\n type ConversationAttentionSource,\n type ConversationIndexEntry,\n type ConversationListFilter,\n type ConversationPullRequest,\n type ConversationSummary,\n type ConversationWake,\n conversationIndexUrlFromStatewireUrl,\n conversationListFilterKey,\n conversationListQueryKeys,\n type DeepAgentHeadersResolver,\n type DeepAgentThreadSnapshotCache,\n deriveConversationAttention,\n patchConversationListCaches,\n removeConversationFromListCaches,\n summaryFromConversationIndexEntry,\n type ThreadSnapshotMirror,\n useConversationIndex,\n useConversationList,\n} from '@athenaintel/agent-runtime';\nimport { useOfflineSessionList } from './offline-session-list';\nimport { QueryClient, QueryClientProvider, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { useAuiState } from '@assistant-ui/react';\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\nimport { AthenaAuthContext } from '../auth/AthenaAuthContext';\nimport {\n type AthenaStatewireTokenGetter,\n resolveStatewireAuthHeaders,\n resolveStatewireAuthToken,\n} from '../runtime/statewire-auth-headers';\nimport { fetchAccountIdentity, getThreadState } from './api';\nimport { createRestConversationListFetcher } from './conversation-list-fetcher';\nimport { credentialShape, sameCredentialShape } from './credential-shape';\nimport { mintThreadId } from './mint-thread-id';\nimport { useAthenaStatewireThreadId } from './thread-id-context';\nimport { createThreadPrefetcher } from './thread-prefetcher';\nimport { useIndexTriggeredRefresh } from './index-triggered-refresh';\nimport {\n type SessionListTabShare,\n sessionListShareKey,\n useSessionListTabShare,\n} from './session-list-tab-share';\nimport { isSerializedContentPartsTitle, normalizeThreadTitle } from './title';\nimport { useRestartAfterAuthRefusal } from '../runtime/auth-refusal-restart';\nimport { type CredentialResolver, withCredentialRenewal } from '../runtime/credential-renewal';\nimport { useThreadArchive } from './use-thread-archive';\nimport {\n type ThreadFavoriteTarget,\n useThreadFavorite,\n withFavoriteOverride,\n withFavoriteOverrides,\n} from './use-thread-favorite';\nimport {\n type ThreadReadStateTarget,\n useThreadReadState,\n withReadStateOverrides,\n} from './use-thread-read-state';\nimport {\n type ThreadRenameTarget,\n useThreadRename,\n withTitleOverride,\n withTitleOverrides,\n} from './use-thread-rename';\n\n/**\n * One row of the thread list — the fields a Devin-style session list renders\n * (`SessionList`), all read from the shared conversation-list contract. The\n * attention fields are `null` on a server that does not derive them; the\n * pull-request state is `null` until the list has fetched it (or when the\n * provider opted out of it).\n */\nexport interface AthenaStatewireThreadSummary {\n id: string;\n title: string | null;\n /**\n * What the session asks of the viewer: the server-derived state when\n * reported, else the client fallback from the status planes\n * (`deriveConversationAttention`).\n */\n attention: ConversationAttention | null;\n /** One line (≤ 80 chars) of what the viewer should do next, when the server derives one. */\n nextAction: string | null;\n attentionSource: ConversationAttentionSource | null;\n /**\n * Whether the viewer pinned the session. Toggled through\n * `AthenaStatewireThreadListState.setFavorited`; a toggle in flight is\n * reflected here at once and held until the list sources report it.\n */\n isFavorited: boolean;\n isUnread: boolean;\n /** Canonical status_v2 planes, or `null` on an older server. */\n sessionStatus: string | null;\n runStatus: string | null;\n /** Channel the session was started from (e.g. `web`, `github`, `api`). */\n startChannel: string | null;\n /** Client app that created the session (SESSION_APP_ID_GUIDE). */\n appId: string | null;\n /** Trigger marker (`manual` remains user-initiated; other values are automated). */\n triggerType: string | null;\n /** Pull requests the session opened with their live GitHub state. */\n pullRequests: ConversationPullRequest[] | null;\n /** The last wake of a resting session. */\n lastWake: ConversationWake | null;\n /** ISO 8601 timestamp of the last update (the list's sort key). */\n updatedAt: string;\n /** ISO 8601 creation time; `null` when the list did not report it. */\n createdAt?: string | null;\n /** The session's last message, for a preview line (the inbox excerpt). */\n preview?: string | null;\n /** The collab agent the session started as, and its channel (`null`: the default channel). */\n collabAgentId?: string | null;\n collabChannelId?: string | null;\n /** Whether the viewer started the session (`false`: shared with them); `null` when not reported. */\n createdByMe?: boolean | null;\n}\n\n/**\n * Thread-list state. The list is the persisted Agora session list; the ACTIVE\n * thread is a plain id the statewire runtime attaches to — switching threads\n * remounts the statewire session against the new `…/v2/threads/{id}` URL\n * (the same model the mobile app uses).\n */\nexport interface AthenaStatewireThreadListState {\n /** The rows the current `search` lists, newest first. */\n threads: AthenaStatewireThreadSummary[];\n isLoading: boolean;\n /** True while the live first page (or a history page) is being fetched. */\n isFetching: boolean;\n /**\n * True once the live first page has answered at least once for the current\n * search and identity. Until then an empty `threads` means \"not known\n * yet\" (no credential resolved, first page in flight), not \"no sessions\":\n * `SessionList` shows skeleton rows, never the empty state.\n */\n hasResolvedFirstPage: boolean;\n activeThreadId: string;\n /**\n * The active thread's row, independent of what `threads` currently lists:\n * a search that hides the row, or a page that has not fetched it, must not\n * make the active chat's attention disappear. Resolved from the listed row,\n * else the pushed index entry folded over the last row seen, else that\n * last row; `null` for a never-started chat the backend does not know yet.\n */\n activeThread: AthenaStatewireThreadSummary | null;\n /** True while the active thread is a locally-minted chat with no server history. */\n isNewChat: boolean;\n /**\n * Switch to `threadId`; `isNew` for a chat this page minted that never started (it loads as a\n * new chat until the list has it), e.g. one holding messages that wait in the chat outbox.\n */\n selectThread: (threadId: string, options?: { isNew?: boolean }) => void;\n /**\n * Warm the provider's snapshot cache with a thread's REST state ahead of a\n * `selectThread`, so the switch paints the transcript on its first frame\n * instead of the loading scrim while the statewire attach lands.\n * `SessionList` calls it after a 120 ms hover dwell and on row focus.\n * Fetches once per thread per identity, deduplicates requests in flight,\n * runs at most two at a time and ignores failures; a no-op for the active\n * thread, a thread the cache already holds, or a provider without a cache.\n */\n prefetchThread: (threadId: string) => void;\n newThread: () => void;\n refresh: () => void;\n /**\n * Server-side title search (`search` on the REST thread list). An empty\n * string lists everything; the search covers the viewer's whole authorized\n * history, not just the live page.\n */\n search: string;\n setSearch: (search: string) => void;\n /** Whether another page of older sessions can be fetched. */\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n /** Fetch the next page of older sessions (frozen history pages, never replayed). */\n fetchNextPage: () => void;\n /**\n * Pin or unpin a session (`POST /api/conversations/threads/favorite`). The\n * row flips at once and moves between the `Pinned` and `Recent` groups; the\n * server's answer is written back, and a failure restores the previous\n * state. Writes for one thread are serialized: a toggle while that\n * thread's write is in flight records the value and sends at most one\n * follow-up write once the first has answered.\n */\n setFavorited: (target: ThreadFavoriteTarget) => void;\n /** True while any pin toggle is in flight. */\n favoritePending: boolean;\n /** The sessions whose pin toggle is in flight; `SessionList` disables their pin control. */\n favoritePendingThreadIds: ReadonlySet<string>;\n /**\n * Whether the backend offers the favorite route. `false` once it answered\n * 404 / 405 (an agora that predates the route): `SessionList` then shows\n * the read-only pin and no toggle, and `setFavorited` keeps failing.\n */\n favoriteToggleAvailable: boolean;\n /**\n * Rename a session for every viewer (`POST /api/conversations/threads/rename`).\n * The row shows the new title at once; the server's stored title is\n * written back, and a failure restores the previous one. Same-thread\n * writes are serialized like the pin's.\n */\n renameThread: (target: ThreadRenameTarget) => void;\n /** The sessions whose rename is in flight; `SessionList` keeps their editor closed. */\n renamePendingThreadIds: ReadonlySet<string>;\n /**\n * Whether the backend offers the rename route. `false` once it answered\n * 404 / 405 (an agora that predates the route): `SessionList` then offers\n * no Rename action, and `renameThread` keeps failing.\n */\n renameAvailable: boolean;\n /**\n * Archive a session (`POST /api/conversations/threads/archive`). The row\n * leaves the list at once and stays hidden until both list sources stop\n * reporting it; a failure brings it back. Archiving the active session\n * moves the chat to a new one once the server has confirmed.\n */\n archiveThread: (threadId: string) => void;\n /** The sessions whose archive is in flight. */\n archivePendingThreadIds: ReadonlySet<string>;\n /** True while any archive is in flight. */\n archivePending: boolean;\n /**\n * Whether the backend offers the archive route. `false` once it answered\n * 404 / 405: `SessionList` then offers no Archive action.\n */\n archiveAvailable: boolean;\n /**\n * Mark a session read or unread for the viewer\n * (`POST /api/conversations/threads/read-state`). The row flips at once in\n * every list under the provider (`SessionList`, `SessionsPage`, `Inbox`);\n * the server's answer is written back, and a failure restores the previous\n * state. Same-thread writes are serialized like the pin's.\n */\n setReadState: (target: ThreadReadStateTarget) => void;\n /** The sessions whose read-state write is in flight. */\n readStatePendingThreadIds: ReadonlySet<string>;\n /**\n * Where titles come from. `'index'` once the signed-in user's live\n * conversation index has replicated — titles and newly created threads then\n * arrive as pushed updates; `'poll'` while it has not (no signed-in\n * principal, e.g. API-key mode, or before its snapshot), when a\n * freshly-started thread's title is picked up by the bounded REST poll.\n */\n titleSource: 'index' | 'poll';\n}\n\nexport const AthenaStatewireThreadListContext =\n createContext<AthenaStatewireThreadListState | null>(null);\n\n/** Read the statewire thread-list state, or null outside that mode. */\nexport function useAthenaStatewireThreadList(): AthenaStatewireThreadListState | null {\n return useContext(AthenaStatewireThreadListContext);\n}\n\n/** Trigger a re-fetch of the thread list. `null` outside `enableThreadList` mode. */\nexport function useRefreshThreadList(): (() => void) | null {\n return useAthenaStatewireThreadList()?.refresh ?? null;\n}\n\n/**\n * Project one contract row onto the thread-list row. Exported for hosts (and\n * tests) that build rows from a `ConversationSummary` of their own.\n */\n/**\n * The threads the conversation index knows that the fetched list does not show yet, and that have\n * not prompted a refresh before: each warrants one refresh of the list. Only threads of the apps\n * the list shows count (its `appId` and `additionalAppIds`); with neither, every app's do.\n * Exported for tests.\n */\nexport function indexedThreadsToRefreshFor({\n entries,\n listedThreadIds,\n refreshedThreadIds,\n appId,\n additionalAppIds,\n}: {\n entries: ReadonlyArray<{ thread_id: string; app_id: string | null }>;\n listedThreadIds: ReadonlySet<string>;\n refreshedThreadIds: ReadonlySet<string>;\n appId: string | undefined;\n additionalAppIds: readonly string[];\n}): string[] {\n const shownAppIds =\n appId || additionalAppIds.length > 0\n ? new Set([...(appId ? [appId] : []), ...additionalAppIds])\n : null;\n return entries\n .filter((entry) => !shownAppIds || shownAppIds.has(entry.app_id ?? ''))\n .map((entry) => entry.thread_id)\n .filter((threadId) => !listedThreadIds.has(threadId) && !refreshedThreadIds.has(threadId));\n}\n\nexport function threadSummaryFromConversation(\n conversation: ConversationSummary,\n): AthenaStatewireThreadSummary {\n return {\n id: conversation.threadId,\n title: normalizeThreadTitle(conversation.title) ?? null,\n attention: deriveConversationAttention(conversation),\n nextAction: conversation.nextAction ?? null,\n attentionSource: conversation.attentionSource ?? null,\n isFavorited: conversation.isFavorited ?? false,\n isUnread: conversation.isUnread,\n sessionStatus: conversation.sessionStatus,\n runStatus: conversation.runStatus,\n startChannel: conversation.sourceChannel,\n appId: conversation.appId,\n triggerType: conversation.triggerType,\n pullRequests: conversation.pullRequests ?? null,\n lastWake: conversation.lastWake ?? null,\n updatedAt: conversation.updatedAt,\n createdAt: conversation.createdAt,\n preview: conversation.preview,\n collabAgentId: conversation.collabAgentId ?? null,\n collabChannelId: conversation.collabChannelId ?? null,\n createdByMe: conversation.createdByMe ?? null,\n };\n}\n\n/**\n * Fold the pushed conversation index into fetched rows: each row with an\n * index entry takes its pushed fields through the shared\n * `summaryFromConversationIndexEntry` (titles, lifecycle, attention), while\n * what the index never carries (pull requests, the last wake, who started\n * it) keeps the fetched value. A pushed title that normalizes to nothing\n * falls back to the fetched one. Exported for tests.\n */\nexport function foldConversationIndex(\n conversations: readonly ConversationSummary[],\n indexByThread: ReadonlyMap<string, ConversationIndexEntry>,\n): ConversationSummary[] {\n return conversations.map((conversation) => {\n const entry = indexByThread.get(conversation.threadId);\n if (!entry) return conversation;\n const merged = summaryFromConversationIndexEntry(entry, conversation);\n return normalizeThreadTitle(merged.title) === undefined\n ? { ...merged, title: conversation.title }\n : merged;\n });\n}\n\n/**\n * The listed rows plus the pinned rows they do not include, appended in the\n * pinned list's own newest-first order. A pinned row the list does not\n * include is older than the list's last row, so appending keeps the merged\n * rows newest-first. A row whose pin was just undone (`isFavorited: false`,\n * patched into every cached list) is not added back. Exported for tests.\n */\nexport function withPinnedConversations({\n conversations,\n pinned,\n}: {\n conversations: readonly ConversationSummary[];\n pinned: readonly ConversationSummary[];\n}): readonly ConversationSummary[] {\n const listed = new Set(conversations.map((conversation) => conversation.threadId));\n const unlisted = pinned.filter(\n (conversation) => conversation.isFavorited !== false && !listed.has(conversation.threadId),\n );\n return unlisted.length === 0 ? conversations : [...conversations, ...unlisted];\n}\n\n/**\n * What the thread-list provider shares with the other lists mounted under it\n * (`SessionsPage`, `Inbox`): the credential, the live conversation index and\n * the optimistic overrides (pin, title, read state, archived rows), so every\n * list folds the same pushes and shows the same in-flight writes. Internal —\n * hosts use `useAthenaSessions`.\n */\nexport interface AthenaSessionsLiveState {\n backendUrl: string;\n getAuth: CredentialResolver<{ apiKey?: string; token?: string | null }>;\n principal: string;\n hasAuth: boolean;\n includePullRequests: boolean;\n /** The provider list's cache scope (backend, principal, auth, PR opt-in). */\n scopeKey: string;\n /**\n * Advances whenever the host's credential changes (a swapped API key or\n * token, a scoped credential for another workspace under the same\n * principal), so caches keyed on it are never read with another credential.\n */\n credentialGeneration: number;\n /** The app scope the provider lists (`appId` / `additionalListAppIds`). */\n baseFilter: ConversationListFilter;\n indexByThread: ReadonlyMap<string, ConversationIndexEntry>;\n /** `null` until the index has replicated (or without one). */\n indexEntries: readonly ConversationIndexEntry[] | null;\n /** Layer the in-flight writes over rows and drop archived ones. */\n applyOverrides: (rows: readonly ConversationSummary[]) => ConversationSummary[];\n /** The user's other open tabs, which share the lists they read. */\n tabShare: SessionListTabShare;\n}\n\nexport const AthenaSessionsLiveContext = createContext<AthenaSessionsLiveState | null>(null);\n\n/**\n * The statewire thread list reads one live page through the shared\n * conversation-list contract; the REST default page size predates the shared\n * pager and is preserved here.\n */\nconst THREAD_LIST_PAGE_SIZE = 50;\nconst EMPTY_THREAD_IDS: ReadonlySet<string> = new Set();\n/** The most pinned pages the sidebar reads: 500 pinned sessions. */\nconst MAX_PINNED_PAGES = 10;\n\nfunction StatewireThreadListState({\n backendUrl,\n syncUrl,\n apiKey,\n token,\n getToken,\n appId,\n additionalListAppIds,\n initialThreadId,\n includePullRequests,\n snapshotCache,\n children,\n}: {\n backendUrl: string;\n syncUrl?: string;\n apiKey?: string;\n token?: string | null;\n getToken?: AthenaStatewireTokenGetter;\n appId?: string;\n additionalListAppIds?: readonly string[];\n initialThreadId?: string;\n includePullRequests: boolean;\n snapshotCache?: DeepAgentThreadSnapshotCache;\n children: ReactNode;\n}) {\n const [active, setActive] = useState<{ id: string; isNew: boolean }>(() =>\n initialThreadId\n ? { id: initialThreadId, isNew: false }\n : { id: mintThreadId(), isNew: true },\n );\n const [search, setSearch] = useState('');\n\n const tokenRef = useRef(token);\n tokenRef.current = token;\n const apiKeyRef = useRef(apiKey);\n apiKeyRef.current = apiKey;\n const getTokenRef = useRef(getToken);\n getTokenRef.current = getToken;\n\n // The cache must be keyed by WHO is asking. Keying on `backendUrl`/`appId`\n // alone let a token swap to a different user read the previous user's thread\n // titles out of the cache. The principal is a non-secret user id, so no\n // credential enters the key (or React Query's devtools). The shared pager\n // never serves placeholder rows across keys, so another principal's list is\n // never shown while the new one loads.\n const authContext = useContext(AthenaAuthContext);\n const principal = authContext?.user?.userId ?? '';\n // A live token getter is a credential too: a `getToken`-only host\n // authenticates its chat, so its list must not stay empty.\n const hasAuth = !!(apiKey || token || getToken);\n\n // Resolved per request with the attach's token semantics (getter first,\n // host-held token as the fallback) so a rotated token is never stale;\n // `force` renews a token the host just refused (`withCredentialRenewal`).\n const getAuth = useCallback(\n async (options?: { force?: boolean }) => ({\n apiKey: apiKeyRef.current,\n token: await resolveStatewireAuthToken(\n { apiKey: apiKeyRef.current, token: tokenRef.current, getToken: getTokenRef.current },\n { staleAuth: options?.force ?? false },\n ),\n }),\n [],\n );\n const fetchPage = useMemo(\n () => createRestConversationListFetcher({ backendUrl, includePullRequests, getAuth }),\n [backendUrl, includePullRequests, getAuth],\n );\n const trimmedSearch = search.trim();\n // A stable key, so a host passing a fresh array each render does not refetch; JSON keeps each\n // id exact (no separator an id could contain).\n const additionalListAppIdsKey = JSON.stringify(additionalListAppIds ?? []);\n const listedAppIds = useMemo<readonly string[]>(\n () => JSON.parse(additionalListAppIdsKey) as string[],\n [additionalListAppIdsKey],\n );\n const filter = useMemo<ConversationListFilter>(\n () => ({\n ...(appId ? { appId } : {}),\n ...(listedAppIds.length > 0 ? { appIds: listedAppIds } : {}),\n ...(trimmedSearch ? { search: trimmedSearch } : {}),\n }),\n [appId, listedAppIds, trimmedSearch],\n );\n // Pull-request opt-in changes what a cached row holds, so it is part of\n // the cache identity.\n const listScopeKey = `${backendUrl}::${principal}::${hasAuth ? 'auth' : 'anon'}::${\n includePullRequests ? 'prs' : 'no-prs'\n }`;\n const list = useConversationList({\n fetchPage,\n filter,\n pageSize: THREAD_LIST_PAGE_SIZE,\n scopeKey: listScopeKey,\n enabled: hasAuth,\n });\n // The pinned sessions are read on their own: `list` holds the newest\n // sessions only, so a session pinned long ago would leave the Pinned group\n // as soon as that many newer sessions exist. Same filter (app scope,\n // search), so a pinned row shows exactly where a listed one would.\n const fetchPinnedPage = useMemo(\n () =>\n createRestConversationListFetcher({\n backendUrl,\n includePullRequests,\n getAuth,\n extraFilters: { favorites_only: true },\n }),\n [backendUrl, includePullRequests, getAuth],\n );\n const pinnedScopeKey = `${listScopeKey}::pinned`;\n const pinnedList = useConversationList({\n fetchPage: fetchPinnedPage,\n filter,\n pageSize: THREAD_LIST_PAGE_SIZE,\n scopeKey: pinnedScopeKey,\n enabled: hasAuth,\n });\n // Every pin is listed, not only the first page of them: the later pinned\n // pages load one after another. Counting requests rather than rows bounds\n // the read even when a page repeats rows the pager already holds.\n const pinnedPagesRequested = useRef({ key: '', count: 0 });\n const pinnedRequestKey = `${pinnedScopeKey}::${JSON.stringify(filter)}`;\n if (pinnedPagesRequested.current.key !== pinnedRequestKey) {\n pinnedPagesRequested.current = { key: pinnedRequestKey, count: 0 };\n }\n const pinnedHasNextPage = pinnedList.hasNextPage;\n const pinnedIsFetchingNextPage = pinnedList.isFetchingNextPage;\n const pinnedNextPageFailed = pinnedList.isFetchNextPageError;\n const pinnedFetchNextPage = pinnedList.fetchNextPage;\n useEffect(() => {\n if (!pinnedHasNextPage || pinnedIsFetchingNextPage || pinnedNextPageFailed) return;\n if (pinnedPagesRequested.current.count >= MAX_PINNED_PAGES - 1) return;\n pinnedPagesRequested.current.count += 1;\n void pinnedFetchNextPage();\n }, [pinnedHasNextPage, pinnedIsFetchingNextPage, pinnedNextPageFailed, pinnedFetchNextPage]);\n const listedConversations = useMemo(\n () =>\n withPinnedConversations({\n conversations: list.conversations,\n pinned: pinnedList.conversations,\n }),\n [list.conversations, pinnedList.conversations],\n );\n\n // Live rows ride the signed-in user's conversation index — the same push\n // channel the conversation-history panel renders from: the host replicates\n // title, lifecycle, attention and creation changes as ops, so a\n // freshly-started thread's server-generated title (or a session flipping to\n // \"needs you\") lands without polling. The index is one instance per\n // `(workspace, user)` in agora's ids, so the pair is resolved from the\n // backend once a principal is known and the index only attaches once both\n // halves are; API-key-only hosts keep the bounded REST poll\n // (`titleSource: 'poll'`).\n const indexHeaders = useCallback<DeepAgentHeadersResolver>(\n (ctx) =>\n resolveStatewireAuthHeaders(\n { apiKey: apiKeyRef.current, token: tokenRef.current, getToken: getTokenRef.current },\n ctx,\n ),\n [],\n );\n // The workspace half is the CREDENTIAL's: a scoped credential (sandbox,\n // preview, Office session) pins its own workspace, so swapping one for\n // another under the same principal must re-resolve the pair. The query is\n // therefore keyed by a credential generation — a counter that advances\n // whenever the host's credentials change — so no secret enters the key.\n const credentialsRef = useRef(credentialShape({ apiKey, token, getToken }));\n const credentialGenerationRef = useRef(0);\n const currentCredentials = credentialShape({ apiKey, token, getToken });\n if (!sameCredentialShape(credentialsRef.current, currentCredentials)) {\n credentialsRef.current = currentCredentials;\n credentialGenerationRef.current += 1;\n }\n const credentialGeneration = credentialGenerationRef.current;\n const identityEnabled = syncUrl !== undefined && principal.length > 0 && hasAuth;\n const identity = useQuery({\n // Keyed by the non-secret principal and credential generation, never the\n // credential itself.\n queryKey: ['athena-sdk', 'account-identity', backendUrl, principal, credentialGeneration],\n queryFn: ({ signal }) =>\n withCredentialRenewal(getAuth, (auth) => fetchAccountIdentity(backendUrl, auth, { signal })),\n enabled: identityEnabled,\n staleTime: Number.POSITIVE_INFINITY,\n });\n const indexWorkspaceId = identity.data?.workspaceId ?? '';\n const indexUserId = identity.data?.userId ?? '';\n const indexEnabled = identityEnabled && indexWorkspaceId.length > 0 && indexUserId.length > 0;\n const index = useConversationIndex({\n baseUrl: syncUrl === undefined ? '' : conversationIndexUrlFromStatewireUrl(syncUrl),\n workspaceId: indexWorkspaceId || 'pending-workspace',\n userId: indexUserId || 'pending-user',\n headers: indexHeaders,\n enabled: indexEnabled,\n });\n // The index parks after repeated credential refusals; re-arm it as soon as\n // a new token exists so live rows resume without a reload.\n useRestartAfterAuthRefusal({\n connection: index.connection,\n reconnect: index.connection.reconnect,\n });\n const indexByThread = useMemo(() => {\n const entries = new Map<string, ConversationIndexEntry>();\n for (const entry of index.entries ?? []) entries.set(entry.thread_id, entry);\n return entries;\n }, [index.entries]);\n\n // Each fetched row folds its pushed index entry through the shared\n // `summaryFromConversationIndexEntry`, so titles, lifecycle and attention\n // update live while the fields the index never carries (pull requests,\n // the last wake) keep their fetched values. A pushed title that normalizes\n // to nothing falls back to the fetched one.\n const mergedConversations = useMemo(\n () => foldConversationIndex(listedConversations, indexByThread),\n [listedConversations, indexByThread],\n );\n // The server's answer to a write is patched into EVERY conversation list\n // cached under this provider (its own page, and any `SessionsPage` /\n // `Inbox` view) — the client is the provider's own, so nothing outside it\n // is touched.\n const queryClient = useQueryClient();\n const patchEveryList = useCallback(\n (threadId: string, patch: Partial<ConversationSummary>) =>\n patchConversationListCaches(queryClient, {\n match: (item) => item.threadId === threadId,\n patch,\n }),\n [queryClient],\n );\n // A pin toggle sits over the fold: the row flips at once, and a pushed\n // index entry that predates the write cannot clobber it. The override drops\n // once both sources report the toggled value.\n const pinnedListRefresh = pinnedList.refresh;\n const favorite = useThreadFavorite({\n backendUrl,\n principalId: principal,\n getAuth,\n rows: mergedConversations,\n onServerTruth: ({ threadId, isFavorited }) => {\n patchEveryList(threadId, { isFavorited });\n // A newly pinned session joins the pinned read, so it stays listed\n // once newer sessions push it out of `list`.\n if (isFavorited) void pinnedListRefresh();\n },\n });\n const favoriteOverrides = favorite.overrides;\n // A rename sits over the fold the same way: the row shows the new title\n // at once, and a pushed index entry that predates the write cannot restore\n // the old one. The override drops once both sources report the new title.\n const rename = useThreadRename({\n backendUrl,\n principalId: principal,\n getAuth,\n rows: mergedConversations,\n onServerTruth: ({ threadId, title }) => patchEveryList(threadId, { title }),\n });\n const titleOverrides = rename.overrides;\n // Read state likewise; the read-state route publishes no index update, so\n // the override is what keeps a stale pushed `is_unread` from flipping the\n // row back until the index's next rebuild agrees.\n const readState = useThreadReadState({\n backendUrl,\n principalId: principal,\n getAuth,\n rows: mergedConversations,\n onServerTruth: ({ threadId, isUnread }) => patchEveryList(threadId, { isUnread }),\n });\n const readOverrides = readState.overrides;\n const readCoveredUpdatedAt = readState.coveredUpdatedAt;\n\n // An archive hides the row at once and keeps it hidden until neither list\n // source reports it any more — the cached page is patched on the server's\n // answer, but the pushed index may still carry the row until the publish\n // that follows the commit lands. A failure drops the id and the row comes\n // back from the still-cached page: no refetch needed. Scoping, the\n // one-write-per-thread guard and the route-missing fallback live in the hook.\n const archive = useThreadArchive({\n backendUrl,\n principalId: principal,\n getAuth,\n onArchived: (threadId) => {\n removeConversationFromListCaches(queryClient, { threadId });\n // The archived session cannot stay the active chat: the server no\n // longer lists it, so the chat moves to a fresh one.\n setActive((current) =>\n current.id === threadId ? { id: mintThreadId(), isNew: true } : current,\n );\n },\n });\n const hiddenThreadIds = archive.hiddenThreadIds;\n const archiveRelease = archive.release;\n // A hidden row is released once both sources have forgotten it, so a\n // session archived elsewhere and then restored is never masked here.\n useEffect(() => {\n if (hiddenThreadIds.size === 0) return;\n const forgotten = [...hiddenThreadIds].filter(\n (threadId) =>\n !mergedConversations.some((conversation) => conversation.threadId === threadId) &&\n !indexByThread.has(threadId),\n );\n if (forgotten.length > 0) archiveRelease(forgotten);\n }, [hiddenThreadIds, mergedConversations, indexByThread, archiveRelease]);\n\n const applyOverrides = useCallback(\n (rows: readonly ConversationSummary[]) =>\n withReadStateOverrides(\n withTitleOverrides(withFavoriteOverrides(rows, favoriteOverrides), titleOverrides),\n readOverrides,\n readCoveredUpdatedAt,\n ).filter((conversation) => !hiddenThreadIds.has(conversation.threadId)),\n [favoriteOverrides, titleOverrides, readOverrides, readCoveredUpdatedAt, hiddenThreadIds],\n );\n const data = useMemo(\n () => applyOverrides(mergedConversations).map(threadSummaryFromConversation),\n [applyOverrides, mergedConversations],\n );\n const isLoading = list.isLoading;\n\n // The active thread's row must survive a search that filters it out of\n // the page (the banner above the chat reads it): the listed row when there\n // is one, else the pushed index entry folded over the last row seen for\n // this thread (so pull requests survive the fold), else that last row.\n const lastActiveConversationRef = useRef<ConversationSummary | null>(null);\n const activeConversation = useMemo<ConversationSummary | null>(() => {\n const listed = mergedConversations.find((conversation) => conversation.threadId === active.id);\n if (listed) return listed;\n const remembered =\n lastActiveConversationRef.current?.threadId === active.id\n ? lastActiveConversationRef.current\n : undefined;\n const entry = indexByThread.get(active.id);\n if (entry) return summaryFromConversationIndexEntry(entry, remembered);\n return remembered ?? null;\n }, [mergedConversations, indexByThread, active.id]);\n if (activeConversation !== null) lastActiveConversationRef.current = activeConversation;\n const activeThread = useMemo(\n () =>\n activeConversation\n ? threadSummaryFromConversation(\n withReadStateOverrides(\n [\n withTitleOverride(\n withFavoriteOverride(activeConversation, favoriteOverrides),\n titleOverrides,\n ),\n ],\n readOverrides,\n readCoveredUpdatedAt,\n )[0] ?? activeConversation,\n )\n : null,\n [activeConversation, favoriteOverrides, titleOverrides, readOverrides, readCoveredUpdatedAt],\n );\n\n const listRefresh = list.refresh;\n const pinnedFetchingRef = useRef(pinnedList.isFetching);\n pinnedFetchingRef.current = pinnedList.isFetching;\n const refresh = useCallback(() => {\n void listRefresh();\n // A pinned read still in flight is left to finish: a refresh restarts it, so the title\n // poll's ticks would never let a pinned read slower than their interval answer.\n if (!pinnedFetchingRef.current) void pinnedListRefresh();\n }, [listRefresh, pinnedListRefresh]);\n const listFetchNextPage = list.fetchNextPage;\n const fetchNextPage = useCallback(() => {\n void listFetchNextPage();\n }, [listFetchNextPage]);\n\n // A thread the index knows — created here on first send, or elsewhere —\n // that the REST page does not list yet refreshes the page so the row\n // appears without the user pressing refresh. Rows for apps this list does\n // not show (its `appId` and `additionalListAppIds`) never trigger one. The\n // refreshes are coalesced, held while the page is already being read, and\n // a refused one is tried again (`useIndexTriggeredRefresh`).\n const listedThreadIds = useMemo(\n () => new Set(list.conversations.map((conversation) => conversation.threadId)),\n [list.conversations],\n );\n const unlistedIndexedThreads = useMemo(\n () =>\n index.entries === null\n ? []\n : indexedThreadsToRefreshFor({\n entries: index.entries,\n listedThreadIds,\n refreshedThreadIds: EMPTY_THREAD_IDS,\n appId,\n additionalAppIds: listedAppIds,\n }),\n [index.entries, listedThreadIds, appId, listedAppIds],\n );\n // The user's other open tabs heard the same push: one of them reads the\n // page and shares it with the rest (`session-list-tab-share.ts`).\n const tabShare = useSessionListTabShare({\n queryClient,\n identity:\n indexWorkspaceId && indexUserId\n ? { backendUrl, userId: indexUserId, workspaceId: indexWorkspaceId }\n : null,\n });\n const listShareKey = sessionListShareKey(\n conversationListQueryKeys.firstPage(\n listScopeKey,\n conversationListFilterKey(filter),\n THREAD_LIST_PAGE_SIZE,\n ),\n );\n useIndexTriggeredRefresh({\n pending: unlistedIndexedThreads,\n refresh: listRefresh,\n // Held only while the page is being read (its first read and that read's\n // own retries included). A first page that was refused does not hold it:\n // the index-triggered refresh is then how the list recovers.\n enabled: hasAuth && index.entries !== null && !list.isFetching,\n tabShare,\n shareKey: listShareKey,\n });\n\n const selectThread = useCallback((threadId: string, options?: { isNew?: boolean }) => {\n if (!threadId) return;\n const isNew = options?.isNew === true;\n setActive((current) => (current.id === threadId ? current : { id: threadId, isNew }));\n }, []);\n\n // The hover prefetcher is one per (backend, principal, credential\n // generation): a credential swap clears the provider's snapshot cache, so\n // the attempt ledger resets with it and a late answer from the previous\n // identity is never remembered under the new one.\n const prefetchScope = `${backendUrl}::${principal}::${credentialGeneration}`;\n const prefetchScopeRef = useRef(prefetchScope);\n prefetchScopeRef.current = prefetchScope;\n const prefetcher = useMemo(() => {\n if (!snapshotCache) return null;\n const scope = prefetchScope;\n return createThreadPrefetcher({\n cache: snapshotCache,\n fetchState: async (threadId) =>\n withCredentialRenewal(getAuth, (auth) =>\n getThreadState(backendUrl, auth, threadId, { skipCache: false }),\n ),\n isCurrent: () => prefetchScopeRef.current === scope,\n });\n }, [snapshotCache, prefetchScope, backendUrl, getAuth]);\n const activeIdRef = useRef(active.id);\n activeIdRef.current = active.id;\n const prefetchThread = useCallback(\n (threadId: string) => {\n if (!prefetcher || !threadId || threadId === activeIdRef.current) return;\n prefetcher.prefetch(threadId);\n },\n [prefetcher],\n );\n\n const newThread = useCallback(() => {\n setActive({ id: mintThreadId(), isNew: true });\n }, []);\n\n // A minted thread stops being \"new\" once the backend lists it — on the\n // fetched page or in the pushed index (a search may keep it off the page):\n // until then `useAthenaThreadManager` reports a null id, so URL sync cannot\n // name it.\n const isPersisted =\n data.some((thread) => thread.id === active.id) || indexByThread.has(active.id);\n useEffect(() => {\n if (isPersisted) {\n setActive((current) => (current.isNew ? { ...current, isNew: false } : current));\n }\n }, [isPersisted]);\n\n const titleSource: AthenaStatewireThreadListState['titleSource'] =\n index.entries !== null ? 'index' : 'poll';\n\n const value = useMemo<AthenaStatewireThreadListState>(\n () => ({\n threads: data,\n isLoading,\n isFetching: list.isFetching,\n hasResolvedFirstPage: list.hasResolvedFirstPage,\n activeThreadId: active.id,\n activeThread,\n isNewChat: active.isNew,\n selectThread,\n prefetchThread,\n newThread,\n refresh,\n search,\n setSearch,\n hasNextPage: list.hasNextPage,\n isFetchingNextPage: list.isFetchingNextPage,\n fetchNextPage,\n setFavorited: favorite.setFavorited,\n favoritePending: favorite.isPending,\n favoritePendingThreadIds: favorite.pendingThreadIds,\n favoriteToggleAvailable: favorite.isAvailable,\n renameThread: rename.rename,\n renamePendingThreadIds: rename.pendingThreadIds,\n renameAvailable: rename.isAvailable,\n archiveThread: archive.archive,\n archivePendingThreadIds: archive.pendingThreadIds,\n archivePending: archive.isPending,\n archiveAvailable: archive.isAvailable,\n setReadState: readState.setReadState,\n readStatePendingThreadIds: readState.pendingThreadIds,\n titleSource,\n }),\n [\n data,\n isLoading,\n list.isFetching,\n list.hasResolvedFirstPage,\n active.id,\n activeThread,\n active.isNew,\n selectThread,\n prefetchThread,\n newThread,\n refresh,\n search,\n list.hasNextPage,\n list.isFetchingNextPage,\n fetchNextPage,\n favorite.setFavorited,\n favorite.isPending,\n favorite.pendingThreadIds,\n favorite.isAvailable,\n rename.rename,\n rename.pendingThreadIds,\n rename.isAvailable,\n archive.archive,\n archive.pendingThreadIds,\n archive.isPending,\n archive.isAvailable,\n readState.setReadState,\n readState.pendingThreadIds,\n titleSource,\n ],\n );\n\n const live = useMemo<AthenaSessionsLiveState>(\n () => ({\n backendUrl,\n getAuth,\n principal,\n hasAuth,\n includePullRequests,\n scopeKey: listScopeKey,\n credentialGeneration,\n baseFilter: {\n ...(appId ? { appId } : {}),\n ...(listedAppIds.length > 0 ? { appIds: listedAppIds } : {}),\n },\n indexByThread,\n indexEntries: index.entries,\n applyOverrides,\n tabShare,\n }),\n [\n backendUrl,\n getAuth,\n principal,\n hasAuth,\n includePullRequests,\n listScopeKey,\n credentialGeneration,\n appId,\n listedAppIds,\n indexByThread,\n index.entries,\n applyOverrides,\n tabShare,\n ],\n );\n\n return (\n <AthenaStatewireThreadListContext.Provider value={value}>\n <AthenaSessionsLiveContext.Provider value={live}>{children}</AthenaSessionsLiveContext.Provider>\n </AthenaStatewireThreadListContext.Provider>\n );\n}\n\n/**\n * Provides the statewire thread-list state, with an SDK-scoped QueryClient so\n * consumers do not need their own QueryClientProvider. `syncUrl` (the\n * `…/v2/threads` mount) locates the conversation index beside it; without it\n * titles fall back to the REST poll. `includePullRequests` (default `true`)\n * asks the list for live pull-request state so `SessionList` can render its\n * chips; a host that never shows them turns it off to spare the server the\n * lookup. `snapshotCache` is the provider's thread snapshot cache — the one\n * the runtime seeds a switched-to thread from — which `prefetchThread` warms;\n * without it prefetching is a no-op.\n */\nexport function AthenaStatewireThreadListProvider({\n backendUrl,\n syncUrl,\n apiKey,\n token,\n getToken,\n appId,\n additionalListAppIds,\n initialThreadId,\n includePullRequests = true,\n snapshotCache,\n offlineList,\n children,\n}: {\n backendUrl: string;\n syncUrl?: string;\n apiKey?: string;\n token?: string | null;\n getToken?: AthenaStatewireTokenGetter;\n appId?: string;\n /** Other client apps whose sessions the list shows too (a union with `appId`). */\n additionalListAppIds?: readonly string[];\n initialThreadId?: string;\n includePullRequests?: boolean;\n snapshotCache?: DeepAgentThreadSnapshotCache;\n /**\n * Keep the list's first pages on this device, sealed (`AthenaProvider`'s `offlineHistory`): they\n * paint at once and read while Athena can't be reached.\n */\n offlineList?: { mirror: ThreadSnapshotMirror; ownerId: string };\n children: ReactNode;\n}) {\n const [queryClient] = useState(() => new QueryClient());\n useOfflineSessionList({ queryClient, offlineList });\n return (\n <QueryClientProvider client={queryClient}>\n <StatewireThreadListState\n backendUrl={backendUrl}\n syncUrl={syncUrl}\n apiKey={apiKey}\n token={token}\n getToken={getToken}\n appId={appId}\n additionalListAppIds={additionalListAppIds}\n initialThreadId={initialThreadId}\n includePullRequests={includePullRequests}\n snapshotCache={snapshotCache}\n >\n {children}\n </StatewireThreadListState>\n </QueryClientProvider>\n );\n}\n\n/** How long after a thread's first message the title poll starts (exported for tests). */\nexport const TITLE_POLL_DELAY_MS = 5000;\nconst TITLE_POLL_INTERVAL_MS = 2000;\nconst TITLE_POLL_MAX_DURATION_MS = 60_000;\n\n/**\n * Fallback for hosts without a live conversation index (`titleSource: 'poll'`,\n * i.e. no signed-in principal): refreshes the thread list until a\n * freshly-started thread's server-generated title appears. Idle while the\n * index carries titles. Mount inside the statewire runtime scope (needs aui\n * state).\n *\n * The thread is read from the statewire scope, not the list: the scope\n * publishes the id together with the client it belongs to, so the messages\n * observed here are always the named thread's (the list's selection moves\n * one commit ahead of the client during a switch).\n */\nexport function StatewireThreadTitlePoller(): null {\n const list = useAthenaStatewireThreadList();\n const activeThreadId = useAthenaStatewireThreadId();\n const hasMessages = useAuiState((s) => (s.optional.thread?.messages.length ?? 0) > 0);\n\n const titleSource = list?.titleSource ?? 'poll';\n const entry = list?.threads.find((thread) => thread.id === activeThreadId);\n const title = entry?.title?.trim() ?? '';\n const hasTitle = title.length > 0 && !isSerializedContentPartsTitle(title);\n\n // The poller stays mounted across thread switches, so the visit is tracked\n // by hand: one poll per visit — no restart when the visited thread's\n // messages or title flip underneath it — and a re-selected thread polls\n // again, as it did when every switch remounted the poller. The record is\n // replaced whenever the attached thread changes, whether or not a poll\n // started for it, so a visit to a thread that needs no poll can never make\n // the next visit to a previous thread look already polled.\n const visitRef = useRef<{ threadId: string; polled: boolean } | null>(null);\n const refreshRef = useRef(list?.refresh);\n refreshRef.current = list?.refresh;\n const fetchingRef = useRef(list?.isFetching ?? false);\n fetchingRef.current = list?.isFetching ?? false;\n\n useEffect(() => {\n if (visitRef.current?.threadId !== activeThreadId) {\n visitRef.current = { threadId: activeThreadId, polled: false };\n }\n const visit = visitRef.current;\n if (!hasMessages || hasTitle || titleSource === 'index') return;\n if (visit.polled) return;\n visit.polled = true;\n\n let intervalId: ReturnType<typeof setInterval> | null = null;\n const startId = setTimeout(() => {\n // A tick while the list's last read is still on its way waits for it: a refresh restarts\n // the read, so against a server slower than the interval no read would ever finish and\n // the new thread would stay unlisted until the poll gives up.\n intervalId = setInterval(() => {\n if (!fetchingRef.current) refreshRef.current?.();\n }, TITLE_POLL_INTERVAL_MS);\n }, TITLE_POLL_DELAY_MS);\n const stopId = setTimeout(() => {\n if (intervalId !== null) clearInterval(intervalId);\n }, TITLE_POLL_MAX_DURATION_MS);\n\n return () => {\n clearTimeout(startId);\n clearTimeout(stopId);\n if (intervalId !== null) clearInterval(intervalId);\n };\n }, [activeThreadId, hasMessages, hasTitle, titleSource]);\n\n return null;\n}\n"],"names":["_a"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA+PO,MAAM,mCACX,cAAqD,IAAI;AAGpD,SAAS,+BAAsE;AACpF,SAAO,WAAW,gCAAgC;AACpD;AAGO,SAAS,uBAA4C;;AAC1D,WAAO,kCAAA,MAAA,mBAAgC,YAAW;AACpD;AAYO,SAAS,2BAA2B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMa;AACX,QAAM,cACJ,SAAS,iBAAiB,SAAS,IAC/B,oBAAI,IAAI,CAAC,GAAI,QAAQ,CAAC,KAAK,IAAI,CAAA,GAAK,GAAG,gBAAgB,CAAC,IACxD;AACN,SAAO,QACJ,OAAO,CAAC,UAAU,CAAC,eAAe,YAAY,IAAI,MAAM,UAAU,EAAE,CAAC,EACrE,IAAI,CAAC,UAAU,MAAM,SAAS,EAC9B,OAAO,CAAC,aAAa,CAAC,gBAAgB,IAAI,QAAQ,KAAK,CAAC,mBAAmB,IAAI,QAAQ,CAAC;AAC7F;AAEO,SAAS,8BACd,cAC8B;AAC9B,SAAO;AAAA,IACL,IAAI,aAAa;AAAA,IACjB,OAAO,qBAAqB,aAAa,KAAK,KAAK;AAAA,IACnD,WAAW,4BAA4B,YAAY;AAAA,IACnD,YAAY,aAAa,cAAc;AAAA,IACvC,iBAAiB,aAAa,mBAAmB;AAAA,IACjD,aAAa,aAAa,eAAe;AAAA,IACzC,UAAU,aAAa;AAAA,IACvB,eAAe,aAAa;AAAA,IAC5B,WAAW,aAAa;AAAA,IACxB,cAAc,aAAa;AAAA,IAC3B,OAAO,aAAa;AAAA,IACpB,aAAa,aAAa;AAAA,IAC1B,cAAc,aAAa,gBAAgB;AAAA,IAC3C,UAAU,aAAa,YAAY;AAAA,IACnC,WAAW,aAAa;AAAA,IACxB,WAAW,aAAa;AAAA,IACxB,SAAS,aAAa;AAAA,IACtB,eAAe,aAAa,iBAAiB;AAAA,IAC7C,iBAAiB,aAAa,mBAAmB;AAAA,IACjD,aAAa,aAAa,eAAe;AAAA,EAAA;AAE7C;AAUO,SAAS,sBACd,eACA,eACuB;AACvB,SAAO,cAAc,IAAI,CAAC,iBAAiB;AACzC,UAAM,QAAQ,cAAc,IAAI,aAAa,QAAQ;AACrD,QAAI,CAAC,MAAO,QAAO;AACnB,UAAM,SAAS,kCAAkC,OAAO,YAAY;AACpE,WAAO,qBAAqB,OAAO,KAAK,MAAM,SAC1C,EAAE,GAAG,QAAQ,OAAO,aAAa,MAAA,IACjC;AAAA,EACN,CAAC;AACH;AASO,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AACF,GAGmC;AACjC,QAAM,SAAS,IAAI,IAAI,cAAc,IAAI,CAAC,iBAAiB,aAAa,QAAQ,CAAC;AACjF,QAAM,WAAW,OAAO;AAAA,IACtB,CAAC,iBAAiB,aAAa,gBAAgB,SAAS,CAAC,OAAO,IAAI,aAAa,QAAQ;AAAA,EAAA;AAE3F,SAAO,SAAS,WAAW,IAAI,gBAAgB,CAAC,GAAG,eAAe,GAAG,QAAQ;AAC/E;AAkCO,MAAM,4BAA4B,cAA8C,IAAI;AAO3F,MAAM,wBAAwB;AAC9B,MAAM,uCAA4C,IAAA;AAElD,MAAM,mBAAmB;AAEzB,SAAS,yBAAyB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAYG;;AACD,QAAM,CAAC,QAAQ,SAAS,IAAI;AAAA,IAAyC,MACnE,kBACI,EAAE,IAAI,iBAAiB,OAAO,MAAA,IAC9B,EAAE,IAAI,aAAA,GAAgB,OAAO,KAAA;AAAA,EAAK;AAExC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,EAAE;AAEvC,QAAM,WAAW,OAAO,KAAK;AAC7B,WAAS,UAAU;AACnB,QAAM,YAAY,OAAO,MAAM;AAC/B,YAAU,UAAU;AACpB,QAAM,cAAc,OAAO,QAAQ;AACnC,cAAY,UAAU;AAQtB,QAAM,cAAc,WAAW,iBAAiB;AAChD,QAAM,cAAY,gDAAa,SAAb,mBAAmB,WAAU;AAG/C,QAAM,UAAU,CAAC,EAAE,UAAU,SAAS;AAKtC,QAAM,UAAU;AAAA,IACd,OAAO,aAAmC;AAAA,MACxC,QAAQ,UAAU;AAAA,MAClB,OAAO,MAAM;AAAA,QACX,EAAE,QAAQ,UAAU,SAAS,OAAO,SAAS,SAAS,UAAU,YAAY,QAAA;AAAA,QAC5E,EAAE,YAAW,mCAAS,UAAS,MAAA;AAAA,MAAM;AAAA,IACvC;AAAA,IAEF,CAAA;AAAA,EAAC;AAEH,QAAM,YAAY;AAAA,IAChB,MAAM,kCAAkC,EAAE,YAAY,qBAAqB,SAAS;AAAA,IACpF,CAAC,YAAY,qBAAqB,OAAO;AAAA,EAAA;AAE3C,QAAM,gBAAgB,OAAO,KAAA;AAG7B,QAAM,0BAA0B,KAAK,UAAU,wBAAwB,CAAA,CAAE;AACzE,QAAM,eAAe;AAAA,IACnB,MAAM,KAAK,MAAM,uBAAuB;AAAA,IACxC,CAAC,uBAAuB;AAAA,EAAA;AAE1B,QAAM,SAAS;AAAA,IACb,OAAO;AAAA,MACL,GAAI,QAAQ,EAAE,MAAA,IAAU,CAAA;AAAA,MACxB,GAAI,aAAa,SAAS,IAAI,EAAE,QAAQ,aAAA,IAAiB,CAAA;AAAA,MACzD,GAAI,gBAAgB,EAAE,QAAQ,kBAAkB,CAAA;AAAA,IAAC;AAAA,IAEnD,CAAC,OAAO,cAAc,aAAa;AAAA,EAAA;AAIrC,QAAM,eAAe,GAAG,UAAU,KAAK,SAAS,KAAK,UAAU,SAAS,MAAM,KAC5E,sBAAsB,QAAQ,QAChC;AACA,QAAM,OAAO,oBAAoB;AAAA,IAC/B;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV,SAAS;AAAA,EAAA,CACV;AAKD,QAAM,kBAAkB;AAAA,IACtB,MACE,kCAAkC;AAAA,MAChC;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAc,EAAE,gBAAgB,KAAA;AAAA,IAAK,CACtC;AAAA,IACH,CAAC,YAAY,qBAAqB,OAAO;AAAA,EAAA;AAE3C,QAAM,iBAAiB,GAAG,YAAY;AACtC,QAAM,aAAa,oBAAoB;AAAA,IACrC,WAAW;AAAA,IACX;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV,SAAS;AAAA,EAAA,CACV;AAID,QAAM,uBAAuB,OAAO,EAAE,KAAK,IAAI,OAAO,GAAG;AACzD,QAAM,mBAAmB,GAAG,cAAc,KAAK,KAAK,UAAU,MAAM,CAAC;AACrE,MAAI,qBAAqB,QAAQ,QAAQ,kBAAkB;AACzD,yBAAqB,UAAU,EAAE,KAAK,kBAAkB,OAAO,EAAA;AAAA,EACjE;AACA,QAAM,oBAAoB,WAAW;AACrC,QAAM,2BAA2B,WAAW;AAC5C,QAAM,uBAAuB,WAAW;AACxC,QAAM,sBAAsB,WAAW;AACvC,YAAU,MAAM;AACd,QAAI,CAAC,qBAAqB,4BAA4B,qBAAsB;AAC5E,QAAI,qBAAqB,QAAQ,SAAS,mBAAmB,EAAG;AAChE,yBAAqB,QAAQ,SAAS;AACtC,SAAK,oBAAA;AAAA,EACP,GAAG,CAAC,mBAAmB,0BAA0B,sBAAsB,mBAAmB,CAAC;AAC3F,QAAM,sBAAsB;AAAA,IAC1B,MACE,wBAAwB;AAAA,MACtB,eAAe,KAAK;AAAA,MACpB,QAAQ,WAAW;AAAA,IAAA,CACpB;AAAA,IACH,CAAC,KAAK,eAAe,WAAW,aAAa;AAAA,EAAA;AAY/C,QAAM,eAAe;AAAA,IACnB,CAAC,QACC;AAAA,MACE,EAAE,QAAQ,UAAU,SAAS,OAAO,SAAS,SAAS,UAAU,YAAY,QAAA;AAAA,MAC5E;AAAA,IAAA;AAAA,IAEJ,CAAA;AAAA,EAAC;AAOH,QAAM,iBAAiB,OAAO,gBAAgB,EAAE,QAAQ,OAAO,SAAA,CAAU,CAAC;AAC1E,QAAM,0BAA0B,OAAO,CAAC;AACxC,QAAM,qBAAqB,gBAAgB,EAAE,QAAQ,OAAO,UAAU;AACtE,MAAI,CAAC,oBAAoB,eAAe,SAAS,kBAAkB,GAAG;AACpE,mBAAe,UAAU;AACzB,4BAAwB,WAAW;AAAA,EACrC;AACA,QAAM,uBAAuB,wBAAwB;AACrD,QAAM,kBAAkB,YAAY,UAAa,UAAU,SAAS,KAAK;AACzE,QAAM,WAAW,SAAS;AAAA;AAAA;AAAA,IAGxB,UAAU,CAAC,cAAc,oBAAoB,YAAY,WAAW,oBAAoB;AAAA,IACxF,SAAS,CAAC,EAAE,aACV,sBAAsB,SAAS,CAAC,SAAS,qBAAqB,YAAY,MAAM,EAAE,OAAA,CAAQ,CAAC;AAAA,IAC7F,SAAS;AAAA,IACT,WAAW,OAAO;AAAA,EAAA,CACnB;AACD,QAAM,qBAAmB,cAAS,SAAT,mBAAe,gBAAe;AACvD,QAAM,gBAAc,cAAS,SAAT,mBAAe,WAAU;AAC7C,QAAM,eAAe,mBAAmB,iBAAiB,SAAS,KAAK,YAAY,SAAS;AAC5F,QAAM,QAAQ,qBAAqB;AAAA,IACjC,SAAS,YAAY,SAAY,KAAK,qCAAqC,OAAO;AAAA,IAClF,aAAa,oBAAoB;AAAA,IACjC,QAAQ,eAAe;AAAA,IACvB,SAAS;AAAA,IACT,SAAS;AAAA,EAAA,CACV;AAGD,6BAA2B;AAAA,IACzB,YAAY,MAAM;AAAA,IAClB,WAAW,MAAM,WAAW;AAAA,EAAA,CAC7B;AACD,QAAM,gBAAgB,QAAQ,MAAM;AAClC,UAAM,8BAAc,IAAA;AACpB,eAAW,SAAS,MAAM,WAAW,CAAA,EAAI,SAAQ,IAAI,MAAM,WAAW,KAAK;AAC3E,WAAO;AAAA,EACT,GAAG,CAAC,MAAM,OAAO,CAAC;AAOlB,QAAM,sBAAsB;AAAA,IAC1B,MAAM,sBAAsB,qBAAqB,aAAa;AAAA,IAC9D,CAAC,qBAAqB,aAAa;AAAA,EAAA;AAMrC,QAAM,cAAc,eAAA;AACpB,QAAM,iBAAiB;AAAA,IACrB,CAAC,UAAkB,UACjB,4BAA4B,aAAa;AAAA,MACvC,OAAO,CAAC,SAAS,KAAK,aAAa;AAAA,MACnC;AAAA,IAAA,CACD;AAAA,IACH,CAAC,WAAW;AAAA,EAAA;AAKd,QAAM,oBAAoB,WAAW;AACrC,QAAM,WAAW,kBAAkB;AAAA,IACjC;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA,MAAM;AAAA,IACN,eAAe,CAAC,EAAE,UAAU,kBAAkB;AAC5C,qBAAe,UAAU,EAAE,aAAa;AAGxC,UAAI,kBAAkB,kBAAA;AAAA,IACxB;AAAA,EAAA,CACD;AACD,QAAM,oBAAoB,SAAS;AAInC,QAAM,SAAS,gBAAgB;AAAA,IAC7B;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA,MAAM;AAAA,IACN,eAAe,CAAC,EAAE,UAAU,MAAA,MAAY,eAAe,UAAU,EAAE,MAAA,CAAO;AAAA,EAAA,CAC3E;AACD,QAAM,iBAAiB,OAAO;AAI9B,QAAM,YAAY,mBAAmB;AAAA,IACnC;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA,MAAM;AAAA,IACN,eAAe,CAAC,EAAE,UAAU,SAAA,MAAe,eAAe,UAAU,EAAE,SAAA,CAAU;AAAA,EAAA,CACjF;AACD,QAAM,gBAAgB,UAAU;AAChC,QAAM,uBAAuB,UAAU;AAQvC,QAAM,UAAU,iBAAiB;AAAA,IAC/B;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA,YAAY,CAAC,aAAa;AACxB,uCAAiC,aAAa,EAAE,UAAU;AAG1D;AAAA,QAAU,CAAC,YACT,QAAQ,OAAO,WAAW,EAAE,IAAI,aAAA,GAAgB,OAAO,SAAS;AAAA,MAAA;AAAA,IAEpE;AAAA,EAAA,CACD;AACD,QAAM,kBAAkB,QAAQ;AAChC,QAAM,iBAAiB,QAAQ;AAG/B,YAAU,MAAM;AACd,QAAI,gBAAgB,SAAS,EAAG;AAChC,UAAM,YAAY,CAAC,GAAG,eAAe,EAAE;AAAA,MACrC,CAAC,aACC,CAAC,oBAAoB,KAAK,CAAC,iBAAiB,aAAa,aAAa,QAAQ,KAC9E,CAAC,cAAc,IAAI,QAAQ;AAAA,IAAA;AAE/B,QAAI,UAAU,SAAS,EAAG,gBAAe,SAAS;AAAA,EACpD,GAAG,CAAC,iBAAiB,qBAAqB,eAAe,cAAc,CAAC;AAExE,QAAM,iBAAiB;AAAA,IACrB,CAAC,SACC;AAAA,MACE,mBAAmB,sBAAsB,MAAM,iBAAiB,GAAG,cAAc;AAAA,MACjF;AAAA,MACA;AAAA,IAAA,EACA,OAAO,CAAC,iBAAiB,CAAC,gBAAgB,IAAI,aAAa,QAAQ,CAAC;AAAA,IACxE,CAAC,mBAAmB,gBAAgB,eAAe,sBAAsB,eAAe;AAAA,EAAA;AAE1F,QAAM,OAAO;AAAA,IACX,MAAM,eAAe,mBAAmB,EAAE,IAAI,6BAA6B;AAAA,IAC3E,CAAC,gBAAgB,mBAAmB;AAAA,EAAA;AAEtC,QAAM,YAAY,KAAK;AAMvB,QAAM,4BAA4B,OAAmC,IAAI;AACzE,QAAM,qBAAqB,QAAoC,MAAM;;AACnE,UAAM,SAAS,oBAAoB,KAAK,CAAC,iBAAiB,aAAa,aAAa,OAAO,EAAE;AAC7F,QAAI,OAAQ,QAAO;AACnB,UAAM,eACJA,MAAA,0BAA0B,YAA1B,gBAAAA,IAAmC,cAAa,OAAO,KACnD,0BAA0B,UAC1B;AACN,UAAM,QAAQ,cAAc,IAAI,OAAO,EAAE;AACzC,QAAI,MAAO,QAAO,kCAAkC,OAAO,UAAU;AACrE,WAAO,cAAc;AAAA,EACvB,GAAG,CAAC,qBAAqB,eAAe,OAAO,EAAE,CAAC;AAClD,MAAI,uBAAuB,KAAM,2BAA0B,UAAU;AACrE,QAAM,eAAe;AAAA,IACnB,MACE,qBACI;AAAA,MACE;AAAA,QACE;AAAA,UACE;AAAA,YACE,qBAAqB,oBAAoB,iBAAiB;AAAA,YAC1D;AAAA,UAAA;AAAA,QACF;AAAA,QAEF;AAAA,QACA;AAAA,MAAA,EACA,CAAC,KAAK;AAAA,IAAA,IAEV;AAAA,IACN,CAAC,oBAAoB,mBAAmB,gBAAgB,eAAe,oBAAoB;AAAA,EAAA;AAG7F,QAAM,cAAc,KAAK;AACzB,QAAM,oBAAoB,OAAO,WAAW,UAAU;AACtD,oBAAkB,UAAU,WAAW;AACvC,QAAM,UAAU,YAAY,MAAM;AAChC,SAAK,YAAA;AAGL,QAAI,CAAC,kBAAkB,QAAS,MAAK,kBAAA;AAAA,EACvC,GAAG,CAAC,aAAa,iBAAiB,CAAC;AACnC,QAAM,oBAAoB,KAAK;AAC/B,QAAM,gBAAgB,YAAY,MAAM;AACtC,SAAK,kBAAA;AAAA,EACP,GAAG,CAAC,iBAAiB,CAAC;AAQtB,QAAM,kBAAkB;AAAA,IACtB,MAAM,IAAI,IAAI,KAAK,cAAc,IAAI,CAAC,iBAAiB,aAAa,QAAQ,CAAC;AAAA,IAC7E,CAAC,KAAK,aAAa;AAAA,EAAA;AAErB,QAAM,yBAAyB;AAAA,IAC7B,MACE,MAAM,YAAY,OACd,CAAA,IACA,2BAA2B;AAAA,MACzB,SAAS,MAAM;AAAA,MACf;AAAA,MACA,oBAAoB;AAAA,MACpB;AAAA,MACA,kBAAkB;AAAA,IAAA,CACnB;AAAA,IACP,CAAC,MAAM,SAAS,iBAAiB,OAAO,YAAY;AAAA,EAAA;AAItD,QAAM,WAAW,uBAAuB;AAAA,IACtC;AAAA,IACA,UACE,oBAAoB,cAChB,EAAE,YAAY,QAAQ,aAAa,aAAa,qBAChD;AAAA,EAAA,CACP;AACD,QAAM,eAAe;AAAA,IACnB,0BAA0B;AAAA,MACxB;AAAA,MACA,0BAA0B,MAAM;AAAA,MAChC;AAAA,IAAA;AAAA,EACF;AAEF,2BAAyB;AAAA,IACvB,SAAS;AAAA,IACT,SAAS;AAAA;AAAA;AAAA;AAAA,IAIT,SAAS,WAAW,MAAM,YAAY,QAAQ,CAAC,KAAK;AAAA,IACpD;AAAA,IACA,UAAU;AAAA,EAAA,CACX;AAED,QAAM,eAAe,YAAY,CAAC,UAAkB,YAAkC;AACpF,QAAI,CAAC,SAAU;AACf,UAAM,SAAQ,mCAAS,WAAU;AACjC,cAAU,CAAC,YAAa,QAAQ,OAAO,WAAW,UAAU,EAAE,IAAI,UAAU,OAAQ;AAAA,EACtF,GAAG,CAAA,CAAE;AAML,QAAM,gBAAgB,GAAG,UAAU,KAAK,SAAS,KAAK,oBAAoB;AAC1E,QAAM,mBAAmB,OAAO,aAAa;AAC7C,mBAAiB,UAAU;AAC3B,QAAM,aAAa,QAAQ,MAAM;AAC/B,QAAI,CAAC,cAAe,QAAO;AAC3B,UAAM,QAAQ;AACd,WAAO,uBAAuB;AAAA,MAC5B,OAAO;AAAA,MACP,YAAY,OAAO,aACjB;AAAA,QAAsB;AAAA,QAAS,CAAC,SAC9B,eAAe,YAAY,MAAM,UAAU,EAAE,WAAW,MAAA,CAAO;AAAA,MAAA;AAAA,MAEnE,WAAW,MAAM,iBAAiB,YAAY;AAAA,IAAA,CAC/C;AAAA,EACH,GAAG,CAAC,eAAe,eAAe,YAAY,OAAO,CAAC;AACtD,QAAM,cAAc,OAAO,OAAO,EAAE;AACpC,cAAY,UAAU,OAAO;AAC7B,QAAM,iBAAiB;AAAA,IACrB,CAAC,aAAqB;AACpB,UAAI,CAAC,cAAc,CAAC,YAAY,aAAa,YAAY,QAAS;AAClE,iBAAW,SAAS,QAAQ;AAAA,IAC9B;AAAA,IACA,CAAC,UAAU;AAAA,EAAA;AAGb,QAAM,YAAY,YAAY,MAAM;AAClC,cAAU,EAAE,IAAI,aAAA,GAAgB,OAAO,MAAM;AAAA,EAC/C,GAAG,CAAA,CAAE;AAML,QAAM,cACJ,KAAK,KAAK,CAAC,WAAW,OAAO,OAAO,OAAO,EAAE,KAAK,cAAc,IAAI,OAAO,EAAE;AAC/E,YAAU,MAAM;AACd,QAAI,aAAa;AACf,gBAAU,CAAC,YAAa,QAAQ,QAAQ,EAAE,GAAG,SAAS,OAAO,MAAA,IAAU,OAAQ;AAAA,IACjF;AAAA,EACF,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,cACJ,MAAM,YAAY,OAAO,UAAU;AAErC,QAAM,QAAQ;AAAA,IACZ,OAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,YAAY,KAAK;AAAA,MACjB,sBAAsB,KAAK;AAAA,MAC3B,gBAAgB,OAAO;AAAA,MACvB;AAAA,MACA,WAAW,OAAO;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa,KAAK;AAAA,MAClB,oBAAoB,KAAK;AAAA,MACzB;AAAA,MACA,cAAc,SAAS;AAAA,MACvB,iBAAiB,SAAS;AAAA,MAC1B,0BAA0B,SAAS;AAAA,MACnC,yBAAyB,SAAS;AAAA,MAClC,cAAc,OAAO;AAAA,MACrB,wBAAwB,OAAO;AAAA,MAC/B,iBAAiB,OAAO;AAAA,MACxB,eAAe,QAAQ;AAAA,MACvB,yBAAyB,QAAQ;AAAA,MACjC,gBAAgB,QAAQ;AAAA,MACxB,kBAAkB,QAAQ;AAAA,MAC1B,cAAc,UAAU;AAAA,MACxB,2BAA2B,UAAU;AAAA,MACrC;AAAA,IAAA;AAAA,IAEF;AAAA,MACE;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,OAAO;AAAA,MACP;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,UAAU;AAAA,MACV;AAAA,IAAA;AAAA,EACF;AAGF,QAAM,OAAO;AAAA,IACX,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV;AAAA,MACA,YAAY;AAAA,QACV,GAAI,QAAQ,EAAE,MAAA,IAAU,CAAA;AAAA,QACxB,GAAI,aAAa,SAAS,IAAI,EAAE,QAAQ,aAAA,IAAiB,CAAA;AAAA,MAAC;AAAA,MAE5D;AAAA,MACA,cAAc,MAAM;AAAA,MACpB;AAAA,MACA;AAAA,IAAA;AAAA,IAEF;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IAAA;AAAA,EACF;AAGF,SACE,oBAAC,iCAAiC,UAAjC,EAA0C,OACzC,UAAA,oBAAC,0BAA0B,UAA1B,EAAmC,OAAO,MAAO,SAAA,CAAS,EAAA,CAC7D;AAEJ;AAaO,SAAS,kCAAkC;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,sBAAsB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AACF,GAkBG;AACD,QAAM,CAAC,WAAW,IAAI,SAAS,MAAM,IAAI,aAAa;AACtD,wBAAsB,EAAE,aAAa,aAAa;AAClD,SACE,oBAAC,qBAAA,EAAoB,QAAQ,aAC3B,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MAEC;AAAA,IAAA;AAAA,EAAA,GAEL;AAEJ;AAGO,MAAM,sBAAsB;AACnC,MAAM,yBAAyB;AAC/B,MAAM,6BAA6B;AAc5B,SAAS,6BAAmC;;AACjD,QAAM,OAAO,6BAAA;AACb,QAAM,iBAAiB,2BAAA;AACvB,QAAM,cAAc,YAAY,CAAC,MAAA;;AAAO,cAAAA,MAAA,EAAE,SAAS,WAAX,gBAAAA,IAAmB,SAAS,WAAU,KAAK;AAAA,GAAC;AAEpF,QAAM,eAAc,6BAAM,gBAAe;AACzC,QAAM,QAAQ,6BAAM,QAAQ,KAAK,CAAC,WAAW,OAAO,OAAO;AAC3D,QAAM,UAAQ,oCAAO,UAAP,mBAAc,WAAU;AACtC,QAAM,WAAW,MAAM,SAAS,KAAK,CAAC,8BAA8B,KAAK;AASzE,QAAM,WAAW,OAAqD,IAAI;AAC1E,QAAM,aAAa,OAAO,6BAAM,OAAO;AACvC,aAAW,UAAU,6BAAM;AAC3B,QAAM,cAAc,QAAO,6BAAM,eAAc,KAAK;AACpD,cAAY,WAAU,6BAAM,eAAc;AAE1C,YAAU,MAAM;;AACd,UAAIA,MAAA,SAAS,YAAT,gBAAAA,IAAkB,cAAa,gBAAgB;AACjD,eAAS,UAAU,EAAE,UAAU,gBAAgB,QAAQ,MAAA;AAAA,IACzD;AACA,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,eAAe,YAAY,gBAAgB,QAAS;AACzD,QAAI,MAAM,OAAQ;AAClB,UAAM,SAAS;AAEf,QAAI,aAAoD;AACxD,UAAM,UAAU,WAAW,MAAM;AAI/B,mBAAa,YAAY,MAAM;;AAC7B,YAAI,CAAC,YAAY,QAAS,EAAAA,MAAA,WAAW,YAAX,gBAAAA,IAAA;AAAA,MAC5B,GAAG,sBAAsB;AAAA,IAC3B,GAAG,mBAAmB;AACtB,UAAM,SAAS,WAAW,MAAM;AAC9B,UAAI,eAAe,KAAM,eAAc,UAAU;AAAA,IACnD,GAAG,0BAA0B;AAE7B,WAAO,MAAM;AACX,mBAAa,OAAO;AACpB,mBAAa,MAAM;AACnB,UAAI,eAAe,KAAM,eAAc,UAAU;AAAA,IACnD;AAAA,EACF,GAAG,CAAC,gBAAgB,aAAa,UAAU,WAAW,CAAC;AAEvD,SAAO;AACT;"}
@@ -633,8 +633,6 @@ const RULES = {
633
633
  web_search: webSearchRule,
634
634
  search_web: webSearchRule,
635
635
  exa_search: webSearchRule,
636
- perplexity_search: webSearchRule,
637
- firecrawl_search: webSearchRule,
638
636
  browse: browseRule,
639
637
  web_fetch: browseRule,
640
638
  fetch_url: browseRule,
@@ -1 +1 @@
1
- {"version":3,"file":"tool-summary.cjs","sources":["../../src/tools/tool-summary.ts"],"sourcesContent":["/**\n * One line per tool call, in the words a person would use: \"Ran git diff\n * --stat\", \"Read live_audio.py\", \"Edited upstream.py\" with \"+4 −1\", \"Found 3\n * assets for \"workstation\"\", \"jevin-box is running\". The single summary the\n * SDK's activity timeline, its grouped transcript and its tool cards read, so\n * a step cannot be the literal command on one surface and \"Ran a command\" on\n * another.\n *\n * Tools without a rule here read the shared display registry\n * (`getToolDisplayMeta`); a tool neither knows falls back to the host's\n * catalog name, else a sentence-cased name — never the Title Cased wire id\n * (\"Computer Asset Ensure Sandbox\").\n *\n * Wording follows the Olympus deep-agent cards where they exist\n * (`olympus/src/components/athena-deep-agent/sandbox-tool-cards.tsx`: \"Read\",\n * \"Wrote\", \"Edited\", the command's first line, \"+N -M\" line counts) and the\n * computer toolkit cards (\"Sandbox is running\"). Pure and React-free.\n */\nimport {\n describeToolArgs,\n getToolDisplayMeta,\n hasToolDisplayMeta,\n type ToolStatusLabelState,\n toolCompletedLabel,\n toolStatusLabel,\n} from \"@athenaintel/agent-runtime\";\nimport {\n describeTestRecordingMark,\n formatRecordingClock,\n formatTestRecordingCounts,\n parseTestRecordingStopResult,\n} from \"./test-recording-report\";\n\n/** The states a summarized call can be in; the activity timeline's step states. */\nexport type ToolSummaryState = ToolStatusLabelState;\n\nexport interface ToolCallSummary {\n /** The whole line, complete on its own: \"Ran git diff --stat\", \"Read live_audio.py\". Plain text. */\n label: string;\n /** The longer form for a tooltip: the full path, the whole first command line, the repository. */\n detail?: string;\n /** A short chip after the label: \"+4 −1\", \"on jevin-box\", \"exit 1\". */\n meta?: string;\n /** The part of `label` that is literal code (a command, a file name, a pattern), for monospace. */\n code?: string;\n}\n\nexport interface SummarizeToolCallInput {\n toolName: string;\n /** The call's arguments; partial while they stream, `undefined` before any arrive. */\n args?: Record<string, unknown> | undefined;\n /** The tool's result; `undefined` while it runs (the streaming placeholder counts as none). */\n result?: unknown;\n status: ToolSummaryState;\n /** Titles for asset ids the turn already holds (see {@link collectAssetTitles}). */\n assetTitles?: ReadonlyMap<string, string>;\n /** A host catalog name for a tool neither this module nor the registry knows. */\n displayName?: string;\n}\n\n// ─── Text helpers ───────────────────────────────────────────────────────\n\nconst TARGET_MAX = 72;\nconst DETAIL_MAX = 300;\nconst EMPTY_TITLES: ReadonlyMap<string, string> = new Map();\n\nfunction str(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() !== \"\" ? value.trim() : undefined;\n}\n\nfunction int(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isFinite(value)) return Math.trunc(value);\n if (typeof value === \"string\" && /^\\d+$/.test(value.trim())) return Number(value.trim());\n return undefined;\n}\n\n/** One line, capped at `max` characters with an ellipsis. */\nexport function clipLine(text: string, max = TARGET_MAX): string {\n const oneLine = text.replace(/\\s+/g, \" \").trim();\n return oneLine.length > max ? `${oneLine.slice(0, max - 1).trimEnd()}…` : oneLine;\n}\n\nfunction capitalize(text: string): string {\n return text.length > 0 ? `${text[0]!.toUpperCase()}${text.slice(1)}` : text;\n}\n\nfunction lowerFirst(text: string): string {\n return text.length > 0 ? `${text[0]!.toLowerCase()}${text.slice(1)}` : text;\n}\n\nfunction quoted(text: string): string {\n return `\"${clipLine(text, 60)}\"`;\n}\n\nfunction plural(count: number, one: string, many = `${one}s`): string {\n return `${count} ${count === 1 ? one : many}`;\n}\n\n/** Line count of `text`; an empty string is zero lines, a trailing newline adds none. */\nexport function countLines(text: string): number {\n if (!text) return 0;\n let lines = 1;\n for (let index = text.indexOf(\"\\n\"); index !== -1; index = text.indexOf(\"\\n\", index + 1)) {\n if (index < text.length - 1) lines += 1;\n }\n return lines;\n}\n\n/**\n * The tool name in sentence case, for a tool nothing else names:\n * `frobnicate_widgets` → \"Frobnicate widgets\", `mcp__linear__create_issue` →\n * \"Linear create issue\", `CreateWorkbook` → \"Create workbook\".\n */\nexport function humanizeToolName(toolName: string): string {\n const words = toolName\n .replace(/^mcp__/i, \"\")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .split(/[\\s_\\-.:/]+/)\n .filter(Boolean)\n .map((word) => (/^[A-Z0-9]{2,}$/.test(word) ? word : word.toLowerCase()));\n return words.length > 0 ? capitalize(words.join(\" \")) : toolName;\n}\n\n// ─── Paths ──────────────────────────────────────────────────────────────\n\n/** Human label for an agent-workspace path (never surface scratch-file garble). */\nexport function friendlyWorkspacePath(value: unknown): string | undefined {\n if (typeof value !== \"string\" || value.trim() === \"\") return undefined;\n const path = value.trim();\n if (path.includes(\"large_tool_results\")) {\n return \"detailed results from an earlier step\";\n }\n if (path.includes(\"/memory/\")) return \"session memory\";\n if (path.includes(\"/skills/\")) return \"a skill reference\";\n const base = path.split(\"/\").filter(Boolean).pop();\n return base ?? path;\n}\n\n/** A file target: its name in code when it is a real path, plain words for scratch space. */\nfunction fileTarget(path: string | undefined): { text: string; code?: string } | undefined {\n const friendly = friendlyWorkspacePath(path);\n if (!friendly || !path) return undefined;\n const isScratch = friendly !== path.split(\"/\").filter(Boolean).pop();\n const text = clipLine(friendly, 60);\n return isScratch ? { text } : { text, code: text };\n}\n\n/** The last `segments` parts of a directory path (\"…/tests/unit\"), or the path itself. */\nexport function shortPath(path: string, segments = 2): string {\n const parts = path.replace(/\\\\/g, \"/\").split(\"/\").filter(Boolean);\n if (parts.length === 0) return \"/\";\n if (parts.length <= segments) return path.startsWith(\"/\") ? `/${parts.join(\"/\")}` : parts.join(\"/\");\n return `…/${parts.slice(-segments).join(\"/\")}`;\n}\n\n// ─── Execute ────────────────────────────────────────────────────────────\n\n/** A shell command's first non-empty line and how many lines follow it. */\nexport function firstCommandLine(command: string): { firstLine: string; extraLines: number } {\n const lines = command.split(\"\\n\");\n const firstIndex = lines.findIndex((line) => line.trim().length > 0);\n if (firstIndex < 0) return { firstLine: \"\", extraLines: 0 };\n return {\n firstLine: lines[firstIndex]!.trim(),\n extraLines: Math.max(0, countLines(command) - firstIndex - 1),\n };\n}\n\nconst EXIT_CODE_NOTE = /^\\[Command (?:succeeded|failed) with exit code (-?\\d+)\\]$/;\n\nfunction textOf(result: unknown): string | undefined {\n if (typeof result === \"string\") return result;\n if (Array.isArray(result)) {\n const texts = result.flatMap((item) => {\n if (typeof item === \"string\") return [item];\n if (item && typeof item === \"object\" && typeof (item as { text?: unknown }).text === \"string\") {\n return [(item as { text: string }).text];\n }\n return [];\n });\n return texts.length > 0 ? texts.join(\"\\n\") : undefined;\n }\n return undefined;\n}\n\n/**\n * The exit code the deepagents sandbox appends to an `execute` result\n * (`[Command failed with exit code 1]`), or `null` when it reported none.\n */\nexport function executeExitCode(result: unknown): number | null {\n const text = textOf(result);\n if (!text) return null;\n const lines = text.trimEnd().split(\"\\n\");\n for (let index = lines.length - 1; index >= 0 && index >= lines.length - 4; index -= 1) {\n const match = EXIT_CODE_NOTE.exec(lines[index]!.trim());\n if (match) return Number.parseInt(match[1]!, 10);\n }\n return null;\n}\n\n// ─── Line counts ────────────────────────────────────────────────────────\n\nconst lineStatsCache = new WeakMap<object, { added: number; removed: number }>();\n\nfunction lineMultiset(text: string): Map<string, number> {\n const counts = new Map<string, number>();\n if (!text) return counts;\n const lines = text.replace(/\\r\\n/g, \"\\n\").split(\"\\n\");\n if (lines.at(-1) === \"\") lines.pop();\n for (const line of lines) counts.set(line, (counts.get(line) ?? 0) + 1);\n return counts;\n}\n\n/**\n * Lines an `edit_file` replacement adds and removes: lines of `new_string`\n * missing from `old_string` and the reverse, counted with multiplicity. Equal\n * to a line diff's counts unless the edit moves lines around. Cached per\n * arguments object, so a settled call is counted once however often the\n * transcript re-renders.\n */\nexport function editLineStats(args: Record<string, unknown>): { added: number; removed: number } {\n const cached = lineStatsCache.get(args);\n if (cached) return cached;\n const before = lineMultiset(typeof args.old_string === \"string\" ? args.old_string : \"\");\n const after = lineMultiset(typeof args.new_string === \"string\" ? args.new_string : \"\");\n let added = 0;\n let removed = 0;\n for (const [line, count] of after) added += Math.max(0, count - (before.get(line) ?? 0));\n for (const [line, count] of before) removed += Math.max(0, count - (after.get(line) ?? 0));\n const stats = { added, removed };\n lineStatsCache.set(args, stats);\n return stats;\n}\n\n// ─── Results ────────────────────────────────────────────────────────────\n\nconst MAX_PARSE_LENGTH = 256 * 1024;\n\n/** A JSON result as an object, or `null` (plain text, too large, or not JSON). */\nfunction parseResult(result: unknown): Record<string, unknown> | unknown[] | null {\n if (result && typeof result === \"object\") return result as Record<string, unknown>;\n if (typeof result !== \"string\" || result.length > MAX_PARSE_LENGTH) return null;\n const trimmed = result.trimStart();\n if (!trimmed.startsWith(\"{\") && !trimmed.startsWith(\"[\")) return null;\n try {\n const parsed: unknown = JSON.parse(trimmed);\n return parsed && typeof parsed === \"object\" ? (parsed as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n}\n\nfunction resultRecord(result: unknown): Record<string, unknown> | null {\n const parsed = parseResult(result);\n return parsed && !Array.isArray(parsed) ? parsed : null;\n}\n\n/** A pull request or issue number off a result (`number`, else the `/pull/N` of its URL). */\nfunction resultNumber(result: unknown): number | undefined {\n const record = resultRecord(result);\n if (!record) return undefined;\n const direct = int(record.number) ?? int(record.pull_number) ?? int(record.issue_number);\n if (direct !== undefined) return direct;\n const url = str(record.html_url) ?? str(record.url);\n const match = url ? /\\/(?:pull|issues)\\/(\\d+)/.exec(url) : null;\n return match ? Number(match[1]) : undefined;\n}\n\n// ─── Asset titles ───────────────────────────────────────────────────────\n\nconst ASSET_ID = /^asset_[A-Za-z0-9-]{6,}$/;\nconst MAX_WALK_NODES = 2000;\ntype TitleEntries = readonly (readonly [string, string])[];\n/** Titles found in an object result, keyed by the result itself. */\nconst titlesByResultObject = new WeakMap<object, TitleEntries>();\n/** Titles found in a string result, keyed by the part that holds it and checked against the string. */\nconst titlesByStringPart = new WeakMap<object, { result: string; entries: TitleEntries }>();\n\nfunction titlesIn(value: unknown): (readonly [string, string])[] {\n const found: (readonly [string, string])[] = [];\n const stack: { node: unknown; depth: number }[] = [{ node: value, depth: 0 }];\n let visited = 0;\n while (stack.length > 0 && visited < MAX_WALK_NODES) {\n const { node, depth } = stack.pop()!;\n visited += 1;\n if (!node || typeof node !== \"object\" || depth > 4) continue;\n if (Array.isArray(node)) {\n for (const child of node) stack.push({ node: child, depth: depth + 1 });\n continue;\n }\n const record = node as Record<string, unknown>;\n const id = str(record.asset_id) ?? str(record.id) ?? str(record.assetId);\n const title = str(record.title) ?? str(record.name);\n if (id && title && ASSET_ID.test(id)) found.push([id, title] as const);\n for (const child of Object.values(record)) {\n if (child && typeof child === \"object\") stack.push({ node: child, depth: depth + 1 });\n }\n }\n return found;\n}\n\n/** The part shape {@link collectAssetTitles} reads; assistant-ui's tool-call parts satisfy it. */\nexport type AssetTitleSourcePart = {\n readonly type: string;\n readonly toolCallId?: string;\n readonly result?: unknown;\n};\n\n/**\n * The titles one result names, parsed once per result: an object result is\n * cached by its own identity, a string result by the part holding it and only\n * while that part still holds the same string, so a replaced result (however\n * long) is always read afresh. Weakly held: nothing outlives the transcript.\n */\nfunction resultTitles(part: AssetTitleSourcePart): TitleEntries {\n const result = part.result;\n if (result !== null && typeof result === \"object\") {\n let entries = titlesByResultObject.get(result);\n if (!entries) {\n entries = titlesIn(result);\n titlesByResultObject.set(result, entries);\n }\n return entries;\n }\n if (typeof result !== \"string\") return [];\n const cached = titlesByStringPart.get(part);\n if (cached && cached.result === result) return cached.entries;\n const entries = titlesIn(parseResult(result));\n titlesByStringPart.set(part, { result, entries });\n return entries;\n}\n\n/**\n * Asset id → title for every asset an earlier result in the turn named\n * (`search_assets` hits, a created asset, a status read), so a later call that\n * targets one by id can say which machine or document it acted on. Only data\n * the turn already holds; nothing is fetched.\n */\nexport function collectAssetTitles(parts: readonly AssetTitleSourcePart[]): Map<string, string> {\n const titles = new Map<string, string>();\n for (const part of parts) {\n if (part.type !== \"tool-call\" || part.result === undefined) continue;\n for (const [id, title] of resultTitles(part)) titles.set(id, title);\n }\n return titles;\n}\n\nfunction assetIdOf(value: unknown): string | undefined {\n const raw = str(value);\n if (!raw) return undefined;\n const bare = raw.split(/[?#]/, 1)[0]!;\n return bare.startsWith(\"asset_\") ? bare : undefined;\n}\n\n// ─── Phrases ────────────────────────────────────────────────────────────\n\n/**\n * How one call reads in every state: the verb in three forms (\"Running\" /\n * \"Ran\" / \"run\") and its object. `active` / `done` replace the composed\n * running / completed sentence when a tool words its outcome differently\n * (\"Found 3 assets for …\", \"jevin-box is running\").\n */\ninterface Phrase {\n verb: readonly [active: string, done: string, base: string];\n /** The object of the verb (\"git status\", \"live_audio.py\", \"a command\"). */\n object: string;\n code?: string;\n detail?: string;\n meta?: readonly (string | false | null | undefined)[];\n active?: string;\n done?: string;\n}\n\nconst RUN = [\"Running\", \"Ran\", \"run\"] as const;\nconst READ = [\"Reading\", \"Read\", \"read\"] as const;\n\nfunction summaryFromPhrase(phrase: Phrase, status: ToolSummaryState): ToolCallSummary {\n const [active, done, base] = phrase.verb;\n const imperative = `${capitalize(base)} ${phrase.object}`;\n let label: string;\n switch (status) {\n case \"running\":\n label = phrase.active ?? `${active} ${phrase.object}`;\n break;\n case \"complete\":\n label = phrase.done ?? `${done} ${phrase.object}`;\n break;\n case \"error\":\n label = `Couldn't ${base} ${phrase.object}`;\n break;\n case \"rejected\":\n label = `${imperative} — rejected`;\n break;\n case \"cancelled\":\n label = `${imperative} — cancelled`;\n break;\n case \"paused\":\n label = `${imperative} — paused`;\n break;\n case \"waiting\":\n label = \"Waiting for your input\";\n break;\n }\n const meta = (phrase.meta ?? []).filter((value): value is string => Boolean(value)).join(\" · \");\n const detail = status === \"waiting\" ? imperative : phrase.detail;\n return {\n label,\n ...(phrase.code && label.includes(phrase.code) ? { code: phrase.code } : {}),\n ...(detail ? { detail: clipLine(detail, DETAIL_MAX) } : {}),\n ...(meta ? { meta } : {}),\n };\n}\n\ntype Rule = (input: Required<Pick<SummarizeToolCallInput, \"status\">> & {\n args: Record<string, unknown>;\n result: unknown;\n titles: ReadonlyMap<string, string>;\n}) => Phrase;\n\nfunction machineName(titles: ReadonlyMap<string, string>, value: unknown): string | undefined {\n const id = assetIdOf(value);\n return id ? titles.get(id) : undefined;\n}\n\n/** \"on <machine>\" when a sandbox tool targeted another asset whose title the turn holds. */\nfunction onMachine(titles: ReadonlyMap<string, string>, args: Record<string, unknown>): string | undefined {\n const machine = machineName(titles, args.sandbox_asset_id);\n return machine ? `on ${machine}` : undefined;\n}\n\nconst COMPUTER_STATES: Record<string, string> = {\n started: \"running\",\n running: \"running\",\n stopped: \"stopped\",\n starting: \"starting\",\n stopping: \"stopping\",\n paused: \"paused\",\n error: \"in an error state\",\n};\n\nfunction computerStateWord(state: string): string {\n return COMPUTER_STATES[state.toLowerCase()] ?? humanizeToolName(state).toLowerCase();\n}\n\nconst executeRule: Rule = ({ args, result, titles }) => {\n const command = typeof args.command === \"string\" ? args.command : \"\";\n const { firstLine, extraLines } = firstCommandLine(command);\n const target = firstLine ? clipLine(firstLine) : undefined;\n const exitCode = executeExitCode(result);\n return {\n verb: RUN,\n object: target ?? \"a command\",\n code: target,\n detail: firstLine\n ? extraLines > 0\n ? `${firstLine} (+${plural(extraLines, \"more line\")})`\n : firstLine\n : undefined,\n meta: [onMachine(titles, args), exitCode !== null && exitCode !== 0 && `exit ${exitCode}`],\n };\n};\n\nfunction filePathOf(args: Record<string, unknown>): string | undefined {\n return str(args.file_path) ?? str(args.path);\n}\n\nconst readFileRule: Rule = ({ args, titles }) => {\n const path = filePathOf(args);\n const target = fileTarget(path);\n const offset = int(args.offset) ?? 0;\n const limit = int(args.limit);\n return {\n verb: READ,\n object: target?.text ?? \"a file\",\n code: target?.code,\n detail: path,\n meta: [\n offset > 0 && (limit ? `lines ${offset + 1}–${offset + limit}` : `from line ${offset + 1}`),\n onMachine(titles, args),\n ],\n };\n};\n\nconst listRule: Rule = ({ args, titles }) => {\n const path = str(args.path) ?? str(args.file_path);\n const target = path ? shortPath(path) : undefined;\n return {\n verb: [\"Listing\", \"Listed\", \"list\"],\n object: target ?? \"the workspace\",\n code: target,\n detail: path,\n meta: [onMachine(titles, args)],\n };\n};\n\nconst globRule: Rule = ({ args, result, titles }) => {\n const pattern = str(args.pattern) ?? str(args.query);\n const path = str(args.path);\n const object = pattern ? `files matching ${clipLine(pattern, 60)}` : \"files\";\n const none = typeof result === \"string\" && /^no files found/i.test(result.trim());\n return {\n verb: [\"Finding\", \"Found\", \"find\"],\n object,\n code: pattern ? clipLine(pattern, 60) : undefined,\n done: none && pattern ? `No files match ${clipLine(pattern, 60)}` : undefined,\n detail: path ? `${pattern ?? \"\"} in ${path}`.trim() : pattern,\n meta: [path && `in ${shortPath(path)}`, onMachine(titles, args)],\n };\n};\n\nconst grepRule: Rule = ({ args, result, titles }) => {\n const pattern = str(args.pattern) ?? str(args.query);\n const path = str(args.path);\n const code = pattern ? clipLine(pattern, 60) : undefined;\n const where = path ? `${shortPath(path)} ` : \"\";\n const object = code ? `${where}for ${code}` : path ? shortPath(path) : \"files\";\n const none =\n typeof result === \"string\" && (result.trim() === \"\" || /^no matches/i.test(result.trim()));\n return {\n verb: [\"Searching\", \"Searched\", \"search\"],\n object,\n code,\n done: none && code ? `No matches for ${code}${path ? ` in ${shortPath(path)}` : \"\"}` : undefined,\n detail: path ? `${pattern ?? \"\"} in ${path}`.trim() : pattern,\n meta: [str(args.glob), onMachine(titles, args)],\n };\n};\n\nconst writeFileRule: Rule = ({ args, titles }) => {\n const path = filePathOf(args);\n const target = fileTarget(path);\n const content = typeof args.content === \"string\" ? args.content : \"\";\n const lines = countLines(content);\n return {\n verb: [\"Writing\", \"Wrote\", \"write\"],\n object: target?.text ?? \"a file\",\n code: target?.code,\n detail: path,\n meta: [lines > 0 && `+${lines}`, onMachine(titles, args)],\n };\n};\n\nconst editFileRule: Rule = ({ args, titles }) => {\n const path = filePathOf(args);\n const target = fileTarget(path);\n const hasReplacement = typeof args.old_string === \"string\" || typeof args.new_string === \"string\";\n const { added, removed } = hasReplacement ? editLineStats(args) : { added: 0, removed: 0 };\n return {\n verb: [\"Editing\", \"Edited\", \"edit\"],\n object: target?.text ?? \"a file\",\n code: target?.code,\n detail: path,\n meta: [\n added + removed > 0 && `+${added} −${removed}`,\n args.replace_all === true && \"all occurrences\",\n onMachine(titles, args),\n ],\n };\n};\n\nconst searchAssetsRule: Rule = ({ args, result }) => {\n const query =\n str(args.title_contains_keyword) ??\n str(args.query) ??\n str(args.search_query) ??\n str(args.keyword);\n const types = Array.isArray(args.types)\n ? args.types.map(str).filter((type): type is string => type !== undefined)\n : [];\n const record = resultRecord(result);\n const assets = Array.isArray(record?.assets) ? record.assets : undefined;\n const count = int(record?.total_count) ?? int(record?.returned_count) ?? assets?.length;\n const forQuery = query ? ` for ${quoted(query)}` : \"\";\n const done =\n count === undefined\n ? undefined\n : count === 0\n ? `No assets found${forQuery}`\n : `Found ${plural(count, \"asset\")}${forQuery}`;\n return {\n verb: [\"Searching\", \"Searched\", \"search\"],\n object: query ? `for ${quoted(query)}` : \"assets\",\n done,\n meta: [types.length > 0 && types.map((type) => humanizeToolName(type).toLowerCase()).join(\", \")],\n };\n};\n\nconst ensureSandboxRule: Rule = ({ args, result, titles }) => {\n const machine = machineName(titles, args.computer_asset_id ?? args.asset_id);\n const record = resultRecord(result);\n const state = str(record?.state);\n return {\n verb: [\"Starting\", \"Started\", \"start\"],\n object: machine ?? \"the computer\",\n done: state ? `${machine ?? \"The computer\"} is ${computerStateWord(state)}` : undefined,\n detail: str(record?.detail),\n };\n};\n\nconst computerStatusRule: Rule = ({ args, result, titles }) => {\n const machine = machineName(titles, args.computer_asset_id ?? args.asset_id);\n const state = str(resultRecord(result)?.state);\n return {\n verb: [\"Checking\", \"Checked\", \"check\"],\n object: machine ?? \"the computer\",\n done: state ? `${machine ?? \"The computer\"} is ${computerStateWord(state)}` : undefined,\n };\n};\n\nconst computerDeployRule: Rule = ({ args, result, titles }) => {\n const machine = machineName(titles, args.computer_asset_id ?? args.asset_id);\n const port = int(args.port);\n const url = str(resultRecord(result)?.url);\n return {\n verb: [\"Deploying\", \"Deployed\", \"deploy\"],\n object: port !== undefined ? `port ${port}` : (machine ?? \"the computer\"),\n detail: url,\n meta: [port !== undefined && machine && `on ${machine}`],\n };\n};\n\nconst computerCreateRule: Rule = ({ args }) => {\n const name = str(args.name) ?? str(args.title);\n return {\n verb: [\"Creating\", \"Created\", \"create\"],\n object: name ? `computer ${quoted(name)}` : \"a computer\",\n };\n};\n\nfunction repoOf(args: Record<string, unknown>): string | undefined {\n const owner = str(args.owner);\n const repo = str(args.repo);\n if (owner && repo) return `${owner}/${repo}`;\n return repo ?? str(args.repository);\n}\n\nconst githubPullRule: Rule = ({ args }) => {\n const number = int(args.pull_number) ?? int(args.number);\n const repo = repoOf(args);\n return {\n verb: [\"Viewing\", \"Viewed\", \"view\"],\n object: number !== undefined ? `pull request #${number}` : \"a pull request\",\n detail: repo && number !== undefined ? `${repo}#${number}` : repo,\n meta: [repo],\n };\n};\n\nconst githubListPullsRule: Rule = ({ args }) => {\n const repo = repoOf(args);\n const state = str(args.state);\n return {\n verb: [\"Listing\", \"Listed\", \"list\"],\n object: state && state !== \"open\" ? `${state} pull requests` : \"pull requests\",\n meta: [repo, str(args.head) && `head ${str(args.head)}`],\n };\n};\n\nconst githubOpenPullRule: Rule = ({ args, result }) => {\n const title = str(args.title);\n const number = resultNumber(result);\n const head = str(args.head);\n const base = str(args.base);\n return {\n verb: [\"Opening\", \"Opened\", \"open\"],\n object: title ? `pull request ${quoted(title)}` : \"a pull request\",\n done: number !== undefined ? `Opened pull request #${number}` : undefined,\n detail: title,\n meta: [head && base && `${head} → ${base}`, args.draft === true && \"draft\", repoOf(args)],\n };\n};\n\nconst githubCommentRule: Rule = ({ args }) => {\n const number = int(args.issue_number) ?? int(args.pull_number);\n return {\n verb: [\"Commenting on\", \"Commented on\", \"comment on\"],\n object: number !== undefined ? `#${number}` : \"an issue\",\n meta: [repoOf(args)],\n };\n};\n\nconst githubIssueRule: Rule = ({ args, result }) => {\n const title = str(args.title);\n const number = resultNumber(result);\n return {\n verb: [\"Creating\", \"Created\", \"create\"],\n object: title ? `issue ${quoted(title)}` : \"an issue\",\n done: number !== undefined ? `Created issue #${number}` : undefined,\n detail: title,\n meta: [repoOf(args)],\n };\n};\n\nconst githubFileRule: Rule = ({ args }) => {\n const path = str(args.path);\n const target = fileTarget(path);\n const repo = repoOf(args);\n return {\n verb: READ,\n object: target?.text ?? \"a file\",\n code: target?.code,\n detail: repo && path ? `${repo}/${path}` : path,\n meta: [repo, str(args.ref)],\n };\n};\n\nconst githubDirectoryRule: Rule = ({ args }) => {\n const path = str(args.path);\n return {\n verb: [\"Listing\", \"Listed\", \"list\"],\n object: path ? shortPath(path) : \"the repository root\",\n code: path ? shortPath(path) : undefined,\n meta: [repoOf(args)],\n };\n};\n\nconst webSearchRule: Rule = ({ args }) => {\n const query = str(args.query) ?? str(args.search_query) ?? str(args.term);\n return {\n verb: [\"Searching\", \"Searched\", \"search\"],\n object: query ? `the web for ${quoted(query)}` : \"the web\",\n };\n};\n\n/** Host and path of a URL, never its credentials, query string or fragment. */\nfunction pageLabel(value: unknown): string | undefined {\n const raw = str(value);\n if (!raw) return undefined;\n try {\n const url = new URL(raw);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return undefined;\n const path = url.pathname === \"/\" ? \"\" : url.pathname;\n return clipLine(`${url.hostname.replace(/^www\\./, \"\")}${path}`, 60);\n } catch {\n return undefined;\n }\n}\n\nconst browseRule: Rule = ({ args }) => {\n const page = pageLabel(args.url) ?? pageLabel(args.link);\n return { verb: READ, object: page ?? \"a web page\" };\n};\n\nconst readAssetRule: Rule = ({ args, titles }) => {\n const rawIds = Array.isArray(args.asset_ids) ? args.asset_ids : [args.asset_id ?? args.asset_ids];\n const ids = rawIds.map(assetIdOf).filter((id): id is string => id !== undefined);\n const named = ids.map((id) => titles.get(id)).filter((title): title is string => Boolean(title));\n const object =\n ids.length > 1\n ? plural(ids.length, \"asset\")\n : named[0]\n ? clipLine(named[0], 60)\n : \"an asset\";\n return {\n verb: READ,\n object,\n detail: ids.length > 1 && named.length > 0 ? named.join(\", \") : undefined,\n };\n};\n\ntype Todo = { status?: unknown; content?: unknown };\n\nconst writeTodosRule: Rule = ({ args }) => {\n const todos = Array.isArray(args.todos) ? (args.todos as Todo[]) : [];\n const done = todos.filter((todo) => todo?.status === \"completed\").length;\n const current = todos.find((todo) => todo?.status === \"in_progress\");\n return {\n verb: [\"Updating\", \"Updated\", \"update\"],\n object: \"plan\",\n done:\n todos.length === 0 ? \"Cleared the plan\" : `Updated plan (${done}/${todos.length} done)`,\n detail: str(current?.content) ? `Now: ${str(current?.content)}` : undefined,\n };\n};\n\nfunction subagentName(value: unknown): string {\n const name = str(value);\n if (!name || name === \"general-purpose\") return \"a subagent\";\n return humanizeToolName(name).toLowerCase();\n}\n\nconst taskRule: Rule = ({ args }) => {\n const description = str(args.description);\n const who = subagentName(args.subagent_type);\n return {\n verb: [\"Delegating to\", \"Delegated to\", \"delegate to\"],\n object: description ? `${who}: ${clipLine(description, 64)}` : who,\n detail: description,\n };\n};\n\nconst startAsyncTaskRule: Rule = ({ args }) => {\n const description = str(args.description);\n return {\n verb: [\"Starting\", \"Started\", \"start\"],\n object: description ? `background task: ${clipLine(description, 56)}` : \"a background task\",\n detail: description,\n };\n};\n\n// ─── UI Testing toolkit ─────────────────────────────────────────────────\n\nconst testRecordingStartRule: Rule = ({ args }) => {\n const title = str(args.title);\n return {\n verb: [\"Starting\", \"Started\", \"start\"],\n object: title ? `recording ${quoted(title)}` : \"a test recording\",\n };\n};\n\n/** \"Started test: It should …\", \"Passed: …\", \"Failed: …\", \"Noted …\", with where in the recording. */\nconst testRecordingMarkRule: Rule = ({ args, result }) => {\n const mark = describeTestRecordingMark({ args, result });\n const text = mark.text ? clipLine(mark.text, 64) : undefined;\n const at = mark.elapsedS !== null ? `at ${formatRecordingClock(mark.elapsedS)}` : undefined;\n const common = { detail: mark.text || undefined, meta: [at] };\n switch (mark.kind) {\n case \"test\":\n return { ...common, verb: [\"Starting\", \"Started\", \"start\"], object: text ? `test: ${text}` : \"a test\" };\n case \"pass\":\n return {\n ...common,\n verb: [\"Marking\", \"Marked\", \"mark\"],\n object: text ? `pass: ${text}` : \"a pass\",\n done: text ? `Passed: ${text}` : \"Marked a pass\",\n };\n case \"fail\":\n return {\n ...common,\n verb: [\"Marking\", \"Marked\", \"mark\"],\n object: text ? `failure: ${text}` : \"a failure\",\n done: text ? `Failed: ${text}` : \"Marked a failure\",\n };\n case \"note\":\n return { ...common, verb: [\"Noting\", \"Noted\", \"note\"], object: text ?? \"a note\" };\n default:\n return { ...common, verb: [\"Marking\", \"Marked\", \"mark\"], object: \"the recording\" };\n }\n};\n\nconst testRecordingStopRule: Rule = ({ args, result }) => {\n const parsed = parseTestRecordingStopResult(result, { fallbackTitle: str(args.title) });\n const title = parsed.kind === \"saved\" ? parsed.report.title : str(args.title);\n return {\n verb: [\"Saving\", \"Saved\", \"save\"],\n object: title ? `test recording ${quoted(title)}` : \"the test recording\",\n meta: [parsed.kind === \"saved\" ? formatTestRecordingCounts(parsed.report) : undefined],\n };\n};\n\nconst RULES: Record<string, Rule> = {\n execute: executeRule,\n read_file: readFileRule,\n ls: listRule,\n glob: globRule,\n grep: grepRule,\n write_file: writeFileRule,\n edit_file: editFileRule,\n search_assets: searchAssetsRule,\n computer_asset_ensure_sandbox: ensureSandboxRule,\n computer_asset_get_status: computerStatusRule,\n computer_asset_deploy: computerDeployRule,\n computer_asset_create: computerCreateRule,\n github_get_pull_request: githubPullRule,\n github_list_pull_requests: githubListPullsRule,\n github_open_pull_request: githubOpenPullRule,\n github_comment: githubCommentRule,\n github_create_issue: githubIssueRule,\n github_fetch_file: githubFileRule,\n github_list_directory: githubDirectoryRule,\n search: webSearchRule,\n web_search: webSearchRule,\n search_web: webSearchRule,\n exa_search: webSearchRule,\n perplexity_search: webSearchRule,\n firecrawl_search: webSearchRule,\n browse: browseRule,\n web_fetch: browseRule,\n fetch_url: browseRule,\n web_scrape: browseRule,\n scrape_web: browseRule,\n firecrawl_scrape: browseRule,\n read_asset: readAssetRule,\n write_todos: writeTodosRule,\n task: taskRule,\n start_async_task: startAsyncTaskRule,\n test_recording_start: testRecordingStartRule,\n test_recording_mark: testRecordingMarkRule,\n test_recording_stop: testRecordingStopRule,\n};\n\n/** Tool names with a dedicated summary rule (the rest read the shared registry). */\nexport function hasToolSummaryRule(toolName: string): boolean {\n return Object.hasOwn(RULES, toolName);\n}\n\n/**\n * The summary for a tool the shared registry describes: its curated running /\n * completed copy with the call's own one-line description as the chip, and\n * the registry's wording for every other state.\n */\nfunction registrySummary(toolName: string, args: Record<string, unknown> | undefined, status: ToolSummaryState): ToolCallSummary {\n const meta = getToolDisplayMeta(toolName);\n const described = describeToolArgs({ toolName, args });\n const label =\n status === \"running\"\n ? meta.activeLabel\n : status === \"complete\"\n ? (meta.completedLabel ?? toolCompletedLabel(meta.activeLabel))\n : toolStatusLabel(toolName, status);\n return {\n label,\n ...(described && status !== \"waiting\" ? { meta: clipLine(described, 60) } : {}),\n };\n}\n\n/**\n * A tool nothing describes: the host's catalog name (\"Running Frobnicate\",\n * then \"Frobnicate\"), else the sentence-cased tool name (\"Running frobnicate\n * widgets\", then \"Ran frobnicate widgets\").\n */\nfunction unknownToolSummary(toolName: string, status: ToolSummaryState, displayName: string | undefined): ToolCallSummary {\n const name = displayName ?? humanizeToolName(toolName);\n const inSentence = displayName ?? lowerFirst(name);\n switch (status) {\n case \"running\":\n return { label: `Running ${inSentence}` };\n case \"complete\":\n return { label: displayName ?? `Ran ${inSentence}` };\n case \"error\":\n return { label: `${name} failed` };\n case \"rejected\":\n return { label: `${name} — rejected` };\n case \"cancelled\":\n return { label: `${name} — cancelled` };\n case \"paused\":\n return { label: `${name} — paused` };\n case \"waiting\":\n return { label: \"Waiting for your input\", detail: name };\n }\n}\n\n/**\n * The one-line summary of a tool call in its current state. Never throws:\n * missing or partial arguments (a call still streaming) fall back to the\n * verb with a generic object (\"Running a command\").\n */\nexport function summarizeToolCall({\n toolName,\n args,\n result,\n status,\n assetTitles,\n displayName,\n}: SummarizeToolCallInput): ToolCallSummary {\n const rule = RULES[toolName];\n if (rule) {\n return summaryFromPhrase(\n rule({ args: args ?? {}, result, status, titles: assetTitles ?? EMPTY_TITLES }),\n status,\n );\n }\n if (hasToolDisplayMeta(toolName)) return registrySummary(toolName, args, status);\n return unknownToolSummary(toolName, status, displayName);\n}\n"],"names":["entries","describeTestRecordingMark","formatRecordingClock","parseTestRecordingStopResult","formatTestRecordingCounts","getToolDisplayMeta","describeToolArgs","toolCompletedLabel","toolStatusLabel","hasToolDisplayMeta"],"mappings":"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"}
1
+ {"version":3,"file":"tool-summary.cjs","sources":["../../src/tools/tool-summary.ts"],"sourcesContent":["/**\n * One line per tool call, in the words a person would use: \"Ran git diff\n * --stat\", \"Read live_audio.py\", \"Edited upstream.py\" with \"+4 −1\", \"Found 3\n * assets for \"workstation\"\", \"jevin-box is running\". The single summary the\n * SDK's activity timeline, its grouped transcript and its tool cards read, so\n * a step cannot be the literal command on one surface and \"Ran a command\" on\n * another.\n *\n * Tools without a rule here read the shared display registry\n * (`getToolDisplayMeta`); a tool neither knows falls back to the host's\n * catalog name, else a sentence-cased name — never the Title Cased wire id\n * (\"Computer Asset Ensure Sandbox\").\n *\n * Wording follows the Olympus deep-agent cards where they exist\n * (`olympus/src/components/athena-deep-agent/sandbox-tool-cards.tsx`: \"Read\",\n * \"Wrote\", \"Edited\", the command's first line, \"+N -M\" line counts) and the\n * computer toolkit cards (\"Sandbox is running\"). Pure and React-free.\n */\nimport {\n describeToolArgs,\n getToolDisplayMeta,\n hasToolDisplayMeta,\n type ToolStatusLabelState,\n toolCompletedLabel,\n toolStatusLabel,\n} from \"@athenaintel/agent-runtime\";\nimport {\n describeTestRecordingMark,\n formatRecordingClock,\n formatTestRecordingCounts,\n parseTestRecordingStopResult,\n} from \"./test-recording-report\";\n\n/** The states a summarized call can be in; the activity timeline's step states. */\nexport type ToolSummaryState = ToolStatusLabelState;\n\nexport interface ToolCallSummary {\n /** The whole line, complete on its own: \"Ran git diff --stat\", \"Read live_audio.py\". Plain text. */\n label: string;\n /** The longer form for a tooltip: the full path, the whole first command line, the repository. */\n detail?: string;\n /** A short chip after the label: \"+4 −1\", \"on jevin-box\", \"exit 1\". */\n meta?: string;\n /** The part of `label` that is literal code (a command, a file name, a pattern), for monospace. */\n code?: string;\n}\n\nexport interface SummarizeToolCallInput {\n toolName: string;\n /** The call's arguments; partial while they stream, `undefined` before any arrive. */\n args?: Record<string, unknown> | undefined;\n /** The tool's result; `undefined` while it runs (the streaming placeholder counts as none). */\n result?: unknown;\n status: ToolSummaryState;\n /** Titles for asset ids the turn already holds (see {@link collectAssetTitles}). */\n assetTitles?: ReadonlyMap<string, string>;\n /** A host catalog name for a tool neither this module nor the registry knows. */\n displayName?: string;\n}\n\n// ─── Text helpers ───────────────────────────────────────────────────────\n\nconst TARGET_MAX = 72;\nconst DETAIL_MAX = 300;\nconst EMPTY_TITLES: ReadonlyMap<string, string> = new Map();\n\nfunction str(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() !== \"\" ? value.trim() : undefined;\n}\n\nfunction int(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isFinite(value)) return Math.trunc(value);\n if (typeof value === \"string\" && /^\\d+$/.test(value.trim())) return Number(value.trim());\n return undefined;\n}\n\n/** One line, capped at `max` characters with an ellipsis. */\nexport function clipLine(text: string, max = TARGET_MAX): string {\n const oneLine = text.replace(/\\s+/g, \" \").trim();\n return oneLine.length > max ? `${oneLine.slice(0, max - 1).trimEnd()}…` : oneLine;\n}\n\nfunction capitalize(text: string): string {\n return text.length > 0 ? `${text[0]!.toUpperCase()}${text.slice(1)}` : text;\n}\n\nfunction lowerFirst(text: string): string {\n return text.length > 0 ? `${text[0]!.toLowerCase()}${text.slice(1)}` : text;\n}\n\nfunction quoted(text: string): string {\n return `\"${clipLine(text, 60)}\"`;\n}\n\nfunction plural(count: number, one: string, many = `${one}s`): string {\n return `${count} ${count === 1 ? one : many}`;\n}\n\n/** Line count of `text`; an empty string is zero lines, a trailing newline adds none. */\nexport function countLines(text: string): number {\n if (!text) return 0;\n let lines = 1;\n for (let index = text.indexOf(\"\\n\"); index !== -1; index = text.indexOf(\"\\n\", index + 1)) {\n if (index < text.length - 1) lines += 1;\n }\n return lines;\n}\n\n/**\n * The tool name in sentence case, for a tool nothing else names:\n * `frobnicate_widgets` → \"Frobnicate widgets\", `mcp__linear__create_issue` →\n * \"Linear create issue\", `CreateWorkbook` → \"Create workbook\".\n */\nexport function humanizeToolName(toolName: string): string {\n const words = toolName\n .replace(/^mcp__/i, \"\")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .split(/[\\s_\\-.:/]+/)\n .filter(Boolean)\n .map((word) => (/^[A-Z0-9]{2,}$/.test(word) ? word : word.toLowerCase()));\n return words.length > 0 ? capitalize(words.join(\" \")) : toolName;\n}\n\n// ─── Paths ──────────────────────────────────────────────────────────────\n\n/** Human label for an agent-workspace path (never surface scratch-file garble). */\nexport function friendlyWorkspacePath(value: unknown): string | undefined {\n if (typeof value !== \"string\" || value.trim() === \"\") return undefined;\n const path = value.trim();\n if (path.includes(\"large_tool_results\")) {\n return \"detailed results from an earlier step\";\n }\n if (path.includes(\"/memory/\")) return \"session memory\";\n if (path.includes(\"/skills/\")) return \"a skill reference\";\n const base = path.split(\"/\").filter(Boolean).pop();\n return base ?? path;\n}\n\n/** A file target: its name in code when it is a real path, plain words for scratch space. */\nfunction fileTarget(path: string | undefined): { text: string; code?: string } | undefined {\n const friendly = friendlyWorkspacePath(path);\n if (!friendly || !path) return undefined;\n const isScratch = friendly !== path.split(\"/\").filter(Boolean).pop();\n const text = clipLine(friendly, 60);\n return isScratch ? { text } : { text, code: text };\n}\n\n/** The last `segments` parts of a directory path (\"…/tests/unit\"), or the path itself. */\nexport function shortPath(path: string, segments = 2): string {\n const parts = path.replace(/\\\\/g, \"/\").split(\"/\").filter(Boolean);\n if (parts.length === 0) return \"/\";\n if (parts.length <= segments) return path.startsWith(\"/\") ? `/${parts.join(\"/\")}` : parts.join(\"/\");\n return `…/${parts.slice(-segments).join(\"/\")}`;\n}\n\n// ─── Execute ────────────────────────────────────────────────────────────\n\n/** A shell command's first non-empty line and how many lines follow it. */\nexport function firstCommandLine(command: string): { firstLine: string; extraLines: number } {\n const lines = command.split(\"\\n\");\n const firstIndex = lines.findIndex((line) => line.trim().length > 0);\n if (firstIndex < 0) return { firstLine: \"\", extraLines: 0 };\n return {\n firstLine: lines[firstIndex]!.trim(),\n extraLines: Math.max(0, countLines(command) - firstIndex - 1),\n };\n}\n\nconst EXIT_CODE_NOTE = /^\\[Command (?:succeeded|failed) with exit code (-?\\d+)\\]$/;\n\nfunction textOf(result: unknown): string | undefined {\n if (typeof result === \"string\") return result;\n if (Array.isArray(result)) {\n const texts = result.flatMap((item) => {\n if (typeof item === \"string\") return [item];\n if (item && typeof item === \"object\" && typeof (item as { text?: unknown }).text === \"string\") {\n return [(item as { text: string }).text];\n }\n return [];\n });\n return texts.length > 0 ? texts.join(\"\\n\") : undefined;\n }\n return undefined;\n}\n\n/**\n * The exit code the deepagents sandbox appends to an `execute` result\n * (`[Command failed with exit code 1]`), or `null` when it reported none.\n */\nexport function executeExitCode(result: unknown): number | null {\n const text = textOf(result);\n if (!text) return null;\n const lines = text.trimEnd().split(\"\\n\");\n for (let index = lines.length - 1; index >= 0 && index >= lines.length - 4; index -= 1) {\n const match = EXIT_CODE_NOTE.exec(lines[index]!.trim());\n if (match) return Number.parseInt(match[1]!, 10);\n }\n return null;\n}\n\n// ─── Line counts ────────────────────────────────────────────────────────\n\nconst lineStatsCache = new WeakMap<object, { added: number; removed: number }>();\n\nfunction lineMultiset(text: string): Map<string, number> {\n const counts = new Map<string, number>();\n if (!text) return counts;\n const lines = text.replace(/\\r\\n/g, \"\\n\").split(\"\\n\");\n if (lines.at(-1) === \"\") lines.pop();\n for (const line of lines) counts.set(line, (counts.get(line) ?? 0) + 1);\n return counts;\n}\n\n/**\n * Lines an `edit_file` replacement adds and removes: lines of `new_string`\n * missing from `old_string` and the reverse, counted with multiplicity. Equal\n * to a line diff's counts unless the edit moves lines around. Cached per\n * arguments object, so a settled call is counted once however often the\n * transcript re-renders.\n */\nexport function editLineStats(args: Record<string, unknown>): { added: number; removed: number } {\n const cached = lineStatsCache.get(args);\n if (cached) return cached;\n const before = lineMultiset(typeof args.old_string === \"string\" ? args.old_string : \"\");\n const after = lineMultiset(typeof args.new_string === \"string\" ? args.new_string : \"\");\n let added = 0;\n let removed = 0;\n for (const [line, count] of after) added += Math.max(0, count - (before.get(line) ?? 0));\n for (const [line, count] of before) removed += Math.max(0, count - (after.get(line) ?? 0));\n const stats = { added, removed };\n lineStatsCache.set(args, stats);\n return stats;\n}\n\n// ─── Results ────────────────────────────────────────────────────────────\n\nconst MAX_PARSE_LENGTH = 256 * 1024;\n\n/** A JSON result as an object, or `null` (plain text, too large, or not JSON). */\nfunction parseResult(result: unknown): Record<string, unknown> | unknown[] | null {\n if (result && typeof result === \"object\") return result as Record<string, unknown>;\n if (typeof result !== \"string\" || result.length > MAX_PARSE_LENGTH) return null;\n const trimmed = result.trimStart();\n if (!trimmed.startsWith(\"{\") && !trimmed.startsWith(\"[\")) return null;\n try {\n const parsed: unknown = JSON.parse(trimmed);\n return parsed && typeof parsed === \"object\" ? (parsed as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n}\n\nfunction resultRecord(result: unknown): Record<string, unknown> | null {\n const parsed = parseResult(result);\n return parsed && !Array.isArray(parsed) ? parsed : null;\n}\n\n/** A pull request or issue number off a result (`number`, else the `/pull/N` of its URL). */\nfunction resultNumber(result: unknown): number | undefined {\n const record = resultRecord(result);\n if (!record) return undefined;\n const direct = int(record.number) ?? int(record.pull_number) ?? int(record.issue_number);\n if (direct !== undefined) return direct;\n const url = str(record.html_url) ?? str(record.url);\n const match = url ? /\\/(?:pull|issues)\\/(\\d+)/.exec(url) : null;\n return match ? Number(match[1]) : undefined;\n}\n\n// ─── Asset titles ───────────────────────────────────────────────────────\n\nconst ASSET_ID = /^asset_[A-Za-z0-9-]{6,}$/;\nconst MAX_WALK_NODES = 2000;\ntype TitleEntries = readonly (readonly [string, string])[];\n/** Titles found in an object result, keyed by the result itself. */\nconst titlesByResultObject = new WeakMap<object, TitleEntries>();\n/** Titles found in a string result, keyed by the part that holds it and checked against the string. */\nconst titlesByStringPart = new WeakMap<object, { result: string; entries: TitleEntries }>();\n\nfunction titlesIn(value: unknown): (readonly [string, string])[] {\n const found: (readonly [string, string])[] = [];\n const stack: { node: unknown; depth: number }[] = [{ node: value, depth: 0 }];\n let visited = 0;\n while (stack.length > 0 && visited < MAX_WALK_NODES) {\n const { node, depth } = stack.pop()!;\n visited += 1;\n if (!node || typeof node !== \"object\" || depth > 4) continue;\n if (Array.isArray(node)) {\n for (const child of node) stack.push({ node: child, depth: depth + 1 });\n continue;\n }\n const record = node as Record<string, unknown>;\n const id = str(record.asset_id) ?? str(record.id) ?? str(record.assetId);\n const title = str(record.title) ?? str(record.name);\n if (id && title && ASSET_ID.test(id)) found.push([id, title] as const);\n for (const child of Object.values(record)) {\n if (child && typeof child === \"object\") stack.push({ node: child, depth: depth + 1 });\n }\n }\n return found;\n}\n\n/** The part shape {@link collectAssetTitles} reads; assistant-ui's tool-call parts satisfy it. */\nexport type AssetTitleSourcePart = {\n readonly type: string;\n readonly toolCallId?: string;\n readonly result?: unknown;\n};\n\n/**\n * The titles one result names, parsed once per result: an object result is\n * cached by its own identity, a string result by the part holding it and only\n * while that part still holds the same string, so a replaced result (however\n * long) is always read afresh. Weakly held: nothing outlives the transcript.\n */\nfunction resultTitles(part: AssetTitleSourcePart): TitleEntries {\n const result = part.result;\n if (result !== null && typeof result === \"object\") {\n let entries = titlesByResultObject.get(result);\n if (!entries) {\n entries = titlesIn(result);\n titlesByResultObject.set(result, entries);\n }\n return entries;\n }\n if (typeof result !== \"string\") return [];\n const cached = titlesByStringPart.get(part);\n if (cached && cached.result === result) return cached.entries;\n const entries = titlesIn(parseResult(result));\n titlesByStringPart.set(part, { result, entries });\n return entries;\n}\n\n/**\n * Asset id → title for every asset an earlier result in the turn named\n * (`search_assets` hits, a created asset, a status read), so a later call that\n * targets one by id can say which machine or document it acted on. Only data\n * the turn already holds; nothing is fetched.\n */\nexport function collectAssetTitles(parts: readonly AssetTitleSourcePart[]): Map<string, string> {\n const titles = new Map<string, string>();\n for (const part of parts) {\n if (part.type !== \"tool-call\" || part.result === undefined) continue;\n for (const [id, title] of resultTitles(part)) titles.set(id, title);\n }\n return titles;\n}\n\nfunction assetIdOf(value: unknown): string | undefined {\n const raw = str(value);\n if (!raw) return undefined;\n const bare = raw.split(/[?#]/, 1)[0]!;\n return bare.startsWith(\"asset_\") ? bare : undefined;\n}\n\n// ─── Phrases ────────────────────────────────────────────────────────────\n\n/**\n * How one call reads in every state: the verb in three forms (\"Running\" /\n * \"Ran\" / \"run\") and its object. `active` / `done` replace the composed\n * running / completed sentence when a tool words its outcome differently\n * (\"Found 3 assets for …\", \"jevin-box is running\").\n */\ninterface Phrase {\n verb: readonly [active: string, done: string, base: string];\n /** The object of the verb (\"git status\", \"live_audio.py\", \"a command\"). */\n object: string;\n code?: string;\n detail?: string;\n meta?: readonly (string | false | null | undefined)[];\n active?: string;\n done?: string;\n}\n\nconst RUN = [\"Running\", \"Ran\", \"run\"] as const;\nconst READ = [\"Reading\", \"Read\", \"read\"] as const;\n\nfunction summaryFromPhrase(phrase: Phrase, status: ToolSummaryState): ToolCallSummary {\n const [active, done, base] = phrase.verb;\n const imperative = `${capitalize(base)} ${phrase.object}`;\n let label: string;\n switch (status) {\n case \"running\":\n label = phrase.active ?? `${active} ${phrase.object}`;\n break;\n case \"complete\":\n label = phrase.done ?? `${done} ${phrase.object}`;\n break;\n case \"error\":\n label = `Couldn't ${base} ${phrase.object}`;\n break;\n case \"rejected\":\n label = `${imperative} — rejected`;\n break;\n case \"cancelled\":\n label = `${imperative} — cancelled`;\n break;\n case \"paused\":\n label = `${imperative} — paused`;\n break;\n case \"waiting\":\n label = \"Waiting for your input\";\n break;\n }\n const meta = (phrase.meta ?? []).filter((value): value is string => Boolean(value)).join(\" · \");\n const detail = status === \"waiting\" ? imperative : phrase.detail;\n return {\n label,\n ...(phrase.code && label.includes(phrase.code) ? { code: phrase.code } : {}),\n ...(detail ? { detail: clipLine(detail, DETAIL_MAX) } : {}),\n ...(meta ? { meta } : {}),\n };\n}\n\ntype Rule = (input: Required<Pick<SummarizeToolCallInput, \"status\">> & {\n args: Record<string, unknown>;\n result: unknown;\n titles: ReadonlyMap<string, string>;\n}) => Phrase;\n\nfunction machineName(titles: ReadonlyMap<string, string>, value: unknown): string | undefined {\n const id = assetIdOf(value);\n return id ? titles.get(id) : undefined;\n}\n\n/** \"on <machine>\" when a sandbox tool targeted another asset whose title the turn holds. */\nfunction onMachine(titles: ReadonlyMap<string, string>, args: Record<string, unknown>): string | undefined {\n const machine = machineName(titles, args.sandbox_asset_id);\n return machine ? `on ${machine}` : undefined;\n}\n\nconst COMPUTER_STATES: Record<string, string> = {\n started: \"running\",\n running: \"running\",\n stopped: \"stopped\",\n starting: \"starting\",\n stopping: \"stopping\",\n paused: \"paused\",\n error: \"in an error state\",\n};\n\nfunction computerStateWord(state: string): string {\n return COMPUTER_STATES[state.toLowerCase()] ?? humanizeToolName(state).toLowerCase();\n}\n\nconst executeRule: Rule = ({ args, result, titles }) => {\n const command = typeof args.command === \"string\" ? args.command : \"\";\n const { firstLine, extraLines } = firstCommandLine(command);\n const target = firstLine ? clipLine(firstLine) : undefined;\n const exitCode = executeExitCode(result);\n return {\n verb: RUN,\n object: target ?? \"a command\",\n code: target,\n detail: firstLine\n ? extraLines > 0\n ? `${firstLine} (+${plural(extraLines, \"more line\")})`\n : firstLine\n : undefined,\n meta: [onMachine(titles, args), exitCode !== null && exitCode !== 0 && `exit ${exitCode}`],\n };\n};\n\nfunction filePathOf(args: Record<string, unknown>): string | undefined {\n return str(args.file_path) ?? str(args.path);\n}\n\nconst readFileRule: Rule = ({ args, titles }) => {\n const path = filePathOf(args);\n const target = fileTarget(path);\n const offset = int(args.offset) ?? 0;\n const limit = int(args.limit);\n return {\n verb: READ,\n object: target?.text ?? \"a file\",\n code: target?.code,\n detail: path,\n meta: [\n offset > 0 && (limit ? `lines ${offset + 1}–${offset + limit}` : `from line ${offset + 1}`),\n onMachine(titles, args),\n ],\n };\n};\n\nconst listRule: Rule = ({ args, titles }) => {\n const path = str(args.path) ?? str(args.file_path);\n const target = path ? shortPath(path) : undefined;\n return {\n verb: [\"Listing\", \"Listed\", \"list\"],\n object: target ?? \"the workspace\",\n code: target,\n detail: path,\n meta: [onMachine(titles, args)],\n };\n};\n\nconst globRule: Rule = ({ args, result, titles }) => {\n const pattern = str(args.pattern) ?? str(args.query);\n const path = str(args.path);\n const object = pattern ? `files matching ${clipLine(pattern, 60)}` : \"files\";\n const none = typeof result === \"string\" && /^no files found/i.test(result.trim());\n return {\n verb: [\"Finding\", \"Found\", \"find\"],\n object,\n code: pattern ? clipLine(pattern, 60) : undefined,\n done: none && pattern ? `No files match ${clipLine(pattern, 60)}` : undefined,\n detail: path ? `${pattern ?? \"\"} in ${path}`.trim() : pattern,\n meta: [path && `in ${shortPath(path)}`, onMachine(titles, args)],\n };\n};\n\nconst grepRule: Rule = ({ args, result, titles }) => {\n const pattern = str(args.pattern) ?? str(args.query);\n const path = str(args.path);\n const code = pattern ? clipLine(pattern, 60) : undefined;\n const where = path ? `${shortPath(path)} ` : \"\";\n const object = code ? `${where}for ${code}` : path ? shortPath(path) : \"files\";\n const none =\n typeof result === \"string\" && (result.trim() === \"\" || /^no matches/i.test(result.trim()));\n return {\n verb: [\"Searching\", \"Searched\", \"search\"],\n object,\n code,\n done: none && code ? `No matches for ${code}${path ? ` in ${shortPath(path)}` : \"\"}` : undefined,\n detail: path ? `${pattern ?? \"\"} in ${path}`.trim() : pattern,\n meta: [str(args.glob), onMachine(titles, args)],\n };\n};\n\nconst writeFileRule: Rule = ({ args, titles }) => {\n const path = filePathOf(args);\n const target = fileTarget(path);\n const content = typeof args.content === \"string\" ? args.content : \"\";\n const lines = countLines(content);\n return {\n verb: [\"Writing\", \"Wrote\", \"write\"],\n object: target?.text ?? \"a file\",\n code: target?.code,\n detail: path,\n meta: [lines > 0 && `+${lines}`, onMachine(titles, args)],\n };\n};\n\nconst editFileRule: Rule = ({ args, titles }) => {\n const path = filePathOf(args);\n const target = fileTarget(path);\n const hasReplacement = typeof args.old_string === \"string\" || typeof args.new_string === \"string\";\n const { added, removed } = hasReplacement ? editLineStats(args) : { added: 0, removed: 0 };\n return {\n verb: [\"Editing\", \"Edited\", \"edit\"],\n object: target?.text ?? \"a file\",\n code: target?.code,\n detail: path,\n meta: [\n added + removed > 0 && `+${added} −${removed}`,\n args.replace_all === true && \"all occurrences\",\n onMachine(titles, args),\n ],\n };\n};\n\nconst searchAssetsRule: Rule = ({ args, result }) => {\n const query =\n str(args.title_contains_keyword) ??\n str(args.query) ??\n str(args.search_query) ??\n str(args.keyword);\n const types = Array.isArray(args.types)\n ? args.types.map(str).filter((type): type is string => type !== undefined)\n : [];\n const record = resultRecord(result);\n const assets = Array.isArray(record?.assets) ? record.assets : undefined;\n const count = int(record?.total_count) ?? int(record?.returned_count) ?? assets?.length;\n const forQuery = query ? ` for ${quoted(query)}` : \"\";\n const done =\n count === undefined\n ? undefined\n : count === 0\n ? `No assets found${forQuery}`\n : `Found ${plural(count, \"asset\")}${forQuery}`;\n return {\n verb: [\"Searching\", \"Searched\", \"search\"],\n object: query ? `for ${quoted(query)}` : \"assets\",\n done,\n meta: [types.length > 0 && types.map((type) => humanizeToolName(type).toLowerCase()).join(\", \")],\n };\n};\n\nconst ensureSandboxRule: Rule = ({ args, result, titles }) => {\n const machine = machineName(titles, args.computer_asset_id ?? args.asset_id);\n const record = resultRecord(result);\n const state = str(record?.state);\n return {\n verb: [\"Starting\", \"Started\", \"start\"],\n object: machine ?? \"the computer\",\n done: state ? `${machine ?? \"The computer\"} is ${computerStateWord(state)}` : undefined,\n detail: str(record?.detail),\n };\n};\n\nconst computerStatusRule: Rule = ({ args, result, titles }) => {\n const machine = machineName(titles, args.computer_asset_id ?? args.asset_id);\n const state = str(resultRecord(result)?.state);\n return {\n verb: [\"Checking\", \"Checked\", \"check\"],\n object: machine ?? \"the computer\",\n done: state ? `${machine ?? \"The computer\"} is ${computerStateWord(state)}` : undefined,\n };\n};\n\nconst computerDeployRule: Rule = ({ args, result, titles }) => {\n const machine = machineName(titles, args.computer_asset_id ?? args.asset_id);\n const port = int(args.port);\n const url = str(resultRecord(result)?.url);\n return {\n verb: [\"Deploying\", \"Deployed\", \"deploy\"],\n object: port !== undefined ? `port ${port}` : (machine ?? \"the computer\"),\n detail: url,\n meta: [port !== undefined && machine && `on ${machine}`],\n };\n};\n\nconst computerCreateRule: Rule = ({ args }) => {\n const name = str(args.name) ?? str(args.title);\n return {\n verb: [\"Creating\", \"Created\", \"create\"],\n object: name ? `computer ${quoted(name)}` : \"a computer\",\n };\n};\n\nfunction repoOf(args: Record<string, unknown>): string | undefined {\n const owner = str(args.owner);\n const repo = str(args.repo);\n if (owner && repo) return `${owner}/${repo}`;\n return repo ?? str(args.repository);\n}\n\nconst githubPullRule: Rule = ({ args }) => {\n const number = int(args.pull_number) ?? int(args.number);\n const repo = repoOf(args);\n return {\n verb: [\"Viewing\", \"Viewed\", \"view\"],\n object: number !== undefined ? `pull request #${number}` : \"a pull request\",\n detail: repo && number !== undefined ? `${repo}#${number}` : repo,\n meta: [repo],\n };\n};\n\nconst githubListPullsRule: Rule = ({ args }) => {\n const repo = repoOf(args);\n const state = str(args.state);\n return {\n verb: [\"Listing\", \"Listed\", \"list\"],\n object: state && state !== \"open\" ? `${state} pull requests` : \"pull requests\",\n meta: [repo, str(args.head) && `head ${str(args.head)}`],\n };\n};\n\nconst githubOpenPullRule: Rule = ({ args, result }) => {\n const title = str(args.title);\n const number = resultNumber(result);\n const head = str(args.head);\n const base = str(args.base);\n return {\n verb: [\"Opening\", \"Opened\", \"open\"],\n object: title ? `pull request ${quoted(title)}` : \"a pull request\",\n done: number !== undefined ? `Opened pull request #${number}` : undefined,\n detail: title,\n meta: [head && base && `${head} → ${base}`, args.draft === true && \"draft\", repoOf(args)],\n };\n};\n\nconst githubCommentRule: Rule = ({ args }) => {\n const number = int(args.issue_number) ?? int(args.pull_number);\n return {\n verb: [\"Commenting on\", \"Commented on\", \"comment on\"],\n object: number !== undefined ? `#${number}` : \"an issue\",\n meta: [repoOf(args)],\n };\n};\n\nconst githubIssueRule: Rule = ({ args, result }) => {\n const title = str(args.title);\n const number = resultNumber(result);\n return {\n verb: [\"Creating\", \"Created\", \"create\"],\n object: title ? `issue ${quoted(title)}` : \"an issue\",\n done: number !== undefined ? `Created issue #${number}` : undefined,\n detail: title,\n meta: [repoOf(args)],\n };\n};\n\nconst githubFileRule: Rule = ({ args }) => {\n const path = str(args.path);\n const target = fileTarget(path);\n const repo = repoOf(args);\n return {\n verb: READ,\n object: target?.text ?? \"a file\",\n code: target?.code,\n detail: repo && path ? `${repo}/${path}` : path,\n meta: [repo, str(args.ref)],\n };\n};\n\nconst githubDirectoryRule: Rule = ({ args }) => {\n const path = str(args.path);\n return {\n verb: [\"Listing\", \"Listed\", \"list\"],\n object: path ? shortPath(path) : \"the repository root\",\n code: path ? shortPath(path) : undefined,\n meta: [repoOf(args)],\n };\n};\n\nconst webSearchRule: Rule = ({ args }) => {\n const query = str(args.query) ?? str(args.search_query) ?? str(args.term);\n return {\n verb: [\"Searching\", \"Searched\", \"search\"],\n object: query ? `the web for ${quoted(query)}` : \"the web\",\n };\n};\n\n/** Host and path of a URL, never its credentials, query string or fragment. */\nfunction pageLabel(value: unknown): string | undefined {\n const raw = str(value);\n if (!raw) return undefined;\n try {\n const url = new URL(raw);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return undefined;\n const path = url.pathname === \"/\" ? \"\" : url.pathname;\n return clipLine(`${url.hostname.replace(/^www\\./, \"\")}${path}`, 60);\n } catch {\n return undefined;\n }\n}\n\nconst browseRule: Rule = ({ args }) => {\n const page = pageLabel(args.url) ?? pageLabel(args.link);\n return { verb: READ, object: page ?? \"a web page\" };\n};\n\nconst readAssetRule: Rule = ({ args, titles }) => {\n const rawIds = Array.isArray(args.asset_ids) ? args.asset_ids : [args.asset_id ?? args.asset_ids];\n const ids = rawIds.map(assetIdOf).filter((id): id is string => id !== undefined);\n const named = ids.map((id) => titles.get(id)).filter((title): title is string => Boolean(title));\n const object =\n ids.length > 1\n ? plural(ids.length, \"asset\")\n : named[0]\n ? clipLine(named[0], 60)\n : \"an asset\";\n return {\n verb: READ,\n object,\n detail: ids.length > 1 && named.length > 0 ? named.join(\", \") : undefined,\n };\n};\n\ntype Todo = { status?: unknown; content?: unknown };\n\nconst writeTodosRule: Rule = ({ args }) => {\n const todos = Array.isArray(args.todos) ? (args.todos as Todo[]) : [];\n const done = todos.filter((todo) => todo?.status === \"completed\").length;\n const current = todos.find((todo) => todo?.status === \"in_progress\");\n return {\n verb: [\"Updating\", \"Updated\", \"update\"],\n object: \"plan\",\n done:\n todos.length === 0 ? \"Cleared the plan\" : `Updated plan (${done}/${todos.length} done)`,\n detail: str(current?.content) ? `Now: ${str(current?.content)}` : undefined,\n };\n};\n\nfunction subagentName(value: unknown): string {\n const name = str(value);\n if (!name || name === \"general-purpose\") return \"a subagent\";\n return humanizeToolName(name).toLowerCase();\n}\n\nconst taskRule: Rule = ({ args }) => {\n const description = str(args.description);\n const who = subagentName(args.subagent_type);\n return {\n verb: [\"Delegating to\", \"Delegated to\", \"delegate to\"],\n object: description ? `${who}: ${clipLine(description, 64)}` : who,\n detail: description,\n };\n};\n\nconst startAsyncTaskRule: Rule = ({ args }) => {\n const description = str(args.description);\n return {\n verb: [\"Starting\", \"Started\", \"start\"],\n object: description ? `background task: ${clipLine(description, 56)}` : \"a background task\",\n detail: description,\n };\n};\n\n// ─── UI Testing toolkit ─────────────────────────────────────────────────\n\nconst testRecordingStartRule: Rule = ({ args }) => {\n const title = str(args.title);\n return {\n verb: [\"Starting\", \"Started\", \"start\"],\n object: title ? `recording ${quoted(title)}` : \"a test recording\",\n };\n};\n\n/** \"Started test: It should …\", \"Passed: …\", \"Failed: …\", \"Noted …\", with where in the recording. */\nconst testRecordingMarkRule: Rule = ({ args, result }) => {\n const mark = describeTestRecordingMark({ args, result });\n const text = mark.text ? clipLine(mark.text, 64) : undefined;\n const at = mark.elapsedS !== null ? `at ${formatRecordingClock(mark.elapsedS)}` : undefined;\n const common = { detail: mark.text || undefined, meta: [at] };\n switch (mark.kind) {\n case \"test\":\n return { ...common, verb: [\"Starting\", \"Started\", \"start\"], object: text ? `test: ${text}` : \"a test\" };\n case \"pass\":\n return {\n ...common,\n verb: [\"Marking\", \"Marked\", \"mark\"],\n object: text ? `pass: ${text}` : \"a pass\",\n done: text ? `Passed: ${text}` : \"Marked a pass\",\n };\n case \"fail\":\n return {\n ...common,\n verb: [\"Marking\", \"Marked\", \"mark\"],\n object: text ? `failure: ${text}` : \"a failure\",\n done: text ? `Failed: ${text}` : \"Marked a failure\",\n };\n case \"note\":\n return { ...common, verb: [\"Noting\", \"Noted\", \"note\"], object: text ?? \"a note\" };\n default:\n return { ...common, verb: [\"Marking\", \"Marked\", \"mark\"], object: \"the recording\" };\n }\n};\n\nconst testRecordingStopRule: Rule = ({ args, result }) => {\n const parsed = parseTestRecordingStopResult(result, { fallbackTitle: str(args.title) });\n const title = parsed.kind === \"saved\" ? parsed.report.title : str(args.title);\n return {\n verb: [\"Saving\", \"Saved\", \"save\"],\n object: title ? `test recording ${quoted(title)}` : \"the test recording\",\n meta: [parsed.kind === \"saved\" ? formatTestRecordingCounts(parsed.report) : undefined],\n };\n};\n\nconst RULES: Record<string, Rule> = {\n execute: executeRule,\n read_file: readFileRule,\n ls: listRule,\n glob: globRule,\n grep: grepRule,\n write_file: writeFileRule,\n edit_file: editFileRule,\n search_assets: searchAssetsRule,\n computer_asset_ensure_sandbox: ensureSandboxRule,\n computer_asset_get_status: computerStatusRule,\n computer_asset_deploy: computerDeployRule,\n computer_asset_create: computerCreateRule,\n github_get_pull_request: githubPullRule,\n github_list_pull_requests: githubListPullsRule,\n github_open_pull_request: githubOpenPullRule,\n github_comment: githubCommentRule,\n github_create_issue: githubIssueRule,\n github_fetch_file: githubFileRule,\n github_list_directory: githubDirectoryRule,\n search: webSearchRule,\n web_search: webSearchRule,\n search_web: webSearchRule,\n exa_search: webSearchRule,\n browse: browseRule,\n web_fetch: browseRule,\n fetch_url: browseRule,\n web_scrape: browseRule,\n scrape_web: browseRule,\n firecrawl_scrape: browseRule,\n read_asset: readAssetRule,\n write_todos: writeTodosRule,\n task: taskRule,\n start_async_task: startAsyncTaskRule,\n test_recording_start: testRecordingStartRule,\n test_recording_mark: testRecordingMarkRule,\n test_recording_stop: testRecordingStopRule,\n};\n\n/** Tool names with a dedicated summary rule (the rest read the shared registry). */\nexport function hasToolSummaryRule(toolName: string): boolean {\n return Object.hasOwn(RULES, toolName);\n}\n\n/**\n * The summary for a tool the shared registry describes: its curated running /\n * completed copy with the call's own one-line description as the chip, and\n * the registry's wording for every other state.\n */\nfunction registrySummary(toolName: string, args: Record<string, unknown> | undefined, status: ToolSummaryState): ToolCallSummary {\n const meta = getToolDisplayMeta(toolName);\n const described = describeToolArgs({ toolName, args });\n const label =\n status === \"running\"\n ? meta.activeLabel\n : status === \"complete\"\n ? (meta.completedLabel ?? toolCompletedLabel(meta.activeLabel))\n : toolStatusLabel(toolName, status);\n return {\n label,\n ...(described && status !== \"waiting\" ? { meta: clipLine(described, 60) } : {}),\n };\n}\n\n/**\n * A tool nothing describes: the host's catalog name (\"Running Frobnicate\",\n * then \"Frobnicate\"), else the sentence-cased tool name (\"Running frobnicate\n * widgets\", then \"Ran frobnicate widgets\").\n */\nfunction unknownToolSummary(toolName: string, status: ToolSummaryState, displayName: string | undefined): ToolCallSummary {\n const name = displayName ?? humanizeToolName(toolName);\n const inSentence = displayName ?? lowerFirst(name);\n switch (status) {\n case \"running\":\n return { label: `Running ${inSentence}` };\n case \"complete\":\n return { label: displayName ?? `Ran ${inSentence}` };\n case \"error\":\n return { label: `${name} failed` };\n case \"rejected\":\n return { label: `${name} — rejected` };\n case \"cancelled\":\n return { label: `${name} — cancelled` };\n case \"paused\":\n return { label: `${name} — paused` };\n case \"waiting\":\n return { label: \"Waiting for your input\", detail: name };\n }\n}\n\n/**\n * The one-line summary of a tool call in its current state. Never throws:\n * missing or partial arguments (a call still streaming) fall back to the\n * verb with a generic object (\"Running a command\").\n */\nexport function summarizeToolCall({\n toolName,\n args,\n result,\n status,\n assetTitles,\n displayName,\n}: SummarizeToolCallInput): ToolCallSummary {\n const rule = RULES[toolName];\n if (rule) {\n return summaryFromPhrase(\n rule({ args: args ?? {}, result, status, titles: assetTitles ?? EMPTY_TITLES }),\n status,\n );\n }\n if (hasToolDisplayMeta(toolName)) return registrySummary(toolName, args, status);\n return unknownToolSummary(toolName, status, displayName);\n}\n"],"names":["entries","describeTestRecordingMark","formatRecordingClock","parseTestRecordingStopResult","formatTestRecordingCounts","getToolDisplayMeta","describeToolArgs","toolCompletedLabel","toolStatusLabel","hasToolDisplayMeta"],"mappings":";;;;AA8DA,MAAM,aAAa;AACnB,MAAM,aAAa;AACnB,MAAM,mCAAgD,IAAA;AAEtD,SAAS,IAAI,OAAoC;AAC/C,SAAO,OAAO,UAAU,YAAY,MAAM,WAAW,KAAK,MAAM,KAAA,IAAS;AAC3E;AAEA,SAAS,IAAI,OAAoC;AAC/C,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO,KAAK,MAAM,KAAK;AAChF,MAAI,OAAO,UAAU,YAAY,QAAQ,KAAK,MAAM,KAAA,CAAM,EAAG,QAAO,OAAO,MAAM,MAAM;AACvF,SAAO;AACT;AAGO,SAAS,SAAS,MAAc,MAAM,YAAoB;AAC/D,QAAM,UAAU,KAAK,QAAQ,QAAQ,GAAG,EAAE,KAAA;AAC1C,SAAO,QAAQ,SAAS,MAAM,GAAG,QAAQ,MAAM,GAAG,MAAM,CAAC,EAAE,QAAA,CAAS,MAAM;AAC5E;AAEA,SAAS,WAAW,MAAsB;AACxC,SAAO,KAAK,SAAS,IAAI,GAAG,KAAK,CAAC,EAAG,YAAA,CAAa,GAAG,KAAK,MAAM,CAAC,CAAC,KAAK;AACzE;AAEA,SAAS,WAAW,MAAsB;AACxC,SAAO,KAAK,SAAS,IAAI,GAAG,KAAK,CAAC,EAAG,YAAA,CAAa,GAAG,KAAK,MAAM,CAAC,CAAC,KAAK;AACzE;AAEA,SAAS,OAAO,MAAsB;AACpC,SAAO,IAAI,SAAS,MAAM,EAAE,CAAC;AAC/B;AAEA,SAAS,OAAO,OAAe,KAAa,OAAO,GAAG,GAAG,KAAa;AACpE,SAAO,GAAG,KAAK,IAAI,UAAU,IAAI,MAAM,IAAI;AAC7C;AAGO,SAAS,WAAW,MAAsB;AAC/C,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,QAAQ;AACZ,WAAS,QAAQ,KAAK,QAAQ,IAAI,GAAG,UAAU,IAAI,QAAQ,KAAK,QAAQ,MAAM,QAAQ,CAAC,GAAG;AACxF,QAAI,QAAQ,KAAK,SAAS,EAAG,UAAS;AAAA,EACxC;AACA,SAAO;AACT;AAOO,SAAS,iBAAiB,UAA0B;AACzD,QAAM,QAAQ,SACX,QAAQ,WAAW,EAAE,EACrB,QAAQ,sBAAsB,OAAO,EACrC,MAAM,aAAa,EACnB,OAAO,OAAO,EACd,IAAI,CAAC,SAAU,iBAAiB,KAAK,IAAI,IAAI,OAAO,KAAK,YAAA,CAAc;AAC1E,SAAO,MAAM,SAAS,IAAI,WAAW,MAAM,KAAK,GAAG,CAAC,IAAI;AAC1D;AAKO,SAAS,sBAAsB,OAAoC;AACxE,MAAI,OAAO,UAAU,YAAY,MAAM,KAAA,MAAW,GAAI,QAAO;AAC7D,QAAM,OAAO,MAAM,KAAA;AACnB,MAAI,KAAK,SAAS,oBAAoB,GAAG;AACvC,WAAO;AAAA,EACT;AACA,MAAI,KAAK,SAAS,UAAU,EAAG,QAAO;AACtC,MAAI,KAAK,SAAS,UAAU,EAAG,QAAO;AACtC,QAAM,OAAO,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,IAAA;AAC7C,SAAO,QAAQ;AACjB;AAGA,SAAS,WAAW,MAAuE;AACzF,QAAM,WAAW,sBAAsB,IAAI;AAC3C,MAAI,CAAC,YAAY,CAAC,KAAM,QAAO;AAC/B,QAAM,YAAY,aAAa,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,IAAA;AAC/D,QAAM,OAAO,SAAS,UAAU,EAAE;AAClC,SAAO,YAAY,EAAE,KAAA,IAAS,EAAE,MAAM,MAAM,KAAA;AAC9C;AAGO,SAAS,UAAU,MAAc,WAAW,GAAW;AAC5D,QAAM,QAAQ,KAAK,QAAQ,OAAO,GAAG,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO;AAChE,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,MAAI,MAAM,UAAU,SAAU,QAAO,KAAK,WAAW,GAAG,IAAI,IAAI,MAAM,KAAK,GAAG,CAAC,KAAK,MAAM,KAAK,GAAG;AAClG,SAAO,KAAK,MAAM,MAAM,CAAC,QAAQ,EAAE,KAAK,GAAG,CAAC;AAC9C;AAKO,SAAS,iBAAiB,SAA4D;AAC3F,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,QAAM,aAAa,MAAM,UAAU,CAAC,SAAS,KAAK,KAAA,EAAO,SAAS,CAAC;AACnE,MAAI,aAAa,EAAG,QAAO,EAAE,WAAW,IAAI,YAAY,EAAA;AACxD,SAAO;AAAA,IACL,WAAW,MAAM,UAAU,EAAG,KAAA;AAAA,IAC9B,YAAY,KAAK,IAAI,GAAG,WAAW,OAAO,IAAI,aAAa,CAAC;AAAA,EAAA;AAEhE;AAEA,MAAM,iBAAiB;AAEvB,SAAS,OAAO,QAAqC;AACnD,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,MAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,UAAM,QAAQ,OAAO,QAAQ,CAAC,SAAS;AACrC,UAAI,OAAO,SAAS,SAAU,QAAO,CAAC,IAAI;AAC1C,UAAI,QAAQ,OAAO,SAAS,YAAY,OAAQ,KAA4B,SAAS,UAAU;AAC7F,eAAO,CAAE,KAA0B,IAAI;AAAA,MACzC;AACA,aAAO,CAAA;AAAA,IACT,CAAC;AACD,WAAO,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI;AAAA,EAC/C;AACA,SAAO;AACT;AAMO,SAAS,gBAAgB,QAAgC;AAC9D,QAAM,OAAO,OAAO,MAAM;AAC1B,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,KAAK,QAAA,EAAU,MAAM,IAAI;AACvC,WAAS,QAAQ,MAAM,SAAS,GAAG,SAAS,KAAK,SAAS,MAAM,SAAS,GAAG,SAAS,GAAG;AACtF,UAAM,QAAQ,eAAe,KAAK,MAAM,KAAK,EAAG,MAAM;AACtD,QAAI,MAAO,QAAO,OAAO,SAAS,MAAM,CAAC,GAAI,EAAE;AAAA,EACjD;AACA,SAAO;AACT;AAIA,MAAM,qCAAqB,QAAA;AAE3B,SAAS,aAAa,MAAmC;AACvD,QAAM,6BAAa,IAAA;AACnB,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,KAAK,QAAQ,SAAS,IAAI,EAAE,MAAM,IAAI;AACpD,MAAI,MAAM,GAAG,EAAE,MAAM,UAAU,IAAA;AAC/B,aAAW,QAAQ,MAAO,QAAO,IAAI,OAAO,OAAO,IAAI,IAAI,KAAK,KAAK,CAAC;AACtE,SAAO;AACT;AASO,SAAS,cAAc,MAAmE;AAC/F,QAAM,SAAS,eAAe,IAAI,IAAI;AACtC,MAAI,OAAQ,QAAO;AACnB,QAAM,SAAS,aAAa,OAAO,KAAK,eAAe,WAAW,KAAK,aAAa,EAAE;AACtF,QAAM,QAAQ,aAAa,OAAO,KAAK,eAAe,WAAW,KAAK,aAAa,EAAE;AACrF,MAAI,QAAQ;AACZ,MAAI,UAAU;AACd,aAAW,CAAC,MAAM,KAAK,KAAK,MAAO,UAAS,KAAK,IAAI,GAAG,SAAS,OAAO,IAAI,IAAI,KAAK,EAAE;AACvF,aAAW,CAAC,MAAM,KAAK,KAAK,OAAQ,YAAW,KAAK,IAAI,GAAG,SAAS,MAAM,IAAI,IAAI,KAAK,EAAE;AACzF,QAAM,QAAQ,EAAE,OAAO,QAAA;AACvB,iBAAe,IAAI,MAAM,KAAK;AAC9B,SAAO;AACT;AAIA,MAAM,mBAAmB,MAAM;AAG/B,SAAS,YAAY,QAA6D;AAChF,MAAI,UAAU,OAAO,WAAW,SAAU,QAAO;AACjD,MAAI,OAAO,WAAW,YAAY,OAAO,SAAS,iBAAkB,QAAO;AAC3E,QAAM,UAAU,OAAO,UAAA;AACvB,MAAI,CAAC,QAAQ,WAAW,GAAG,KAAK,CAAC,QAAQ,WAAW,GAAG,EAAG,QAAO;AACjE,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,OAAO;AAC1C,WAAO,UAAU,OAAO,WAAW,WAAY,SAAqC;AAAA,EACtF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,aAAa,QAAiD;AACrE,QAAM,SAAS,YAAY,MAAM;AACjC,SAAO,UAAU,CAAC,MAAM,QAAQ,MAAM,IAAI,SAAS;AACrD;AAGA,SAAS,aAAa,QAAqC;AACzD,QAAM,SAAS,aAAa,MAAM;AAClC,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,SAAS,IAAI,OAAO,MAAM,KAAK,IAAI,OAAO,WAAW,KAAK,IAAI,OAAO,YAAY;AACvF,MAAI,WAAW,OAAW,QAAO;AACjC,QAAM,MAAM,IAAI,OAAO,QAAQ,KAAK,IAAI,OAAO,GAAG;AAClD,QAAM,QAAQ,MAAM,2BAA2B,KAAK,GAAG,IAAI;AAC3D,SAAO,QAAQ,OAAO,MAAM,CAAC,CAAC,IAAI;AACpC;AAIA,MAAM,WAAW;AACjB,MAAM,iBAAiB;AAGvB,MAAM,2CAA2B,QAAA;AAEjC,MAAM,yCAAyB,QAAA;AAE/B,SAAS,SAAS,OAA+C;AAC/D,QAAM,QAAuC,CAAA;AAC7C,QAAM,QAA4C,CAAC,EAAE,MAAM,OAAO,OAAO,GAAG;AAC5E,MAAI,UAAU;AACd,SAAO,MAAM,SAAS,KAAK,UAAU,gBAAgB;AACnD,UAAM,EAAE,MAAM,UAAU,MAAM,IAAA;AAC9B,eAAW;AACX,QAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,QAAQ,EAAG;AACpD,QAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,iBAAW,SAAS,KAAM,OAAM,KAAK,EAAE,MAAM,OAAO,OAAO,QAAQ,EAAA,CAAG;AACtE;AAAA,IACF;AACA,UAAM,SAAS;AACf,UAAM,KAAK,IAAI,OAAO,QAAQ,KAAK,IAAI,OAAO,EAAE,KAAK,IAAI,OAAO,OAAO;AACvE,UAAM,QAAQ,IAAI,OAAO,KAAK,KAAK,IAAI,OAAO,IAAI;AAClD,QAAI,MAAM,SAAS,SAAS,KAAK,EAAE,EAAG,OAAM,KAAK,CAAC,IAAI,KAAK,CAAU;AACrE,eAAW,SAAS,OAAO,OAAO,MAAM,GAAG;AACzC,UAAI,SAAS,OAAO,UAAU,SAAU,OAAM,KAAK,EAAE,MAAM,OAAO,OAAO,QAAQ,EAAA,CAAG;AAAA,IACtF;AAAA,EACF;AACA,SAAO;AACT;AAeA,SAAS,aAAa,MAA0C;AAC9D,QAAM,SAAS,KAAK;AACpB,MAAI,WAAW,QAAQ,OAAO,WAAW,UAAU;AACjD,QAAIA,WAAU,qBAAqB,IAAI,MAAM;AAC7C,QAAI,CAACA,UAAS;AACZA,iBAAU,SAAS,MAAM;AACzB,2BAAqB,IAAI,QAAQA,QAAO;AAAA,IAC1C;AACA,WAAOA;AAAAA,EACT;AACA,MAAI,OAAO,WAAW,SAAU,QAAO,CAAA;AACvC,QAAM,SAAS,mBAAmB,IAAI,IAAI;AAC1C,MAAI,UAAU,OAAO,WAAW,eAAe,OAAO;AACtD,QAAM,UAAU,SAAS,YAAY,MAAM,CAAC;AAC5C,qBAAmB,IAAI,MAAM,EAAE,QAAQ,SAAS;AAChD,SAAO;AACT;AAQO,SAAS,mBAAmB,OAA6D;AAC9F,QAAM,6BAAa,IAAA;AACnB,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,SAAS,eAAe,KAAK,WAAW,OAAW;AAC5D,eAAW,CAAC,IAAI,KAAK,KAAK,aAAa,IAAI,EAAG,QAAO,IAAI,IAAI,KAAK;AAAA,EACpE;AACA,SAAO;AACT;AAEA,SAAS,UAAU,OAAoC;AACrD,QAAM,MAAM,IAAI,KAAK;AACrB,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,OAAO,IAAI,MAAM,QAAQ,CAAC,EAAE,CAAC;AACnC,SAAO,KAAK,WAAW,QAAQ,IAAI,OAAO;AAC5C;AAqBA,MAAM,MAAM,CAAC,WAAW,OAAO,KAAK;AACpC,MAAM,OAAO,CAAC,WAAW,QAAQ,MAAM;AAEvC,SAAS,kBAAkB,QAAgB,QAA2C;AACpF,QAAM,CAAC,QAAQ,MAAM,IAAI,IAAI,OAAO;AACpC,QAAM,aAAa,GAAG,WAAW,IAAI,CAAC,IAAI,OAAO,MAAM;AACvD,MAAI;AACJ,UAAQ,QAAA;AAAA,IACN,KAAK;AACH,cAAQ,OAAO,UAAU,GAAG,MAAM,IAAI,OAAO,MAAM;AACnD;AAAA,IACF,KAAK;AACH,cAAQ,OAAO,QAAQ,GAAG,IAAI,IAAI,OAAO,MAAM;AAC/C;AAAA,IACF,KAAK;AACH,cAAQ,YAAY,IAAI,IAAI,OAAO,MAAM;AACzC;AAAA,IACF,KAAK;AACH,cAAQ,GAAG,UAAU;AACrB;AAAA,IACF,KAAK;AACH,cAAQ,GAAG,UAAU;AACrB;AAAA,IACF,KAAK;AACH,cAAQ,GAAG,UAAU;AACrB;AAAA,IACF,KAAK;AACH,cAAQ;AACR;AAAA,EAAA;AAEJ,QAAM,QAAQ,OAAO,QAAQ,CAAA,GAAI,OAAO,CAAC,UAA2B,QAAQ,KAAK,CAAC,EAAE,KAAK,KAAK;AAC9F,QAAM,SAAS,WAAW,YAAY,aAAa,OAAO;AAC1D,SAAO;AAAA,IACL;AAAA,IACA,GAAI,OAAO,QAAQ,MAAM,SAAS,OAAO,IAAI,IAAI,EAAE,MAAM,OAAO,KAAA,IAAS,CAAA;AAAA,IACzE,GAAI,SAAS,EAAE,QAAQ,SAAS,QAAQ,UAAU,EAAA,IAAM,CAAA;AAAA,IACxD,GAAI,OAAO,EAAE,SAAS,CAAA;AAAA,EAAC;AAE3B;AAQA,SAAS,YAAY,QAAqC,OAAoC;AAC5F,QAAM,KAAK,UAAU,KAAK;AAC1B,SAAO,KAAK,OAAO,IAAI,EAAE,IAAI;AAC/B;AAGA,SAAS,UAAU,QAAqC,MAAmD;AACzG,QAAM,UAAU,YAAY,QAAQ,KAAK,gBAAgB;AACzD,SAAO,UAAU,MAAM,OAAO,KAAK;AACrC;AAEA,MAAM,kBAA0C;AAAA,EAC9C,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,OAAO;AACT;AAEA,SAAS,kBAAkB,OAAuB;AAChD,SAAO,gBAAgB,MAAM,YAAA,CAAa,KAAK,iBAAiB,KAAK,EAAE,YAAA;AACzE;AAEA,MAAM,cAAoB,CAAC,EAAE,MAAM,QAAQ,aAAa;AACtD,QAAM,UAAU,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;AAClE,QAAM,EAAE,WAAW,eAAe,iBAAiB,OAAO;AAC1D,QAAM,SAAS,YAAY,SAAS,SAAS,IAAI;AACjD,QAAM,WAAW,gBAAgB,MAAM;AACvC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,UAAU;AAAA,IAClB,MAAM;AAAA,IACN,QAAQ,YACJ,aAAa,IACX,GAAG,SAAS,MAAM,OAAO,YAAY,WAAW,CAAC,MACjD,YACF;AAAA,IACJ,MAAM,CAAC,UAAU,QAAQ,IAAI,GAAG,aAAa,QAAQ,aAAa,KAAK,QAAQ,QAAQ,EAAE;AAAA,EAAA;AAE7F;AAEA,SAAS,WAAW,MAAmD;AACrE,SAAO,IAAI,KAAK,SAAS,KAAK,IAAI,KAAK,IAAI;AAC7C;AAEA,MAAM,eAAqB,CAAC,EAAE,MAAM,aAAa;AAC/C,QAAM,OAAO,WAAW,IAAI;AAC5B,QAAM,SAAS,WAAW,IAAI;AAC9B,QAAM,SAAS,IAAI,KAAK,MAAM,KAAK;AACnC,QAAM,QAAQ,IAAI,KAAK,KAAK;AAC5B,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAQ,iCAAQ,SAAQ;AAAA,IACxB,MAAM,iCAAQ;AAAA,IACd,QAAQ;AAAA,IACR,MAAM;AAAA,MACJ,SAAS,MAAM,QAAQ,SAAS,SAAS,CAAC,IAAI,SAAS,KAAK,KAAK,aAAa,SAAS,CAAC;AAAA,MACxF,UAAU,QAAQ,IAAI;AAAA,IAAA;AAAA,EACxB;AAEJ;AAEA,MAAM,WAAiB,CAAC,EAAE,MAAM,aAAa;AAC3C,QAAM,OAAO,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,SAAS;AACjD,QAAM,SAAS,OAAO,UAAU,IAAI,IAAI;AACxC,SAAO;AAAA,IACL,MAAM,CAAC,WAAW,UAAU,MAAM;AAAA,IAClC,QAAQ,UAAU;AAAA,IAClB,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,MAAM,CAAC,UAAU,QAAQ,IAAI,CAAC;AAAA,EAAA;AAElC;AAEA,MAAM,WAAiB,CAAC,EAAE,MAAM,QAAQ,aAAa;AACnD,QAAM,UAAU,IAAI,KAAK,OAAO,KAAK,IAAI,KAAK,KAAK;AACnD,QAAM,OAAO,IAAI,KAAK,IAAI;AAC1B,QAAM,SAAS,UAAU,kBAAkB,SAAS,SAAS,EAAE,CAAC,KAAK;AACrE,QAAM,OAAO,OAAO,WAAW,YAAY,mBAAmB,KAAK,OAAO,MAAM;AAChF,SAAO;AAAA,IACL,MAAM,CAAC,WAAW,SAAS,MAAM;AAAA,IACjC;AAAA,IACA,MAAM,UAAU,SAAS,SAAS,EAAE,IAAI;AAAA,IACxC,MAAM,QAAQ,UAAU,kBAAkB,SAAS,SAAS,EAAE,CAAC,KAAK;AAAA,IACpE,QAAQ,OAAO,GAAG,WAAW,EAAE,OAAO,IAAI,GAAG,KAAA,IAAS;AAAA,IACtD,MAAM,CAAC,QAAQ,MAAM,UAAU,IAAI,CAAC,IAAI,UAAU,QAAQ,IAAI,CAAC;AAAA,EAAA;AAEnE;AAEA,MAAM,WAAiB,CAAC,EAAE,MAAM,QAAQ,aAAa;AACnD,QAAM,UAAU,IAAI,KAAK,OAAO,KAAK,IAAI,KAAK,KAAK;AACnD,QAAM,OAAO,IAAI,KAAK,IAAI;AAC1B,QAAM,OAAO,UAAU,SAAS,SAAS,EAAE,IAAI;AAC/C,QAAM,QAAQ,OAAO,GAAG,UAAU,IAAI,CAAC,MAAM;AAC7C,QAAM,SAAS,OAAO,GAAG,KAAK,OAAO,IAAI,KAAK,OAAO,UAAU,IAAI,IAAI;AACvE,QAAM,OACJ,OAAO,WAAW,aAAa,OAAO,KAAA,MAAW,MAAM,eAAe,KAAK,OAAO,KAAA,CAAM;AAC1F,SAAO;AAAA,IACL,MAAM,CAAC,aAAa,YAAY,QAAQ;AAAA,IACxC;AAAA,IACA;AAAA,IACA,MAAM,QAAQ,OAAO,kBAAkB,IAAI,GAAG,OAAO,OAAO,UAAU,IAAI,CAAC,KAAK,EAAE,KAAK;AAAA,IACvF,QAAQ,OAAO,GAAG,WAAW,EAAE,OAAO,IAAI,GAAG,KAAA,IAAS;AAAA,IACtD,MAAM,CAAC,IAAI,KAAK,IAAI,GAAG,UAAU,QAAQ,IAAI,CAAC;AAAA,EAAA;AAElD;AAEA,MAAM,gBAAsB,CAAC,EAAE,MAAM,aAAa;AAChD,QAAM,OAAO,WAAW,IAAI;AAC5B,QAAM,SAAS,WAAW,IAAI;AAC9B,QAAM,UAAU,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;AAClE,QAAM,QAAQ,WAAW,OAAO;AAChC,SAAO;AAAA,IACL,MAAM,CAAC,WAAW,SAAS,OAAO;AAAA,IAClC,SAAQ,iCAAQ,SAAQ;AAAA,IACxB,MAAM,iCAAQ;AAAA,IACd,QAAQ;AAAA,IACR,MAAM,CAAC,QAAQ,KAAK,IAAI,KAAK,IAAI,UAAU,QAAQ,IAAI,CAAC;AAAA,EAAA;AAE5D;AAEA,MAAM,eAAqB,CAAC,EAAE,MAAM,aAAa;AAC/C,QAAM,OAAO,WAAW,IAAI;AAC5B,QAAM,SAAS,WAAW,IAAI;AAC9B,QAAM,iBAAiB,OAAO,KAAK,eAAe,YAAY,OAAO,KAAK,eAAe;AACzF,QAAM,EAAE,OAAO,QAAA,IAAY,iBAAiB,cAAc,IAAI,IAAI,EAAE,OAAO,GAAG,SAAS,EAAA;AACvF,SAAO;AAAA,IACL,MAAM,CAAC,WAAW,UAAU,MAAM;AAAA,IAClC,SAAQ,iCAAQ,SAAQ;AAAA,IACxB,MAAM,iCAAQ;AAAA,IACd,QAAQ;AAAA,IACR,MAAM;AAAA,MACJ,QAAQ,UAAU,KAAK,IAAI,KAAK,KAAK,OAAO;AAAA,MAC5C,KAAK,gBAAgB,QAAQ;AAAA,MAC7B,UAAU,QAAQ,IAAI;AAAA,IAAA;AAAA,EACxB;AAEJ;AAEA,MAAM,mBAAyB,CAAC,EAAE,MAAM,aAAa;AACnD,QAAM,QACJ,IAAI,KAAK,sBAAsB,KAC/B,IAAI,KAAK,KAAK,KACd,IAAI,KAAK,YAAY,KACrB,IAAI,KAAK,OAAO;AAClB,QAAM,QAAQ,MAAM,QAAQ,KAAK,KAAK,IAClC,KAAK,MAAM,IAAI,GAAG,EAAE,OAAO,CAAC,SAAyB,SAAS,MAAS,IACvE,CAAA;AACJ,QAAM,SAAS,aAAa,MAAM;AAClC,QAAM,SAAS,MAAM,QAAQ,iCAAQ,MAAM,IAAI,OAAO,SAAS;AAC/D,QAAM,QAAQ,IAAI,iCAAQ,WAAW,KAAK,IAAI,iCAAQ,cAAc,MAAK,iCAAQ;AACjF,QAAM,WAAW,QAAQ,QAAQ,OAAO,KAAK,CAAC,KAAK;AACnD,QAAM,OACJ,UAAU,SACN,SACA,UAAU,IACR,kBAAkB,QAAQ,KAC1B,SAAS,OAAO,OAAO,OAAO,CAAC,GAAG,QAAQ;AAClD,SAAO;AAAA,IACL,MAAM,CAAC,aAAa,YAAY,QAAQ;AAAA,IACxC,QAAQ,QAAQ,OAAO,OAAO,KAAK,CAAC,KAAK;AAAA,IACzC;AAAA,IACA,MAAM,CAAC,MAAM,SAAS,KAAK,MAAM,IAAI,CAAC,SAAS,iBAAiB,IAAI,EAAE,YAAA,CAAa,EAAE,KAAK,IAAI,CAAC;AAAA,EAAA;AAEnG;AAEA,MAAM,oBAA0B,CAAC,EAAE,MAAM,QAAQ,aAAa;AAC5D,QAAM,UAAU,YAAY,QAAQ,KAAK,qBAAqB,KAAK,QAAQ;AAC3E,QAAM,SAAS,aAAa,MAAM;AAClC,QAAM,QAAQ,IAAI,iCAAQ,KAAK;AAC/B,SAAO;AAAA,IACL,MAAM,CAAC,YAAY,WAAW,OAAO;AAAA,IACrC,QAAQ,WAAW;AAAA,IACnB,MAAM,QAAQ,GAAG,WAAW,cAAc,OAAO,kBAAkB,KAAK,CAAC,KAAK;AAAA,IAC9E,QAAQ,IAAI,iCAAQ,MAAM;AAAA,EAAA;AAE9B;AAEA,MAAM,qBAA2B,CAAC,EAAE,MAAM,QAAQ,aAAa;;AAC7D,QAAM,UAAU,YAAY,QAAQ,KAAK,qBAAqB,KAAK,QAAQ;AAC3E,QAAM,QAAQ,KAAI,kBAAa,MAAM,MAAnB,mBAAsB,KAAK;AAC7C,SAAO;AAAA,IACL,MAAM,CAAC,YAAY,WAAW,OAAO;AAAA,IACrC,QAAQ,WAAW;AAAA,IACnB,MAAM,QAAQ,GAAG,WAAW,cAAc,OAAO,kBAAkB,KAAK,CAAC,KAAK;AAAA,EAAA;AAElF;AAEA,MAAM,qBAA2B,CAAC,EAAE,MAAM,QAAQ,aAAa;;AAC7D,QAAM,UAAU,YAAY,QAAQ,KAAK,qBAAqB,KAAK,QAAQ;AAC3E,QAAM,OAAO,IAAI,KAAK,IAAI;AAC1B,QAAM,MAAM,KAAI,kBAAa,MAAM,MAAnB,mBAAsB,GAAG;AACzC,SAAO;AAAA,IACL,MAAM,CAAC,aAAa,YAAY,QAAQ;AAAA,IACxC,QAAQ,SAAS,SAAY,QAAQ,IAAI,KAAM,WAAW;AAAA,IAC1D,QAAQ;AAAA,IACR,MAAM,CAAC,SAAS,UAAa,WAAW,MAAM,OAAO,EAAE;AAAA,EAAA;AAE3D;AAEA,MAAM,qBAA2B,CAAC,EAAE,WAAW;AAC7C,QAAM,OAAO,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK;AAC7C,SAAO;AAAA,IACL,MAAM,CAAC,YAAY,WAAW,QAAQ;AAAA,IACtC,QAAQ,OAAO,YAAY,OAAO,IAAI,CAAC,KAAK;AAAA,EAAA;AAEhD;AAEA,SAAS,OAAO,MAAmD;AACjE,QAAM,QAAQ,IAAI,KAAK,KAAK;AAC5B,QAAM,OAAO,IAAI,KAAK,IAAI;AAC1B,MAAI,SAAS,KAAM,QAAO,GAAG,KAAK,IAAI,IAAI;AAC1C,SAAO,QAAQ,IAAI,KAAK,UAAU;AACpC;AAEA,MAAM,iBAAuB,CAAC,EAAE,WAAW;AACzC,QAAM,SAAS,IAAI,KAAK,WAAW,KAAK,IAAI,KAAK,MAAM;AACvD,QAAM,OAAO,OAAO,IAAI;AACxB,SAAO;AAAA,IACL,MAAM,CAAC,WAAW,UAAU,MAAM;AAAA,IAClC,QAAQ,WAAW,SAAY,iBAAiB,MAAM,KAAK;AAAA,IAC3D,QAAQ,QAAQ,WAAW,SAAY,GAAG,IAAI,IAAI,MAAM,KAAK;AAAA,IAC7D,MAAM,CAAC,IAAI;AAAA,EAAA;AAEf;AAEA,MAAM,sBAA4B,CAAC,EAAE,WAAW;AAC9C,QAAM,OAAO,OAAO,IAAI;AACxB,QAAM,QAAQ,IAAI,KAAK,KAAK;AAC5B,SAAO;AAAA,IACL,MAAM,CAAC,WAAW,UAAU,MAAM;AAAA,IAClC,QAAQ,SAAS,UAAU,SAAS,GAAG,KAAK,mBAAmB;AAAA,IAC/D,MAAM,CAAC,MAAM,IAAI,KAAK,IAAI,KAAK,QAAQ,IAAI,KAAK,IAAI,CAAC,EAAE;AAAA,EAAA;AAE3D;AAEA,MAAM,qBAA2B,CAAC,EAAE,MAAM,aAAa;AACrD,QAAM,QAAQ,IAAI,KAAK,KAAK;AAC5B,QAAM,SAAS,aAAa,MAAM;AAClC,QAAM,OAAO,IAAI,KAAK,IAAI;AAC1B,QAAM,OAAO,IAAI,KAAK,IAAI;AAC1B,SAAO;AAAA,IACL,MAAM,CAAC,WAAW,UAAU,MAAM;AAAA,IAClC,QAAQ,QAAQ,gBAAgB,OAAO,KAAK,CAAC,KAAK;AAAA,IAClD,MAAM,WAAW,SAAY,wBAAwB,MAAM,KAAK;AAAA,IAChE,QAAQ;AAAA,IACR,MAAM,CAAC,QAAQ,QAAQ,GAAG,IAAI,MAAM,IAAI,IAAI,KAAK,UAAU,QAAQ,SAAS,OAAO,IAAI,CAAC;AAAA,EAAA;AAE5F;AAEA,MAAM,oBAA0B,CAAC,EAAE,WAAW;AAC5C,QAAM,SAAS,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,WAAW;AAC7D,SAAO;AAAA,IACL,MAAM,CAAC,iBAAiB,gBAAgB,YAAY;AAAA,IACpD,QAAQ,WAAW,SAAY,IAAI,MAAM,KAAK;AAAA,IAC9C,MAAM,CAAC,OAAO,IAAI,CAAC;AAAA,EAAA;AAEvB;AAEA,MAAM,kBAAwB,CAAC,EAAE,MAAM,aAAa;AAClD,QAAM,QAAQ,IAAI,KAAK,KAAK;AAC5B,QAAM,SAAS,aAAa,MAAM;AAClC,SAAO;AAAA,IACL,MAAM,CAAC,YAAY,WAAW,QAAQ;AAAA,IACtC,QAAQ,QAAQ,SAAS,OAAO,KAAK,CAAC,KAAK;AAAA,IAC3C,MAAM,WAAW,SAAY,kBAAkB,MAAM,KAAK;AAAA,IAC1D,QAAQ;AAAA,IACR,MAAM,CAAC,OAAO,IAAI,CAAC;AAAA,EAAA;AAEvB;AAEA,MAAM,iBAAuB,CAAC,EAAE,WAAW;AACzC,QAAM,OAAO,IAAI,KAAK,IAAI;AAC1B,QAAM,SAAS,WAAW,IAAI;AAC9B,QAAM,OAAO,OAAO,IAAI;AACxB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAQ,iCAAQ,SAAQ;AAAA,IACxB,MAAM,iCAAQ;AAAA,IACd,QAAQ,QAAQ,OAAO,GAAG,IAAI,IAAI,IAAI,KAAK;AAAA,IAC3C,MAAM,CAAC,MAAM,IAAI,KAAK,GAAG,CAAC;AAAA,EAAA;AAE9B;AAEA,MAAM,sBAA4B,CAAC,EAAE,WAAW;AAC9C,QAAM,OAAO,IAAI,KAAK,IAAI;AAC1B,SAAO;AAAA,IACL,MAAM,CAAC,WAAW,UAAU,MAAM;AAAA,IAClC,QAAQ,OAAO,UAAU,IAAI,IAAI;AAAA,IACjC,MAAM,OAAO,UAAU,IAAI,IAAI;AAAA,IAC/B,MAAM,CAAC,OAAO,IAAI,CAAC;AAAA,EAAA;AAEvB;AAEA,MAAM,gBAAsB,CAAC,EAAE,WAAW;AACxC,QAAM,QAAQ,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,IAAI;AACxE,SAAO;AAAA,IACL,MAAM,CAAC,aAAa,YAAY,QAAQ;AAAA,IACxC,QAAQ,QAAQ,eAAe,OAAO,KAAK,CAAC,KAAK;AAAA,EAAA;AAErD;AAGA,SAAS,UAAU,OAAoC;AACrD,QAAM,MAAM,IAAI,KAAK;AACrB,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,GAAG;AACvB,QAAI,IAAI,aAAa,WAAW,IAAI,aAAa,SAAU,QAAO;AAClE,UAAM,OAAO,IAAI,aAAa,MAAM,KAAK,IAAI;AAC7C,WAAO,SAAS,GAAG,IAAI,SAAS,QAAQ,UAAU,EAAE,CAAC,GAAG,IAAI,IAAI,EAAE;AAAA,EACpE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,MAAM,aAAmB,CAAC,EAAE,WAAW;AACrC,QAAM,OAAO,UAAU,KAAK,GAAG,KAAK,UAAU,KAAK,IAAI;AACvD,SAAO,EAAE,MAAM,MAAM,QAAQ,QAAQ,aAAA;AACvC;AAEA,MAAM,gBAAsB,CAAC,EAAE,MAAM,aAAa;AAChD,QAAM,SAAS,MAAM,QAAQ,KAAK,SAAS,IAAI,KAAK,YAAY,CAAC,KAAK,YAAY,KAAK,SAAS;AAChG,QAAM,MAAM,OAAO,IAAI,SAAS,EAAE,OAAO,CAAC,OAAqB,OAAO,MAAS;AAC/E,QAAM,QAAQ,IAAI,IAAI,CAAC,OAAO,OAAO,IAAI,EAAE,CAAC,EAAE,OAAO,CAAC,UAA2B,QAAQ,KAAK,CAAC;AAC/F,QAAM,SACJ,IAAI,SAAS,IACT,OAAO,IAAI,QAAQ,OAAO,IAC1B,MAAM,CAAC,IACL,SAAS,MAAM,CAAC,GAAG,EAAE,IACrB;AACR,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,QAAQ,IAAI,SAAS,KAAK,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI;AAAA,EAAA;AAEpE;AAIA,MAAM,iBAAuB,CAAC,EAAE,WAAW;AACzC,QAAM,QAAQ,MAAM,QAAQ,KAAK,KAAK,IAAK,KAAK,QAAmB,CAAA;AACnE,QAAM,OAAO,MAAM,OAAO,CAAC,UAAS,6BAAM,YAAW,WAAW,EAAE;AAClE,QAAM,UAAU,MAAM,KAAK,CAAC,UAAS,6BAAM,YAAW,aAAa;AACnE,SAAO;AAAA,IACL,MAAM,CAAC,YAAY,WAAW,QAAQ;AAAA,IACtC,QAAQ;AAAA,IACR,MACE,MAAM,WAAW,IAAI,qBAAqB,iBAAiB,IAAI,IAAI,MAAM,MAAM;AAAA,IACjF,QAAQ,IAAI,mCAAS,OAAO,IAAI,QAAQ,IAAI,mCAAS,OAAO,CAAC,KAAK;AAAA,EAAA;AAEtE;AAEA,SAAS,aAAa,OAAwB;AAC5C,QAAM,OAAO,IAAI,KAAK;AACtB,MAAI,CAAC,QAAQ,SAAS,kBAAmB,QAAO;AAChD,SAAO,iBAAiB,IAAI,EAAE,YAAA;AAChC;AAEA,MAAM,WAAiB,CAAC,EAAE,WAAW;AACnC,QAAM,cAAc,IAAI,KAAK,WAAW;AACxC,QAAM,MAAM,aAAa,KAAK,aAAa;AAC3C,SAAO;AAAA,IACL,MAAM,CAAC,iBAAiB,gBAAgB,aAAa;AAAA,IACrD,QAAQ,cAAc,GAAG,GAAG,KAAK,SAAS,aAAa,EAAE,CAAC,KAAK;AAAA,IAC/D,QAAQ;AAAA,EAAA;AAEZ;AAEA,MAAM,qBAA2B,CAAC,EAAE,WAAW;AAC7C,QAAM,cAAc,IAAI,KAAK,WAAW;AACxC,SAAO;AAAA,IACL,MAAM,CAAC,YAAY,WAAW,OAAO;AAAA,IACrC,QAAQ,cAAc,oBAAoB,SAAS,aAAa,EAAE,CAAC,KAAK;AAAA,IACxE,QAAQ;AAAA,EAAA;AAEZ;AAIA,MAAM,yBAA+B,CAAC,EAAE,WAAW;AACjD,QAAM,QAAQ,IAAI,KAAK,KAAK;AAC5B,SAAO;AAAA,IACL,MAAM,CAAC,YAAY,WAAW,OAAO;AAAA,IACrC,QAAQ,QAAQ,aAAa,OAAO,KAAK,CAAC,KAAK;AAAA,EAAA;AAEnD;AAGA,MAAM,wBAA8B,CAAC,EAAE,MAAM,aAAa;AACxD,QAAM,OAAOC,oBAAAA,0BAA0B,EAAE,MAAM,QAAQ;AACvD,QAAM,OAAO,KAAK,OAAO,SAAS,KAAK,MAAM,EAAE,IAAI;AACnD,QAAM,KAAK,KAAK,aAAa,OAAO,MAAMC,oBAAAA,qBAAqB,KAAK,QAAQ,CAAC,KAAK;AAClF,QAAM,SAAS,EAAE,QAAQ,KAAK,QAAQ,QAAW,MAAM,CAAC,EAAE,EAAA;AAC1D,UAAQ,KAAK,MAAA;AAAA,IACX,KAAK;AACH,aAAO,EAAE,GAAG,QAAQ,MAAM,CAAC,YAAY,WAAW,OAAO,GAAG,QAAQ,OAAO,SAAS,IAAI,KAAK,SAAA;AAAA,IAC/F,KAAK;AACH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,MAAM,CAAC,WAAW,UAAU,MAAM;AAAA,QAClC,QAAQ,OAAO,SAAS,IAAI,KAAK;AAAA,QACjC,MAAM,OAAO,WAAW,IAAI,KAAK;AAAA,MAAA;AAAA,IAErC,KAAK;AACH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,MAAM,CAAC,WAAW,UAAU,MAAM;AAAA,QAClC,QAAQ,OAAO,YAAY,IAAI,KAAK;AAAA,QACpC,MAAM,OAAO,WAAW,IAAI,KAAK;AAAA,MAAA;AAAA,IAErC,KAAK;AACH,aAAO,EAAE,GAAG,QAAQ,MAAM,CAAC,UAAU,SAAS,MAAM,GAAG,QAAQ,QAAQ,SAAA;AAAA,IACzE;AACE,aAAO,EAAE,GAAG,QAAQ,MAAM,CAAC,WAAW,UAAU,MAAM,GAAG,QAAQ,gBAAA;AAAA,EAAgB;AAEvF;AAEA,MAAM,wBAA8B,CAAC,EAAE,MAAM,aAAa;AACxD,QAAM,SAASC,oBAAAA,6BAA6B,QAAQ,EAAE,eAAe,IAAI,KAAK,KAAK,GAAG;AACtF,QAAM,QAAQ,OAAO,SAAS,UAAU,OAAO,OAAO,QAAQ,IAAI,KAAK,KAAK;AAC5E,SAAO;AAAA,IACL,MAAM,CAAC,UAAU,SAAS,MAAM;AAAA,IAChC,QAAQ,QAAQ,kBAAkB,OAAO,KAAK,CAAC,KAAK;AAAA,IACpD,MAAM,CAAC,OAAO,SAAS,UAAUC,oBAAAA,0BAA0B,OAAO,MAAM,IAAI,MAAS;AAAA,EAAA;AAEzF;AAEA,MAAM,QAA8B;AAAA,EAClC,SAAS;AAAA,EACT,WAAW;AAAA,EACX,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,eAAe;AAAA,EACf,+BAA+B;AAAA,EAC/B,2BAA2B;AAAA,EAC3B,uBAAuB;AAAA,EACvB,uBAAuB;AAAA,EACvB,yBAAyB;AAAA,EACzB,2BAA2B;AAAA,EAC3B,0BAA0B;AAAA,EAC1B,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EACnB,uBAAuB;AAAA,EACvB,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,MAAM;AAAA,EACN,kBAAkB;AAAA,EAClB,sBAAsB;AAAA,EACtB,qBAAqB;AAAA,EACrB,qBAAqB;AACvB;AAYA,SAAS,gBAAgB,UAAkB,MAA2C,QAA2C;AAC/H,QAAM,OAAOC,aAAAA,mBAAmB,QAAQ;AACxC,QAAM,YAAYC,aAAAA,iBAAiB,EAAE,UAAU,MAAM;AACrD,QAAM,QACJ,WAAW,YACP,KAAK,cACL,WAAW,aACR,KAAK,kBAAkBC,aAAAA,mBAAmB,KAAK,WAAW,IAC3DC,aAAAA,gBAAgB,UAAU,MAAM;AACxC,SAAO;AAAA,IACL;AAAA,IACA,GAAI,aAAa,WAAW,YAAY,EAAE,MAAM,SAAS,WAAW,EAAE,MAAM,CAAA;AAAA,EAAC;AAEjF;AAOA,SAAS,mBAAmB,UAAkB,QAA0B,aAAkD;AACxH,QAAM,OAAO,eAAe,iBAAiB,QAAQ;AACrD,QAAM,aAAa,eAAe,WAAW,IAAI;AACjD,UAAQ,QAAA;AAAA,IACN,KAAK;AACH,aAAO,EAAE,OAAO,WAAW,UAAU,GAAA;AAAA,IACvC,KAAK;AACH,aAAO,EAAE,OAAO,eAAe,OAAO,UAAU,GAAA;AAAA,IAClD,KAAK;AACH,aAAO,EAAE,OAAO,GAAG,IAAI,UAAA;AAAA,IACzB,KAAK;AACH,aAAO,EAAE,OAAO,GAAG,IAAI,cAAA;AAAA,IACzB,KAAK;AACH,aAAO,EAAE,OAAO,GAAG,IAAI,eAAA;AAAA,IACzB,KAAK;AACH,aAAO,EAAE,OAAO,GAAG,IAAI,YAAA;AAAA,IACzB,KAAK;AACH,aAAO,EAAE,OAAO,0BAA0B,QAAQ,KAAA;AAAA,EAAK;AAE7D;AAOO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA4C;AAC1C,QAAM,OAAO,MAAM,QAAQ;AAC3B,MAAI,MAAM;AACR,WAAO;AAAA,MACL,KAAK,EAAE,MAAM,QAAQ,CAAA,GAAI,QAAQ,QAAQ,QAAQ,eAAe,cAAc;AAAA,MAC9E;AAAA,IAAA;AAAA,EAEJ;AACA,MAAIC,aAAAA,mBAAmB,QAAQ,UAAU,gBAAgB,UAAU,MAAM,MAAM;AAC/E,SAAO,mBAAmB,UAAU,QAAQ,WAAW;AACzD;;;;;;;;;;;"}