cortena-ui 1.4.2 → 1.5.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 (133) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +149 -1
  3. package/dist/a2ui/views.js +2 -2
  4. package/dist/agent-chat/a2ui-block.d.ts +60 -0
  5. package/dist/agent-chat/a2ui-block.js +69 -0
  6. package/dist/agent-chat/a2ui-block.js.map +1 -0
  7. package/dist/agent-chat/agui-client.d.ts +40 -0
  8. package/dist/agent-chat/agui-client.js +251 -0
  9. package/dist/agent-chat/agui-client.js.map +1 -0
  10. package/dist/agent-chat/bridge.d.ts +109 -0
  11. package/dist/agent-chat/bridge.js +349 -0
  12. package/dist/agent-chat/bridge.js.map +1 -0
  13. package/dist/agent-chat/session.d.ts +58 -0
  14. package/dist/agent-chat/session.js +249 -0
  15. package/dist/agent-chat/session.js.map +1 -0
  16. package/dist/agent-chat/store.d.ts +67 -0
  17. package/dist/agent-chat/store.js +548 -0
  18. package/dist/agent-chat/store.js.map +1 -0
  19. package/dist/agent-chat/types.d.ts +187 -0
  20. package/dist/agent-chat/types.js +17 -0
  21. package/dist/agent-chat/types.js.map +1 -0
  22. package/dist/agent-chat.d.ts +10 -0
  23. package/dist/agent-chat.js +10 -0
  24. package/dist/components/admin-permissions/admin-permissions.d.ts +66 -0
  25. package/dist/components/admin-permissions/admin-permissions.js +101 -0
  26. package/dist/components/admin-permissions/admin-permissions.js.map +1 -0
  27. package/dist/components/admin-permissions/context.d.ts +70 -0
  28. package/dist/components/admin-permissions/context.js +258 -0
  29. package/dist/components/admin-permissions/context.js.map +1 -0
  30. package/dist/components/admin-permissions/index.d.ts +10 -0
  31. package/dist/components/admin-permissions/licence.d.ts +15 -0
  32. package/dist/components/admin-permissions/licence.js +78 -0
  33. package/dist/components/admin-permissions/licence.js.map +1 -0
  34. package/dist/components/admin-permissions/matrix.d.ts +20 -0
  35. package/dist/components/admin-permissions/matrix.js +191 -0
  36. package/dist/components/admin-permissions/matrix.js.map +1 -0
  37. package/dist/components/admin-permissions/members.d.ts +18 -0
  38. package/dist/components/admin-permissions/members.js +185 -0
  39. package/dist/components/admin-permissions/members.js.map +1 -0
  40. package/dist/components/admin-permissions/role-assignment.d.ts +35 -0
  41. package/dist/components/admin-permissions/role-assignment.js +174 -0
  42. package/dist/components/admin-permissions/role-assignment.js.map +1 -0
  43. package/dist/components/admin-permissions/roles.d.ts +25 -0
  44. package/dist/components/admin-permissions/roles.js +168 -0
  45. package/dist/components/admin-permissions/roles.js.map +1 -0
  46. package/dist/components/admin-permissions/types.d.ts +152 -0
  47. package/dist/components/admin-permissions/types.js +63 -0
  48. package/dist/components/admin-permissions/types.js.map +1 -0
  49. package/dist/components/agent-chat-popup.d.ts +29 -0
  50. package/dist/components/agent-chat-popup.js +188 -0
  51. package/dist/components/agent-chat-popup.js.map +1 -0
  52. package/dist/components/agent-chat.d.ts +143 -0
  53. package/dist/components/agent-chat.js +578 -0
  54. package/dist/components/agent-chat.js.map +1 -0
  55. package/dist/components/app-shell.d.ts +126 -0
  56. package/dist/components/app-shell.js +297 -0
  57. package/dist/components/app-shell.js.map +1 -0
  58. package/dist/components/badge.d.ts +1 -1
  59. package/dist/components/button-link.js +1 -1
  60. package/dist/components/button.d.ts +2 -2
  61. package/dist/components/checkbox.d.ts +1 -1
  62. package/dist/components/combobox.d.ts +1 -1
  63. package/dist/components/combobox.js +1 -1
  64. package/dist/components/consent-screen.d.ts +65 -0
  65. package/dist/components/consent-screen.js +123 -0
  66. package/dist/components/consent-screen.js.map +1 -0
  67. package/dist/components/data-table/data-table.d.ts +15 -1
  68. package/dist/components/data-table/data-table.js +18 -4
  69. package/dist/components/data-table/data-table.js.map +1 -1
  70. package/dist/components/data-table/index.d.ts +4 -4
  71. package/dist/components/data-table/parts.d.ts +27 -3
  72. package/dist/components/data-table/parts.js +175 -55
  73. package/dist/components/data-table/parts.js.map +1 -1
  74. package/dist/components/data-table/types.d.ts +61 -0
  75. package/dist/components/data-table/use-data-table.js +91 -6
  76. package/dist/components/data-table/use-data-table.js.map +1 -1
  77. package/dist/components/data-table/use-server-source.js +119 -28
  78. package/dist/components/data-table/use-server-source.js.map +1 -1
  79. package/dist/components/help-panel.d.ts +131 -0
  80. package/dist/components/help-panel.js +545 -0
  81. package/dist/components/help-panel.js.map +1 -0
  82. package/dist/components/login-screen.d.ts +127 -0
  83. package/dist/components/login-screen.js +339 -0
  84. package/dist/components/login-screen.js.map +1 -0
  85. package/dist/components/session-guard.d.ts +268 -0
  86. package/dist/components/session-guard.js +632 -0
  87. package/dist/components/session-guard.js.map +1 -0
  88. package/dist/components/toast.d.ts +1 -1
  89. package/dist/core.d.ts +5 -1
  90. package/dist/core.js +11 -7
  91. package/dist/data-table.d.ts +13 -4
  92. package/dist/data-table.js +10 -2
  93. package/dist/hooks/use-cortena-theme.js +49 -3
  94. package/dist/hooks/use-cortena-theme.js.map +1 -1
  95. package/dist/index.d.ts +17 -4
  96. package/dist/index.js +21 -8
  97. package/dist/markdown.d.ts +2 -1
  98. package/dist/markdown.js +2 -1
  99. package/package.json +16 -4
  100. package/src/agent-chat/a2ui-block.ts +118 -0
  101. package/src/agent-chat/agui-client.ts +405 -0
  102. package/src/agent-chat/bridge.ts +433 -0
  103. package/src/agent-chat/session.ts +392 -0
  104. package/src/agent-chat/store.ts +738 -0
  105. package/src/agent-chat/types.ts +213 -0
  106. package/src/components/admin-permissions/admin-permissions.tsx +130 -0
  107. package/src/components/admin-permissions/context.tsx +376 -0
  108. package/src/components/admin-permissions/index.tsx +32 -0
  109. package/src/components/admin-permissions/licence.tsx +84 -0
  110. package/src/components/admin-permissions/matrix.tsx +257 -0
  111. package/src/components/admin-permissions/members.tsx +204 -0
  112. package/src/components/admin-permissions/role-assignment.tsx +239 -0
  113. package/src/components/admin-permissions/roles.tsx +169 -0
  114. package/src/components/admin-permissions/types.ts +231 -0
  115. package/src/components/agent-chat-popup.tsx +289 -0
  116. package/src/components/agent-chat.tsx +843 -0
  117. package/src/components/app-shell.tsx +502 -0
  118. package/src/components/consent-screen.tsx +239 -0
  119. package/src/components/data-table/data-table.tsx +36 -0
  120. package/src/components/data-table/index.tsx +6 -1
  121. package/src/components/data-table/parts.tsx +223 -47
  122. package/src/components/data-table/types.ts +68 -0
  123. package/src/components/data-table/use-data-table.ts +152 -4
  124. package/src/components/data-table/use-server-source.ts +150 -12
  125. package/src/components/help-panel.tsx +765 -0
  126. package/src/components/login-screen.tsx +479 -0
  127. package/src/components/session-guard.tsx +1071 -0
  128. package/src/entries/agent-chat.ts +113 -0
  129. package/src/entries/core.ts +8 -0
  130. package/src/entries/data-table.ts +41 -0
  131. package/src/entries/markdown.ts +25 -0
  132. package/src/hooks/use-cortena-theme.ts +63 -4
  133. package/src/index.ts +6 -0
@@ -0,0 +1,548 @@
1
+ "use client";
2
+ "use client";
3
+ import { extractA2UIBlocks, foldA2UIBlocks } from "./a2ui-block.js";
4
+ import { isProvisionalSessionKey, isSessionOfAgent, mintSessionKey, parseHistoryMessages, readStoredSessionKey, separateThinking, wouldShrinkHistory, writeStoredSessionKey } from "./session.js";
5
+ import * as React from "react";
6
+ //#region src/agent-chat/store.ts
7
+ /**
8
+ * The chat state, and the hook that drives an `AgentChatClient` from React.
9
+ *
10
+ * The accumulation rules are ported from `cortena-shared/src/stores/use-chat.ts`
11
+ * (Cortena monorepo) so a run leaves this state in the same place it leaves
12
+ * cortenaweb's. The ones that are not obvious, and that a rewrite gets wrong:
13
+ *
14
+ * - A `delta` carries a snapshot, so text and A2UI blocks are **replaced**, not
15
+ * appended. Appending doubles every character.
16
+ * - Thinking is never replaced by something shorter. The upstream emitter can
17
+ * fall back to increments when a snapshot is unavailable, and treating one of
18
+ * those as a snapshot truncates the accumulated chain of thought.
19
+ * - On `final`, a second assistant message in the same turn folds the previous
20
+ * visible text into the chain of thought rather than adding a second bubble,
21
+ * and keeps the A2UI blocks of the message it folded away — the surface is
22
+ * still on screen and still interactive.
23
+ * - A reply that is only a drawn surface has no text, so `text || a2ui.length`
24
+ * is the test for "there is something to show". Testing text alone silently
25
+ * dropped everything the agent drew.
26
+ */
27
+ const EMPTY = {
28
+ sessionKey: null,
29
+ messages: [],
30
+ streaming: false,
31
+ streamText: "",
32
+ streamThinkingText: "",
33
+ streamA2ui: [],
34
+ runId: null,
35
+ hasMoreHistory: false,
36
+ loadingHistory: false,
37
+ toolOrder: [],
38
+ tools: {},
39
+ approvals: [],
40
+ historyCount: 0,
41
+ sessions: [],
42
+ loadingSessions: false,
43
+ error: null
44
+ };
45
+ function generateId() {
46
+ return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
47
+ }
48
+ function extractText(message) {
49
+ return (message?.content ?? []).filter((c) => c.type === "text" && c.text).map((c) => c.text).join("");
50
+ }
51
+ function extractThinking(message) {
52
+ return (message?.content ?? []).filter((c) => c.type === "thinking").map((c) => c.thinking ?? c.text ?? "").filter(Boolean).join("\n\n");
53
+ }
54
+ function reduce(state, action) {
55
+ switch (action.kind) {
56
+ case "chat": return reduceChat(state, action.event);
57
+ case "tool": return reduceTool(state, action.event);
58
+ case "approval/request": return state.approvals.some((r) => r.id === action.request.id) ? state : {
59
+ ...state,
60
+ approvals: [...state.approvals, action.request]
61
+ };
62
+ case "approval/resolved": return {
63
+ ...state,
64
+ approvals: state.approvals.filter((r) => r.id !== action.id)
65
+ };
66
+ case "approval/failed": return state.approvals.some((r) => r.id === action.request.id) ? state : {
67
+ ...state,
68
+ approvals: [...state.approvals, {
69
+ ...action.request,
70
+ error: action.message
71
+ }]
72
+ };
73
+ case "session": return action.key === state.sessionKey ? state : {
74
+ ...EMPTY,
75
+ sessions: state.sessions,
76
+ sessionKey: action.key
77
+ };
78
+ case "send": return {
79
+ ...state,
80
+ sessionKey: action.sessionKey,
81
+ messages: action.message ? [...state.messages, action.message] : state.messages,
82
+ streaming: true,
83
+ streamText: "",
84
+ streamThinkingText: "",
85
+ streamA2ui: [],
86
+ runId: action.runId,
87
+ toolOrder: [],
88
+ tools: {},
89
+ approvals: [],
90
+ error: null
91
+ };
92
+ case "send/failed": return {
93
+ ...state,
94
+ streaming: false,
95
+ runId: null,
96
+ messages: [...state.messages, {
97
+ id: generateId(),
98
+ role: "assistant",
99
+ text: action.message,
100
+ timestamp: Date.now(),
101
+ isError: true
102
+ }]
103
+ };
104
+ case "abort": return {
105
+ ...state,
106
+ streaming: false,
107
+ streamText: "",
108
+ streamThinkingText: "",
109
+ streamA2ui: [],
110
+ runId: null,
111
+ approvals: []
112
+ };
113
+ case "history/loading": return {
114
+ ...state,
115
+ loadingHistory: true
116
+ };
117
+ case "history/loaded":
118
+ if (wouldShrinkHistory({
119
+ currentSessionKey: state.sessionKey,
120
+ loadedSessionKey: action.sessionKey,
121
+ currentCount: state.messages.length,
122
+ loadedCount: action.messages.length
123
+ })) return {
124
+ ...state,
125
+ sessionKey: action.sessionKey,
126
+ hasMoreHistory: action.hasMore,
127
+ loadingHistory: false,
128
+ historyCount: action.records
129
+ };
130
+ return {
131
+ ...state,
132
+ sessionKey: action.sessionKey,
133
+ messages: action.messages,
134
+ hasMoreHistory: action.hasMore,
135
+ loadingHistory: false,
136
+ historyCount: action.records,
137
+ streaming: false,
138
+ streamText: "",
139
+ streamThinkingText: "",
140
+ streamA2ui: []
141
+ };
142
+ case "history/older": return {
143
+ ...state,
144
+ messages: [...action.messages, ...state.messages],
145
+ hasMoreHistory: action.hasMore,
146
+ loadingHistory: false,
147
+ historyCount: state.historyCount + action.records
148
+ };
149
+ case "history/failed": return {
150
+ ...state,
151
+ loadingHistory: false
152
+ };
153
+ case "sessions/loading": return {
154
+ ...state,
155
+ loadingSessions: true
156
+ };
157
+ case "sessions/loaded": return {
158
+ ...state,
159
+ sessions: action.sessions,
160
+ loadingSessions: false
161
+ };
162
+ case "error": return {
163
+ ...state,
164
+ error: action.message
165
+ };
166
+ default: return state;
167
+ }
168
+ }
169
+ function reduceChat(state, event) {
170
+ if (event.sessionKey && state.sessionKey && event.sessionKey !== state.sessionKey) {
171
+ if (!isProvisionalSessionKey(state.sessionKey)) return state;
172
+ state = {
173
+ ...state,
174
+ sessionKey: event.sessionKey
175
+ };
176
+ } else if (event.sessionKey && !state.sessionKey) state = {
177
+ ...state,
178
+ sessionKey: event.sessionKey
179
+ };
180
+ switch (event.state) {
181
+ case "delta": {
182
+ const text = extractText(event.message);
183
+ const thinking = extractThinking(event.message);
184
+ const a2ui = extractA2UIBlocks(event.message);
185
+ const next = {
186
+ ...state,
187
+ streaming: true
188
+ };
189
+ if (text) next.streamText = text;
190
+ if (a2ui.length > 0) next.streamA2ui = a2ui;
191
+ const acceptThinking = !!thinking && thinking.length >= state.streamThinkingText.length;
192
+ if (acceptThinking) next.streamThinkingText = thinking;
193
+ return text || acceptThinking || a2ui.length > 0 ? next : state;
194
+ }
195
+ case "final": {
196
+ const finalText = extractText(event.message);
197
+ const finalThinking = extractThinking(event.message);
198
+ const finalA2ui = extractA2UIBlocks(event.message);
199
+ const text = finalText || state.streamText;
200
+ const thinkingFromBlocks = finalThinking || state.streamThinkingText;
201
+ const a2ui = foldA2UIBlocks(finalA2ui.length > 0 ? finalA2ui : state.streamA2ui);
202
+ const cleared = {
203
+ streaming: false,
204
+ streamText: "",
205
+ streamThinkingText: "",
206
+ streamA2ui: []
207
+ };
208
+ if (!text && a2ui.length === 0) {
209
+ const accumulated = state.streamThinkingText;
210
+ const last = state.messages[state.messages.length - 1];
211
+ if (!accumulated || !foldsInto(last, state.runId)) return {
212
+ ...state,
213
+ ...cleared
214
+ };
215
+ const messages = [...state.messages];
216
+ messages[messages.length - 1] = {
217
+ ...last,
218
+ timestamp: Date.now(),
219
+ thinkingText: [last.thinkingText ?? "", accumulated].filter(Boolean).join("\n\n")
220
+ };
221
+ return {
222
+ ...state,
223
+ ...cleared,
224
+ messages
225
+ };
226
+ }
227
+ const { thinkingText: tagThinking, mainText } = separateThinking(text);
228
+ const thinkingText = thinkingFromBlocks || tagThinking;
229
+ const displayText = thinkingFromBlocks ? text : mainText;
230
+ const messages = [...state.messages];
231
+ const last = messages[messages.length - 1];
232
+ if (foldsInto(last, state.runId)) {
233
+ const updated = [[last.thinkingText ?? "", last.text.trim()].filter(Boolean).join("\n\n"), thinkingText].filter(Boolean).join("\n\n");
234
+ messages[messages.length - 1] = {
235
+ ...last,
236
+ text: displayText,
237
+ timestamp: Date.now(),
238
+ ...updated ? { thinkingText: updated } : { thinkingText: void 0 },
239
+ ...a2ui.length > 0 || last.a2ui?.length ? { a2ui: foldA2UIBlocks([...last.a2ui ?? [], ...a2ui]) } : {}
240
+ };
241
+ } else messages.push({
242
+ id: generateId(),
243
+ role: "assistant",
244
+ text: displayText,
245
+ timestamp: Date.now(),
246
+ ...state.runId ? { runId: state.runId } : {},
247
+ ...thinkingText ? { thinkingText } : {},
248
+ ...a2ui.length > 0 ? { a2ui } : {}
249
+ });
250
+ return {
251
+ ...state,
252
+ ...cleared,
253
+ messages
254
+ };
255
+ }
256
+ case "error": return {
257
+ ...state,
258
+ streaming: false,
259
+ streamText: "",
260
+ streamThinkingText: "",
261
+ streamA2ui: [],
262
+ runId: null,
263
+ messages: [...state.messages, {
264
+ id: generateId(),
265
+ role: "assistant",
266
+ text: event.errorMessage ?? "An error occurred",
267
+ timestamp: Date.now(),
268
+ isError: true
269
+ }]
270
+ };
271
+ default: return state;
272
+ }
273
+ }
274
+ /**
275
+ * May a `final` fold into this message?
276
+ *
277
+ * Only when it is an assistant bubble from the SAME run. Two assistant
278
+ * messages in one turn are one answer with an interruption in the middle, and
279
+ * folding them is right. Two assistant messages from different runs are two
280
+ * answers to two questions, and folding them rewrote the earlier answer with
281
+ * the later one — visibly, in a transcript the user had already read.
282
+ *
283
+ * A message with no `runId` is history, which never folds either.
284
+ */
285
+ function foldsInto(last, runId) {
286
+ return last !== void 0 && last.role === "assistant" && !last.isError && runId !== null && last.runId === runId;
287
+ }
288
+ function reduceTool(state, event) {
289
+ const id = event.data.id;
290
+ if (!id) return state;
291
+ const base = state.tools[id] ?? {
292
+ id,
293
+ name: event.data.name ?? "tool",
294
+ status: "running",
295
+ startedAt: event.data.startedAt ?? Date.now()
296
+ };
297
+ let entry;
298
+ switch (event.event) {
299
+ case "tool.start":
300
+ entry = {
301
+ ...base,
302
+ name: event.data.name ?? base.name,
303
+ status: "running"
304
+ };
305
+ break;
306
+ case "tool.args":
307
+ entry = {
308
+ ...base,
309
+ input: event.data.input
310
+ };
311
+ break;
312
+ case "tool.progress":
313
+ entry = {
314
+ ...base,
315
+ output: event.data.output
316
+ };
317
+ break;
318
+ case "tool.complete":
319
+ entry = {
320
+ ...base,
321
+ status: "complete",
322
+ output: event.data.output,
323
+ completedAt: event.data.completedAt ?? Date.now()
324
+ };
325
+ break;
326
+ case "tool.error":
327
+ entry = {
328
+ ...base,
329
+ status: "error",
330
+ output: event.data.output,
331
+ completedAt: event.data.completedAt ?? Date.now()
332
+ };
333
+ break;
334
+ default: return state;
335
+ }
336
+ return {
337
+ ...state,
338
+ tools: {
339
+ ...state.tools,
340
+ [id]: entry
341
+ },
342
+ toolOrder: state.toolOrder.includes(id) ? state.toolOrder : [...state.toolOrder, id]
343
+ };
344
+ }
345
+ function useAgentChat({ client, storageScope, ephemeral, thinking }) {
346
+ const [state, dispatch] = React.useReducer(reduce, EMPTY);
347
+ const stateRef = React.useRef(state);
348
+ stateRef.current = state;
349
+ React.useEffect(() => {
350
+ const offChat = client.onChat((event) => dispatch({
351
+ kind: "chat",
352
+ event
353
+ }));
354
+ const offTool = client.onTool((event) => dispatch({
355
+ kind: "tool",
356
+ event
357
+ }));
358
+ const offApproval = client.onApproval((event) => {
359
+ if (event.type === "request") dispatch({
360
+ kind: "approval/request",
361
+ request: event.request
362
+ });
363
+ else dispatch({
364
+ kind: "approval/resolved",
365
+ id: event.id
366
+ });
367
+ });
368
+ return () => {
369
+ offChat();
370
+ offTool();
371
+ offApproval();
372
+ };
373
+ }, [client]);
374
+ const refreshSessions = React.useCallback(() => {
375
+ dispatch({ kind: "sessions/loading" });
376
+ client.request("sessions.list", { includeDerivedTitles: true }).then((response) => {
377
+ const sessions = (response?.sessions ?? []).filter((s) => isSessionOfAgent(s.key, client.agentId));
378
+ dispatch({
379
+ kind: "sessions/loaded",
380
+ sessions
381
+ });
382
+ }).catch((err) => {
383
+ dispatch({
384
+ kind: "sessions/loaded",
385
+ sessions: []
386
+ });
387
+ dispatch({
388
+ kind: "error",
389
+ message: errorText(err)
390
+ });
391
+ });
392
+ }, [client]);
393
+ const loadHistory = React.useCallback((sessionKey) => {
394
+ dispatch({ kind: "history/loading" });
395
+ client.request("chat.history", {
396
+ sessionKey,
397
+ limit: 100
398
+ }).then((response) => {
399
+ const records = response?.messages ?? [];
400
+ dispatch({
401
+ kind: "history/loaded",
402
+ sessionKey,
403
+ messages: parseHistoryMessages(records),
404
+ hasMore: response?.hasMore ?? false,
405
+ records: records.length
406
+ });
407
+ }).catch(() => dispatch({ kind: "history/failed" }));
408
+ }, [client]);
409
+ const restored = React.useRef(false);
410
+ React.useEffect(() => {
411
+ if (restored.current) return;
412
+ restored.current = true;
413
+ const stored = ephemeral ? null : readStoredSessionKey(storageScope);
414
+ const key = stored && isSessionOfAgent(stored, client.agentId) ? stored : null;
415
+ if (key) {
416
+ dispatch({
417
+ kind: "session",
418
+ key
419
+ });
420
+ loadHistory(key);
421
+ }
422
+ refreshSessions();
423
+ }, [
424
+ client,
425
+ ephemeral,
426
+ loadHistory,
427
+ refreshSessions,
428
+ storageScope
429
+ ]);
430
+ const setSession = React.useCallback((key) => {
431
+ dispatch({
432
+ kind: "session",
433
+ key
434
+ });
435
+ if (!ephemeral) writeStoredSessionKey(storageScope, key);
436
+ }, [ephemeral, storageScope]);
437
+ const run = React.useCallback((text, extra = {}) => {
438
+ const current = stateRef.current;
439
+ if (current.streaming) return;
440
+ const sessionKey = current.sessionKey ?? mintSessionKey(client.agentId);
441
+ if (!current.sessionKey && !ephemeral) writeStoredSessionKey(storageScope, sessionKey);
442
+ const runId = generateId();
443
+ const message = text || !extra.a2uiAction && !extra.mcpAppAction ? {
444
+ id: runId,
445
+ role: "user",
446
+ text,
447
+ timestamp: Date.now(),
448
+ runId
449
+ } : null;
450
+ dispatch({
451
+ kind: "send",
452
+ message,
453
+ runId,
454
+ sessionKey
455
+ });
456
+ client.send({
457
+ sessionKey,
458
+ message: text,
459
+ runId,
460
+ ...thinking ? { thinking } : {},
461
+ ...extra
462
+ }).then(() => refreshSessions()).catch((err) => dispatch({
463
+ kind: "send/failed",
464
+ message: errorText(err)
465
+ }));
466
+ }, [
467
+ client,
468
+ ephemeral,
469
+ refreshSessions,
470
+ storageScope,
471
+ thinking
472
+ ]);
473
+ const send = React.useCallback((text, attachments) => {
474
+ run(text, attachments?.length ? { attachments } : {});
475
+ }, [run]);
476
+ const sendAction = React.useCallback((a2uiAction) => {
477
+ run("", { a2uiAction });
478
+ }, [run]);
479
+ const abort = React.useCallback(() => {
480
+ const { runId, sessionKey } = stateRef.current;
481
+ dispatch({ kind: "abort" });
482
+ client.abort({
483
+ ...runId ? { runId } : {},
484
+ ...sessionKey ? { sessionKey } : {}
485
+ }).catch(() => {});
486
+ }, [client]);
487
+ const resolveApproval = React.useCallback((id, decision) => {
488
+ const request = stateRef.current.approvals.find((r) => r.id === id);
489
+ if (!request) return;
490
+ dispatch({
491
+ kind: "approval/resolved",
492
+ id
493
+ });
494
+ client.resolveApproval(id, decision, {
495
+ runId: request.runId,
496
+ sessionKey: request.sessionKey
497
+ }).catch((err) => {
498
+ dispatch({
499
+ kind: "approval/failed",
500
+ request,
501
+ message: errorText(err)
502
+ });
503
+ });
504
+ }, [client]);
505
+ const newSession = React.useCallback(() => {
506
+ setSession(mintSessionKey(client.agentId));
507
+ }, [client, setSession]);
508
+ const resumeSession = React.useCallback((key) => {
509
+ setSession(key);
510
+ loadHistory(key);
511
+ }, [loadHistory, setSession]);
512
+ const loadOlder = React.useCallback(() => {
513
+ const current = stateRef.current;
514
+ if (!current.sessionKey || current.loadingHistory || !current.hasMoreHistory) return;
515
+ dispatch({ kind: "history/loading" });
516
+ client.request("chat.history", {
517
+ sessionKey: current.sessionKey,
518
+ limit: 100,
519
+ offset: current.historyCount
520
+ }).then((response) => {
521
+ const records = response?.messages ?? [];
522
+ dispatch({
523
+ kind: "history/older",
524
+ messages: parseHistoryMessages(records),
525
+ hasMore: response?.hasMore ?? false,
526
+ records: records.length
527
+ });
528
+ }).catch(() => dispatch({ kind: "history/failed" }));
529
+ }, [client]);
530
+ return {
531
+ ...state,
532
+ send,
533
+ sendAction,
534
+ abort,
535
+ resolveApproval,
536
+ newSession,
537
+ resumeSession,
538
+ loadOlder,
539
+ refreshSessions
540
+ };
541
+ }
542
+ function errorText(err) {
543
+ return err instanceof Error ? err.message : "The agent could not be reached";
544
+ }
545
+ //#endregion
546
+ export { useAgentChat };
547
+
548
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","names":[],"sources":["../../src/agent-chat/store.ts"],"sourcesContent":["\"use client\";\n\n/**\n * The chat state, and the hook that drives an `AgentChatClient` from React.\n *\n * The accumulation rules are ported from `cortena-shared/src/stores/use-chat.ts`\n * (Cortena monorepo) so a run leaves this state in the same place it leaves\n * cortenaweb's. The ones that are not obvious, and that a rewrite gets wrong:\n *\n * - A `delta` carries a snapshot, so text and A2UI blocks are **replaced**, not\n * appended. Appending doubles every character.\n * - Thinking is never replaced by something shorter. The upstream emitter can\n * fall back to increments when a snapshot is unavailable, and treating one of\n * those as a snapshot truncates the accumulated chain of thought.\n * - On `final`, a second assistant message in the same turn folds the previous\n * visible text into the chain of thought rather than adding a second bubble,\n * and keeps the A2UI blocks of the message it folded away — the surface is\n * still on screen and still interactive.\n * - A reply that is only a drawn surface has no text, so `text || a2ui.length`\n * is the test for \"there is something to show\". Testing text alone silently\n * dropped everything the agent drew.\n */\n\nimport * as React from \"react\";\nimport { type A2UIChatBlock, extractA2UIBlocks, foldA2UIBlocks } from \"./a2ui-block\";\nimport {\n isProvisionalSessionKey,\n isSessionOfAgent,\n mintSessionKey,\n parseHistoryMessages,\n readStoredSessionKey,\n separateThinking,\n wouldShrinkHistory,\n writeStoredSessionKey,\n} from \"./session\";\nimport type {\n AgentApprovalDecision,\n AgentApprovalRequest,\n AgentChatClient,\n AgentChatEvent,\n AgentChatHistoryResponse,\n AgentChatMessage,\n AgentSession,\n AgentSessionsListResponse,\n AgentToolEvent,\n} from \"./types\";\n\nexport interface AgentToolEntry {\n id: string;\n name: string;\n status: \"running\" | \"complete\" | \"error\";\n input?: unknown;\n output?: unknown;\n startedAt: number;\n completedAt?: number;\n}\n\nexport interface AgentChatState {\n sessionKey: string | null;\n messages: AgentChatMessage[];\n streaming: boolean;\n streamText: string;\n streamThinkingText: string;\n streamA2ui: A2UIChatBlock[];\n runId: string | null;\n hasMoreHistory: boolean;\n loadingHistory: boolean;\n toolOrder: string[];\n tools: Record<string, AgentToolEntry>;\n approvals: AgentApprovalRequest[];\n /**\n * Transcript RECORDS loaded, which is not `messages.length`.\n *\n * `parseHistoryMessages` folds a turn — an assistant preamble, the tool call,\n * the tool result, the answer — into one visible message, so the two numbers\n * diverge on the very first agent turn. `offset` is an offset into the\n * server's records, and paging with the folded count asked for records the\n * screen already had and skipped the ones it did not: \"load older\" silently\n * lost messages the further back a user read.\n */\n historyCount: number;\n sessions: AgentSession[];\n loadingSessions: boolean;\n /** A failure the user has to be told about, e.g. no template for the slug. */\n error: string | null;\n}\n\nconst EMPTY: AgentChatState = {\n sessionKey: null,\n messages: [],\n streaming: false,\n streamText: \"\",\n streamThinkingText: \"\",\n streamA2ui: [],\n runId: null,\n hasMoreHistory: false,\n loadingHistory: false,\n toolOrder: [],\n tools: {},\n approvals: [],\n historyCount: 0,\n sessions: [],\n loadingSessions: false,\n error: null,\n};\n\ntype Action =\n | { kind: \"chat\"; event: AgentChatEvent }\n | { kind: \"tool\"; event: AgentToolEvent }\n | { kind: \"approval/request\"; request: AgentApprovalRequest }\n | { kind: \"approval/resolved\"; id: string }\n | { kind: \"approval/failed\"; request: AgentApprovalRequest; message: string }\n | { kind: \"session\"; key: string | null }\n | { kind: \"send\"; message: AgentChatMessage | null; runId: string; sessionKey: string }\n | { kind: \"send/failed\"; message: string }\n | { kind: \"abort\" }\n | { kind: \"history/loading\" }\n | {\n kind: \"history/loaded\";\n sessionKey: string;\n messages: AgentChatMessage[];\n hasMore: boolean;\n records: number;\n }\n | { kind: \"history/older\"; messages: AgentChatMessage[]; hasMore: boolean; records: number }\n | { kind: \"history/failed\" }\n | { kind: \"sessions/loading\" }\n | { kind: \"sessions/loaded\"; sessions: AgentSession[] }\n | { kind: \"error\"; message: string | null };\n\nfunction generateId(): string {\n return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nfunction extractText(message?: AgentChatEvent[\"message\"]): string {\n return (message?.content ?? [])\n .filter((c) => c.type === \"text\" && c.text)\n .map((c) => c.text!)\n .join(\"\");\n}\n\nfunction extractThinking(message?: AgentChatEvent[\"message\"]): string {\n const blocks = (message?.content ?? [])\n .filter((c) => c.type === \"thinking\")\n .map((c) => c.thinking ?? c.text ?? \"\")\n .filter(Boolean);\n return blocks.join(\"\\n\\n\");\n}\n\nfunction reduce(state: AgentChatState, action: Action): AgentChatState {\n switch (action.kind) {\n case \"chat\":\n return reduceChat(state, action.event);\n case \"tool\":\n return reduceTool(state, action.event);\n case \"approval/request\":\n return state.approvals.some((r) => r.id === action.request.id)\n ? state\n : { ...state, approvals: [...state.approvals, action.request] };\n case \"approval/resolved\":\n return { ...state, approvals: state.approvals.filter((r) => r.id !== action.id) };\n case \"approval/failed\":\n // The decision did not land, so the card comes back — with the reason on\n // it. Removing it optimistically and then swallowing the failure left\n // the user certain they had answered and the run paused for ever.\n return state.approvals.some((r) => r.id === action.request.id)\n ? state\n : {\n ...state,\n approvals: [...state.approvals, { ...action.request, error: action.message }],\n };\n case \"session\":\n return action.key === state.sessionKey\n ? state\n : { ...EMPTY, sessions: state.sessions, sessionKey: action.key };\n case \"send\":\n return {\n ...state,\n sessionKey: action.sessionKey,\n messages: action.message ? [...state.messages, action.message] : state.messages,\n streaming: true,\n streamText: \"\",\n streamThinkingText: \"\",\n streamA2ui: [],\n runId: action.runId,\n toolOrder: [],\n tools: {},\n // A new run is a new set of pending decisions. An approval card left\n // over from the previous run points at a run that is over, and\n // answering it sends a decision nothing is waiting for.\n approvals: [],\n error: null,\n };\n case \"send/failed\":\n return {\n ...state,\n streaming: false,\n runId: null,\n messages: [\n ...state.messages,\n {\n id: generateId(),\n role: \"assistant\",\n text: action.message,\n timestamp: Date.now(),\n isError: true,\n },\n ],\n };\n case \"abort\":\n // Reset immediately so the composer flips Stop -> Send without waiting on\n // a server event an aborted run does not reliably send.\n return {\n ...state,\n streaming: false,\n streamText: \"\",\n streamThinkingText: \"\",\n streamA2ui: [],\n runId: null,\n // The run they belonged to is gone; the decisions go with it.\n approvals: [],\n };\n case \"history/loading\":\n return { ...state, loadingHistory: true };\n case \"history/loaded\":\n if (\n wouldShrinkHistory({\n currentSessionKey: state.sessionKey,\n loadedSessionKey: action.sessionKey,\n currentCount: state.messages.length,\n loadedCount: action.messages.length,\n })\n ) {\n return {\n ...state,\n sessionKey: action.sessionKey,\n hasMoreHistory: action.hasMore,\n loadingHistory: false,\n historyCount: action.records,\n };\n }\n return {\n ...state,\n sessionKey: action.sessionKey,\n messages: action.messages,\n hasMoreHistory: action.hasMore,\n loadingHistory: false,\n historyCount: action.records,\n // Avoids an orphan streaming bubble beside reloaded history.\n streaming: false,\n streamText: \"\",\n streamThinkingText: \"\",\n streamA2ui: [],\n };\n case \"history/older\":\n return {\n ...state,\n messages: [...action.messages, ...state.messages],\n hasMoreHistory: action.hasMore,\n loadingHistory: false,\n historyCount: state.historyCount + action.records,\n };\n case \"history/failed\":\n return { ...state, loadingHistory: false };\n case \"sessions/loading\":\n return { ...state, loadingSessions: true };\n case \"sessions/loaded\":\n return { ...state, sessions: action.sessions, loadingSessions: false };\n case \"error\":\n return { ...state, error: action.message };\n default:\n return state;\n }\n}\n\nfunction reduceChat(state: AgentChatState, event: AgentChatEvent): AgentChatState {\n // Events from another session are dropped, or one window's run leaks into\n // another window's transcript. A provisional key is the exception: the server\n // echoes it back today, but if it ever canonicalised the key instead, dropping\n // the event would silently kill the whole first turn.\n if (event.sessionKey && state.sessionKey && event.sessionKey !== state.sessionKey) {\n if (!isProvisionalSessionKey(state.sessionKey)) {\n return state;\n }\n state = { ...state, sessionKey: event.sessionKey };\n } else if (event.sessionKey && !state.sessionKey) {\n state = { ...state, sessionKey: event.sessionKey };\n }\n\n switch (event.state) {\n case \"delta\": {\n const text = extractText(event.message);\n const thinking = extractThinking(event.message);\n const a2ui = extractA2UIBlocks(event.message);\n const next: AgentChatState = { ...state, streaming: true };\n if (text) {\n next.streamText = text;\n }\n if (a2ui.length > 0) {\n next.streamA2ui = a2ui;\n }\n // Never replace accumulated thinking with something shorter.\n const acceptThinking = !!thinking && thinking.length >= state.streamThinkingText.length;\n if (acceptThinking) {\n next.streamThinkingText = thinking;\n }\n return text || acceptThinking || a2ui.length > 0 ? next : state;\n }\n case \"final\": {\n const finalText = extractText(event.message);\n const finalThinking = extractThinking(event.message);\n const finalA2ui = extractA2UIBlocks(event.message);\n const text = finalText || state.streamText;\n const thinkingFromBlocks = finalThinking || state.streamThinkingText;\n const a2ui = foldA2UIBlocks(finalA2ui.length > 0 ? finalA2ui : state.streamA2ui);\n // The run id deliberately SURVIVES a final. A run can end more than\n // once — an interrupt for an approval, then the continuation — and\n // clearing it here meant the second final saw `runId: null`, could not\n // fold, and pushed a second bubble: one answer to one question, drawn\n // as two. `send` sets the next run's id, and `abort`, `error` and\n // `send/failed` each clear it, so nothing outlives the turn it belongs\n // to.\n const cleared = {\n streaming: false,\n streamText: \"\",\n streamThinkingText: \"\",\n streamA2ui: [] as A2UIChatBlock[],\n };\n\n if (!text && a2ui.length === 0) {\n // No text and nothing drawn: fold whatever thinking accumulated into\n // the previous turn rather than dropping it.\n const accumulated = state.streamThinkingText;\n const last = state.messages[state.messages.length - 1];\n if (!accumulated || !foldsInto(last, state.runId)) {\n return { ...state, ...cleared };\n }\n const messages = [...state.messages];\n messages[messages.length - 1] = {\n ...last,\n timestamp: Date.now(),\n thinkingText: [last.thinkingText ?? \"\", accumulated].filter(Boolean).join(\"\\n\\n\"),\n };\n return { ...state, ...cleared, messages };\n }\n\n const { thinkingText: tagThinking, mainText } = separateThinking(text);\n const thinkingText = thinkingFromBlocks || tagThinking;\n const displayText = thinkingFromBlocks ? text : mainText;\n const messages = [...state.messages];\n const last = messages[messages.length - 1];\n\n if (foldsInto(last, state.runId)) {\n const combined = [last.thinkingText ?? \"\", last.text.trim()].filter(Boolean).join(\"\\n\\n\");\n const updated = [combined, thinkingText].filter(Boolean).join(\"\\n\\n\");\n messages[messages.length - 1] = {\n ...last,\n text: displayText,\n timestamp: Date.now(),\n ...(updated ? { thinkingText: updated } : { thinkingText: undefined }),\n ...(a2ui.length > 0 || last.a2ui?.length\n ? { a2ui: foldA2UIBlocks([...(last.a2ui ?? []), ...a2ui]) }\n : {}),\n };\n } else {\n messages.push({\n id: generateId(),\n role: \"assistant\",\n text: displayText,\n timestamp: Date.now(),\n // The run this bubble belongs to, so the NEXT run's `final` cannot\n // fold its answer into it.\n ...(state.runId ? { runId: state.runId } : {}),\n ...(thinkingText ? { thinkingText } : {}),\n ...(a2ui.length > 0 ? { a2ui } : {}),\n });\n }\n return { ...state, ...cleared, messages };\n }\n case \"error\":\n return {\n ...state,\n streaming: false,\n streamText: \"\",\n streamThinkingText: \"\",\n streamA2ui: [],\n runId: null,\n messages: [\n ...state.messages,\n {\n id: generateId(),\n role: \"assistant\",\n text: event.errorMessage ?? \"An error occurred\",\n timestamp: Date.now(),\n isError: true,\n },\n ],\n };\n default:\n return state;\n }\n}\n\n/**\n * May a `final` fold into this message?\n *\n * Only when it is an assistant bubble from the SAME run. Two assistant\n * messages in one turn are one answer with an interruption in the middle, and\n * folding them is right. Two assistant messages from different runs are two\n * answers to two questions, and folding them rewrote the earlier answer with\n * the later one — visibly, in a transcript the user had already read.\n *\n * A message with no `runId` is history, which never folds either.\n */\nfunction foldsInto(\n last: AgentChatMessage | undefined,\n runId: string | null,\n): last is AgentChatMessage {\n return (\n last !== undefined &&\n last.role === \"assistant\" &&\n !last.isError &&\n runId !== null &&\n last.runId === runId\n );\n}\n\nfunction reduceTool(state: AgentChatState, event: AgentToolEvent): AgentChatState {\n const id = event.data.id;\n if (!id) {\n return state;\n }\n const existing = state.tools[id];\n const base: AgentToolEntry = existing ?? {\n id,\n name: event.data.name ?? \"tool\",\n status: \"running\",\n startedAt: event.data.startedAt ?? Date.now(),\n };\n let entry: AgentToolEntry;\n switch (event.event) {\n case \"tool.start\":\n entry = { ...base, name: event.data.name ?? base.name, status: \"running\" };\n break;\n case \"tool.args\":\n entry = { ...base, input: event.data.input };\n break;\n case \"tool.progress\":\n entry = { ...base, output: event.data.output };\n break;\n case \"tool.complete\":\n entry = {\n ...base,\n status: \"complete\",\n output: event.data.output,\n completedAt: event.data.completedAt ?? Date.now(),\n };\n break;\n case \"tool.error\":\n entry = {\n ...base,\n status: \"error\",\n output: event.data.output,\n completedAt: event.data.completedAt ?? Date.now(),\n };\n break;\n default:\n return state;\n }\n return {\n ...state,\n tools: { ...state.tools, [id]: entry },\n toolOrder: state.toolOrder.includes(id) ? state.toolOrder : [...state.toolOrder, id],\n };\n}\n\n/* ── the hook ────────────────────────────────────────────────────────────── */\n\nexport interface UseAgentChatOptions {\n client: AgentChatClient;\n /**\n * Names the `sessionStorage` slot the current session key lives in. Two\n * pop-ups with different scopes never share a key, which is what keeps one\n * extension's chat out of another's.\n */\n storageScope: string;\n /** Skip reading and writing `sessionStorage`; for a transient mount. */\n ephemeral?: boolean;\n thinking?: \"low\" | \"medium\" | \"high\";\n}\n\nexport interface UseAgentChatResult extends AgentChatState {\n send: (text: string, attachments?: unknown[]) => void;\n sendAction: (action: NonNullable<Parameters<AgentChatClient[\"send\"]>[0][\"a2uiAction\"]>) => void;\n abort: () => void;\n resolveApproval: (id: string, decision: AgentApprovalDecision) => void;\n newSession: () => void;\n resumeSession: (key: string) => void;\n loadOlder: () => void;\n refreshSessions: () => void;\n}\n\nexport function useAgentChat({\n client,\n storageScope,\n ephemeral,\n thinking,\n}: UseAgentChatOptions): UseAgentChatResult {\n const [state, dispatch] = React.useReducer(reduce, EMPTY);\n const stateRef = React.useRef(state);\n stateRef.current = state;\n\n // Subscribe once per client. A pop-up that collapses unmounts the surface but\n // keeps the client, so a run started before the collapse still lands.\n React.useEffect(() => {\n const offChat = client.onChat((event) => dispatch({ kind: \"chat\", event }));\n const offTool = client.onTool((event) => dispatch({ kind: \"tool\", event }));\n const offApproval = client.onApproval((event) => {\n if (event.type === \"request\") {\n dispatch({ kind: \"approval/request\", request: event.request });\n } else {\n dispatch({ kind: \"approval/resolved\", id: event.id });\n }\n });\n return () => {\n offChat();\n offTool();\n offApproval();\n };\n }, [client]);\n\n const refreshSessions = React.useCallback(() => {\n dispatch({ kind: \"sessions/loading\" });\n void client\n .request<AgentSessionsListResponse>(\"sessions.list\", { includeDerivedTitles: true })\n .then((response) => {\n // Filtered to this agent: a shared control plane lists every session on\n // the pod, and an extension must never show another agent's chats.\n const sessions = (response?.sessions ?? []).filter((s) =>\n isSessionOfAgent(s.key, client.agentId),\n );\n dispatch({ kind: \"sessions/loaded\", sessions });\n })\n .catch((err: unknown) => {\n dispatch({ kind: \"sessions/loaded\", sessions: [] });\n dispatch({ kind: \"error\", message: errorText(err) });\n });\n }, [client]);\n\n const loadHistory = React.useCallback(\n (sessionKey: string) => {\n dispatch({ kind: \"history/loading\" });\n void client\n .request<AgentChatHistoryResponse>(\"chat.history\", { sessionKey, limit: 100 })\n .then((response) => {\n const records = response?.messages ?? [];\n dispatch({\n kind: \"history/loaded\",\n sessionKey,\n messages: parseHistoryMessages(records),\n hasMore: response?.hasMore ?? false,\n records: records.length,\n });\n })\n .catch(() => dispatch({ kind: \"history/failed\" }));\n },\n [client],\n );\n\n // Restore this window's session, or mint one. `sessionStorage`, not\n // `localStorage`: a second window must not adopt the first window's session.\n const restored = React.useRef(false);\n React.useEffect(() => {\n if (restored.current) {\n return;\n }\n restored.current = true;\n const stored = ephemeral ? null : readStoredSessionKey(storageScope);\n const key = stored && isSessionOfAgent(stored, client.agentId) ? stored : null;\n if (key) {\n dispatch({ kind: \"session\", key });\n loadHistory(key);\n }\n refreshSessions();\n }, [client, ephemeral, loadHistory, refreshSessions, storageScope]);\n\n const setSession = React.useCallback(\n (key: string) => {\n dispatch({ kind: \"session\", key });\n if (!ephemeral) {\n writeStoredSessionKey(storageScope, key);\n }\n },\n [ephemeral, storageScope],\n );\n\n const run = React.useCallback(\n (text: string, extra: Partial<Parameters<AgentChatClient[\"send\"]>[0]> = {}) => {\n const current = stateRef.current;\n // One run at a time. A second `send` on a streaming session opens a\n // second stream into one store: two sets of deltas overwrite each\n // other's snapshot, `final` folds one run's answer into the other's\n // bubble, and `abort` can only reach whichever the client thinks is\n // active. The composer disables Send while a run is live; this is the\n // same rule for Enter, for a2ui actions and for a caller of the hook.\n if (current.streaming) {\n return;\n }\n const sessionKey = current.sessionKey ?? mintSessionKey(client.agentId);\n if (!current.sessionKey && !ephemeral) {\n writeStoredSessionKey(storageScope, sessionKey);\n }\n const runId = generateId();\n // An action with no typed text is a click, not a sentence: a user bubble\n // for it would put `{\"name\":\"createTask\"}` in the transcript as though\n // the user had said it.\n const message: AgentChatMessage | null =\n text || (!extra.a2uiAction && !extra.mcpAppAction)\n ? { id: runId, role: \"user\", text, timestamp: Date.now(), runId }\n : null;\n dispatch({ kind: \"send\", message, runId, sessionKey });\n void client\n .send({\n sessionKey,\n message: text,\n runId,\n ...(thinking ? { thinking } : {}),\n ...extra,\n })\n .then(() => refreshSessions())\n .catch((err: unknown) => dispatch({ kind: \"send/failed\", message: errorText(err) }));\n },\n [client, ephemeral, refreshSessions, storageScope, thinking],\n );\n\n const send = React.useCallback(\n (text: string, attachments?: unknown[]) => {\n run(text, attachments?.length ? { attachments } : {});\n },\n [run],\n );\n\n const sendAction = React.useCallback(\n (a2uiAction: NonNullable<Parameters<AgentChatClient[\"send\"]>[0][\"a2uiAction\"]>) => {\n run(\"\", { a2uiAction });\n },\n [run],\n );\n\n const abort = React.useCallback(() => {\n const { runId, sessionKey } = stateRef.current;\n dispatch({ kind: \"abort\" });\n void client\n .abort({\n ...(runId ? { runId } : {}),\n ...(sessionKey ? { sessionKey } : {}),\n })\n .catch(() => {\n // Best-effort: local state is already reset, and dropping the stream\n // aborts the run server-side anyway.\n });\n }, [client]);\n\n const resolveApproval = React.useCallback(\n (id: string, decision: AgentApprovalDecision) => {\n const request = stateRef.current.approvals.find((r) => r.id === id);\n if (!request) {\n return;\n }\n dispatch({ kind: \"approval/resolved\", id });\n // Bound to the run and the session the REQUEST arrived on, never to\n // whatever is in flight now. The two come apart the moment a user opens\n // another session while a command waits for an answer.\n void client\n .resolveApproval(id, decision, {\n runId: request.runId,\n sessionKey: request.sessionKey,\n })\n .catch((err: unknown) => {\n dispatch({ kind: \"approval/failed\", request, message: errorText(err) });\n });\n },\n [client],\n );\n\n const newSession = React.useCallback(() => {\n setSession(mintSessionKey(client.agentId));\n }, [client, setSession]);\n\n const resumeSession = React.useCallback(\n (key: string) => {\n setSession(key);\n loadHistory(key);\n },\n [loadHistory, setSession],\n );\n\n const loadOlder = React.useCallback(() => {\n const current = stateRef.current;\n if (!current.sessionKey || current.loadingHistory || !current.hasMoreHistory) {\n return;\n }\n dispatch({ kind: \"history/loading\" });\n void client\n .request<AgentChatHistoryResponse>(\"chat.history\", {\n sessionKey: current.sessionKey,\n limit: 100,\n // Records, not bubbles. See `historyCount`.\n offset: current.historyCount,\n })\n .then((response) => {\n const records = response?.messages ?? [];\n dispatch({\n kind: \"history/older\",\n messages: parseHistoryMessages(records),\n hasMore: response?.hasMore ?? false,\n records: records.length,\n });\n })\n .catch(() => dispatch({ kind: \"history/failed\" }));\n }, [client]);\n\n return {\n ...state,\n send,\n sendAction,\n abort,\n resolveApproval,\n newSession,\n resumeSession,\n loadOlder,\n refreshSessions,\n };\n}\n\nfunction errorText(err: unknown): string {\n return err instanceof Error ? err.message : \"The agent could not be reached\";\n}\n"],"mappings":"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"}