@stigmer/react 3.6.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 (260) hide show
  1. package/billing/CreditLedgerTable.d.ts.map +1 -1
  2. package/billing/CreditLedgerTable.js +2 -7
  3. package/billing/CreditLedgerTable.js.map +1 -1
  4. package/channel/AgentChannelsPanel.d.ts.map +1 -1
  5. package/channel/AgentChannelsPanel.js +21 -2
  6. package/channel/AgentChannelsPanel.js.map +1 -1
  7. package/channel/ChannelConversationsDialog.d.ts +12 -4
  8. package/channel/ChannelConversationsDialog.d.ts.map +1 -1
  9. package/channel/ChannelConversationsDialog.js +18 -24
  10. package/channel/ChannelConversationsDialog.js.map +1 -1
  11. package/channel/providers.d.ts +23 -0
  12. package/channel/providers.d.ts.map +1 -1
  13. package/channel/providers.js +4 -0
  14. package/channel/providers.js.map +1 -1
  15. package/channel/useSaveAgentChannel.d.ts +5 -4
  16. package/channel/useSaveAgentChannel.d.ts.map +1 -1
  17. package/channel/useSaveAgentChannel.js +18 -4
  18. package/channel/useSaveAgentChannel.js.map +1 -1
  19. package/composer/ComposerToolbar.d.ts +6 -1
  20. package/composer/ComposerToolbar.d.ts.map +1 -1
  21. package/composer/ComposerToolbar.js +2 -2
  22. package/composer/ComposerToolbar.js.map +1 -1
  23. package/composer/SessionComposer.d.ts +9 -0
  24. package/composer/SessionComposer.d.ts.map +1 -1
  25. package/composer/SessionComposer.js +13 -2
  26. package/composer/SessionComposer.js.map +1 -1
  27. package/conversation/ConversationAttentionBanner.d.ts +22 -0
  28. package/conversation/ConversationAttentionBanner.d.ts.map +1 -0
  29. package/conversation/ConversationAttentionBanner.js +25 -0
  30. package/conversation/ConversationAttentionBanner.js.map +1 -0
  31. package/conversation/ConversationComposer.d.ts +41 -0
  32. package/conversation/ConversationComposer.d.ts.map +1 -0
  33. package/conversation/ConversationComposer.js +57 -0
  34. package/conversation/ConversationComposer.js.map +1 -0
  35. package/conversation/ConversationControlBanner.d.ts +52 -0
  36. package/conversation/ConversationControlBanner.d.ts.map +1 -0
  37. package/conversation/ConversationControlBanner.js +59 -0
  38. package/conversation/ConversationControlBanner.js.map +1 -0
  39. package/conversation/ConversationListPane.d.ts +68 -0
  40. package/conversation/ConversationListPane.d.ts.map +1 -0
  41. package/conversation/ConversationListPane.js +120 -0
  42. package/conversation/ConversationListPane.js.map +1 -0
  43. package/conversation/ConversationTimelineView.d.ts +46 -0
  44. package/conversation/ConversationTimelineView.d.ts.map +1 -0
  45. package/conversation/ConversationTimelineView.js +166 -0
  46. package/conversation/ConversationTimelineView.js.map +1 -0
  47. package/conversation/ConversationsWorkbench.d.ts +63 -0
  48. package/conversation/ConversationsWorkbench.d.ts.map +1 -0
  49. package/conversation/ConversationsWorkbench.js +165 -0
  50. package/conversation/ConversationsWorkbench.js.map +1 -0
  51. package/conversation/conversationPresentation.d.ts +137 -0
  52. package/conversation/conversationPresentation.d.ts.map +1 -0
  53. package/conversation/conversationPresentation.js +230 -0
  54. package/conversation/conversationPresentation.js.map +1 -0
  55. package/conversation/index.d.ts +26 -0
  56. package/conversation/index.d.ts.map +1 -0
  57. package/conversation/index.js +14 -0
  58. package/conversation/index.js.map +1 -0
  59. package/conversation/polling.d.ts +39 -0
  60. package/conversation/polling.d.ts.map +1 -0
  61. package/conversation/polling.js +39 -0
  62. package/conversation/polling.js.map +1 -0
  63. package/conversation/useConversation.d.ts +61 -0
  64. package/conversation/useConversation.d.ts.map +1 -0
  65. package/conversation/useConversation.js +153 -0
  66. package/conversation/useConversation.js.map +1 -0
  67. package/conversation/useConversationList.d.ts +98 -0
  68. package/conversation/useConversationList.d.ts.map +1 -0
  69. package/conversation/useConversationList.js +200 -0
  70. package/conversation/useConversationList.js.map +1 -0
  71. package/conversation/useConversationParticipation.d.ts +63 -0
  72. package/conversation/useConversationParticipation.d.ts.map +1 -0
  73. package/conversation/useConversationParticipation.js +84 -0
  74. package/conversation/useConversationParticipation.js.map +1 -0
  75. package/conversation/useConversationTimeline.d.ts +71 -0
  76. package/conversation/useConversationTimeline.d.ts.map +1 -0
  77. package/conversation/useConversationTimeline.js +152 -0
  78. package/conversation/useConversationTimeline.js.map +1 -0
  79. package/conversation/useConversationsWantsHumanCount.d.ts +42 -0
  80. package/conversation/useConversationsWantsHumanCount.d.ts.map +1 -0
  81. package/conversation/useConversationsWantsHumanCount.js +40 -0
  82. package/conversation/useConversationsWantsHumanCount.js.map +1 -0
  83. package/execution/useCreateAgentExecution.d.ts +14 -0
  84. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  85. package/execution/useCreateAgentExecution.js +5 -0
  86. package/execution/useCreateAgentExecution.js.map +1 -1
  87. package/index.d.ts +6 -2
  88. package/index.d.ts.map +1 -1
  89. package/index.js +14 -5
  90. package/index.js.map +1 -1
  91. package/internal/Pagination.d.ts +15 -0
  92. package/internal/Pagination.d.ts.map +1 -0
  93. package/internal/Pagination.js +20 -0
  94. package/internal/Pagination.js.map +1 -0
  95. package/internal/tooltip.d.ts.map +1 -1
  96. package/internal/tooltip.js +7 -1
  97. package/internal/tooltip.js.map +1 -1
  98. package/internal/useAutoScroll.d.ts +25 -4
  99. package/internal/useAutoScroll.d.ts.map +1 -1
  100. package/internal/useAutoScroll.js +55 -15
  101. package/internal/useAutoScroll.js.map +1 -1
  102. package/internal/useFetch.d.ts.map +1 -1
  103. package/internal/useFetch.js +14 -4
  104. package/internal/useFetch.js.map +1 -1
  105. package/models/ModelSelector.d.ts +45 -1
  106. package/models/ModelSelector.d.ts.map +1 -1
  107. package/models/ModelSelector.js +49 -6
  108. package/models/ModelSelector.js.map +1 -1
  109. package/models/index.d.ts +2 -0
  110. package/models/index.d.ts.map +1 -1
  111. package/models/index.js +1 -0
  112. package/models/index.js.map +1 -1
  113. package/models/registry.d.ts +11 -0
  114. package/models/registry.d.ts.map +1 -1
  115. package/models/registry.js +3 -0
  116. package/models/registry.js.map +1 -1
  117. package/models/service-tier.d.ts +27 -0
  118. package/models/service-tier.d.ts.map +1 -0
  119. package/models/service-tier.js +34 -0
  120. package/models/service-tier.js.map +1 -0
  121. package/package.json +4 -4
  122. package/schedule/ScheduleDetailView.d.ts +31 -8
  123. package/schedule/ScheduleDetailView.d.ts.map +1 -1
  124. package/schedule/ScheduleDetailView.js +441 -29
  125. package/schedule/ScheduleDetailView.js.map +1 -1
  126. package/schedule/ScheduleForm.d.ts +7 -4
  127. package/schedule/ScheduleForm.d.ts.map +1 -1
  128. package/schedule/ScheduleForm.js +94 -8
  129. package/schedule/ScheduleForm.js.map +1 -1
  130. package/schedule/ScheduleRunsTable.d.ts +72 -0
  131. package/schedule/ScheduleRunsTable.d.ts.map +1 -0
  132. package/schedule/ScheduleRunsTable.js +161 -0
  133. package/schedule/ScheduleRunsTable.js.map +1 -0
  134. package/schedule/index.d.ts +6 -0
  135. package/schedule/index.d.ts.map +1 -1
  136. package/schedule/index.js +3 -0
  137. package/schedule/index.js.map +1 -1
  138. package/schedule/useScheduleRuns.d.ts +44 -0
  139. package/schedule/useScheduleRuns.d.ts.map +1 -0
  140. package/schedule/useScheduleRuns.js +48 -0
  141. package/schedule/useScheduleRuns.js.map +1 -0
  142. package/schedule/useTriggerSchedule.d.ts +23 -10
  143. package/schedule/useTriggerSchedule.d.ts.map +1 -1
  144. package/schedule/useTriggerSchedule.js +28 -9
  145. package/schedule/useTriggerSchedule.js.map +1 -1
  146. package/schedule/useUpdateScheduleSpec.d.ts +49 -0
  147. package/schedule/useUpdateScheduleSpec.d.ts.map +1 -0
  148. package/schedule/useUpdateScheduleSpec.js +70 -0
  149. package/schedule/useUpdateScheduleSpec.js.map +1 -0
  150. package/session/useNewSessionFlow.d.ts.map +1 -1
  151. package/session/useNewSessionFlow.js +1 -0
  152. package/session/useNewSessionFlow.js.map +1 -1
  153. package/session/useSessionConversation.d.ts +11 -0
  154. package/session/useSessionConversation.d.ts.map +1 -1
  155. package/session/useSessionConversation.js +1 -0
  156. package/session/useSessionConversation.js.map +1 -1
  157. package/session/useSessionPageFlow.d.ts.map +1 -1
  158. package/session/useSessionPageFlow.js +1 -0
  159. package/session/useSessionPageFlow.js.map +1 -1
  160. package/sharing/useSaveAgentShare.d.ts +10 -1
  161. package/sharing/useSaveAgentShare.d.ts.map +1 -1
  162. package/sharing/useSaveAgentShare.js +9 -0
  163. package/sharing/useSaveAgentShare.js.map +1 -1
  164. package/sidebar/WorkspaceSidebar.d.ts +12 -4
  165. package/sidebar/WorkspaceSidebar.d.ts.map +1 -1
  166. package/sidebar/WorkspaceSidebar.js +19 -7
  167. package/sidebar/WorkspaceSidebar.js.map +1 -1
  168. package/src/billing/CreditLedgerTable.tsx +3 -42
  169. package/src/channel/AgentChannelsPanel.tsx +33 -2
  170. package/src/channel/ChannelConversationsDialog.tsx +22 -25
  171. package/src/channel/__tests__/AgentChannelsPanel.test.tsx +36 -5
  172. package/src/channel/__tests__/ChannelConversationsDialog.test.tsx +38 -1
  173. package/src/channel/__tests__/useSaveAgentChannel.test.tsx +26 -0
  174. package/src/channel/providers.ts +27 -0
  175. package/src/channel/useSaveAgentChannel.ts +18 -4
  176. package/src/composer/ComposerToolbar.tsx +9 -0
  177. package/src/composer/SessionComposer.tsx +24 -1
  178. package/src/composer/__tests__/SessionComposer-serviceTier.test.tsx +157 -0
  179. package/src/conversation/ConversationAttentionBanner.tsx +77 -0
  180. package/src/conversation/ConversationComposer.tsx +156 -0
  181. package/src/conversation/ConversationControlBanner.tsx +193 -0
  182. package/src/conversation/ConversationListPane.tsx +459 -0
  183. package/src/conversation/ConversationTimelineView.tsx +435 -0
  184. package/src/conversation/ConversationsWorkbench.tsx +378 -0
  185. package/src/conversation/__tests__/ConversationAttentionBanner.test.tsx +82 -0
  186. package/src/conversation/__tests__/ConversationComposer.test.tsx +160 -0
  187. package/src/conversation/__tests__/ConversationControlBanner.test.tsx +190 -0
  188. package/src/conversation/__tests__/ConversationListPane.test.tsx +372 -0
  189. package/src/conversation/__tests__/ConversationTimelineView.layout.test.tsx +160 -0
  190. package/src/conversation/__tests__/ConversationTimelineView.test.tsx +405 -0
  191. package/src/conversation/__tests__/ConversationsWorkbench.test.tsx +860 -0
  192. package/src/conversation/__tests__/a11y/conversation-surfaces.a11y.test.tsx +235 -0
  193. package/src/conversation/__tests__/a11y/harness.tsx +100 -0
  194. package/src/conversation/__tests__/conversation-tooltips.layout.test.tsx +167 -0
  195. package/src/conversation/__tests__/conversationPollingComposition.test.tsx +138 -0
  196. package/src/conversation/__tests__/conversationPresentation.test.ts +289 -0
  197. package/src/conversation/__tests__/useConversation.test.tsx +243 -0
  198. package/src/conversation/__tests__/useConversationList.test.tsx +492 -0
  199. package/src/conversation/__tests__/useConversationParticipation.test.tsx +190 -0
  200. package/src/conversation/__tests__/useConversationTimeline.test.tsx +258 -0
  201. package/src/conversation/__tests__/useConversationsWantsHumanCount.test.tsx +102 -0
  202. package/src/conversation/conversationPresentation.ts +305 -0
  203. package/src/conversation/index.ts +80 -0
  204. package/src/conversation/polling.ts +41 -0
  205. package/src/conversation/useConversation.ts +221 -0
  206. package/src/conversation/useConversationList.ts +328 -0
  207. package/src/conversation/useConversationParticipation.ts +178 -0
  208. package/src/conversation/useConversationTimeline.ts +244 -0
  209. package/src/conversation/useConversationsWantsHumanCount.ts +86 -0
  210. package/src/execution/useCreateAgentExecution.ts +18 -0
  211. package/src/index.ts +76 -4
  212. package/src/internal/Pagination.tsx +72 -0
  213. package/src/internal/__tests__/fake-timer-slices.ts +29 -0
  214. package/src/internal/__tests__/useAutoScroll.layout.test.tsx +143 -0
  215. package/src/internal/__tests__/useAutoScroll.test.tsx +62 -5
  216. package/src/internal/__tests__/useFetch-poll-under-render-pressure.test.tsx +79 -0
  217. package/src/internal/tooltip.tsx +6 -1
  218. package/src/internal/useAutoScroll.ts +66 -20
  219. package/src/internal/useFetch.ts +15 -4
  220. package/src/models/ModelSelector.tsx +133 -4
  221. package/src/models/__tests__/ModelSelector-serviceTier.test.tsx +256 -0
  222. package/src/models/__tests__/useModelRegistry.test.tsx +15 -0
  223. package/src/models/index.ts +2 -0
  224. package/src/models/registry.ts +17 -0
  225. package/src/models/service-tier.ts +49 -0
  226. package/src/schedule/ScheduleDetailView.tsx +1179 -111
  227. package/src/schedule/ScheduleForm.tsx +218 -7
  228. package/src/schedule/ScheduleRunsTable.tsx +424 -0
  229. package/src/schedule/__tests__/ScheduleDetailView.test.tsx +431 -8
  230. package/src/schedule/__tests__/scheduleCreation.test.tsx +197 -0
  231. package/src/schedule/__tests__/scheduleHooks.test.tsx +94 -7
  232. package/src/schedule/index.ts +12 -0
  233. package/src/schedule/useScheduleRuns.ts +93 -0
  234. package/src/schedule/useTriggerSchedule.ts +39 -14
  235. package/src/schedule/useUpdateScheduleSpec.ts +108 -0
  236. package/src/session/useNewSessionFlow.ts +1 -0
  237. package/src/session/useSessionConversation.ts +12 -0
  238. package/src/session/useSessionPageFlow.ts +1 -0
  239. package/src/sharing/__tests__/useSaveAgentShare.test.tsx +55 -1
  240. package/src/sharing/__tests__/useShareToolReadiness.test.tsx +1 -0
  241. package/src/sharing/useSaveAgentShare.ts +19 -1
  242. package/src/sidebar/WorkspaceSidebar.tsx +62 -3
  243. package/src/sidebar/__tests__/WorkspaceSidebar.test.tsx +56 -1
  244. package/src/workflow/__tests__/inspector-forms.test.tsx +119 -2
  245. package/src/workflow/inspector/forms/AgentCallForm.tsx +260 -37
  246. package/src/workflow/inspector/tabs/RuntimeTab.tsx +72 -33
  247. package/src/workflow/starter-workflow-yaml.ts +2 -1
  248. package/styles.css +1 -1
  249. package/workflow/inspector/forms/AgentCallForm.d.ts +18 -2
  250. package/workflow/inspector/forms/AgentCallForm.d.ts.map +1 -1
  251. package/workflow/inspector/forms/AgentCallForm.js +87 -9
  252. package/workflow/inspector/forms/AgentCallForm.js.map +1 -1
  253. package/workflow/inspector/tabs/RuntimeTab.d.ts +2 -1
  254. package/workflow/inspector/tabs/RuntimeTab.d.ts.map +1 -1
  255. package/workflow/inspector/tabs/RuntimeTab.js +31 -5
  256. package/workflow/inspector/tabs/RuntimeTab.js.map +1 -1
  257. package/workflow/starter-workflow-yaml.d.ts +1 -1
  258. package/workflow/starter-workflow-yaml.d.ts.map +1 -1
  259. package/workflow/starter-workflow-yaml.js +2 -1
  260. package/workflow/starter-workflow-yaml.js.map +1 -1
@@ -0,0 +1,153 @@
1
+ "use client";
2
+ import { create, equals } from "@bufbuild/protobuf";
3
+ import { isNotFound } from "@stigmer/sdk";
4
+ import { ChannelConversationSchema, GetChannelConversationInputSchema, } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
+ import { useStigmer } from "../hooks.js";
7
+ import { toError } from "../internal/toError.js";
8
+ import { CONVERSATION_DETAIL_POLL_INTERVAL_MS } from "./polling.js";
9
+ /**
10
+ * Data hook for one conversation's identity and participation state —
11
+ * the single-row read behind a conversation detail view (who holds
12
+ * control, whether attention is needed and why, the display name).
13
+ *
14
+ * Deliberately NOT built on the shared `useFetch`: participation
15
+ * commands hand back fresher state than any in-flight poll
16
+ * ({@link UseConversationReturn.applyServerState}), which needs a write
17
+ * fence over the fetch state — a seam `useFetch`'s single-writer model
18
+ * does not have. The fence is an epoch counter: applying server state
19
+ * bumps it, and a poll that started under an older epoch discards its
20
+ * answer instead of resurrecting pre-command state under the user's
21
+ * cursor.
22
+ *
23
+ * Pass empty strings to skip fetching (the `null`-fetchFn convention).
24
+ */
25
+ export function useConversation(agentChannelId, conversationKey, options) {
26
+ const stigmer = useStigmer();
27
+ const refetchIntervalMs = options?.refetchIntervalMs ?? CONVERSATION_DETAIL_POLL_INTERVAL_MS;
28
+ const enabled = agentChannelId !== "" && conversationKey !== "";
29
+ const [conversation, setConversation] = useState(null);
30
+ const [awaitingCustomer, setAwaitingCustomer] = useState(false);
31
+ const [hasAnswer, setHasAnswer] = useState(false);
32
+ const [isFetching, setIsFetching] = useState(enabled);
33
+ const [error, setError] = useState(null);
34
+ const [fetchKey, setFetchKey] = useState(0);
35
+ // The write fence: bumped by applyServerState and by identity changes;
36
+ // a fetch that started under an older epoch throws its answer away.
37
+ const epochRef = useRef(0);
38
+ // Which fetch is the latest — ONLY the latest fetch's resolution may
39
+ // clear the in-flight flags, so a fenced stale answer cannot strand
40
+ // isFetching=true (which would silence the poll interval forever).
41
+ const fetchIdRef = useRef(0);
42
+ const isFetchingRef = useRef(false);
43
+ const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
44
+ const applyServerState = useCallback((fresh) => {
45
+ epochRef.current += 1;
46
+ setConversation(fresh);
47
+ setAwaitingCustomer(false);
48
+ setHasAnswer(true);
49
+ setError(null);
50
+ }, []);
51
+ useEffect(() => {
52
+ // Identity change (or disable): reset so channel A's control state
53
+ // can never render under channel B's key.
54
+ epochRef.current += 1;
55
+ setConversation(null);
56
+ setAwaitingCustomer(false);
57
+ setHasAnswer(false);
58
+ setError(null);
59
+ if (!enabled) {
60
+ setIsFetching(false);
61
+ isFetchingRef.current = false;
62
+ }
63
+ }, [agentChannelId, conversationKey, stigmer, enabled]);
64
+ useEffect(() => {
65
+ if (!enabled)
66
+ return;
67
+ const epoch = epochRef.current;
68
+ const fetchId = ++fetchIdRef.current;
69
+ setIsFetching(true);
70
+ isFetchingRef.current = true;
71
+ const settle = () => {
72
+ if (fetchIdRef.current !== fetchId)
73
+ return;
74
+ setIsFetching(false);
75
+ isFetchingRef.current = false;
76
+ };
77
+ stigmer.agentChannel
78
+ .getConversation(create(GetChannelConversationInputSchema, { agentChannelId, conversationKey }))
79
+ .then((row) => {
80
+ if (epochRef.current === epoch) {
81
+ // Preserve the reference across polls that change nothing —
82
+ // a fresh-but-equal proto every 5s would defeat React.memo
83
+ // on everything rendering the row (DD-010).
84
+ setConversation((current) => current && equals(ChannelConversationSchema, current, row)
85
+ ? current
86
+ : row);
87
+ setAwaitingCustomer(false);
88
+ setHasAnswer(true);
89
+ setError(null);
90
+ }
91
+ settle();
92
+ }, (err) => {
93
+ if (epochRef.current === epoch) {
94
+ if (isNotFound(err)) {
95
+ // The legitimate "no row yet" answer — not an error state.
96
+ setConversation(null);
97
+ setAwaitingCustomer(true);
98
+ setHasAnswer(true);
99
+ setError(null);
100
+ }
101
+ else {
102
+ setError(toError(err));
103
+ }
104
+ }
105
+ settle();
106
+ });
107
+ }, [enabled, agentChannelId, conversationKey, stigmer, fetchKey]);
108
+ useEffect(() => {
109
+ if (!enabled || !refetchIntervalMs || refetchIntervalMs <= 0)
110
+ return;
111
+ const id = setInterval(() => {
112
+ if (!isFetchingRef.current)
113
+ refetch();
114
+ }, refetchIntervalMs);
115
+ return () => clearInterval(id);
116
+ }, [enabled, refetchIntervalMs, refetch]);
117
+ // Focus refetch (DD-012 D-a: returning to the tab is fresh), guarded by
118
+ // the same in-flight flag as the interval. Mirrors useFetch's
119
+ // refetchOnWindowFocus — the second sighting of this listener shape;
120
+ // extract a shared helper if a third hand-rolled loop ever needs it.
121
+ useEffect(() => {
122
+ if (!enabled)
123
+ return;
124
+ if (typeof window === "undefined")
125
+ return;
126
+ const onActive = () => {
127
+ if (!isFetchingRef.current)
128
+ refetch();
129
+ };
130
+ const onVisible = () => {
131
+ if (document.visibilityState === "visible")
132
+ onActive();
133
+ };
134
+ window.addEventListener("focus", onActive);
135
+ document.addEventListener("visibilitychange", onVisible);
136
+ return () => {
137
+ window.removeEventListener("focus", onActive);
138
+ document.removeEventListener("visibilitychange", onVisible);
139
+ };
140
+ }, [enabled, refetch]);
141
+ const isLoading = isFetching && !hasAnswer;
142
+ const isRefetching = isFetching && hasAnswer;
143
+ return useMemo(() => ({
144
+ conversation,
145
+ awaitingCustomer,
146
+ isLoading,
147
+ isRefetching,
148
+ error,
149
+ refetch,
150
+ applyServerState,
151
+ }), [conversation, awaitingCustomer, isLoading, isRefetching, error, refetch, applyServerState]);
152
+ }
153
+ //# sourceMappingURL=useConversation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConversation.js","sourceRoot":"","sources":["../../src/conversation/useConversation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ import type { ChannelConversation } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
2
+ import { ChannelConversationListFilter } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
3
+ /** Options for {@link useConversationList}. */
4
+ export interface UseConversationListOptions {
5
+ /** Organization whose conversations to list. `null` skips fetching. */
6
+ readonly org: string | null;
7
+ /** Optional filter: only conversations on this agent channel. */
8
+ readonly agentChannelId?: string;
9
+ /**
10
+ * Optional server-evaluated predicate filter (channel-conversations
11
+ * DD-011 D-g). The generated enum is the vocabulary on purpose — the
12
+ * predicate lives in ONE place, the server, and the hook never
13
+ * re-expresses it. Defaults to unspecified (no filter).
14
+ */
15
+ readonly filter?: ChannelConversationListFilter;
16
+ /** Page size for the head page and each loadMore page. Default 50. */
17
+ readonly pageSize?: number;
18
+ /**
19
+ * Poll interval in milliseconds; `false` disables polling. Defaults to
20
+ * {@link CONVERSATION_LIST_POLL_INTERVAL_MS}. Only the head page polls
21
+ * — loaded older pages refresh on the next full identity change.
22
+ */
23
+ readonly refetchIntervalMs?: number | false;
24
+ }
25
+ /** Return value of {@link useConversationList}. */
26
+ export interface UseConversationListReturn {
27
+ /**
28
+ * Conversations, newest activity first: the polled head page followed
29
+ * by the accumulated older pages, deduplicated by conversation
30
+ * identity (offset pages drift under live activity — a conversation
31
+ * that moved up between fetches appears once, in its head position),
32
+ * with any {@link applyServerState} rows overriding both.
33
+ */
34
+ readonly conversations: readonly ChannelConversation[];
35
+ /** Exact total for the current filter, from the server. */
36
+ readonly totalCount: number;
37
+ /** `true` while more conversations exist beyond what is loaded. */
38
+ readonly hasMore: boolean;
39
+ /** Load the next older page. No-op while one is already loading. */
40
+ readonly loadMore: () => void;
41
+ /** `true` while a loadMore page is in flight. */
42
+ readonly isLoadingMore: boolean;
43
+ /** Error from the last failed loadMore, or `null`. Cleared on retry. */
44
+ readonly loadMoreError: Error | null;
45
+ /** `true` only during the first head-page load. */
46
+ readonly isLoading: boolean;
47
+ /** `true` while the head page refreshes in the background. */
48
+ readonly isRefetching: boolean;
49
+ /** Error from the last failed head-page fetch, or `null`. */
50
+ readonly error: Error | null;
51
+ /** Re-fetch the head page. */
52
+ readonly refetch: () => void;
53
+ /**
54
+ * Adopt a fresh row the server just returned — every participation
55
+ * command answers the post-command state, and that answer is newer
56
+ * than anything a poll already in flight will deliver (DD-012 D-a:
57
+ * your own action reflects immediately, zero polls involved). The row
58
+ * overrides the listed copy for exactly one round-trip: applying also
59
+ * starts a fresh head fetch, and the override is dropped when a page
60
+ * provably fetched after the apply answers.
61
+ */
62
+ readonly applyServerState: (fresh: ChannelConversation) => void;
63
+ }
64
+ /**
65
+ * Data hook for the org-wide conversation list (channel-conversations
66
+ * DD-004): newest activity first across every channel the caller can
67
+ * view, optionally narrowed to one channel and/or a server-evaluated
68
+ * predicate ({@link ChannelConversationListFilter}).
69
+ *
70
+ * The head page rides `useFetch` and polls; older pages accumulate via
71
+ * {@link UseConversationListReturn.loadMore} and are merged with head
72
+ * precedence — the server's offset pagination shifts under live activity,
73
+ * so identity dedup (head copy wins: it is the fresher read) is what
74
+ * keeps a moving conversation from rendering twice.
75
+ *
76
+ * Command answers enter through
77
+ * {@link UseConversationListReturn.applyServerState} as a short-lived
78
+ * OVERLAY rather than a write into the fetched state, because `useFetch`
79
+ * has no write fence (the reason `useConversation` is hand-rolled): a
80
+ * poll answer already in the microtask queue when the command answers can
81
+ * commit AFTER the apply, and clearing the adopted row on "any head
82
+ * change" would hand the inbox back to pre-command state for a full poll
83
+ * period. Instead every head page is stamped with the apply-sequence at
84
+ * fetch start, and the overlay is dropped only when a page with
85
+ * `seq >= overlay.seq` — one provably started after the apply — answers.
86
+ * Applying triggers that fetch immediately, so the overlay lives for
87
+ * exactly one round-trip.
88
+ *
89
+ * Two honesty guards on overlay INSERTS (rows the server has not listed):
90
+ * a row from another channel never enters a channel-scoped list (identity
91
+ * equality — the same scope the hook sends to the server), and under a
92
+ * server-evaluated predicate filter the overlay is update-only, because
93
+ * the client cannot honestly claim membership the server has not
94
+ * asserted (the DD-011 A-1 one-predicate discipline). Updates in place
95
+ * are always honest: the server listed the row.
96
+ */
97
+ export declare function useConversationList(options: UseConversationListOptions): UseConversationListReturn;
98
+ //# sourceMappingURL=useConversationList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConversationList.d.ts","sourceRoot":"","sources":["../../src/conversation/useConversationList.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uEAAuE,CAAC;AACjH,OAAO,EACL,6BAA6B,EAE9B,MAAM,uEAAuE,CAAC;AAO/E,+CAA+C;AAC/C,MAAM,WAAW,0BAA0B;IACzC,uEAAuE;IACvE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,6BAA6B,CAAC;IAChD,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC7C;AAED,mDAAmD;AACnD,MAAM,WAAW,yBAAyB;IACxC;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACvD,2DAA2D;IAC3D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;IAC9B,iDAAiD;IACjD,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,wEAAwE;IACxE,QAAQ,CAAC,aAAa,EAAE,KAAK,GAAG,IAAI,CAAC;IACrC,mDAAmD;IACnD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,8DAA8D;IAC9D,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC7B,8BAA8B;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B;;;;;;;;OAQG;IACH,QAAQ,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACjE;AAmCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,0BAA0B,GAClC,yBAAyB,CAsL3B"}
@@ -0,0 +1,200 @@
1
+ "use client";
2
+ import { create } from "@bufbuild/protobuf";
3
+ import { ChannelConversationListFilter, ListChannelConversationsInputSchema, } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
4
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
+ import { useStigmer } from "../hooks.js";
6
+ import { toError } from "../internal/toError.js";
7
+ import { useFetch } from "../internal/useFetch.js";
8
+ import { CONVERSATION_LIST_POLL_INTERVAL_MS } from "./polling.js";
9
+ const EMPTY_HEAD = { items: [], totalCount: 0, seq: 0 };
10
+ const EMPTY_OVERLAY = { rows: new Map(), seq: 0 };
11
+ function identityOf(conversation) {
12
+ return `${conversation.agentChannelId}\u0000${conversation.conversationKey}`;
13
+ }
14
+ /** Activity instant in epoch milliseconds, for sorted overlay inserts. */
15
+ function activityMillisOf(conversation) {
16
+ const at = conversation.lastActivityAt;
17
+ if (!at)
18
+ return 0;
19
+ return Number(at.seconds) * 1_000 + Math.floor(at.nanos / 1_000_000);
20
+ }
21
+ /**
22
+ * Data hook for the org-wide conversation list (channel-conversations
23
+ * DD-004): newest activity first across every channel the caller can
24
+ * view, optionally narrowed to one channel and/or a server-evaluated
25
+ * predicate ({@link ChannelConversationListFilter}).
26
+ *
27
+ * The head page rides `useFetch` and polls; older pages accumulate via
28
+ * {@link UseConversationListReturn.loadMore} and are merged with head
29
+ * precedence — the server's offset pagination shifts under live activity,
30
+ * so identity dedup (head copy wins: it is the fresher read) is what
31
+ * keeps a moving conversation from rendering twice.
32
+ *
33
+ * Command answers enter through
34
+ * {@link UseConversationListReturn.applyServerState} as a short-lived
35
+ * OVERLAY rather than a write into the fetched state, because `useFetch`
36
+ * has no write fence (the reason `useConversation` is hand-rolled): a
37
+ * poll answer already in the microtask queue when the command answers can
38
+ * commit AFTER the apply, and clearing the adopted row on "any head
39
+ * change" would hand the inbox back to pre-command state for a full poll
40
+ * period. Instead every head page is stamped with the apply-sequence at
41
+ * fetch start, and the overlay is dropped only when a page with
42
+ * `seq >= overlay.seq` — one provably started after the apply — answers.
43
+ * Applying triggers that fetch immediately, so the overlay lives for
44
+ * exactly one round-trip.
45
+ *
46
+ * Two honesty guards on overlay INSERTS (rows the server has not listed):
47
+ * a row from another channel never enters a channel-scoped list (identity
48
+ * equality — the same scope the hook sends to the server), and under a
49
+ * server-evaluated predicate filter the overlay is update-only, because
50
+ * the client cannot honestly claim membership the server has not
51
+ * asserted (the DD-011 A-1 one-predicate discipline). Updates in place
52
+ * are always honest: the server listed the row.
53
+ */
54
+ export function useConversationList(options) {
55
+ const { org, agentChannelId = "", filter = ChannelConversationListFilter.channel_conversation_list_filter_unspecified, pageSize = 50, } = options;
56
+ const refetchIntervalMs = options.refetchIntervalMs ?? CONVERSATION_LIST_POLL_INTERVAL_MS;
57
+ const stigmer = useStigmer();
58
+ // Monotonic count of applyServerState calls; head pages carry the
59
+ // value read at their fetch start (see HeadPage.seq).
60
+ const applySeqRef = useRef(0);
61
+ const fetchFn = org
62
+ ? async () => {
63
+ const seq = applySeqRef.current;
64
+ const result = await stigmer.agentChannel.listConversations(create(ListChannelConversationsInputSchema, {
65
+ org,
66
+ agentChannelId,
67
+ filter,
68
+ pageInfo: { num: 1, size: pageSize },
69
+ }));
70
+ return { items: result.items, totalCount: result.totalCount, seq };
71
+ }
72
+ : null;
73
+ const { data: head, isLoading, isRefetching, error, refetch, } = useFetch(fetchFn, [org, agentChannelId, filter, pageSize, stigmer], EMPTY_HEAD, {
74
+ refetchInterval: refetchIntervalMs,
75
+ // DD-012 D-a: returning to the tab is fresh.
76
+ refetchOnWindowFocus: true,
77
+ });
78
+ // Older pages, keyed by the same identity deps as the head fetch; the
79
+ // epoch fences in-flight loadMore answers across an identity change.
80
+ const [olderPages, setOlderPages] = useState([]);
81
+ const [loadedThroughPage, setLoadedThroughPage] = useState(1);
82
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
83
+ const [loadMoreError, setLoadMoreError] = useState(null);
84
+ const [overlay, setOverlay] = useState(EMPTY_OVERLAY);
85
+ const epochRef = useRef(0);
86
+ useEffect(() => {
87
+ epochRef.current += 1;
88
+ setOlderPages([]);
89
+ setLoadedThroughPage(1);
90
+ setIsLoadingMore(false);
91
+ setLoadMoreError(null);
92
+ setOverlay(EMPTY_OVERLAY);
93
+ }, [org, agentChannelId, filter, pageSize, stigmer]);
94
+ const applyServerState = useCallback((fresh) => {
95
+ const seq = ++applySeqRef.current;
96
+ setOverlay((current) => {
97
+ const rows = new Map(current.rows);
98
+ rows.set(identityOf(fresh), fresh);
99
+ return { rows, seq };
100
+ });
101
+ // Starts the round-trip that retires the overlay; also cancels any
102
+ // in-flight pre-command head fetch (useFetch's cleanup semantics).
103
+ refetch();
104
+ }, [refetch]);
105
+ // Retire the overlay once a head page fetched after the newest apply
106
+ // answers — server truth has caught up. A page with an older stamp
107
+ // (the microtask-race case) keeps the overlay in force.
108
+ useEffect(() => {
109
+ if (overlay.rows.size === 0)
110
+ return;
111
+ if (head.seq >= overlay.seq)
112
+ setOverlay(EMPTY_OVERLAY);
113
+ }, [head, overlay]);
114
+ const loadMore = useCallback(() => {
115
+ if (!org || isLoadingMore)
116
+ return;
117
+ const epoch = epochRef.current;
118
+ const nextPage = loadedThroughPage + 1;
119
+ setIsLoadingMore(true);
120
+ setLoadMoreError(null);
121
+ stigmer.agentChannel
122
+ .listConversations(create(ListChannelConversationsInputSchema, {
123
+ org,
124
+ agentChannelId,
125
+ filter,
126
+ pageInfo: { num: nextPage, size: pageSize },
127
+ }))
128
+ .then((result) => {
129
+ if (epochRef.current !== epoch)
130
+ return;
131
+ setOlderPages((pages) => [...pages, result.items]);
132
+ setLoadedThroughPage(nextPage);
133
+ setIsLoadingMore(false);
134
+ }, (err) => {
135
+ if (epochRef.current !== epoch)
136
+ return;
137
+ setLoadMoreError(toError(err));
138
+ setIsLoadingMore(false);
139
+ });
140
+ }, [org, agentChannelId, filter, pageSize, stigmer, isLoadingMore, loadedThroughPage]);
141
+ const conversations = useMemo(() => {
142
+ const seen = new Set();
143
+ const merged = [];
144
+ for (const conversation of [...head.items, ...olderPages.flat()]) {
145
+ const identity = identityOf(conversation);
146
+ if (seen.has(identity))
147
+ continue;
148
+ seen.add(identity);
149
+ // Update-in-place is always honest: the server listed this row.
150
+ merged.push(overlay.rows.get(identity) ?? conversation);
151
+ }
152
+ // Overlay rows the server has not listed: sorted insert (a command
153
+ // answer is not a recency claim — a takeover does not advance the
154
+ // activity clock, so head-of-list would misorder), subject to the
155
+ // two honesty guards documented on the hook.
156
+ for (const [identity, fresh] of overlay.rows) {
157
+ if (seen.has(identity))
158
+ continue;
159
+ if (filter !==
160
+ ChannelConversationListFilter.channel_conversation_list_filter_unspecified) {
161
+ continue;
162
+ }
163
+ if (agentChannelId !== "" && fresh.agentChannelId !== agentChannelId) {
164
+ continue;
165
+ }
166
+ const at = activityMillisOf(fresh);
167
+ const index = merged.findIndex((c) => activityMillisOf(c) < at);
168
+ merged.splice(index === -1 ? merged.length : index, 0, fresh);
169
+ seen.add(identity);
170
+ }
171
+ return merged;
172
+ }, [head.items, olderPages, overlay, agentChannelId, filter]);
173
+ const hasMore = conversations.length < head.totalCount;
174
+ return useMemo(() => ({
175
+ conversations,
176
+ totalCount: head.totalCount,
177
+ hasMore,
178
+ loadMore,
179
+ isLoadingMore,
180
+ loadMoreError,
181
+ isLoading,
182
+ isRefetching,
183
+ error,
184
+ refetch,
185
+ applyServerState,
186
+ }), [
187
+ conversations,
188
+ head.totalCount,
189
+ hasMore,
190
+ loadMore,
191
+ isLoadingMore,
192
+ loadMoreError,
193
+ isLoading,
194
+ isRefetching,
195
+ error,
196
+ refetch,
197
+ applyServerState,
198
+ ]);
199
+ }
200
+ //# sourceMappingURL=useConversationList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConversationList.js","sourceRoot":"","sources":["../../src/conversation/useConversationList.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import type { ChannelConversation } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
2
+ import type { SendChannelMessageOutput } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/message_io_pb";
3
+ /** The four staff-facing participation commands. */
4
+ export type ConversationCommand = "reply" | "takeOver" | "handBack" | "clearAttention";
5
+ /** Options for {@link useConversationParticipation}. */
6
+ export interface UseConversationParticipationOptions {
7
+ /** AgentChannel the conversation belongs to. */
8
+ readonly agentChannelId: string;
9
+ /** Conversation key within the channel. */
10
+ readonly conversationKey: string;
11
+ /**
12
+ * Receives the fresh conversation row every command answers with —
13
+ * wire it to {@link useConversation}'s `applyServerState` so banners
14
+ * and controls adopt server truth immediately. Called for reply too
15
+ * (via a follow-up row read): a staff reply implicitly takes over
16
+ * BEFORE the send, unconditionally, so control has moved even when
17
+ * the send itself was refused.
18
+ */
19
+ readonly onConversation?: (fresh: ChannelConversation) => void;
20
+ }
21
+ /** Return value of {@link useConversationParticipation}. */
22
+ export interface UseConversationParticipationReturn {
23
+ /**
24
+ * Send a staff text reply. Resolves with the truthful outcome of the
25
+ * inline attempt: `accepted` (with `outboundMessageId` — the item will
26
+ * appear on the timeline as `ob:<that id>`), `queued`, or `refused`.
27
+ * A `refused` outcome WITHOUT an `outboundMessageId` left no ledger
28
+ * row and no timeline item will ever exist for it.
29
+ */
30
+ readonly reply: (text: string) => Promise<SendChannelMessageOutput>;
31
+ /**
32
+ * Take the conversation over: the agent goes quiet until handBack.
33
+ * Resolves with the fresh row — for the LOSER of a concurrent
34
+ * takeover too (success-with-the-winner's-state, never an error), so
35
+ * compare `controlledBy` against the caller rather than assuming the
36
+ * transition won.
37
+ */
38
+ readonly takeOver: () => Promise<ChannelConversation>;
39
+ /** Hand the conversation back to the agent. */
40
+ readonly handBack: () => Promise<ChannelConversation>;
41
+ /** Dismiss the needs-attention flag in place; control is untouched. */
42
+ readonly clearAttention: () => Promise<ChannelConversation>;
43
+ /** Commands currently in flight. */
44
+ readonly pendingCommands: ReadonlySet<ConversationCommand>;
45
+ /** Per-command failures, cleared when that command is retried. */
46
+ readonly commandErrors: ReadonlyMap<ConversationCommand, Error>;
47
+ /** Reset every recorded command error. */
48
+ readonly clearErrors: () => void;
49
+ }
50
+ /**
51
+ * Behavior hook for the participation commands on one conversation
52
+ * (channel-conversations DD-005/DD-007/DD-008/DD-009): reply, takeOver,
53
+ * handBack, clearAttention — each keyed independently so a slow reply
54
+ * and a takeover can be in flight at once with attributable spinners
55
+ * and errors.
56
+ *
57
+ * Every command re-throws its failure after recording it in
58
+ * {@link UseConversationParticipationReturn.commandErrors}, so callers
59
+ * choose the propagation (await-and-toast, or ignore and render the
60
+ * keyed error inline).
61
+ */
62
+ export declare function useConversationParticipation(options: UseConversationParticipationOptions): UseConversationParticipationReturn;
63
+ //# sourceMappingURL=useConversationParticipation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConversationParticipation.d.ts","sourceRoot":"","sources":["../../src/conversation/useConversationParticipation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,mBAAmB,EACpB,MAAM,uEAAuE,CAAC;AAM/E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kEAAkE,CAAC;AAKjH,oDAAoD;AACpD,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAEvF,wDAAwD;AACxD,MAAM,WAAW,mCAAmC;IAClD,gDAAgD;IAChD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,2CAA2C;IAC3C,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC;;;;;;;OAOG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;CAChE;AAED,4DAA4D;AAC5D,MAAM,WAAW,kCAAkC;IACjD;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACpE;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACtD,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACtD,uEAAuE;IACvE,QAAQ,CAAC,cAAc,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC5D,oCAAoC;IACpC,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC,mBAAmB,CAAC,CAAC;IAC3D,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC;IAChE,0CAA0C;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,IAAI,CAAC;CAClC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,mCAAmC,GAC3C,kCAAkC,CAiGpC"}
@@ -0,0 +1,84 @@
1
+ "use client";
2
+ import { create } from "@bufbuild/protobuf";
3
+ import { ConversationControlInputSchema, GetChannelConversationInputSchema, ReplyToConversationInputSchema, } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/conversation_io_pb";
4
+ import { useCallback, useMemo } from "react";
5
+ import { useStigmer } from "../hooks.js";
6
+ import { useKeyedSubmission } from "../internal/useKeyedSubmission.js";
7
+ /**
8
+ * Behavior hook for the participation commands on one conversation
9
+ * (channel-conversations DD-005/DD-007/DD-008/DD-009): reply, takeOver,
10
+ * handBack, clearAttention — each keyed independently so a slow reply
11
+ * and a takeover can be in flight at once with attributable spinners
12
+ * and errors.
13
+ *
14
+ * Every command re-throws its failure after recording it in
15
+ * {@link UseConversationParticipationReturn.commandErrors}, so callers
16
+ * choose the propagation (await-and-toast, or ignore and render the
17
+ * keyed error inline).
18
+ */
19
+ export function useConversationParticipation(options) {
20
+ const { agentChannelId, conversationKey, onConversation } = options;
21
+ const stigmer = useStigmer();
22
+ const submission = useKeyedSubmission();
23
+ const { run } = submission;
24
+ const controlInput = useCallback(() => create(ConversationControlInputSchema, { agentChannelId, conversationKey }), [agentChannelId, conversationKey]);
25
+ const takeOver = useCallback(() => run("takeOver", async () => {
26
+ const fresh = await stigmer.agentChannel.takeOver(controlInput());
27
+ onConversation?.(fresh);
28
+ return fresh;
29
+ }), [run, stigmer, controlInput, onConversation]);
30
+ const handBack = useCallback(() => run("handBack", async () => {
31
+ const fresh = await stigmer.agentChannel.handBack(controlInput());
32
+ onConversation?.(fresh);
33
+ return fresh;
34
+ }), [run, stigmer, controlInput, onConversation]);
35
+ const clearAttention = useCallback(() => run("clearAttention", async () => {
36
+ const fresh = await stigmer.agentChannel.clearAttention(controlInput());
37
+ onConversation?.(fresh);
38
+ return fresh;
39
+ }), [run, stigmer, controlInput, onConversation]);
40
+ const reply = useCallback((text) => run("reply", async () => {
41
+ const output = await stigmer.agentChannel.reply(create(ReplyToConversationInputSchema, {
42
+ agentChannelId,
43
+ conversationKey,
44
+ payload: { kind: { case: "text", value: { body: text } } },
45
+ }));
46
+ // The implicit takeover flipped control BEFORE the send ran —
47
+ // even a refused reply moved it — but reply answers the send
48
+ // outcome, not the row. One follow-up read keeps the control
49
+ // banner honest without waiting a poll tick. Best-effort: a
50
+ // failed read only delays the banner until the next poll.
51
+ if (onConversation) {
52
+ try {
53
+ onConversation(await stigmer.agentChannel.getConversation(create(GetChannelConversationInputSchema, {
54
+ agentChannelId,
55
+ conversationKey,
56
+ })));
57
+ }
58
+ catch {
59
+ // The reply itself succeeded; the poll will catch the row up.
60
+ }
61
+ }
62
+ return output;
63
+ }), [run, stigmer, agentChannelId, conversationKey, onConversation]);
64
+ return useMemo(() => ({
65
+ reply,
66
+ takeOver,
67
+ handBack,
68
+ clearAttention,
69
+ // The submission primitive keys by string; this hook only ever
70
+ // submits the four command names, so the narrowing is sound.
71
+ pendingCommands: submission.submittingKeys,
72
+ commandErrors: submission.errorsByKey,
73
+ clearErrors: submission.clearErrors,
74
+ }), [
75
+ reply,
76
+ takeOver,
77
+ handBack,
78
+ clearAttention,
79
+ submission.submittingKeys,
80
+ submission.errorsByKey,
81
+ submission.clearErrors,
82
+ ]);
83
+ }
84
+ //# sourceMappingURL=useConversationParticipation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConversationParticipation.js","sourceRoot":"","sources":["../../src/conversation/useConversationParticipation.ts"],"names":[],"mappings":"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"}