@stigmer/react 3.7.0 → 3.8.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 (177) hide show
  1. package/channel/AgentChannelsPanel.d.ts.map +1 -1
  2. package/channel/AgentChannelsPanel.js +21 -2
  3. package/channel/AgentChannelsPanel.js.map +1 -1
  4. package/channel/ChannelConversationsDialog.d.ts +12 -4
  5. package/channel/ChannelConversationsDialog.d.ts.map +1 -1
  6. package/channel/ChannelConversationsDialog.js +18 -24
  7. package/channel/ChannelConversationsDialog.js.map +1 -1
  8. package/channel/providers.d.ts +23 -0
  9. package/channel/providers.d.ts.map +1 -1
  10. package/channel/providers.js +4 -0
  11. package/channel/providers.js.map +1 -1
  12. package/channel/useSaveAgentChannel.d.ts +5 -4
  13. package/channel/useSaveAgentChannel.d.ts.map +1 -1
  14. package/channel/useSaveAgentChannel.js +18 -4
  15. package/channel/useSaveAgentChannel.js.map +1 -1
  16. package/composer/ComposerToolbar.d.ts +1 -1
  17. package/composer/ComposerToolbar.d.ts.map +1 -1
  18. package/composer/SessionComposer.d.ts +1 -1
  19. package/composer/SessionComposer.js +1 -1
  20. package/composer/SessionComposer.js.map +1 -1
  21. package/conversation/ConversationAttentionBanner.d.ts +22 -0
  22. package/conversation/ConversationAttentionBanner.d.ts.map +1 -0
  23. package/conversation/ConversationAttentionBanner.js +25 -0
  24. package/conversation/ConversationAttentionBanner.js.map +1 -0
  25. package/conversation/ConversationComposer.d.ts +41 -0
  26. package/conversation/ConversationComposer.d.ts.map +1 -0
  27. package/conversation/ConversationComposer.js +57 -0
  28. package/conversation/ConversationComposer.js.map +1 -0
  29. package/conversation/ConversationControlBanner.d.ts +52 -0
  30. package/conversation/ConversationControlBanner.d.ts.map +1 -0
  31. package/conversation/ConversationControlBanner.js +59 -0
  32. package/conversation/ConversationControlBanner.js.map +1 -0
  33. package/conversation/ConversationListPane.d.ts +68 -0
  34. package/conversation/ConversationListPane.d.ts.map +1 -0
  35. package/conversation/ConversationListPane.js +120 -0
  36. package/conversation/ConversationListPane.js.map +1 -0
  37. package/conversation/ConversationTimelineView.d.ts +46 -0
  38. package/conversation/ConversationTimelineView.d.ts.map +1 -0
  39. package/conversation/ConversationTimelineView.js +166 -0
  40. package/conversation/ConversationTimelineView.js.map +1 -0
  41. package/conversation/ConversationsWorkbench.d.ts +63 -0
  42. package/conversation/ConversationsWorkbench.d.ts.map +1 -0
  43. package/conversation/ConversationsWorkbench.js +165 -0
  44. package/conversation/ConversationsWorkbench.js.map +1 -0
  45. package/conversation/conversationPresentation.d.ts +137 -0
  46. package/conversation/conversationPresentation.d.ts.map +1 -0
  47. package/conversation/conversationPresentation.js +230 -0
  48. package/conversation/conversationPresentation.js.map +1 -0
  49. package/conversation/index.d.ts +26 -0
  50. package/conversation/index.d.ts.map +1 -0
  51. package/conversation/index.js +14 -0
  52. package/conversation/index.js.map +1 -0
  53. package/conversation/polling.d.ts +39 -0
  54. package/conversation/polling.d.ts.map +1 -0
  55. package/conversation/polling.js +39 -0
  56. package/conversation/polling.js.map +1 -0
  57. package/conversation/useConversation.d.ts +61 -0
  58. package/conversation/useConversation.d.ts.map +1 -0
  59. package/conversation/useConversation.js +153 -0
  60. package/conversation/useConversation.js.map +1 -0
  61. package/conversation/useConversationList.d.ts +98 -0
  62. package/conversation/useConversationList.d.ts.map +1 -0
  63. package/conversation/useConversationList.js +200 -0
  64. package/conversation/useConversationList.js.map +1 -0
  65. package/conversation/useConversationParticipation.d.ts +63 -0
  66. package/conversation/useConversationParticipation.d.ts.map +1 -0
  67. package/conversation/useConversationParticipation.js +84 -0
  68. package/conversation/useConversationParticipation.js.map +1 -0
  69. package/conversation/useConversationTimeline.d.ts +71 -0
  70. package/conversation/useConversationTimeline.d.ts.map +1 -0
  71. package/conversation/useConversationTimeline.js +152 -0
  72. package/conversation/useConversationTimeline.js.map +1 -0
  73. package/conversation/useConversationsWantsHumanCount.d.ts +42 -0
  74. package/conversation/useConversationsWantsHumanCount.d.ts.map +1 -0
  75. package/conversation/useConversationsWantsHumanCount.js +40 -0
  76. package/conversation/useConversationsWantsHumanCount.js.map +1 -0
  77. package/index.d.ts +4 -0
  78. package/index.d.ts.map +1 -1
  79. package/index.js +8 -0
  80. package/index.js.map +1 -1
  81. package/internal/tooltip.d.ts.map +1 -1
  82. package/internal/tooltip.js +7 -1
  83. package/internal/tooltip.js.map +1 -1
  84. package/internal/useAutoScroll.d.ts +25 -4
  85. package/internal/useAutoScroll.d.ts.map +1 -1
  86. package/internal/useAutoScroll.js +55 -15
  87. package/internal/useAutoScroll.js.map +1 -1
  88. package/internal/useFetch.d.ts.map +1 -1
  89. package/internal/useFetch.js +14 -4
  90. package/internal/useFetch.js.map +1 -1
  91. package/models/ModelSelector.d.ts +24 -5
  92. package/models/ModelSelector.d.ts.map +1 -1
  93. package/models/ModelSelector.js +31 -9
  94. package/models/ModelSelector.js.map +1 -1
  95. package/models/registry.d.ts +3 -2
  96. package/models/registry.d.ts.map +1 -1
  97. package/models/registry.js.map +1 -1
  98. package/models/service-tier.d.ts +1 -1
  99. package/models/service-tier.d.ts.map +1 -1
  100. package/models/service-tier.js +1 -1
  101. package/models/service-tier.js.map +1 -1
  102. package/package.json +4 -4
  103. package/schedule/ScheduleDetailView.js +1 -1
  104. package/schedule/ScheduleDetailView.js.map +1 -1
  105. package/schedule/ScheduleForm.d.ts.map +1 -1
  106. package/schedule/ScheduleForm.js +7 -6
  107. package/schedule/ScheduleForm.js.map +1 -1
  108. package/sharing/useSaveAgentShare.d.ts +10 -1
  109. package/sharing/useSaveAgentShare.d.ts.map +1 -1
  110. package/sharing/useSaveAgentShare.js +9 -0
  111. package/sharing/useSaveAgentShare.js.map +1 -1
  112. package/sidebar/WorkspaceSidebar.d.ts +12 -4
  113. package/sidebar/WorkspaceSidebar.d.ts.map +1 -1
  114. package/sidebar/WorkspaceSidebar.js +19 -7
  115. package/sidebar/WorkspaceSidebar.js.map +1 -1
  116. package/src/channel/AgentChannelsPanel.tsx +33 -2
  117. package/src/channel/ChannelConversationsDialog.tsx +22 -25
  118. package/src/channel/__tests__/AgentChannelsPanel.test.tsx +36 -5
  119. package/src/channel/__tests__/ChannelConversationsDialog.test.tsx +38 -1
  120. package/src/channel/__tests__/useSaveAgentChannel.test.tsx +26 -0
  121. package/src/channel/providers.ts +27 -0
  122. package/src/channel/useSaveAgentChannel.ts +18 -4
  123. package/src/composer/ComposerToolbar.tsx +1 -1
  124. package/src/composer/SessionComposer.tsx +2 -2
  125. package/src/composer/__tests__/SessionComposer-serviceTier.test.tsx +4 -3
  126. package/src/conversation/ConversationAttentionBanner.tsx +77 -0
  127. package/src/conversation/ConversationComposer.tsx +156 -0
  128. package/src/conversation/ConversationControlBanner.tsx +193 -0
  129. package/src/conversation/ConversationListPane.tsx +459 -0
  130. package/src/conversation/ConversationTimelineView.tsx +435 -0
  131. package/src/conversation/ConversationsWorkbench.tsx +378 -0
  132. package/src/conversation/__tests__/ConversationAttentionBanner.test.tsx +82 -0
  133. package/src/conversation/__tests__/ConversationComposer.test.tsx +160 -0
  134. package/src/conversation/__tests__/ConversationControlBanner.test.tsx +190 -0
  135. package/src/conversation/__tests__/ConversationListPane.test.tsx +372 -0
  136. package/src/conversation/__tests__/ConversationTimelineView.layout.test.tsx +160 -0
  137. package/src/conversation/__tests__/ConversationTimelineView.test.tsx +405 -0
  138. package/src/conversation/__tests__/ConversationsWorkbench.test.tsx +860 -0
  139. package/src/conversation/__tests__/a11y/conversation-surfaces.a11y.test.tsx +235 -0
  140. package/src/conversation/__tests__/a11y/harness.tsx +100 -0
  141. package/src/conversation/__tests__/conversation-tooltips.layout.test.tsx +167 -0
  142. package/src/conversation/__tests__/conversationPollingComposition.test.tsx +138 -0
  143. package/src/conversation/__tests__/conversationPresentation.test.ts +289 -0
  144. package/src/conversation/__tests__/useConversation.test.tsx +243 -0
  145. package/src/conversation/__tests__/useConversationList.test.tsx +492 -0
  146. package/src/conversation/__tests__/useConversationParticipation.test.tsx +190 -0
  147. package/src/conversation/__tests__/useConversationTimeline.test.tsx +258 -0
  148. package/src/conversation/__tests__/useConversationsWantsHumanCount.test.tsx +102 -0
  149. package/src/conversation/conversationPresentation.ts +305 -0
  150. package/src/conversation/index.ts +80 -0
  151. package/src/conversation/polling.ts +41 -0
  152. package/src/conversation/useConversation.ts +221 -0
  153. package/src/conversation/useConversationList.ts +328 -0
  154. package/src/conversation/useConversationParticipation.ts +178 -0
  155. package/src/conversation/useConversationTimeline.ts +244 -0
  156. package/src/conversation/useConversationsWantsHumanCount.ts +86 -0
  157. package/src/index.ts +67 -0
  158. package/src/internal/__tests__/fake-timer-slices.ts +29 -0
  159. package/src/internal/__tests__/useAutoScroll.layout.test.tsx +143 -0
  160. package/src/internal/__tests__/useAutoScroll.test.tsx +62 -5
  161. package/src/internal/__tests__/useFetch-poll-under-render-pressure.test.tsx +79 -0
  162. package/src/internal/tooltip.tsx +6 -1
  163. package/src/internal/useAutoScroll.ts +66 -20
  164. package/src/internal/useFetch.ts +15 -4
  165. package/src/models/ModelSelector.tsx +81 -49
  166. package/src/models/__tests__/ModelSelector-serviceTier.test.tsx +105 -21
  167. package/src/models/registry.ts +3 -2
  168. package/src/models/service-tier.ts +1 -1
  169. package/src/schedule/ScheduleDetailView.tsx +1 -1
  170. package/src/schedule/ScheduleForm.tsx +7 -6
  171. package/src/schedule/__tests__/scheduleCreation.test.tsx +2 -2
  172. package/src/sharing/__tests__/useSaveAgentShare.test.tsx +55 -1
  173. package/src/sharing/__tests__/useShareToolReadiness.test.tsx +1 -0
  174. package/src/sharing/useSaveAgentShare.ts +19 -1
  175. package/src/sidebar/WorkspaceSidebar.tsx +62 -3
  176. package/src/sidebar/__tests__/WorkspaceSidebar.test.tsx +56 -1
  177. package/styles.css +1 -1
@@ -0,0 +1,22 @@
1
+ import type { ChannelConversation } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
2
+ import type { UseConversationParticipationReturn } from "./useConversationParticipation.js";
3
+ /** Props for {@link ConversationAttentionBanner}. */
4
+ export interface ConversationAttentionBannerProps {
5
+ /** The conversation's fresh participation state. */
6
+ readonly conversation: ChannelConversation;
7
+ /** The participation commands (from `useConversationParticipation`). */
8
+ readonly participation: UseConversationParticipationReturn;
9
+ /** Additional classes for the banner container. */
10
+ readonly className?: string;
11
+ }
12
+ /**
13
+ * The needs-attention banner (channel-conversations DD-008): the
14
+ * escalating agent's reason, verbatim, with the false-alarm dismissal.
15
+ *
16
+ * Dismiss is `clearAttention` — attention clears in place and control
17
+ * never moves (taking over also clears it, structurally, because the
18
+ * human arriving IS the answer to an escalation). Renders nothing while
19
+ * the conversation is unflagged.
20
+ */
21
+ export declare function ConversationAttentionBanner({ conversation, participation, className, }: ConversationAttentionBannerProps): import("react/jsx-runtime").JSX.Element | null;
22
+ //# sourceMappingURL=ConversationAttentionBanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationAttentionBanner.d.ts","sourceRoot":"","sources":["../../src/conversation/ConversationAttentionBanner.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uEAAuE,CAAC;AAEjH,OAAO,KAAK,EAAE,kCAAkC,EAAE,MAAM,mCAAmC,CAAC;AAE5F,qDAAqD;AACrD,MAAM,WAAW,gCAAgC;IAC/C,oDAAoD;IACpD,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,wEAAwE;IACxE,QAAQ,CAAC,aAAa,EAAE,kCAAkC,CAAC;IAC3D,mDAAmD;IACnD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,EAC1C,YAAY,EACZ,aAAa,EACb,SAAS,GACV,EAAE,gCAAgC,kDA4ClC"}
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { TriangleAlert } from "lucide-react";
4
+ import { cn } from "@stigmer/theme";
5
+ import { getUserMessage } from "@stigmer/sdk";
6
+ import { Button } from "../button/Button.js";
7
+ /**
8
+ * The needs-attention banner (channel-conversations DD-008): the
9
+ * escalating agent's reason, verbatim, with the false-alarm dismissal.
10
+ *
11
+ * Dismiss is `clearAttention` — attention clears in place and control
12
+ * never moves (taking over also clears it, structurally, because the
13
+ * human arriving IS the answer to an escalation). Renders nothing while
14
+ * the conversation is unflagged.
15
+ */
16
+ export function ConversationAttentionBanner({ conversation, participation, className, }) {
17
+ if (!conversation.needsAttention)
18
+ return null;
19
+ const { clearAttention, pendingCommands, commandErrors } = participation;
20
+ const error = commandErrors.get("clearAttention");
21
+ return (_jsxs("div", { className: cn("border-b border-border bg-destructive-subtle px-4 py-2", className), role: "status", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("p", { className: "flex min-w-0 items-start gap-1.5 text-sm text-destructive", children: [_jsx(TriangleAlert, { "aria-hidden": "true", className: "mt-0.5 size-4 shrink-0" }), _jsxs("span", { className: "min-w-0 break-words", children: ["The agent asked for a human", conversation.attentionReason ? (_jsxs(_Fragment, { children: [" \u2014 \u201C", conversation.attentionReason, "\u201D"] })) : (".")] })] }), _jsx(Button, { variant: "outline", size: "xs", onClick: () => void clearAttention().catch(() => {
22
+ // Recorded in commandErrors and rendered below.
23
+ }), disabled: pendingCommands.has("clearAttention"), children: pendingCommands.has("clearAttention") ? "Dismissing…" : "Dismiss" })] }), error && (_jsx("p", { className: "mt-1 text-xs text-destructive", children: getUserMessage(error) }))] }));
24
+ }
25
+ //# sourceMappingURL=ConversationAttentionBanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationAttentionBanner.js","sourceRoot":"","sources":["../../src/conversation/ConversationAttentionBanner.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAa7C;;;;;;;;GAQG;AACH,MAAM,UAAU,2BAA2B,CAAC,EAC1C,YAAY,EACZ,aAAa,EACb,SAAS,GACwB;IACjC,IAAI,CAAC,YAAY,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IAE9C,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,GAAG,aAAa,CAAC;IACzE,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAElD,OAAO,CACL,eACE,SAAS,EAAE,EAAE,CACX,wDAAwD,EACxD,SAAS,CACV,EACD,IAAI,EAAC,QAAQ,aAEb,eAAK,SAAS,EAAC,mDAAmD,aAChE,aAAG,SAAS,EAAC,2DAA2D,aACtE,KAAC,aAAa,mBAAa,MAAM,EAAC,SAAS,EAAC,wBAAwB,GAAG,EACvE,gBAAM,SAAS,EAAC,qBAAqB,4CAElC,YAAY,CAAC,eAAe,CAAC,CAAC,CAAC,CAC9B,gDAAa,YAAY,CAAC,eAAe,cAAW,CACrD,CAAC,CAAC,CAAC,CACF,GAAG,CACJ,IACI,IACL,EACJ,KAAC,MAAM,IACL,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,GAAG,EAAE,CACZ,KAAK,cAAc,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;4BAC/B,gDAAgD;wBAClD,CAAC,CAAC,EAEJ,QAAQ,EAAE,eAAe,CAAC,GAAG,CAAC,gBAAgB,CAAC,YAE9C,eAAe,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,GAC3D,IACL,EACL,KAAK,IAAI,CACR,YAAG,SAAS,EAAC,+BAA+B,YAAE,cAAc,CAAC,KAAK,CAAC,GAAK,CACzE,IACG,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { SendChannelMessageOutput } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/message_io_pb";
2
+ /** Props for {@link ConversationComposer}. */
3
+ export interface ConversationComposerProps {
4
+ /** Send a staff reply (from `useConversationParticipation().reply`). */
5
+ readonly onSend: (text: string) => Promise<SendChannelMessageOutput>;
6
+ /** `true` while a reply is in flight. */
7
+ readonly isSending: boolean;
8
+ /**
9
+ * Why replying is unavailable, when it is — rendered in place of the
10
+ * input (a senderless provider, or a conversation the customer has
11
+ * not started yet). `null` enables the composer.
12
+ */
13
+ readonly disabledReason: string | null;
14
+ /**
15
+ * A pre-send forecast that ANNOTATES the enabled input — the
16
+ * `disabledReason` pattern's sibling with the opposite contract
17
+ * (channel-conversations DD-014 D-e): a closed service window is a
18
+ * forecast, not a structural block, so the input stays usable and the
19
+ * send engines remain the authority. Rendered above the input and
20
+ * associated via `aria-describedby` (never a live region: the
21
+ * advisory is state a reader meets on open, not an event). Compute it
22
+ * with `serviceWindowOf`; `null` or omitted renders nothing.
23
+ */
24
+ readonly advisory?: string | null;
25
+ /** Additional classes for the composer container. */
26
+ readonly className?: string;
27
+ }
28
+ /**
29
+ * The staff reply composer. Sending is an implicit takeover — the agent
30
+ * goes quiet the moment a staff reply lands, before the send itself —
31
+ * so this composer is only ever rendered alongside the control banner
32
+ * that shows that state change.
33
+ *
34
+ * Outcome honesty: `refused` keeps the draft (the words never left) and
35
+ * renders the server's refusal detail verbatim; `queued` clears it and
36
+ * says the platform is retrying; a thrown failure keeps the draft and
37
+ * renders the error. The timeline shows the delivered item via its own
38
+ * read — this component never fabricates one.
39
+ */
40
+ export declare function ConversationComposer({ onSend, isSending, disabledReason, advisory, className, }: ConversationComposerProps): import("react/jsx-runtime").JSX.Element;
41
+ //# sourceMappingURL=ConversationComposer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationComposer.d.ts","sourceRoot":"","sources":["../../src/conversation/ConversationComposer.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kEAAkE,CAAC;AAKjH,8CAA8C;AAC9C,MAAM,WAAW,yBAAyB;IACxC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACrE,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC;;;;;;;;;OASG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,qDAAqD;IACrD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,MAAM,EACN,SAAS,EACT,cAAc,EACd,QAAQ,EACR,SAAS,GACV,EAAE,yBAAyB,2CAkG3B"}
@@ -0,0 +1,57 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId, useState } from "react";
4
+ import { Send, TriangleAlert } from "lucide-react";
5
+ import { cn } from "@stigmer/theme";
6
+ import { getUserMessage } from "@stigmer/sdk";
7
+ import { ChannelSendOutcome } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/message_io_pb";
8
+ import { Button } from "../button/Button.js";
9
+ import { SpinnerIcon } from "../composer/icons.js";
10
+ import { useComposer } from "../composer/useComposer.js";
11
+ /**
12
+ * The staff reply composer. Sending is an implicit takeover — the agent
13
+ * goes quiet the moment a staff reply lands, before the send itself —
14
+ * so this composer is only ever rendered alongside the control banner
15
+ * that shows that state change.
16
+ *
17
+ * Outcome honesty: `refused` keeps the draft (the words never left) and
18
+ * renders the server's refusal detail verbatim; `queued` clears it and
19
+ * says the platform is retrying; a thrown failure keeps the draft and
20
+ * renders the error. The timeline shows the delivered item via its own
21
+ * read — this component never fabricates one.
22
+ */
23
+ export function ConversationComposer({ onSend, isSending, disabledReason, advisory, className, }) {
24
+ const advisoryId = useId();
25
+ const hasAdvisory = advisory != null && advisory !== "";
26
+ const [notice, setNotice] = useState(null);
27
+ const composer = useComposer({
28
+ onSubmit: (message) => {
29
+ setNotice(null);
30
+ onSend(message).then((output) => {
31
+ if (output.outcome === ChannelSendOutcome.refused) {
32
+ // The words never left; keep them so the user can adjust.
33
+ composer.setMessage(message);
34
+ setNotice({
35
+ kind: "refused",
36
+ text: output.detail || "The provider refused this message.",
37
+ });
38
+ }
39
+ else if (output.outcome === ChannelSendOutcome.queued) {
40
+ setNotice({
41
+ kind: "queued",
42
+ text: "Delivery hit a transient issue — the platform is retrying in the background.",
43
+ });
44
+ }
45
+ }, (err) => {
46
+ composer.setMessage(message);
47
+ setNotice({ kind: "error", text: getUserMessage(err) });
48
+ });
49
+ },
50
+ disabled: disabledReason !== null || isSending,
51
+ });
52
+ if (disabledReason !== null) {
53
+ return (_jsx("div", { className: cn("border-t border-border px-4 py-3", className), children: _jsx("p", { className: "text-center text-xs text-muted-foreground", children: disabledReason }) }));
54
+ }
55
+ return (_jsxs("div", { className: cn("border-t border-border px-4 py-3", className), children: [hasAdvisory && (_jsxs("p", { id: advisoryId, className: "mb-2 flex items-start gap-1.5 rounded-md bg-status-degraded-subtle px-2.5 py-1.5 text-xs text-status-degraded", children: [_jsx(TriangleAlert, { "aria-hidden": "true", className: "mt-0.5 size-3.5 shrink-0" }), _jsx("span", { className: "min-w-0", children: advisory })] })), notice && (_jsx("p", { className: cn("mb-2 text-xs", notice.kind === "queued" ? "text-muted-foreground" : "text-destructive"), role: "status", children: notice.text })), _jsxs("div", { className: "flex items-end gap-2", children: [_jsx("textarea", { ...composer.textareaProps, rows: 1, placeholder: "Reply as your business\u2026", "aria-label": "Reply to the customer", "aria-describedby": hasAdvisory ? advisoryId : undefined, className: cn("min-h-9 flex-1 resize-none rounded-md border border-border bg-background", "px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground-faint", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring") }), _jsxs(Button, { variant: "primary", size: "sm", onClick: composer.submit, disabled: !composer.canSubmit || isSending, "aria-label": "Send reply", children: [isSending ? _jsx(SpinnerIcon, {}) : _jsx(Send, { "aria-hidden": "true", className: "size-4" }), isSending ? "Sending…" : "Send"] })] })] }));
56
+ }
57
+ //# sourceMappingURL=ConversationComposer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationComposer.js","sourceRoot":"","sources":["../../src/conversation/ConversationComposer.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import { type ChannelConversation } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
2
+ import type { UseConversationParticipationReturn } from "./useConversationParticipation.js";
3
+ /** Props for {@link ConversationControlBanner}. */
4
+ export interface ConversationControlBannerProps {
5
+ /** The conversation's fresh participation state. */
6
+ readonly conversation: ChannelConversation;
7
+ /** The participation commands (from `useConversationParticipation`). */
8
+ readonly participation: UseConversationParticipationReturn;
9
+ /**
10
+ * `true` when the customer's last message has not been answered —
11
+ * the conversation row's server-derived `awaiting_reply` fact, which
12
+ * is bounce-aware: a staff reply the provider later failed does not
13
+ * count as an answer (channel-conversations F-28/DD-015). Arms the
14
+ * handback confirm guard (DD-007 D-e): handing back runs NO turn, so
15
+ * the agent stays quiet until the customer next speaks, and the
16
+ * unanswered state must be unmissable. Never derive this from
17
+ * timeline authorship — the send attempt's presence on the timeline
18
+ * says nothing about whether the customer received it.
19
+ */
20
+ readonly unansweredCustomer: boolean;
21
+ /**
22
+ * Whether the channel's provider has a staff send lane. Senderless
23
+ * providers disable takeover with the reason — a takeover there would
24
+ * silence the agent while no human reply could reach the customer,
25
+ * which is exactly what the server refuses.
26
+ */
27
+ readonly supportsStaffReplies: boolean;
28
+ /**
29
+ * The signed-in staff member's identity account id, when the host
30
+ * knows it — lets the banner attribute a human hold: "You have this
31
+ * conversation" when it matches, "A teammate has this conversation"
32
+ * when it differs. When omitted the banner only states that a human
33
+ * holds it — the holder may be the viewer themself, so "a teammate"
34
+ * would be a guess. Optional: the SDK never assumes a host's auth
35
+ * state.
36
+ */
37
+ readonly currentIdentityAccountId?: string;
38
+ /** Additional classes for the banner container. */
39
+ readonly className?: string;
40
+ }
41
+ /**
42
+ * Who may speak to the customer, and the controls that move it: take
43
+ * over (the agent goes quiet) and hand back (the agent resumes, with
44
+ * the context it missed injected on its next turn).
45
+ *
46
+ * Truth rules: state renders from the conversation row the server
47
+ * answered — a takeover that lost its race renders the WINNER's hold,
48
+ * because that is what happened. Failures render verbatim beside the
49
+ * control that caused them.
50
+ */
51
+ export declare function ConversationControlBanner({ conversation, participation, unansweredCustomer, supportsStaffReplies, currentIdentityAccountId, className, }: ConversationControlBannerProps): import("react/jsx-runtime").JSX.Element;
52
+ //# sourceMappingURL=ConversationControlBanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationControlBanner.d.ts","sourceRoot":"","sources":["../../src/conversation/ConversationControlBanner.tsx"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,uEAAuE,CAAC;AAE/E,OAAO,KAAK,EAAE,kCAAkC,EAAE,MAAM,mCAAmC,CAAC;AAE5F,mDAAmD;AACnD,MAAM,WAAW,8BAA8B;IAC7C,oDAAoD;IACpD,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,wEAAwE;IACxE,QAAQ,CAAC,aAAa,EAAE,kCAAkC,CAAC;IAC3D;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACvC;;;;;;;;OAQG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAC3C,mDAAmD;IACnD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACxB,SAAS,GACV,EAAE,8BAA8B,2CA2HhC"}
@@ -0,0 +1,59 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { Bot, User } from "lucide-react";
5
+ import { cn } from "@stigmer/theme";
6
+ import { getUserMessage } from "@stigmer/sdk";
7
+ import { ConversationControl, } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
8
+ import { Button } from "../button/Button.js";
9
+ /**
10
+ * Who may speak to the customer, and the controls that move it: take
11
+ * over (the agent goes quiet) and hand back (the agent resumes, with
12
+ * the context it missed injected on its next turn).
13
+ *
14
+ * Truth rules: state renders from the conversation row the server
15
+ * answered — a takeover that lost its race renders the WINNER's hold,
16
+ * because that is what happened. Failures render verbatim beside the
17
+ * control that caused them.
18
+ */
19
+ export function ConversationControlBanner({ conversation, participation, unansweredCustomer, supportsStaffReplies, currentIdentityAccountId, className, }) {
20
+ const [confirmingHandBack, setConfirmingHandBack] = useState(false);
21
+ const humanHeld = conversation.control === ConversationControl.control_human;
22
+ const heldByMe = humanHeld &&
23
+ currentIdentityAccountId !== undefined &&
24
+ conversation.controlledBy === currentIdentityAccountId;
25
+ const { takeOver, handBack, pendingCommands, commandErrors } = participation;
26
+ const error = commandErrors.get("takeOver") ?? commandErrors.get("handBack");
27
+ const requestHandBack = () => {
28
+ if (unansweredCustomer) {
29
+ setConfirmingHandBack(true);
30
+ return;
31
+ }
32
+ void handBack().catch(() => {
33
+ // Recorded in commandErrors and rendered below.
34
+ });
35
+ };
36
+ return (_jsxs("div", { className: cn("border-b border-border px-4 py-2", humanHeld ? "bg-primary-subtle" : "bg-muted-faint", className), children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsx("p", { className: "flex items-center gap-1.5 text-sm text-foreground", children: humanHeld ? (_jsxs(_Fragment, { children: [_jsx(User, { "aria-hidden": "true", className: "size-4 text-muted-foreground" }), heldByMe
37
+ ? "You have this conversation — the agent is quiet until you hand it back."
38
+ : currentIdentityAccountId !== undefined
39
+ ? "A teammate has this conversation — the agent is quiet until handback."
40
+ : // Without the host's identity the holder may be the
41
+ // viewer themself — claiming "a teammate" would be a
42
+ // guess. State only what the row proves: a human
43
+ // holds it (channel-conversations F-01).
44
+ "This conversation is with a human — the agent is quiet until handback."] })) : (_jsxs(_Fragment, { children: [_jsx(Bot, { "aria-hidden": "true", className: "size-4 text-muted-foreground" }), "The agent is serving this conversation."] })) }), confirmingHandBack ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-xs text-destructive", children: "The customer's last message has no reply \u2014 the agent stays quiet until they write again." }), _jsx(Button, { variant: "outline", size: "xs", onClick: () => {
45
+ setConfirmingHandBack(false);
46
+ void handBack().catch(() => {
47
+ // Recorded in commandErrors and rendered below.
48
+ });
49
+ }, disabled: pendingCommands.has("handBack"), children: "Hand back anyway" }), _jsx(Button, { variant: "primary", size: "xs", onClick: () => setConfirmingHandBack(false), children: "Keep the conversation" })] })) : humanHeld ? (_jsx(Button, { variant: "outline", size: "xs", onClick: requestHandBack, disabled: pendingCommands.has("handBack"), children: pendingCommands.has("handBack") ? "Handing back…" : "Hand back to agent" })) : (_jsxs("div", { className: "flex items-center gap-2", children: [!supportsStaffReplies && (
50
+ // Visible text, never a native title (F-18): a disabled
51
+ // Button swallows hover (disabled:pointer-events-none) and
52
+ // leaves the tab order, so a title on it is unreachable by
53
+ // every input method. Kept short — the composer's own
54
+ // notice already explains the missing staff reply lane.
55
+ _jsx("span", { className: "text-xs text-muted-foreground", children: "This channel's provider has no staff send lane yet \u2014 a takeover would silence the agent with no way to reply." })), _jsx(Button, { variant: "outline", size: "xs", onClick: () => void takeOver().catch(() => {
56
+ // Recorded in commandErrors and rendered below.
57
+ }), disabled: !supportsStaffReplies || pendingCommands.has("takeOver"), children: pendingCommands.has("takeOver") ? "Taking over…" : "Take over" })] }))] }), error && (_jsx("p", { className: "mt-1 text-xs text-destructive", children: getUserMessage(error) }))] }));
58
+ }
59
+ //# sourceMappingURL=ConversationControlBanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationControlBanner.js","sourceRoot":"","sources":["../../src/conversation/ConversationControlBanner.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import type { AgentChannel } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
2
+ import { ChannelConversationListFilter, type ChannelConversation } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
3
+ /** The (channel, key) pair identifying one conversation. */
4
+ export interface ConversationIdentity {
5
+ readonly agentChannelId: string;
6
+ readonly conversationKey: string;
7
+ }
8
+ /** Props for {@link ConversationListPane}. */
9
+ export interface ConversationListPaneProps {
10
+ /** Conversations, newest activity first (from `useConversationList`). */
11
+ readonly conversations: readonly ChannelConversation[];
12
+ /** `true` only during the first load. */
13
+ readonly isLoading: boolean;
14
+ /** Error from the list read, or `null`. */
15
+ readonly error: Error | null;
16
+ /** `true` while more conversations exist beyond what is loaded. */
17
+ readonly hasMore: boolean;
18
+ /** Load the next page. */
19
+ readonly loadMore: () => void;
20
+ /** `true` while the next page is in flight. */
21
+ readonly isLoadingMore: boolean;
22
+ /**
23
+ * The org's channels the caller can view (from
24
+ * `useOrgAgentChannelList`) — the channel filter's options, each
25
+ * row's provider identity, and the honest empty-state distinction
26
+ * (no channel access vs no conversations yet).
27
+ */
28
+ readonly channels: readonly AgentChannel[];
29
+ /** Current channel filter; empty string means all channels. */
30
+ readonly channelFilter: string;
31
+ /** Called when the user picks a channel filter. */
32
+ readonly onChannelFilterChange: (agentChannelId: string) => void;
33
+ /**
34
+ * The server-evaluated predicate filter currently applied to the list
35
+ * (DD-011 D-g — the same predicate the nav badge counts, so the
36
+ * filtered list always matches the number that opened it). Defaults
37
+ * to unspecified (all conversations).
38
+ */
39
+ readonly filter?: ChannelConversationListFilter;
40
+ /**
41
+ * Called when the user picks a predicate filter. The control renders
42
+ * only when this is wired — a dead toggle over an unfiltered list
43
+ * would lie.
44
+ */
45
+ readonly onFilterChange?: (filter: ChannelConversationListFilter) => void;
46
+ /** The open conversation, for row highlighting. `null` when none. */
47
+ readonly selected: ConversationIdentity | null;
48
+ /** Called when the user opens a conversation. */
49
+ readonly onSelect: (conversation: ChannelConversation) => void;
50
+ /** Frozen instant for deterministic hosts (tests, documentation tours). */
51
+ readonly now?: Date;
52
+ /** Additional classes for the pane container. */
53
+ readonly className?: string;
54
+ }
55
+ /**
56
+ * The conversation inbox pane (channel-conversations T04): every
57
+ * conversation across the org's channels, newest activity first, with
58
+ * control, attention, and awaiting-reply indicators.
59
+ *
60
+ * Presentational — pair with `useConversationList` and
61
+ * `useOrgAgentChannelList`, or compose via `ConversationsWorkbench`.
62
+ * The wants-human filter is SERVER-evaluated (DD-011 D-g closed the API
63
+ * gap that T04 D1 cited): a client-side tab over one fetched page would
64
+ * silently lie across pages, so the pane only reports the choice and the
65
+ * list hook sends it to the server.
66
+ */
67
+ export declare function ConversationListPane({ conversations, isLoading, error, hasMore, loadMore, isLoadingMore, channels, channelFilter, onChannelFilterChange, filter, onFilterChange, selected, onSelect, now, className, }: ConversationListPaneProps): import("react/jsx-runtime").JSX.Element;
68
+ //# sourceMappingURL=ConversationListPane.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationListPane.d.ts","sourceRoot":"","sources":["../../src/conversation/ConversationListPane.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2DAA2D,CAAC;AAC9F,OAAO,EACL,6BAA6B,EAE7B,KAAK,mBAAmB,EACzB,MAAM,uEAAuE,CAAC;AAiB/E,4DAA4D;AAC5D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,8CAA8C;AAC9C,MAAM,WAAW,yBAAyB;IACxC,yEAAyE;IACzE,QAAQ,CAAC,aAAa,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACvD,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,0BAA0B;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;IAC9B,+CAA+C;IAC/C,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,+DAA+D;IAC/D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,mDAAmD;IACnD,QAAQ,CAAC,qBAAqB,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,6BAA6B,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,6BAA6B,KAAK,IAAI,CAAC;IAC1E,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC/C,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,CAAC,YAAY,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC/D,2EAA2E;IAC3E,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC;IACpB,iDAAiD;IACjD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,EACb,SAAS,EACT,KAAK,EACL,OAAO,EACP,QAAQ,EACR,aAAa,EACb,QAAQ,EACR,aAAa,EACb,qBAAqB,EACrB,MAAmF,EACnF,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,GAAG,EACH,SAAS,GACV,EAAE,yBAAyB,2CA2H3B"}
@@ -0,0 +1,120 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { memo, useCallback, useRef } from "react";
4
+ import { timestampDate } from "@bufbuild/protobuf/wkt";
5
+ import { MessageSquare, TriangleAlert, User } from "lucide-react";
6
+ import { cn } from "@stigmer/theme";
7
+ import { getUserMessage } from "@stigmer/sdk";
8
+ import { ChannelConversationListFilter, ConversationControl, } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
9
+ import { formatRelativeTime } from "../activity/format-relative-time.js";
10
+ import { channelProviderOf } from "../channel/providers.js";
11
+ import { Button } from "../button/Button.js";
12
+ import { EmptyState } from "../empty-state/EmptyState.js";
13
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "../internal/tooltip.js";
14
+ import { awaitingIndicatorOf, conversationContactOf, conversationLabelOf, } from "./conversationPresentation.js";
15
+ /**
16
+ * The conversation inbox pane (channel-conversations T04): every
17
+ * conversation across the org's channels, newest activity first, with
18
+ * control, attention, and awaiting-reply indicators.
19
+ *
20
+ * Presentational — pair with `useConversationList` and
21
+ * `useOrgAgentChannelList`, or compose via `ConversationsWorkbench`.
22
+ * The wants-human filter is SERVER-evaluated (DD-011 D-g closed the API
23
+ * gap that T04 D1 cited): a client-side tab over one fetched page would
24
+ * silently lie across pages, so the pane only reports the choice and the
25
+ * list hook sends it to the server.
26
+ */
27
+ export function ConversationListPane({ conversations, isLoading, error, hasMore, loadMore, isLoadingMore, channels, channelFilter, onChannelFilterChange, filter = ChannelConversationListFilter.channel_conversation_list_filter_unspecified, onFilterChange, selected, onSelect, now, className, }) {
28
+ const providerById = new Map(channels.map((channel) => [
29
+ channel.metadata?.id ?? "",
30
+ channelProviderOf(channel.spec?.providerConfig?.case),
31
+ ]));
32
+ return (_jsxs("div", { "aria-label": "Conversation list", className: cn("flex min-h-0 flex-col", className), children: [(onFilterChange !== undefined || channels.length > 1) && (_jsxs("div", { className: "flex flex-col gap-2 border-b border-border px-3 py-2", children: [onFilterChange !== undefined && (_jsx(ConversationFilterToggle, { value: filter, onChange: onFilterChange })), channels.length > 1 && (_jsxs(_Fragment, { children: [_jsx("label", { className: "sr-only", htmlFor: "stgm-conversation-channel-filter", children: "Filter by channel" }), _jsxs("select", { id: "stgm-conversation-channel-filter", value: channelFilter, onChange: (e) => onChannelFilterChange(e.target.value), className: cn("w-full rounded-md border border-border bg-background px-2 py-1.5", "text-sm text-foreground", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"), children: [_jsx("option", { value: "", children: "All channels" }), channels.map((channel) => (_jsx("option", { value: channel.metadata?.id, children: channel.metadata?.name || channel.metadata?.slug }, channel.metadata?.id)))] })] }))] })), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-2", children: isLoading ? (_jsx(ListSkeleton, {})) : error ? (_jsx("p", { className: "px-2 py-6 text-center text-sm text-destructive", children: getUserMessage(error) })) : conversations.length === 0 ? (channels.length === 0 ? (_jsx(EmptyState, { variant: "first-use", icon: _jsx(MessageSquare, { className: "size-8" }), title: "No channels to watch", description: "Conversations arrive through an agent's channels. Connect a channel \u2014 or ask a channel owner for access \u2014 and customer conversations appear here." })) : filter === ChannelConversationListFilter.filter_wants_human ? (
33
+ // Its own empty state on purpose: under the filter, "No
34
+ // conversations yet — when a customer messages…" would lie
35
+ // to an org with a full inbox and nothing needing a person.
36
+ _jsx(EmptyState, { variant: "first-use", icon: _jsx(MessageSquare, { className: "size-8" }), title: "Nothing needs a human right now", description: "Conversations appear here when the agent asks for attention, or when a human-held conversation has a customer message awaiting a reply." })) : (_jsx(EmptyState, { variant: "first-use", icon: _jsx(MessageSquare, { className: "size-8" }), title: "No conversations yet", description: "When a customer messages one of your channels, their conversation appears here." }))) : (_jsxs(_Fragment, { children: [_jsx(TooltipProvider, { children: _jsx("ul", { className: "space-y-0.5", children: conversations.map((conversation) => (_jsx(ConversationRow, { conversation: conversation, label: conversationLabelOf(conversation, providerById.get(conversation.agentChannelId)?.id ?? null), contact: conversationContactOf(conversation, providerById.get(conversation.agentChannelId)?.id ?? null), isSelected: selected?.agentChannelId === conversation.agentChannelId &&
37
+ selected?.conversationKey === conversation.conversationKey, onSelect: onSelect, now: now }, `${conversation.agentChannelId}:${conversation.conversationKey}`))) }) }), hasMore && (_jsx("div", { className: "flex justify-center py-2", children: _jsx(Button, { variant: "ghost", size: "xs", onClick: loadMore, disabled: isLoadingMore, children: isLoadingMore ? "Loading…" : "Show more" }) }))] })) })] }));
38
+ }
39
+ const FILTER_OPTIONS = [
40
+ {
41
+ value: ChannelConversationListFilter.channel_conversation_list_filter_unspecified,
42
+ label: "All",
43
+ ariaLabel: "All conversations",
44
+ },
45
+ {
46
+ value: ChannelConversationListFilter.filter_wants_human,
47
+ label: "Needs human",
48
+ ariaLabel: "Conversations where a human action is wanted",
49
+ },
50
+ ];
51
+ /**
52
+ * Segmented control for the server-evaluated list filter. Renders as a
53
+ * WAI-ARIA Radio Group with roving tabindex and arrow-key navigation —
54
+ * the `ScopeToggle` pattern (its second sighting; extract a shared
55
+ * primitive if a third segmented control appears).
56
+ *
57
+ * "Needs human" is exactly the nav badge's predicate (DD-011 D-f/D-g),
58
+ * so the number on the badge and the list this control opens can never
59
+ * disagree.
60
+ */
61
+ function ConversationFilterToggle({ value, onChange, }) {
62
+ const optionRefs = useRef([]);
63
+ const handleSelect = useCallback((next) => {
64
+ if (next !== value)
65
+ onChange(next);
66
+ }, [value, onChange]);
67
+ const handleKeyDown = useCallback((e, index) => {
68
+ let nextIndex = null;
69
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
70
+ e.preventDefault();
71
+ nextIndex = (index + 1) % FILTER_OPTIONS.length;
72
+ }
73
+ else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
74
+ e.preventDefault();
75
+ nextIndex = (index - 1 + FILTER_OPTIONS.length) % FILTER_OPTIONS.length;
76
+ }
77
+ if (nextIndex !== null) {
78
+ optionRefs.current[nextIndex]?.focus();
79
+ handleSelect(FILTER_OPTIONS[nextIndex].value);
80
+ }
81
+ }, [handleSelect]);
82
+ return (_jsx("div", { role: "radiogroup", "aria-label": "Conversation filter", className: "inline-flex self-start rounded-md bg-muted p-0.5", children: FILTER_OPTIONS.map((option, index) => {
83
+ const isSelected = value === option.value;
84
+ return (_jsx("button", { ref: (el) => {
85
+ optionRefs.current[index] = el;
86
+ }, type: "button", role: "radio", "aria-checked": isSelected, "aria-label": option.ariaLabel, tabIndex: isSelected ? 0 : -1, onClick: () => handleSelect(option.value), onKeyDown: (e) => handleKeyDown(e, index), className: cn("inline-flex cursor-pointer items-center rounded-sm px-2 py-1 text-xs font-medium transition-colors", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", isSelected
87
+ ? "bg-background text-foreground shadow-sm"
88
+ : "text-muted-foreground hover:text-foreground"), children: option.label }, option.value));
89
+ }) }));
90
+ }
91
+ const ConversationRow = memo(function ConversationRow({ conversation, label, contact, isSelected, onSelect, now, }) {
92
+ const humanHeld = conversation.control === ConversationControl.control_human;
93
+ const awaiting = awaitingIndicatorOf(conversation);
94
+ const lastActivity = conversation.lastActivityAt
95
+ ? timestampDate(conversation.lastActivityAt)
96
+ : null;
97
+ return (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => onSelect(conversation), "aria-current": isSelected ? "true" : undefined, className: cn("flex w-full items-center gap-2.5 rounded-md px-2.5 py-2 text-left", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", isSelected ? "bg-primary-subtle" : "hover:bg-accent"), children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("p", { className: "truncate text-sm font-medium text-foreground", children: label }), conversation.needsAttention && (
98
+ // The reason rides the house tooltip (F-18). The trigger
99
+ // renders as a span — it sits INSIDE the row button, so a
100
+ // default (button) trigger would nest buttons — and stays
101
+ // out of the tab order; the sr-only text remains the
102
+ // accessible name. The full reason is also always visible
103
+ // in the open conversation's attention banner.
104
+ _jsxs(Tooltip, { children: [_jsxs(TooltipTrigger, { render: _jsx("span", { className: "shrink-0" }), children: [_jsx(TriangleAlert, { "aria-hidden": "true", className: "size-3.5 text-destructive" }), _jsx("span", { className: "sr-only", children: conversation.attentionReason
105
+ ? `Needs attention: ${conversation.attentionReason}`
106
+ : "Needs attention" })] }), _jsx(TooltipContent, { side: "top", children: conversation.attentionReason || "Needs attention" })] }))] }), contact !== null && (_jsx("p", { className: "mt-0.5 truncate text-xs text-muted-foreground-faint", children: contact })), humanHeld && (_jsxs("p", { className: "mt-0.5 flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(User, { "aria-hidden": "true", className: "size-3" }), "Human has the conversation"] }))] }), (lastActivity !== null || awaiting !== null) && (_jsxs("span", { className: "flex shrink-0 flex-col items-end gap-1", children: [lastActivity && (_jsx("span", { className: "text-xs text-muted-foreground-faint", children: formatRelativeTime(lastActivity, now) })), awaiting && (
107
+ // The WhatsApp inbox convention: an awaiting conversation
108
+ // carries a dot under its timestamp. Strength maps the
109
+ // holder (DD-011 D-a): strong when a human holds it — the
110
+ // agent will not answer, a person must — muted when the
111
+ // agent does. No tooltip and no tab stop (the F-18
112
+ // discipline); the sr-only text is the accessible meaning.
113
+ _jsxs("span", { className: "flex", children: [_jsx("span", { "aria-hidden": "true", className: cn("size-2 rounded-full", awaiting === "strong" ? "bg-primary" : "bg-muted-foreground") }), _jsx("span", { className: "sr-only", children: awaiting === "strong"
114
+ ? "Customer awaiting reply — the conversation is human-held"
115
+ : "Customer awaiting reply" })] }))] }))] }) }));
116
+ });
117
+ function ListSkeleton() {
118
+ return (_jsx("div", { className: "space-y-1 p-1", "aria-hidden": "true", children: [1, 2, 3, 4].map((i) => (_jsx("div", { className: "h-11 animate-pulse rounded-md bg-muted-faint" }, i))) }));
119
+ }
120
+ //# sourceMappingURL=ConversationListPane.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationListPane.js","sourceRoot":"","sources":["../../src/conversation/ConversationListPane.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { ConversationTimelineItem } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
2
+ import { type ChannelProviderId } from "../channel/providers.js";
3
+ /** Props for {@link ConversationTimelineView}. */
4
+ export interface ConversationTimelineViewProps {
5
+ /** Timeline items in chronological order (from `useConversationTimeline`). */
6
+ readonly items: readonly ConversationTimelineItem[];
7
+ /** `true` only during the first load. */
8
+ readonly isLoading: boolean;
9
+ /** Error from the timeline read, or `null`. */
10
+ readonly error: Error | null;
11
+ /** `true` while older history exists beyond what is loaded. */
12
+ readonly hasOlder: boolean;
13
+ /** Load the next older page. */
14
+ readonly loadOlder: () => void;
15
+ /** `true` while an older page is in flight. */
16
+ readonly isLoadingOlder: boolean;
17
+ /**
18
+ * The channel's provider, for the honest-timeline notice: providers
19
+ * whose inbound lane has no timeline source (Slack) render an
20
+ * explicit "customer messages aren't shown" notice instead of a
21
+ * silently one-sided thread. `null` renders no notice.
22
+ */
23
+ readonly provider: ChannelProviderId | null;
24
+ /** Frozen instant for deterministic hosts (tests, documentation tours). */
25
+ readonly now?: Date;
26
+ /** Additional classes for the view container. */
27
+ readonly className?: string;
28
+ }
29
+ /**
30
+ * One conversation's customer-visible thread, WhatsApp-style: customer
31
+ * on the left, the business (agent, teammate, platform copy) on the
32
+ * right, internal-lane events (escalations, attention clears) as inline
33
+ * system rows the customer never saw.
34
+ *
35
+ * Honesty rules, enforced here rather than hoped for: outbound items
36
+ * carry their send attempt AND the provider's receipt as two independent
37
+ * facts (DD-004 D-d — an item can be handed to the provider yet fail on
38
+ * the customer's side), failed and suppressed sends render as exactly
39
+ * that, and non-text inbound renders a typed placeholder instead of
40
+ * disappearing.
41
+ *
42
+ * Presentational — pair with `useConversationTimeline`, or compose via
43
+ * `ConversationsWorkbench`.
44
+ */
45
+ export declare function ConversationTimelineView({ items, isLoading, error, hasOlder, loadOlder, isLoadingOlder, provider, now, className, }: ConversationTimelineViewProps): import("react/jsx-runtime").JSX.Element;
46
+ //# sourceMappingURL=ConversationTimelineView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationTimelineView.d.ts","sourceRoot":"","sources":["../../src/conversation/ConversationTimelineView.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uEAAuE,CAAC;AACtH,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAoBpF,kDAAkD;AAClD,MAAM,WAAW,6BAA6B;IAC5C,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,SAAS,wBAAwB,EAAE,CAAC;IACpD,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,+CAA+C;IAC/C,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC7B,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,gCAAgC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC;IAC/B,+CAA+C;IAC/C,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC;IACpB,iDAAiD;IACjD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,KAAK,EACL,SAAS,EACT,KAAK,EACL,QAAQ,EACR,SAAS,EACT,cAAc,EACd,QAAQ,EACR,GAAG,EACH,SAAS,GACV,EAAE,6BAA6B,2CAgF/B"}