@athenaintel/react 0.56.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +71 -1
  2. package/dist/agent-runtime/src/inbound-channel.cjs +96 -0
  3. package/dist/agent-runtime/src/inbound-channel.cjs.map +1 -0
  4. package/dist/agent-runtime/src/inbound-channel.d.ts +75 -0
  5. package/dist/agent-runtime/src/inbound-channel.js +96 -0
  6. package/dist/agent-runtime/src/inbound-channel.js.map +1 -0
  7. package/dist/agent-runtime/src/index.d.ts +2 -1
  8. package/dist/agent-runtime/src/interrupts.cjs +45 -3
  9. package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
  10. package/dist/agent-runtime/src/interrupts.d.ts +51 -4
  11. package/dist/agent-runtime/src/interrupts.js +46 -4
  12. package/dist/agent-runtime/src/interrupts.js.map +1 -1
  13. package/dist/agent-runtime/src/offline-status.cjs.map +1 -1
  14. package/dist/agent-runtime/src/offline-status.d.ts +25 -0
  15. package/dist/agent-runtime/src/offline-status.js.map +1 -1
  16. package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +8 -8
  17. package/dist/agent-runtime/src/tool-metadata.cjs +8 -0
  18. package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
  19. package/dist/agent-runtime/src/tool-metadata.js +8 -0
  20. package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
  21. package/dist/auth/AthenaSSOProvider.cjs +54 -12
  22. package/dist/auth/AthenaSSOProvider.cjs.map +1 -1
  23. package/dist/auth/AthenaSSOProvider.d.ts +23 -2
  24. package/dist/auth/AthenaSSOProvider.js +54 -12
  25. package/dist/auth/AthenaSSOProvider.js.map +1 -1
  26. package/dist/auth/index.cjs +2 -0
  27. package/dist/auth/index.cjs.map +1 -1
  28. package/dist/auth/index.d.ts +1 -0
  29. package/dist/auth/index.js +2 -0
  30. package/dist/auth/index.js.map +1 -1
  31. package/dist/auth/sign-back-in.cjs +141 -0
  32. package/dist/auth/sign-back-in.cjs.map +1 -0
  33. package/dist/auth/sign-back-in.d.ts +117 -0
  34. package/dist/auth/sign-back-in.js +141 -0
  35. package/dist/auth/sign-back-in.js.map +1 -0
  36. package/dist/chat/AthenaChat.cjs +7 -2
  37. package/dist/chat/AthenaChat.cjs.map +1 -1
  38. package/dist/chat/AthenaChat.js +7 -2
  39. package/dist/chat/AthenaChat.js.map +1 -1
  40. package/dist/chat/StatewireApprovalCard.cjs +6 -2
  41. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  42. package/dist/chat/StatewireApprovalCard.d.ts +8 -1
  43. package/dist/chat/StatewireApprovalCard.js +7 -3
  44. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  45. package/dist/chat-ui/index.cjs +4 -0
  46. package/dist/chat-ui/index.cjs.map +1 -1
  47. package/dist/chat-ui/index.d.ts +1 -0
  48. package/dist/chat-ui/index.js +5 -1
  49. package/dist/chat-ui/index.js.map +1 -1
  50. package/dist/chat-ui/messages/inbound-channel-message.cjs +219 -0
  51. package/dist/chat-ui/messages/inbound-channel-message.cjs.map +1 -0
  52. package/dist/chat-ui/messages/inbound-channel-message.d.ts +60 -0
  53. package/dist/chat-ui/messages/inbound-channel-message.js +219 -0
  54. package/dist/chat-ui/messages/inbound-channel-message.js.map +1 -0
  55. package/dist/chat-ui/messages/inbound-channel-reader.cjs +184 -0
  56. package/dist/chat-ui/messages/inbound-channel-reader.cjs.map +1 -0
  57. package/dist/chat-ui/messages/inbound-channel-reader.d.ts +65 -0
  58. package/dist/chat-ui/messages/inbound-channel-reader.js +184 -0
  59. package/dist/chat-ui/messages/inbound-channel-reader.js.map +1 -0
  60. package/dist/chat-ui/messages/message-components.cjs +18 -14
  61. package/dist/chat-ui/messages/message-components.cjs.map +1 -1
  62. package/dist/chat-ui/messages/message-components.d.ts +7 -0
  63. package/dist/chat-ui/messages/message-components.js +19 -15
  64. package/dist/chat-ui/messages/message-components.js.map +1 -1
  65. package/dist/composer/sign-back-in-draft.cjs +62 -0
  66. package/dist/composer/sign-back-in-draft.cjs.map +1 -0
  67. package/dist/composer/sign-back-in-draft.d.ts +34 -0
  68. package/dist/composer/sign-back-in-draft.js +62 -0
  69. package/dist/composer/sign-back-in-draft.js.map +1 -0
  70. package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
  71. package/dist/diagnostics/athena-diagnostics.d.ts +6 -1
  72. package/dist/diagnostics/athena-diagnostics.js.map +1 -1
  73. package/dist/index.cjs +8 -0
  74. package/dist/index.cjs.map +1 -1
  75. package/dist/index.d.ts +2 -0
  76. package/dist/index.js +8 -0
  77. package/dist/index.js.map +1 -1
  78. package/dist/provider/AthenaProvider.cjs +48 -3
  79. package/dist/provider/AthenaProvider.cjs.map +1 -1
  80. package/dist/provider/AthenaProvider.js +49 -4
  81. package/dist/provider/AthenaProvider.js.map +1 -1
  82. package/dist/react/package.json.cjs +1 -1
  83. package/dist/react/package.json.js +1 -1
  84. package/dist/runtime/auth.cjs +11 -0
  85. package/dist/runtime/auth.cjs.map +1 -1
  86. package/dist/runtime/auth.d.ts +18 -1
  87. package/dist/runtime/auth.js +11 -0
  88. package/dist/runtime/auth.js.map +1 -1
  89. package/dist/styles.css +1 -1
  90. package/package.json +1 -1
package/README.md CHANGED
@@ -567,6 +567,35 @@ only when it is an `https://github.com/<repo>/…` URL for that repository
567
567
  The full message text always reaches the model; the marker is provenance for
568
568
  rendering only.
569
569
 
570
+ ### Messages from Slack, email, texts and Teams
571
+
572
+ A message that reached an agent over a channel arrives with two things from
573
+ agora's channel routers: the `inbound_channel` envelope in
574
+ `metadata.custom` (the channel, the sender's name and address or number, an
575
+ email's subject), and a routing block at the start of its text
576
+ (`<inbound-message-context>` … `</inbound-message-context>`: the Slack ids, the
577
+ numbers a text went between, how a reply is delivered), which is how the model
578
+ knows where the message came from.
579
+
580
+ `AthenaChat`'s user message and the `chat-ui` `UserMessage` show the words that
581
+ were sent, without the block, under one line: the channel's mark (Slack's and
582
+ Teams' logos; a mail, text, API or voice-memo glyph), the sender's name and
583
+ their address or number. Hovering the line shows the channel, the place (a
584
+ DM, `#launch`, a group text) and the sender in full; clicking it opens what
585
+ the agent received, every fact of the block labelled, with the Slack thread a
586
+ mention came in. An email's subject is the bubble's first line. A typed
587
+ message is unchanged.
588
+
589
+ Only a message carrying the envelope is drawn this way. Tags someone types at
590
+ the start of an ordinary chat message show as typed.
591
+
592
+ A host with its own user message composes the same pieces:
593
+ `InboundChannelLine` (the line, for the message in scope),
594
+ `InboundChannelSubject`, and `withInboundMessageBody(Text)` (or
595
+ `UserMessageText`, which already shows the words without the block).
596
+ `isInboundChannelMessage(custom)` and `inboundMessageBody({ custom, text })`
597
+ read a message without React.
598
+
570
599
  ## Sessions page and inbox
571
600
 
572
601
  A full-page, Devin-style session list and a notifications inbox, both on top
@@ -951,6 +980,19 @@ Both use the same bearer / API key as session creation. The full recipe
951
980
  template skill `voice-session-guide.md`. `createAthenaVoiceSession` is also
952
981
  exported if you hold the bridge state yourself.
953
982
 
983
+ ## What's new in 0.57.0
984
+
985
+ **Messages from Slack, email, texts and Teams read as what was sent.** A message that reached the agent over a channel used to show, above its words, the routing block the channel writes for the agent: `<inbound-message-context>`, the Slack workspace and channel ids, the numbers a text went between, how a reply is delivered. `AthenaChat` and chat-ui's `UserMessage` now show:
986
+ - **The words that were sent.** The block leaves the bubble, and so does what a channel puts ahead of the words for the agent: the Slack thread a mention came in, a group text's "<name> said:" line. An email's subject is the bubble's first line.
987
+ - **Who sent it, from where.** A line above the bubble carries the channel's mark (Slack's and Teams' logos, a mail, text, API or voice-memo glyph), the sender's name and their email address or phone number. Hovering it shows the channel, the place (a DM, `#launch`, a group text) and the sender in full.
988
+ - **What the agent received.** Clicking the line opens every fact the agent read with the message, labelled, and the thread it quoted.
989
+
990
+ Nothing to change in your app. A message without the channel's envelope (tags someone typed) shows as typed. The hover card is plain CSS and the glyphs are icons the chat already draws, so the line adds no tooltip library to your bundle. Hosts with their own user message compose the same pieces (**Messages from Slack, email, texts and Teams**, above).
991
+
992
+ Also in this release:
993
+ - A session that ends under an open page signs back in instead of stranding the app on a sign-in screen (**When a session ends while the page is open**).
994
+ - "Approve for this session" on a send (an email, a text) covers only the recipients the approval card named, and the card says so.
995
+
954
996
  ## What's new in 0.56.0
955
997
 
956
998
  **One history store per conversation, shared by every reader.** Older history read with `history/page` now lives in a per-conversation store that the thread, the history edge, edits and reveals all join. It no longer sits in a store `AthenaProvider` hands around. The store keeps each message by its parent, not its position:
@@ -2012,7 +2054,35 @@ If you need full control, `ssoUserInfoUrl`, `ssoLoginUrl`, and `ssoLogoutUrl` re
2012
2054
  - A transient failure (5xx, 408, 429, a network error, a timeout) is retried within the read, about 0.5 s, 1 s and 2 s apart, before `displayOnError` shows.
2013
2055
  - Only `unauthenticated` means the person has no session. `request_failed` means the endpoint did not answer usably; the person may well be signed in, so offer `retry` rather than sign-in.
2014
2056
  - `onError` receives the typed `auth_sso_failed` error of a read that gave up.
2015
- - Without `displayOnError`, a person with no session is sent to `ssoLoginUrl`.
2057
+ - Without `displayOnError`, a person with no session is sent to `ssoLoginUrl`, with `return_to` set to the page.
2058
+
2059
+ ### When a session ends while the page is open
2060
+
2061
+ A deployed app's session can end under an open tab: the person signs out of Athena elsewhere (the web app, the desktop app or the phone), or the host's session for the app expires. The tab finds out on its next credential read, a 401 from `/api/sso/userinfo` or `/_athena/auth`. The SDK then sends the page through sign-in once, with `return_to` set to the page, instead of showing a sign-in screen. A person still signed in to Athena in that browser comes straight back to the same page, signed in again, in a second or two. One who is not sees Athena's sign-in page first.
2062
+
2063
+ A 403 there is the host refusing this person the app (Marathon's `not_authorized` or `unknown_asset`). Signing in again cannot change that, so a 403 never starts a trip.
2064
+
2065
+ - **`AthenaSSOProvider`** does this for a session it served on this page (`signBackInOnSessionEnd`, default `true`). The app stays on screen until the page leaves. These show `displayOnError` as before:
2066
+ - a 403;
2067
+ - a first read with no session;
2068
+ - a sign-out through `logout()`;
2069
+ - `signBackInOnSessionEnd={false}`.
2070
+ - **`AthenaProvider`** does it through the Marathon bridge, but only when the bridge's token is the chat's credential. That means no sign-in provider above it, and no `token`, `getToken` or `apiKey` of the host's own, since those keep working when the bridge's session ends.
2071
+ - The bridge goes to `/api/sso/initiate` when `/_athena/auth` refuses, with a 401, a renewal of a credential the page already minted.
2072
+ - Never on the first mint: a page served on a preview grant alone has no session.
2073
+ - Never on a 403, and never on a transient failure.
2074
+ - The typed `auth_bridge_failed` error still reaches `onError` first.
2075
+ - **At most once per tab every two minutes** (`sessionStorage`). A second ending within that time, or a tab without `sessionStorage`, shows the sign-in state instead, so a trip that did not help never loops.
2076
+ - `AthenaSSOProvider` without `displayOnError` keeps the same guard for its own redirect to `ssoLoginUrl`.
2077
+ - Within two minutes of a trip, that provider renders nothing and logs a console warning instead of sending the page again.
2078
+ - **Hash routes come back.** Marathon returns to `return_to` without its URL fragment. The SDK keeps the fragment in the tab and puts it back (`location.replace`) when the page returns to the same path within ten minutes, so a hash-routed app (`/#/chat/123`) lands on the same route.
2079
+ - **The composer's draft comes back.** `AthenaProvider` keeps the draft right before the page leaves and puts it back when the page returns.
2080
+ - Only for the same person (a sign-in provider names them), and within ten minutes. A draft kept for someone else is deleted unread.
2081
+ - It returns into the conversation it was typed in. A draft typed in a new chat goes into the new chat the page comes back to.
2082
+ - A page that comes back to a different conversation shows the draft when the person opens the original one. For example, a thread-list app that does not keep the open conversation in its URL (`threadId` or `initialThreadId`) comes back to a new chat.
2083
+ - Text only: attachments are not carried.
2084
+ - **Your own state.** `ATHENA_SIGN_BACK_IN_EVENT` (from `@athenaintel/react/auth`) is dispatched on `window` right before the page leaves, while the app is still mounted.
2085
+ - **Diagnostics.** Each ending emits `auth.session.ended` with `{ source: 'sso' | 'bridge', status, sign_back_in }`.
2016
2086
 
2017
2087
  ## Mentions and citations
2018
2088
 
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const INBOUND_CHANNEL_KEY = "inbound_channel";
4
+ const INBOUND_CONTEXT_OPEN = "<inbound-message-context>";
5
+ const LEADING_CONTEXT = /^<inbound-message-context>([\s\S]*?)<\/inbound-message-context>\n*/;
6
+ const SLACK_QUOTED_THREAD_OPEN = "Earlier messages in this Slack thread (oldest first):";
7
+ const SLACK_QUOTED_THREAD_MESSAGE = "The message that mentioned you:";
8
+ const GROUP_TEXT_SOURCE = "Group SMS text message";
9
+ function leadingInboundContext(text) {
10
+ if (!text.startsWith(INBOUND_CONTEXT_OPEN)) return null;
11
+ const match = LEADING_CONTEXT.exec(text);
12
+ if (match === null) return null;
13
+ return {
14
+ raw: match[0].trimEnd(),
15
+ lines: match[1] ?? "",
16
+ rest: text.slice(match[0].length).trimStart()
17
+ };
18
+ }
19
+ function inboundContextValue(lines, key) {
20
+ for (const line of lines.split("\n")) {
21
+ const separator = line.indexOf(":");
22
+ if (separator > 0 && line.slice(0, separator).trim() === key) {
23
+ return line.slice(separator + 1).trim() || null;
24
+ }
25
+ }
26
+ return null;
27
+ }
28
+ function stripInboundContext(text) {
29
+ var _a;
30
+ return ((_a = leadingInboundContext(text)) == null ? void 0 : _a.rest) ?? text;
31
+ }
32
+ function splitInboundBody(context) {
33
+ const { lines, rest } = context;
34
+ const source = inboundContextValue(lines, "source");
35
+ if (source === "Slack" && rest.startsWith(`${SLACK_QUOTED_THREAD_OPEN}
36
+ `)) {
37
+ const marker = `
38
+
39
+ ${SLACK_QUOTED_THREAD_MESSAGE}`;
40
+ const at = rest.indexOf(marker);
41
+ if (at !== -1) {
42
+ const quoted = rest.slice(SLACK_QUOTED_THREAD_OPEN.length, at).trim();
43
+ return { body: rest.slice(at + marker.length).trimStart(), quoted: quoted || null };
44
+ }
45
+ }
46
+ if (source === GROUP_TEXT_SOURCE) {
47
+ const sender = inboundContextValue(lines, "sender_name");
48
+ const newline = rest.indexOf("\n");
49
+ const firstLine = newline === -1 ? rest : rest.slice(0, newline);
50
+ if (sender && firstLine.endsWith(GROUP_TEXT_SAID) && collapseWhitespace(firstLine.slice(0, -GROUP_TEXT_SAID.length)) === sender) {
51
+ return { body: newline === -1 ? "" : rest.slice(newline + 1), quoted: null };
52
+ }
53
+ }
54
+ return { body: rest, quoted: null };
55
+ }
56
+ const GROUP_TEXT_SAID = " said:";
57
+ function collapseWhitespace(value) {
58
+ return value.split(/\s+/).filter((word) => word.length > 0).join(" ");
59
+ }
60
+ function envelopeOf(custom) {
61
+ const raw = custom == null ? void 0 : custom[INBOUND_CHANNEL_KEY];
62
+ return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : null;
63
+ }
64
+ function envelopeString(envelope, key) {
65
+ const value = envelope[key];
66
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
67
+ }
68
+ function isInboundChannelMessage(custom) {
69
+ const envelope = envelopeOf(custom);
70
+ return envelope !== null && envelopeString(envelope, "channel") !== null;
71
+ }
72
+ function inboundMessageBody({
73
+ custom,
74
+ text
75
+ }) {
76
+ if (!isInboundChannelMessage(custom)) return text;
77
+ const context = leadingInboundContext(text);
78
+ return context === null ? text : splitInboundBody(context).body;
79
+ }
80
+ function inboundEmailSubject(custom) {
81
+ const envelope = envelopeOf(custom);
82
+ if (envelope === null) return null;
83
+ const channel = envelopeString(envelope, "channel");
84
+ return channel === "email" || channel === "agent_email" ? envelopeString(envelope, "subject") : null;
85
+ }
86
+ exports.GROUP_TEXT_SOURCE = GROUP_TEXT_SOURCE;
87
+ exports.INBOUND_CHANNEL_KEY = INBOUND_CHANNEL_KEY;
88
+ exports.INBOUND_CONTEXT_OPEN = INBOUND_CONTEXT_OPEN;
89
+ exports.inboundContextValue = inboundContextValue;
90
+ exports.inboundEmailSubject = inboundEmailSubject;
91
+ exports.inboundMessageBody = inboundMessageBody;
92
+ exports.isInboundChannelMessage = isInboundChannelMessage;
93
+ exports.leadingInboundContext = leadingInboundContext;
94
+ exports.splitInboundBody = splitInboundBody;
95
+ exports.stripInboundContext = stripInboundContext;
96
+ //# sourceMappingURL=inbound-channel.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-channel.cjs","sources":["../../../../agent-runtime/src/inbound-channel.ts"],"sourcesContent":["/**\n * A user turn that reached the agent through a channel (Slack, email, a\n * text, Microsoft Teams, the Programmatic API, a voice memo): the reading\n * every client needs on every user bubble, kept small.\n *\n * Two things arrive with such a turn (agora\n * `services/channels/inbound_channel.py`):\n *\n * - The `inbound_channel` envelope in its message metadata, which the shipped\n * converter exposes as `metadata.custom.inbound_channel`: the channel, who\n * sent it (a name and an address or number), an email's subject and the\n * files the channel imported. Metadata never reaches the model.\n * - A routing block at the very start of its text, between\n * `<inbound-message-context>` tags: one `key: value` line per fact the agent\n * is told (the Slack channel and ids, the numbers a text went between, an\n * email's recipients, how a reply is delivered). Text is the model's only\n * way to know where a message came from, so the block stays in it.\n *\n * A person reads the words that were sent ({@link inboundMessageBody}), marked\n * with the channel and the sender; what the agent read is there on request\n * (the SDK's channel line, which loads its full reading only when a channel\n * message is shown). A message is a channel message only when it carries the\n * envelope: anyone can type the tags at the start of a chat message, and\n * their words then show as typed.\n */\n\n/** `metadata.custom` key the channel routers stamp on an inbound user turn. */\nexport const INBOUND_CHANNEL_KEY = 'inbound_channel';\n\n/** The routing block's tags (agora `INBOUND_CONTEXT_OPEN` / `INBOUND_CONTEXT_CLOSE`). */\nexport const INBOUND_CONTEXT_OPEN = '<inbound-message-context>';\nexport const INBOUND_CONTEXT_CLOSE = '</inbound-message-context>';\n\n/**\n * Anchored to the start: routers only ever prepend the block, so a sender who\n * types the tags inside their own words keeps that text (agora\n * `_LEADING_CONTEXT_RE`, Olympus `stripInboundContext`).\n */\nconst LEADING_CONTEXT = /^<inbound-message-context>([\\s\\S]*?)<\\/inbound-message-context>\\n*/;\n\n/**\n * The lines the Slack router puts around the thread a mention came in, when\n * the mention starts a conversation (agora `slack_router._build_message_text`).\n */\nconst SLACK_QUOTED_THREAD_OPEN = 'Earlier messages in this Slack thread (oldest first):';\nconst SLACK_QUOTED_THREAD_MESSAGE = 'The message that mentioned you:';\n\n/** The `source:` a group text's block names (agora `sms_group_router`). */\nexport const GROUP_TEXT_SOURCE = 'Group SMS text message';\n\n/** The routing block at the start of a message's text, as read without labels. */\nexport interface LeadingInboundContext {\n /** The block exactly as the agent read it, tags included. */\n readonly raw: string;\n /** The `key: value` lines between the tags. */\n readonly lines: string;\n /** The text after the block. */\n readonly rest: string;\n}\n\n/** The routing block at the start of `text`, or `null` when the text does not start with one. */\nexport function leadingInboundContext(text: string): LeadingInboundContext | null {\n if (!text.startsWith(INBOUND_CONTEXT_OPEN)) return null;\n const match = LEADING_CONTEXT.exec(text);\n if (match === null) return null;\n return {\n raw: match[0].trimEnd(),\n lines: match[1] ?? '',\n rest: text.slice(match[0].length).trimStart(),\n };\n}\n\n/** The block's value for `key` (its first `key: value` line), or `null`. */\nexport function inboundContextValue(lines: string, key: string): string | null {\n for (const line of lines.split('\\n')) {\n const separator = line.indexOf(':');\n if (separator > 0 && line.slice(0, separator).trim() === key) {\n return line.slice(separator + 1).trim() || null;\n }\n }\n return null;\n}\n\n/** The text with a leading routing block removed (agora `strip_inbound_context`). */\nexport function stripInboundContext(text: string): string {\n return leadingInboundContext(text)?.rest ?? text;\n}\n\n/**\n * The words after the block, without what a channel put ahead of them for\n * the agent: the Slack thread a mention came in (returned as `quoted`), a\n * group text's \"<name> said:\" line.\n */\nexport function splitInboundBody(context: LeadingInboundContext): {\n readonly body: string;\n readonly quoted: string | null;\n} {\n const { lines, rest } = context;\n const source = inboundContextValue(lines, 'source');\n if (source === 'Slack' && rest.startsWith(`${SLACK_QUOTED_THREAD_OPEN}\\n`)) {\n const marker = `\\n\\n${SLACK_QUOTED_THREAD_MESSAGE}`;\n const at = rest.indexOf(marker);\n if (at !== -1) {\n const quoted = rest.slice(SLACK_QUOTED_THREAD_OPEN.length, at).trim();\n return { body: rest.slice(at + marker.length).trimStart(), quoted: quoted || null };\n }\n }\n if (source === GROUP_TEXT_SOURCE) {\n // The block's values have their whitespace collapsed (agora\n // `build_inbound_context_block`); the \"<name> said:\" line keeps the name\n // as saved, so the two compare collapsed.\n const sender = inboundContextValue(lines, 'sender_name');\n const newline = rest.indexOf('\\n');\n const firstLine = newline === -1 ? rest : rest.slice(0, newline);\n if (sender && firstLine.endsWith(GROUP_TEXT_SAID) && collapseWhitespace(firstLine.slice(0, -GROUP_TEXT_SAID.length)) === sender) {\n return { body: newline === -1 ? '' : rest.slice(newline + 1), quoted: null };\n }\n }\n return { body: rest, quoted: null };\n}\n\n/** What follows the sender's name on a group text's first line (agora `_build_group_message_text`). */\nconst GROUP_TEXT_SAID = ' said:';\n\nfunction collapseWhitespace(value: string): string {\n return value.split(/\\s+/).filter((word) => word.length > 0).join(' ');\n}\n\nfunction envelopeOf(custom: Record<string, unknown> | null | undefined): Record<string, unknown> | null {\n const raw = custom?.[INBOUND_CHANNEL_KEY];\n return typeof raw === 'object' && raw !== null && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : null;\n}\n\nfunction envelopeString(envelope: Record<string, unknown>, key: string): string | null {\n const value = envelope[key];\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;\n}\n\n/**\n * Whether a thread message came in over a channel: it carries the\n * `inbound_channel` envelope with a channel. A primitive, so safe as a\n * `useAuiState` selector.\n */\nexport function isInboundChannelMessage(custom: Record<string, unknown> | null | undefined): boolean {\n const envelope = envelopeOf(custom);\n return envelope !== null && envelopeString(envelope, 'channel') !== null;\n}\n\n/**\n * What the person wrote: a channel message's `text` without its leading\n * routing block and what the channel quoted ahead of the words. Any other\n * text comes back unchanged: a message without the envelope (tags someone\n * typed stay as typed), and text without a block. Safe on every user turn and\n * on its own output.\n */\nexport function inboundMessageBody({\n custom,\n text,\n}: {\n custom: Record<string, unknown> | null | undefined;\n text: string;\n}): string {\n if (!isInboundChannelMessage(custom)) return text;\n const context = leadingInboundContext(text);\n return context === null ? text : splitInboundBody(context).body;\n}\n\n/** An email's subject, from the envelope; `null` for any other message. */\nexport function inboundEmailSubject(custom: Record<string, unknown> | null | undefined): string | null {\n const envelope = envelopeOf(custom);\n if (envelope === null) return null;\n const channel = envelopeString(envelope, 'channel');\n return channel === 'email' || channel === 'agent_email' ? envelopeString(envelope, 'subject') : null;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * A user turn that reached the agent through a channel (Slack, email, a
3
+ * text, Microsoft Teams, the Programmatic API, a voice memo): the reading
4
+ * every client needs on every user bubble, kept small.
5
+ *
6
+ * Two things arrive with such a turn (agora
7
+ * `services/channels/inbound_channel.py`):
8
+ *
9
+ * - The `inbound_channel` envelope in its message metadata, which the shipped
10
+ * converter exposes as `metadata.custom.inbound_channel`: the channel, who
11
+ * sent it (a name and an address or number), an email's subject and the
12
+ * files the channel imported. Metadata never reaches the model.
13
+ * - A routing block at the very start of its text, between
14
+ * `<inbound-message-context>` tags: one `key: value` line per fact the agent
15
+ * is told (the Slack channel and ids, the numbers a text went between, an
16
+ * email's recipients, how a reply is delivered). Text is the model's only
17
+ * way to know where a message came from, so the block stays in it.
18
+ *
19
+ * A person reads the words that were sent ({@link inboundMessageBody}), marked
20
+ * with the channel and the sender; what the agent read is there on request
21
+ * (the SDK's channel line, which loads its full reading only when a channel
22
+ * message is shown). A message is a channel message only when it carries the
23
+ * envelope: anyone can type the tags at the start of a chat message, and
24
+ * their words then show as typed.
25
+ */
26
+ /** `metadata.custom` key the channel routers stamp on an inbound user turn. */
27
+ export declare const INBOUND_CHANNEL_KEY = "inbound_channel";
28
+ /** The routing block's tags (agora `INBOUND_CONTEXT_OPEN` / `INBOUND_CONTEXT_CLOSE`). */
29
+ export declare const INBOUND_CONTEXT_OPEN = "<inbound-message-context>";
30
+ export declare const INBOUND_CONTEXT_CLOSE = "</inbound-message-context>";
31
+ /** The `source:` a group text's block names (agora `sms_group_router`). */
32
+ export declare const GROUP_TEXT_SOURCE = "Group SMS text message";
33
+ /** The routing block at the start of a message's text, as read without labels. */
34
+ export interface LeadingInboundContext {
35
+ /** The block exactly as the agent read it, tags included. */
36
+ readonly raw: string;
37
+ /** The `key: value` lines between the tags. */
38
+ readonly lines: string;
39
+ /** The text after the block. */
40
+ readonly rest: string;
41
+ }
42
+ /** The routing block at the start of `text`, or `null` when the text does not start with one. */
43
+ export declare function leadingInboundContext(text: string): LeadingInboundContext | null;
44
+ /** The block's value for `key` (its first `key: value` line), or `null`. */
45
+ export declare function inboundContextValue(lines: string, key: string): string | null;
46
+ /** The text with a leading routing block removed (agora `strip_inbound_context`). */
47
+ export declare function stripInboundContext(text: string): string;
48
+ /**
49
+ * The words after the block, without what a channel put ahead of them for
50
+ * the agent: the Slack thread a mention came in (returned as `quoted`), a
51
+ * group text's "<name> said:" line.
52
+ */
53
+ export declare function splitInboundBody(context: LeadingInboundContext): {
54
+ readonly body: string;
55
+ readonly quoted: string | null;
56
+ };
57
+ /**
58
+ * Whether a thread message came in over a channel: it carries the
59
+ * `inbound_channel` envelope with a channel. A primitive, so safe as a
60
+ * `useAuiState` selector.
61
+ */
62
+ export declare function isInboundChannelMessage(custom: Record<string, unknown> | null | undefined): boolean;
63
+ /**
64
+ * What the person wrote: a channel message's `text` without its leading
65
+ * routing block and what the channel quoted ahead of the words. Any other
66
+ * text comes back unchanged: a message without the envelope (tags someone
67
+ * typed stay as typed), and text without a block. Safe on every user turn and
68
+ * on its own output.
69
+ */
70
+ export declare function inboundMessageBody({ custom, text, }: {
71
+ custom: Record<string, unknown> | null | undefined;
72
+ text: string;
73
+ }): string;
74
+ /** An email's subject, from the envelope; `null` for any other message. */
75
+ export declare function inboundEmailSubject(custom: Record<string, unknown> | null | undefined): string | null;
@@ -0,0 +1,96 @@
1
+ const INBOUND_CHANNEL_KEY = "inbound_channel";
2
+ const INBOUND_CONTEXT_OPEN = "<inbound-message-context>";
3
+ const LEADING_CONTEXT = /^<inbound-message-context>([\s\S]*?)<\/inbound-message-context>\n*/;
4
+ const SLACK_QUOTED_THREAD_OPEN = "Earlier messages in this Slack thread (oldest first):";
5
+ const SLACK_QUOTED_THREAD_MESSAGE = "The message that mentioned you:";
6
+ const GROUP_TEXT_SOURCE = "Group SMS text message";
7
+ function leadingInboundContext(text) {
8
+ if (!text.startsWith(INBOUND_CONTEXT_OPEN)) return null;
9
+ const match = LEADING_CONTEXT.exec(text);
10
+ if (match === null) return null;
11
+ return {
12
+ raw: match[0].trimEnd(),
13
+ lines: match[1] ?? "",
14
+ rest: text.slice(match[0].length).trimStart()
15
+ };
16
+ }
17
+ function inboundContextValue(lines, key) {
18
+ for (const line of lines.split("\n")) {
19
+ const separator = line.indexOf(":");
20
+ if (separator > 0 && line.slice(0, separator).trim() === key) {
21
+ return line.slice(separator + 1).trim() || null;
22
+ }
23
+ }
24
+ return null;
25
+ }
26
+ function stripInboundContext(text) {
27
+ var _a;
28
+ return ((_a = leadingInboundContext(text)) == null ? void 0 : _a.rest) ?? text;
29
+ }
30
+ function splitInboundBody(context) {
31
+ const { lines, rest } = context;
32
+ const source = inboundContextValue(lines, "source");
33
+ if (source === "Slack" && rest.startsWith(`${SLACK_QUOTED_THREAD_OPEN}
34
+ `)) {
35
+ const marker = `
36
+
37
+ ${SLACK_QUOTED_THREAD_MESSAGE}`;
38
+ const at = rest.indexOf(marker);
39
+ if (at !== -1) {
40
+ const quoted = rest.slice(SLACK_QUOTED_THREAD_OPEN.length, at).trim();
41
+ return { body: rest.slice(at + marker.length).trimStart(), quoted: quoted || null };
42
+ }
43
+ }
44
+ if (source === GROUP_TEXT_SOURCE) {
45
+ const sender = inboundContextValue(lines, "sender_name");
46
+ const newline = rest.indexOf("\n");
47
+ const firstLine = newline === -1 ? rest : rest.slice(0, newline);
48
+ if (sender && firstLine.endsWith(GROUP_TEXT_SAID) && collapseWhitespace(firstLine.slice(0, -GROUP_TEXT_SAID.length)) === sender) {
49
+ return { body: newline === -1 ? "" : rest.slice(newline + 1), quoted: null };
50
+ }
51
+ }
52
+ return { body: rest, quoted: null };
53
+ }
54
+ const GROUP_TEXT_SAID = " said:";
55
+ function collapseWhitespace(value) {
56
+ return value.split(/\s+/).filter((word) => word.length > 0).join(" ");
57
+ }
58
+ function envelopeOf(custom) {
59
+ const raw = custom == null ? void 0 : custom[INBOUND_CHANNEL_KEY];
60
+ return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : null;
61
+ }
62
+ function envelopeString(envelope, key) {
63
+ const value = envelope[key];
64
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
65
+ }
66
+ function isInboundChannelMessage(custom) {
67
+ const envelope = envelopeOf(custom);
68
+ return envelope !== null && envelopeString(envelope, "channel") !== null;
69
+ }
70
+ function inboundMessageBody({
71
+ custom,
72
+ text
73
+ }) {
74
+ if (!isInboundChannelMessage(custom)) return text;
75
+ const context = leadingInboundContext(text);
76
+ return context === null ? text : splitInboundBody(context).body;
77
+ }
78
+ function inboundEmailSubject(custom) {
79
+ const envelope = envelopeOf(custom);
80
+ if (envelope === null) return null;
81
+ const channel = envelopeString(envelope, "channel");
82
+ return channel === "email" || channel === "agent_email" ? envelopeString(envelope, "subject") : null;
83
+ }
84
+ export {
85
+ GROUP_TEXT_SOURCE,
86
+ INBOUND_CHANNEL_KEY,
87
+ INBOUND_CONTEXT_OPEN,
88
+ inboundContextValue,
89
+ inboundEmailSubject,
90
+ inboundMessageBody,
91
+ isInboundChannelMessage,
92
+ leadingInboundContext,
93
+ splitInboundBody,
94
+ stripInboundContext
95
+ };
96
+ //# sourceMappingURL=inbound-channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-channel.js","sources":["../../../../agent-runtime/src/inbound-channel.ts"],"sourcesContent":["/**\n * A user turn that reached the agent through a channel (Slack, email, a\n * text, Microsoft Teams, the Programmatic API, a voice memo): the reading\n * every client needs on every user bubble, kept small.\n *\n * Two things arrive with such a turn (agora\n * `services/channels/inbound_channel.py`):\n *\n * - The `inbound_channel` envelope in its message metadata, which the shipped\n * converter exposes as `metadata.custom.inbound_channel`: the channel, who\n * sent it (a name and an address or number), an email's subject and the\n * files the channel imported. Metadata never reaches the model.\n * - A routing block at the very start of its text, between\n * `<inbound-message-context>` tags: one `key: value` line per fact the agent\n * is told (the Slack channel and ids, the numbers a text went between, an\n * email's recipients, how a reply is delivered). Text is the model's only\n * way to know where a message came from, so the block stays in it.\n *\n * A person reads the words that were sent ({@link inboundMessageBody}), marked\n * with the channel and the sender; what the agent read is there on request\n * (the SDK's channel line, which loads its full reading only when a channel\n * message is shown). A message is a channel message only when it carries the\n * envelope: anyone can type the tags at the start of a chat message, and\n * their words then show as typed.\n */\n\n/** `metadata.custom` key the channel routers stamp on an inbound user turn. */\nexport const INBOUND_CHANNEL_KEY = 'inbound_channel';\n\n/** The routing block's tags (agora `INBOUND_CONTEXT_OPEN` / `INBOUND_CONTEXT_CLOSE`). */\nexport const INBOUND_CONTEXT_OPEN = '<inbound-message-context>';\nexport const INBOUND_CONTEXT_CLOSE = '</inbound-message-context>';\n\n/**\n * Anchored to the start: routers only ever prepend the block, so a sender who\n * types the tags inside their own words keeps that text (agora\n * `_LEADING_CONTEXT_RE`, Olympus `stripInboundContext`).\n */\nconst LEADING_CONTEXT = /^<inbound-message-context>([\\s\\S]*?)<\\/inbound-message-context>\\n*/;\n\n/**\n * The lines the Slack router puts around the thread a mention came in, when\n * the mention starts a conversation (agora `slack_router._build_message_text`).\n */\nconst SLACK_QUOTED_THREAD_OPEN = 'Earlier messages in this Slack thread (oldest first):';\nconst SLACK_QUOTED_THREAD_MESSAGE = 'The message that mentioned you:';\n\n/** The `source:` a group text's block names (agora `sms_group_router`). */\nexport const GROUP_TEXT_SOURCE = 'Group SMS text message';\n\n/** The routing block at the start of a message's text, as read without labels. */\nexport interface LeadingInboundContext {\n /** The block exactly as the agent read it, tags included. */\n readonly raw: string;\n /** The `key: value` lines between the tags. */\n readonly lines: string;\n /** The text after the block. */\n readonly rest: string;\n}\n\n/** The routing block at the start of `text`, or `null` when the text does not start with one. */\nexport function leadingInboundContext(text: string): LeadingInboundContext | null {\n if (!text.startsWith(INBOUND_CONTEXT_OPEN)) return null;\n const match = LEADING_CONTEXT.exec(text);\n if (match === null) return null;\n return {\n raw: match[0].trimEnd(),\n lines: match[1] ?? '',\n rest: text.slice(match[0].length).trimStart(),\n };\n}\n\n/** The block's value for `key` (its first `key: value` line), or `null`. */\nexport function inboundContextValue(lines: string, key: string): string | null {\n for (const line of lines.split('\\n')) {\n const separator = line.indexOf(':');\n if (separator > 0 && line.slice(0, separator).trim() === key) {\n return line.slice(separator + 1).trim() || null;\n }\n }\n return null;\n}\n\n/** The text with a leading routing block removed (agora `strip_inbound_context`). */\nexport function stripInboundContext(text: string): string {\n return leadingInboundContext(text)?.rest ?? text;\n}\n\n/**\n * The words after the block, without what a channel put ahead of them for\n * the agent: the Slack thread a mention came in (returned as `quoted`), a\n * group text's \"<name> said:\" line.\n */\nexport function splitInboundBody(context: LeadingInboundContext): {\n readonly body: string;\n readonly quoted: string | null;\n} {\n const { lines, rest } = context;\n const source = inboundContextValue(lines, 'source');\n if (source === 'Slack' && rest.startsWith(`${SLACK_QUOTED_THREAD_OPEN}\\n`)) {\n const marker = `\\n\\n${SLACK_QUOTED_THREAD_MESSAGE}`;\n const at = rest.indexOf(marker);\n if (at !== -1) {\n const quoted = rest.slice(SLACK_QUOTED_THREAD_OPEN.length, at).trim();\n return { body: rest.slice(at + marker.length).trimStart(), quoted: quoted || null };\n }\n }\n if (source === GROUP_TEXT_SOURCE) {\n // The block's values have their whitespace collapsed (agora\n // `build_inbound_context_block`); the \"<name> said:\" line keeps the name\n // as saved, so the two compare collapsed.\n const sender = inboundContextValue(lines, 'sender_name');\n const newline = rest.indexOf('\\n');\n const firstLine = newline === -1 ? rest : rest.slice(0, newline);\n if (sender && firstLine.endsWith(GROUP_TEXT_SAID) && collapseWhitespace(firstLine.slice(0, -GROUP_TEXT_SAID.length)) === sender) {\n return { body: newline === -1 ? '' : rest.slice(newline + 1), quoted: null };\n }\n }\n return { body: rest, quoted: null };\n}\n\n/** What follows the sender's name on a group text's first line (agora `_build_group_message_text`). */\nconst GROUP_TEXT_SAID = ' said:';\n\nfunction collapseWhitespace(value: string): string {\n return value.split(/\\s+/).filter((word) => word.length > 0).join(' ');\n}\n\nfunction envelopeOf(custom: Record<string, unknown> | null | undefined): Record<string, unknown> | null {\n const raw = custom?.[INBOUND_CHANNEL_KEY];\n return typeof raw === 'object' && raw !== null && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : null;\n}\n\nfunction envelopeString(envelope: Record<string, unknown>, key: string): string | null {\n const value = envelope[key];\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;\n}\n\n/**\n * Whether a thread message came in over a channel: it carries the\n * `inbound_channel` envelope with a channel. A primitive, so safe as a\n * `useAuiState` selector.\n */\nexport function isInboundChannelMessage(custom: Record<string, unknown> | null | undefined): boolean {\n const envelope = envelopeOf(custom);\n return envelope !== null && envelopeString(envelope, 'channel') !== null;\n}\n\n/**\n * What the person wrote: a channel message's `text` without its leading\n * routing block and what the channel quoted ahead of the words. Any other\n * text comes back unchanged: a message without the envelope (tags someone\n * typed stay as typed), and text without a block. Safe on every user turn and\n * on its own output.\n */\nexport function inboundMessageBody({\n custom,\n text,\n}: {\n custom: Record<string, unknown> | null | undefined;\n text: string;\n}): string {\n if (!isInboundChannelMessage(custom)) return text;\n const context = leadingInboundContext(text);\n return context === null ? text : splitInboundBody(context).body;\n}\n\n/** An email's subject, from the envelope; `null` for any other message. */\nexport function inboundEmailSubject(custom: Record<string, unknown> | null | undefined): string | null {\n const envelope = envelopeOf(custom);\n if (envelope === null) return null;\n const channel = envelopeString(envelope, 'channel');\n return channel === 'email' || channel === 'agent_email' ? envelopeString(envelope, 'subject') : null;\n}\n"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@ export { type ConversationListController, useConversationList, type UseConversat
11
11
  export { CLIENT_TOOL_RESULT_STATUS_ERROR, CLIENT_TOOL_RESULT_STATUS_SUCCESS, CLIENT_TOOL_RESULT_STATUSES, type ClientToolResultEnvelopeContract, type ClientToolResultStatus, DEEP_AGENT_CONTEXT_PROTOCOL, DEEP_AGENT_CUT_LANE_DOCUMENT, DEEP_AGENT_DEFAULT_MESSAGE_WINDOW, DEEP_AGENT_FOLLOW_TOOL_CALL_ID_KEY, DEEP_AGENT_INTEREST_PROTOCOL, DEEP_AGENT_LANE_CUT_PROTOCOL_VERSION, DEEP_AGENT_LANE_FOLLOW_PROTOCOL_VERSION, DEEP_AGENT_MAIN_NS, DEEP_AGENT_MAX_FOLLOWED_STEPS, DEEP_AGENT_MAX_LANE_PAGE, DEEP_AGENT_HISTORY_IMAGE_COMMAND, DEEP_AGENT_HISTORY_LANE_COMMAND, DEEP_AGENT_HISTORY_LANE_IMAGE_COMMAND, DEEP_AGENT_HISTORY_LANE_PAGE_CHARS, DEEP_AGENT_HISTORY_MESSAGE_COMMAND, DEEP_AGENT_HISTORY_PAGE_COMMAND, DEEP_AGENT_HISTORY_PAGE_PROTOCOL_VERSION, DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND, DEEP_AGENT_INLINE_IMAGE_WIRE_BUDGET, DEEP_AGENT_MAX_HELD_HEAD, DEEP_AGENT_MAX_HISTORY_PAGE_STEPS, DEEP_AGENT_MAX_HISTORY_PAGE_TURNS, DEEP_AGENT_MESSAGE_HISTORY_HEADER, DEEP_AGENT_MESSAGE_HISTORY_PAGED, DEEP_AGENT_MAX_INTERESTS, DEEP_AGENT_MAX_MESSAGE_WINDOW, DEEP_AGENT_MESSAGE_WINDOW_HEADER, DEEP_AGENT_MIN_MESSAGE_WINDOW, DEEP_AGENT_OMITTED_IMAGE_KEY, DEEP_AGENT_OMITTED_IMAGE_PLACEHOLDER_URL, DEEP_AGENT_PROTOCOL_STATE_KEY, DEEP_AGENT_PROTOCOL_VERSION, DEEP_AGENT_PROTOCOL_VERSION_HEADER, DEEP_AGENT_REPEATS_RESULT_KEY, DEEP_AGENT_RUNNING_LANE_WIRE_MESSAGES, DEEP_AGENT_RUNTIME_MARKER, DEEP_AGENT_TRUNCATED_CONTENT_KEY, DEEP_AGENT_TRUNCATED_LANE_KEY, KNOWN_START_CHANNEL_SET, KNOWN_START_CHANNELS, type KnownStartChannel, RUN_CONFIG_COMPANION_CUSTOM_KEYS, RUN_CONFIG_CUSTOM_KEY_SET, RUN_CONFIG_CUSTOM_KEYS, RUN_CONFIG_PARSED_CUSTOM_KEYS, RUN_CONFIG_PER_SEND_CUSTOM_KEY_SET, RUN_CONFIG_PER_SEND_CUSTOM_KEYS, type RunConfigCustomKey, type RunConfigPerSendCustomKey, DEEP_AGENT_CONFIG_COMMANDS, DEEP_AGENT_CONFIG_STATE_KEY, THREAD_CONFIG_CUSTOM_KEY_SET, THREAD_CONFIG_CUSTOM_KEYS, type ThreadConfigCustomKey, } from "./generated/runconfig-contract";
12
12
  export { COLLAB_AGENT_CHANNEL_SESSION_MISMATCH, CONVERSATION_ARCHIVED, CONVERSATION_ARCHIVED_MESSAGE, isConversationArchivedRejection, isLegacyThreadReadOnlyRejection, isStatewireChannelTeardown, isStatewireCommandAuthContextExpired, isStatewireExpiredVerdict, isStatewireRewindAnchorRejection, projectDeepAgentConnection, readRetryableCommandRejection, readRetryableRunRefusal, readRunStartRefusal, type RetryableCommandRejection, type RetryableRunRefusal, type RunStartRefusal, STATEWIRE_COMMAND_AUTH_CONTEXT_EXPIRED, statewireRejectionCode, isVoiceCommandRejection, statewireRejectionDetailMessage, statewireRejectionPayload, type StatewireRejectionPayload, statewireTransportOutcome, } from "./errors";
13
13
  export { type ParsedExecuteOutput, parseExecuteOutput, type ShellCommandSummary, summarizeShellCommand, } from "./execute-output";
14
- export { type AbandonedInputRequest, type ApprovalResumeLock, asHitlApproval, canApproveForSession, DEFAULT_INTERRUPT_PAUSED_MESSAGE, fanLaneDecision, findAbandonedInterrupt, HITL_PENDING_RESUME_GRACE_MS, HITL_REJECT_REASON, HITL_RESUME_TRANSPORT_RETRIES, type HitlActionDecision, type HitlApprovalPayload, actionRequestToolCallIds, hitlResumeActionDecisions, hitlResumeApprove, hitlResumeApproveForSession, INTERRUPT_DECLINE_REASON, interruptMirrorPayload, interruptResumeDecline, hitlResumeReject, isApprovalCardInterrupt, isPendingInterrupt, isResumeTransportLoss, isSystemPauseInterrupt, laneInterruptIds, type PendingActionRequest, type PendingApprovalContext, type PendingApprovalFact, type PendingApprovalTitle, type PendingApprovalWarning, parsePendingApprovalContext, pendingActionRequests, pendingApprovalTitle, readAbandonedInputRequests, readInterruptMessage, readInterruptMirror, readQueuedLaneDecisions, type ResumeExtras, resumeValueApproves, toolCallIdsAwaitingApproval, useApprovalResumeLock, type UseApprovalResumeLockOptions, } from "./interrupts";
14
+ export { type AbandonedInputRequest, type ApprovalResumeLock, asHitlApproval, canApproveForSession, DEFAULT_INTERRUPT_PAUSED_MESSAGE, fanLaneDecision, findAbandonedInterrupt, HITL_PENDING_RESUME_GRACE_MS, HITL_REJECT_REASON, HITL_RESUME_TRANSPORT_RETRIES, type HitlActionDecision, type HitlApprovalPayload, actionRequestToolCallIds, hitlResumeActionDecisions, hitlResumeApprove, hitlResumeApproveForSession, INTERRUPT_DECLINE_REASON, interruptMirrorPayload, interruptResumeDecline, hitlResumeReject, isApprovalCardInterrupt, isPendingInterrupt, isResumeTransportLoss, isSystemPauseInterrupt, laneInterruptIds, type PendingActionRequest, type PendingApprovalContext, type PendingApprovalFact, type PendingApprovalTitle, type PendingApprovalWarning, parsePendingApprovalContext, pendingActionRequests, pendingApprovalTitle, readAbandonedInputRequests, readInterruptMessage, readInterruptMirror, readQueuedLaneDecisions, type ResumeExtras, resumeValueApproves, type SessionApprovalOffer, sessionApprovalLabel, sessionApprovalOffer, toolCallIdsAwaitingApproval, useApprovalResumeLock, type UseApprovalResumeLockOptions, visibleText, } from "./interrupts";
15
15
  export { type AgoraFetch, bareDecisionThreadId, type BrowserDecisionCheckOutcome, DECISION_CHECK_HEADER, type DecisionCheckClient, type DecisionCheckClientKind, type DecisionCheckDescriptor, DecisionCheckKind, MAX_DECISIONS_PER_CHECK, type PendingBrowserDecisionCheck, respondDecisionDescriptor, type RunBrowserDecisionCheckOptions, runBrowserDecisionCheck, type StatewireAnswerForCheck, statewireAnswerDescriptors, } from "./decision-checks";
16
16
  export { buildQueueEditCommand, buildStatewireQueueRows, describeQueueSendFailure, isFailedSendEntryRace, isQueueEntryUnsettled, QUEUE_AUTO_CONTINUE_DELAY_MS, type QueueEditCommand, queueEntryText, queueLaneFlags, type QueueLaneFlags, readPrioritizedQueueIds, showQueueRowSendNow, showQueueRowSteer, type StatewireQueueRow, userStopBlocksQueueAutoContinue, } from "./queue";
17
17
  export { createDeepAgentSendRecovery, type DeepAgentSendRecovery, type DeepAgentSendRecoveryOptions, shouldAutoRetryFailedSend, } from "./send-recovery";
@@ -74,6 +74,7 @@ export { CALL_RECAP_APPROVAL_LABEL, CALL_RECAP_TITLE, type CallRecap, type CallR
74
74
  export { canPersistDeepAgentSession, type DeepAgentPendingSession, type DeepAgentRunConfigRegistration, type DeepAgentSessionPersistence, type DeepAgentThreadElement, type DeepAgentThreadStateClientHost, logUnhandledStatewireError, useDeepAgentRuntime, type UseDeepAgentRuntimeOptions, } from "./use-deep-agent-runtime";
75
75
  export { type DeepAgentAttachSource, type DeepAgentHeadersResolver, readSessionPersisted, type RunningSnapshot, shouldNotifyRunningChange, useDeepAgentThread, type UseDeepAgentThreadOptions, } from "./use-deep-agent-thread";
76
76
  export { GITHUB_EVENT_SOURCE_KEY, type GithubWakeEvent, githubWakeEventLabel, githubWakeEventUrl, githubWakeMarkerText, githubWakeReference, isGithubWakeMessage, readGithubWakeEvent, } from "./github-wake-event";
77
+ export { GROUP_TEXT_SOURCE, INBOUND_CHANNEL_KEY, INBOUND_CONTEXT_CLOSE, INBOUND_CONTEXT_OPEN, inboundContextValue, inboundEmailSubject, inboundMessageBody, isInboundChannelMessage, type LeadingInboundContext, leadingInboundContext, splitInboundBody, stripInboundContext, } from "./inbound-channel";
77
78
  export { githubSubscriptionHeadline, isSubscriptionWakeMessage, readSubscriptionWakeEvent, SUBSCRIPTION_EVENT_KEY, type SubscriptionWakeChange, type SubscriptionWakeEvent, type SubscriptionWakeGithub, subscriptionPlaceWords, subscriptionWakeDetail, subscriptionWakeGithub, subscriptionWakeHeadline, } from "./subscription-wake-event";
78
79
  export { describeTerminationReason, readRuntimeMessageFromSourceMessages, readSoleRuntimeMessageKind, readTerminationReason, runtimeMessageMarkerText, TERMINATION_REASONS, type AthenaRuntimeMessage, type AthenaRuntimeMessageKind, type AthenaRuntimeMessageSeverity, type AthenaTerminationReason, type TerminationDescription, } from "./runtime-messages";
79
80
  export { buildHandoffQrSvg, buildMobileHandoffSchemeUrl, classifyHandoffTransportError, type CreateThreadHandoffInput, type CreateThreadHandoffResponse, createThreadHandoffResponseSchema, describeHandoffTransportError, type DestinationReadiness, HANDOFF_DESTINATION_LABELS, HANDOFF_DESTINATIONS, HANDOFF_EXPIRY_GRACE_MS, HANDOFF_TICKET_LIFETIME_MS, HANDOFF_URL_PARAM, type HandoffDestination, type HandoffDestinationPresentation, type HandoffEnvironment, HandoffGoneError, type HandoffLaunch, type HandoffOutcome, HandoffPermanentError, type HandoffReadinessResponse, type HandoffReadinessState, type HandoffSource, type HandoffState, type HandoffTransportErrorKind, handoffHttpError, handoffReadinessResponseSchema, handoffStateSchema, handoffTicketLifetimeMs, isHandoffGoneError, isHandoffPermanentError, isRetryableHandoffHttpStatus, isOfficeHandoffDestination, isServerHandoffDestination, isTerminalHandoffState, type MobileHandoffLaunch, type MobileLaunchMode, mobileHandoffLaunchSchema, OFFICE_HANDOFF_DESTINATIONS, type OfficeHandoffDestination, type OfficeHandoffLaunch, officeHandoffLaunchSchema, type OfficeInvitationLaunch, officeInvitationLaunchSchema, type OfficeLaunchMode, officeLaunchModeSchema, threadHandoffCreateBody, parseHandoffIdParam, presentHandoffDestination, readinessByDestination, SERVER_HANDOFF_DESTINATIONS, type ServerHandoffDestination, selectMobileLaunchMode, supportsDesktopHandoffSource, type ThreadHandoffTransport, type ThreadHandoffView, threadHandoffViewSchema, waitForHandoffOutcome, type WaitForHandoffOutcomeOptions, type ClaimThreadHandoffResponse, claimThreadHandoffResponseSchema, clampHandoffDraft, handoffDraftToCarry, continuedFromLabel, pickUpFromLabel, pickUpOfferLabel, type DesktopThreadHandoffLaunch, desktopThreadHandoffLaunchSchema, driverSurfaceForChannel, drivingOnLabel, type HandoffLanding, handoffLandingFromView, isPickupSurface, MAX_HANDOFF_DRAFT_LENGTH, nextHandoffLaunchSchema, parseThreadHandoffCreateResponse, pendingThreadHandoffResponseSchema, pickedUpOnLabel, PICKUP_SURFACES, type PickupSurface, shouldOfferPendingHandoff, IMPLICIT_PICKUP_ACTIVATION_WINDOW_MS, IMPLICIT_PICKUP_DECLINE_HOLD_MS, type ImplicitPickupAnswer, type ImplicitPickupContext, implicitPickupAllowed, isImplicitHandoff, nextImplicitPickupAnswer, SPACES_HANDOFF_URL_PARAM, type SpacesHandoffLaunch, spacesHandoffLaunchSchema, SURFACE_HANDOFF_DESTINATIONS, type SurfaceHandoffDestination, supportsSurfaceHandoffDestinations, THREAD_HANDOFF_DESTINATION_LABELS, THREAD_HANDOFF_DESTINATIONS, threadHandoffClaimBody, threadHandoffCreateRequest, type ThreadHandoffDestination, type ThreadHandoffReceiverTransport, MAX_PENDING_EXCLUDE, handoffDeclineKey, pendingExcludeIds, pickupExcludeIds, pendingHandoffPath, type HandoffMode, handoffModeSchema, isVoiceHandoffDestination, MAX_HANDOFF_CURSOR_LENGTH, parseHandoffCursor, supportsHandoffCursor, livenessAgo, presentSurfaceLiveness, type SurfaceLiveness, type SurfaceLivenessBasis, surfaceLivenessBasisSchema, surfaceLivenessBySurface, type SurfaceLivenessPresentation, surfaceLivenessSchema, type SurfaceLivenessState, surfaceLivenessStateSchema, type SurfaceLivenessTone, supportsSurfaceLiveness, } from "./thread-handoff";
@@ -87,12 +87,16 @@ function pendingActionRequests(hitl) {
87
87
  const requests = Array.isArray(context.action_requests) ? context.action_requests.filter(isRecord) : context.tool_name && context.tool_args ? [{ name: context.tool_name, args: context.tool_args, tool_call_id: context.tool_call_id }] : [];
88
88
  return requests.map((request) => {
89
89
  const approvalContext = parsePendingApprovalContext(request.approval_context);
90
+ const sessionApprovalRecipients = parseSessionApprovalRecipients(
91
+ request.session_approval_recipients
92
+ );
90
93
  return {
91
94
  name: typeof request.name === "string" ? request.name : "tool",
92
95
  args: isRecord(request.args) ? request.args : {},
93
96
  ...typeof request.tool_call_id === "string" && { toolCallId: request.tool_call_id },
94
97
  ...typeof request.interrupt_id === "string" && { interruptId: request.interrupt_id },
95
98
  ...request.session_approval_allowed === true && { sessionApprovalAllowed: true },
99
+ ...sessionApprovalRecipients && { sessionApprovalRecipients },
96
100
  ...Array.isArray(request.allowed_decisions) && request.allowed_decisions.includes("edit") && { editAllowed: true },
97
101
  ...approvalContext && { approvalContext }
98
102
  };
@@ -111,8 +115,44 @@ function pendingApprovalTitle({
111
115
  if ((envelope == null ? void 0 : envelope.headline) === void 0) return void 0;
112
116
  return envelope.summary === void 0 ? { headline: envelope.headline } : { headline: envelope.headline, summary: envelope.summary };
113
117
  }
114
- function canApproveForSession(actionRequests) {
115
- return actionRequests.length > 0 && actionRequests.every((request) => request.sessionApprovalAllowed === true);
118
+ function visibleText(text) {
119
+ return text.replace(
120
+ /[\p{C}\p{Z}]/gu,
121
+ (character) => character === " " ? character : `<U+${(character.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, "0")}>`
122
+ );
123
+ }
124
+ function parseSessionApprovalRecipients(value) {
125
+ if (!Array.isArray(value) || value.length === 0) return void 0;
126
+ const names = [];
127
+ for (const name of value) {
128
+ if (typeof name !== "string" || name.trim() === "") return void 0;
129
+ names.push(visibleText(name));
130
+ }
131
+ return names;
132
+ }
133
+ function sessionApprovalOffer(actionRequests) {
134
+ if (actionRequests.length === 0) return void 0;
135
+ if (actionRequests.every(
136
+ (request) => request.sessionApprovalAllowed === true && request.sessionApprovalRecipients === void 0
137
+ )) {
138
+ return { kind: "tools" };
139
+ }
140
+ const recipients = [];
141
+ for (const request of actionRequests) {
142
+ if (request.sessionApprovalRecipients === void 0) return void 0;
143
+ for (const name of request.sessionApprovalRecipients) {
144
+ if (!recipients.includes(name)) recipients.push(name);
145
+ }
146
+ }
147
+ return { kind: "recipients", recipients };
148
+ }
149
+ function namesInProse(names) {
150
+ if (names.length <= 1) return names.join("");
151
+ return `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
152
+ }
153
+ function sessionApprovalLabel(offer) {
154
+ if (offer.kind === "tools") return "Approve all for this session";
155
+ return `Approve for this session: only ${namesInProse(offer.recipients)}`;
116
156
  }
117
157
  function readAbandonedInputRequests(state) {
118
158
  const abandoned = statewireState.readDeepAgentStreamValues(state).abandonedInputRequests;
@@ -332,7 +372,6 @@ exports.HITL_RESUME_TRANSPORT_RETRIES = HITL_RESUME_TRANSPORT_RETRIES;
332
372
  exports.INTERRUPT_DECLINE_REASON = INTERRUPT_DECLINE_REASON;
333
373
  exports.actionRequestToolCallIds = actionRequestToolCallIds;
334
374
  exports.asHitlApproval = asHitlApproval;
335
- exports.canApproveForSession = canApproveForSession;
336
375
  exports.fanLaneDecision = fanLaneDecision;
337
376
  exports.findAbandonedInterrupt = findAbandonedInterrupt;
338
377
  exports.hitlResumeActionDecisions = hitlResumeActionDecisions;
@@ -354,6 +393,9 @@ exports.readInterruptMessage = readInterruptMessage;
354
393
  exports.readInterruptMirror = readInterruptMirror;
355
394
  exports.readQueuedLaneDecisions = readQueuedLaneDecisions;
356
395
  exports.resumeValueApproves = resumeValueApproves;
396
+ exports.sessionApprovalLabel = sessionApprovalLabel;
397
+ exports.sessionApprovalOffer = sessionApprovalOffer;
357
398
  exports.toolCallIdsAwaitingApproval = toolCallIdsAwaitingApproval;
358
399
  exports.useApprovalResumeLock = useApprovalResumeLock;
400
+ exports.visibleText = visibleText;
359
401
  //# sourceMappingURL=interrupts.cjs.map