@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.
- package/README.md +71 -1
- package/dist/agent-runtime/src/inbound-channel.cjs +96 -0
- package/dist/agent-runtime/src/inbound-channel.cjs.map +1 -0
- package/dist/agent-runtime/src/inbound-channel.d.ts +75 -0
- package/dist/agent-runtime/src/inbound-channel.js +96 -0
- package/dist/agent-runtime/src/inbound-channel.js.map +1 -0
- package/dist/agent-runtime/src/index.d.ts +2 -1
- package/dist/agent-runtime/src/interrupts.cjs +45 -3
- package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/agent-runtime/src/interrupts.d.ts +51 -4
- package/dist/agent-runtime/src/interrupts.js +46 -4
- package/dist/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/agent-runtime/src/offline-status.cjs.map +1 -1
- package/dist/agent-runtime/src/offline-status.d.ts +25 -0
- package/dist/agent-runtime/src/offline-status.js.map +1 -1
- package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +8 -8
- package/dist/agent-runtime/src/tool-metadata.cjs +8 -0
- package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
- package/dist/agent-runtime/src/tool-metadata.js +8 -0
- package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
- package/dist/auth/AthenaSSOProvider.cjs +54 -12
- package/dist/auth/AthenaSSOProvider.cjs.map +1 -1
- package/dist/auth/AthenaSSOProvider.d.ts +23 -2
- package/dist/auth/AthenaSSOProvider.js +54 -12
- package/dist/auth/AthenaSSOProvider.js.map +1 -1
- package/dist/auth/index.cjs +2 -0
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/sign-back-in.cjs +141 -0
- package/dist/auth/sign-back-in.cjs.map +1 -0
- package/dist/auth/sign-back-in.d.ts +117 -0
- package/dist/auth/sign-back-in.js +141 -0
- package/dist/auth/sign-back-in.js.map +1 -0
- package/dist/chat/AthenaChat.cjs +7 -2
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.js +7 -2
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/StatewireApprovalCard.cjs +6 -2
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.d.ts +8 -1
- package/dist/chat/StatewireApprovalCard.js +7 -3
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat-ui/index.cjs +4 -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 +5 -1
- package/dist/chat-ui/index.js.map +1 -1
- package/dist/chat-ui/messages/inbound-channel-message.cjs +219 -0
- package/dist/chat-ui/messages/inbound-channel-message.cjs.map +1 -0
- package/dist/chat-ui/messages/inbound-channel-message.d.ts +60 -0
- package/dist/chat-ui/messages/inbound-channel-message.js +219 -0
- package/dist/chat-ui/messages/inbound-channel-message.js.map +1 -0
- package/dist/chat-ui/messages/inbound-channel-reader.cjs +184 -0
- package/dist/chat-ui/messages/inbound-channel-reader.cjs.map +1 -0
- package/dist/chat-ui/messages/inbound-channel-reader.d.ts +65 -0
- package/dist/chat-ui/messages/inbound-channel-reader.js +184 -0
- package/dist/chat-ui/messages/inbound-channel-reader.js.map +1 -0
- package/dist/chat-ui/messages/message-components.cjs +18 -14
- package/dist/chat-ui/messages/message-components.cjs.map +1 -1
- package/dist/chat-ui/messages/message-components.d.ts +7 -0
- package/dist/chat-ui/messages/message-components.js +19 -15
- package/dist/chat-ui/messages/message-components.js.map +1 -1
- package/dist/composer/sign-back-in-draft.cjs +62 -0
- package/dist/composer/sign-back-in-draft.cjs.map +1 -0
- package/dist/composer/sign-back-in-draft.d.ts +34 -0
- package/dist/composer/sign-back-in-draft.js +62 -0
- package/dist/composer/sign-back-in-draft.js.map +1 -0
- package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
- package/dist/diagnostics/athena-diagnostics.d.ts +6 -1
- package/dist/diagnostics/athena-diagnostics.js.map +1 -1
- package/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +48 -3
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.js +49 -4
- 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/runtime/auth.cjs +11 -0
- package/dist/runtime/auth.cjs.map +1 -1
- package/dist/runtime/auth.d.ts +18 -1
- package/dist/runtime/auth.js +11 -0
- package/dist/runtime/auth.js.map +1 -1
- package/dist/styles.css +1 -1
- 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":";;AA2BO,MAAM,sBAAsB;AAG5B,MAAM,uBAAuB;AAQpC,MAAM,kBAAkB;AAMxB,MAAM,2BAA2B;AACjC,MAAM,8BAA8B;AAG7B,MAAM,oBAAoB;AAa1B,SAAS,sBAAsB,MAA4C;AAChF,MAAI,CAAC,KAAK,WAAW,oBAAoB,EAAG,QAAO;AACnD,QAAM,QAAQ,gBAAgB,KAAK,IAAI;AACvC,MAAI,UAAU,KAAM,QAAO;AAC3B,SAAO;AAAA,IACL,KAAK,MAAM,CAAC,EAAE,QAAA;AAAA,IACd,OAAO,MAAM,CAAC,KAAK;AAAA,IACnB,MAAM,KAAK,MAAM,MAAM,CAAC,EAAE,MAAM,EAAE,UAAA;AAAA,EAAU;AAEhD;AAGO,SAAS,oBAAoB,OAAe,KAA4B;AAC7E,aAAW,QAAQ,MAAM,MAAM,IAAI,GAAG;AACpC,UAAM,YAAY,KAAK,QAAQ,GAAG;AAClC,QAAI,YAAY,KAAK,KAAK,MAAM,GAAG,SAAS,EAAE,KAAA,MAAW,KAAK;AAC5D,aAAO,KAAK,MAAM,YAAY,CAAC,EAAE,UAAU;AAAA,IAC7C;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,oBAAoB,MAAsB;;AACxD,WAAO,2BAAsB,IAAI,MAA1B,mBAA6B,SAAQ;AAC9C;AAOO,SAAS,iBAAiB,SAG/B;AACA,QAAM,EAAE,OAAO,KAAA,IAAS;AACxB,QAAM,SAAS,oBAAoB,OAAO,QAAQ;AAClD,MAAI,WAAW,WAAW,KAAK,WAAW,GAAG,wBAAwB;AAAA,CAAI,GAAG;AAC1E,UAAM,SAAS;AAAA;AAAA,EAAO,2BAA2B;AACjD,UAAM,KAAK,KAAK,QAAQ,MAAM;AAC9B,QAAI,OAAO,IAAI;AACb,YAAM,SAAS,KAAK,MAAM,yBAAyB,QAAQ,EAAE,EAAE,KAAA;AAC/D,aAAO,EAAE,MAAM,KAAK,MAAM,KAAK,OAAO,MAAM,EAAE,UAAA,GAAa,QAAQ,UAAU,KAAA;AAAA,IAC/E;AAAA,EACF;AACA,MAAI,WAAW,mBAAmB;AAIhC,UAAM,SAAS,oBAAoB,OAAO,aAAa;AACvD,UAAM,UAAU,KAAK,QAAQ,IAAI;AACjC,UAAM,YAAY,YAAY,KAAK,OAAO,KAAK,MAAM,GAAG,OAAO;AAC/D,QAAI,UAAU,UAAU,SAAS,eAAe,KAAK,mBAAmB,UAAU,MAAM,GAAG,CAAC,gBAAgB,MAAM,CAAC,MAAM,QAAQ;AAC/H,aAAO,EAAE,MAAM,YAAY,KAAK,KAAK,KAAK,MAAM,UAAU,CAAC,GAAG,QAAQ,KAAA;AAAA,IACxE;AAAA,EACF;AACA,SAAO,EAAE,MAAM,MAAM,QAAQ,KAAA;AAC/B;AAGA,MAAM,kBAAkB;AAExB,SAAS,mBAAmB,OAAuB;AACjD,SAAO,MAAM,MAAM,KAAK,EAAE,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,EAAE,KAAK,GAAG;AACtE;AAEA,SAAS,WAAW,QAAoF;AACtG,QAAM,MAAM,iCAAS;AACrB,SAAO,OAAO,QAAQ,YAAY,QAAQ,QAAQ,CAAC,MAAM,QAAQ,GAAG,IAC/D,MACD;AACN;AAEA,SAAS,eAAe,UAAmC,KAA4B;AACrF,QAAM,QAAQ,SAAS,GAAG;AAC1B,SAAO,OAAO,UAAU,YAAY,MAAM,KAAA,EAAO,SAAS,IAAI,MAAM,KAAA,IAAS;AAC/E;AAOO,SAAS,wBAAwB,QAA6D;AACnG,QAAM,WAAW,WAAW,MAAM;AAClC,SAAO,aAAa,QAAQ,eAAe,UAAU,SAAS,MAAM;AACtE;AASO,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AACF,GAGW;AACT,MAAI,CAAC,wBAAwB,MAAM,EAAG,QAAO;AAC7C,QAAM,UAAU,sBAAsB,IAAI;AAC1C,SAAO,YAAY,OAAO,OAAO,iBAAiB,OAAO,EAAE;AAC7D;AAGO,SAAS,oBAAoB,QAAmE;AACrG,QAAM,WAAW,WAAW,MAAM;AAClC,MAAI,aAAa,KAAM,QAAO;AAC9B,QAAM,UAAU,eAAe,UAAU,SAAS;AAClD,SAAO,YAAY,WAAW,YAAY,gBAAgB,eAAe,UAAU,SAAS,IAAI;AAClG;;;;;;;;;;;"}
|
|
@@ -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
|
|
115
|
-
return
|
|
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
|