@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-channel-message.js","sources":["../../../src/chat-ui/messages/inbound-channel-message.tsx"],"sourcesContent":["/**\n * A user turn that came in over a channel (Slack, email, a text, Microsoft\n * Teams, the Programmatic API, a voice memo), drawn as the words that were\n * sent under a line naming the channel and the sender.\n *\n * The channel's routing block (`<inbound-message-context>`: the facts the\n * agent is told, such as Slack ids, the numbers a text went between and how a\n * reply is delivered) leaves the bubble. The line above the bubble opens it\n * as a labelled list, and hovering the line shows the channel, the place and\n * the sender in full. Every host that renders the SDK's user message gets\n * this; a host with its own user message composes it from\n * `InboundChannelLine`, `InboundChannelSubject` and `withInboundMessageBody`\n * (or `UserMessageText`). Only a message with the `inbound_channel` envelope\n * is a channel message: tags someone types show as typed.\n *\n * The hover card is CSS, not a Radix tooltip, and the glyphs are icons the\n * chat already draws: a host that splits `node_modules` into startup vendor\n * chunks (the Office pane) would otherwise load a tooltip stack at startup for\n * a line it rarely shows.\n */\nimport { type TextMessagePartComponent, useAuiState } from \"@assistant-ui/react\";\nimport {\n inboundEmailSubject,\n inboundMessageBody,\n isInboundChannelMessage,\n} from \"@athenaintel/agent-runtime\";\nimport { AudioLinesIcon, ChevronDownIcon, CodeIcon, MailIcon, MessageSquareIcon } from \"lucide-react\";\nimport { type FC, useMemo, useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from \"../../ui/collapsible\";\nimport {\n type InboundChannelKind,\n type InboundChannelMessage,\n readInboundChannelMessage,\n} from \"./inbound-channel-reader\";\n\n/** Slack's four-colour mark. */\nconst SlackMark: FC<{ className?: string }> = ({ className }) => (\n <svg viewBox=\"0 0 2447.6 2452.5\" className={className} aria-hidden=\"true\" focusable=\"false\">\n <g clipRule=\"evenodd\" fillRule=\"evenodd\">\n <path\n d=\"m897.4 0c-135.3.1-244.8 109.9-244.7 245.2-.1 135.3 109.5 245.1 244.8 245.2h244.8v-245.1c.1-135.3-109.5-245.1-244.9-245.3.1 0 .1 0 0 0m0 654h-652.6c-135.3.1-244.9 109.9-244.8 245.2-.2 135.3 109.4 245.1 244.7 245.3h652.7c135.3-.1 244.9-109.9 244.8-245.2.1-135.4-109.5-245.2-244.8-245.3z\"\n fill=\"#36c5f0\"\n />\n <path\n d=\"m2447.6 899.2c.1-135.3-109.5-245.1-244.8-245.2-135.3.1-244.9 109.9-244.8 245.2v245.3h244.8c135.3-.1 244.9-109.9 244.8-245.3zm-652.7 0v-654c.1-135.2-109.4-245-244.7-245.2-135.3.1-244.9 109.9-244.8 245.2v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.3z\"\n fill=\"#2eb67d\"\n />\n <path\n d=\"m1550.1 2452.5c135.3-.1 244.9-109.9 244.8-245.2.1-135.3-109.5-245.1-244.8-245.2h-244.8v245.2c-.1 135.2 109.5 245 244.8 245.2zm0-654.1h652.7c135.3-.1 244.9-109.9 244.8-245.2.2-135.3-109.4-245.1-244.7-245.3h-652.7c-135.3.1-244.9 109.9-244.8 245.2-.1 135.4 109.4 245.2 244.7 245.3z\"\n fill=\"#ecb22e\"\n />\n <path\n d=\"m0 1553.2c-.1 135.3 109.5 245.1 244.8 245.2 135.3-.1 244.9-109.9 244.8-245.2v-245.2h-244.8c-135.3.1-244.9 109.9-244.8 245.2zm652.7 0v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.2v-653.9c.2-135.3-109.4-245.1-244.7-245.3-135.4 0-244.9 109.8-244.8 245.1 0 0 0 .1 0 0\"\n fill=\"#e01e5a\"\n />\n </g>\n </svg>\n);\n\n/** Microsoft Teams' mark: the two people and the T tile, in flat colour. */\nconst TeamsMark: FC<{ className?: string }> = ({ className }) => (\n <svg viewBox=\"0 0 2228.833 2073.333\" className={className} aria-hidden=\"true\" focusable=\"false\">\n <path\n fill=\"#5059C9\"\n d=\"M1554.637 777.5h575.713c54.391 0 98.483 44.092 98.483 98.483v524.398c0 199.901-162.051 361.952-361.952 361.952h-1.711c-199.901.028-361.975-162-362.004-361.901V828.971c0-28.427 23.044-51.471 51.471-51.471z\"\n />\n <circle fill=\"#5059C9\" cx=\"1943.75\" cy=\"440.583\" r=\"233.25\" />\n <circle fill=\"#7B83EB\" cx=\"1218.083\" cy=\"336.917\" r=\"336.917\" />\n <path\n fill=\"#7B83EB\"\n d=\"M1667.323 777.5H717.01c-53.743 1.33-96.257 45.931-95.01 99.676v598.105c-7.505 322.519 247.657 590.16 570.167 598.053 322.51-7.893 577.671-275.534 570.167-598.053V877.176c1.245-53.745-41.268-98.346-95.011-99.676z\"\n />\n <path\n fill=\"#4B53BC\"\n d=\"M95.01 466.5h950.312c52.473 0 95.01 42.538 95.01 95.01v950.312c0 52.473-42.538 95.01-95.01 95.01H95.01c-52.473 0-95.01-42.538-95.01-95.01V561.51c0-52.472 42.538-95.01 95.01-95.01z\"\n />\n <path fill=\"#FFF\" d=\"M820.211 828.193H630.241v517.297H509.211V828.193H320.123V727.844h500.088z\" />\n </svg>\n);\n\n/**\n * The channel's mark at the size `className` gives: Slack's and Teams' own\n * logos, a tinted glyph for email, texts, the API and voice memos, a speech\n * bubble for any other channel. Decorative: the line it sits on names the\n * channel.\n */\nexport const ChannelMark: FC<{ kind: InboundChannelKind; className?: string }> = ({\n kind,\n className,\n}) => {\n switch (kind) {\n case \"slack\":\n return <SlackMark className={className} />;\n case \"teams\":\n return <TeamsMark className={className} />;\n case \"email\":\n return <MailIcon className={cn(\"text-sky-600\", className)} aria-hidden=\"true\" />;\n case \"sms\":\n return <MessageSquareIcon className={cn(\"text-emerald-600\", className)} aria-hidden=\"true\" />;\n case \"api\":\n return <CodeIcon className={cn(\"text-muted-foreground\", className)} aria-hidden=\"true\" />;\n case \"voice_memo\":\n return <AudioLinesIcon className={cn(\"text-violet-600\", className)} aria-hidden=\"true\" />;\n default:\n return <MessageSquareIcon className={cn(\"text-muted-foreground\", className)} aria-hidden=\"true\" />;\n }\n};\n\n/** The first text part's words: where a channel's routing block sits. */\nfunction firstText(parts: unknown): string {\n if (!Array.isArray(parts)) return \"\";\n for (const part of parts) {\n if (\n typeof part === \"object\" &&\n part !== null &&\n (part as { type?: unknown }).type === \"text\" &&\n typeof (part as { text?: unknown }).text === \"string\"\n ) {\n return (part as { text: string }).text;\n }\n }\n return \"\";\n}\n\n/** Everything the agent read with the message, labelled; the thread a mention came in below it. */\nconst InboundChannelFacts: FC<{ message: InboundChannelMessage }> = ({ message }) => (\n <div\n className=\"flex w-full min-w-0 flex-col gap-2 rounded-xl border border-border bg-background px-3 py-2.5 text-left text-xs\"\n data-slot=\"inbound-channel-facts\"\n >\n <p className=\"text-muted-foreground\">What the agent received with this message</p>\n {message.facts.length > 0 ? (\n <dl className=\"grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1\">\n {message.facts.map((fact, index) => (\n <div key={`${fact.key}-${index}`} className=\"contents\">\n <dt className=\"whitespace-nowrap text-muted-foreground\">{fact.label}</dt>\n <dd className=\"text-foreground [overflow-wrap:anywhere]\">{fact.value}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n {message.quoted ? (\n <div className=\"flex flex-col gap-1\">\n <p className=\"text-muted-foreground\">Earlier in the thread</p>\n <p className=\"whitespace-pre-wrap border-border border-l-2 pl-2 text-foreground [overflow-wrap:anywhere]\">\n {message.quoted}\n </p>\n </div>\n ) : null}\n </div>\n);\n\n/**\n * The line above a channel message's bubble: the channel's mark, the sender's\n * name and address (an email address, a phone number), and a chevron that\n * opens what the agent received. Hovering or focusing it shows a card with\n * the channel, the place and the sender in full, while the facts are closed.\n */\nexport const InboundChannelSource: FC<{ message: InboundChannelMessage; className?: string }> = ({\n message,\n className,\n}) => {\n const [open, setOpen] = useState(false);\n const origin = message.origin;\n const name = origin.senderName;\n const handle = origin.senderHandle;\n const primary = name ?? handle ?? origin.label;\n const secondary = name && handle ? handle : null;\n const channelLine = [origin.label, origin.place].filter(Boolean).join(\" · \");\n\n return (\n <Collapsible\n open={open}\n onOpenChange={setOpen}\n className={cn(\"flex w-full min-w-0 flex-col items-end gap-1\", className)}\n data-slot=\"inbound-channel\"\n data-channel={origin.kind}\n >\n <div className=\"group/inbound-channel relative flex min-w-0 max-w-full justify-end\">\n <CollapsibleTrigger\n data-testid=\"inbound-channel-source\"\n className=\"flex min-w-0 max-w-full items-center gap-1.5 rounded-md px-1.5 py-0.5 text-muted-foreground text-xs transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <ChannelMark kind={origin.kind} className=\"size-3.5 shrink-0\" />\n <span className=\"truncate font-medium text-foreground/80\">{primary}</span>\n {secondary ? <span className=\"truncate\">{secondary}</span> : null}\n <span className=\"sr-only\">{`, via ${channelLine}. Show what the agent received`}</span>\n <ChevronDownIcon\n className={cn(\"size-3 shrink-0 transition-transform\", open && \"rotate-180\")}\n aria-hidden=\"true\"\n />\n </CollapsibleTrigger>\n {/* The hover card: what the line says, in full (the button's own text\n carries it for screen readers). Shown below the line, over the\n bubble, after a short rest, and never while the facts are open. */}\n {open ? null : (\n <div\n aria-hidden=\"true\"\n data-slot=\"inbound-channel-card\"\n className=\"pointer-events-none absolute top-full right-0 z-20 mt-1 flex w-max max-w-xs flex-col gap-0.5 rounded-md bg-foreground px-3 py-1.5 text-left text-background text-xs opacity-0 shadow-md transition-opacity delay-0 group-focus-within/inbound-channel:opacity-100 group-hover/inbound-channel:opacity-100 group-hover/inbound-channel:delay-300\"\n >\n <span className=\"flex items-center gap-1.5 font-medium\">\n <ChannelMark kind={origin.kind} className=\"size-3.5 shrink-0\" />\n {channelLine}\n </span>\n {name ? <span>{name}</span> : null}\n {handle ? <span className=\"opacity-80\">{handle}</span> : null}\n {origin.subject ? (\n <span className=\"opacity-80\">\n {`${origin.kind === \"voice_memo\" ? \"Memo\" : \"Subject\"}: ${origin.subject}`}\n </span>\n ) : null}\n </div>\n )}\n </div>\n <CollapsibleContent className=\"w-full min-w-0\">\n <InboundChannelFacts message={message} />\n </CollapsibleContent>\n </Collapsible>\n );\n};\n\n/**\n * The line above the bubble for the assistant-ui message in scope; nothing\n * for a message without the `inbound_channel` envelope. Selects the raw\n * metadata and parts (stable for a settled message) and derives in `useMemo`.\n */\nexport const InboundChannelLine: FC<{ className?: string }> = ({ className }) => {\n const custom = useAuiState((s) => s.message.metadata?.custom);\n const parts = useAuiState((s) => s.message.parts);\n const message = useMemo(\n () =>\n isInboundChannelMessage(custom)\n ? readInboundChannelMessage({ custom, text: firstText(parts) })\n : null,\n [custom, parts],\n );\n return message ? <InboundChannelSource message={message} className={className} /> : null;\n};\n\n/** An email's subject, the bubble's first line. Nothing for any other message. */\nexport const InboundChannelSubject: FC = () => {\n const subject = useAuiState((s) => inboundEmailSubject(s.message.metadata?.custom));\n if (!subject) return null;\n return (\n <div className=\"mb-1 font-semibold\" data-slot=\"inbound-channel-subject\">\n {subject}\n </div>\n );\n};\n\nconst wrappedTextComponents = new WeakMap<TextMessagePartComponent, TextMessagePartComponent>();\n\n/**\n * `Text` showing a channel message's words without the routing block the\n * agent read ahead of them. Any other text passes through unchanged, tags a\n * person typed included. Cached per component, so a host's part map keeps a\n * stable identity.\n */\nexport function withInboundMessageBody(Text: TextMessagePartComponent): TextMessagePartComponent {\n const cached = wrappedTextComponents.get(Text);\n if (cached) return cached;\n const InboundMessageBodyText: TextMessagePartComponent = (props) => {\n const custom = useAuiState((s) => s.message.metadata?.custom);\n const text = useMemo(() => inboundMessageBody({ custom, text: props.text }), [custom, props.text]);\n return <Text {...props} text={text} />;\n };\n wrappedTextComponents.set(Text, InboundMessageBodyText);\n return InboundMessageBodyText;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const inboundChannel = require("../../agent-runtime/src/inbound-channel.cjs");
4
+ const InboundChannelKind = {
5
+ SLACK: "slack",
6
+ EMAIL: "email",
7
+ SMS: "sms",
8
+ TEAMS: "teams",
9
+ API: "api",
10
+ VOICE_MEMO: "voice_memo",
11
+ OTHER: "other"
12
+ };
13
+ const KIND_BY_CHANNEL = {
14
+ slack: InboundChannelKind.SLACK,
15
+ agent_slack: InboundChannelKind.SLACK,
16
+ email: InboundChannelKind.EMAIL,
17
+ agent_email: InboundChannelKind.EMAIL,
18
+ sms: InboundChannelKind.SMS,
19
+ agent_sms: InboundChannelKind.SMS,
20
+ sms_group: InboundChannelKind.SMS,
21
+ agent_sms_group: InboundChannelKind.SMS,
22
+ teams: InboundChannelKind.TEAMS,
23
+ agent_teams: InboundChannelKind.TEAMS,
24
+ programmatic: InboundChannelKind.API,
25
+ api: InboundChannelKind.API,
26
+ voice_memo: InboundChannelKind.VOICE_MEMO
27
+ };
28
+ const KIND_LABEL = {
29
+ slack: "Slack",
30
+ email: "Email",
31
+ sms: "Text message",
32
+ teams: "Microsoft Teams",
33
+ api: "API",
34
+ voice_memo: "Voice memo"
35
+ };
36
+ const LABEL_WORDS = {
37
+ api: "API",
38
+ cc: "CC",
39
+ id: "ID",
40
+ ids: "IDs",
41
+ sid: "SID",
42
+ sms: "SMS",
43
+ ts: "timestamp",
44
+ url: "URL"
45
+ };
46
+ function inboundContextFieldLabel(key) {
47
+ const words = key.split(/[_\s]+/).filter((word) => word.length > 0).map((word) => LABEL_WORDS[word.toLowerCase()] ?? word.toLowerCase());
48
+ const [first, ...rest] = words;
49
+ if (first === void 0) return key;
50
+ return [first.charAt(0).toUpperCase() + first.slice(1), ...rest].join(" ");
51
+ }
52
+ function field(key, value) {
53
+ return { key, label: inboundContextFieldLabel(key), value };
54
+ }
55
+ function contextFields(lines) {
56
+ const fields = [];
57
+ for (const line of lines.split("\n")) {
58
+ const separator = line.indexOf(":");
59
+ if (separator <= 0) continue;
60
+ const key = line.slice(0, separator).trim();
61
+ const value = line.slice(separator + 1).trim();
62
+ if (key && value) fields.push(field(key, value));
63
+ }
64
+ return fields;
65
+ }
66
+ function isRecord(value) {
67
+ return typeof value === "object" && value !== null && !Array.isArray(value);
68
+ }
69
+ function readString(record, key) {
70
+ const value = record[key];
71
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
72
+ }
73
+ function readScalar(value) {
74
+ if (typeof value === "string") return value.trim() || null;
75
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
76
+ if (typeof value === "boolean") return value ? "yes" : "no";
77
+ if (Array.isArray(value)) {
78
+ const items = value.filter((item) => typeof item === "string" && item.trim() !== "");
79
+ return items.length > 0 ? items.join(", ") : null;
80
+ }
81
+ return null;
82
+ }
83
+ const HIDDEN_DETAILS = /* @__PURE__ */ new Set(["body_text"]);
84
+ function inboundChannelKind(channel) {
85
+ return KIND_BY_CHANNEL[channel.trim().toLowerCase()] ?? InboundChannelKind.OTHER;
86
+ }
87
+ function humanize(value) {
88
+ const words = value.replace(/^agent_/, "").replace(/[-_.]+/g, " ").trim();
89
+ return words ? words.charAt(0).toUpperCase() + words.slice(1) : "Message";
90
+ }
91
+ function inboundChannelLabel(channel) {
92
+ const kind = inboundChannelKind(channel);
93
+ return kind === InboundChannelKind.OTHER ? humanize(channel) : KIND_LABEL[kind];
94
+ }
95
+ function formatInboundPhoneNumber(value) {
96
+ const match = /^\+1(\d{3})(\d{3})(\d{4})$/.exec(value.trim());
97
+ return match ? `+1 (${match[1]}) ${match[2]}-${match[3]}` : value;
98
+ }
99
+ const SLACK_PLACES = {
100
+ "direct message": "Direct message",
101
+ "group direct message": "Group DM",
102
+ "public channel": "Channel",
103
+ "private channel": "Private channel"
104
+ };
105
+ const TEAMS_PLACES = {
106
+ personal: "Personal chat",
107
+ groupchat: "Group chat",
108
+ channel: "Channel"
109
+ };
110
+ function factValue(facts, key) {
111
+ var _a;
112
+ return ((_a = facts.find((entry) => entry.key === key)) == null ? void 0 : _a.value) ?? null;
113
+ }
114
+ function slackPlace(facts) {
115
+ const named = /^(#[^\s(]+)/.exec(factValue(facts, "slack_channel") ?? "");
116
+ if (named == null ? void 0 : named[1]) return named[1];
117
+ const type = factValue(facts, "slack_channel_type");
118
+ return type ? SLACK_PLACES[type.toLowerCase()] ?? humanize(type) : null;
119
+ }
120
+ function teamsPlace(facts) {
121
+ const type = factValue(facts, "teams_conversation_type") ?? factValue(facts, "conversation_type");
122
+ return type ? TEAMS_PLACES[type.toLowerCase()] ?? humanize(type) : null;
123
+ }
124
+ function groupTextPlace(facts, channel) {
125
+ const isGroup = factValue(facts, "source") === inboundChannel.GROUP_TEXT_SOURCE || channel === "sms_group" || channel === "agent_sms_group" || factValue(facts, "group_thread_id") !== null;
126
+ if (!isGroup) return null;
127
+ const title = factValue(facts, "group_title");
128
+ return title ? `Group text · ${title}` : "Group text";
129
+ }
130
+ function readInboundChannelMessage({
131
+ custom,
132
+ text
133
+ }) {
134
+ const envelope = custom == null ? void 0 : custom[inboundChannel.INBOUND_CHANNEL_KEY];
135
+ if (!isRecord(envelope)) return null;
136
+ const channel = readString(envelope, "channel");
137
+ if (channel === null) return null;
138
+ const details = [];
139
+ if (isRecord(envelope.details)) {
140
+ for (const [key, value] of Object.entries(envelope.details)) {
141
+ if (HIDDEN_DETAILS.has(key)) continue;
142
+ const scalar = readScalar(value);
143
+ if (scalar !== null) details.push(field(key, scalar));
144
+ }
145
+ }
146
+ const senderName = readString(envelope, "sender_name");
147
+ const senderHandle = readString(envelope, "sender_handle");
148
+ const subject = readString(envelope, "subject");
149
+ const context = inboundChannel.leadingInboundContext(text);
150
+ const facts = context ? contextFields(context.lines) : [
151
+ field("source", inboundChannelLabel(channel)),
152
+ ...senderName ? [field("sender_name", senderName)] : [],
153
+ ...senderHandle ? [field("sender_handle", senderHandle)] : [],
154
+ ...subject ? [field("subject", subject)] : [],
155
+ ...details
156
+ ];
157
+ const kind = inboundChannelKind(channel);
158
+ const known = [...facts, ...details];
159
+ let place = null;
160
+ if (kind === InboundChannelKind.SLACK) place = slackPlace(known);
161
+ else if (kind === InboundChannelKind.TEAMS) place = teamsPlace(known);
162
+ else if (kind === InboundChannelKind.SMS) place = groupTextPlace(known, channel);
163
+ return {
164
+ origin: {
165
+ kind,
166
+ channel,
167
+ label: inboundChannelLabel(channel),
168
+ senderName,
169
+ senderHandle: senderHandle !== null && kind === InboundChannelKind.SMS ? formatInboundPhoneNumber(senderHandle) : senderHandle,
170
+ place,
171
+ subject
172
+ },
173
+ facts,
174
+ rawContext: (context == null ? void 0 : context.raw) ?? null,
175
+ quoted: context ? inboundChannel.splitInboundBody(context).quoted : null
176
+ };
177
+ }
178
+ exports.InboundChannelKind = InboundChannelKind;
179
+ exports.formatInboundPhoneNumber = formatInboundPhoneNumber;
180
+ exports.inboundChannelKind = inboundChannelKind;
181
+ exports.inboundChannelLabel = inboundChannelLabel;
182
+ exports.inboundContextFieldLabel = inboundContextFieldLabel;
183
+ exports.readInboundChannelMessage = readInboundChannelMessage;
184
+ //# sourceMappingURL=inbound-channel-reader.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-channel-reader.cjs","sources":["../../../src/chat-ui/messages/inbound-channel-reader.ts"],"sourcesContent":["/**\n * A channel message read in full, for the line above its bubble\n * (`inbound-channel-message.tsx`): where it came from (channel, place), who\n * sent it (name, address or number), and every fact the agent received with\n * it, labelled for a person. The small reading every bubble needs (the words\n * without the routing block, the envelope check) lives in\n * `@athenaintel/agent-runtime`'s `inbound-channel.ts`.\n */\nimport {\n GROUP_TEXT_SOURCE,\n INBOUND_CHANNEL_KEY,\n leadingInboundContext,\n splitInboundBody,\n} from \"@athenaintel/agent-runtime\";\n\n/** Where a message came from, as a client draws it. */\nexport const InboundChannelKind = {\n SLACK: \"slack\",\n EMAIL: \"email\",\n SMS: \"sms\",\n TEAMS: \"teams\",\n API: \"api\",\n VOICE_MEMO: \"voice_memo\",\n OTHER: \"other\",\n} as const;\nexport type InboundChannelKind = (typeof InboundChannelKind)[keyof typeof InboundChannelKind];\n\n/** The envelope's `channel` values, bare and as the collab-agent routers name them. */\nconst KIND_BY_CHANNEL: Readonly<Record<string, InboundChannelKind>> = {\n slack: InboundChannelKind.SLACK,\n agent_slack: InboundChannelKind.SLACK,\n email: InboundChannelKind.EMAIL,\n agent_email: InboundChannelKind.EMAIL,\n sms: InboundChannelKind.SMS,\n agent_sms: InboundChannelKind.SMS,\n sms_group: InboundChannelKind.SMS,\n agent_sms_group: InboundChannelKind.SMS,\n teams: InboundChannelKind.TEAMS,\n agent_teams: InboundChannelKind.TEAMS,\n programmatic: InboundChannelKind.API,\n api: InboundChannelKind.API,\n voice_memo: InboundChannelKind.VOICE_MEMO,\n};\n\nconst KIND_LABEL: Readonly<Record<Exclude<InboundChannelKind, \"other\">, string>> = {\n slack: \"Slack\",\n email: \"Email\",\n sms: \"Text message\",\n teams: \"Microsoft Teams\",\n api: \"API\",\n voice_memo: \"Voice memo\",\n};\n\n/** One fact the agent read, labelled for a person. */\nexport interface InboundContextField {\n /** The key as the agent read it (`slack_channel`). */\n readonly key: string;\n /** The key for a person (`Slack channel`). */\n readonly label: string;\n readonly value: string;\n}\n\n/** Where a message came from and who sent it: the envelope, ready to draw. */\nexport interface InboundChannelOrigin {\n readonly kind: InboundChannelKind;\n /** The envelope's channel as stamped (`slack`, `sms`, `voice_memo`, …). */\n readonly channel: string;\n /** The channel as people name it: \"Slack\", \"Email\", \"Text message\", \"Microsoft Teams\". */\n readonly label: string;\n readonly senderName: string | null;\n /** An email address, or a phone number written for reading. */\n readonly senderHandle: string | null;\n /** Where on the channel it was said: \"#launch\", \"Direct message\", \"Group text · Family\". */\n readonly place: string | null;\n /** An email's subject; a voice memo's title. */\n readonly subject: string | null;\n}\n\n/** A channel message, read for its line. */\nexport interface InboundChannelMessage {\n readonly origin: InboundChannelOrigin;\n /** What the agent read with it: the routing block's facts, else the envelope's own. */\n readonly facts: readonly InboundContextField[];\n /** The block exactly as the agent read it; `null` when the text carried none. */\n readonly rawContext: string | null;\n /** Earlier messages the channel quoted for the agent (the Slack thread a mention came in). */\n readonly quoted: string | null;\n}\n\nconst LABEL_WORDS: Readonly<Record<string, string>> = {\n api: \"API\",\n cc: \"CC\",\n id: \"ID\",\n ids: \"IDs\",\n sid: \"SID\",\n sms: \"SMS\",\n ts: \"timestamp\",\n url: \"URL\",\n};\n\n/** A fact's key for a person: `slack_workspace_id` → \"Slack workspace ID\". */\nexport function inboundContextFieldLabel(key: string): string {\n const words = key\n .split(/[_\\s]+/)\n .filter((word) => word.length > 0)\n .map((word) => LABEL_WORDS[word.toLowerCase()] ?? word.toLowerCase());\n const [first, ...rest] = words;\n if (first === undefined) return key;\n return [first.charAt(0).toUpperCase() + first.slice(1), ...rest].join(\" \");\n}\n\nfunction field(key: string, value: string): InboundContextField {\n return { key, label: inboundContextFieldLabel(key), value };\n}\n\n/** The block's `key: value` lines, labelled, in order. */\nfunction contextFields(lines: string): InboundContextField[] {\n const fields: InboundContextField[] = [];\n for (const line of lines.split(\"\\n\")) {\n const separator = line.indexOf(\":\");\n if (separator <= 0) continue;\n const key = line.slice(0, separator).trim();\n const value = line.slice(separator + 1).trim();\n if (key && value) fields.push(field(key, value));\n }\n return fields;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction readString(record: Record<string, unknown>, key: string): string | null {\n const value = record[key];\n return typeof value === \"string\" && value.trim().length > 0 ? value.trim() : null;\n}\n\nfunction readScalar(value: unknown): string | null {\n if (typeof value === \"string\") return value.trim() || null;\n if (typeof value === \"number\" && Number.isFinite(value)) return String(value);\n if (typeof value === \"boolean\") return value ? \"yes\" : \"no\";\n if (Array.isArray(value)) {\n const items = value.filter((item): item is string => typeof item === \"string\" && item.trim() !== \"\");\n return items.length > 0 ? items.join(\", \") : null;\n }\n return null;\n}\n\n/** Envelope details a person doesn't need repeated: an email's body, which is the message itself. */\nconst HIDDEN_DETAILS: ReadonlySet<string> = new Set([\"body_text\"]);\n\n/** The kind a client draws for an envelope's channel. */\nexport function inboundChannelKind(channel: string): InboundChannelKind {\n return KIND_BY_CHANNEL[channel.trim().toLowerCase()] ?? InboundChannelKind.OTHER;\n}\n\nfunction humanize(value: string): string {\n const words = value.replace(/^agent_/, \"\").replace(/[-_.]+/g, \" \").trim();\n return words ? words.charAt(0).toUpperCase() + words.slice(1) : \"Message\";\n}\n\n/** The channel as people name it: \"Slack\", \"Text message\"; another channel in its own words. */\nexport function inboundChannelLabel(channel: string): string {\n const kind = inboundChannelKind(channel);\n return kind === InboundChannelKind.OTHER ? humanize(channel) : KIND_LABEL[kind];\n}\n\n/**\n * A phone number written for reading: a North American `+1` number as\n * `+1 (224) 555-0100`; any other number as it came.\n */\nexport function formatInboundPhoneNumber(value: string): string {\n const match = /^\\+1(\\d{3})(\\d{3})(\\d{4})$/.exec(value.trim());\n return match ? `+1 (${match[1]}) ${match[2]}-${match[3]}` : value;\n}\n\nconst SLACK_PLACES: Readonly<Record<string, string>> = {\n \"direct message\": \"Direct message\",\n \"group direct message\": \"Group DM\",\n \"public channel\": \"Channel\",\n \"private channel\": \"Private channel\",\n};\n\nconst TEAMS_PLACES: Readonly<Record<string, string>> = {\n personal: \"Personal chat\",\n groupchat: \"Group chat\",\n channel: \"Channel\",\n};\n\nfunction factValue(facts: readonly InboundContextField[], key: string): string | null {\n return facts.find((entry) => entry.key === key)?.value ?? null;\n}\n\nfunction slackPlace(facts: readonly InboundContextField[]): string | null {\n const named = /^(#[^\\s(]+)/.exec(factValue(facts, \"slack_channel\") ?? \"\");\n if (named?.[1]) return named[1];\n const type = factValue(facts, \"slack_channel_type\");\n return type ? (SLACK_PLACES[type.toLowerCase()] ?? humanize(type)) : null;\n}\n\nfunction teamsPlace(facts: readonly InboundContextField[]): string | null {\n const type = factValue(facts, \"teams_conversation_type\") ?? factValue(facts, \"conversation_type\");\n return type ? (TEAMS_PLACES[type.toLowerCase()] ?? humanize(type)) : null;\n}\n\nfunction groupTextPlace(facts: readonly InboundContextField[], channel: string): string | null {\n const isGroup =\n factValue(facts, \"source\") === GROUP_TEXT_SOURCE ||\n channel === \"sms_group\" ||\n channel === \"agent_sms_group\" ||\n factValue(facts, \"group_thread_id\") !== null;\n if (!isGroup) return null;\n const title = factValue(facts, \"group_title\");\n return title ? `Group text · ${title}` : \"Group text\";\n}\n\n/**\n * A user turn read as a channel message, or `null` when it carries no\n * `inbound_channel` envelope in `custom` (an ordinary turn, tags a person\n * typed included). The facts are the routing block's, or the envelope's own\n * when the text starts with none.\n */\nexport function readInboundChannelMessage({\n custom,\n text,\n}: {\n custom: Record<string, unknown> | null | undefined;\n text: string;\n}): InboundChannelMessage | null {\n const envelope = custom?.[INBOUND_CHANNEL_KEY];\n if (!isRecord(envelope)) return null;\n const channel = readString(envelope, \"channel\");\n if (channel === null) return null;\n\n const details: InboundContextField[] = [];\n if (isRecord(envelope.details)) {\n for (const [key, value] of Object.entries(envelope.details)) {\n if (HIDDEN_DETAILS.has(key)) continue;\n const scalar = readScalar(value);\n if (scalar !== null) details.push(field(key, scalar));\n }\n }\n const senderName = readString(envelope, \"sender_name\");\n const senderHandle = readString(envelope, \"sender_handle\");\n const subject = readString(envelope, \"subject\");\n\n const context = leadingInboundContext(text);\n const facts: InboundContextField[] = context\n ? contextFields(context.lines)\n : [\n field(\"source\", inboundChannelLabel(channel)),\n ...(senderName ? [field(\"sender_name\", senderName)] : []),\n ...(senderHandle ? [field(\"sender_handle\", senderHandle)] : []),\n ...(subject ? [field(\"subject\", subject)] : []),\n ...details,\n ];\n\n const kind = inboundChannelKind(channel);\n // The block and the envelope's details name the same places; either serves.\n const known = [...facts, ...details];\n let place: string | null = null;\n if (kind === InboundChannelKind.SLACK) place = slackPlace(known);\n else if (kind === InboundChannelKind.TEAMS) place = teamsPlace(known);\n else if (kind === InboundChannelKind.SMS) place = groupTextPlace(known, channel);\n\n return {\n origin: {\n kind,\n channel,\n label: inboundChannelLabel(channel),\n senderName,\n senderHandle:\n senderHandle !== null && kind === InboundChannelKind.SMS\n ? formatInboundPhoneNumber(senderHandle)\n : senderHandle,\n place,\n subject,\n },\n facts,\n rawContext: context?.raw ?? null,\n quoted: context ? splitInboundBody(context).quoted : null,\n };\n}\n"],"names":["GROUP_TEXT_SOURCE","INBOUND_CHANNEL_KEY","leadingInboundContext","splitInboundBody"],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /** Where a message came from, as a client draws it. */
2
+ export declare const InboundChannelKind: {
3
+ readonly SLACK: "slack";
4
+ readonly EMAIL: "email";
5
+ readonly SMS: "sms";
6
+ readonly TEAMS: "teams";
7
+ readonly API: "api";
8
+ readonly VOICE_MEMO: "voice_memo";
9
+ readonly OTHER: "other";
10
+ };
11
+ export type InboundChannelKind = (typeof InboundChannelKind)[keyof typeof InboundChannelKind];
12
+ /** One fact the agent read, labelled for a person. */
13
+ export interface InboundContextField {
14
+ /** The key as the agent read it (`slack_channel`). */
15
+ readonly key: string;
16
+ /** The key for a person (`Slack channel`). */
17
+ readonly label: string;
18
+ readonly value: string;
19
+ }
20
+ /** Where a message came from and who sent it: the envelope, ready to draw. */
21
+ export interface InboundChannelOrigin {
22
+ readonly kind: InboundChannelKind;
23
+ /** The envelope's channel as stamped (`slack`, `sms`, `voice_memo`, …). */
24
+ readonly channel: string;
25
+ /** The channel as people name it: "Slack", "Email", "Text message", "Microsoft Teams". */
26
+ readonly label: string;
27
+ readonly senderName: string | null;
28
+ /** An email address, or a phone number written for reading. */
29
+ readonly senderHandle: string | null;
30
+ /** Where on the channel it was said: "#launch", "Direct message", "Group text · Family". */
31
+ readonly place: string | null;
32
+ /** An email's subject; a voice memo's title. */
33
+ readonly subject: string | null;
34
+ }
35
+ /** A channel message, read for its line. */
36
+ export interface InboundChannelMessage {
37
+ readonly origin: InboundChannelOrigin;
38
+ /** What the agent read with it: the routing block's facts, else the envelope's own. */
39
+ readonly facts: readonly InboundContextField[];
40
+ /** The block exactly as the agent read it; `null` when the text carried none. */
41
+ readonly rawContext: string | null;
42
+ /** Earlier messages the channel quoted for the agent (the Slack thread a mention came in). */
43
+ readonly quoted: string | null;
44
+ }
45
+ /** A fact's key for a person: `slack_workspace_id` → "Slack workspace ID". */
46
+ export declare function inboundContextFieldLabel(key: string): string;
47
+ /** The kind a client draws for an envelope's channel. */
48
+ export declare function inboundChannelKind(channel: string): InboundChannelKind;
49
+ /** The channel as people name it: "Slack", "Text message"; another channel in its own words. */
50
+ export declare function inboundChannelLabel(channel: string): string;
51
+ /**
52
+ * A phone number written for reading: a North American `+1` number as
53
+ * `+1 (224) 555-0100`; any other number as it came.
54
+ */
55
+ export declare function formatInboundPhoneNumber(value: string): string;
56
+ /**
57
+ * A user turn read as a channel message, or `null` when it carries no
58
+ * `inbound_channel` envelope in `custom` (an ordinary turn, tags a person
59
+ * typed included). The facts are the routing block's, or the envelope's own
60
+ * when the text starts with none.
61
+ */
62
+ export declare function readInboundChannelMessage({ custom, text, }: {
63
+ custom: Record<string, unknown> | null | undefined;
64
+ text: string;
65
+ }): InboundChannelMessage | null;
@@ -0,0 +1,184 @@
1
+ import { INBOUND_CHANNEL_KEY, leadingInboundContext, splitInboundBody, GROUP_TEXT_SOURCE } from "../../agent-runtime/src/inbound-channel.js";
2
+ const InboundChannelKind = {
3
+ SLACK: "slack",
4
+ EMAIL: "email",
5
+ SMS: "sms",
6
+ TEAMS: "teams",
7
+ API: "api",
8
+ VOICE_MEMO: "voice_memo",
9
+ OTHER: "other"
10
+ };
11
+ const KIND_BY_CHANNEL = {
12
+ slack: InboundChannelKind.SLACK,
13
+ agent_slack: InboundChannelKind.SLACK,
14
+ email: InboundChannelKind.EMAIL,
15
+ agent_email: InboundChannelKind.EMAIL,
16
+ sms: InboundChannelKind.SMS,
17
+ agent_sms: InboundChannelKind.SMS,
18
+ sms_group: InboundChannelKind.SMS,
19
+ agent_sms_group: InboundChannelKind.SMS,
20
+ teams: InboundChannelKind.TEAMS,
21
+ agent_teams: InboundChannelKind.TEAMS,
22
+ programmatic: InboundChannelKind.API,
23
+ api: InboundChannelKind.API,
24
+ voice_memo: InboundChannelKind.VOICE_MEMO
25
+ };
26
+ const KIND_LABEL = {
27
+ slack: "Slack",
28
+ email: "Email",
29
+ sms: "Text message",
30
+ teams: "Microsoft Teams",
31
+ api: "API",
32
+ voice_memo: "Voice memo"
33
+ };
34
+ const LABEL_WORDS = {
35
+ api: "API",
36
+ cc: "CC",
37
+ id: "ID",
38
+ ids: "IDs",
39
+ sid: "SID",
40
+ sms: "SMS",
41
+ ts: "timestamp",
42
+ url: "URL"
43
+ };
44
+ function inboundContextFieldLabel(key) {
45
+ const words = key.split(/[_\s]+/).filter((word) => word.length > 0).map((word) => LABEL_WORDS[word.toLowerCase()] ?? word.toLowerCase());
46
+ const [first, ...rest] = words;
47
+ if (first === void 0) return key;
48
+ return [first.charAt(0).toUpperCase() + first.slice(1), ...rest].join(" ");
49
+ }
50
+ function field(key, value) {
51
+ return { key, label: inboundContextFieldLabel(key), value };
52
+ }
53
+ function contextFields(lines) {
54
+ const fields = [];
55
+ for (const line of lines.split("\n")) {
56
+ const separator = line.indexOf(":");
57
+ if (separator <= 0) continue;
58
+ const key = line.slice(0, separator).trim();
59
+ const value = line.slice(separator + 1).trim();
60
+ if (key && value) fields.push(field(key, value));
61
+ }
62
+ return fields;
63
+ }
64
+ function isRecord(value) {
65
+ return typeof value === "object" && value !== null && !Array.isArray(value);
66
+ }
67
+ function readString(record, key) {
68
+ const value = record[key];
69
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
70
+ }
71
+ function readScalar(value) {
72
+ if (typeof value === "string") return value.trim() || null;
73
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
74
+ if (typeof value === "boolean") return value ? "yes" : "no";
75
+ if (Array.isArray(value)) {
76
+ const items = value.filter((item) => typeof item === "string" && item.trim() !== "");
77
+ return items.length > 0 ? items.join(", ") : null;
78
+ }
79
+ return null;
80
+ }
81
+ const HIDDEN_DETAILS = /* @__PURE__ */ new Set(["body_text"]);
82
+ function inboundChannelKind(channel) {
83
+ return KIND_BY_CHANNEL[channel.trim().toLowerCase()] ?? InboundChannelKind.OTHER;
84
+ }
85
+ function humanize(value) {
86
+ const words = value.replace(/^agent_/, "").replace(/[-_.]+/g, " ").trim();
87
+ return words ? words.charAt(0).toUpperCase() + words.slice(1) : "Message";
88
+ }
89
+ function inboundChannelLabel(channel) {
90
+ const kind = inboundChannelKind(channel);
91
+ return kind === InboundChannelKind.OTHER ? humanize(channel) : KIND_LABEL[kind];
92
+ }
93
+ function formatInboundPhoneNumber(value) {
94
+ const match = /^\+1(\d{3})(\d{3})(\d{4})$/.exec(value.trim());
95
+ return match ? `+1 (${match[1]}) ${match[2]}-${match[3]}` : value;
96
+ }
97
+ const SLACK_PLACES = {
98
+ "direct message": "Direct message",
99
+ "group direct message": "Group DM",
100
+ "public channel": "Channel",
101
+ "private channel": "Private channel"
102
+ };
103
+ const TEAMS_PLACES = {
104
+ personal: "Personal chat",
105
+ groupchat: "Group chat",
106
+ channel: "Channel"
107
+ };
108
+ function factValue(facts, key) {
109
+ var _a;
110
+ return ((_a = facts.find((entry) => entry.key === key)) == null ? void 0 : _a.value) ?? null;
111
+ }
112
+ function slackPlace(facts) {
113
+ const named = /^(#[^\s(]+)/.exec(factValue(facts, "slack_channel") ?? "");
114
+ if (named == null ? void 0 : named[1]) return named[1];
115
+ const type = factValue(facts, "slack_channel_type");
116
+ return type ? SLACK_PLACES[type.toLowerCase()] ?? humanize(type) : null;
117
+ }
118
+ function teamsPlace(facts) {
119
+ const type = factValue(facts, "teams_conversation_type") ?? factValue(facts, "conversation_type");
120
+ return type ? TEAMS_PLACES[type.toLowerCase()] ?? humanize(type) : null;
121
+ }
122
+ function groupTextPlace(facts, channel) {
123
+ const isGroup = factValue(facts, "source") === GROUP_TEXT_SOURCE || channel === "sms_group" || channel === "agent_sms_group" || factValue(facts, "group_thread_id") !== null;
124
+ if (!isGroup) return null;
125
+ const title = factValue(facts, "group_title");
126
+ return title ? `Group text · ${title}` : "Group text";
127
+ }
128
+ function readInboundChannelMessage({
129
+ custom,
130
+ text
131
+ }) {
132
+ const envelope = custom == null ? void 0 : custom[INBOUND_CHANNEL_KEY];
133
+ if (!isRecord(envelope)) return null;
134
+ const channel = readString(envelope, "channel");
135
+ if (channel === null) return null;
136
+ const details = [];
137
+ if (isRecord(envelope.details)) {
138
+ for (const [key, value] of Object.entries(envelope.details)) {
139
+ if (HIDDEN_DETAILS.has(key)) continue;
140
+ const scalar = readScalar(value);
141
+ if (scalar !== null) details.push(field(key, scalar));
142
+ }
143
+ }
144
+ const senderName = readString(envelope, "sender_name");
145
+ const senderHandle = readString(envelope, "sender_handle");
146
+ const subject = readString(envelope, "subject");
147
+ const context = leadingInboundContext(text);
148
+ const facts = context ? contextFields(context.lines) : [
149
+ field("source", inboundChannelLabel(channel)),
150
+ ...senderName ? [field("sender_name", senderName)] : [],
151
+ ...senderHandle ? [field("sender_handle", senderHandle)] : [],
152
+ ...subject ? [field("subject", subject)] : [],
153
+ ...details
154
+ ];
155
+ const kind = inboundChannelKind(channel);
156
+ const known = [...facts, ...details];
157
+ let place = null;
158
+ if (kind === InboundChannelKind.SLACK) place = slackPlace(known);
159
+ else if (kind === InboundChannelKind.TEAMS) place = teamsPlace(known);
160
+ else if (kind === InboundChannelKind.SMS) place = groupTextPlace(known, channel);
161
+ return {
162
+ origin: {
163
+ kind,
164
+ channel,
165
+ label: inboundChannelLabel(channel),
166
+ senderName,
167
+ senderHandle: senderHandle !== null && kind === InboundChannelKind.SMS ? formatInboundPhoneNumber(senderHandle) : senderHandle,
168
+ place,
169
+ subject
170
+ },
171
+ facts,
172
+ rawContext: (context == null ? void 0 : context.raw) ?? null,
173
+ quoted: context ? splitInboundBody(context).quoted : null
174
+ };
175
+ }
176
+ export {
177
+ InboundChannelKind,
178
+ formatInboundPhoneNumber,
179
+ inboundChannelKind,
180
+ inboundChannelLabel,
181
+ inboundContextFieldLabel,
182
+ readInboundChannelMessage
183
+ };
184
+ //# sourceMappingURL=inbound-channel-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-channel-reader.js","sources":["../../../src/chat-ui/messages/inbound-channel-reader.ts"],"sourcesContent":["/**\n * A channel message read in full, for the line above its bubble\n * (`inbound-channel-message.tsx`): where it came from (channel, place), who\n * sent it (name, address or number), and every fact the agent received with\n * it, labelled for a person. The small reading every bubble needs (the words\n * without the routing block, the envelope check) lives in\n * `@athenaintel/agent-runtime`'s `inbound-channel.ts`.\n */\nimport {\n GROUP_TEXT_SOURCE,\n INBOUND_CHANNEL_KEY,\n leadingInboundContext,\n splitInboundBody,\n} from \"@athenaintel/agent-runtime\";\n\n/** Where a message came from, as a client draws it. */\nexport const InboundChannelKind = {\n SLACK: \"slack\",\n EMAIL: \"email\",\n SMS: \"sms\",\n TEAMS: \"teams\",\n API: \"api\",\n VOICE_MEMO: \"voice_memo\",\n OTHER: \"other\",\n} as const;\nexport type InboundChannelKind = (typeof InboundChannelKind)[keyof typeof InboundChannelKind];\n\n/** The envelope's `channel` values, bare and as the collab-agent routers name them. */\nconst KIND_BY_CHANNEL: Readonly<Record<string, InboundChannelKind>> = {\n slack: InboundChannelKind.SLACK,\n agent_slack: InboundChannelKind.SLACK,\n email: InboundChannelKind.EMAIL,\n agent_email: InboundChannelKind.EMAIL,\n sms: InboundChannelKind.SMS,\n agent_sms: InboundChannelKind.SMS,\n sms_group: InboundChannelKind.SMS,\n agent_sms_group: InboundChannelKind.SMS,\n teams: InboundChannelKind.TEAMS,\n agent_teams: InboundChannelKind.TEAMS,\n programmatic: InboundChannelKind.API,\n api: InboundChannelKind.API,\n voice_memo: InboundChannelKind.VOICE_MEMO,\n};\n\nconst KIND_LABEL: Readonly<Record<Exclude<InboundChannelKind, \"other\">, string>> = {\n slack: \"Slack\",\n email: \"Email\",\n sms: \"Text message\",\n teams: \"Microsoft Teams\",\n api: \"API\",\n voice_memo: \"Voice memo\",\n};\n\n/** One fact the agent read, labelled for a person. */\nexport interface InboundContextField {\n /** The key as the agent read it (`slack_channel`). */\n readonly key: string;\n /** The key for a person (`Slack channel`). */\n readonly label: string;\n readonly value: string;\n}\n\n/** Where a message came from and who sent it: the envelope, ready to draw. */\nexport interface InboundChannelOrigin {\n readonly kind: InboundChannelKind;\n /** The envelope's channel as stamped (`slack`, `sms`, `voice_memo`, …). */\n readonly channel: string;\n /** The channel as people name it: \"Slack\", \"Email\", \"Text message\", \"Microsoft Teams\". */\n readonly label: string;\n readonly senderName: string | null;\n /** An email address, or a phone number written for reading. */\n readonly senderHandle: string | null;\n /** Where on the channel it was said: \"#launch\", \"Direct message\", \"Group text · Family\". */\n readonly place: string | null;\n /** An email's subject; a voice memo's title. */\n readonly subject: string | null;\n}\n\n/** A channel message, read for its line. */\nexport interface InboundChannelMessage {\n readonly origin: InboundChannelOrigin;\n /** What the agent read with it: the routing block's facts, else the envelope's own. */\n readonly facts: readonly InboundContextField[];\n /** The block exactly as the agent read it; `null` when the text carried none. */\n readonly rawContext: string | null;\n /** Earlier messages the channel quoted for the agent (the Slack thread a mention came in). */\n readonly quoted: string | null;\n}\n\nconst LABEL_WORDS: Readonly<Record<string, string>> = {\n api: \"API\",\n cc: \"CC\",\n id: \"ID\",\n ids: \"IDs\",\n sid: \"SID\",\n sms: \"SMS\",\n ts: \"timestamp\",\n url: \"URL\",\n};\n\n/** A fact's key for a person: `slack_workspace_id` → \"Slack workspace ID\". */\nexport function inboundContextFieldLabel(key: string): string {\n const words = key\n .split(/[_\\s]+/)\n .filter((word) => word.length > 0)\n .map((word) => LABEL_WORDS[word.toLowerCase()] ?? word.toLowerCase());\n const [first, ...rest] = words;\n if (first === undefined) return key;\n return [first.charAt(0).toUpperCase() + first.slice(1), ...rest].join(\" \");\n}\n\nfunction field(key: string, value: string): InboundContextField {\n return { key, label: inboundContextFieldLabel(key), value };\n}\n\n/** The block's `key: value` lines, labelled, in order. */\nfunction contextFields(lines: string): InboundContextField[] {\n const fields: InboundContextField[] = [];\n for (const line of lines.split(\"\\n\")) {\n const separator = line.indexOf(\":\");\n if (separator <= 0) continue;\n const key = line.slice(0, separator).trim();\n const value = line.slice(separator + 1).trim();\n if (key && value) fields.push(field(key, value));\n }\n return fields;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction readString(record: Record<string, unknown>, key: string): string | null {\n const value = record[key];\n return typeof value === \"string\" && value.trim().length > 0 ? value.trim() : null;\n}\n\nfunction readScalar(value: unknown): string | null {\n if (typeof value === \"string\") return value.trim() || null;\n if (typeof value === \"number\" && Number.isFinite(value)) return String(value);\n if (typeof value === \"boolean\") return value ? \"yes\" : \"no\";\n if (Array.isArray(value)) {\n const items = value.filter((item): item is string => typeof item === \"string\" && item.trim() !== \"\");\n return items.length > 0 ? items.join(\", \") : null;\n }\n return null;\n}\n\n/** Envelope details a person doesn't need repeated: an email's body, which is the message itself. */\nconst HIDDEN_DETAILS: ReadonlySet<string> = new Set([\"body_text\"]);\n\n/** The kind a client draws for an envelope's channel. */\nexport function inboundChannelKind(channel: string): InboundChannelKind {\n return KIND_BY_CHANNEL[channel.trim().toLowerCase()] ?? InboundChannelKind.OTHER;\n}\n\nfunction humanize(value: string): string {\n const words = value.replace(/^agent_/, \"\").replace(/[-_.]+/g, \" \").trim();\n return words ? words.charAt(0).toUpperCase() + words.slice(1) : \"Message\";\n}\n\n/** The channel as people name it: \"Slack\", \"Text message\"; another channel in its own words. */\nexport function inboundChannelLabel(channel: string): string {\n const kind = inboundChannelKind(channel);\n return kind === InboundChannelKind.OTHER ? humanize(channel) : KIND_LABEL[kind];\n}\n\n/**\n * A phone number written for reading: a North American `+1` number as\n * `+1 (224) 555-0100`; any other number as it came.\n */\nexport function formatInboundPhoneNumber(value: string): string {\n const match = /^\\+1(\\d{3})(\\d{3})(\\d{4})$/.exec(value.trim());\n return match ? `+1 (${match[1]}) ${match[2]}-${match[3]}` : value;\n}\n\nconst SLACK_PLACES: Readonly<Record<string, string>> = {\n \"direct message\": \"Direct message\",\n \"group direct message\": \"Group DM\",\n \"public channel\": \"Channel\",\n \"private channel\": \"Private channel\",\n};\n\nconst TEAMS_PLACES: Readonly<Record<string, string>> = {\n personal: \"Personal chat\",\n groupchat: \"Group chat\",\n channel: \"Channel\",\n};\n\nfunction factValue(facts: readonly InboundContextField[], key: string): string | null {\n return facts.find((entry) => entry.key === key)?.value ?? null;\n}\n\nfunction slackPlace(facts: readonly InboundContextField[]): string | null {\n const named = /^(#[^\\s(]+)/.exec(factValue(facts, \"slack_channel\") ?? \"\");\n if (named?.[1]) return named[1];\n const type = factValue(facts, \"slack_channel_type\");\n return type ? (SLACK_PLACES[type.toLowerCase()] ?? humanize(type)) : null;\n}\n\nfunction teamsPlace(facts: readonly InboundContextField[]): string | null {\n const type = factValue(facts, \"teams_conversation_type\") ?? factValue(facts, \"conversation_type\");\n return type ? (TEAMS_PLACES[type.toLowerCase()] ?? humanize(type)) : null;\n}\n\nfunction groupTextPlace(facts: readonly InboundContextField[], channel: string): string | null {\n const isGroup =\n factValue(facts, \"source\") === GROUP_TEXT_SOURCE ||\n channel === \"sms_group\" ||\n channel === \"agent_sms_group\" ||\n factValue(facts, \"group_thread_id\") !== null;\n if (!isGroup) return null;\n const title = factValue(facts, \"group_title\");\n return title ? `Group text · ${title}` : \"Group text\";\n}\n\n/**\n * A user turn read as a channel message, or `null` when it carries no\n * `inbound_channel` envelope in `custom` (an ordinary turn, tags a person\n * typed included). The facts are the routing block's, or the envelope's own\n * when the text starts with none.\n */\nexport function readInboundChannelMessage({\n custom,\n text,\n}: {\n custom: Record<string, unknown> | null | undefined;\n text: string;\n}): InboundChannelMessage | null {\n const envelope = custom?.[INBOUND_CHANNEL_KEY];\n if (!isRecord(envelope)) return null;\n const channel = readString(envelope, \"channel\");\n if (channel === null) return null;\n\n const details: InboundContextField[] = [];\n if (isRecord(envelope.details)) {\n for (const [key, value] of Object.entries(envelope.details)) {\n if (HIDDEN_DETAILS.has(key)) continue;\n const scalar = readScalar(value);\n if (scalar !== null) details.push(field(key, scalar));\n }\n }\n const senderName = readString(envelope, \"sender_name\");\n const senderHandle = readString(envelope, \"sender_handle\");\n const subject = readString(envelope, \"subject\");\n\n const context = leadingInboundContext(text);\n const facts: InboundContextField[] = context\n ? contextFields(context.lines)\n : [\n field(\"source\", inboundChannelLabel(channel)),\n ...(senderName ? [field(\"sender_name\", senderName)] : []),\n ...(senderHandle ? [field(\"sender_handle\", senderHandle)] : []),\n ...(subject ? [field(\"subject\", subject)] : []),\n ...details,\n ];\n\n const kind = inboundChannelKind(channel);\n // The block and the envelope's details name the same places; either serves.\n const known = [...facts, ...details];\n let place: string | null = null;\n if (kind === InboundChannelKind.SLACK) place = slackPlace(known);\n else if (kind === InboundChannelKind.TEAMS) place = teamsPlace(known);\n else if (kind === InboundChannelKind.SMS) place = groupTextPlace(known, channel);\n\n return {\n origin: {\n kind,\n channel,\n label: inboundChannelLabel(channel),\n senderName,\n senderHandle:\n senderHandle !== null && kind === InboundChannelKind.SMS\n ? formatInboundPhoneNumber(senderHandle)\n : senderHandle,\n place,\n subject,\n },\n facts,\n rawContext: context?.raw ?? null,\n quoted: context ? splitInboundBody(context).quoted : null,\n };\n}\n"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ const require$$0 = require("react");
7
7
  const useAthenaRunLive = require("../../chat/use-athena-run-live.cjs");
8
8
  const chatMessageBody = require("../rich-text/chat-message-body.cjs");
9
9
  const answerLabel = require("./answer-label.cjs");
10
+ const inboundChannelMessage = require("./inbound-channel-message.cjs");
10
11
  const messageActionsConfig = require("./message-actions-config.cjs");
11
12
  const messageImages = require("./message-images.cjs");
12
13
  const runtimeMessage = require("./runtime-message.cjs");
@@ -18,6 +19,7 @@ const streamingMarkdown = require("../../agent-runtime/src/streaming-markdown.cj
18
19
  const githubWakeEvent = require("../../agent-runtime/src/github-wake-event.cjs");
19
20
  const subscriptionWakeEvent = require("../../agent-runtime/src/subscription-wake-event.cjs");
20
21
  const voicePresentation = require("../../agent-runtime/src/voice-presentation.cjs");
22
+ const inboundChannel = require("../../agent-runtime/src/inbound-channel.cjs");
21
23
  const composerMarkdown = require("../../agent-runtime/src/composer-markdown.cjs");
22
24
  const runtimeMessages = require("../../agent-runtime/src/runtime-messages.cjs");
23
25
  const useDeepAgentChatOutbox = require("../../agent-runtime/src/use-deep-agent-chat-outbox.cjs");
@@ -84,19 +86,17 @@ const MarkdownText = () => {
84
86
  return /* @__PURE__ */ jsxRuntime.jsx(chatMessageBody.ChatMessageBody, { text, isRunning });
85
87
  };
86
88
  const UserMessageText = ({ text }) => {
87
- const composerMarkdown$1 = react.useAuiState(
88
- (s) => {
89
- var _a, _b;
90
- return (_b = (_a = s.message.metadata) == null ? void 0 : _a.custom) == null ? void 0 : _b[composerMarkdown.DEEP_AGENT_COMPOSER_MARKDOWN_KEY];
91
- }
92
- );
93
- const shown = require$$0.useMemo(
94
- () => composerMarkdown.readDeepAgentComposerMarkdown({
95
- metadata: { [composerMarkdown.DEEP_AGENT_COMPOSER_MARKDOWN_KEY]: composerMarkdown$1 },
96
- text
97
- }) ?? text,
98
- [composerMarkdown$1, text]
99
- );
89
+ const custom = react.useAuiState((s) => {
90
+ var _a;
91
+ return (_a = s.message.metadata) == null ? void 0 : _a.custom;
92
+ });
93
+ const shown = require$$0.useMemo(() => {
94
+ const body = inboundChannel.inboundMessageBody({ custom, text });
95
+ return composerMarkdown.readDeepAgentComposerMarkdown({
96
+ metadata: { [composerMarkdown.DEEP_AGENT_COMPOSER_MARKDOWN_KEY]: custom == null ? void 0 : custom[composerMarkdown.DEEP_AGENT_COMPOSER_MARKDOWN_KEY] },
97
+ text: body
98
+ }) ?? body;
99
+ }, [custom, text]);
100
100
  return /* @__PURE__ */ jsxRuntime.jsx(chatMessageBody.ChatMessageBody, { text: shown, typed: true });
101
101
  };
102
102
  const USER_MESSAGE_PART_COMPONENTS = { Text: UserMessageText, Image: messageImages.MessageImagePart };
@@ -140,7 +140,11 @@ const UserMessage = () => {
140
140
  "data-message-role": "user",
141
141
  className: "group/user-message mb-3 flex justify-end",
142
142
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 max-w-[85%] flex-col items-end gap-1", children: [
143
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 max-w-full rounded-2xl rounded-br-md bg-muted px-3.5 py-2 text-sm text-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(messageImages.MessageImageGallery, { children: /* @__PURE__ */ jsxRuntime.jsx(react.MessagePrimitive.Content, { components: USER_MESSAGE_PART_COMPONENTS }) }) }),
143
+ /* @__PURE__ */ jsxRuntime.jsx(inboundChannelMessage.InboundChannelLine, {}),
144
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 max-w-full rounded-2xl rounded-br-md bg-muted px-3.5 py-2 text-sm text-foreground", children: [
145
+ /* @__PURE__ */ jsxRuntime.jsx(inboundChannelMessage.InboundChannelSubject, {}),
146
+ /* @__PURE__ */ jsxRuntime.jsx(messageImages.MessageImageGallery, { children: /* @__PURE__ */ jsxRuntime.jsx(react.MessagePrimitive.Content, { components: USER_MESSAGE_PART_COMPONENTS }) })
147
+ ] }),
144
148
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-6 items-center gap-1", children: [
145
149
  /* @__PURE__ */ jsxRuntime.jsx(BranchPicker, {}),
146
150
  /* @__PURE__ */ jsxRuntime.jsx(UserActionBar, {})