@athenaintel/react 0.48.0 → 0.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -10
- package/dist/agent-runtime/src/call-recap.cjs +319 -0
- package/dist/agent-runtime/src/call-recap.cjs.map +1 -0
- package/dist/agent-runtime/src/call-recap.d.ts +142 -0
- package/dist/agent-runtime/src/call-recap.js +319 -0
- package/dist/agent-runtime/src/call-recap.js.map +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.cjs +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.cjs.map +1 -1
- package/dist/agent-runtime/src/chat-outbox-store.d.ts +7 -0
- package/dist/agent-runtime/src/chat-outbox-store.js +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.js.map +1 -1
- package/dist/agent-runtime/src/chat-outbox.cjs +46 -5
- package/dist/agent-runtime/src/chat-outbox.cjs.map +1 -1
- package/dist/agent-runtime/src/chat-outbox.d.ts +29 -0
- package/dist/agent-runtime/src/chat-outbox.js +46 -5
- package/dist/agent-runtime/src/chat-outbox.js.map +1 -1
- package/dist/agent-runtime/src/conversation-index.cjs.map +1 -1
- package/dist/agent-runtime/src/conversation-index.d.ts +7 -0
- package/dist/agent-runtime/src/conversation-index.js.map +1 -1
- package/dist/agent-runtime/src/conversation-list.cjs +2 -1
- package/dist/agent-runtime/src/conversation-list.cjs.map +1 -1
- package/dist/agent-runtime/src/conversation-list.d.ts +19 -3
- package/dist/agent-runtime/src/conversation-list.js +2 -1
- package/dist/agent-runtime/src/conversation-list.js.map +1 -1
- package/dist/agent-runtime/src/desktop-handoff.d.ts +10 -0
- package/dist/agent-runtime/src/edit-command.cjs +21 -0
- package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
- package/dist/agent-runtime/src/edit-command.d.ts +8 -0
- package/dist/agent-runtime/src/edit-command.js +21 -0
- package/dist/agent-runtime/src/edit-command.js.map +1 -1
- package/dist/agent-runtime/src/handoff-arrival.cjs +19 -0
- package/dist/agent-runtime/src/handoff-arrival.cjs.map +1 -1
- package/dist/agent-runtime/src/handoff-arrival.d.ts +18 -3
- package/dist/agent-runtime/src/handoff-arrival.js +20 -1
- package/dist/agent-runtime/src/handoff-arrival.js.map +1 -1
- package/dist/agent-runtime/src/handoff-cursor-id.cjs +6 -0
- package/dist/agent-runtime/src/handoff-cursor-id.cjs.map +1 -0
- package/dist/agent-runtime/src/handoff-cursor-id.d.ts +12 -0
- package/dist/agent-runtime/src/handoff-cursor-id.js +6 -0
- package/dist/agent-runtime/src/handoff-cursor-id.js.map +1 -0
- package/dist/agent-runtime/src/handoff-cursor.cjs +88 -0
- package/dist/agent-runtime/src/handoff-cursor.cjs.map +1 -0
- package/dist/agent-runtime/src/handoff-cursor.d.ts +120 -0
- package/dist/agent-runtime/src/handoff-cursor.js +88 -0
- package/dist/agent-runtime/src/handoff-cursor.js.map +1 -0
- package/dist/agent-runtime/src/index.d.ts +9 -7
- package/dist/agent-runtime/src/interrupts.cjs +28 -1
- package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/agent-runtime/src/interrupts.d.ts +21 -1
- package/dist/agent-runtime/src/interrupts.js +28 -1
- package/dist/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/agent-runtime/src/model-context.cjs +3 -2
- package/dist/agent-runtime/src/model-context.cjs.map +1 -1
- package/dist/agent-runtime/src/model-context.d.ts +6 -1
- package/dist/agent-runtime/src/model-context.js +3 -2
- package/dist/agent-runtime/src/model-context.js.map +1 -1
- package/dist/agent-runtime/src/runtime.d.ts +3 -2
- package/dist/agent-runtime/src/send-recovery.cjs +37 -3
- package/dist/agent-runtime/src/send-recovery.cjs.map +1 -1
- package/dist/agent-runtime/src/send-recovery.d.ts +19 -0
- package/dist/agent-runtime/src/send-recovery.js +37 -3
- package/dist/agent-runtime/src/send-recovery.js.map +1 -1
- package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +14 -8
- package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +227 -5
- package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
- package/dist/agent-runtime/src/use-conversation-list.cjs +3 -2
- package/dist/agent-runtime/src/use-conversation-list.cjs.map +1 -1
- package/dist/agent-runtime/src/use-conversation-list.d.ts +6 -0
- package/dist/agent-runtime/src/use-conversation-list.js +3 -2
- package/dist/agent-runtime/src/use-conversation-list.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs +21 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.d.ts +8 -2
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js +21 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +24 -6
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +17 -0
- package/dist/agent-runtime/src/use-deep-agent-runtime.js +24 -6
- package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs +44 -9
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +34 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.js +44 -9
- package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs +8 -3
- package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-window-sync.d.ts +7 -0
- package/dist/agent-runtime/src/use-deep-agent-window-sync.js +9 -4
- package/dist/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
- package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +13 -2
- package/dist/agent-runtime/src/voice-earcon.cjs +139 -0
- package/dist/agent-runtime/src/voice-earcon.cjs.map +1 -0
- package/dist/agent-runtime/src/voice-earcon.d.ts +38 -0
- package/dist/agent-runtime/src/voice-earcon.js +139 -0
- package/dist/agent-runtime/src/voice-earcon.js.map +1 -0
- package/dist/agent-runtime/src/voice-media.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-media.d.ts +14 -0
- package/dist/agent-runtime/src/voice-media.js.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
- package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.js +2 -0
- package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
- package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
- package/dist/assets/AthenaAssetEmbed.d.ts +11 -5
- package/dist/assets/AthenaAssetEmbed.js.map +1 -1
- package/dist/assets/use-asset-embed.cjs.map +1 -1
- package/dist/assets/use-asset-embed.d.ts +12 -5
- package/dist/assets/use-asset-embed.js.map +1 -1
- package/dist/chat/AthenaChat.cjs +27 -12
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.d.ts +13 -0
- package/dist/chat/AthenaChat.js +27 -12
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/AthenaPlanStrip.cjs +97 -0
- package/dist/chat/AthenaPlanStrip.cjs.map +1 -0
- package/dist/chat/AthenaPlanStrip.d.ts +15 -0
- package/dist/chat/AthenaPlanStrip.js +97 -0
- package/dist/chat/AthenaPlanStrip.js.map +1 -0
- package/dist/chat/StatewireApprovalCard.cjs +41 -6
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.d.ts +18 -1
- package/dist/chat/StatewireApprovalCard.js +42 -7
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat/StatewireQueuedMessages.cjs +56 -6
- package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
- package/dist/chat/StatewireQueuedMessages.d.ts +14 -0
- package/dist/chat/StatewireQueuedMessages.js +58 -8
- package/dist/chat/StatewireQueuedMessages.js.map +1 -1
- package/dist/chat/WaitingOnYouBanner.cjs +6 -2
- package/dist/chat/WaitingOnYouBanner.cjs.map +1 -1
- package/dist/chat/WaitingOnYouBanner.js +7 -3
- package/dist/chat/WaitingOnYouBanner.js.map +1 -1
- package/dist/chat/chat-voice-controls.cjs +3 -1
- package/dist/chat/chat-voice-controls.cjs.map +1 -1
- package/dist/chat/chat-voice-controls.d.ts +6 -1
- package/dist/chat/chat-voice-controls.js +3 -1
- package/dist/chat/chat-voice-controls.js.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingCard.cjs +4 -2
- package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingCard.js +4 -2
- package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingTranscript.cjs +4 -2
- package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingTranscript.js +4 -2
- package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.cjs +6 -4
- package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.d.ts +9 -1
- package/dist/chat/super-grouping/activity-timeline.js +6 -4
- package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
- package/dist/chat/super-grouping/index.d.ts +1 -1
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs +17 -0
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs.map +1 -0
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.d.ts +8 -0
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.js +17 -0
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.js.map +1 -0
- package/dist/chat-ui/index.cjs +11 -0
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.d.ts +3 -1
- package/dist/chat-ui/index.js +12 -1
- package/dist/chat-ui/index.js.map +1 -1
- package/dist/chat-ui/messages/from-the-call-card.cjs +124 -0
- package/dist/chat-ui/messages/from-the-call-card.cjs.map +1 -0
- package/dist/chat-ui/messages/from-the-call-card.d.ts +17 -0
- package/dist/chat-ui/messages/from-the-call-card.js +124 -0
- package/dist/chat-ui/messages/from-the-call-card.js.map +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-link.d.ts +4 -0
- package/dist/chat-ui/rich-text/chat-link.js +4 -3
- package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.d.ts +12 -0
- package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
- package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.cjs +4 -0
- package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.d.ts +4 -0
- package/dist/chat-ui/thread/chat-thread-shell.js +4 -0
- package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
- package/dist/chat-ui/thread/handoff-cursor.cjs +216 -0
- package/dist/chat-ui/thread/handoff-cursor.cjs.map +1 -0
- package/dist/chat-ui/thread/handoff-cursor.d.ts +57 -0
- package/dist/chat-ui/thread/handoff-cursor.js +216 -0
- package/dist/chat-ui/thread/handoff-cursor.js.map +1 -0
- package/dist/chat-ui/thread/statewire-history-edge.cjs +2 -0
- package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
- package/dist/chat-ui/thread/statewire-history-edge.d.ts +2 -0
- package/dist/chat-ui/thread/statewire-history-edge.js +3 -1
- package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
- package/dist/collab/client.cjs +15 -3
- package/dist/collab/client.cjs.map +1 -1
- package/dist/collab/client.d.ts +4 -2
- package/dist/collab/client.js +16 -4
- package/dist/collab/client.js.map +1 -1
- package/dist/composer/index.d.ts +1 -1
- package/dist/composer/useReadComposerDraft.cjs +26 -7
- package/dist/composer/useReadComposerDraft.cjs.map +1 -1
- package/dist/composer/useReadComposerDraft.d.ts +23 -1
- package/dist/composer/useReadComposerDraft.js +27 -8
- package/dist/composer/useReadComposerDraft.js.map +1 -1
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +207 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +141 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js +207 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -0
- package/dist/keryx-http/src/update-attributions.cjs +34 -4
- package/dist/keryx-http/src/update-attributions.cjs.map +1 -1
- package/dist/keryx-http/src/update-attributions.d.ts +36 -4
- package/dist/keryx-http/src/update-attributions.js +35 -5
- package/dist/keryx-http/src/update-attributions.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +4 -0
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +11 -1
- package/dist/provider/AthenaProvider.js +4 -0
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/styles.css +1 -1
- package/dist/threads/api.cjs.map +1 -1
- package/dist/threads/api.d.ts +15 -0
- package/dist/threads/api.js.map +1 -1
- package/dist/threads/conversation-list-fetcher.cjs +13 -5
- package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
- package/dist/threads/conversation-list-fetcher.d.ts +4 -1
- package/dist/threads/conversation-list-fetcher.js +13 -5
- package/dist/threads/conversation-list-fetcher.js.map +1 -1
- package/dist/threads/statewire-thread-list.cjs +28 -6
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.d.ts +20 -8
- package/dist/threads/statewire-thread-list.js +28 -6
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/threads/use-athena-sessions.cjs +27 -5
- package/dist/threads/use-athena-sessions.cjs.map +1 -1
- package/dist/threads/use-athena-sessions.d.ts +10 -5
- package/dist/threads/use-athena-sessions.js +28 -6
- package/dist/threads/use-athena-sessions.js.map +1 -1
- package/dist/threads/use-rest-conversation-history.d.ts +1 -0
- package/dist/tools/write-todos-tool-ui.cjs +8 -0
- package/dist/tools/write-todos-tool-ui.cjs.map +1 -1
- package/dist/tools/write-todos-tool-ui.d.ts +2 -0
- package/dist/tools/write-todos-tool-ui.js +8 -0
- package/dist/tools/write-todos-tool-ui.js.map +1 -1
- package/dist/workbench/session-workbench-panes.cjs +1 -8
- package/dist/workbench/session-workbench-panes.cjs.map +1 -1
- package/dist/workbench/session-workbench-panes.js +2 -9
- package/dist/workbench/session-workbench-panes.js.map +1 -1
- package/package.json +2 -2
|
@@ -5,7 +5,7 @@ const DEFAULT_PAGE_SIZE = 20;
|
|
|
5
5
|
const DEFAULT_STALE_TIME_MS = 3e4;
|
|
6
6
|
const DEFAULT_GC_TIME_MS = 10 * 60 * 1e3;
|
|
7
7
|
function useConversationList(options) {
|
|
8
|
-
var _a, _b, _c;
|
|
8
|
+
var _a, _b, _c, _d;
|
|
9
9
|
const {
|
|
10
10
|
filter,
|
|
11
11
|
pageSize = DEFAULT_PAGE_SIZE,
|
|
@@ -131,10 +131,11 @@ function useConversationList(options) {
|
|
|
131
131
|
return {
|
|
132
132
|
conversations,
|
|
133
133
|
totalCount: ((_b = firstPage.data) == null ? void 0 : _b.totalCount) ?? null,
|
|
134
|
+
homeAgentApplied: ((_c = firstPage.data) == null ? void 0 : _c.homeAgentApplied) === true,
|
|
134
135
|
isLoading: firstPage.isLoading,
|
|
135
136
|
isFetching: firstPage.isFetching || history.isFetching,
|
|
136
137
|
hasResolvedFirstPage: firstPage.data !== void 0,
|
|
137
|
-
hasNextPage: hasLoadedHistory ? history.hasNextPage ?? false : ((
|
|
138
|
+
hasNextPage: hasLoadedHistory ? history.hasNextPage ?? false : ((_d = firstPage.data) == null ? void 0 : _d.hasMore) ?? false,
|
|
138
139
|
isFetchingNextPage: history.isFetchingNextPage || !hasLoadedHistory && history.isFetching,
|
|
139
140
|
isFetchNextPageError: history.isError,
|
|
140
141
|
fetchNextPage,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-list.js","sources":["../../../../agent-runtime/src/use-conversation-list.ts"],"sourcesContent":["import {\n type InfiniteData,\n type QueryClient,\n useInfiniteQuery,\n useQuery,\n useQueryClient,\n} from '@tanstack/react-query';\nimport { useCallback, useMemo, useRef } from 'react';\nimport {\n CONVERSATION_LIST_DEFAULT_SCOPE_KEY,\n type ConversationListFetcher,\n type ConversationListFilter,\n type ConversationListPage,\n type ConversationSummary,\n conversationListFilterKey,\n conversationListQueryKeys,\n invalidateConversationListCaches,\n mergeConversationPages,\n normalizeConversationListFilter,\n patchConversationsInPage,\n prependConversationToPage,\n removeConversationFromPage,\n} from './conversation-list';\n\nconst DEFAULT_PAGE_SIZE = 20;\nconst DEFAULT_STALE_TIME_MS = 30_000;\nconst DEFAULT_GC_TIME_MS = 10 * 60 * 1000;\n\nexport interface UseConversationListOptions {\n /**\n * Transport adapter for one page (GraphQL `listConversations`, the REST\n * thread list, …). Captured in a ref — identity changes do not re-key the\n * cache, so pair a changing fetcher with a changing `scopeKey`.\n */\n fetchPage: ConversationListFetcher;\n filter?: ConversationListFilter;\n pageSize?: number;\n /**\n * Cache identity beyond the filter. MUST include everything that changes\n * whose list this is — principal/user id, backend base URL — so a\n * credential swap can never serve another identity's rows from the cache.\n */\n scopeKey?: string;\n enabled?: boolean;\n staleTime?: number;\n gcTime?: number;\n refetchOnMount?: boolean | 'always';\n refetchOnWindowFocus?: boolean;\n /**\n * Newest-page freshness is push-based by default (the conversation-index\n * statewire invalidates these caches), so no timer runs unless asked.\n */\n refetchInterval?: number | false;\n retry?: boolean | number | ((failureCount: number, error: Error) => boolean);\n /**\n * The client to cache in. Defaults to the one in context; a hook that may\n * render outside any `QueryClientProvider` (and stays disabled there)\n * passes its own.\n */\n queryClient?: QueryClient;\n}\n\nexport interface ConversationListController {\n /** Merged newest-first rows: live first page + retained history pages. */\n conversations: ConversationSummary[];\n /** Advisory total from the live first page, when the transport counts. */\n totalCount: number | null;\n isLoading: boolean;\n isFetching: boolean;\n /** True once the live first page has resolved at least once. */\n hasResolvedFirstPage: boolean;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n /** True while the last next-page request failed (cleared by a successful one). */\n isFetchNextPageError: boolean;\n fetchNextPage: () => Promise<unknown>;\n /** Refetch the live first page (history pages stay frozen). */\n refresh: () => Promise<unknown>;\n /** Invalidate every conversation-list cache in this scope (all filters). */\n invalidate: () => Promise<void>;\n /** Optimistically show a new conversation at the top of THIS filter view. */\n prependConversation: (item: ConversationSummary) => void;\n /** Optimistically drop a conversation from THIS filter view. */\n removeConversation: (threadId: string) => void;\n /** Optimistically patch a conversation's row in THIS filter view. */\n patchConversation: (threadId: string, patch: Partial<ConversationSummary>) => void;\n}\n\n/**\n * The shared headless conversation-list pager (chat-stack unification —\n * thread-list-contract-convergence).\n *\n * Implements, once, the two-tier TanStack Query pattern Olympus and mobile\n * each hand-rolled:\n *\n * - a LIVE first-page query — the only observer that refetches on\n * invalidation/focus, so push-driven freshness costs one page, and\n * - a permanently-disabled infinite \"history\" query whose pages are fetched\n * only by explicit `fetchNextPage()` calls and retained without replay\n * (invalidation/polling never refetches every loaded page).\n *\n * Rows merge newest-first with the live page winning duplicate threads.\n */\nexport function useConversationList(options: UseConversationListOptions): ConversationListController {\n const {\n filter,\n pageSize = DEFAULT_PAGE_SIZE,\n scopeKey = CONVERSATION_LIST_DEFAULT_SCOPE_KEY,\n enabled,\n staleTime = DEFAULT_STALE_TIME_MS,\n gcTime = DEFAULT_GC_TIME_MS,\n refetchOnMount,\n refetchOnWindowFocus,\n refetchInterval = false,\n retry,\n } = options;\n const queryClient = useQueryClient(options.queryClient);\n\n const fetchPageRef = useRef(options.fetchPage);\n fetchPageRef.current = options.fetchPage;\n\n // The normalized filter is both the cache identity (via the key) and what\n // queryFn closures fetch, so a cache entry holds exactly what it claims —\n // fresh-but-equal filter objects from the caller never re-key or refetch.\n // (Plain per-render values: only the serialized key participates in\n // identity, so object freshness here is irrelevant.)\n const normalizedFilter = normalizeConversationListFilter(filter ?? {});\n const filterKey = conversationListFilterKey(normalizedFilter);\n\n const firstPageKey = useMemo(\n () => conversationListQueryKeys.firstPage(scopeKey, filterKey, pageSize),\n [scopeKey, filterKey, pageSize]\n );\n const historyKey = useMemo(\n () => conversationListQueryKeys.history(scopeKey, filterKey, pageSize),\n [scopeKey, filterKey, pageSize]\n );\n\n // React Query merges per-query options OVER the client's defaultOptions with\n // Object.assign, which copies explicitly-`undefined` keys — so passing\n // `refetchOnWindowFocus: undefined` CLOBBERS a consumer's\n // `refetchOnWindowFocus: false` default and silently re-enables focus\n // refetching. Options the caller did not set must therefore be omitted, not\n // forwarded as undefined, so they fall through to the consuming app's client.\n const passthrough = {\n ...(enabled !== undefined ? { enabled } : {}),\n ...(refetchOnMount !== undefined ? { refetchOnMount } : {}),\n ...(refetchOnWindowFocus !== undefined ? { refetchOnWindowFocus } : {}),\n ...(retry !== undefined ? { retry } : {}),\n };\n\n const firstPage = useQuery(\n {\n queryKey: firstPageKey,\n queryFn: ({ signal }) =>\n fetchPageRef.current({ limit: pageSize, offset: 0, filter: normalizedFilter, signal }),\n staleTime,\n gcTime,\n refetchInterval,\n ...passthrough,\n },\n queryClient\n );\n\n const history = useInfiniteQuery(\n {\n queryKey: historyKey,\n initialPageParam: pageSize,\n gcTime,\n queryFn: ({ pageParam, signal }) =>\n fetchPageRef.current({\n limit: pageSize,\n offset: pageParam,\n filter: normalizedFilter,\n signal,\n }),\n getNextPageParam: (lastPage, _allPages, lastOffset) =>\n lastPage.hasMore ? lastOffset + pageSize : undefined,\n ...passthrough,\n // fetchNextPage/refetch still perform explicit fetches for disabled\n // queries. Keeping this observer disabled prevents focus, invalidation,\n // and polling from replaying every retained historical page. This must\n // win over `passthrough.enabled`, so it is spread last.\n enabled: false,\n },\n queryClient\n );\n\n const historyPages = history.data?.pages;\n const hasLoadedHistory = (historyPages?.length ?? 0) > 0;\n\n const conversations = useMemo(\n () =>\n mergeConversationPages([\n ...(firstPage.data ? [firstPage.data] : []),\n ...(historyPages ?? []),\n ]),\n [firstPage.data, historyPages]\n );\n\n const fetchNextHistoryPage = history.fetchNextPage;\n const refetchHistoryFirstPage = history.refetch;\n const fetchNextPage = useCallback(\n () => (hasLoadedHistory ? fetchNextHistoryPage() : refetchHistoryFirstPage()),\n [hasLoadedHistory, fetchNextHistoryPage, refetchHistoryFirstPage]\n );\n\n const refresh = useCallback(() => firstPage.refetch(), [firstPage.refetch]);\n\n const invalidate = useCallback(\n () => invalidateConversationListCaches(queryClient, { scopeKey }),\n [queryClient, scopeKey]\n );\n\n const prependConversation = useCallback(\n (item: ConversationSummary) => {\n queryClient.setQueryData<ConversationListPage>(firstPageKey, (page) =>\n page ? prependConversationToPage(page, item) : page\n );\n },\n [queryClient, firstPageKey]\n );\n\n const removeConversation = useCallback(\n (threadId: string) => {\n queryClient.setQueryData<ConversationListPage>(firstPageKey, (page) =>\n page ? removeConversationFromPage(page, threadId) : page\n );\n queryClient.setQueryData<InfiniteData<ConversationListPage, number>>(historyKey, (data) =>\n data\n ? { ...data, pages: data.pages.map((page) => removeConversationFromPage(page, threadId)) }\n : data\n );\n },\n [queryClient, firstPageKey, historyKey]\n );\n\n const patchConversation = useCallback(\n (threadId: string, patch: Partial<ConversationSummary>) => {\n const match = (item: ConversationSummary) => item.threadId === threadId;\n queryClient.setQueryData<ConversationListPage>(firstPageKey, (page) =>\n page ? patchConversationsInPage(page, match, patch) : page\n );\n queryClient.setQueryData<InfiniteData<ConversationListPage, number>>(historyKey, (data) =>\n data\n ? {\n ...data,\n pages: data.pages.map((page) => patchConversationsInPage(page, match, patch)),\n }\n : data\n );\n },\n [queryClient, firstPageKey, historyKey]\n );\n\n return {\n conversations,\n totalCount: firstPage.data?.totalCount ?? null,\n isLoading: firstPage.isLoading,\n isFetching: firstPage.isFetching || history.isFetching,\n hasResolvedFirstPage: firstPage.data !== undefined,\n hasNextPage: hasLoadedHistory\n ? (history.hasNextPage ?? false)\n : (firstPage.data?.hasMore ?? false),\n isFetchingNextPage: history.isFetchingNextPage || (!hasLoadedHistory && history.isFetching),\n isFetchNextPageError: history.isError,\n fetchNextPage,\n refresh,\n invalidate,\n prependConversation,\n removeConversation,\n patchConversation,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-conversation-list.js","sources":["../../../../agent-runtime/src/use-conversation-list.ts"],"sourcesContent":["import {\n type InfiniteData,\n type QueryClient,\n useInfiniteQuery,\n useQuery,\n useQueryClient,\n} from '@tanstack/react-query';\nimport { useCallback, useMemo, useRef } from 'react';\nimport {\n CONVERSATION_LIST_DEFAULT_SCOPE_KEY,\n type ConversationListFetcher,\n type ConversationListFilter,\n type ConversationListPage,\n type ConversationSummary,\n conversationListFilterKey,\n conversationListQueryKeys,\n invalidateConversationListCaches,\n mergeConversationPages,\n normalizeConversationListFilter,\n patchConversationsInPage,\n prependConversationToPage,\n removeConversationFromPage,\n} from './conversation-list';\n\nconst DEFAULT_PAGE_SIZE = 20;\nconst DEFAULT_STALE_TIME_MS = 30_000;\nconst DEFAULT_GC_TIME_MS = 10 * 60 * 1000;\n\nexport interface UseConversationListOptions {\n /**\n * Transport adapter for one page (GraphQL `listConversations`, the REST\n * thread list, …). Captured in a ref — identity changes do not re-key the\n * cache, so pair a changing fetcher with a changing `scopeKey`.\n */\n fetchPage: ConversationListFetcher;\n filter?: ConversationListFilter;\n pageSize?: number;\n /**\n * Cache identity beyond the filter. MUST include everything that changes\n * whose list this is — principal/user id, backend base URL — so a\n * credential swap can never serve another identity's rows from the cache.\n */\n scopeKey?: string;\n enabled?: boolean;\n staleTime?: number;\n gcTime?: number;\n refetchOnMount?: boolean | 'always';\n refetchOnWindowFocus?: boolean;\n /**\n * Newest-page freshness is push-based by default (the conversation-index\n * statewire invalidates these caches), so no timer runs unless asked.\n */\n refetchInterval?: number | false;\n retry?: boolean | number | ((failureCount: number, error: Error) => boolean);\n /**\n * The client to cache in. Defaults to the one in context; a hook that may\n * render outside any `QueryClientProvider` (and stays disabled there)\n * passes its own.\n */\n queryClient?: QueryClient;\n}\n\nexport interface ConversationListController {\n /** Merged newest-first rows: live first page + retained history pages. */\n conversations: ConversationSummary[];\n /** Advisory total from the live first page, when the transport counts. */\n totalCount: number | null;\n /**\n * Whether the live first page's latest answer held the conversations with\n * the filter's `homeAgentId` agent ({@link ConversationListPage.homeAgentApplied}).\n * `false` until it answers, and wherever the server did not widen the list.\n */\n homeAgentApplied: boolean;\n isLoading: boolean;\n isFetching: boolean;\n /** True once the live first page has resolved at least once. */\n hasResolvedFirstPage: boolean;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n /** True while the last next-page request failed (cleared by a successful one). */\n isFetchNextPageError: boolean;\n fetchNextPage: () => Promise<unknown>;\n /** Refetch the live first page (history pages stay frozen). */\n refresh: () => Promise<unknown>;\n /** Invalidate every conversation-list cache in this scope (all filters). */\n invalidate: () => Promise<void>;\n /** Optimistically show a new conversation at the top of THIS filter view. */\n prependConversation: (item: ConversationSummary) => void;\n /** Optimistically drop a conversation from THIS filter view. */\n removeConversation: (threadId: string) => void;\n /** Optimistically patch a conversation's row in THIS filter view. */\n patchConversation: (threadId: string, patch: Partial<ConversationSummary>) => void;\n}\n\n/**\n * The shared headless conversation-list pager (chat-stack unification —\n * thread-list-contract-convergence).\n *\n * Implements, once, the two-tier TanStack Query pattern Olympus and mobile\n * each hand-rolled:\n *\n * - a LIVE first-page query — the only observer that refetches on\n * invalidation/focus, so push-driven freshness costs one page, and\n * - a permanently-disabled infinite \"history\" query whose pages are fetched\n * only by explicit `fetchNextPage()` calls and retained without replay\n * (invalidation/polling never refetches every loaded page).\n *\n * Rows merge newest-first with the live page winning duplicate threads.\n */\nexport function useConversationList(options: UseConversationListOptions): ConversationListController {\n const {\n filter,\n pageSize = DEFAULT_PAGE_SIZE,\n scopeKey = CONVERSATION_LIST_DEFAULT_SCOPE_KEY,\n enabled,\n staleTime = DEFAULT_STALE_TIME_MS,\n gcTime = DEFAULT_GC_TIME_MS,\n refetchOnMount,\n refetchOnWindowFocus,\n refetchInterval = false,\n retry,\n } = options;\n const queryClient = useQueryClient(options.queryClient);\n\n const fetchPageRef = useRef(options.fetchPage);\n fetchPageRef.current = options.fetchPage;\n\n // The normalized filter is both the cache identity (via the key) and what\n // queryFn closures fetch, so a cache entry holds exactly what it claims —\n // fresh-but-equal filter objects from the caller never re-key or refetch.\n // (Plain per-render values: only the serialized key participates in\n // identity, so object freshness here is irrelevant.)\n const normalizedFilter = normalizeConversationListFilter(filter ?? {});\n const filterKey = conversationListFilterKey(normalizedFilter);\n\n const firstPageKey = useMemo(\n () => conversationListQueryKeys.firstPage(scopeKey, filterKey, pageSize),\n [scopeKey, filterKey, pageSize]\n );\n const historyKey = useMemo(\n () => conversationListQueryKeys.history(scopeKey, filterKey, pageSize),\n [scopeKey, filterKey, pageSize]\n );\n\n // React Query merges per-query options OVER the client's defaultOptions with\n // Object.assign, which copies explicitly-`undefined` keys — so passing\n // `refetchOnWindowFocus: undefined` CLOBBERS a consumer's\n // `refetchOnWindowFocus: false` default and silently re-enables focus\n // refetching. Options the caller did not set must therefore be omitted, not\n // forwarded as undefined, so they fall through to the consuming app's client.\n const passthrough = {\n ...(enabled !== undefined ? { enabled } : {}),\n ...(refetchOnMount !== undefined ? { refetchOnMount } : {}),\n ...(refetchOnWindowFocus !== undefined ? { refetchOnWindowFocus } : {}),\n ...(retry !== undefined ? { retry } : {}),\n };\n\n const firstPage = useQuery(\n {\n queryKey: firstPageKey,\n queryFn: ({ signal }) =>\n fetchPageRef.current({ limit: pageSize, offset: 0, filter: normalizedFilter, signal }),\n staleTime,\n gcTime,\n refetchInterval,\n ...passthrough,\n },\n queryClient\n );\n\n const history = useInfiniteQuery(\n {\n queryKey: historyKey,\n initialPageParam: pageSize,\n gcTime,\n queryFn: ({ pageParam, signal }) =>\n fetchPageRef.current({\n limit: pageSize,\n offset: pageParam,\n filter: normalizedFilter,\n signal,\n }),\n getNextPageParam: (lastPage, _allPages, lastOffset) =>\n lastPage.hasMore ? lastOffset + pageSize : undefined,\n ...passthrough,\n // fetchNextPage/refetch still perform explicit fetches for disabled\n // queries. Keeping this observer disabled prevents focus, invalidation,\n // and polling from replaying every retained historical page. This must\n // win over `passthrough.enabled`, so it is spread last.\n enabled: false,\n },\n queryClient\n );\n\n const historyPages = history.data?.pages;\n const hasLoadedHistory = (historyPages?.length ?? 0) > 0;\n\n const conversations = useMemo(\n () =>\n mergeConversationPages([\n ...(firstPage.data ? [firstPage.data] : []),\n ...(historyPages ?? []),\n ]),\n [firstPage.data, historyPages]\n );\n\n const fetchNextHistoryPage = history.fetchNextPage;\n const refetchHistoryFirstPage = history.refetch;\n const fetchNextPage = useCallback(\n () => (hasLoadedHistory ? fetchNextHistoryPage() : refetchHistoryFirstPage()),\n [hasLoadedHistory, fetchNextHistoryPage, refetchHistoryFirstPage]\n );\n\n const refresh = useCallback(() => firstPage.refetch(), [firstPage.refetch]);\n\n const invalidate = useCallback(\n () => invalidateConversationListCaches(queryClient, { scopeKey }),\n [queryClient, scopeKey]\n );\n\n const prependConversation = useCallback(\n (item: ConversationSummary) => {\n queryClient.setQueryData<ConversationListPage>(firstPageKey, (page) =>\n page ? prependConversationToPage(page, item) : page\n );\n },\n [queryClient, firstPageKey]\n );\n\n const removeConversation = useCallback(\n (threadId: string) => {\n queryClient.setQueryData<ConversationListPage>(firstPageKey, (page) =>\n page ? removeConversationFromPage(page, threadId) : page\n );\n queryClient.setQueryData<InfiniteData<ConversationListPage, number>>(historyKey, (data) =>\n data\n ? { ...data, pages: data.pages.map((page) => removeConversationFromPage(page, threadId)) }\n : data\n );\n },\n [queryClient, firstPageKey, historyKey]\n );\n\n const patchConversation = useCallback(\n (threadId: string, patch: Partial<ConversationSummary>) => {\n const match = (item: ConversationSummary) => item.threadId === threadId;\n queryClient.setQueryData<ConversationListPage>(firstPageKey, (page) =>\n page ? patchConversationsInPage(page, match, patch) : page\n );\n queryClient.setQueryData<InfiniteData<ConversationListPage, number>>(historyKey, (data) =>\n data\n ? {\n ...data,\n pages: data.pages.map((page) => patchConversationsInPage(page, match, patch)),\n }\n : data\n );\n },\n [queryClient, firstPageKey, historyKey]\n );\n\n return {\n conversations,\n totalCount: firstPage.data?.totalCount ?? null,\n homeAgentApplied: firstPage.data?.homeAgentApplied === true,\n isLoading: firstPage.isLoading,\n isFetching: firstPage.isFetching || history.isFetching,\n hasResolvedFirstPage: firstPage.data !== undefined,\n hasNextPage: hasLoadedHistory\n ? (history.hasNextPage ?? false)\n : (firstPage.data?.hasMore ?? false),\n isFetchingNextPage: history.isFetchingNextPage || (!hasLoadedHistory && history.isFetching),\n isFetchNextPageError: history.isError,\n fetchNextPage,\n refresh,\n invalidate,\n prependConversation,\n removeConversation,\n patchConversation,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@ function useDeepAgentChatOutbox({
|
|
|
14
14
|
openThread,
|
|
15
15
|
onEvent,
|
|
16
16
|
checkAttachments,
|
|
17
|
+
sendGate,
|
|
17
18
|
enabled = true,
|
|
18
19
|
countdownMs
|
|
19
20
|
}) {
|
|
@@ -29,6 +30,9 @@ function useDeepAgentChatOutbox({
|
|
|
29
30
|
const checkAttachmentsRef = require$$0.useRef(checkAttachments);
|
|
30
31
|
checkAttachmentsRef.current = checkAttachments;
|
|
31
32
|
const checksAttachments = checkAttachments !== void 0;
|
|
33
|
+
const sendGateRef = require$$0.useRef(sendGate);
|
|
34
|
+
sendGateRef.current = sendGate;
|
|
35
|
+
const gatesSends = sendGate !== void 0;
|
|
32
36
|
const reachabilityRef = require$$0.useRef(reachability);
|
|
33
37
|
reachabilityRef.current = reachability;
|
|
34
38
|
const foregroundRef = require$$0.useRef(foreground);
|
|
@@ -91,6 +95,22 @@ function useDeepAgentChatOutbox({
|
|
|
91
95
|
return ((_a = checkAttachmentsRef.current) == null ? void 0 : _a.call(checkAttachmentsRef, attachments)) ?? Promise.resolve("ok");
|
|
92
96
|
}
|
|
93
97
|
},
|
|
98
|
+
...gatesSends && {
|
|
99
|
+
sendGate: {
|
|
100
|
+
keyOf: (entry) => {
|
|
101
|
+
var _a;
|
|
102
|
+
return ((_a = sendGateRef.current) == null ? void 0 : _a.keyOf(entry)) ?? "";
|
|
103
|
+
},
|
|
104
|
+
mayHandOff: (entry) => {
|
|
105
|
+
var _a;
|
|
106
|
+
return ((_a = sendGateRef.current) == null ? void 0 : _a.mayHandOff(entry)) ?? true;
|
|
107
|
+
},
|
|
108
|
+
subscribe: (listener) => {
|
|
109
|
+
var _a;
|
|
110
|
+
return ((_a = sendGateRef.current) == null ? void 0 : _a.subscribe(listener)) ?? (() => void 0);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
94
114
|
...countdownMs !== void 0 && { countdownMs }
|
|
95
115
|
});
|
|
96
116
|
setOutbox(created);
|
|
@@ -98,7 +118,7 @@ function useDeepAgentChatOutbox({
|
|
|
98
118
|
created.dispose();
|
|
99
119
|
setOutbox((current) => current === created ? null : current);
|
|
100
120
|
};
|
|
101
|
-
}, [enabled, threadId, store, countdownMs, canOpenThreads, checksAttachments]);
|
|
121
|
+
}, [enabled, threadId, store, countdownMs, canOpenThreads, checksAttachments, gatesSends]);
|
|
102
122
|
return outbox !== null && outbox.threadId === threadId ? outbox : null;
|
|
103
123
|
}
|
|
104
124
|
const EMPTY_SNAPSHOT = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-deep-agent-chat-outbox.cjs","sources":["../../../../agent-runtime/src/use-deep-agent-chat-outbox.ts"],"sourcesContent":["import {\n type Context,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport type { ChatOutboxAttachmentCheck } from \"./chat-outbox-attachments\";\nimport {\n type ChatOutbox,\n type ChatOutboxCommandUpdate,\n type ChatOutboxEvent,\n type ChatOutboxSignal,\n type ChatOutboxSnapshot,\n createChatOutbox,\n} from \"./chat-outbox\";\nimport type { ChatOutboxEntry, ChatOutboxStore } from \"./chat-outbox-store\";\nimport { type ChatOutboxClientHost, createStatewireChatOutboxPort } from \"./chat-outbox-thread\";\n\nexport interface UseDeepAgentChatOutboxOptions {\n /** The thread's assistant-ui client (`useDeepAgentRuntime`'s result). */\n client: ChatOutboxClientHost;\n threadId: string;\n /** The conversation has never started (a new chat). */\n isNewChat: boolean;\n /**\n * This person's outbox store, one per person for the life of the page or app (a message the\n * device cannot keep waits in its memory, across thread switches); `null` keeps waiting messages\n * in this outbox alone.\n */\n store: ChatOutboxStore | null;\n /** Athena's reachability (the host's own model: the browser online AND agora answering). */\n reachability: ChatOutboxSignal;\n /** The app is in front (a visible tab, an active app, a focused desktop window). */\n foreground: ChatOutboxSignal;\n /** Give a cancelled message's text back to the composer. */\n restore: (entry: ChatOutboxEntry) => void;\n /**\n * Open another of the person's conversations holding waiting messages (the tray's Open); omit\n * it where the host cannot switch conversations.\n */\n openThread?: (target: { threadId: string; newChat: boolean }) => void;\n onEvent?: (event: ChatOutboxEvent) => void;\n /**\n * Asks Athena whether a waiting message's attachments can still be opened, right before it goes\n * (`createChatOutboxAttachmentCheck`); omitted, attachments go unchecked.\n */\n checkAttachments?: ChatOutboxAttachmentCheck;\n /** Off: no outbox (a read-only or suspended surface); sends behave as before. */\n enabled?: boolean;\n /** The countdown before a waiting message is sent (tests); defaults to `CHAT_OUTBOX_COUNTDOWN_MS`. */\n countdownMs?: number;\n}\n\n/**\n * The chat outbox for the mounted thread (`chat-outbox.ts`), one per thread and store: a thread\n * switch disposes it and makes the next thread's. The host forwards statewire's command lifecycle\n * to {@link ChatOutbox.observeCommand} (its `useDeepAgentRuntime` `onCommandChange`), shares the\n * instance through {@link DeepAgentChatOutboxContext}, and has its composer call\n * {@link ChatOutbox.shouldHold} / {@link ChatOutbox.hold} before sending.\n */\nexport function useDeepAgentChatOutbox({\n client,\n threadId,\n isNewChat,\n store,\n reachability,\n foreground,\n restore,\n openThread,\n onEvent,\n checkAttachments,\n enabled = true,\n countdownMs,\n}: UseDeepAgentChatOutboxOptions): ChatOutbox | null {\n const clientRef = useRef(client);\n clientRef.current = client;\n const restoreRef = useRef(restore);\n restoreRef.current = restore;\n const openThreadRef = useRef(openThread);\n openThreadRef.current = openThread;\n const canOpenThreads = openThread !== undefined;\n const onEventRef = useRef(onEvent);\n onEventRef.current = onEvent;\n const checkAttachmentsRef = useRef(checkAttachments);\n checkAttachmentsRef.current = checkAttachments;\n const checksAttachments = checkAttachments !== undefined;\n const reachabilityRef = useRef(reachability);\n reachabilityRef.current = reachability;\n const foregroundRef = useRef(foreground);\n foregroundRef.current = foreground;\n // The client's notifications reach the port through this set, so a client whose identity\n // changes mid-thread is followed without remaking the outbox (and losing its countdown).\n const [threadListeners] = useState(() => new Set<() => void>());\n // A host without an outbox (`enabled` false) is never subscribed to.\n useEffect(() => {\n if (!enabled) return undefined;\n return client.subscribe(() => {\n for (const listener of threadListeners) listener();\n });\n }, [client, threadListeners, enabled]);\n // Signals read through refs for the same reason: a host rebuilding its signal objects per render\n // never remakes the outbox.\n const [stableSignals] = useState(() => {\n const forward = (pick: () => ChatOutboxSignal): ChatOutboxSignal => ({\n get: () => pick().get(),\n subscribe: (listener) => pick().subscribe(listener),\n });\n return {\n reachability: forward(() => reachabilityRef.current),\n foreground: forward(() => foregroundRef.current),\n };\n });\n const isNewChatRef = useRef(isNewChat);\n isNewChatRef.current = isNewChat;\n\n const [outbox, setOutbox] = useState<ChatOutbox | null>(null);\n // Made in an effect, not during render: the outbox subscribes to its signals and reads its\n // store, and a render React throws away must not leave those behind.\n // biome-ignore lint/correctness/useExhaustiveDependencies: one outbox per thread and store; everything else is read through refs.\n useEffect(() => {\n if (!enabled || !threadId) {\n setOutbox(null);\n return undefined;\n }\n const created = createChatOutbox({\n threadId,\n // Read at each hold: a chat that starts while mounted stamps its later messages as started.\n isNewChat: () => isNewChatRef.current,\n store,\n reachability: stableSignals.reachability,\n foreground: stableSignals.foreground,\n thread: {\n ...createStatewireChatOutboxPort({ client: () => clientRef.current }),\n subscribe: (listener) => {\n threadListeners.add(listener);\n return () => {\n threadListeners.delete(listener);\n };\n },\n },\n restore: (entry) => restoreRef.current(entry),\n ...(canOpenThreads && {\n openThread: (target: { threadId: string; newChat: boolean }) => openThreadRef.current?.(target),\n }),\n onEvent: (event) => onEventRef.current?.(event),\n ...(checksAttachments && {\n checkAttachments: (attachments: Parameters<ChatOutboxAttachmentCheck>[0]) =>\n checkAttachmentsRef.current?.(attachments) ?? Promise.resolve(\"ok\" as const),\n }),\n ...(countdownMs !== undefined && { countdownMs }),\n });\n setOutbox(created);\n return () => {\n created.dispose();\n setOutbox((current) => (current === created ? null : current));\n };\n }, [enabled, threadId, store, countdownMs, canOpenThreads, checksAttachments]);\n // During a thread switch's first render the previous thread's outbox is still in state.\n return outbox !== null && outbox.threadId === threadId ? outbox : null;\n}\n\nconst EMPTY_SNAPSHOT: ChatOutboxSnapshot = {\n rows: [],\n countdownEndsAt: null,\n durable: true,\n loaded: true,\n elsewhere: [],\n};\nconst noopSubscribe = () => () => undefined;\n\n/** The outbox's rows and countdown, re-rendering on every change (`null` outbox: nothing waits). */\nexport function useChatOutboxSnapshot(outbox: ChatOutbox | null): ChatOutboxSnapshot {\n return useSyncExternalStore(\n outbox ? outbox.subscribe : noopSubscribe,\n () => outbox?.getSnapshot() ?? EMPTY_SNAPSHOT,\n () => outbox?.getSnapshot() ?? EMPTY_SNAPSHOT,\n );\n}\n\n/**\n * Whether Athena can be reached, as the thread's outbox reads it: `true` without an outbox (a host\n * that keeps none sends and edits as before). What a sent message's Edit is offered on; a boolean,\n * so a reader re-renders only when it flips.\n */\nexport function useChatOutboxReachable(outbox: ChatOutbox | null): boolean {\n const subscribe = useCallback(\n (listener: () => void) => (outbox ? outbox.reachability.subscribe(listener) : () => undefined),\n [outbox],\n );\n const reachable = () => outbox?.reachability.get() ?? true;\n return useSyncExternalStore(subscribe, reachable, reachable);\n}\n\n/**\n * The context lives on `globalThis`, not in module scope, because two copies of this module can\n * run in one page: the SDK bundles agent-runtime into its dist while a host such as Olympus imports\n * agent-runtime directly. The host provides its thread's outbox through its own copy and mounts the\n * SDK's queue tray, which reads it through the SDK's copy: one context per copy, and the tray would\n * never see a waiting message (`asset-attachment.ts` shares its registry the same way).\n */\nconst CHAT_OUTBOX_CONTEXT = Symbol.for(\"@athenaintel/agent-runtime/deep-agent-chat-outbox-context\");\n\ntype ChatOutboxContextHost = typeof globalThis & {\n [CHAT_OUTBOX_CONTEXT]?: Context<ChatOutbox | null>;\n};\n\nfunction sharedChatOutboxContext(): Context<ChatOutbox | null> {\n const host = globalThis as ChatOutboxContextHost;\n return (host[CHAT_OUTBOX_CONTEXT] ??= createContext<ChatOutbox | null>(null));\n}\n\n/** The mounted thread's chat outbox, shared from its runtime provider to its composer and tray. */\nexport const DeepAgentChatOutboxContext: Context<ChatOutbox | null> = sharedChatOutboxContext();\n\nexport function useDeepAgentChatOutboxContext(): ChatOutbox | null {\n return useContext(DeepAgentChatOutboxContext);\n}\n\n/** Forward statewire's command lifecycle to an outbox held in a ref (the runtime is made first). */\nexport function forwardChatOutboxCommand(\n outbox: { current: ChatOutbox | null },\n update: ChatOutboxCommandUpdate,\n): void {\n outbox.current?.observeCommand(update);\n}\n"],"names":["useRef","useState","useEffect","createChatOutbox","createStatewireChatOutboxPort","useSyncExternalStore","useCallback","createContext","useContext"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-deep-agent-chat-outbox.cjs","sources":["../../../../agent-runtime/src/use-deep-agent-chat-outbox.ts"],"sourcesContent":["import {\n type Context,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport type { ChatOutboxAttachmentCheck } from \"./chat-outbox-attachments\";\nimport {\n type ChatOutbox,\n type ChatOutboxCommandUpdate,\n type ChatOutboxEvent,\n type ChatOutboxSendGate,\n type ChatOutboxSignal,\n type ChatOutboxSnapshot,\n createChatOutbox,\n} from \"./chat-outbox\";\nimport type { ChatOutboxEntry, ChatOutboxStore } from \"./chat-outbox-store\";\nimport { type ChatOutboxClientHost, createStatewireChatOutboxPort } from \"./chat-outbox-thread\";\n\nexport interface UseDeepAgentChatOutboxOptions {\n /** The thread's assistant-ui client (`useDeepAgentRuntime`'s result). */\n client: ChatOutboxClientHost;\n threadId: string;\n /** The conversation has never started (a new chat). */\n isNewChat: boolean;\n /**\n * This person's outbox store, one per person for the life of the page or app (a message the\n * device cannot keep waits in its memory, across thread switches); `null` keeps waiting messages\n * in this outbox alone.\n */\n store: ChatOutboxStore | null;\n /** Athena's reachability (the host's own model: the browser online AND agora answering). */\n reachability: ChatOutboxSignal;\n /** The app is in front (a visible tab, an active app, a focused desktop window). */\n foreground: ChatOutboxSignal;\n /** Give a cancelled message's text back to the composer. */\n restore: (entry: ChatOutboxEntry) => void;\n /**\n * Open another of the person's conversations holding waiting messages (the tray's Open); omit\n * it where the host cannot switch conversations.\n */\n openThread?: (target: { threadId: string; newChat: boolean }) => void;\n onEvent?: (event: ChatOutboxEvent) => void;\n /**\n * Asks Athena whether a waiting message's attachments can still be opened, right before it goes\n * (`createChatOutboxAttachmentCheck`); omitted, attachments go unchecked.\n */\n checkAttachments?: ChatOutboxAttachmentCheck;\n /**\n * Which waiting messages go together, and when (`ChatOutboxOptions.sendGate`): a host whose\n * messages carry per-send settings keeps a message with other settings than a send the\n * conversation has not taken yet waiting until it has. Read through a ref: a host may rebuild it.\n */\n sendGate?: ChatOutboxSendGate;\n /** Off: no outbox (a read-only or suspended surface); sends behave as before. */\n enabled?: boolean;\n /** The countdown before a waiting message is sent (tests); defaults to `CHAT_OUTBOX_COUNTDOWN_MS`. */\n countdownMs?: number;\n}\n\n/**\n * The chat outbox for the mounted thread (`chat-outbox.ts`), one per thread and store: a thread\n * switch disposes it and makes the next thread's. The host forwards statewire's command lifecycle\n * to {@link ChatOutbox.observeCommand} (its `useDeepAgentRuntime` `onCommandChange`), shares the\n * instance through {@link DeepAgentChatOutboxContext}, and has its composer call\n * {@link ChatOutbox.shouldHold} / {@link ChatOutbox.hold} before sending.\n */\nexport function useDeepAgentChatOutbox({\n client,\n threadId,\n isNewChat,\n store,\n reachability,\n foreground,\n restore,\n openThread,\n onEvent,\n checkAttachments,\n sendGate,\n enabled = true,\n countdownMs,\n}: UseDeepAgentChatOutboxOptions): ChatOutbox | null {\n const clientRef = useRef(client);\n clientRef.current = client;\n const restoreRef = useRef(restore);\n restoreRef.current = restore;\n const openThreadRef = useRef(openThread);\n openThreadRef.current = openThread;\n const canOpenThreads = openThread !== undefined;\n const onEventRef = useRef(onEvent);\n onEventRef.current = onEvent;\n const checkAttachmentsRef = useRef(checkAttachments);\n checkAttachmentsRef.current = checkAttachments;\n const checksAttachments = checkAttachments !== undefined;\n const sendGateRef = useRef(sendGate);\n sendGateRef.current = sendGate;\n const gatesSends = sendGate !== undefined;\n const reachabilityRef = useRef(reachability);\n reachabilityRef.current = reachability;\n const foregroundRef = useRef(foreground);\n foregroundRef.current = foreground;\n // The client's notifications reach the port through this set, so a client whose identity\n // changes mid-thread is followed without remaking the outbox (and losing its countdown).\n const [threadListeners] = useState(() => new Set<() => void>());\n // A host without an outbox (`enabled` false) is never subscribed to.\n useEffect(() => {\n if (!enabled) return undefined;\n return client.subscribe(() => {\n for (const listener of threadListeners) listener();\n });\n }, [client, threadListeners, enabled]);\n // Signals read through refs for the same reason: a host rebuilding its signal objects per render\n // never remakes the outbox.\n const [stableSignals] = useState(() => {\n const forward = (pick: () => ChatOutboxSignal): ChatOutboxSignal => ({\n get: () => pick().get(),\n subscribe: (listener) => pick().subscribe(listener),\n });\n return {\n reachability: forward(() => reachabilityRef.current),\n foreground: forward(() => foregroundRef.current),\n };\n });\n const isNewChatRef = useRef(isNewChat);\n isNewChatRef.current = isNewChat;\n\n const [outbox, setOutbox] = useState<ChatOutbox | null>(null);\n // Made in an effect, not during render: the outbox subscribes to its signals and reads its\n // store, and a render React throws away must not leave those behind.\n // biome-ignore lint/correctness/useExhaustiveDependencies: one outbox per thread and store; everything else is read through refs.\n useEffect(() => {\n if (!enabled || !threadId) {\n setOutbox(null);\n return undefined;\n }\n const created = createChatOutbox({\n threadId,\n // Read at each hold: a chat that starts while mounted stamps its later messages as started.\n isNewChat: () => isNewChatRef.current,\n store,\n reachability: stableSignals.reachability,\n foreground: stableSignals.foreground,\n thread: {\n ...createStatewireChatOutboxPort({ client: () => clientRef.current }),\n subscribe: (listener) => {\n threadListeners.add(listener);\n return () => {\n threadListeners.delete(listener);\n };\n },\n },\n restore: (entry) => restoreRef.current(entry),\n ...(canOpenThreads && {\n openThread: (target: { threadId: string; newChat: boolean }) => openThreadRef.current?.(target),\n }),\n onEvent: (event) => onEventRef.current?.(event),\n ...(checksAttachments && {\n checkAttachments: (attachments: Parameters<ChatOutboxAttachmentCheck>[0]) =>\n checkAttachmentsRef.current?.(attachments) ?? Promise.resolve(\"ok\" as const),\n }),\n ...(gatesSends && {\n sendGate: {\n keyOf: (entry: ChatOutboxEntry) => sendGateRef.current?.keyOf(entry) ?? \"\",\n mayHandOff: (entry: ChatOutboxEntry) => sendGateRef.current?.mayHandOff(entry) ?? true,\n subscribe: (listener: () => void) => sendGateRef.current?.subscribe(listener) ?? (() => undefined),\n },\n }),\n ...(countdownMs !== undefined && { countdownMs }),\n });\n setOutbox(created);\n return () => {\n created.dispose();\n setOutbox((current) => (current === created ? null : current));\n };\n }, [enabled, threadId, store, countdownMs, canOpenThreads, checksAttachments, gatesSends]);\n // During a thread switch's first render the previous thread's outbox is still in state.\n return outbox !== null && outbox.threadId === threadId ? outbox : null;\n}\n\nconst EMPTY_SNAPSHOT: ChatOutboxSnapshot = {\n rows: [],\n countdownEndsAt: null,\n durable: true,\n loaded: true,\n elsewhere: [],\n};\nconst noopSubscribe = () => () => undefined;\n\n/** The outbox's rows and countdown, re-rendering on every change (`null` outbox: nothing waits). */\nexport function useChatOutboxSnapshot(outbox: ChatOutbox | null): ChatOutboxSnapshot {\n return useSyncExternalStore(\n outbox ? outbox.subscribe : noopSubscribe,\n () => outbox?.getSnapshot() ?? EMPTY_SNAPSHOT,\n () => outbox?.getSnapshot() ?? EMPTY_SNAPSHOT,\n );\n}\n\n/**\n * Whether Athena can be reached, as the thread's outbox reads it: `true` without an outbox (a host\n * that keeps none sends and edits as before). What a sent message's Edit is offered on; a boolean,\n * so a reader re-renders only when it flips.\n */\nexport function useChatOutboxReachable(outbox: ChatOutbox | null): boolean {\n const subscribe = useCallback(\n (listener: () => void) => (outbox ? outbox.reachability.subscribe(listener) : () => undefined),\n [outbox],\n );\n const reachable = () => outbox?.reachability.get() ?? true;\n return useSyncExternalStore(subscribe, reachable, reachable);\n}\n\n/**\n * The context lives on `globalThis`, not in module scope, because two copies of this module can\n * run in one page: the SDK bundles agent-runtime into its dist while a host such as Olympus imports\n * agent-runtime directly. The host provides its thread's outbox through its own copy and mounts the\n * SDK's queue tray, which reads it through the SDK's copy: one context per copy, and the tray would\n * never see a waiting message (`asset-attachment.ts` shares its registry the same way).\n */\nconst CHAT_OUTBOX_CONTEXT = Symbol.for(\"@athenaintel/agent-runtime/deep-agent-chat-outbox-context\");\n\ntype ChatOutboxContextHost = typeof globalThis & {\n [CHAT_OUTBOX_CONTEXT]?: Context<ChatOutbox | null>;\n};\n\nfunction sharedChatOutboxContext(): Context<ChatOutbox | null> {\n const host = globalThis as ChatOutboxContextHost;\n return (host[CHAT_OUTBOX_CONTEXT] ??= createContext<ChatOutbox | null>(null));\n}\n\n/** The mounted thread's chat outbox, shared from its runtime provider to its composer and tray. */\nexport const DeepAgentChatOutboxContext: Context<ChatOutbox | null> = sharedChatOutboxContext();\n\nexport function useDeepAgentChatOutboxContext(): ChatOutbox | null {\n return useContext(DeepAgentChatOutboxContext);\n}\n\n/** Forward statewire's command lifecycle to an outbox held in a ref (the runtime is made first). */\nexport function forwardChatOutboxCommand(\n outbox: { current: ChatOutbox | null },\n update: ChatOutboxCommandUpdate,\n): void {\n outbox.current?.observeCommand(update);\n}\n"],"names":["useRef","useState","useEffect","createChatOutbox","createStatewireChatOutboxPort","useSyncExternalStore","useCallback","createContext","useContext"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Context } from "react";
|
|
2
2
|
import type { ChatOutboxAttachmentCheck } from "./chat-outbox-attachments";
|
|
3
|
-
import { type ChatOutbox, type ChatOutboxCommandUpdate, type ChatOutboxEvent, type ChatOutboxSignal, type ChatOutboxSnapshot } from "./chat-outbox";
|
|
3
|
+
import { type ChatOutbox, type ChatOutboxCommandUpdate, type ChatOutboxEvent, type ChatOutboxSendGate, type ChatOutboxSignal, type ChatOutboxSnapshot } from "./chat-outbox";
|
|
4
4
|
import type { ChatOutboxEntry, ChatOutboxStore } from "./chat-outbox-store";
|
|
5
5
|
import { type ChatOutboxClientHost } from "./chat-outbox-thread";
|
|
6
6
|
export interface UseDeepAgentChatOutboxOptions {
|
|
@@ -35,6 +35,12 @@ export interface UseDeepAgentChatOutboxOptions {
|
|
|
35
35
|
* (`createChatOutboxAttachmentCheck`); omitted, attachments go unchecked.
|
|
36
36
|
*/
|
|
37
37
|
checkAttachments?: ChatOutboxAttachmentCheck;
|
|
38
|
+
/**
|
|
39
|
+
* Which waiting messages go together, and when (`ChatOutboxOptions.sendGate`): a host whose
|
|
40
|
+
* messages carry per-send settings keeps a message with other settings than a send the
|
|
41
|
+
* conversation has not taken yet waiting until it has. Read through a ref: a host may rebuild it.
|
|
42
|
+
*/
|
|
43
|
+
sendGate?: ChatOutboxSendGate;
|
|
38
44
|
/** Off: no outbox (a read-only or suspended surface); sends behave as before. */
|
|
39
45
|
enabled?: boolean;
|
|
40
46
|
/** The countdown before a waiting message is sent (tests); defaults to `CHAT_OUTBOX_COUNTDOWN_MS`. */
|
|
@@ -47,7 +53,7 @@ export interface UseDeepAgentChatOutboxOptions {
|
|
|
47
53
|
* instance through {@link DeepAgentChatOutboxContext}, and has its composer call
|
|
48
54
|
* {@link ChatOutbox.shouldHold} / {@link ChatOutbox.hold} before sending.
|
|
49
55
|
*/
|
|
50
|
-
export declare function useDeepAgentChatOutbox({ client, threadId, isNewChat, store, reachability, foreground, restore, openThread, onEvent, checkAttachments, enabled, countdownMs, }: UseDeepAgentChatOutboxOptions): ChatOutbox | null;
|
|
56
|
+
export declare function useDeepAgentChatOutbox({ client, threadId, isNewChat, store, reachability, foreground, restore, openThread, onEvent, checkAttachments, sendGate, enabled, countdownMs, }: UseDeepAgentChatOutboxOptions): ChatOutbox | null;
|
|
51
57
|
/** The outbox's rows and countdown, re-rendering on every change (`null` outbox: nothing waits). */
|
|
52
58
|
export declare function useChatOutboxSnapshot(outbox: ChatOutbox | null): ChatOutboxSnapshot;
|
|
53
59
|
/**
|
|
@@ -12,6 +12,7 @@ function useDeepAgentChatOutbox({
|
|
|
12
12
|
openThread,
|
|
13
13
|
onEvent,
|
|
14
14
|
checkAttachments,
|
|
15
|
+
sendGate,
|
|
15
16
|
enabled = true,
|
|
16
17
|
countdownMs
|
|
17
18
|
}) {
|
|
@@ -27,6 +28,9 @@ function useDeepAgentChatOutbox({
|
|
|
27
28
|
const checkAttachmentsRef = useRef(checkAttachments);
|
|
28
29
|
checkAttachmentsRef.current = checkAttachments;
|
|
29
30
|
const checksAttachments = checkAttachments !== void 0;
|
|
31
|
+
const sendGateRef = useRef(sendGate);
|
|
32
|
+
sendGateRef.current = sendGate;
|
|
33
|
+
const gatesSends = sendGate !== void 0;
|
|
30
34
|
const reachabilityRef = useRef(reachability);
|
|
31
35
|
reachabilityRef.current = reachability;
|
|
32
36
|
const foregroundRef = useRef(foreground);
|
|
@@ -89,6 +93,22 @@ function useDeepAgentChatOutbox({
|
|
|
89
93
|
return ((_a = checkAttachmentsRef.current) == null ? void 0 : _a.call(checkAttachmentsRef, attachments)) ?? Promise.resolve("ok");
|
|
90
94
|
}
|
|
91
95
|
},
|
|
96
|
+
...gatesSends && {
|
|
97
|
+
sendGate: {
|
|
98
|
+
keyOf: (entry) => {
|
|
99
|
+
var _a;
|
|
100
|
+
return ((_a = sendGateRef.current) == null ? void 0 : _a.keyOf(entry)) ?? "";
|
|
101
|
+
},
|
|
102
|
+
mayHandOff: (entry) => {
|
|
103
|
+
var _a;
|
|
104
|
+
return ((_a = sendGateRef.current) == null ? void 0 : _a.mayHandOff(entry)) ?? true;
|
|
105
|
+
},
|
|
106
|
+
subscribe: (listener) => {
|
|
107
|
+
var _a;
|
|
108
|
+
return ((_a = sendGateRef.current) == null ? void 0 : _a.subscribe(listener)) ?? (() => void 0);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
92
112
|
...countdownMs !== void 0 && { countdownMs }
|
|
93
113
|
});
|
|
94
114
|
setOutbox(created);
|
|
@@ -96,7 +116,7 @@ function useDeepAgentChatOutbox({
|
|
|
96
116
|
created.dispose();
|
|
97
117
|
setOutbox((current) => current === created ? null : current);
|
|
98
118
|
};
|
|
99
|
-
}, [enabled, threadId, store, countdownMs, canOpenThreads, checksAttachments]);
|
|
119
|
+
}, [enabled, threadId, store, countdownMs, canOpenThreads, checksAttachments, gatesSends]);
|
|
100
120
|
return outbox !== null && outbox.threadId === threadId ? outbox : null;
|
|
101
121
|
}
|
|
102
122
|
const EMPTY_SNAPSHOT = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-deep-agent-chat-outbox.js","sources":["../../../../agent-runtime/src/use-deep-agent-chat-outbox.ts"],"sourcesContent":["import {\n type Context,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport type { ChatOutboxAttachmentCheck } from \"./chat-outbox-attachments\";\nimport {\n type ChatOutbox,\n type ChatOutboxCommandUpdate,\n type ChatOutboxEvent,\n type ChatOutboxSignal,\n type ChatOutboxSnapshot,\n createChatOutbox,\n} from \"./chat-outbox\";\nimport type { ChatOutboxEntry, ChatOutboxStore } from \"./chat-outbox-store\";\nimport { type ChatOutboxClientHost, createStatewireChatOutboxPort } from \"./chat-outbox-thread\";\n\nexport interface UseDeepAgentChatOutboxOptions {\n /** The thread's assistant-ui client (`useDeepAgentRuntime`'s result). */\n client: ChatOutboxClientHost;\n threadId: string;\n /** The conversation has never started (a new chat). */\n isNewChat: boolean;\n /**\n * This person's outbox store, one per person for the life of the page or app (a message the\n * device cannot keep waits in its memory, across thread switches); `null` keeps waiting messages\n * in this outbox alone.\n */\n store: ChatOutboxStore | null;\n /** Athena's reachability (the host's own model: the browser online AND agora answering). */\n reachability: ChatOutboxSignal;\n /** The app is in front (a visible tab, an active app, a focused desktop window). */\n foreground: ChatOutboxSignal;\n /** Give a cancelled message's text back to the composer. */\n restore: (entry: ChatOutboxEntry) => void;\n /**\n * Open another of the person's conversations holding waiting messages (the tray's Open); omit\n * it where the host cannot switch conversations.\n */\n openThread?: (target: { threadId: string; newChat: boolean }) => void;\n onEvent?: (event: ChatOutboxEvent) => void;\n /**\n * Asks Athena whether a waiting message's attachments can still be opened, right before it goes\n * (`createChatOutboxAttachmentCheck`); omitted, attachments go unchecked.\n */\n checkAttachments?: ChatOutboxAttachmentCheck;\n /** Off: no outbox (a read-only or suspended surface); sends behave as before. */\n enabled?: boolean;\n /** The countdown before a waiting message is sent (tests); defaults to `CHAT_OUTBOX_COUNTDOWN_MS`. */\n countdownMs?: number;\n}\n\n/**\n * The chat outbox for the mounted thread (`chat-outbox.ts`), one per thread and store: a thread\n * switch disposes it and makes the next thread's. The host forwards statewire's command lifecycle\n * to {@link ChatOutbox.observeCommand} (its `useDeepAgentRuntime` `onCommandChange`), shares the\n * instance through {@link DeepAgentChatOutboxContext}, and has its composer call\n * {@link ChatOutbox.shouldHold} / {@link ChatOutbox.hold} before sending.\n */\nexport function useDeepAgentChatOutbox({\n client,\n threadId,\n isNewChat,\n store,\n reachability,\n foreground,\n restore,\n openThread,\n onEvent,\n checkAttachments,\n enabled = true,\n countdownMs,\n}: UseDeepAgentChatOutboxOptions): ChatOutbox | null {\n const clientRef = useRef(client);\n clientRef.current = client;\n const restoreRef = useRef(restore);\n restoreRef.current = restore;\n const openThreadRef = useRef(openThread);\n openThreadRef.current = openThread;\n const canOpenThreads = openThread !== undefined;\n const onEventRef = useRef(onEvent);\n onEventRef.current = onEvent;\n const checkAttachmentsRef = useRef(checkAttachments);\n checkAttachmentsRef.current = checkAttachments;\n const checksAttachments = checkAttachments !== undefined;\n const reachabilityRef = useRef(reachability);\n reachabilityRef.current = reachability;\n const foregroundRef = useRef(foreground);\n foregroundRef.current = foreground;\n // The client's notifications reach the port through this set, so a client whose identity\n // changes mid-thread is followed without remaking the outbox (and losing its countdown).\n const [threadListeners] = useState(() => new Set<() => void>());\n // A host without an outbox (`enabled` false) is never subscribed to.\n useEffect(() => {\n if (!enabled) return undefined;\n return client.subscribe(() => {\n for (const listener of threadListeners) listener();\n });\n }, [client, threadListeners, enabled]);\n // Signals read through refs for the same reason: a host rebuilding its signal objects per render\n // never remakes the outbox.\n const [stableSignals] = useState(() => {\n const forward = (pick: () => ChatOutboxSignal): ChatOutboxSignal => ({\n get: () => pick().get(),\n subscribe: (listener) => pick().subscribe(listener),\n });\n return {\n reachability: forward(() => reachabilityRef.current),\n foreground: forward(() => foregroundRef.current),\n };\n });\n const isNewChatRef = useRef(isNewChat);\n isNewChatRef.current = isNewChat;\n\n const [outbox, setOutbox] = useState<ChatOutbox | null>(null);\n // Made in an effect, not during render: the outbox subscribes to its signals and reads its\n // store, and a render React throws away must not leave those behind.\n // biome-ignore lint/correctness/useExhaustiveDependencies: one outbox per thread and store; everything else is read through refs.\n useEffect(() => {\n if (!enabled || !threadId) {\n setOutbox(null);\n return undefined;\n }\n const created = createChatOutbox({\n threadId,\n // Read at each hold: a chat that starts while mounted stamps its later messages as started.\n isNewChat: () => isNewChatRef.current,\n store,\n reachability: stableSignals.reachability,\n foreground: stableSignals.foreground,\n thread: {\n ...createStatewireChatOutboxPort({ client: () => clientRef.current }),\n subscribe: (listener) => {\n threadListeners.add(listener);\n return () => {\n threadListeners.delete(listener);\n };\n },\n },\n restore: (entry) => restoreRef.current(entry),\n ...(canOpenThreads && {\n openThread: (target: { threadId: string; newChat: boolean }) => openThreadRef.current?.(target),\n }),\n onEvent: (event) => onEventRef.current?.(event),\n ...(checksAttachments && {\n checkAttachments: (attachments: Parameters<ChatOutboxAttachmentCheck>[0]) =>\n checkAttachmentsRef.current?.(attachments) ?? Promise.resolve(\"ok\" as const),\n }),\n ...(countdownMs !== undefined && { countdownMs }),\n });\n setOutbox(created);\n return () => {\n created.dispose();\n setOutbox((current) => (current === created ? null : current));\n };\n }, [enabled, threadId, store, countdownMs, canOpenThreads, checksAttachments]);\n // During a thread switch's first render the previous thread's outbox is still in state.\n return outbox !== null && outbox.threadId === threadId ? outbox : null;\n}\n\nconst EMPTY_SNAPSHOT: ChatOutboxSnapshot = {\n rows: [],\n countdownEndsAt: null,\n durable: true,\n loaded: true,\n elsewhere: [],\n};\nconst noopSubscribe = () => () => undefined;\n\n/** The outbox's rows and countdown, re-rendering on every change (`null` outbox: nothing waits). */\nexport function useChatOutboxSnapshot(outbox: ChatOutbox | null): ChatOutboxSnapshot {\n return useSyncExternalStore(\n outbox ? outbox.subscribe : noopSubscribe,\n () => outbox?.getSnapshot() ?? EMPTY_SNAPSHOT,\n () => outbox?.getSnapshot() ?? EMPTY_SNAPSHOT,\n );\n}\n\n/**\n * Whether Athena can be reached, as the thread's outbox reads it: `true` without an outbox (a host\n * that keeps none sends and edits as before). What a sent message's Edit is offered on; a boolean,\n * so a reader re-renders only when it flips.\n */\nexport function useChatOutboxReachable(outbox: ChatOutbox | null): boolean {\n const subscribe = useCallback(\n (listener: () => void) => (outbox ? outbox.reachability.subscribe(listener) : () => undefined),\n [outbox],\n );\n const reachable = () => outbox?.reachability.get() ?? true;\n return useSyncExternalStore(subscribe, reachable, reachable);\n}\n\n/**\n * The context lives on `globalThis`, not in module scope, because two copies of this module can\n * run in one page: the SDK bundles agent-runtime into its dist while a host such as Olympus imports\n * agent-runtime directly. The host provides its thread's outbox through its own copy and mounts the\n * SDK's queue tray, which reads it through the SDK's copy: one context per copy, and the tray would\n * never see a waiting message (`asset-attachment.ts` shares its registry the same way).\n */\nconst CHAT_OUTBOX_CONTEXT = Symbol.for(\"@athenaintel/agent-runtime/deep-agent-chat-outbox-context\");\n\ntype ChatOutboxContextHost = typeof globalThis & {\n [CHAT_OUTBOX_CONTEXT]?: Context<ChatOutbox | null>;\n};\n\nfunction sharedChatOutboxContext(): Context<ChatOutbox | null> {\n const host = globalThis as ChatOutboxContextHost;\n return (host[CHAT_OUTBOX_CONTEXT] ??= createContext<ChatOutbox | null>(null));\n}\n\n/** The mounted thread's chat outbox, shared from its runtime provider to its composer and tray. */\nexport const DeepAgentChatOutboxContext: Context<ChatOutbox | null> = sharedChatOutboxContext();\n\nexport function useDeepAgentChatOutboxContext(): ChatOutbox | null {\n return useContext(DeepAgentChatOutboxContext);\n}\n\n/** Forward statewire's command lifecycle to an outbox held in a ref (the runtime is made first). */\nexport function forwardChatOutboxCommand(\n outbox: { current: ChatOutbox | null },\n update: ChatOutboxCommandUpdate,\n): void {\n outbox.current?.observeCommand(update);\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-deep-agent-chat-outbox.js","sources":["../../../../agent-runtime/src/use-deep-agent-chat-outbox.ts"],"sourcesContent":["import {\n type Context,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport type { ChatOutboxAttachmentCheck } from \"./chat-outbox-attachments\";\nimport {\n type ChatOutbox,\n type ChatOutboxCommandUpdate,\n type ChatOutboxEvent,\n type ChatOutboxSendGate,\n type ChatOutboxSignal,\n type ChatOutboxSnapshot,\n createChatOutbox,\n} from \"./chat-outbox\";\nimport type { ChatOutboxEntry, ChatOutboxStore } from \"./chat-outbox-store\";\nimport { type ChatOutboxClientHost, createStatewireChatOutboxPort } from \"./chat-outbox-thread\";\n\nexport interface UseDeepAgentChatOutboxOptions {\n /** The thread's assistant-ui client (`useDeepAgentRuntime`'s result). */\n client: ChatOutboxClientHost;\n threadId: string;\n /** The conversation has never started (a new chat). */\n isNewChat: boolean;\n /**\n * This person's outbox store, one per person for the life of the page or app (a message the\n * device cannot keep waits in its memory, across thread switches); `null` keeps waiting messages\n * in this outbox alone.\n */\n store: ChatOutboxStore | null;\n /** Athena's reachability (the host's own model: the browser online AND agora answering). */\n reachability: ChatOutboxSignal;\n /** The app is in front (a visible tab, an active app, a focused desktop window). */\n foreground: ChatOutboxSignal;\n /** Give a cancelled message's text back to the composer. */\n restore: (entry: ChatOutboxEntry) => void;\n /**\n * Open another of the person's conversations holding waiting messages (the tray's Open); omit\n * it where the host cannot switch conversations.\n */\n openThread?: (target: { threadId: string; newChat: boolean }) => void;\n onEvent?: (event: ChatOutboxEvent) => void;\n /**\n * Asks Athena whether a waiting message's attachments can still be opened, right before it goes\n * (`createChatOutboxAttachmentCheck`); omitted, attachments go unchecked.\n */\n checkAttachments?: ChatOutboxAttachmentCheck;\n /**\n * Which waiting messages go together, and when (`ChatOutboxOptions.sendGate`): a host whose\n * messages carry per-send settings keeps a message with other settings than a send the\n * conversation has not taken yet waiting until it has. Read through a ref: a host may rebuild it.\n */\n sendGate?: ChatOutboxSendGate;\n /** Off: no outbox (a read-only or suspended surface); sends behave as before. */\n enabled?: boolean;\n /** The countdown before a waiting message is sent (tests); defaults to `CHAT_OUTBOX_COUNTDOWN_MS`. */\n countdownMs?: number;\n}\n\n/**\n * The chat outbox for the mounted thread (`chat-outbox.ts`), one per thread and store: a thread\n * switch disposes it and makes the next thread's. The host forwards statewire's command lifecycle\n * to {@link ChatOutbox.observeCommand} (its `useDeepAgentRuntime` `onCommandChange`), shares the\n * instance through {@link DeepAgentChatOutboxContext}, and has its composer call\n * {@link ChatOutbox.shouldHold} / {@link ChatOutbox.hold} before sending.\n */\nexport function useDeepAgentChatOutbox({\n client,\n threadId,\n isNewChat,\n store,\n reachability,\n foreground,\n restore,\n openThread,\n onEvent,\n checkAttachments,\n sendGate,\n enabled = true,\n countdownMs,\n}: UseDeepAgentChatOutboxOptions): ChatOutbox | null {\n const clientRef = useRef(client);\n clientRef.current = client;\n const restoreRef = useRef(restore);\n restoreRef.current = restore;\n const openThreadRef = useRef(openThread);\n openThreadRef.current = openThread;\n const canOpenThreads = openThread !== undefined;\n const onEventRef = useRef(onEvent);\n onEventRef.current = onEvent;\n const checkAttachmentsRef = useRef(checkAttachments);\n checkAttachmentsRef.current = checkAttachments;\n const checksAttachments = checkAttachments !== undefined;\n const sendGateRef = useRef(sendGate);\n sendGateRef.current = sendGate;\n const gatesSends = sendGate !== undefined;\n const reachabilityRef = useRef(reachability);\n reachabilityRef.current = reachability;\n const foregroundRef = useRef(foreground);\n foregroundRef.current = foreground;\n // The client's notifications reach the port through this set, so a client whose identity\n // changes mid-thread is followed without remaking the outbox (and losing its countdown).\n const [threadListeners] = useState(() => new Set<() => void>());\n // A host without an outbox (`enabled` false) is never subscribed to.\n useEffect(() => {\n if (!enabled) return undefined;\n return client.subscribe(() => {\n for (const listener of threadListeners) listener();\n });\n }, [client, threadListeners, enabled]);\n // Signals read through refs for the same reason: a host rebuilding its signal objects per render\n // never remakes the outbox.\n const [stableSignals] = useState(() => {\n const forward = (pick: () => ChatOutboxSignal): ChatOutboxSignal => ({\n get: () => pick().get(),\n subscribe: (listener) => pick().subscribe(listener),\n });\n return {\n reachability: forward(() => reachabilityRef.current),\n foreground: forward(() => foregroundRef.current),\n };\n });\n const isNewChatRef = useRef(isNewChat);\n isNewChatRef.current = isNewChat;\n\n const [outbox, setOutbox] = useState<ChatOutbox | null>(null);\n // Made in an effect, not during render: the outbox subscribes to its signals and reads its\n // store, and a render React throws away must not leave those behind.\n // biome-ignore lint/correctness/useExhaustiveDependencies: one outbox per thread and store; everything else is read through refs.\n useEffect(() => {\n if (!enabled || !threadId) {\n setOutbox(null);\n return undefined;\n }\n const created = createChatOutbox({\n threadId,\n // Read at each hold: a chat that starts while mounted stamps its later messages as started.\n isNewChat: () => isNewChatRef.current,\n store,\n reachability: stableSignals.reachability,\n foreground: stableSignals.foreground,\n thread: {\n ...createStatewireChatOutboxPort({ client: () => clientRef.current }),\n subscribe: (listener) => {\n threadListeners.add(listener);\n return () => {\n threadListeners.delete(listener);\n };\n },\n },\n restore: (entry) => restoreRef.current(entry),\n ...(canOpenThreads && {\n openThread: (target: { threadId: string; newChat: boolean }) => openThreadRef.current?.(target),\n }),\n onEvent: (event) => onEventRef.current?.(event),\n ...(checksAttachments && {\n checkAttachments: (attachments: Parameters<ChatOutboxAttachmentCheck>[0]) =>\n checkAttachmentsRef.current?.(attachments) ?? Promise.resolve(\"ok\" as const),\n }),\n ...(gatesSends && {\n sendGate: {\n keyOf: (entry: ChatOutboxEntry) => sendGateRef.current?.keyOf(entry) ?? \"\",\n mayHandOff: (entry: ChatOutboxEntry) => sendGateRef.current?.mayHandOff(entry) ?? true,\n subscribe: (listener: () => void) => sendGateRef.current?.subscribe(listener) ?? (() => undefined),\n },\n }),\n ...(countdownMs !== undefined && { countdownMs }),\n });\n setOutbox(created);\n return () => {\n created.dispose();\n setOutbox((current) => (current === created ? null : current));\n };\n }, [enabled, threadId, store, countdownMs, canOpenThreads, checksAttachments, gatesSends]);\n // During a thread switch's first render the previous thread's outbox is still in state.\n return outbox !== null && outbox.threadId === threadId ? outbox : null;\n}\n\nconst EMPTY_SNAPSHOT: ChatOutboxSnapshot = {\n rows: [],\n countdownEndsAt: null,\n durable: true,\n loaded: true,\n elsewhere: [],\n};\nconst noopSubscribe = () => () => undefined;\n\n/** The outbox's rows and countdown, re-rendering on every change (`null` outbox: nothing waits). */\nexport function useChatOutboxSnapshot(outbox: ChatOutbox | null): ChatOutboxSnapshot {\n return useSyncExternalStore(\n outbox ? outbox.subscribe : noopSubscribe,\n () => outbox?.getSnapshot() ?? EMPTY_SNAPSHOT,\n () => outbox?.getSnapshot() ?? EMPTY_SNAPSHOT,\n );\n}\n\n/**\n * Whether Athena can be reached, as the thread's outbox reads it: `true` without an outbox (a host\n * that keeps none sends and edits as before). What a sent message's Edit is offered on; a boolean,\n * so a reader re-renders only when it flips.\n */\nexport function useChatOutboxReachable(outbox: ChatOutbox | null): boolean {\n const subscribe = useCallback(\n (listener: () => void) => (outbox ? outbox.reachability.subscribe(listener) : () => undefined),\n [outbox],\n );\n const reachable = () => outbox?.reachability.get() ?? true;\n return useSyncExternalStore(subscribe, reachable, reachable);\n}\n\n/**\n * The context lives on `globalThis`, not in module scope, because two copies of this module can\n * run in one page: the SDK bundles agent-runtime into its dist while a host such as Olympus imports\n * agent-runtime directly. The host provides its thread's outbox through its own copy and mounts the\n * SDK's queue tray, which reads it through the SDK's copy: one context per copy, and the tray would\n * never see a waiting message (`asset-attachment.ts` shares its registry the same way).\n */\nconst CHAT_OUTBOX_CONTEXT = Symbol.for(\"@athenaintel/agent-runtime/deep-agent-chat-outbox-context\");\n\ntype ChatOutboxContextHost = typeof globalThis & {\n [CHAT_OUTBOX_CONTEXT]?: Context<ChatOutbox | null>;\n};\n\nfunction sharedChatOutboxContext(): Context<ChatOutbox | null> {\n const host = globalThis as ChatOutboxContextHost;\n return (host[CHAT_OUTBOX_CONTEXT] ??= createContext<ChatOutbox | null>(null));\n}\n\n/** The mounted thread's chat outbox, shared from its runtime provider to its composer and tray. */\nexport const DeepAgentChatOutboxContext: Context<ChatOutbox | null> = sharedChatOutboxContext();\n\nexport function useDeepAgentChatOutboxContext(): ChatOutbox | null {\n return useContext(DeepAgentChatOutboxContext);\n}\n\n/** Forward statewire's command lifecycle to an outbox held in a ref (the runtime is made first). */\nexport function forwardChatOutboxCommand(\n outbox: { current: ChatOutbox | null },\n update: ChatOutboxCommandUpdate,\n): void {\n outbox.current?.observeCommand(update);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -55,6 +55,7 @@ function useDeepAgentRuntime(options) {
|
|
|
55
55
|
session = false,
|
|
56
56
|
useClient,
|
|
57
57
|
runConfig,
|
|
58
|
+
onBeforeSendRetry,
|
|
58
59
|
onError,
|
|
59
60
|
onStateChange,
|
|
60
61
|
onCommandChange,
|
|
@@ -71,6 +72,8 @@ function useDeepAgentRuntime(options) {
|
|
|
71
72
|
onCommandChangeRef.current = onCommandChange;
|
|
72
73
|
const diagnosticsRef = require$$0.useRef(threadOptions.diagnostics);
|
|
73
74
|
diagnosticsRef.current = threadOptions.diagnostics;
|
|
75
|
+
const onBeforeSendRetryRef = require$$0.useRef(onBeforeSendRetry);
|
|
76
|
+
onBeforeSendRetryRef.current = onBeforeSendRetry;
|
|
74
77
|
const clientRef = require$$0.useRef(null);
|
|
75
78
|
const failureStampsRef = require$$0.useRef(
|
|
76
79
|
void 0
|
|
@@ -105,7 +108,11 @@ function useDeepAgentRuntime(options) {
|
|
|
105
108
|
var _a;
|
|
106
109
|
return ((_a = failureStampsRef.current) == null ? void 0 : _a.call(failureStampsRef)) ?? null;
|
|
107
110
|
},
|
|
108
|
-
diagnostics: () => diagnosticsRef.current
|
|
111
|
+
diagnostics: () => diagnosticsRef.current,
|
|
112
|
+
beforeRetry: (messageId) => {
|
|
113
|
+
var _a;
|
|
114
|
+
return (_a = onBeforeSendRetryRef.current) == null ? void 0 : _a.call(onBeforeSendRetryRef, messageId);
|
|
115
|
+
}
|
|
109
116
|
}),
|
|
110
117
|
[attachId]
|
|
111
118
|
);
|
|
@@ -167,13 +174,24 @@ function useDeepAgentRuntime(options) {
|
|
|
167
174
|
getRunConfigRef.current = runConfig == null ? void 0 : runConfig.get;
|
|
168
175
|
const hasRunConfig = runConfig !== void 0;
|
|
169
176
|
const runConfigKey = runConfig == null ? void 0 : runConfig.key;
|
|
177
|
+
const subscribeRunConfigRef = require$$0.useRef(runConfig == null ? void 0 : runConfig.subscribe);
|
|
178
|
+
subscribeRunConfigRef.current = runConfig == null ? void 0 : runConfig.subscribe;
|
|
179
|
+
const hasRunConfigSubscribe = (runConfig == null ? void 0 : runConfig.subscribe) !== void 0;
|
|
170
180
|
require$$0.useEffect(() => {
|
|
171
181
|
if (!hasRunConfig) return void 0;
|
|
172
|
-
return modelContext.registerDeepAgentRunConfig(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
182
|
+
return modelContext.registerDeepAgentRunConfig(
|
|
183
|
+
client,
|
|
184
|
+
() => {
|
|
185
|
+
var _a;
|
|
186
|
+
return ((_a = getRunConfigRef.current) == null ? void 0 : _a.call(getRunConfigRef)) ?? {};
|
|
187
|
+
},
|
|
188
|
+
hasRunConfigSubscribe ? (listener) => {
|
|
189
|
+
var _a;
|
|
190
|
+
return ((_a = subscribeRunConfigRef.current) == null ? void 0 : _a.call(subscribeRunConfigRef, listener)) ?? (() => {
|
|
191
|
+
});
|
|
192
|
+
} : void 0
|
|
193
|
+
);
|
|
194
|
+
}, [client, hasRunConfig, runConfigKey, hasRunConfigSubscribe]);
|
|
177
195
|
return client;
|
|
178
196
|
}
|
|
179
197
|
exports.canPersistDeepAgentSession = canPersistDeepAgentSession;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-deep-agent-runtime.cjs","sources":["../../../../agent-runtime/src/use-deep-agent-runtime.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { isStatewireChannelTeardown } from './errors';\nimport { type DeepAgentModelContextHost, registerDeepAgentRunConfig } from './model-context';\nimport {\n isStorageQuotaError,\n QuotaSafeLocalStorageSession,\n readFailedSendStampsFromRecord,\n readPersistedFailedSendStamps,\n type SessionStorageDegradation,\n} from './quota-safe-local-storage-session';\nimport { createDeepAgentSendRecovery } from './send-recovery';\nimport { INITIAL_SESSION_LANE_EPOCH, nextSessionLaneEpoch } from './session-lane-scope';\nimport type { StatewireTransportOptions } from './statewire-transport';\nimport { readStatewireFailedSendLane } from './thread-extras';\nimport { isConfigSyncUnavailableRejection } from './use-deep-agent-config';\nimport {\n deepAgentAttachId,\n useDeepAgentThread,\n type UseDeepAgentThreadOptions,\n} from './use-deep-agent-thread';\n\n/** The thread element `useDeepAgentThread` produces — what the host's `useAui`\n * consumes (`useAui({ thread })`). */\nexport type DeepAgentThreadElement = ReturnType<typeof useDeepAgentThread>;\n\n/**\n * Durable command-lane persistence for the thread — where the lane identity and\n * the commands still awaiting a durable verdict live, so a reload resumes the\n * lane instead of minting a new client.\n *\n * - `local-storage`: browser hosts. Backed by {@link QuotaSafeLocalStorageSession}\n * (the upstream `LocalStorageSession` contract with Athena keys, legacy\n * metadata, and retention). A storage failure after mounting — the origin\n * out of quota after statewire's own sweep, a lockdown-mode write refusal —\n * degrades the session to memory-only persistence and is reported through\n * `onStorageDegraded` (default: one console warning); it never stops the\n * connection. Guarded by a shared availability probe: SSR, storage-blocked\n * iframes, and locked-down browsers keep the session process-local, exactly\n * as before the seam existed, while an origin that is merely full mounts the\n * session and lets it degrade. `key` scopes the stored lane — a thread id is\n * only meaningful against the backend (and, where relevant, principal) that\n * minted it, so include the sync-host URL.\n * - `session-store`: React Native hosts pass a hydrated library store backed\n * by AsyncStorage. Its synchronous `load()` is\n * also the send-recovery freshness gate's view of the persisted failed lane,\n * so a store that persists `failed` gets the same 24 h staleness rule as the\n * browser hosts.\n * - `pending`: the principal the lane is scoped by is not known yet (the\n * host's signed-in user is still loading). See {@link DeepAgentPendingSession}.\n * - `false`: process-local session (a reload mints a new client).\n *\n * A `local-storage` `key` names one principal's lanes (and one backend's). The\n * statewire session that mounts on a lane keeps the client id, lane clocks and\n * journal it loaded there, and everything in that journal is sent under\n * whatever credential the transport holds at its next attach. So a mounted\n * session never moves to another key: when the key starts naming another\n * principal's lanes for the same thread (another account signed in under a\n * mounted chat), the runtime starts a new lane epoch\n * (`UseDeepAgentThreadOptions['laneEpoch']`). The whole statewire thread\n * remounts on the new principal's lane and attaches afresh, as that lane's own\n * client, with nothing remembered or pooled from before. The previous\n * principal's record stays where it is, with its client id and whatever it had\n * journaled and not yet delivered: that principal's next mount on this browser\n * resends it as them, and the retention sweep retires it otherwise. It is never\n * sent as anyone else. A key for the same principal (a token refresh, a fresh\n * `key` function) and a thread switch keep the lane as it is. A host whose\n * principal arrives after mount passes `pending` until it does, never a\n * stand-in key: moving off a stand-in would be a principal change, a second\n * attach.\n */\nexport type DeepAgentSessionPersistence =\n | {\n kind: 'local-storage';\n key: (threadId: string) => string;\n onStorageDegraded?: (degradation: SessionStorageDegradation) => void;\n }\n | { kind: 'session-store'; store: NonNullable<StatewireTransportOptions['sessionStore']> }\n | DeepAgentPendingSession\n | false;\n\n/**\n * The durable lane's principal is not known yet. A lane belongs to one\n * principal and a mounted session cannot move to another lane (see\n * {@link DeepAgentSessionPersistence}), so nothing is mounted, nothing is\n * journaled and the transport stays parked — no dial — until the host passes\n * the real persistence. The park is an attach identity of its own: the attach\n * that follows makes every per-attach decision afresh (the remembered-snapshot\n * seed, the pooled hello, which the park leaves in the pool) and dials the real\n * lane once, as the thread's first attach. The park carries no connection\n * message, so it reads like a never-started chat rather than a paused one; a\n * host suspension, when one applies, wins.\n */\nexport type DeepAgentPendingSession = { kind: 'pending' };\n\n/**\n * The attach key a pending-lane park runs under. It differs from every key the\n * host passes, so the real attach after it is a new attach identity.\n */\nexport function pendingSessionAttachKey(attachKey: string | number | undefined): string {\n return `session-pending:${attachKey ?? ''}`;\n}\n\n/** Structural view of the browser storage global — this package compiles\n * without the DOM lib (React Native honesty; see `globals.d.ts`). */\ntype StorageGlobals = {\n localStorage?: {\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n };\n navigator?: { locks?: unknown };\n};\n\n/**\n * Whether a localStorage-backed session can persist here. A write probe, not a\n * `typeof` check: storage-blocked iframes (SDK embeds under third-party-storage\n * blocking) throw from the `localStorage` accessor itself, and lockdown-mode\n * origins throw from `setItem` — each of those must mean \"stay process-local\",\n * never a crash at mount. An origin that is merely out of quota still reads\n * and takes locks, so it mounts the session: {@link QuotaSafeLocalStorageSession}\n * degrades that to memory-only persistence and recovers when room frees,\n * where staying process-local would give up cross-tab sharing for the whole\n * page load over a condition that clears.\n *\n * The Web Locks API is probed too: the session storage serializes the shared\n * record across tabs through `navigator.locks` and refuses to mount without it\n * (the same precondition as the vendor `LocalStorageSession`), so an origin\n * with localStorage but no Web Locks — older WebViews, Safari before 15.4 —\n * must also stay process-local instead of throwing at mount.\n */\nexport function canPersistDeepAgentSession(): boolean {\n try {\n const globals = globalThis as StorageGlobals;\n if (!globals.navigator?.locks) return false;\n const storage = globals.localStorage;\n if (!storage) return false;\n const probeKey = '__athena_statewire_session_probe__';\n try {\n storage.setItem(probeKey, probeKey);\n storage.removeItem(probeKey);\n } catch (error) {\n return isStorageQuotaError(error);\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/** A host session store's current record, or `null` when the read throws —\n * the recovery gate must never take the runtime down over a storage fault. */\nfunction loadSessionRecord(\n store: NonNullable<StatewireTransportOptions['sessionStore']>\n): ReturnType<NonNullable<StatewireTransportOptions['sessionStore']>['load']> | null {\n try {\n return store.load();\n } catch {\n return null;\n }\n}\n\n/**\n * The shared tail of every surface's transport error policy: channel teardown\n * is lifecycle noise (logged at debug), as is `config_sync_unavailable` — only\n * `config/*` writes emit it, and the host's config sync already handles it\n * through its own command promise (regionally-pinned workspaces run on\n * local-only config); everything else is a real transport error. Hosts with their own error chain\n * (paywall, telemetry, send-failure stores) end it with this so the fallback\n * logging stays uniform per surface.\n */\nexport function logUnhandledStatewireError(surface: string, error: Error): void {\n if (isStatewireChannelTeardown(error)) {\n console.debug(`[${surface}] statewire channel teardown:`, error);\n return;\n }\n if (isConfigSyncUnavailableRejection(error)) {\n console.debug(`[${surface}] config sync unavailable (handled by config sync):`, error);\n return;\n }\n console.error(`[${surface}] statewire transport error:`, error);\n}\n\n/**\n * The imperative thread-state slice of an assistant-ui client — structural so\n * web and native `useAui` results both satisfy it. The send-recovery layer\n * reads the statewire thread extras through it (the sanctioned imperative\n * pattern: `aui.thread().getState()` inside callbacks/effects, never render).\n */\nexport interface DeepAgentThreadStateClientHost {\n thread: () => { getState: () => { extras?: unknown } };\n subscribe: (listener: () => void) => () => void;\n}\n\n/** Deep-agent runConfig registration on the ambient model context (see\n * {@link registerDeepAgentRunConfig}). */\nexport interface DeepAgentRunConfigRegistration {\n /**\n * Builds the `runConfig` that drives the run. The core pulls it when the\n * model context notifies (mount and each key change); the server reads that\n * synced value at command admission. The per-render ref makes each pull use\n * the latest closure but does not trigger a pull itself.\n */\n get: () => Record<string, unknown>;\n /**\n * Re-registers the provider and notifies model-context sync when this value\n * changes (reference equality). Include getter inputs that can change while\n * mounted; they reach the server only after a key change triggers a pull.\n */\n key?: unknown;\n}\n\nexport interface UseDeepAgentRuntimeOptions<\n TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost,\n> extends Omit<UseDeepAgentThreadOptions, 'storage' | 'sessionStore' | 'laneEpoch'> {\n /** Host surface tag for the shared fallback error logging (e.g.\n * `'AthenaDeepAgent'`, `'AthenaSDK'`, `'ChromeStatewire'`). */\n surface: string;\n /** Durable command-lane persistence; defaults to process-local. */\n session?: DeepAgentSessionPersistence;\n /**\n * Builds the host's assistant-ui client from the thread element — pass the\n * platform `useAui` (web hosts from `@assistant-ui/react`, React Native\n * hosts from `@assistant-ui/react-native`; this package imports neither).\n * Called unconditionally on every render, so it MUST be (or call) a hook\n * following the rules of hooks — define it at module scope with a `use*`\n * name: `const useHostClient = (thread: DeepAgentThreadElement) =>\n * useAui({ thread })`.\n */\n useClient: (thread: DeepAgentThreadElement) => TClient;\n /** Registers the deep-agent runConfig on the client's model context. Hosts\n * that register elsewhere (e.g. a bridge component mounted under the\n * provider) omit it. */\n runConfig?: DeepAgentRunConfigRegistration;\n}\n\n/**\n * The configurable deep-agent statewire runtime — the one hook every Athena\n * chat surface (Olympus, `@athenaintel/react` SDK, Chrome extension, Office\n * add-in, mobile) mounts its assistant-ui client through.\n *\n * Composes the pieces every surface previously wired by hand:\n * `useDeepAgentThread` (attach lifecycle, auth parking, lane projection,\n * connection/running observers), durable session persistence with the shared\n * availability guard, transport-level send recovery (failed sends the server\n * never judged auto-retry under their original message id — `send-recovery.ts`),\n * the platform client via `useClient`, and the\n * runConfig model-context registration on provider notifications. Everything\n * host-specific — auth headers, run-config building, error policy, adapters,\n * diagnostics recorders — stays an option, so per-surface behavior is\n * configuration, not a fork.\n */\nexport function useDeepAgentRuntime<\n TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost,\n>(options: UseDeepAgentRuntimeOptions<TClient>): TClient {\n const {\n surface,\n session = false,\n useClient,\n runConfig,\n onError,\n onStateChange,\n onCommandChange,\n ...threadOptions\n } = options;\n\n // Probed once per mount, like every browser host did individually: an origin\n // that cannot persist stays process-local for the mount's lifetime.\n const [canPersistLocalSession] = useState(canPersistDeepAgentSession);\n\n // Per-render refs so the transport never remounts on a new closure identity\n // and always reads the latest policy.\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n const surfaceRef = useRef(surface);\n surfaceRef.current = surface;\n const onStateChangeRef = useRef(onStateChange);\n onStateChangeRef.current = onStateChange;\n const onCommandChangeRef = useRef(onCommandChange);\n onCommandChangeRef.current = onCommandChange;\n const diagnosticsRef = useRef(threadOptions.diagnostics);\n diagnosticsRef.current = threadOptions.diagnostics;\n const clientRef = useRef<TClient | null>(null);\n\n // Where the persisted session record lives, for the recovery freshness gate\n // (read per sweep — undefined means \"no persistence, everything is fresh\").\n const failureStampsRef = useRef<(() => ReadonlyMap<string, number> | null) | undefined>(\n undefined\n );\n failureStampsRef.current =\n session === false || session.kind === 'pending'\n ? undefined\n : session.kind === 'local-storage'\n ? canPersistLocalSession\n ? () => readPersistedFailedSendStamps(session.key(threadOptions.threadId))\n : undefined\n : () => readFailedSendStampsFromRecord(loadSessionRecord(session.store));\n\n // A pending lane parks the thread under an attach identity of its own (see\n // `DeepAgentPendingSession`), so the attach once the lane is known is new.\n const sessionPending = session !== false && session.kind === 'pending';\n const attachKey = sessionPending\n ? pendingSessionAttachKey(threadOptions.attachKey)\n : threadOptions.attachKey;\n\n // The lane's scope, as the host's `key` names it: whose lanes this mount\n // runs on. A key that starts naming another principal's lanes starts a new\n // lane epoch, which remounts the whole thread on the new lane instead of\n // moving the mounted session there (see `DeepAgentSessionPersistence`).\n // Tracked whether or not this origin can persist: a process-local session\n // holds the previous principal's journal in memory just the same. A pending\n // lane keeps the last known scope, so a sign-out and a sign-in as someone\n // else with a gap between them is still a change.\n const laneKey = session !== false && session.kind === 'local-storage' ? session.key : null;\n const [laneEpochState, setLaneEpochState] = useState(() =>\n nextSessionLaneEpoch({\n previous: INITIAL_SESSION_LANE_EPOCH,\n threadId: threadOptions.threadId,\n key: laneKey,\n })\n );\n const nextLaneEpochState = nextSessionLaneEpoch({\n previous: laneEpochState,\n threadId: threadOptions.threadId,\n key: laneKey,\n });\n // React's derived-state update: re-renders at once with the new epoch, and\n // this render already uses it, so no render pairs the new key with the\n // mounted thread.\n if (nextLaneEpochState !== laneEpochState) setLaneEpochState(nextLaneEpochState);\n const laneEpoch = nextLaneEpochState.epoch;\n\n // Send recovery, keyed by attach identity exactly like the thread's session\n // (a switch, re-attach or new lane epoch gets fresh hydration gating and\n // retry budgets, so it never re-dispatches a previous principal's failed\n // sends). The controller re-dispatches failed sends the server never judged;\n // see `send-recovery.ts` for the contract.\n const attachId = deepAgentAttachId({ threadId: threadOptions.threadId, attachKey, laneEpoch });\n // biome-ignore lint/correctness/useExhaustiveDependencies: keyed by attach identity on purpose; every other input is read through a per-render ref.\n const sendRecovery = useMemo(\n () =>\n createDeepAgentSendRecovery({\n lane: () => {\n const client = clientRef.current;\n if (!client) return null;\n return readStatewireFailedSendLane(client.thread().getState().extras);\n },\n failureStamps: () => failureStampsRef.current?.() ?? null,\n diagnostics: () => diagnosticsRef.current,\n }),\n [attachId]\n );\n useEffect(() => () => sendRecovery.dispose(), [sendRecovery]);\n const sendRecoveryRef = useRef(sendRecovery);\n sendRecoveryRef.current = sendRecovery;\n\n const thread = useDeepAgentThread({\n ...threadOptions,\n ...(attachKey !== undefined && { attachKey }),\n laneEpoch,\n // Parked without a message (a host suspension, when one applies, wins):\n // nothing dials and nothing journals until the lane is known. What paints\n // the first frame is the real attach's to report, not the park's.\n ...(sessionPending && {\n suspended: threadOptions.suspended || {},\n onAttachSource: undefined,\n }),\n // A pooled dial for the thread is the real attach's to take: a park that\n // only waits for the lane neither takes nor discards it. A host suspension\n // still discards it, as it does without a pending lane.\n ...(sessionPending && !threadOptions.suspended && { prewarmPool: undefined }),\n ...(session !== false &&\n session.kind === 'local-storage' &&\n canPersistLocalSession && {\n storage: {\n session: QuotaSafeLocalStorageSession({\n threadId: threadOptions.threadId,\n key: session.key,\n ...(session.onStorageDegraded === undefined\n ? {}\n : { onStorageDegraded: session.onStorageDegraded }),\n }),\n },\n }),\n ...(session !== false && session.kind === 'session-store' && { sessionStore: session.store }),\n onStateChange: (state, ctx) => {\n sendRecoveryRef.current.observeStateChange(state);\n onStateChangeRef.current?.(state, ctx);\n },\n onCommandChange: (update, ctx) => {\n sendRecoveryRef.current.observeCommand(update);\n onCommandChangeRef.current?.(update, ctx);\n },\n onError: (error) => {\n const handler = onErrorRef.current;\n if (handler) {\n handler(error);\n return;\n }\n logUnhandledStatewireError(surfaceRef.current, error);\n },\n });\n\n const client = useClient(thread);\n clientRef.current = client;\n\n // A locked shared journal can publish failed history after onCommandChange,\n // with no canonical state change to wake recovery. Observe the client that\n // owns the visible failed lane; the controller guards in-flight retries.\n useEffect(() => {\n const unsubscribe = client.subscribe(() => sendRecovery.observeFailedLaneChange());\n sendRecovery.observeFailedLaneChange();\n return unsubscribe;\n }, [client, sendRecovery]);\n\n // Commands travel bare, so the runConfig that drives the run rides the\n // ambient model context: `contextSync: 'command-send'` mirrors it to the\n // server, which reads `config.runConfig` at command admission. The core pulls\n // on model-context notifications; the per-render ref ensures each pull uses\n // the latest closure but does not trigger a pull itself.\n const getRunConfigRef = useRef(runConfig?.get);\n getRunConfigRef.current = runConfig?.get;\n const hasRunConfig = runConfig !== undefined;\n const runConfigKey = runConfig?.key;\n useEffect(() => {\n if (!hasRunConfig) return undefined;\n return registerDeepAgentRunConfig(client, () => getRunConfigRef.current?.() ?? {});\n }, [client, hasRunConfig, runConfigKey]);\n\n return client;\n}\n\n"],"names":["isStorageQuotaError","isStatewireChannelTeardown","isConfigSyncUnavailableRejection","useState","useRef","readPersistedFailedSendStamps","readFailedSendStampsFromRecord","nextSessionLaneEpoch","INITIAL_SESSION_LANE_EPOCH","deepAgentAttachId","sendRecovery","useMemo","createDeepAgentSendRecovery","client","readStatewireFailedSendLane","useEffect","useDeepAgentThread","QuotaSafeLocalStorageSession","registerDeepAgentRunConfig"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-deep-agent-runtime.cjs","sources":["../../../../agent-runtime/src/use-deep-agent-runtime.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { isStatewireChannelTeardown } from './errors';\nimport { type DeepAgentModelContextHost, registerDeepAgentRunConfig } from './model-context';\nimport {\n isStorageQuotaError,\n QuotaSafeLocalStorageSession,\n readFailedSendStampsFromRecord,\n readPersistedFailedSendStamps,\n type SessionStorageDegradation,\n} from './quota-safe-local-storage-session';\nimport { createDeepAgentSendRecovery } from './send-recovery';\nimport { INITIAL_SESSION_LANE_EPOCH, nextSessionLaneEpoch } from './session-lane-scope';\nimport type { StatewireTransportOptions } from './statewire-transport';\nimport { readStatewireFailedSendLane } from './thread-extras';\nimport { isConfigSyncUnavailableRejection } from './use-deep-agent-config';\nimport {\n deepAgentAttachId,\n useDeepAgentThread,\n type UseDeepAgentThreadOptions,\n} from './use-deep-agent-thread';\n\n/** The thread element `useDeepAgentThread` produces — what the host's `useAui`\n * consumes (`useAui({ thread })`). */\nexport type DeepAgentThreadElement = ReturnType<typeof useDeepAgentThread>;\n\n/**\n * Durable command-lane persistence for the thread — where the lane identity and\n * the commands still awaiting a durable verdict live, so a reload resumes the\n * lane instead of minting a new client.\n *\n * - `local-storage`: browser hosts. Backed by {@link QuotaSafeLocalStorageSession}\n * (the upstream `LocalStorageSession` contract with Athena keys, legacy\n * metadata, and retention). A storage failure after mounting — the origin\n * out of quota after statewire's own sweep, a lockdown-mode write refusal —\n * degrades the session to memory-only persistence and is reported through\n * `onStorageDegraded` (default: one console warning); it never stops the\n * connection. Guarded by a shared availability probe: SSR, storage-blocked\n * iframes, and locked-down browsers keep the session process-local, exactly\n * as before the seam existed, while an origin that is merely full mounts the\n * session and lets it degrade. `key` scopes the stored lane — a thread id is\n * only meaningful against the backend (and, where relevant, principal) that\n * minted it, so include the sync-host URL.\n * - `session-store`: React Native hosts pass a hydrated library store backed\n * by AsyncStorage. Its synchronous `load()` is\n * also the send-recovery freshness gate's view of the persisted failed lane,\n * so a store that persists `failed` gets the same 24 h staleness rule as the\n * browser hosts.\n * - `pending`: the principal the lane is scoped by is not known yet (the\n * host's signed-in user is still loading). See {@link DeepAgentPendingSession}.\n * - `false`: process-local session (a reload mints a new client).\n *\n * A `local-storage` `key` names one principal's lanes (and one backend's). The\n * statewire session that mounts on a lane keeps the client id, lane clocks and\n * journal it loaded there, and everything in that journal is sent under\n * whatever credential the transport holds at its next attach. So a mounted\n * session never moves to another key: when the key starts naming another\n * principal's lanes for the same thread (another account signed in under a\n * mounted chat), the runtime starts a new lane epoch\n * (`UseDeepAgentThreadOptions['laneEpoch']`). The whole statewire thread\n * remounts on the new principal's lane and attaches afresh, as that lane's own\n * client, with nothing remembered or pooled from before. The previous\n * principal's record stays where it is, with its client id and whatever it had\n * journaled and not yet delivered: that principal's next mount on this browser\n * resends it as them, and the retention sweep retires it otherwise. It is never\n * sent as anyone else. A key for the same principal (a token refresh, a fresh\n * `key` function) and a thread switch keep the lane as it is. A host whose\n * principal arrives after mount passes `pending` until it does, never a\n * stand-in key: moving off a stand-in would be a principal change, a second\n * attach.\n */\nexport type DeepAgentSessionPersistence =\n | {\n kind: 'local-storage';\n key: (threadId: string) => string;\n onStorageDegraded?: (degradation: SessionStorageDegradation) => void;\n }\n | { kind: 'session-store'; store: NonNullable<StatewireTransportOptions['sessionStore']> }\n | DeepAgentPendingSession\n | false;\n\n/**\n * The durable lane's principal is not known yet. A lane belongs to one\n * principal and a mounted session cannot move to another lane (see\n * {@link DeepAgentSessionPersistence}), so nothing is mounted, nothing is\n * journaled and the transport stays parked — no dial — until the host passes\n * the real persistence. The park is an attach identity of its own: the attach\n * that follows makes every per-attach decision afresh (the remembered-snapshot\n * seed, the pooled hello, which the park leaves in the pool) and dials the real\n * lane once, as the thread's first attach. The park carries no connection\n * message, so it reads like a never-started chat rather than a paused one; a\n * host suspension, when one applies, wins.\n */\nexport type DeepAgentPendingSession = { kind: 'pending' };\n\n/**\n * The attach key a pending-lane park runs under. It differs from every key the\n * host passes, so the real attach after it is a new attach identity.\n */\nexport function pendingSessionAttachKey(attachKey: string | number | undefined): string {\n return `session-pending:${attachKey ?? ''}`;\n}\n\n/** Structural view of the browser storage global — this package compiles\n * without the DOM lib (React Native honesty; see `globals.d.ts`). */\ntype StorageGlobals = {\n localStorage?: {\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n };\n navigator?: { locks?: unknown };\n};\n\n/**\n * Whether a localStorage-backed session can persist here. A write probe, not a\n * `typeof` check: storage-blocked iframes (SDK embeds under third-party-storage\n * blocking) throw from the `localStorage` accessor itself, and lockdown-mode\n * origins throw from `setItem` — each of those must mean \"stay process-local\",\n * never a crash at mount. An origin that is merely out of quota still reads\n * and takes locks, so it mounts the session: {@link QuotaSafeLocalStorageSession}\n * degrades that to memory-only persistence and recovers when room frees,\n * where staying process-local would give up cross-tab sharing for the whole\n * page load over a condition that clears.\n *\n * The Web Locks API is probed too: the session storage serializes the shared\n * record across tabs through `navigator.locks` and refuses to mount without it\n * (the same precondition as the vendor `LocalStorageSession`), so an origin\n * with localStorage but no Web Locks — older WebViews, Safari before 15.4 —\n * must also stay process-local instead of throwing at mount.\n */\nexport function canPersistDeepAgentSession(): boolean {\n try {\n const globals = globalThis as StorageGlobals;\n if (!globals.navigator?.locks) return false;\n const storage = globals.localStorage;\n if (!storage) return false;\n const probeKey = '__athena_statewire_session_probe__';\n try {\n storage.setItem(probeKey, probeKey);\n storage.removeItem(probeKey);\n } catch (error) {\n return isStorageQuotaError(error);\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/** A host session store's current record, or `null` when the read throws —\n * the recovery gate must never take the runtime down over a storage fault. */\nfunction loadSessionRecord(\n store: NonNullable<StatewireTransportOptions['sessionStore']>\n): ReturnType<NonNullable<StatewireTransportOptions['sessionStore']>['load']> | null {\n try {\n return store.load();\n } catch {\n return null;\n }\n}\n\n/**\n * The shared tail of every surface's transport error policy: channel teardown\n * is lifecycle noise (logged at debug), as is `config_sync_unavailable` — only\n * `config/*` writes emit it, and the host's config sync already handles it\n * through its own command promise (regionally-pinned workspaces run on\n * local-only config); everything else is a real transport error. Hosts with their own error chain\n * (paywall, telemetry, send-failure stores) end it with this so the fallback\n * logging stays uniform per surface.\n */\nexport function logUnhandledStatewireError(surface: string, error: Error): void {\n if (isStatewireChannelTeardown(error)) {\n console.debug(`[${surface}] statewire channel teardown:`, error);\n return;\n }\n if (isConfigSyncUnavailableRejection(error)) {\n console.debug(`[${surface}] config sync unavailable (handled by config sync):`, error);\n return;\n }\n console.error(`[${surface}] statewire transport error:`, error);\n}\n\n/**\n * The imperative thread-state slice of an assistant-ui client — structural so\n * web and native `useAui` results both satisfy it. The send-recovery layer\n * reads the statewire thread extras through it (the sanctioned imperative\n * pattern: `aui.thread().getState()` inside callbacks/effects, never render).\n */\nexport interface DeepAgentThreadStateClientHost {\n thread: () => { getState: () => { extras?: unknown } };\n subscribe: (listener: () => void) => () => void;\n}\n\n/** Deep-agent runConfig registration on the ambient model context (see\n * {@link registerDeepAgentRunConfig}). */\nexport interface DeepAgentRunConfigRegistration {\n /**\n * Builds the `runConfig` that drives the run. The core pulls it when the\n * model context notifies (mount and each key change); the server reads that\n * synced value at command admission. The per-render ref makes each pull use\n * the latest closure but does not trigger a pull itself.\n */\n get: () => Record<string, unknown>;\n /**\n * Re-registers the provider and notifies model-context sync when this value\n * changes (reference equality). Include getter inputs that can change while\n * mounted; they reach the server only after a key change triggers a pull.\n */\n key?: unknown;\n /**\n * Notifies when a getter input changes outside React, so model-context sync\n * pulls `get` at once, before a command sent right after leaves (Olympus\n * stages a send's named agents right before `composer.send`). Subscribed\n * when the provider registers; a key change subscribes again.\n */\n subscribe?: (listener: () => void) => () => void;\n}\n\nexport interface UseDeepAgentRuntimeOptions<\n TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost,\n> extends Omit<UseDeepAgentThreadOptions, 'storage' | 'sessionStore' | 'laneEpoch'> {\n /** Host surface tag for the shared fallback error logging (e.g.\n * `'AthenaDeepAgent'`, `'AthenaSDK'`, `'ChromeStatewire'`). */\n surface: string;\n /** Durable command-lane persistence; defaults to process-local. */\n session?: DeepAgentSessionPersistence;\n /**\n * Builds the host's assistant-ui client from the thread element — pass the\n * platform `useAui` (web hosts from `@assistant-ui/react`, React Native\n * hosts from `@assistant-ui/react-native`; this package imports neither).\n * Called unconditionally on every render, so it MUST be (or call) a hook\n * following the rules of hooks — define it at module scope with a `use*`\n * name: `const useHostClient = (thread: DeepAgentThreadElement) =>\n * useAui({ thread })`.\n */\n useClient: (thread: DeepAgentThreadElement) => TClient;\n /** Registers the deep-agent runConfig on the client's model context. Hosts\n * that register elsewhere (e.g. a bridge component mounted under the\n * provider) omit it. */\n runConfig?: DeepAgentRunConfigRegistration;\n /**\n * Called right before send recovery dispatches a failed send again, with its\n * message id: the host puts back the per-send context that message was sent\n * with (Olympus stages the agents it named) before its command leaves. A\n * promise holds that retry (and the ones behind it) until it settles, then\n * the host is asked again: it can't put the context back while a send with\n * another one is still on its way (see `DeepAgentSendRecoveryOptions.beforeRetry`).\n * A function returned is called when the retry does not go after all.\n */\n onBeforeSendRetry?: (messageId: string) => void | PromiseLike<void> | (() => void);\n}\n\n/**\n * The configurable deep-agent statewire runtime — the one hook every Athena\n * chat surface (Olympus, `@athenaintel/react` SDK, Chrome extension, Office\n * add-in, mobile) mounts its assistant-ui client through.\n *\n * Composes the pieces every surface previously wired by hand:\n * `useDeepAgentThread` (attach lifecycle, auth parking, lane projection,\n * connection/running observers), durable session persistence with the shared\n * availability guard, transport-level send recovery (failed sends the server\n * never judged auto-retry under their original message id — `send-recovery.ts`),\n * the platform client via `useClient`, and the\n * runConfig model-context registration on provider notifications. Everything\n * host-specific — auth headers, run-config building, error policy, adapters,\n * diagnostics recorders — stays an option, so per-surface behavior is\n * configuration, not a fork.\n */\nexport function useDeepAgentRuntime<\n TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost,\n>(options: UseDeepAgentRuntimeOptions<TClient>): TClient {\n const {\n surface,\n session = false,\n useClient,\n runConfig,\n onBeforeSendRetry,\n onError,\n onStateChange,\n onCommandChange,\n ...threadOptions\n } = options;\n\n // Probed once per mount, like every browser host did individually: an origin\n // that cannot persist stays process-local for the mount's lifetime.\n const [canPersistLocalSession] = useState(canPersistDeepAgentSession);\n\n // Per-render refs so the transport never remounts on a new closure identity\n // and always reads the latest policy.\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n const surfaceRef = useRef(surface);\n surfaceRef.current = surface;\n const onStateChangeRef = useRef(onStateChange);\n onStateChangeRef.current = onStateChange;\n const onCommandChangeRef = useRef(onCommandChange);\n onCommandChangeRef.current = onCommandChange;\n const diagnosticsRef = useRef(threadOptions.diagnostics);\n diagnosticsRef.current = threadOptions.diagnostics;\n const onBeforeSendRetryRef = useRef(onBeforeSendRetry);\n onBeforeSendRetryRef.current = onBeforeSendRetry;\n const clientRef = useRef<TClient | null>(null);\n\n // Where the persisted session record lives, for the recovery freshness gate\n // (read per sweep — undefined means \"no persistence, everything is fresh\").\n const failureStampsRef = useRef<(() => ReadonlyMap<string, number> | null) | undefined>(\n undefined\n );\n failureStampsRef.current =\n session === false || session.kind === 'pending'\n ? undefined\n : session.kind === 'local-storage'\n ? canPersistLocalSession\n ? () => readPersistedFailedSendStamps(session.key(threadOptions.threadId))\n : undefined\n : () => readFailedSendStampsFromRecord(loadSessionRecord(session.store));\n\n // A pending lane parks the thread under an attach identity of its own (see\n // `DeepAgentPendingSession`), so the attach once the lane is known is new.\n const sessionPending = session !== false && session.kind === 'pending';\n const attachKey = sessionPending\n ? pendingSessionAttachKey(threadOptions.attachKey)\n : threadOptions.attachKey;\n\n // The lane's scope, as the host's `key` names it: whose lanes this mount\n // runs on. A key that starts naming another principal's lanes starts a new\n // lane epoch, which remounts the whole thread on the new lane instead of\n // moving the mounted session there (see `DeepAgentSessionPersistence`).\n // Tracked whether or not this origin can persist: a process-local session\n // holds the previous principal's journal in memory just the same. A pending\n // lane keeps the last known scope, so a sign-out and a sign-in as someone\n // else with a gap between them is still a change.\n const laneKey = session !== false && session.kind === 'local-storage' ? session.key : null;\n const [laneEpochState, setLaneEpochState] = useState(() =>\n nextSessionLaneEpoch({\n previous: INITIAL_SESSION_LANE_EPOCH,\n threadId: threadOptions.threadId,\n key: laneKey,\n })\n );\n const nextLaneEpochState = nextSessionLaneEpoch({\n previous: laneEpochState,\n threadId: threadOptions.threadId,\n key: laneKey,\n });\n // React's derived-state update: re-renders at once with the new epoch, and\n // this render already uses it, so no render pairs the new key with the\n // mounted thread.\n if (nextLaneEpochState !== laneEpochState) setLaneEpochState(nextLaneEpochState);\n const laneEpoch = nextLaneEpochState.epoch;\n\n // Send recovery, keyed by attach identity exactly like the thread's session\n // (a switch, re-attach or new lane epoch gets fresh hydration gating and\n // retry budgets, so it never re-dispatches a previous principal's failed\n // sends). The controller re-dispatches failed sends the server never judged;\n // see `send-recovery.ts` for the contract.\n const attachId = deepAgentAttachId({ threadId: threadOptions.threadId, attachKey, laneEpoch });\n // biome-ignore lint/correctness/useExhaustiveDependencies: keyed by attach identity on purpose; every other input is read through a per-render ref.\n const sendRecovery = useMemo(\n () =>\n createDeepAgentSendRecovery({\n lane: () => {\n const client = clientRef.current;\n if (!client) return null;\n return readStatewireFailedSendLane(client.thread().getState().extras);\n },\n failureStamps: () => failureStampsRef.current?.() ?? null,\n diagnostics: () => diagnosticsRef.current,\n beforeRetry: (messageId) => onBeforeSendRetryRef.current?.(messageId),\n }),\n [attachId]\n );\n useEffect(() => () => sendRecovery.dispose(), [sendRecovery]);\n const sendRecoveryRef = useRef(sendRecovery);\n sendRecoveryRef.current = sendRecovery;\n\n const thread = useDeepAgentThread({\n ...threadOptions,\n ...(attachKey !== undefined && { attachKey }),\n laneEpoch,\n // Parked without a message (a host suspension, when one applies, wins):\n // nothing dials and nothing journals until the lane is known. What paints\n // the first frame is the real attach's to report, not the park's.\n ...(sessionPending && {\n suspended: threadOptions.suspended || {},\n onAttachSource: undefined,\n }),\n // A pooled dial for the thread is the real attach's to take: a park that\n // only waits for the lane neither takes nor discards it. A host suspension\n // still discards it, as it does without a pending lane.\n ...(sessionPending && !threadOptions.suspended && { prewarmPool: undefined }),\n ...(session !== false &&\n session.kind === 'local-storage' &&\n canPersistLocalSession && {\n storage: {\n session: QuotaSafeLocalStorageSession({\n threadId: threadOptions.threadId,\n key: session.key,\n ...(session.onStorageDegraded === undefined\n ? {}\n : { onStorageDegraded: session.onStorageDegraded }),\n }),\n },\n }),\n ...(session !== false && session.kind === 'session-store' && { sessionStore: session.store }),\n onStateChange: (state, ctx) => {\n sendRecoveryRef.current.observeStateChange(state);\n onStateChangeRef.current?.(state, ctx);\n },\n onCommandChange: (update, ctx) => {\n sendRecoveryRef.current.observeCommand(update);\n onCommandChangeRef.current?.(update, ctx);\n },\n onError: (error) => {\n const handler = onErrorRef.current;\n if (handler) {\n handler(error);\n return;\n }\n logUnhandledStatewireError(surfaceRef.current, error);\n },\n });\n\n const client = useClient(thread);\n clientRef.current = client;\n\n // A locked shared journal can publish failed history after onCommandChange,\n // with no canonical state change to wake recovery. Observe the client that\n // owns the visible failed lane; the controller guards in-flight retries.\n useEffect(() => {\n const unsubscribe = client.subscribe(() => sendRecovery.observeFailedLaneChange());\n sendRecovery.observeFailedLaneChange();\n return unsubscribe;\n }, [client, sendRecovery]);\n\n // Commands travel bare, so the runConfig that drives the run rides the\n // ambient model context: `contextSync: 'command-send'` mirrors it to the\n // server, which reads `config.runConfig` at command admission. The core pulls\n // on model-context notifications; the per-render ref ensures each pull uses\n // the latest closure but does not trigger a pull itself.\n const getRunConfigRef = useRef(runConfig?.get);\n getRunConfigRef.current = runConfig?.get;\n const hasRunConfig = runConfig !== undefined;\n const runConfigKey = runConfig?.key;\n // A host's own notifications (inputs that change outside React) pull too.\n const subscribeRunConfigRef = useRef(runConfig?.subscribe);\n subscribeRunConfigRef.current = runConfig?.subscribe;\n const hasRunConfigSubscribe = runConfig?.subscribe !== undefined;\n useEffect(() => {\n if (!hasRunConfig) return undefined;\n return registerDeepAgentRunConfig(\n client,\n () => getRunConfigRef.current?.() ?? {},\n hasRunConfigSubscribe\n ? (listener) => subscribeRunConfigRef.current?.(listener) ?? (() => {})\n : undefined\n );\n }, [client, hasRunConfig, runConfigKey, hasRunConfigSubscribe]);\n\n return client;\n}\n\n"],"names":["isStorageQuotaError","isStatewireChannelTeardown","isConfigSyncUnavailableRejection","useState","useRef","readPersistedFailedSendStamps","readFailedSendStampsFromRecord","nextSessionLaneEpoch","INITIAL_SESSION_LANE_EPOCH","deepAgentAttachId","sendRecovery","useMemo","createDeepAgentSendRecovery","client","readStatewireFailedSendLane","useEffect","useDeepAgentThread","QuotaSafeLocalStorageSession","registerDeepAgentRunConfig"],"mappings":"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"}
|
|
@@ -136,6 +136,13 @@ export interface DeepAgentRunConfigRegistration {
|
|
|
136
136
|
* mounted; they reach the server only after a key change triggers a pull.
|
|
137
137
|
*/
|
|
138
138
|
key?: unknown;
|
|
139
|
+
/**
|
|
140
|
+
* Notifies when a getter input changes outside React, so model-context sync
|
|
141
|
+
* pulls `get` at once, before a command sent right after leaves (Olympus
|
|
142
|
+
* stages a send's named agents right before `composer.send`). Subscribed
|
|
143
|
+
* when the provider registers; a key change subscribes again.
|
|
144
|
+
*/
|
|
145
|
+
subscribe?: (listener: () => void) => () => void;
|
|
139
146
|
}
|
|
140
147
|
export interface UseDeepAgentRuntimeOptions<TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost> extends Omit<UseDeepAgentThreadOptions, 'storage' | 'sessionStore' | 'laneEpoch'> {
|
|
141
148
|
/** Host surface tag for the shared fallback error logging (e.g.
|
|
@@ -157,6 +164,16 @@ export interface UseDeepAgentRuntimeOptions<TClient extends DeepAgentModelContex
|
|
|
157
164
|
* that register elsewhere (e.g. a bridge component mounted under the
|
|
158
165
|
* provider) omit it. */
|
|
159
166
|
runConfig?: DeepAgentRunConfigRegistration;
|
|
167
|
+
/**
|
|
168
|
+
* Called right before send recovery dispatches a failed send again, with its
|
|
169
|
+
* message id: the host puts back the per-send context that message was sent
|
|
170
|
+
* with (Olympus stages the agents it named) before its command leaves. A
|
|
171
|
+
* promise holds that retry (and the ones behind it) until it settles, then
|
|
172
|
+
* the host is asked again: it can't put the context back while a send with
|
|
173
|
+
* another one is still on its way (see `DeepAgentSendRecoveryOptions.beforeRetry`).
|
|
174
|
+
* A function returned is called when the retry does not go after all.
|
|
175
|
+
*/
|
|
176
|
+
onBeforeSendRetry?: (messageId: string) => void | PromiseLike<void> | (() => void);
|
|
160
177
|
}
|
|
161
178
|
/**
|
|
162
179
|
* The configurable deep-agent statewire runtime — the one hook every Athena
|