@athenaintel/react 0.48.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -10
- package/dist/agent-runtime/src/call-recap.cjs +319 -0
- package/dist/agent-runtime/src/call-recap.cjs.map +1 -0
- package/dist/agent-runtime/src/call-recap.d.ts +142 -0
- package/dist/agent-runtime/src/call-recap.js +319 -0
- package/dist/agent-runtime/src/call-recap.js.map +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.cjs +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.cjs.map +1 -1
- package/dist/agent-runtime/src/chat-outbox-store.d.ts +7 -0
- package/dist/agent-runtime/src/chat-outbox-store.js +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.js.map +1 -1
- package/dist/agent-runtime/src/chat-outbox.cjs +46 -5
- package/dist/agent-runtime/src/chat-outbox.cjs.map +1 -1
- package/dist/agent-runtime/src/chat-outbox.d.ts +29 -0
- package/dist/agent-runtime/src/chat-outbox.js +46 -5
- package/dist/agent-runtime/src/chat-outbox.js.map +1 -1
- package/dist/agent-runtime/src/conversation-index.cjs.map +1 -1
- package/dist/agent-runtime/src/conversation-index.d.ts +7 -0
- package/dist/agent-runtime/src/conversation-index.js.map +1 -1
- package/dist/agent-runtime/src/conversation-list.cjs +2 -1
- package/dist/agent-runtime/src/conversation-list.cjs.map +1 -1
- package/dist/agent-runtime/src/conversation-list.d.ts +19 -3
- package/dist/agent-runtime/src/conversation-list.js +2 -1
- package/dist/agent-runtime/src/conversation-list.js.map +1 -1
- package/dist/agent-runtime/src/index.d.ts +6 -5
- package/dist/agent-runtime/src/interrupts.cjs +28 -1
- package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/agent-runtime/src/interrupts.d.ts +21 -1
- package/dist/agent-runtime/src/interrupts.js +28 -1
- package/dist/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/agent-runtime/src/model-context.cjs +3 -2
- package/dist/agent-runtime/src/model-context.cjs.map +1 -1
- package/dist/agent-runtime/src/model-context.d.ts +6 -1
- package/dist/agent-runtime/src/model-context.js +3 -2
- package/dist/agent-runtime/src/model-context.js.map +1 -1
- package/dist/agent-runtime/src/runtime.d.ts +3 -2
- package/dist/agent-runtime/src/send-recovery.cjs +37 -3
- package/dist/agent-runtime/src/send-recovery.cjs.map +1 -1
- package/dist/agent-runtime/src/send-recovery.d.ts +19 -0
- package/dist/agent-runtime/src/send-recovery.js +37 -3
- package/dist/agent-runtime/src/send-recovery.js.map +1 -1
- package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +8 -8
- package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +113 -1
- package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
- package/dist/agent-runtime/src/use-conversation-list.cjs +3 -2
- package/dist/agent-runtime/src/use-conversation-list.cjs.map +1 -1
- package/dist/agent-runtime/src/use-conversation-list.d.ts +6 -0
- package/dist/agent-runtime/src/use-conversation-list.js +3 -2
- package/dist/agent-runtime/src/use-conversation-list.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs +21 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.d.ts +8 -2
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js +21 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +24 -6
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +17 -0
- package/dist/agent-runtime/src/use-deep-agent-runtime.js +24 -6
- package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs +44 -9
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +34 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.js +44 -9
- package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
- package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +8 -2
- package/dist/agent-runtime/src/voice-earcon.cjs +139 -0
- package/dist/agent-runtime/src/voice-earcon.cjs.map +1 -0
- package/dist/agent-runtime/src/voice-earcon.d.ts +38 -0
- package/dist/agent-runtime/src/voice-earcon.js +139 -0
- package/dist/agent-runtime/src/voice-earcon.js.map +1 -0
- package/dist/agent-runtime/src/voice-media.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-media.d.ts +14 -0
- package/dist/agent-runtime/src/voice-media.js.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
- package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.js +2 -0
- package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
- package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
- package/dist/assets/AthenaAssetEmbed.d.ts +11 -5
- package/dist/assets/AthenaAssetEmbed.js.map +1 -1
- package/dist/assets/use-asset-embed.cjs.map +1 -1
- package/dist/assets/use-asset-embed.d.ts +12 -5
- package/dist/assets/use-asset-embed.js.map +1 -1
- package/dist/chat/StatewireApprovalCard.cjs +41 -6
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.d.ts +18 -1
- package/dist/chat/StatewireApprovalCard.js +42 -7
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat/StatewireQueuedMessages.cjs +56 -6
- package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
- package/dist/chat/StatewireQueuedMessages.d.ts +14 -0
- package/dist/chat/StatewireQueuedMessages.js +58 -8
- package/dist/chat/StatewireQueuedMessages.js.map +1 -1
- package/dist/chat/WaitingOnYouBanner.cjs +6 -2
- package/dist/chat/WaitingOnYouBanner.cjs.map +1 -1
- package/dist/chat/WaitingOnYouBanner.js +7 -3
- package/dist/chat/WaitingOnYouBanner.js.map +1 -1
- package/dist/chat/chat-voice-controls.cjs +3 -1
- package/dist/chat/chat-voice-controls.cjs.map +1 -1
- package/dist/chat/chat-voice-controls.d.ts +6 -1
- package/dist/chat/chat-voice-controls.js +3 -1
- package/dist/chat/chat-voice-controls.js.map +1 -1
- package/dist/chat-ui/index.cjs +3 -0
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.d.ts +1 -0
- package/dist/chat-ui/index.js +3 -0
- package/dist/chat-ui/index.js.map +1 -1
- package/dist/chat-ui/messages/from-the-call-card.cjs +124 -0
- package/dist/chat-ui/messages/from-the-call-card.cjs.map +1 -0
- package/dist/chat-ui/messages/from-the-call-card.d.ts +17 -0
- package/dist/chat-ui/messages/from-the-call-card.js +124 -0
- package/dist/chat-ui/messages/from-the-call-card.js.map +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-link.d.ts +4 -0
- package/dist/chat-ui/rich-text/chat-link.js +4 -3
- package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.d.ts +12 -0
- package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
- package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.cjs +2 -0
- package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.js +2 -0
- package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
- package/dist/collab/client.cjs +15 -3
- package/dist/collab/client.cjs.map +1 -1
- package/dist/collab/client.d.ts +4 -2
- package/dist/collab/client.js +16 -4
- package/dist/collab/client.js.map +1 -1
- package/dist/composer/index.d.ts +1 -1
- package/dist/composer/useReadComposerDraft.cjs +26 -7
- package/dist/composer/useReadComposerDraft.cjs.map +1 -1
- package/dist/composer/useReadComposerDraft.d.ts +23 -1
- package/dist/composer/useReadComposerDraft.js +27 -8
- package/dist/composer/useReadComposerDraft.js.map +1 -1
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +190 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +132 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js +190 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -0
- package/dist/keryx-http/src/update-attributions.cjs +34 -4
- package/dist/keryx-http/src/update-attributions.cjs.map +1 -1
- package/dist/keryx-http/src/update-attributions.d.ts +36 -4
- package/dist/keryx-http/src/update-attributions.js +35 -5
- package/dist/keryx-http/src/update-attributions.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +4 -0
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +11 -1
- package/dist/provider/AthenaProvider.js +4 -0
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/threads/api.cjs.map +1 -1
- package/dist/threads/api.d.ts +15 -0
- package/dist/threads/api.js.map +1 -1
- package/dist/threads/conversation-list-fetcher.cjs +13 -5
- package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
- package/dist/threads/conversation-list-fetcher.d.ts +4 -1
- package/dist/threads/conversation-list-fetcher.js +13 -5
- package/dist/threads/conversation-list-fetcher.js.map +1 -1
- package/dist/threads/statewire-thread-list.cjs +28 -6
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.d.ts +20 -8
- package/dist/threads/statewire-thread-list.js +28 -6
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/threads/use-athena-sessions.cjs +27 -5
- package/dist/threads/use-athena-sessions.cjs.map +1 -1
- package/dist/threads/use-athena-sessions.d.ts +10 -5
- package/dist/threads/use-athena-sessions.js +28 -6
- package/dist/threads/use-athena-sessions.js.map +1 -1
- package/dist/threads/use-rest-conversation-history.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -261,7 +261,9 @@ What the statewire transport wires up in `AthenaChat`:
|
|
|
261
261
|
(or Continue for a generic pause) and resumes the run through the statewire
|
|
262
262
|
`run/input` command. Hosts that build their own thread UI should mount the
|
|
263
263
|
exported `StatewireApprovalCard` themselves — without it an approval-gated
|
|
264
|
-
run stays parked with no way to answer it.
|
|
264
|
+
run stays parked with no way to answer it. A lone call whose resolved
|
|
265
|
+
envelope carries a headline is titled by it, with its summary under it,
|
|
266
|
+
above the card's own details (`title` on the slot context). Its `renderDetails`,
|
|
265
267
|
`isHandledElsewhere`, and `readOnly` seams take host-specific review UIs,
|
|
266
268
|
requests another surface answers, and viewers without edit rights; the
|
|
267
269
|
headless `useStatewireApprovalRequest` behind it is exported for hosts with
|
|
@@ -365,6 +367,10 @@ What the statewire transport wires up in `AthenaChat`:
|
|
|
365
367
|
`useAthenaSessionAttention`, and `useRefreshThreadList` read that state
|
|
366
368
|
(and render nothing / return `null` without it). See
|
|
367
369
|
[Session list, attention and wake markers](#session-list-attention-and-wake-markers).
|
|
370
|
+
The list holds the sessions of the provider's `appId` (and
|
|
371
|
+
`threadListAdditionalAppIds`); with `threadListAgentHome` and a collab
|
|
372
|
+
`agent`, also the conversations with that agent on its other channels (see
|
|
373
|
+
[What's new in 0.50.0](#whats-new-in-0500)).
|
|
368
374
|
|
|
369
375
|
The `agent` prop only accepts `collab_agent:<asset_id>` refs (see below),
|
|
370
376
|
because the statewire host always runs the Athena deep agent. **Any other
|
|
@@ -524,7 +530,8 @@ index pushes.
|
|
|
524
530
|
Mounted by `AthenaChat` above the thread viewport: a persistent strip while
|
|
525
531
|
the active thread's attention is `needs_you` or the approval hook
|
|
526
532
|
(`useStatewireApprovalRequest`) reports a pending request. Its text is the
|
|
527
|
-
server's `nextAction`, else a summary of the pending request
|
|
533
|
+
server's `nextAction`, else a summary of the pending request (the approval
|
|
534
|
+
card's title when it has one); a click scrolls
|
|
528
535
|
to the approval card (or to the composer when there is none). It never
|
|
529
536
|
duplicates the card's Approve / Reject buttons. `WaitingOnYouBannerView` is
|
|
530
537
|
the pure presenter for hosts that resolve the inputs themselves.
|
|
@@ -671,7 +678,9 @@ Server-applied filters are still checked on rows a live push changed, so a
|
|
|
671
678
|
session you just read leaves "Unread" and a finished one leaves "Running".
|
|
672
679
|
`listThreads` takes the server filters as `filters` (`ThreadListFilterBody`),
|
|
673
680
|
and `app_ids` (`additionalListAppIds`) now reaches the server: the request
|
|
674
|
-
body used to drop it.
|
|
681
|
+
body used to drop it. With `threadListAgentHome`, `home_agent_id` rides beside
|
|
682
|
+
it, and the answer's `home_agent_applied` says whether the list holds the
|
|
683
|
+
agent's conversations (absent on an older server: it does not).
|
|
675
684
|
|
|
676
685
|
## Session workbench
|
|
677
686
|
|
|
@@ -809,7 +818,9 @@ After a handover the person sent in voice mode, a host passes
|
|
|
809
818
|
`continueByVoice={{ onDone }}` and the composer offers **Continue by voice**:
|
|
810
819
|
one tap starts a call whose model opens by saying where the conversation
|
|
811
820
|
stands (`start({ opening: 'recap' })`). `onDone` runs on the tap, **Not now**,
|
|
812
|
-
or a typed send.
|
|
821
|
+
or a typed send. A host's own "Continue on…" control offers to send the
|
|
822
|
+
conversation in voice mode only while `useChatVoiceEnabled({ enabled })` is
|
|
823
|
+
true: the same workspace gate the composer's voice control uses.
|
|
813
824
|
|
|
814
825
|
Use `<AthenaChat voice={false} />` to omit realtime voice in a host that cannot
|
|
815
826
|
support it, or `voice={{ model: 'gemini-3.8-live' }}` to request a host-offered
|
|
@@ -940,6 +951,24 @@ Both use the same bearer / API key as session creation. The full recipe
|
|
|
940
951
|
template skill `voice-session-guide.md`. `createAthenaVoiceSession` is also
|
|
941
952
|
exported if you hold the bridge state yourself.
|
|
942
953
|
|
|
954
|
+
## What's new in 0.50.0
|
|
955
|
+
|
|
956
|
+
**An agent's own app lists its conversations from every channel.** An app that runs as a collab agent (`agent="collab_agent:<asset_id>"`) can now list the conversations people have with that agent anywhere else, such as Slack, email or SMS, beside its own sessions. Pass `threadListAgentHome` with `enableThreadList`:
|
|
957
|
+
|
|
958
|
+
```tsx
|
|
959
|
+
<AthenaProvider enableThreadList appId={appId} agent="collab_agent:asset_…" threadListAgentHome>
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
Every thread-list read the provider makes (`SessionList` and its Pinned group, `SessionsPage`, `Inbox` and `useAthenaSessions`) then names the agent as `home_agent_id`, and once the agent has a home app set, Athena adds the conversations with it that the viewer can already open. Each such row shows where it started ("Slack", "Email", "SMS"), and one that starts while the app is open appears without a reload. Nothing else about the list changes: its search and filters still apply, and Athena checks access to the list exactly as before. Against an Athena that predates the setting, or for an agent without a home app, the list is the app's own sessions, as it was. New sessions are still tagged `appId`. In `@athenaintel/agent-runtime` 0.3.11, `ConversationListFilter` takes `homeAgentId`, the list page and `useConversationList` report `homeAgentApplied`, and a conversation-index entry carries the session's `collab_agent_id`.
|
|
963
|
+
|
|
964
|
+
## What's new in 0.49.0
|
|
965
|
+
|
|
966
|
+
**An approval card leads with what it asks.** When the one tool call an approval covers arrives with a headline from Athena (its `approval_context`), `StatewireApprovalCard`, and so `AthenaChat`, shows that headline in place of "`tool` requires approval before it runs", with the summary under it, and names the tool in the details instead, above the facts, warnings and arguments it already showed. The first such approval asks to use one of the person's own devices in a conversation: "Use Studio Mac in this conversation?". The waiting-on-you banner names the approval the same way. A batch keeps the generic heading, since one decision answers every call, and an approval without a headline looks exactly as it did. So does a card whose details come from your `renderDetails` (or an editor from your `renderEditor`): your review shows the envelope, so the header doesn't say the headline again. The title is display-only: Approve and Reject send what they always sent. Your `renderHeader`, `renderDetails` and `renderEditor` receive it as `title` (`{ headline, summary? }`) to lead with if you want it. For an approval UI of your own, `pendingApprovalTitle` from `@athenaintel/agent-runtime` (0.3.10) returns it, and `parsePendingApprovalContext` now keeps an envelope's `headline` and `summary`. Nothing to change in your app.
|
|
967
|
+
|
|
968
|
+
**Housekeeping written offline stays housekeeping.** A write your app makes inside `runWithDocUpdateAttributions` for a `connectGenericDoc` handle's document while the handle has no connection used to reach Athena inside the reconnect's sync, which carries no attributions, so it counted as the person's edit. The handle now keeps such writes until Athena acknowledges them, and when it reconnects sends each one ahead of that sync as its own marked update. Nothing to change in your app.
|
|
969
|
+
|
|
970
|
+
**A host can put a failed message's own context back before Retry.** `StatewireQueuedMessages` takes an optional `onBeforeRetry(messageId)`, called right before a failed message is sent again from its Retry button, so a host that keeps per-send context for each message (Athena's web app stages the agents a message named) restores it before the retry leaves. The hook may return a promise to hold the Retry while it can't do that yet (a send with another context is still on its way); the tray asks it again once the promise settles, unless the message was sent or discarded meanwhile, and sends the message regardless after ten holds. A held Retry is held once (clicking it again does nothing), and a function the hook returns is called if the message does not go after all, so the host lets go of what it put back. Nothing to change in your app.
|
|
971
|
+
|
|
943
972
|
## What's new in 0.48.0
|
|
944
973
|
|
|
945
974
|
**Scrolling back through a long session no longer flashes.** Opening a long session loads its newest messages, and scrolling to the top loads older pages. When a session's first loaded turn was one long run, each page of its earlier steps joined that same turn and gave it a new id. `ChatThreadShell` (and so `AthenaChat`) keyed rows by message id, so the turn remounted on every page: it replayed its entrance animation once per page, closed anything you had opened in it, and lost the browser's scroll anchor. A 74-minute run took 16 pages and flashed 16 times. Rows are now keyed by the row key the deep-agent conversion keeps for a turn that grows at its head (`deepAgentRowKey`), so the page lands in the mounted row. `AthenaChat`'s message rows also animate in only when they arrive live (the newest message, mounted while its run is running), never when a session opens or older history loads, and they respect reduced motion. The "Loading earlier messages" row is centred on the thread column. Nothing to change in your app.
|
|
@@ -2014,7 +2043,9 @@ host that opens a cited place in Athena on the web writes it into the Spaces lin
|
|
|
2014
2043
|
|
|
2015
2044
|
Use `AthenaAssetEmbed` to render a native Athena asset without chat, thread history, tabs, or the SDK split-pane layout. It defaults to the editable, full native asset experience (`readOnly={false}` and `displayMode="full"`). The embed token and the current user must still have edit permission; otherwise Athena renders the asset read-only.
|
|
2016
2045
|
|
|
2017
|
-
|
|
2046
|
+
Editing a Word document, an Athena document, a spreadsheet, a user interface or a deck through an embed needs a viewer who is signed in to Athena: give the provider the viewer's `token`, which the component hands the frame. A viewer it hands no token (a provider with only an `apiKey`, or none) is not signed in, and the embed is read-only for them, as a `readOnly` link is, whatever `readOnly={false}` asked for. Athena enforces this on its servers, for links minted before it too. Other asset types are unchanged.
|
|
2047
|
+
|
|
2048
|
+
With `displayMode="minimal"`, which runs on the link rather than an Athena sign-in, pass `allowQuickEdit` (and `readOnly={false}`) to let the viewer Quick Edit a spreadsheet, a Word document or a deck with ⌘J. Like editing, it is offered only to a signed-in viewer. Its runs are billed to the asset's workspace, with an hourly cap per link.
|
|
2018
2049
|
|
|
2019
2050
|
A minimal embed shows the asset's comment threads, read-only, only to a signed-in viewer with their own access to the asset (the bearer the component hands the frame, or an Athena session). Pass `allowComments` to show them to everyone holding the link, anonymous viewers included. No embed writes a comment. The request carries `allow_comments: true` only when you ask.
|
|
2020
2051
|
|
|
@@ -2243,10 +2274,14 @@ runWithDocUpdateAttributions({
|
|
|
2243
2274
|
The handle's socket sends that write's update with `origin: background`, so
|
|
2244
2275
|
Athena counts it as automatic work: it is not shown as the person's edit and
|
|
2245
2276
|
does not notify subscriptions that follow people's edits. Only a synchronous
|
|
2246
|
-
write inside `run` is marked
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2277
|
+
write inside `run` is marked. One made while the handle has no connection is
|
|
2278
|
+
kept until Athena acknowledges it and sent marked the same way when the handle
|
|
2279
|
+
reconnects, ahead of the reconnect's sync. The handle keeps it in memory; a
|
|
2280
|
+
host whose local copy is Athena's (`@athenaintel/local-copy`, as in Athena's
|
|
2281
|
+
desktop and phone apps) also keeps it across restarts. Without one, a reload
|
|
2282
|
+
before the reconnect sends it with the person's unsynced edits, as theirs. On a
|
|
2283
|
+
read-only grant the handle's socket sends nothing that carries document content
|
|
2284
|
+
(presence still goes out); Athena would refuse it anyway.
|
|
2250
2285
|
|
|
2251
2286
|
### Workspace presence (admin only)
|
|
2252
2287
|
|
|
@@ -2447,7 +2482,10 @@ as it is, for a host that shows the chat again and wants typing to land in it (a
|
|
|
2447
2482
|
launcher window brought back by a shortcut). Asked before the editor has mounted,
|
|
2448
2483
|
it focuses the editor as it mounts. `useReadComposerDraft()` returns a function
|
|
2449
2484
|
that reads the draft's text when called, for a host that hands the chat to
|
|
2450
|
-
another window and carries the unsent text along.
|
|
2485
|
+
another window and carries the unsent text along. `useTryReadComposerDraft()`
|
|
2486
|
+
reads the same text but answers `null` when there is no composer to read, for a
|
|
2487
|
+
host that must tell a blank composer from one it could not read ("Continue on…"
|
|
2488
|
+
sends a blank one as an empty draft and an unread one as none).
|
|
2451
2489
|
|
|
2452
2490
|
Use `useSendMessage()` for workflow buttons, sidebar shortcuts, and other UI that lives outside `<AthenaChat>` but still needs to submit a prompt. Its promise settles once the send has been dispatched, including a send that waited for the connection.
|
|
2453
2491
|
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const core = require("@assistant-ui/core");
|
|
4
|
+
const statewireState = require("./statewire-state.cjs");
|
|
5
|
+
const toolResult = require("./tool-result.cjs");
|
|
6
|
+
const voicePresentation = require("./voice-presentation.cjs");
|
|
7
|
+
const VOICE_TYPED_ID_PREFIX = "voicetext_";
|
|
8
|
+
const CALL_SOURCES = /* @__PURE__ */ new Set([
|
|
9
|
+
voicePresentation.VOICE_SOURCE_SPOKEN,
|
|
10
|
+
voicePresentation.VOICE_SOURCE_TYPED,
|
|
11
|
+
voicePresentation.VOICE_SOURCE_DELEGATION
|
|
12
|
+
]);
|
|
13
|
+
function isCallUserMessage(message) {
|
|
14
|
+
var _a, _b;
|
|
15
|
+
if (message.role !== "user") return false;
|
|
16
|
+
if (CALL_SOURCES.has((_b = (_a = message.metadata) == null ? void 0 : _a.custom) == null ? void 0 : _b.source)) return true;
|
|
17
|
+
return message.id.startsWith(voicePresentation.VOICE_DELEGATION_ID_PREFIX) || message.id.startsWith(VOICE_TYPED_ID_PREFIX);
|
|
18
|
+
}
|
|
19
|
+
function mayHaveLiveCall(state) {
|
|
20
|
+
if (!statewireState.isDeepAgentStateHydrated(state)) return true;
|
|
21
|
+
const voice = statewireState.readDeepAgentStreamValues(state).voice;
|
|
22
|
+
return voice !== null && voice !== void 0;
|
|
23
|
+
}
|
|
24
|
+
const NO_ENTRIES = Object.freeze([]);
|
|
25
|
+
const MARKDOWN_LINK = /\[((?:[^[\]\n]|\[[^[\]\n]*\])*)\]\(\s*<?((?:[^()\s<>]|\([^()\s]*\))+)>?(?:\s+"[^"\n]*")?\s*\)/g;
|
|
26
|
+
const BARE_URL = /\bhttps?:\/\/[^\s<>()[\]"'`]+/g;
|
|
27
|
+
const TRAILING_PUNCTUATION = /[.,;:!?'"*_]+$/;
|
|
28
|
+
const SPACES_PATH = /\/dashboard\/spaces\/?$/;
|
|
29
|
+
const MAX_TOOL_RESULT_CHARS = 2e4;
|
|
30
|
+
function assetEntry(asset) {
|
|
31
|
+
return {
|
|
32
|
+
kind: "asset",
|
|
33
|
+
asset: { kind: "asset", key: `asset:${asset.assetId}`, ...asset }
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function normalizeAssetType(value) {
|
|
37
|
+
if (!value) return null;
|
|
38
|
+
return value === "sheet" ? "spreadsheet" : value;
|
|
39
|
+
}
|
|
40
|
+
function labelOf(raw) {
|
|
41
|
+
return raw.trim().replace(/^@/, "").trim();
|
|
42
|
+
}
|
|
43
|
+
function displayUrl(url) {
|
|
44
|
+
const host = url.hostname.replace(/^www\./, "");
|
|
45
|
+
const path = url.pathname === "/" ? "" : url.pathname.replace(/\/$/, "");
|
|
46
|
+
return `${host}${path}`;
|
|
47
|
+
}
|
|
48
|
+
const RELATIVE_LINK_BASE = "https://app.athenaintel.com";
|
|
49
|
+
function entriesOfHref(href, rawLabel) {
|
|
50
|
+
const label = labelOf(rawLabel);
|
|
51
|
+
let url;
|
|
52
|
+
try {
|
|
53
|
+
url = new URL(href, href.startsWith("/") ? RELATIVE_LINK_BASE : void 0);
|
|
54
|
+
} catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
if (url.protocol === "mention:") {
|
|
58
|
+
if (url.hostname !== "asset") return [];
|
|
59
|
+
const assetId = url.searchParams.get("id") ?? url.searchParams.get("asset_id");
|
|
60
|
+
if (!(assetId == null ? void 0 : assetId.startsWith("asset_"))) return [];
|
|
61
|
+
return [
|
|
62
|
+
assetEntry({
|
|
63
|
+
assetId,
|
|
64
|
+
title: label || (url.searchParams.get("name") ?? ""),
|
|
65
|
+
assetType: normalizeAssetType(
|
|
66
|
+
url.searchParams.get("asset_type") ?? url.searchParams.get("type")
|
|
67
|
+
),
|
|
68
|
+
href
|
|
69
|
+
})
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return [];
|
|
73
|
+
const assetIds = url.searchParams.get("asset_ids");
|
|
74
|
+
if (assetIds && SPACES_PATH.test(url.pathname)) {
|
|
75
|
+
const ids = [
|
|
76
|
+
...new Set(
|
|
77
|
+
assetIds.split(",").map((id) => id.trim()).filter((id) => id.startsWith("asset_"))
|
|
78
|
+
)
|
|
79
|
+
];
|
|
80
|
+
const assetType = normalizeAssetType(
|
|
81
|
+
url.searchParams.get("type") ?? url.searchParams.get("assetType") ?? url.searchParams.get("asset_type")
|
|
82
|
+
);
|
|
83
|
+
return ids.map((assetId) => {
|
|
84
|
+
if (ids.length === 1) return assetEntry({ assetId, title: label, assetType, href });
|
|
85
|
+
const single = new URL(url.toString());
|
|
86
|
+
single.searchParams.set("asset_ids", assetId);
|
|
87
|
+
return assetEntry({
|
|
88
|
+
assetId,
|
|
89
|
+
title: label,
|
|
90
|
+
assetType,
|
|
91
|
+
href: href.startsWith("/") ? `${single.pathname}${single.search}` : single.toString()
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (href.startsWith("/")) return [];
|
|
96
|
+
const shown = label && label !== href ? label : displayUrl(url);
|
|
97
|
+
const key = `${url.origin}${url.pathname.replace(/\/$/, "")}${url.search}`;
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
kind: "link",
|
|
101
|
+
link: { kind: "link", key: `link:${key}`, url: url.toString(), label: shown }
|
|
102
|
+
}
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
const CODE = /```[\s\S]*?(?:```|$)|`[^`\n]*`/g;
|
|
106
|
+
function textEntries(raw) {
|
|
107
|
+
if (!raw.includes("://") && !raw.includes("](/")) return NO_ENTRIES;
|
|
108
|
+
const text = raw.includes("`") ? raw.replace(CODE, (code) => " ".repeat(code.length)) : raw;
|
|
109
|
+
const found = [];
|
|
110
|
+
const covered = [];
|
|
111
|
+
for (const match of text.matchAll(MARKDOWN_LINK)) {
|
|
112
|
+
const [whole, label = "", href = ""] = match;
|
|
113
|
+
covered.push([match.index, match.index + whole.length]);
|
|
114
|
+
if (text[match.index - 1] === "!") continue;
|
|
115
|
+
for (const entry of entriesOfHref(href, label)) found.push({ index: match.index, entry });
|
|
116
|
+
}
|
|
117
|
+
for (const match of text.matchAll(BARE_URL)) {
|
|
118
|
+
const start = match.index;
|
|
119
|
+
if (covered.some(([from, to]) => start >= from && start < to)) continue;
|
|
120
|
+
const href = match[0].replace(TRAILING_PUNCTUATION, "");
|
|
121
|
+
for (const entry of entriesOfHref(href, "")) found.push({ index: start, entry });
|
|
122
|
+
}
|
|
123
|
+
if (found.length === 0) return NO_ENTRIES;
|
|
124
|
+
found.sort((a, b) => a.index - b.index);
|
|
125
|
+
return Object.freeze(found.map(({ entry }) => entry));
|
|
126
|
+
}
|
|
127
|
+
function toolCallEntries(part) {
|
|
128
|
+
const toolCallId = part.toolCallId;
|
|
129
|
+
const toolName = part.toolName ?? "tool";
|
|
130
|
+
if (!toolCallId) return NO_ENTRIES;
|
|
131
|
+
if (part.result === void 0) {
|
|
132
|
+
return Object.freeze([{ kind: "open-call", call: { toolCallId, toolName } }]);
|
|
133
|
+
}
|
|
134
|
+
if (part.isPreliminary === true) return NO_ENTRIES;
|
|
135
|
+
if (typeof part.result === "string" && part.result.length > MAX_TOOL_RESULT_CHARS)
|
|
136
|
+
return NO_ENTRIES;
|
|
137
|
+
const record = toolResult.resultRecord(part.result);
|
|
138
|
+
if (!record) return NO_ENTRIES;
|
|
139
|
+
const assetId = toolResult.stringField(record, ["asset_id", "assetId"]);
|
|
140
|
+
const title = toolResult.stringField(record, ["title", "asset_title", "name"]);
|
|
141
|
+
if (!(assetId == null ? void 0 : assetId.startsWith("asset_")) || !title) return NO_ENTRIES;
|
|
142
|
+
const outcome = toolResult.classifyToolResult({
|
|
143
|
+
status: { type: "complete" },
|
|
144
|
+
result: part.result,
|
|
145
|
+
isError: part.isError
|
|
146
|
+
});
|
|
147
|
+
if (outcome.state !== "complete") return NO_ENTRIES;
|
|
148
|
+
return Object.freeze([
|
|
149
|
+
assetEntry({
|
|
150
|
+
assetId,
|
|
151
|
+
title: title.trim(),
|
|
152
|
+
assetType: normalizeAssetType(toolResult.stringField(record, ["asset_type", "assetType"])),
|
|
153
|
+
href: null
|
|
154
|
+
})
|
|
155
|
+
]);
|
|
156
|
+
}
|
|
157
|
+
const partEntriesCache = /* @__PURE__ */ new WeakMap();
|
|
158
|
+
function partEntries(part) {
|
|
159
|
+
const cached = partEntriesCache.get(part);
|
|
160
|
+
if (cached !== void 0) return cached;
|
|
161
|
+
const entries = part.type === "text" && typeof part.text === "string" ? textEntries(part.text) : part.type === "tool-call" ? toolCallEntries(part) : NO_ENTRIES;
|
|
162
|
+
partEntriesCache.set(part, entries);
|
|
163
|
+
return entries;
|
|
164
|
+
}
|
|
165
|
+
const factsCache = /* @__PURE__ */ new WeakMap();
|
|
166
|
+
function isBoundPart(part) {
|
|
167
|
+
return core.getExternalStoreMessages(part).length > 0;
|
|
168
|
+
}
|
|
169
|
+
function messageFacts(message) {
|
|
170
|
+
var _a, _b;
|
|
171
|
+
const cached = factsCache.get(message);
|
|
172
|
+
if (cached !== void 0) return cached;
|
|
173
|
+
let facts;
|
|
174
|
+
if (message.role === "user") {
|
|
175
|
+
const call = isCallUserMessage(message);
|
|
176
|
+
facts = { breaksCall: !call, callFrom: call ? 0 : -1 };
|
|
177
|
+
} else if (message.role === "assistant") {
|
|
178
|
+
let callFrom = message.content.findIndex(
|
|
179
|
+
(part) => part.type === "text" && voicePresentation.isSpokenTurnTextPart(part) || part.type === "tool-call" && part.toolName === voicePresentation.VOICE_UPDATE_TOOL_NAME
|
|
180
|
+
);
|
|
181
|
+
if (callFrom < 0 && ((_b = (_a = message.metadata) == null ? void 0 : _a.custom) == null ? void 0 : _b.source) === voicePresentation.VOICE_SOURCE_SPOKEN && !message.content.some(isBoundPart)) {
|
|
182
|
+
callFrom = 0;
|
|
183
|
+
}
|
|
184
|
+
facts = { breaksCall: false, callFrom };
|
|
185
|
+
} else {
|
|
186
|
+
facts = { breaksCall: false, callFrom: -1 };
|
|
187
|
+
}
|
|
188
|
+
factsCache.set(message, facts);
|
|
189
|
+
return facts;
|
|
190
|
+
}
|
|
191
|
+
const recapMemo = /* @__PURE__ */ new WeakMap();
|
|
192
|
+
const internedRecaps = /* @__PURE__ */ new Map();
|
|
193
|
+
const MAX_INTERNED_RECAPS = 200;
|
|
194
|
+
function recapSignature(recap) {
|
|
195
|
+
return JSON.stringify([
|
|
196
|
+
recap.endMessageId,
|
|
197
|
+
recap.trailing,
|
|
198
|
+
recap.assets.map((asset) => [asset.assetId, asset.title, asset.assetType, asset.href]),
|
|
199
|
+
recap.links.map((link) => [link.url, link.label]),
|
|
200
|
+
recap.openToolCalls.map((call) => [call.toolCallId, call.toolName])
|
|
201
|
+
]);
|
|
202
|
+
}
|
|
203
|
+
function intern(recap) {
|
|
204
|
+
const signature = recapSignature(recap);
|
|
205
|
+
const existing = internedRecaps.get(signature);
|
|
206
|
+
if (existing !== void 0) {
|
|
207
|
+
internedRecaps.delete(signature);
|
|
208
|
+
internedRecaps.set(signature, existing);
|
|
209
|
+
return existing;
|
|
210
|
+
}
|
|
211
|
+
const frozen = Object.freeze({
|
|
212
|
+
...recap,
|
|
213
|
+
assets: Object.freeze([...recap.assets]),
|
|
214
|
+
links: Object.freeze([...recap.links]),
|
|
215
|
+
openToolCalls: Object.freeze([...recap.openToolCalls])
|
|
216
|
+
});
|
|
217
|
+
internedRecaps.set(signature, frozen);
|
|
218
|
+
if (internedRecaps.size > MAX_INTERNED_RECAPS) {
|
|
219
|
+
const oldest = internedRecaps.keys().next().value;
|
|
220
|
+
if (oldest !== void 0) internedRecaps.delete(oldest);
|
|
221
|
+
}
|
|
222
|
+
return frozen;
|
|
223
|
+
}
|
|
224
|
+
function sameSpan(a, b) {
|
|
225
|
+
if (a.length !== b.length) return false;
|
|
226
|
+
for (let index = 0; index < a.length; index += 1) {
|
|
227
|
+
if (a[index] !== b[index]) return false;
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
function buildRecap({
|
|
232
|
+
span,
|
|
233
|
+
startPart,
|
|
234
|
+
trailing
|
|
235
|
+
}) {
|
|
236
|
+
const assets = /* @__PURE__ */ new Map();
|
|
237
|
+
const links = /* @__PURE__ */ new Map();
|
|
238
|
+
const openToolCalls = /* @__PURE__ */ new Map();
|
|
239
|
+
span.forEach((message, position) => {
|
|
240
|
+
const parts = position === 0 ? message.content.slice(startPart) : message.content;
|
|
241
|
+
for (const part of parts) {
|
|
242
|
+
for (const entry of partEntries(part)) {
|
|
243
|
+
if (entry.kind === "asset") {
|
|
244
|
+
const known = assets.get(entry.asset.assetId);
|
|
245
|
+
if (!known) {
|
|
246
|
+
assets.set(entry.asset.assetId, entry.asset);
|
|
247
|
+
} else if (!known.title && entry.asset.title || !known.assetType && entry.asset.assetType) {
|
|
248
|
+
assets.set(entry.asset.assetId, {
|
|
249
|
+
...known,
|
|
250
|
+
title: known.title || entry.asset.title,
|
|
251
|
+
assetType: known.assetType ?? entry.asset.assetType,
|
|
252
|
+
href: known.href ?? entry.asset.href
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
} else if (entry.kind === "link") {
|
|
256
|
+
if (!links.has(entry.link.key)) links.set(entry.link.key, entry.link);
|
|
257
|
+
} else if (!openToolCalls.has(entry.call.toolCallId)) {
|
|
258
|
+
openToolCalls.set(entry.call.toolCallId, entry.call);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
const end = span[span.length - 1];
|
|
264
|
+
return {
|
|
265
|
+
endMessageId: (end == null ? void 0 : end.id) ?? "",
|
|
266
|
+
trailing,
|
|
267
|
+
assets: [...assets.values()],
|
|
268
|
+
links: [...links.values()],
|
|
269
|
+
openToolCalls: [...openToolCalls.values()]
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function callRecapAfter(messages, index) {
|
|
273
|
+
const message = messages[index];
|
|
274
|
+
if (message === void 0) return null;
|
|
275
|
+
const next = messages[index + 1];
|
|
276
|
+
if (next !== void 0 && (next.role !== "user" || isCallUserMessage(next))) return null;
|
|
277
|
+
let start = -1;
|
|
278
|
+
let startPart = -1;
|
|
279
|
+
for (let position = index; position >= 0; position -= 1) {
|
|
280
|
+
const candidate = messages[position];
|
|
281
|
+
if (candidate === void 0) break;
|
|
282
|
+
const facts = messageFacts(candidate);
|
|
283
|
+
if (facts.breaksCall) break;
|
|
284
|
+
if (facts.callFrom >= 0) {
|
|
285
|
+
start = position;
|
|
286
|
+
startPart = facts.callFrom;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (start < 0) return null;
|
|
290
|
+
const span = messages.slice(start, index + 1);
|
|
291
|
+
const trailing = next === void 0;
|
|
292
|
+
const memo = recapMemo.get(message);
|
|
293
|
+
if (memo !== void 0 && memo.startPart === startPart && memo.trailing === trailing && sameSpan(memo.span, span)) {
|
|
294
|
+
return memo.recap;
|
|
295
|
+
}
|
|
296
|
+
const recap = intern(buildRecap({ span, startPart, trailing }));
|
|
297
|
+
recapMemo.set(message, { span, startPart, trailing, recap });
|
|
298
|
+
return recap;
|
|
299
|
+
}
|
|
300
|
+
const EMPTY_IDS = /* @__PURE__ */ new Set();
|
|
301
|
+
function callRecapItems(recap, awaitingApprovalToolCallIds = EMPTY_IDS) {
|
|
302
|
+
const approvals = recap.openToolCalls.filter((call) => awaitingApprovalToolCallIds.has(call.toolCallId)).map((call) => ({
|
|
303
|
+
kind: "approval",
|
|
304
|
+
key: `approval:${call.toolCallId}`,
|
|
305
|
+
toolCallId: call.toolCallId,
|
|
306
|
+
toolName: call.toolName
|
|
307
|
+
}));
|
|
308
|
+
return [...approvals, ...recap.assets, ...recap.links];
|
|
309
|
+
}
|
|
310
|
+
const CALL_RECAP_TITLE = "From the call";
|
|
311
|
+
const CALL_RECAP_APPROVAL_LABEL = "Waiting for your approval";
|
|
312
|
+
exports.CALL_RECAP_APPROVAL_LABEL = CALL_RECAP_APPROVAL_LABEL;
|
|
313
|
+
exports.CALL_RECAP_TITLE = CALL_RECAP_TITLE;
|
|
314
|
+
exports.VOICE_TYPED_ID_PREFIX = VOICE_TYPED_ID_PREFIX;
|
|
315
|
+
exports.callRecapAfter = callRecapAfter;
|
|
316
|
+
exports.callRecapItems = callRecapItems;
|
|
317
|
+
exports.isCallUserMessage = isCallUserMessage;
|
|
318
|
+
exports.mayHaveLiveCall = mayHaveLiveCall;
|
|
319
|
+
//# sourceMappingURL=call-recap.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-recap.cjs","sources":["../../../../agent-runtime/src/call-recap.ts"],"sourcesContent":["/**\n * \"From the call\": what a voice call produced or mentioned, gathered where the\n * call's stretch of the transcript ends, so a person who talked to the agent\n * and later reads the conversation as text (on the same app or another) finds\n * the call's assets, links and open approvals in one place.\n *\n * Everything here is read from the transcript every app already renders, and\n * nothing is invented: an asset item is an asset link or `@`-mention in a\n * message, or a tool result that names the asset it made or read (`asset_id`\n * with a `title`); a link item is a web link in a message; an approval item is\n * a tool call of the call's that has no result yet, shown only while the\n * host's live approval request still names it (the presenter's\n * {@link callRecapItems}).\n *\n * **Where a call is in the transcript.** The host marks every turn a call\n * commits (`metadata.custom.source`, see `voice-presentation.ts`): spoken turns\n * (`voice`), messages typed into the call (`text`, id `voicetext_…`) and the\n * tasks the live model handed the thread (`voice_delegation`, id\n * `voicetask_…`). A typed message sent outside a call carries none of these. So\n * a call's stretch starts at its first marked turn and runs, through the\n * assistant work in between (the delegated runs), up to the next user message\n * that is not a call turn, or the end of the transcript. A spoken assistant\n * turn the converter joined onto the end of an earlier run's message starts\n * the stretch at that part, so the earlier run's artifacts stay out. A\n * delegated run's `send_voice_update` call (a tool only voice-delegated runs\n * get) marks the call too, so a windowed transcript whose loaded tail starts\n * inside a long delegated run still finds it.\n *\n * Two calls with no typed message between them read as one stretch: the\n * transcript does not say where one ended and the next began (the host keeps\n * no per-call marker), so their card collects both. A stretch that ends the\n * transcript may still be live; the presenter shows it once the host reports\n * no voice session (`state.voice` is null), see {@link mayHaveLiveCall}.\n *\n * **Cost.** Every message row asks {@link callRecapAfter} on every transcript\n * update. A row that does not end a stretch answers after looking at the next\n * message; a row that does walks back over its own stretch only. What a message\n * and each of its parts contribute is memoized per object (statewire replaces a\n * changed message and assistant-ui keeps every unchanged part), and an equal\n * recap is the same frozen object, so a row selecting it re-renders only when\n * the card's contents change. The transcript is never re-scanned per delta.\n */\n\nimport { getExternalStoreMessages, type ThreadMessage } from '@assistant-ui/core';\nimport { isDeepAgentStateHydrated, readDeepAgentStreamValues } from './statewire-state';\nimport {\n classifyToolResult,\n resultRecord,\n stringField,\n type ToolCallStatusLike,\n} from './tool-result';\nimport {\n isSpokenTurnTextPart,\n VOICE_DELEGATION_ID_PREFIX,\n VOICE_UPDATE_TOOL_NAME,\n VOICE_SOURCE_DELEGATION,\n VOICE_SOURCE_SPOKEN,\n VOICE_SOURCE_TYPED,\n} from './voice-presentation';\n\n/** Id prefix of a message typed into a live call (`voice/user-text`). */\nexport const VOICE_TYPED_ID_PREFIX = 'voicetext_';\n\n/** An asset the call created, read or linked. */\nexport interface CallRecapAsset {\n readonly kind: 'asset';\n /** Unique within a recap. */\n readonly key: string;\n readonly assetId: string;\n /** What the transcript calls it (the link's label, or the tool result's title); may be empty. */\n readonly title: string;\n /** The asset type the link or tool named (`document`, `spreadsheet`, …), when it named one. */\n readonly assetType: string | null;\n /** The link the transcript carried for it, when it came from a link. */\n readonly href: string | null;\n}\n\n/** A web page the call linked. */\nexport interface CallRecapLink {\n readonly kind: 'link';\n readonly key: string;\n readonly url: string;\n /** The link's own label, else the page's host and path. */\n readonly label: string;\n}\n\n/** A tool call of the call's that is waiting for the person's approval. */\nexport interface CallRecapApproval {\n readonly kind: 'approval';\n readonly key: string;\n readonly toolCallId: string;\n readonly toolName: string;\n}\n\nexport type CallRecapItem = CallRecapAsset | CallRecapLink | CallRecapApproval;\n\n/** A tool call in the stretch with no result yet: an approval while the host's request names it. */\nexport interface CallRecapOpenToolCall {\n readonly toolCallId: string;\n readonly toolName: string;\n}\n\nexport interface CallRecap {\n /** The message the stretch ends at; the card renders right after it. */\n readonly endMessageId: string;\n /** The stretch ends the transcript, so the call may still be live. */\n readonly trailing: boolean;\n /** Assets, in the order the stretch first mentioned them. */\n readonly assets: readonly CallRecapAsset[];\n /** Web links, in the order the stretch first mentioned them. */\n readonly links: readonly CallRecapLink[];\n /** Result-less tool calls, in transcript order. */\n readonly openToolCalls: readonly CallRecapOpenToolCall[];\n}\n\n/** The fields of an assistant-ui thread message this reads (structural, so any runtime's message fits). */\nexport interface CallRecapMessage {\n readonly id: string;\n readonly role: string;\n readonly content: readonly CallRecapPart[];\n readonly metadata?: { readonly custom?: Record<string, unknown> } | undefined;\n}\n\n/** The fields of a message part this reads. */\nexport interface CallRecapPart {\n readonly type: string;\n readonly text?: string;\n readonly toolCallId?: string;\n readonly toolName?: string;\n readonly result?: unknown;\n readonly isError?: boolean;\n /** An interim result a running tool published (`markPreliminaryToolResults`); the real one has not arrived. */\n readonly isPreliminary?: boolean;\n readonly status?: unknown;\n}\n\nconst CALL_SOURCES: ReadonlySet<unknown> = new Set([\n VOICE_SOURCE_SPOKEN,\n VOICE_SOURCE_TYPED,\n VOICE_SOURCE_DELEGATION,\n]);\n\n/**\n * Whether a user message is one of a call's turns: spoken, typed into the call,\n * or a task the live model delegated (by source, or by id on hosts that drop\n * the command's metadata).\n */\nexport function isCallUserMessage(\n message: Pick<CallRecapMessage, 'id' | 'role' | 'metadata'>,\n): boolean {\n if (message.role !== 'user') return false;\n if (CALL_SOURCES.has(message.metadata?.custom?.source)) return true;\n return (\n message.id.startsWith(VOICE_DELEGATION_ID_PREFIX) ||\n message.id.startsWith(VOICE_TYPED_ID_PREFIX)\n );\n}\n\n/**\n * Whether a stretch that ends the transcript may still be a live call: the\n * host reports a voice session (`state.voice`), or its state has not loaded\n * yet. A primitive read of the raw statewire state, safe as a selector.\n */\nexport function mayHaveLiveCall(state: unknown): boolean {\n if (!isDeepAgentStateHydrated(state)) return true;\n const voice: unknown = readDeepAgentStreamValues(state).voice;\n return voice !== null && voice !== undefined;\n}\n\n// ── Per-part entries ─────────────────────────────────────────────────────────\n\ntype Entry =\n | { readonly kind: 'asset'; readonly asset: CallRecapAsset }\n | { readonly kind: 'link'; readonly link: CallRecapLink }\n | { readonly kind: 'open-call'; readonly call: CallRecapOpenToolCall };\n\nconst NO_ENTRIES: readonly Entry[] = Object.freeze([]);\n\n/** `[label](href)` with an optional `\"title\"`; one level of brackets in the label, one of parens in the href. */\nconst MARKDOWN_LINK =\n /\\[((?:[^[\\]\\n]|\\[[^[\\]\\n]*\\])*)\\]\\(\\s*<?((?:[^()\\s<>]|\\([^()\\s]*\\))+)>?(?:\\s+\"[^\"\\n]*\")?\\s*\\)/g;\n/** A bare web address in prose. */\nconst BARE_URL = /\\bhttps?:\\/\\/[^\\s<>()[\\]\"'`]+/g;\n/** Sentence punctuation that follows an address rather than belonging to it. */\nconst TRAILING_PUNCTUATION = /[.,;:!?'\"*_]+$/;\n/** The Spaces route an asset link opens (`/dashboard/spaces?asset_ids=…`). */\nconst SPACES_PATH = /\\/dashboard\\/spaces\\/?$/;\n/** Past this a tool result is not read for an asset: a created asset's answer is small. */\nconst MAX_TOOL_RESULT_CHARS = 20_000;\n\nfunction assetEntry(asset: Omit<CallRecapAsset, 'kind' | 'key'>): Entry {\n return {\n kind: 'asset',\n asset: { kind: 'asset', key: `asset:${asset.assetId}`, ...asset },\n };\n}\n\n/** `sheet` is the old spelling of `spreadsheet` in Spaces links. */\nfunction normalizeAssetType(value: string | null): string | null {\n if (!value) return null;\n return value === 'sheet' ? 'spreadsheet' : value;\n}\n\nfunction labelOf(raw: string): string {\n return raw.trim().replace(/^@/, '').trim();\n}\n\n/** The page's host and path, for a link that carries no label of its own. */\nfunction displayUrl(url: URL): string {\n const host = url.hostname.replace(/^www\\./, '');\n const path = url.pathname === '/' ? '' : url.pathname.replace(/\\/$/, '');\n return `${host}${path}`;\n}\n\n/** The base a relative Spaces link (`/dashboard/spaces?asset_ids=…`) resolves against; only its query is read. */\nconst RELATIVE_LINK_BASE = 'https://app.athenaintel.com';\n\n/** The assets an Athena link names, the web page a link names, or nothing. */\nfunction entriesOfHref(href: string, rawLabel: string): readonly Entry[] {\n const label = labelOf(rawLabel);\n let url: URL;\n try {\n url = new URL(href, href.startsWith('/') ? RELATIVE_LINK_BASE : undefined);\n } catch {\n return [];\n }\n if (url.protocol === 'mention:') {\n if (url.hostname !== 'asset') return [];\n const assetId = url.searchParams.get('id') ?? url.searchParams.get('asset_id');\n if (!assetId?.startsWith('asset_')) return [];\n return [\n assetEntry({\n assetId,\n title: label || (url.searchParams.get('name') ?? ''),\n assetType: normalizeAssetType(\n url.searchParams.get('asset_type') ?? url.searchParams.get('type'),\n ),\n href,\n }),\n ];\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') return [];\n const assetIds = url.searchParams.get('asset_ids');\n if (assetIds && SPACES_PATH.test(url.pathname)) {\n const ids = [\n ...new Set(\n assetIds\n .split(',')\n .map((id) => id.trim())\n .filter((id) => id.startsWith('asset_')),\n ),\n ];\n const assetType = normalizeAssetType(\n url.searchParams.get('type') ??\n url.searchParams.get('assetType') ??\n url.searchParams.get('asset_type'),\n );\n // A link that opens several assets side by side names each of them: one\n // item per asset, each opening only its own asset.\n return ids.map((assetId) => {\n if (ids.length === 1) return assetEntry({ assetId, title: label, assetType, href });\n const single = new URL(url.toString());\n single.searchParams.set('asset_ids', assetId);\n return assetEntry({\n assetId,\n title: label,\n assetType,\n href: href.startsWith('/') ? `${single.pathname}${single.search}` : single.toString(),\n });\n });\n }\n // Any other relative link is a route inside the app, not a page the call named.\n if (href.startsWith('/')) return [];\n const shown = label && label !== href ? label : displayUrl(url);\n const key = `${url.origin}${url.pathname.replace(/\\/$/, '')}${url.search}`;\n return [\n {\n kind: 'link',\n link: { kind: 'link', key: `link:${key}`, url: url.toString(), label: shown },\n },\n ];\n}\n\n/** Fenced code blocks and inline code spans: an address shown as code is not a link the message names. */\nconst CODE = /```[\\s\\S]*?(?:```|$)|`[^`\\n]*`/g;\n\n/** The asset and web links a text part carries, in the order it carries them. */\nfunction textEntries(raw: string): readonly Entry[] {\n // An address (`https://`, `mention://`) or a relative Spaces link (`](/dashboard/…`).\n if (!raw.includes('://') && !raw.includes('](/')) return NO_ENTRIES;\n // Blanked to the same length, so the rest keeps its offsets (the order the part names things in).\n const text = raw.includes('`') ? raw.replace(CODE, (code) => ' '.repeat(code.length)) : raw;\n const found: { index: number; entry: Entry }[] = [];\n const covered: [number, number][] = [];\n for (const match of text.matchAll(MARKDOWN_LINK)) {\n const [whole, label = '', href = ''] = match;\n covered.push([match.index, match.index + whole.length]);\n // `` is an image the message shows, not a link it names.\n if (text[match.index - 1] === '!') continue;\n for (const entry of entriesOfHref(href, label)) found.push({ index: match.index, entry });\n }\n for (const match of text.matchAll(BARE_URL)) {\n const start = match.index;\n if (covered.some(([from, to]) => start >= from && start < to)) continue;\n const href = match[0].replace(TRAILING_PUNCTUATION, '');\n for (const entry of entriesOfHref(href, '')) found.push({ index: start, entry });\n }\n if (found.length === 0) return NO_ENTRIES;\n found.sort((a, b) => a.index - b.index);\n return Object.freeze(found.map(({ entry }) => entry));\n}\n\n/** The asset a finished tool call reports (`asset_id` with a `title`), the open call, or nothing. */\nfunction toolCallEntries(part: CallRecapPart): readonly Entry[] {\n const toolCallId = part.toolCallId;\n const toolName = part.toolName ?? 'tool';\n if (!toolCallId) return NO_ENTRIES;\n if (part.result === undefined) {\n return Object.freeze([{ kind: 'open-call', call: { toolCallId, toolName } }]);\n }\n // A running tool's interim payload is not its outcome: the asset counts once the tool finished.\n if (part.isPreliminary === true) return NO_ENTRIES;\n if (typeof part.result === 'string' && part.result.length > MAX_TOOL_RESULT_CHARS)\n return NO_ENTRIES;\n const record = resultRecord(part.result);\n if (!record) return NO_ENTRIES;\n const assetId = stringField(record, ['asset_id', 'assetId']);\n const title = stringField(record, ['title', 'asset_title', 'name']);\n if (!assetId?.startsWith('asset_') || !title) return NO_ENTRIES;\n const outcome = classifyToolResult({\n status: { type: 'complete' } satisfies ToolCallStatusLike,\n result: part.result,\n isError: part.isError,\n });\n if (outcome.state !== 'complete') return NO_ENTRIES;\n return Object.freeze([\n assetEntry({\n assetId,\n title: title.trim(),\n assetType: normalizeAssetType(stringField(record, ['asset_type', 'assetType'])),\n href: null,\n }),\n ]);\n}\n\nconst partEntriesCache = new WeakMap<object, readonly Entry[]>();\n\nfunction partEntries(part: CallRecapPart): readonly Entry[] {\n const cached = partEntriesCache.get(part);\n if (cached !== undefined) return cached;\n const entries =\n part.type === 'text' && typeof part.text === 'string'\n ? textEntries(part.text)\n : part.type === 'tool-call'\n ? toolCallEntries(part)\n : NO_ENTRIES;\n partEntriesCache.set(part, entries);\n return entries;\n}\n\n// ── Per-message facts ────────────────────────────────────────────────────────\n\ninterface MessageFacts {\n /** A user message outside any call: it ends the stretch before it. */\n readonly breaksCall: boolean;\n /** The first part that belongs to a call (a call turn's first part, a spoken assistant part, a voice update), or -1. */\n readonly callFrom: number;\n}\n\nconst factsCache = new WeakMap<object, MessageFacts>();\n\n/** Whether the converter bound a part to its wire entry (`isSpokenTurnTextPart` reads that entry). */\nfunction isBoundPart(part: CallRecapPart): boolean {\n return getExternalStoreMessages<unknown>(part as ThreadMessage['content'][number]).length > 0;\n}\n\nfunction messageFacts(message: CallRecapMessage): MessageFacts {\n const cached = factsCache.get(message);\n if (cached !== undefined) return cached;\n let facts: MessageFacts;\n if (message.role === 'user') {\n const call = isCallUserMessage(message);\n facts = { breaksCall: !call, callFrom: call ? 0 : -1 };\n } else if (message.role === 'assistant') {\n // A spoken turn, or the interim-update tool only a run the live model\n // delegated is given (`send_voice_update`): a delegated run still marks its\n // call when a windowed transcript has not loaded the turns before it.\n let callFrom = message.content.findIndex(\n (part) =>\n (part.type === 'text' && isSpokenTurnTextPart(part)) ||\n (part.type === 'tool-call' && part.toolName === VOICE_UPDATE_TOOL_NAME),\n );\n // A message no converter bound reads by its own source (one spoken turn).\n if (\n callFrom < 0 &&\n message.metadata?.custom?.source === VOICE_SOURCE_SPOKEN &&\n !message.content.some(isBoundPart)\n ) {\n callFrom = 0;\n }\n facts = { breaksCall: false, callFrom };\n } else {\n facts = { breaksCall: false, callFrom: -1 };\n }\n factsCache.set(message, facts);\n return facts;\n}\n\n// ── The recap at a row ───────────────────────────────────────────────────────\n\ninterface RecapMemo {\n /** The stretch's messages, start first, as last read. */\n readonly span: readonly CallRecapMessage[];\n readonly startPart: number;\n readonly trailing: boolean;\n readonly recap: CallRecap;\n}\n\n/** Keyed by the stretch's last message: a settled stretch answers from here without rebuilding. */\nconst recapMemo = new WeakMap<object, RecapMemo>();\n\n/** Equal recaps share one frozen object (bounded; a miss only costs a rebuild). */\nconst internedRecaps = new Map<string, CallRecap>();\nconst MAX_INTERNED_RECAPS = 200;\n\nfunction recapSignature(recap: CallRecap): string {\n return JSON.stringify([\n recap.endMessageId,\n recap.trailing,\n recap.assets.map((asset) => [asset.assetId, asset.title, asset.assetType, asset.href]),\n recap.links.map((link) => [link.url, link.label]),\n recap.openToolCalls.map((call) => [call.toolCallId, call.toolName]),\n ]);\n}\n\nfunction intern(recap: CallRecap): CallRecap {\n const signature = recapSignature(recap);\n const existing = internedRecaps.get(signature);\n if (existing !== undefined) {\n // Most recently used last, so the bound evicts the stalest.\n internedRecaps.delete(signature);\n internedRecaps.set(signature, existing);\n return existing;\n }\n const frozen: CallRecap = Object.freeze({\n ...recap,\n assets: Object.freeze([...recap.assets]),\n links: Object.freeze([...recap.links]),\n openToolCalls: Object.freeze([...recap.openToolCalls]),\n });\n internedRecaps.set(signature, frozen);\n if (internedRecaps.size > MAX_INTERNED_RECAPS) {\n const oldest = internedRecaps.keys().next().value;\n if (oldest !== undefined) internedRecaps.delete(oldest);\n }\n return frozen;\n}\n\nfunction sameSpan(a: readonly CallRecapMessage[], b: readonly CallRecapMessage[]): boolean {\n if (a.length !== b.length) return false;\n for (let index = 0; index < a.length; index += 1) {\n if (a[index] !== b[index]) return false;\n }\n return true;\n}\n\nfunction buildRecap({\n span,\n startPart,\n trailing,\n}: {\n span: readonly CallRecapMessage[];\n startPart: number;\n trailing: boolean;\n}): CallRecap {\n const assets = new Map<string, CallRecapAsset>();\n const links = new Map<string, CallRecapLink>();\n const openToolCalls = new Map<string, CallRecapOpenToolCall>();\n span.forEach((message, position) => {\n const parts = position === 0 ? message.content.slice(startPart) : message.content;\n for (const part of parts) {\n for (const entry of partEntries(part)) {\n if (entry.kind === 'asset') {\n const known = assets.get(entry.asset.assetId);\n if (!known) {\n assets.set(entry.asset.assetId, entry.asset);\n } else if (\n (!known.title && entry.asset.title) ||\n (!known.assetType && entry.asset.assetType)\n ) {\n // A later mention fills what the first one left out; the first keeps its place.\n assets.set(entry.asset.assetId, {\n ...known,\n title: known.title || entry.asset.title,\n assetType: known.assetType ?? entry.asset.assetType,\n href: known.href ?? entry.asset.href,\n });\n }\n } else if (entry.kind === 'link') {\n if (!links.has(entry.link.key)) links.set(entry.link.key, entry.link);\n } else if (!openToolCalls.has(entry.call.toolCallId)) {\n openToolCalls.set(entry.call.toolCallId, entry.call);\n }\n }\n }\n });\n const end = span[span.length - 1];\n return {\n endMessageId: end?.id ?? '',\n trailing,\n assets: [...assets.values()],\n links: [...links.values()],\n openToolCalls: [...openToolCalls.values()],\n };\n}\n\n/**\n * The recap of the call stretch that ends at `messages[index]`, or `null` when\n * no call stretch ends there. Cheap for every other row (it looks at the next\n * message only); the same frozen object while the recap is unchanged, so it is\n * safe as a `useAuiState` selector result.\n */\nexport function callRecapAfter(\n messages: readonly CallRecapMessage[],\n index: number,\n): CallRecap | null {\n const message = messages[index];\n if (message === undefined) return null;\n const next = messages[index + 1];\n // Only a user message outside a call ends a stretch; an assistant message never does.\n if (next !== undefined && (next.role !== 'user' || isCallUserMessage(next))) return null;\n\n // Walk back over this stretch only: to the user message outside a call that opened it.\n let start = -1;\n let startPart = -1;\n for (let position = index; position >= 0; position -= 1) {\n const candidate = messages[position];\n if (candidate === undefined) break;\n const facts = messageFacts(candidate);\n if (facts.breaksCall) break;\n if (facts.callFrom >= 0) {\n start = position;\n startPart = facts.callFrom;\n }\n }\n if (start < 0) return null;\n\n const span = messages.slice(start, index + 1);\n const trailing = next === undefined;\n const memo = recapMemo.get(message);\n if (\n memo !== undefined &&\n memo.startPart === startPart &&\n memo.trailing === trailing &&\n sameSpan(memo.span, span)\n ) {\n return memo.recap;\n }\n const recap = intern(buildRecap({ span, startPart, trailing }));\n recapMemo.set(message, { span, startPart, trailing, recap });\n return recap;\n}\n\n/** Whether a recap has anything to show once approvals are matched (see {@link callRecapItems}). */\nexport function callRecapHasItems(\n recap: CallRecap,\n awaitingApprovalToolCallIds: ReadonlySet<string> = EMPTY_IDS,\n): boolean {\n return (\n recap.assets.length > 0 ||\n recap.links.length > 0 ||\n recap.openToolCalls.some((call) => awaitingApprovalToolCallIds.has(call.toolCallId))\n );\n}\n\nconst EMPTY_IDS: ReadonlySet<string> = new Set();\n\n/**\n * The card's items: the call's tool calls still waiting for the person's\n * approval (the host's live request names them), then its assets, then its\n * web links. Pure; hosts memoize it on the recap and the id set.\n */\nexport function callRecapItems(\n recap: CallRecap,\n awaitingApprovalToolCallIds: ReadonlySet<string> = EMPTY_IDS,\n): CallRecapItem[] {\n const approvals: CallRecapApproval[] = recap.openToolCalls\n .filter((call) => awaitingApprovalToolCallIds.has(call.toolCallId))\n .map((call) => ({\n kind: 'approval',\n key: `approval:${call.toolCallId}`,\n toolCallId: call.toolCallId,\n toolName: call.toolName,\n }));\n return [...approvals, ...recap.assets, ...recap.links];\n}\n\n/** The card's heading, worded once for every app. */\nexport const CALL_RECAP_TITLE = 'From the call';\n\n/** An approval item's copy, worded once for every app. */\nexport const CALL_RECAP_APPROVAL_LABEL = 'Waiting for your approval';\n"],"names":["VOICE_SOURCE_SPOKEN","VOICE_SOURCE_TYPED","VOICE_SOURCE_DELEGATION","VOICE_DELEGATION_ID_PREFIX","isDeepAgentStateHydrated","readDeepAgentStreamValues","resultRecord","stringField","classifyToolResult","getExternalStoreMessages","isSpokenTurnTextPart","VOICE_UPDATE_TOOL_NAME"],"mappings":"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"}
|