bazilion 0.14.2 → 0.15.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 (119) hide show
  1. package/dist/cli.js +1126 -334
  2. package/dist/cli.js.map +1 -1
  3. package/dist/daemon.js +8180 -3651
  4. package/dist/daemon.js.map +1 -1
  5. package/dist/migrations/0001_init.sql +171 -3
  6. package/dist/web/client/assets/ChatPane-55j13bBj.js +74 -0
  7. package/dist/web/client/assets/{ConfirmDialog-_bmG-wVG.js → ConfirmDialog-CGZzom9E.js} +1 -1
  8. package/dist/web/client/assets/{FieldRow-Do1cBkRc.js → FieldRow-DYNU9gWb.js} +1 -1
  9. package/dist/web/client/assets/{ProductionTeamPolicyEditor-XP-QJul4.js → ProductionTeamPolicyEditor-DT5Sl8jd.js} +1 -1
  10. package/dist/web/client/assets/{ProfileCommunicationEditor-CwdFPdpk.js → ProfileCommunicationEditor-TyCU5vXX.js} +1 -1
  11. package/dist/web/client/assets/ResultCard-Cps_lXVN.js +1 -0
  12. package/dist/web/client/assets/ResultSourceConversation-CGhKTSTD.js +1 -0
  13. package/dist/web/client/assets/TeamTabs-BZHyRnbd.js +1 -0
  14. package/dist/web/client/assets/{TemplatesTabs-D4WHvs6b.js → TemplatesTabs-BHzkPMRC.js} +1 -1
  15. package/dist/web/client/assets/{UnsavedChangesGuard-B1aLqiK5.js → UnsavedChangesGuard-CFQcKsrf.js} +1 -1
  16. package/dist/web/client/assets/_id-9nCIhbVd.js +1 -0
  17. package/dist/web/client/assets/{_id-3w4PFfma.js → _id-BSu3AqKA.js} +1 -1
  18. package/dist/web/client/assets/_id-CGd21j3k.js +1 -0
  19. package/dist/web/client/assets/{_id-BH3v_vI_.js → _id-D_eKokkd.js} +1 -1
  20. package/dist/web/client/assets/{_id-Cx7o4Zmf.js → _id-SRMFlIgP.js} +1 -1
  21. package/dist/web/client/assets/{activity-A_HrG1Tv.js → activity-Cn2-9PpZ.js} +1 -1
  22. package/dist/web/client/assets/{agents-BeeFCt2c.js → agents-CCmiZek6.js} +1 -1
  23. package/dist/web/client/assets/{agents-C8OVKq3N.js → agents-NY3VrZgB.js} +1 -1
  24. package/dist/web/client/assets/{approvals-BIdZ9BSn.js → approvals-CPDAPgrM.js} +1 -1
  25. package/dist/web/client/assets/{attention-B9bUrxiU.js → attention-Qra_9_OI.js} +1 -1
  26. package/dist/web/client/assets/chevron-right-DXE4CWf2.js +1 -0
  27. package/dist/web/client/assets/{config-By86S7k-.js → config-D8EO0LPB.js} +1 -1
  28. package/dist/web/client/assets/{inbox-DlXQCJLe.js → inbox-C5W-sm1q.js} +1 -1
  29. package/dist/web/client/assets/{index-CLdmsmiN.js → index-BUhhk_u9.js} +2 -2
  30. package/dist/web/client/assets/{learning-BqZIw2nA.js → learning-Dr-7_7J4.js} +1 -1
  31. package/dist/web/client/assets/login-Bu5UxbJh.js +1 -0
  32. package/dist/web/client/assets/{mcp-DKSY2yVa.js → mcp-BueuwQiv.js} +1 -1
  33. package/dist/web/client/assets/{members-9GuOiq6J.js → members-ZikuYNcy.js} +1 -1
  34. package/dist/web/client/assets/{memory-DiGMI6FC.js → memory-1JUJlPrn.js} +1 -1
  35. package/dist/web/client/assets/{policy-TBGQffw5.js → policy-DbkhsFNA.js} +1 -1
  36. package/dist/web/client/assets/results-mMr8Ar2u.js +1 -0
  37. package/dist/web/client/assets/routes-DJYB-MS9.js +1 -0
  38. package/dist/web/client/assets/{services-1Z398CES.js → services-kfQA9cpJ.js} +1 -1
  39. package/dist/web/client/assets/{skills-BP2gciFt.js → skills-C6pcgxaC.js} +1 -1
  40. package/dist/web/client/assets/styles-CcNf8jT_.css +2 -0
  41. package/dist/web/client/assets/{teams-tNoMDJ_M.js → teams-C6_Noz5X.js} +1 -1
  42. package/dist/web/client/assets/{teams-DBIivGT7.js → teams-LB0KBXY9.js} +1 -1
  43. package/dist/web/client/assets/telegram-A2jaI1QM.js +1 -0
  44. package/dist/web/client/assets/{tokens-eDWCGQQ2.js → tokens-BGlUpJcL.js} +1 -1
  45. package/dist/web/client/assets/{triggers-DZME3IcK.js → triggers-C54_feGg.js} +1 -1
  46. package/dist/web/client/assets/{welcome-B49sh_uf.js → welcome-DyUIswRi.js} +1 -1
  47. package/dist/web/client/assets/wire-constants-CBD-lTaa.js +1 -0
  48. package/dist/web/client/assets/{x-CHc7_-TH.js → x-BY2MPLr9.js} +1 -1
  49. package/dist/web/server/assets/{ChatPane-CgSe7tsy.js → ChatPane-DJ30FdFG.js} +1122 -107
  50. package/dist/web/server/assets/ResultCard-B3zD9N0t.js +143 -0
  51. package/dist/web/server/assets/ResultSourceConversation-zYogeOWp.js +64 -0
  52. package/dist/web/server/assets/{TeamTabs-Co3n2exj.js → TeamTabs-TCKjCvMA.js} +4 -0
  53. package/dist/web/server/assets/{_id-sNu5aXZt.js → _id-1LwoHhJg.js} +2 -1
  54. package/dist/web/server/assets/{_id-BeKhPKw3.js → _id-1hTvnKLN2.js} +7 -3
  55. package/dist/web/server/assets/{_id-BAA9KJBU.js → _id-B2EEyStS.js} +1 -1
  56. package/dist/web/server/assets/_id-BDEpe6k0.js +6 -0
  57. package/dist/web/server/assets/{_id-BqWTB8Wl.js → _id-B_-CDHOi2.js} +1 -1
  58. package/dist/web/server/assets/{_id-DxCnEFpm.js → _id-BvSWsPI5.js} +3 -4
  59. package/dist/web/server/assets/{_id-ndxFHx0t.js → _id-CDBtO_jL.js} +2 -2
  60. package/dist/web/server/assets/{_id-DvlDkk04.js → _id-DDNVQx3y.js} +1 -1
  61. package/dist/web/server/assets/{_id-BYKpT2Tu.js → _id-DFHB9JFQ.js} +1 -1
  62. package/dist/web/server/assets/{_id-HMy7jM3P.js → _id-DxFLSIio.js} +1 -1
  63. package/dist/web/server/assets/{_id-DYU6uIBx.js → _id-T_8btpbB.js} +1 -1
  64. package/dist/web/server/assets/_id-wyjrf0JG.js +17 -0
  65. package/dist/web/server/assets/{_tanstack-start-manifest_v-Duhf0z7H.js → _tanstack-start-manifest_v-DP4ZUBw3.js} +102 -75
  66. package/dist/web/server/assets/{activity-BWv1goK6.js → activity-5fWKPDxj.js} +2 -2
  67. package/dist/web/server/assets/{activity-LjpblbDL.js → activity-C88qrNqv.js} +1 -1
  68. package/dist/web/server/assets/{activity-9VP6QDTH.js → activity-D73w4mkZ.js} +1 -1
  69. package/dist/web/server/assets/{agents-DjyQk94u.js → agents-DB5hCSAI.js} +1 -1
  70. package/dist/web/server/assets/{agents-BJAv919x.js → agents-DRSo7lRX.js} +1 -1
  71. package/dist/web/server/assets/{approvals-BYi84gwl.js → approvals-4y9bZF4r.js} +1 -1
  72. package/dist/web/server/assets/{approvals-7JOoBchn.js → approvals-DTdrltyk.js} +1 -1
  73. package/dist/web/server/assets/{approvals-Bz123liE.js → approvals-DXoN-T57.js} +15 -1
  74. package/dist/web/server/assets/{attention-UhqPK_Ro.js → attention-BFx8ZkmH.js} +1 -1
  75. package/dist/web/server/assets/{auth-7BHTH-KS.js → auth-_eaCwhyq.js} +1 -1
  76. package/dist/web/server/assets/{config-BMiajTZP.js → config-B7JIfv_7.js} +1 -1
  77. package/dist/web/server/assets/{daemon-client-CI5NHwNC.js → daemon-client-B06W1Qa2.js} +54 -0
  78. package/dist/web/server/assets/{inbox-Dv55rGYq.js → inbox-YRpDQcvp.js} +1 -1
  79. package/dist/web/server/assets/{learning-C2upa1CE.js → learning-UBZb6h6M.js} +1 -1
  80. package/dist/web/server/assets/{login-BNj3U6ok.js → login-B_-9L8TU.js} +7 -2
  81. package/dist/web/server/assets/{login-Be8eP4Jp.js → login-rDox7PRD.js} +5 -2
  82. package/dist/web/server/assets/{mcp-BvnvlVFd.js → mcp-Dv6RXMaS.js} +1 -1
  83. package/dist/web/server/assets/{members-BP3UTqu1.js → members-BFTKSlOe.js} +1 -1
  84. package/dist/web/server/assets/{members-DiYtr-zm.js → members-CD3GiTfF.js} +1 -1
  85. package/dist/web/server/assets/{members-DHYTVgwb.js → members-cc5SeEaW.js} +2 -2
  86. package/dist/web/server/assets/{memory-CkXUgf5s.js → memory-C5i_GxlY.js} +2 -2
  87. package/dist/web/server/assets/{memory-DTFEmfF3.js → memory-CVqnyUaM.js} +1 -1
  88. package/dist/web/server/assets/{memory-BqV-Afy1.js → memory-D0PQgZAn.js} +1 -1
  89. package/dist/web/server/assets/{policy-18fdTFO4.js → policy-C-p8HIhA.js} +1 -1
  90. package/dist/web/server/assets/{policy-Bvvav8fh.js → policy-Dgjb0_gK.js} +2 -2
  91. package/dist/web/server/assets/{policy-BddK4xom.js → policy-bLKwvnja.js} +1 -1
  92. package/dist/web/server/assets/results-Cfev4KNt.js +12 -0
  93. package/dist/web/server/assets/results-Cj-Dlw3C.js +173 -0
  94. package/dist/web/server/assets/results-CwEQ5EUL.js +12 -0
  95. package/dist/web/server/assets/{router-CY_pCgP1.js → router-d3sTMpMX.js} +60 -43
  96. package/dist/web/server/assets/{routes-DAU2g7Uz.js → routes-B765DVhj.js} +1 -1
  97. package/dist/web/server/assets/{routes-BgHisAxH.js → routes-MIZz6uMr.js} +2 -2
  98. package/dist/web/server/assets/{routes-COtkyZG1.js → routes-cW4MWU18.js} +3 -3
  99. package/dist/web/server/assets/{services-DozuJtzX.js → services-BCbdn3R9.js} +1 -1
  100. package/dist/web/server/assets/{skills-DfGSJTCB.js → skills-Bpoa8MIp.js} +1 -1
  101. package/dist/web/server/assets/{teams-be05ci17.js → teams-CXzwqzcE.js} +1 -1
  102. package/dist/web/server/assets/{teams-DPnAX4wD.js → teams-hWGjbpUA.js} +1 -1
  103. package/dist/web/server/assets/{telegram-Dj4Wwhd6.js → telegram-CoNeOgqZ.js} +465 -1
  104. package/dist/web/server/assets/{telegram-1AWL6lFw.js → telegram-DhpvtGUc.js} +1 -1
  105. package/dist/web/server/assets/{telegram-DbOFpTFm.js → telegram-Zd1rIZxg.js} +1 -1
  106. package/dist/web/server/assets/{tokens-DjM8GsgS.js → tokens-Bjp19Vkn.js} +1 -1
  107. package/dist/web/server/assets/{triggers-Nezfw-Bd.js → triggers-DJlcvXzn.js} +1 -1
  108. package/dist/web/server/server.js +31 -27
  109. package/dist/worker.js +294 -104
  110. package/dist/worker.js.map +1 -1
  111. package/package.json +3 -3
  112. package/dist/web/client/assets/TeamTabs-DIRiKcqx.js +0 -1
  113. package/dist/web/client/assets/_id-DIiVVxBl.js +0 -1
  114. package/dist/web/client/assets/chevron-right-VWrk_dTX.js +0 -1
  115. package/dist/web/client/assets/login-CCMPXiny.js +0 -1
  116. package/dist/web/client/assets/routes-g3yUOhCb.js +0 -1
  117. package/dist/web/client/assets/styles-B5vcgXln.css +0 -2
  118. package/dist/web/client/assets/telegram-CJG_DFdI.js +0 -1
  119. package/dist/web/client/assets/wire-constants-zZ7Wjb9C.js +0 -74
@@ -1,6 +1,6 @@
1
1
  import { t as Button } from "./Button-Dsnw22OF.js";
2
- import { t as ConfirmDialog } from "./ConfirmDialog-C-MPYYSU.js";
3
- import { useCallback, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
2
+ import { t as ResultCard } from "./ResultCard-B3zD9N0t.js";
3
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import DOMPurify from "dompurify";
6
6
  import { marked } from "marked";
@@ -35,6 +35,952 @@ function escapeHtml(s) {
35
35
  return String(s).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
36
36
  }
37
37
  //#endregion
38
+ //#region src/components/ConversationLibrary.tsx
39
+ function ConversationLibrary({ agentId, turnBusy, onCreated, renderHistory, initialConversationId }) {
40
+ const [library, setLibrary] = useState(null);
41
+ const [offset, setOffset] = useState(0);
42
+ const [refresh, setRefresh] = useState(0);
43
+ const [selected, setSelected] = useState(null);
44
+ const [history, setHistory] = useState(null);
45
+ const [title, setTitle] = useState("");
46
+ const [error, setError] = useState("");
47
+ const [historyError, setHistoryError] = useState("");
48
+ const [busy, setBusy] = useState(false);
49
+ const pendingKey = `bazilion:conversation-create:${agentId}`;
50
+ const pendingCreate = useRef(null);
51
+ useEffect(() => {
52
+ try {
53
+ pendingCreate.current = JSON.parse(sessionStorage.getItem(pendingKey) ?? "null");
54
+ } catch {
55
+ pendingCreate.current = null;
56
+ }
57
+ }, [pendingKey]);
58
+ function clearPending() {
59
+ pendingCreate.current = null;
60
+ sessionStorage.removeItem(pendingKey);
61
+ }
62
+ const base = `/api/agents/${encodeURIComponent(agentId)}/conversations`;
63
+ useEffect(() => {
64
+ if (!initialConversationId) return;
65
+ const controller = new AbortController();
66
+ fetch(`${base}/${encodeURIComponent(initialConversationId)}`, { signal: controller.signal }).then(async (response) => {
67
+ if (!response.ok) throw new Error("Retained conversation is unavailable");
68
+ const body = await response.json();
69
+ if (!controller.signal.aborted) {
70
+ setSelected(body.conversation);
71
+ setTitle(body.conversation.title);
72
+ }
73
+ }).catch((error) => {
74
+ if (!controller.signal.aborted) setError(error.message);
75
+ });
76
+ return () => controller.abort();
77
+ }, [base, initialConversationId]);
78
+ useEffect(() => {
79
+ const controller = new AbortController();
80
+ setLibrary(null);
81
+ fetch(`${base}?limit=20&offset=${offset}`, { signal: controller.signal }).then(async (response) => {
82
+ if (!response.ok) throw new Error("Could not load conversations.");
83
+ const body = await response.json();
84
+ if (!controller.signal.aborted) setLibrary(body);
85
+ }).catch((error) => {
86
+ if (!controller.signal.aborted) setError(error.message);
87
+ });
88
+ return () => controller.abort();
89
+ }, [
90
+ base,
91
+ offset,
92
+ refresh
93
+ ]);
94
+ useEffect(() => {
95
+ if (!selected) return;
96
+ const controller = new AbortController();
97
+ setHistory(null);
98
+ setHistoryError("");
99
+ fetch(`${base}/${encodeURIComponent(selected.id)}`, { signal: controller.signal }).then(async (response) => {
100
+ if (!response.ok) throw new Error("This conversation history is unavailable. You can still start a new conversation.");
101
+ const body = await response.json();
102
+ if (!controller.signal.aborted) setHistory(body.messages);
103
+ }).catch((error) => {
104
+ if (!controller.signal.aborted) setHistoryError(error.message);
105
+ });
106
+ return () => controller.abort();
107
+ }, [base, selected?.id]);
108
+ async function create() {
109
+ if (!library || busy || turnBusy) return;
110
+ setBusy(true);
111
+ setError("");
112
+ const request = pendingCreate.current ?? {
113
+ requestId: crypto.randomUUID(),
114
+ expectedSelection: library.selection
115
+ };
116
+ pendingCreate.current = request;
117
+ try {
118
+ sessionStorage.setItem(pendingKey, JSON.stringify(request));
119
+ const response = await fetch(base, {
120
+ method: "POST",
121
+ headers: { "content-type": "application/json" },
122
+ body: JSON.stringify(request)
123
+ });
124
+ const body = await response.json();
125
+ if (!response.ok) {
126
+ if (response.status === 409 || response.status === 400) clearPending();
127
+ throw new Error(body.error ?? "Could not create conversation.");
128
+ }
129
+ clearPending();
130
+ setSelected(null);
131
+ setOffset(0);
132
+ setRefresh((value) => value + 1);
133
+ await onCreated();
134
+ } catch (error) {
135
+ setError(error instanceof Error ? error.message : "Could not create conversation.");
136
+ } finally {
137
+ setBusy(false);
138
+ }
139
+ }
140
+ async function rename() {
141
+ if (!selected || busy) return;
142
+ setBusy(true);
143
+ setError("");
144
+ try {
145
+ const response = await fetch(`${base}/${encodeURIComponent(selected.id)}`, {
146
+ method: "PATCH",
147
+ headers: { "content-type": "application/json" },
148
+ body: JSON.stringify({
149
+ title,
150
+ expectedTitleRevision: selected.titleRevision
151
+ })
152
+ });
153
+ const body = await response.json();
154
+ if (!response.ok) throw new Error(body.error ?? "Could not rename conversation.");
155
+ setSelected(body.conversation);
156
+ setRefresh((value) => value + 1);
157
+ } catch (error) {
158
+ setError(error instanceof Error ? error.message : "Could not rename conversation.");
159
+ } finally {
160
+ setBusy(false);
161
+ }
162
+ }
163
+ return /* @__PURE__ */ jsxs("section", {
164
+ "aria-live": "off",
165
+ className: "my-3 min-w-0 space-y-4 rounded-lg border border-fawn bg-ivory p-4",
166
+ "aria-label": "Conversation library",
167
+ children: [
168
+ /* @__PURE__ */ jsxs("div", {
169
+ className: "flex flex-col items-start gap-3 sm:flex-row sm:items-center sm:justify-between",
170
+ children: [/* @__PURE__ */ jsx("h2", {
171
+ className: "m-0 text-lg",
172
+ children: "Conversations"
173
+ }), /* @__PURE__ */ jsx(Button, {
174
+ variant: "primary",
175
+ disabled: !library || busy || turnBusy,
176
+ onClick: () => void create(),
177
+ children: "New conversation"
178
+ })]
179
+ }),
180
+ /* @__PURE__ */ jsx("p", {
181
+ className: "m-0 text-sm",
182
+ children: "Starting a new conversation keeps previous history and saved files. Viewing history does not change the active conversation."
183
+ }),
184
+ turnBusy && /* @__PURE__ */ jsx("p", {
185
+ role: "status",
186
+ children: "Wait for the current turn to finish before starting a new conversation."
187
+ }),
188
+ error && /* @__PURE__ */ jsxs("p", {
189
+ role: "alert",
190
+ children: [
191
+ error,
192
+ " ",
193
+ /* @__PURE__ */ jsx(Button, {
194
+ variant: "ghost",
195
+ disabled: busy,
196
+ onClick: () => {
197
+ setError("");
198
+ setRefresh((value) => value + 1);
199
+ },
200
+ children: "Refresh library"
201
+ })
202
+ ]
203
+ }),
204
+ !library ? /* @__PURE__ */ jsx("p", {
205
+ role: "status",
206
+ children: "Loading conversations…"
207
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
208
+ !library.total && /* @__PURE__ */ jsx("p", { children: "No conversations yet." }),
209
+ /* @__PURE__ */ jsx("ul", {
210
+ className: "mt-4 grid list-none gap-2 p-0",
211
+ children: library.conversations.map((item) => /* @__PURE__ */ jsxs("li", {
212
+ className: "flex min-w-0 flex-col items-start gap-3 rounded border border-fawn p-3 sm:flex-row sm:items-center sm:justify-between",
213
+ children: [/* @__PURE__ */ jsxs("span", {
214
+ className: "flex min-w-0 flex-1 flex-wrap items-center gap-2",
215
+ children: [/* @__PURE__ */ jsx("span", {
216
+ className: "min-w-0 break-words [overflow-wrap:anywhere]",
217
+ children: item.title
218
+ }), item.id === library.selection.conversationId && /* @__PURE__ */ jsx("strong", {
219
+ className: "shrink-0 rounded-full bg-frost px-2 py-0.5 text-xs",
220
+ children: "Active"
221
+ })]
222
+ }), /* @__PURE__ */ jsxs(Button, {
223
+ variant: "ghost",
224
+ className: "max-w-full whitespace-normal break-words text-left sm:max-w-[50%]",
225
+ disabled: busy,
226
+ "aria-pressed": selected?.id === item.id,
227
+ onClick: () => {
228
+ setSelected(item);
229
+ setTitle(item.title);
230
+ },
231
+ children: ["View ", item.title]
232
+ })]
233
+ }, item.id))
234
+ }),
235
+ /* @__PURE__ */ jsxs("div", {
236
+ className: "mt-4 flex flex-wrap items-center gap-2",
237
+ children: [
238
+ /* @__PURE__ */ jsx(Button, {
239
+ variant: "ghost",
240
+ disabled: busy || offset === 0,
241
+ onClick: () => setOffset(Math.max(0, offset - 20)),
242
+ children: "Previous"
243
+ }),
244
+ /* @__PURE__ */ jsx(Button, {
245
+ variant: "ghost",
246
+ disabled: busy || offset + library.conversations.length >= library.total,
247
+ onClick: () => setOffset(offset + 20),
248
+ children: "Next"
249
+ }),
250
+ /* @__PURE__ */ jsxs("span", {
251
+ className: "text-sm",
252
+ children: [library.total, " conversations"]
253
+ })
254
+ ]
255
+ })
256
+ ] }),
257
+ selected && /* @__PURE__ */ jsxs("section", {
258
+ className: "space-y-4 border-t border-fawn pt-4",
259
+ "aria-label": "Retained conversation",
260
+ children: [
261
+ /* @__PURE__ */ jsxs("div", {
262
+ className: "grid min-w-0 grid-cols-2 items-end gap-2 sm:grid-cols-[minmax(0,1fr)_auto_auto]",
263
+ children: [
264
+ /* @__PURE__ */ jsxs("label", {
265
+ className: "col-span-2 m-0 grid min-w-0 gap-2 sm:col-span-1",
266
+ children: ["Conversation title", /* @__PURE__ */ jsx("input", {
267
+ className: "h-11",
268
+ value: title,
269
+ maxLength: 200,
270
+ onChange: (event) => setTitle(event.target.value)
271
+ })]
272
+ }),
273
+ /* @__PURE__ */ jsx(Button, {
274
+ variant: "ghost",
275
+ className: "h-11",
276
+ disabled: busy || !title.trim(),
277
+ onClick: () => void rename(),
278
+ children: "Rename"
279
+ }),
280
+ /* @__PURE__ */ jsx(Button, {
281
+ variant: "ghost",
282
+ className: "h-11",
283
+ disabled: busy,
284
+ onClick: () => setSelected(null),
285
+ children: "Close history"
286
+ })
287
+ ]
288
+ }),
289
+ /* @__PURE__ */ jsx("p", {
290
+ className: "m-0 text-sm",
291
+ children: "Viewing retained history. New messages still go to the active conversation."
292
+ }),
293
+ historyError ? /* @__PURE__ */ jsx("p", {
294
+ role: "alert",
295
+ children: historyError
296
+ }) : history === null ? /* @__PURE__ */ jsx("p", {
297
+ role: "status",
298
+ children: "Loading history…"
299
+ }) : history.length ? renderHistory(history) : /* @__PURE__ */ jsx("p", { children: "This conversation is empty." })
300
+ ]
301
+ })
302
+ ]
303
+ });
304
+ }
305
+ //#endregion
306
+ //#region src/lib/queue-journal.ts
307
+ /** Store bytes before submission so a reload can retry the exact same attempt. */
308
+ async function queueJournal(agentId, value, clearRequestId) {
309
+ return new Promise((resolve, reject) => {
310
+ const opening = indexedDB.open("bazilion-queue-attempts", 1);
311
+ opening.onupgradeneeded = () => {
312
+ opening.result.createObjectStore("requests", { keyPath: "input.requestId" }).createIndex("agentId", "agentId");
313
+ };
314
+ opening.onerror = () => reject(/* @__PURE__ */ new Error("Local queue recovery storage is unavailable"));
315
+ opening.onblocked = () => reject(/* @__PURE__ */ new Error("Close other Bazilion tabs to open queue recovery storage"));
316
+ opening.onsuccess = () => {
317
+ const db = opening.result;
318
+ const tx = db.transaction("requests", value === void 0 ? "readonly" : "readwrite");
319
+ const store = tx.objectStore("requests");
320
+ if (value === null && !clearRequestId) {
321
+ db.close();
322
+ reject(/* @__PURE__ */ new Error("Queue request identity is required to clear recovery state"));
323
+ return;
324
+ }
325
+ const request = value === void 0 ? store.index("agentId").get(agentId) : value === null ? store.delete(clearRequestId) : store.put(value);
326
+ tx.oncomplete = () => {
327
+ db.close();
328
+ resolve(value === void 0 ? request.result ?? null : value);
329
+ };
330
+ tx.onerror = tx.onabort = () => {
331
+ db.close();
332
+ reject(/* @__PURE__ */ new Error("Unable to retain queue request locally; it was not acknowledged"));
333
+ };
334
+ };
335
+ });
336
+ }
337
+ //#endregion
338
+ //#region src/components/UserQueuePanel.tsx
339
+ var UserQueuePanel = forwardRef(function UserQueuePanel({ agentId, selection, onQueueMode, onViewConversation }, ref) {
340
+ const base = `/api/agents/${encodeURIComponent(agentId)}/queue`;
341
+ const [state, setState] = useState(null);
342
+ const [error, setError] = useState("");
343
+ const [notice, setNotice] = useState("");
344
+ const [busy, setBusy] = useState(false);
345
+ const [ready, setReady] = useState(false);
346
+ const readyRef = useRef(false);
347
+ const [pending, setPending] = useState(null);
348
+ const pendingRef = useRef(null);
349
+ const locked = useRef(false);
350
+ const mounted = useRef(true);
351
+ const refreshGeneration = useRef(0);
352
+ useEffect(() => {
353
+ mounted.current = true;
354
+ return () => {
355
+ mounted.current = false;
356
+ };
357
+ }, []);
358
+ const [editing, setEditing] = useState(null);
359
+ const [text, setText] = useState("");
360
+ const [editFiles, setEditFiles] = useState([]);
361
+ const [history, setHistory] = useState(false);
362
+ const [offset, setOffset] = useState(0);
363
+ const modeRef = useRef(onQueueMode);
364
+ modeRef.current = onQueueMode;
365
+ async function refresh() {
366
+ const generation = ++refreshGeneration.current;
367
+ const response = await fetch(`${base}?all=${history ? 1 : 0}&offset=${offset}`);
368
+ if (!response.ok) throw new Error("Queue could not be loaded");
369
+ const body = await response.json();
370
+ const activeResponse = history ? await fetch(base) : null;
371
+ const active = activeResponse?.ok ? await activeResponse.json() : body;
372
+ if (mounted.current && generation === refreshGeneration.current) {
373
+ setState(body);
374
+ if (readyRef.current) modeRef.current(active.total > 0 || active.control.paused || !!pendingRef.current);
375
+ }
376
+ return body;
377
+ }
378
+ useEffect(() => {
379
+ let live = true;
380
+ queueJournal(agentId).then((value) => {
381
+ if (live) {
382
+ pendingRef.current = value;
383
+ setPending(value);
384
+ readyRef.current = true;
385
+ setReady(true);
386
+ refresh().catch((e) => setError(e.message));
387
+ }
388
+ }).catch((e) => {
389
+ if (live) setError(e.message);
390
+ });
391
+ return () => {
392
+ live = false;
393
+ };
394
+ }, [agentId]);
395
+ useEffect(() => {
396
+ refresh().catch((e) => setError(e.message));
397
+ const timer = setInterval(() => {
398
+ refresh().catch((e) => setError(e.message));
399
+ }, 2e3);
400
+ return () => clearInterval(timer);
401
+ }, [
402
+ agentId,
403
+ history,
404
+ offset
405
+ ]);
406
+ async function clearPending(record) {
407
+ await queueJournal(agentId, null, record.input.requestId);
408
+ const remaining = await queueJournal(agentId);
409
+ pendingRef.current = remaining;
410
+ setPending(remaining);
411
+ }
412
+ async function submit(record, retry = false) {
413
+ if (locked.current || !ready) return false;
414
+ if (pendingRef.current && !retry) {
415
+ setError("Resolve the unacknowledged queue request before sending another.");
416
+ return false;
417
+ }
418
+ locked.current = true;
419
+ setBusy(true);
420
+ setError("");
421
+ setNotice("");
422
+ try {
423
+ await queueJournal(agentId, record);
424
+ pendingRef.current = record;
425
+ setPending(record);
426
+ const response = await fetch(record.replacementId ? `${base}/${encodeURIComponent(record.replacementId)}` : base, {
427
+ method: record.replacementId ? "PATCH" : "POST",
428
+ headers: { "content-type": "application/json" },
429
+ body: JSON.stringify(record.input)
430
+ });
431
+ if (!response.ok) {
432
+ const detail = await response.json().catch(() => ({}));
433
+ if (response.status >= 400 && response.status < 500) await clearPending(record);
434
+ throw new Error(detail.error ?? "Queue acknowledgement unavailable; retry this same request.");
435
+ }
436
+ const item = await response.json();
437
+ await clearPending(record);
438
+ setNotice(`Follow-up ${item.status}.`);
439
+ setEditing(null);
440
+ await refresh().catch(() => setError("Input acknowledged; queue display could not refresh."));
441
+ return true;
442
+ } catch (e) {
443
+ setError(e instanceof Error ? e.message : "Queue request failed");
444
+ return false;
445
+ } finally {
446
+ locked.current = false;
447
+ setBusy(false);
448
+ }
449
+ }
450
+ async function action(path, body, method = "POST") {
451
+ if (locked.current) return false;
452
+ locked.current = true;
453
+ setBusy(true);
454
+ setError("");
455
+ try {
456
+ const response = await fetch(`${base}${path}`, {
457
+ method,
458
+ headers: { "content-type": "application/json" },
459
+ body: JSON.stringify(body)
460
+ });
461
+ if (!response.ok) {
462
+ const detail = await response.json();
463
+ throw new Error(detail.error ?? "Queue changed; refresh and try again");
464
+ }
465
+ await refresh();
466
+ return true;
467
+ } catch (e) {
468
+ setError(e instanceof Error ? e.message : "Queue action failed");
469
+ await refresh().catch(() => {});
470
+ return false;
471
+ } finally {
472
+ locked.current = false;
473
+ setBusy(false);
474
+ }
475
+ }
476
+ useImperativeHandle(ref, () => ({
477
+ async enqueue(message, attachments, expectedSelection) {
478
+ if (!expectedSelection) {
479
+ setError("Refresh conversation history before queuing input.");
480
+ return false;
481
+ }
482
+ return submit({
483
+ agentId,
484
+ input: {
485
+ requestId: crypto.randomUUID(),
486
+ message,
487
+ attachments,
488
+ expectedSelection
489
+ }
490
+ });
491
+ },
492
+ async stop() {
493
+ if (!state) {
494
+ setError("Load queue controls before stopping.");
495
+ return false;
496
+ }
497
+ return action("/stop", { expectedRevision: state.control.revision });
498
+ }
499
+ }));
500
+ async function edit(item) {
501
+ try {
502
+ const response = await fetch(`${base}/${encodeURIComponent(item.id)}/input`);
503
+ if (!response.ok) throw new Error("Retained input is unavailable");
504
+ const input = await response.json();
505
+ setEditing(item);
506
+ setText(input.message);
507
+ setEditFiles(input.attachments);
508
+ } catch (e) {
509
+ setError(e instanceof Error ? e.message : "Could not edit");
510
+ }
511
+ }
512
+ return /* @__PURE__ */ jsxs("section", {
513
+ "aria-label": "Follow-up queue",
514
+ className: "max-h-[45%] min-w-0 shrink-0 space-y-3 overflow-y-auto border-t border-frost px-5 py-4 text-sm",
515
+ children: [
516
+ /* @__PURE__ */ jsxs("div", {
517
+ className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
518
+ children: [/* @__PURE__ */ jsxs("strong", {
519
+ className: "shrink-0",
520
+ children: ["Follow-ups ", state?.control.paused ? "· Paused" : ""]
521
+ }), /* @__PURE__ */ jsxs("div", {
522
+ className: "flex flex-wrap items-center gap-2",
523
+ children: [
524
+ state && /* @__PURE__ */ jsx(Button, {
525
+ variant: "ghost",
526
+ disabled: busy,
527
+ onClick: () => void action("/control", {
528
+ paused: !state.control.paused,
529
+ expectedRevision: state.control.revision
530
+ }),
531
+ children: state.control.paused ? "Resume queue" : "Pause queue"
532
+ }),
533
+ state && /* @__PURE__ */ jsx(Button, {
534
+ variant: "danger",
535
+ disabled: busy,
536
+ onClick: () => void action("/stop", { expectedRevision: state.control.revision }),
537
+ children: "Stop and pause"
538
+ }),
539
+ /* @__PURE__ */ jsx(Button, {
540
+ variant: "ghost",
541
+ onClick: () => {
542
+ setHistory(!history);
543
+ setOffset(0);
544
+ },
545
+ children: history ? "Pending" : "History"
546
+ })
547
+ ]
548
+ })]
549
+ }),
550
+ error && /* @__PURE__ */ jsx("p", {
551
+ role: "alert",
552
+ className: "m-0 break-words text-danger",
553
+ children: error
554
+ }),
555
+ notice && /* @__PURE__ */ jsx("p", {
556
+ role: "status",
557
+ className: "m-0 text-mocha",
558
+ children: notice
559
+ }),
560
+ pending && /* @__PURE__ */ jsxs("div", {
561
+ role: "status",
562
+ className: "flex min-w-0 flex-col gap-3 rounded-lg border border-frost bg-ivory p-3",
563
+ children: [
564
+ /* @__PURE__ */ jsx("p", { children: "A queue request has no confirmed acknowledgement. Retrying uses the same request and files." }),
565
+ /* @__PURE__ */ jsx("p", {
566
+ className: "truncate",
567
+ children: pending.input.message
568
+ }),
569
+ /* @__PURE__ */ jsx(Button, {
570
+ variant: "primary",
571
+ disabled: busy,
572
+ onClick: () => void submit(pending, true),
573
+ children: "Retry saved request"
574
+ })
575
+ ]
576
+ }),
577
+ /* @__PURE__ */ jsxs("div", {
578
+ className: "min-w-0 space-y-3",
579
+ children: [state?.items.map((item) => /* @__PURE__ */ jsxs("article", {
580
+ className: "flex min-w-0 flex-col gap-3 rounded-lg border border-frost bg-ivory p-3",
581
+ children: [
582
+ /* @__PURE__ */ jsxs("div", {
583
+ className: "flex flex-wrap items-center justify-between gap-3",
584
+ children: [/* @__PURE__ */ jsxs("div", {
585
+ className: "flex flex-wrap items-center gap-2",
586
+ children: [/* @__PURE__ */ jsx("strong", {
587
+ className: "rounded-full bg-frost px-2 py-1 text-xs capitalize",
588
+ children: item.status
589
+ }), /* @__PURE__ */ jsx("span", {
590
+ className: "text-xs text-mocha",
591
+ children: item.source === "telegram" ? "Telegram" : "Web / API"
592
+ })]
593
+ }), /* @__PURE__ */ jsx(Button, {
594
+ variant: "ghost",
595
+ onClick: () => onViewConversation(item.conversationId),
596
+ children: "View conversation"
597
+ })]
598
+ }),
599
+ /* @__PURE__ */ jsx("p", {
600
+ className: "m-0 whitespace-pre-wrap break-words [overflow-wrap:anywhere]",
601
+ children: item.text ?? "Input retention expired"
602
+ }),
603
+ item.attachments.map((file) => /* @__PURE__ */ jsxs("p", {
604
+ className: "m-0 break-all text-xs text-mocha",
605
+ children: [
606
+ file.name ?? "Attachment",
607
+ " · ",
608
+ file.byteLength,
609
+ " bytes"
610
+ ]
611
+ }, file.id)),
612
+ item.diagnostic && /* @__PURE__ */ jsx("p", {
613
+ className: "m-0 break-words",
614
+ children: item.diagnostic
615
+ }),
616
+ item.approvalId && /* @__PURE__ */ jsx("a", {
617
+ href: `/approvals`,
618
+ children: "Review communication approval"
619
+ }),
620
+ item.status === "pending" && /* @__PURE__ */ jsxs("div", {
621
+ className: "flex flex-wrap items-center gap-2",
622
+ children: [/* @__PURE__ */ jsx(Button, {
623
+ variant: "ghost",
624
+ disabled: busy || !!pending,
625
+ onClick: () => void edit(item),
626
+ children: "Edit"
627
+ }), /* @__PURE__ */ jsx(Button, {
628
+ variant: "danger",
629
+ disabled: busy,
630
+ onClick: () => void action(`/${item.id}`, { expectedRevision: item.revision }, "DELETE"),
631
+ children: "Remove"
632
+ })]
633
+ }),
634
+ item.status === "uncertain" && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", { children: "This input may already have acted. Review its conversation before closing it. Closing never retries it." }), /* @__PURE__ */ jsx(Button, {
635
+ variant: "danger",
636
+ disabled: busy,
637
+ onClick: () => void action(`/${item.id}/reconcile`, {
638
+ expectedRevision: item.revision,
639
+ acknowledged: true
640
+ }),
641
+ children: "Acknowledge and close"
642
+ })] })
643
+ ]
644
+ }, item.id)), editing && /* @__PURE__ */ jsxs("form", {
645
+ className: "flex min-w-0 flex-col gap-3 rounded-lg border border-frost bg-ivory p-3",
646
+ onSubmit: (e) => {
647
+ e.preventDefault();
648
+ const expectedSelection = selection();
649
+ if (expectedSelection) submit({
650
+ agentId,
651
+ replacementId: editing.id,
652
+ input: {
653
+ requestId: crypto.randomUUID(),
654
+ expectedRevision: editing.revision,
655
+ expectedSelection,
656
+ message: text,
657
+ attachments: editFiles
658
+ }
659
+ });
660
+ },
661
+ children: [
662
+ /* @__PURE__ */ jsxs("label", {
663
+ className: "m-0 grid gap-2",
664
+ children: ["Edit queued input", /* @__PURE__ */ jsx("textarea", {
665
+ "aria-label": "Edit queued input",
666
+ rows: 3,
667
+ className: "w-full resize-y",
668
+ value: text,
669
+ onChange: (e) => setText(e.target.value)
670
+ })]
671
+ }),
672
+ editFiles.map((file, index) => /* @__PURE__ */ jsxs("div", { children: [
673
+ file.name ?? "Attachment",
674
+ " ",
675
+ /* @__PURE__ */ jsx(Button, {
676
+ variant: "ghost",
677
+ onClick: () => setEditFiles((files) => files.filter((_, i) => i !== index)),
678
+ children: "Remove attachment"
679
+ })
680
+ ] }, index)),
681
+ /* @__PURE__ */ jsxs("div", {
682
+ className: "flex flex-wrap items-center gap-2",
683
+ children: [/* @__PURE__ */ jsx(Button, {
684
+ variant: "primary",
685
+ type: "submit",
686
+ disabled: busy || !!pending,
687
+ children: "Save replacement"
688
+ }), /* @__PURE__ */ jsx(Button, {
689
+ variant: "ghost",
690
+ onClick: () => setEditing(null),
691
+ children: "Cancel edit"
692
+ })]
693
+ })
694
+ ]
695
+ })]
696
+ }),
697
+ state && (offset > 0 || offset + state.items.length < state.total) && /* @__PURE__ */ jsxs("div", {
698
+ className: "flex flex-wrap items-center gap-2",
699
+ children: [/* @__PURE__ */ jsx(Button, {
700
+ variant: "ghost",
701
+ disabled: offset === 0,
702
+ onClick: () => setOffset(Math.max(0, offset - 20)),
703
+ children: "Previous"
704
+ }), /* @__PURE__ */ jsx(Button, {
705
+ variant: "ghost",
706
+ disabled: offset + state.items.length >= state.total,
707
+ onClick: () => setOffset(offset + 20),
708
+ children: "Next"
709
+ })]
710
+ })
711
+ ]
712
+ });
713
+ });
714
+ //#endregion
715
+ //#region src/components/AgentQuestions.tsx
716
+ function AgentQuestions({ agentId }) {
717
+ const [items, setItems] = useState([]);
718
+ const [error, setError] = useState("");
719
+ useEffect(() => {
720
+ let live = true;
721
+ let running = false;
722
+ const refresh = async () => {
723
+ if (running) return;
724
+ running = true;
725
+ try {
726
+ const response = await fetch(`/api/agents/${encodeURIComponent(agentId)}/questions`, { cache: "no-store" });
727
+ if (!response.ok) throw new Error("Questions could not be refreshed");
728
+ const body = await response.json();
729
+ if (live) {
730
+ setItems(body.questions);
731
+ setError("");
732
+ }
733
+ } catch (e) {
734
+ if (live) {
735
+ setItems([]);
736
+ setError(e instanceof Error ? e.message : "Questions unavailable");
737
+ }
738
+ } finally {
739
+ running = false;
740
+ }
741
+ };
742
+ refresh();
743
+ const timer = setInterval(() => {
744
+ refresh();
745
+ }, 2e3);
746
+ return () => {
747
+ live = false;
748
+ clearInterval(timer);
749
+ };
750
+ }, [agentId]);
751
+ if (!items.length && !error) return null;
752
+ return /* @__PURE__ */ jsxs("section", {
753
+ "aria-label": "Agent questions",
754
+ className: "max-h-[40vh] min-w-0 shrink-0 space-y-3 overflow-y-auto border-t border-frost px-5 py-3",
755
+ children: [
756
+ error && /* @__PURE__ */ jsx("p", {
757
+ role: "status",
758
+ children: error
759
+ }),
760
+ items.filter((item) => item.status === "pending" || item.continuation === "unconfirmed").map((item) => /* @__PURE__ */ jsx(QuestionCard, { item }, item.id)),
761
+ items.some((item) => item.status !== "pending" && item.continuation !== "unconfirmed") && /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsx("summary", { children: "Recent question outcomes" }), items.filter((item) => item.status !== "pending" && item.continuation !== "unconfirmed").map((item) => /* @__PURE__ */ jsx(QuestionCard, { item }, item.id))] })
762
+ ]
763
+ });
764
+ }
765
+ function QuestionCard({ item }) {
766
+ const storageKey = `bazilion-question-answer:${item.agentId}:${item.id}`;
767
+ const [choice, setChoice] = useState("");
768
+ const [text, setText] = useState("");
769
+ const [pending, setPending] = useState(null);
770
+ const pendingRef = useRef(null);
771
+ const [ready, setReady] = useState(false);
772
+ const [busy, setBusy] = useState(false);
773
+ const locked = useRef(false);
774
+ const [error, setError] = useState("");
775
+ const [notice, setNotice] = useState("");
776
+ useEffect(() => {
777
+ try {
778
+ const saved = sessionStorage.getItem(storageKey);
779
+ if (saved) {
780
+ const value = JSON.parse(saved);
781
+ pendingRef.current = value;
782
+ setPending(value);
783
+ }
784
+ setReady(true);
785
+ } catch {
786
+ setError("Answer recovery storage is unavailable. Enable session storage before answering.");
787
+ }
788
+ }, [storageKey]);
789
+ useEffect(() => {
790
+ if (item.status === "pending") return;
791
+ try {
792
+ sessionStorage.removeItem(storageKey);
793
+ pendingRef.current = null;
794
+ setPending(null);
795
+ } catch {}
796
+ }, [item.status, storageKey]);
797
+ async function submit(answer) {
798
+ if (locked.current || !ready || item.status !== "pending") return;
799
+ locked.current = true;
800
+ setBusy(true);
801
+ setError("");
802
+ try {
803
+ if (!pendingRef.current && !answer) throw new Error("Choose an answer first");
804
+ const input = pendingRef.current ?? {
805
+ requestId: crypto.randomUUID(),
806
+ conversationId: item.conversationId,
807
+ answer
808
+ };
809
+ sessionStorage.setItem(storageKey, JSON.stringify(input));
810
+ pendingRef.current = input;
811
+ setPending(input);
812
+ const response = await fetch(`/api/agents/${encodeURIComponent(item.agentId)}/questions/${encodeURIComponent(item.id)}/answer`, {
813
+ method: "POST",
814
+ headers: { "content-type": "application/json" },
815
+ body: JSON.stringify(input)
816
+ }).catch(() => {
817
+ throw new Error("Answer acknowledgement unavailable. Retry the same answer.");
818
+ });
819
+ if (!response.ok) {
820
+ if (response.status === 409 || response.status === 404) throw new Error("This question is no longer answerable. Its outcome will refresh shortly.");
821
+ throw new Error("Answer acknowledgement unavailable. Retry the same answer.");
822
+ }
823
+ if ((await response.json()).kind === "held") setNotice("Answer awaits communication approval. The Agent has not received it.");
824
+ else {
825
+ sessionStorage.removeItem(storageKey);
826
+ setNotice("Answer accepted. Waiting for saved conversation evidence that the Agent consumed it.");
827
+ }
828
+ } catch (e) {
829
+ setError(e instanceof Error ? e.message : "Answer failed");
830
+ } finally {
831
+ locked.current = false;
832
+ setBusy(false);
833
+ }
834
+ }
835
+ const disabled = !ready || busy || !!pending || item.status !== "pending" || !!item.answerApprovalId;
836
+ return /* @__PURE__ */ jsxs("article", {
837
+ className: "flex min-w-0 flex-col gap-3 rounded-lg border border-frost bg-ivory p-3",
838
+ children: [
839
+ /* @__PURE__ */ jsx("p", {
840
+ className: "m-0 break-words font-medium",
841
+ children: item.question.prompt
842
+ }),
843
+ /* @__PURE__ */ jsxs("details", {
844
+ className: "text-xs text-mocha",
845
+ children: [/* @__PURE__ */ jsxs("summary", {
846
+ className: "cursor-pointer",
847
+ children: ["Question details · ", item.status]
848
+ }), /* @__PURE__ */ jsxs("p", {
849
+ className: "mt-2 break-all",
850
+ children: [
851
+ "Conversation ",
852
+ item.conversationId,
853
+ " · ",
854
+ item.continuation
855
+ ]
856
+ })]
857
+ }),
858
+ item.status === "pending" ? /* @__PURE__ */ jsxs("form", {
859
+ className: "flex min-w-0 flex-col gap-3",
860
+ onSubmit: (event) => {
861
+ event.preventDefault();
862
+ if (choice === "other") submit({
863
+ kind: "text",
864
+ text
865
+ });
866
+ else if (choice !== "") submit({
867
+ kind: "choice",
868
+ index: Number(choice)
869
+ });
870
+ },
871
+ children: [
872
+ /* @__PURE__ */ jsxs("fieldset", {
873
+ disabled,
874
+ className: "m-0 grid min-w-0 gap-2 border-0 p-0 sm:grid-cols-2",
875
+ children: [
876
+ /* @__PURE__ */ jsx("legend", {
877
+ className: "sr-only",
878
+ children: "Answer this question"
879
+ }),
880
+ item.question.choices.map((option, index) => /* @__PURE__ */ jsxs("label", {
881
+ className: "m-0 flex min-w-0 cursor-pointer items-center gap-2 rounded-md border border-frost px-3 py-2.5 has-[:checked]:border-sapphire has-[:checked]:bg-sapphire-glow",
882
+ children: [/* @__PURE__ */ jsx("input", {
883
+ className: "m-0 h-4 w-4 shrink-0 accent-sapphire",
884
+ type: "radio",
885
+ name: `question-${item.id}`,
886
+ value: index,
887
+ checked: choice === String(index),
888
+ onChange: () => setChoice(String(index))
889
+ }), /* @__PURE__ */ jsxs("span", {
890
+ className: "min-w-0 break-words [overflow-wrap:anywhere]",
891
+ children: [
892
+ option.label,
893
+ item.question.recommendedIndex === index ? " (Recommended)" : "",
894
+ option.description && /* @__PURE__ */ jsx("small", {
895
+ className: "block",
896
+ children: option.description
897
+ })
898
+ ]
899
+ })]
900
+ }, index)),
901
+ /* @__PURE__ */ jsxs("label", {
902
+ className: "m-0 flex min-w-0 cursor-pointer items-center gap-2 rounded-md border border-frost px-3 py-2.5 has-[:checked]:border-sapphire has-[:checked]:bg-sapphire-glow",
903
+ children: [/* @__PURE__ */ jsx("input", {
904
+ className: "m-0 h-4 w-4 shrink-0 accent-sapphire",
905
+ type: "radio",
906
+ name: `question-${item.id}`,
907
+ checked: choice === "other",
908
+ onChange: () => setChoice("other")
909
+ }), /* @__PURE__ */ jsx("span", { children: "Other" })]
910
+ }),
911
+ choice === "other" && /* @__PURE__ */ jsxs("label", {
912
+ className: "m-0 grid gap-2 sm:col-span-2",
913
+ children: ["Your answer", /* @__PURE__ */ jsx("textarea", {
914
+ rows: 2,
915
+ className: "w-full resize-y",
916
+ value: text,
917
+ maxLength: 4096,
918
+ onChange: (event) => setText(event.target.value)
919
+ })]
920
+ })
921
+ ]
922
+ }),
923
+ pending && /* @__PURE__ */ jsxs("p", {
924
+ className: "break-words",
925
+ children: ["Saved answer: ", pending.answer.kind === "choice" ? item.question.choices[pending.answer.index]?.label : pending.answer.kind === "text" ? pending.answer.text : "Skip"]
926
+ }),
927
+ /* @__PURE__ */ jsxs("div", {
928
+ className: "flex flex-wrap gap-2",
929
+ children: [
930
+ /* @__PURE__ */ jsx(Button, {
931
+ variant: "primary",
932
+ type: "submit",
933
+ disabled: disabled || !choice || choice === "other" && (!text.trim() || new TextEncoder().encode(text).length > 4096),
934
+ children: "Send answer"
935
+ }),
936
+ /* @__PURE__ */ jsx(Button, {
937
+ variant: "ghost",
938
+ disabled,
939
+ onClick: () => {
940
+ submit({ kind: "skip" });
941
+ },
942
+ children: "Skip"
943
+ }),
944
+ pending && !item.answerApprovalId && /* @__PURE__ */ jsx(Button, {
945
+ variant: "ghost",
946
+ disabled: busy,
947
+ onClick: () => {
948
+ submit();
949
+ },
950
+ children: "Retry same answer"
951
+ })
952
+ ]
953
+ }),
954
+ /* @__PURE__ */ jsxs("p", {
955
+ className: "m-0 text-xs text-mocha",
956
+ children: [
957
+ "Clarification does not grant permission. This question expires at ",
958
+ new Date(item.expiresAt).toLocaleTimeString(),
959
+ "."
960
+ ]
961
+ }),
962
+ item.answerApprovalId && /* @__PURE__ */ jsx("a", {
963
+ href: "/approvals",
964
+ children: "View communication approvals"
965
+ })
966
+ ]
967
+ }) : /* @__PURE__ */ jsxs("p", { children: [
968
+ item.answer?.kind === "choice" ? item.question.choices[item.answer.index]?.label : item.answer?.kind === "text" ? item.answer.text : `No answer: ${item.noAnswerReason ?? item.status}`,
969
+ ". ",
970
+ item.continuation === "consumed" ? "Saved in the originating conversation; task completion is separate." : item.continuation === "interrupted" ? "The waiting turn ended." : "Consumption has not been confirmed."
971
+ ] }),
972
+ notice && /* @__PURE__ */ jsx("p", {
973
+ role: "status",
974
+ children: notice
975
+ }),
976
+ error && /* @__PURE__ */ jsx("p", {
977
+ role: "alert",
978
+ children: error
979
+ })
980
+ ]
981
+ });
982
+ }
983
+ //#endregion
38
984
  //#region src/components/ChatPane.tsx
39
985
  var INBOX_WAKE_PREFIX = "[[bazilion:inbox-wake]]\n";
40
986
  var COMPACTION_REPLAY_PREFIX = "[conversation summary]";
@@ -63,13 +1009,14 @@ function interactiveChatRequest(message, attachments) {
63
1009
  return {
64
1010
  message,
65
1011
  attachments,
66
- bashApprovalMode: "interactive"
1012
+ bashApprovalMode: "interactive",
1013
+ questionMode: "web"
67
1014
  };
68
1015
  }
69
1016
  function shellApprovalsUrl(agentId) {
70
1017
  return `/api/shell-approvals?agentId=${encodeURIComponent(agentId)}`;
71
1018
  }
72
- var SLASH_HELP = "slash commands:\n /context — context breakdown (system prompt, tools, skills, history)\n /compact [N] — summarize the head; keep the last N messages verbatim (default 10)\n /reset — reset chat history for this agent\n /help — show this list";
1019
+ var SLASH_HELP = "slash commands:\n /context — context breakdown (system prompt, tools, skills, history)\n /compact [N] — summarize the head; keep the last N messages verbatim (default 10)\n /new — open the retained conversation library\n /help — show this list";
73
1020
  function prettyArgs(raw) {
74
1021
  if (!raw) return "";
75
1022
  try {
@@ -149,6 +1096,13 @@ function projectMessages(msgs) {
149
1096
  name: m.toolName ?? "",
150
1097
  body: m.content
151
1098
  });
1099
+ if (m.result) {
1100
+ entries.push({
1101
+ type: "result",
1102
+ resultId: m.result.resultId
1103
+ });
1104
+ openTool = null;
1105
+ }
152
1106
  if (m.images && m.images.length > 0) {
153
1107
  entries.push({
154
1108
  type: "images",
@@ -174,7 +1128,12 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
174
1128
  const [recoveredTurn, setRecoveredTurn] = useState(false);
175
1129
  const [approvalBusy, setApprovalBusy] = useState({});
176
1130
  const [approvalErrors, setApprovalErrors] = useState({});
177
- const [resetConfirmOpen, setResetConfirmOpen] = useState(false);
1131
+ const [libraryOpen, setLibraryOpen] = useState(false);
1132
+ const [queueConversation, setQueueConversation] = useState();
1133
+ const [queueMode, setQueueMode] = useState(false);
1134
+ const [queueReady, setQueueReady] = useState(false);
1135
+ const queueRef = useRef(null);
1136
+ const [historyUnavailable, setHistoryUnavailable] = useState(initialSessionHead?.unavailable ?? false);
178
1137
  const messagesRef = useRef(null);
179
1138
  const fileInputRef = useRef(null);
180
1139
  const inputRef = useRef(null);
@@ -200,6 +1159,9 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
200
1159
  setSystemBubbles([]);
201
1160
  setEditIdx(null);
202
1161
  setInput("");
1162
+ setQueueReady(false);
1163
+ setAttachments([]);
1164
+ setHistoryUnavailable(initialSessionHead?.unavailable ?? false);
203
1165
  setThinking(false);
204
1166
  setStreaming(false);
205
1167
  setStaleBanner(false);
@@ -259,7 +1221,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
259
1221
  function onVis() {
260
1222
  if (document.visibilityState === "visible" && sessionStorage.getItem(key)) {
261
1223
  sessionStorage.removeItem(key);
262
- window.location.reload();
1224
+ setStaleBanner(true);
263
1225
  }
264
1226
  }
265
1227
  document.addEventListener("visibilitychange", onVis);
@@ -277,7 +1239,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
277
1239
  const body = await res.json();
278
1240
  if (typeof body.size === "number") {
279
1241
  const known = knownHeadRef.current;
280
- if (body.file !== known.file || body.size !== known.size) setStaleBanner(true);
1242
+ if (body.unavailable !== known.unavailable || body.file !== known.file || body.size !== known.size) setStaleBanner(true);
281
1243
  }
282
1244
  }
283
1245
  } catch {}
@@ -293,16 +1255,38 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
293
1255
  initialSessionHead,
294
1256
  staleBanner
295
1257
  ]);
1258
+ async function refreshConversationHistory() {
1259
+ if (streamingRef.current) return;
1260
+ try {
1261
+ const response = await fetch(`/api/agents/${encodeURIComponent(agentId)}/sessions/messages`);
1262
+ if (!response.ok) throw new Error("Conversation history is unavailable. Your draft is preserved.");
1263
+ const body = await response.json();
1264
+ if (currentAgentIdRef.current !== agentId) return;
1265
+ setHistoryUnavailable(body.head.unavailable ?? false);
1266
+ setServerMessages(body.messages);
1267
+ setLiveEntries([]);
1268
+ setEditIdx(null);
1269
+ knownHeadRef.current = body.head;
1270
+ setStaleBanner(false);
1271
+ } catch (error) {
1272
+ pushSystem(error instanceof Error ? error.message : "Could not refresh conversation");
1273
+ }
1274
+ }
296
1275
  async function refreshKnownHead() {
297
1276
  if (currentAgentIdRef.current !== agentId) return;
298
1277
  try {
299
1278
  const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/sessions/head`);
300
1279
  if (!res.ok) return;
301
1280
  const body = await res.json();
302
- if (currentAgentIdRef.current === agentId && typeof body.size === "number") knownHeadRef.current = {
303
- file: body.file ?? null,
304
- size: body.size
305
- };
1281
+ if (currentAgentIdRef.current === agentId && typeof body.size === "number") {
1282
+ const prior = knownHeadRef.current.selection;
1283
+ if (prior?.conversationId && prior.conversationId !== body.selection?.conversationId) {
1284
+ setStaleBanner(true);
1285
+ return;
1286
+ }
1287
+ setHistoryUnavailable(body.unavailable ?? false);
1288
+ knownHeadRef.current = body;
1289
+ }
306
1290
  } catch {}
307
1291
  }
308
1292
  function pushSystem(text) {
@@ -347,28 +1331,6 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
347
1331
  pushSystem(`/context failed: ${err.message}`);
348
1332
  }
349
1333
  }
350
- async function performReset() {
351
- exitEditMode();
352
- const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/reset`, { method: "POST" });
353
- if (!res.ok) {
354
- let message = res.statusText;
355
- try {
356
- message = (await res.json()).error || message;
357
- } catch {}
358
- throw new Error(message);
359
- }
360
- setServerMessages([]);
361
- setLiveEntries([]);
362
- setSystemBubbles([]);
363
- pushSystem("/reset: history wiped");
364
- }
365
- async function runResetCommand() {
366
- if (serverMessages.length === 0) {
367
- pushSystem("/reset: history already empty");
368
- return;
369
- }
370
- setResetConfirmOpen(true);
371
- }
372
1334
  async function runCompactCommand(rest) {
373
1335
  if (serverMessages.length < 2) {
374
1336
  pushSystem("/compact: need ≥2 messages to compact");
@@ -387,7 +1349,10 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
387
1349
  const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/compact`, {
388
1350
  method: "POST",
389
1351
  headers: { "content-type": "application/json" },
390
- body: JSON.stringify(keepTail !== void 0 ? { keepTail } : {})
1352
+ body: JSON.stringify({
1353
+ keepTail,
1354
+ expectedSelection: knownHeadRef.current.selection
1355
+ })
391
1356
  });
392
1357
  if (!res.ok) {
393
1358
  let msg = res.statusText;
@@ -420,8 +1385,8 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
420
1385
  case "/context":
421
1386
  await runContextCommand();
422
1387
  return true;
423
- case "/reset":
424
- await runResetCommand();
1388
+ case "/new":
1389
+ setLibraryOpen(true);
425
1390
  return true;
426
1391
  case "/compact":
427
1392
  await runCompactCommand(rest);
@@ -447,7 +1412,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
447
1412
  setEditIdx(null);
448
1413
  }
449
1414
  async function addFiles(files) {
450
- if (!files || turnBusy) return;
1415
+ if (!files) return;
451
1416
  const arr = Array.from(files);
452
1417
  if (arr.length === 0) return;
453
1418
  const encoded = await Promise.all(arr.map(fileToAttachment));
@@ -463,10 +1428,6 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
463
1428
  function onDragOver(e) {
464
1429
  if (!Array.from(e.dataTransfer.types).includes("Files")) return;
465
1430
  e.preventDefault();
466
- if (turnBusy) {
467
- setDragging(false);
468
- return;
469
- }
470
1431
  setDragging(true);
471
1432
  }
472
1433
  function onDragLeave(e) {
@@ -476,12 +1437,22 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
476
1437
  if (e.dataTransfer.files.length === 0) return;
477
1438
  e.preventDefault();
478
1439
  setDragging(false);
479
- if (turnBusy) return;
480
1440
  addFiles(e.dataTransfer.files);
481
1441
  }
482
1442
  const send = useCallback(async (text) => {
483
1443
  const atts = attachments;
484
- if (!text.trim() && atts.length === 0 || turnBusy) return;
1444
+ if (!text.trim() && atts.length === 0) return;
1445
+ if (!queueReady) {
1446
+ pushSystem("Queue status is loading. Your draft is preserved.");
1447
+ return;
1448
+ }
1449
+ if (turnBusy || queueMode) {
1450
+ if (await queueRef.current?.enqueue(text, atts, knownHeadRef.current.selection) && currentAgentIdRef.current === agentId) {
1451
+ setInput((current) => current === text ? "" : current);
1452
+ setAttachments((current) => current === atts ? [] : current);
1453
+ }
1454
+ return;
1455
+ }
485
1456
  setInput("");
486
1457
  if (await maybeHandleSlashCommand(text)) return;
487
1458
  setAttachments([]);
@@ -492,7 +1463,10 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
492
1463
  const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/truncate`, {
493
1464
  method: "POST",
494
1465
  headers: { "content-type": "application/json" },
495
- body: JSON.stringify({ keepCount: keep })
1466
+ body: JSON.stringify({
1467
+ keepCount: keep,
1468
+ expectedSelection: knownHeadRef.current.selection
1469
+ })
496
1470
  });
497
1471
  if (!res.ok) {
498
1472
  let err = res.statusText;
@@ -535,7 +1509,10 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
535
1509
  const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat`, {
536
1510
  method: "POST",
537
1511
  headers: { "content-type": "application/json" },
538
- body: JSON.stringify(interactiveChatRequest(text, atts)),
1512
+ body: JSON.stringify({
1513
+ ...interactiveChatRequest(text, atts),
1514
+ expectedSelection: knownHeadRef.current.selection
1515
+ }),
539
1516
  signal: abort.signal
540
1517
  });
541
1518
  if (res.status === 202) {
@@ -547,6 +1524,15 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
547
1524
  sessionStorage.removeItem(`bz_pending_${agentId}`);
548
1525
  return;
549
1526
  }
1527
+ if (res.status === 409) {
1528
+ setInput(text);
1529
+ setAttachments(atts);
1530
+ setLiveEntries([]);
1531
+ setStaleBanner(true);
1532
+ sessionStorage.removeItem(`bz_pending_${agentId}`);
1533
+ pushSystem("Conversation changed. Refresh history, then review and send your preserved draft.");
1534
+ return;
1535
+ }
550
1536
  if (!res.ok || !res.body) {
551
1537
  let err = res.statusText;
552
1538
  try {
@@ -558,6 +1544,14 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
558
1544
  }]);
559
1545
  return;
560
1546
  }
1547
+ const admittedSelection = res.headers.get("x-bazilion-conversation-selection");
1548
+ if (admittedSelection) try {
1549
+ const selection = JSON.parse(admittedSelection);
1550
+ if (typeof selection.conversationId === "string" && Number.isSafeInteger(selection.revision) && selection.revision >= 0) knownHeadRef.current = {
1551
+ ...knownHeadRef.current,
1552
+ selection
1553
+ };
1554
+ } catch {}
561
1555
  const reader = res.body.getReader();
562
1556
  const decoder = new TextDecoder();
563
1557
  let buffer = "";
@@ -605,6 +1599,8 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
605
1599
  editIdx,
606
1600
  serverMessages,
607
1601
  turnBusy,
1602
+ queueMode,
1603
+ queueReady,
608
1604
  attachments
609
1605
  ]);
610
1606
  function handleFrame(frame) {
@@ -716,6 +1712,14 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
716
1712
  return;
717
1713
  }
718
1714
  if (ev.type === "file") {
1715
+ if (ev.result) {
1716
+ const resultId = ev.result.resultId;
1717
+ setLiveEntries((prev) => [...prev, {
1718
+ type: "result",
1719
+ resultId
1720
+ }]);
1721
+ return;
1722
+ }
719
1723
  setLiveEntries((prev) => [...prev, {
720
1724
  type: "file",
721
1725
  name: ev.name,
@@ -790,31 +1794,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
790
1794
  }
791
1795
  }
792
1796
  async function cancel() {
793
- const hasLocalStream = currentAbortRef.current !== null;
794
- try {
795
- const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/cancel`, { method: "POST" });
796
- if (currentAgentIdRef.current !== agentId) return;
797
- if (res.ok || res.status === 204) {
798
- if (!hasLocalStream) {
799
- commandApprovalTurnRef.current = false;
800
- setLiveEntries((entries) => entries.map((entry) => entry.type === "command_approval" && entry.approval.status === "pending" ? {
801
- type: "command_approval",
802
- approval: {
803
- ...entry.approval,
804
- status: "cancelled"
805
- }
806
- } : entry));
807
- setRecoveredTurn(false);
808
- }
809
- return;
810
- }
811
- } catch {}
812
- if (currentAgentIdRef.current !== agentId) return;
813
- if (hasLocalStream) currentAbortRef.current?.abort();
814
- else setLiveEntries((entries) => [...entries, {
815
- type: "error",
816
- content: "[cancel failed] could not reach the active turn"
817
- }]);
1797
+ if (!await queueRef.current?.stop() && currentAgentIdRef.current === agentId) pushSystem("Stop was not confirmed. Check queue controls and try again; the active response may still be running.");
818
1798
  }
819
1799
  function onKeyDown(e) {
820
1800
  if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
@@ -866,6 +1846,20 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
866
1846
  children: "Manage agent →"
867
1847
  })]
868
1848
  }),
1849
+ /* @__PURE__ */ jsx("div", {
1850
+ className: "mx-5 mt-2",
1851
+ children: /* @__PURE__ */ jsx(Button, {
1852
+ variant: "ghost",
1853
+ "aria-expanded": libraryOpen,
1854
+ onClick: () => setLibraryOpen((value) => !value),
1855
+ children: libraryOpen ? "Close conversations" : "Conversations"
1856
+ })
1857
+ }),
1858
+ historyUnavailable && /* @__PURE__ */ jsx("p", {
1859
+ role: "alert",
1860
+ className: "mx-5 mt-2 text-sm",
1861
+ children: "Selected conversation history is unavailable. Open Conversations to start a new one; retained history and files stay listed."
1862
+ }),
869
1863
  staleBanner && /* @__PURE__ */ jsxs("div", {
870
1864
  role: "status",
871
1865
  "aria-live": "polite",
@@ -882,7 +1876,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
882
1876
  }),
883
1877
  /* @__PURE__ */ jsx("button", {
884
1878
  type: "button",
885
- onClick: () => window.location.reload(),
1879
+ onClick: () => void refreshConversationHistory(),
886
1880
  className: "rounded-sm bg-sapphire px-3 py-1 text-[0.92em] text-snow hover:opacity-90",
887
1881
  children: "reload"
888
1882
  }),
@@ -903,9 +1897,16 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
903
1897
  "aria-live": "polite",
904
1898
  "aria-relevant": "additions",
905
1899
  "aria-busy": turnBusy,
906
- className: `min-h-[240px] flex-1 overflow-y-auto px-5 py-5 ${editIdx !== null ? "is-editing" : ""}`,
1900
+ className: `min-h-0 flex-1 overflow-y-auto px-5 py-5 ${editIdx !== null ? "is-editing" : ""}`,
907
1901
  children: [
908
- baseEntries.length === 0 && liveEntries.length === 0 && systemBubbles.length === 0 && /* @__PURE__ */ jsx("p", {
1902
+ libraryOpen && /* @__PURE__ */ jsx(ConversationLibrary, {
1903
+ initialConversationId: queueConversation,
1904
+ agentId,
1905
+ turnBusy,
1906
+ onCreated: refreshConversationHistory,
1907
+ renderHistory: (messages) => /* @__PURE__ */ jsx(ResultTranscript, { messages })
1908
+ }, agentId),
1909
+ !historyUnavailable && baseEntries.length === 0 && liveEntries.length === 0 && systemBubbles.length === 0 && /* @__PURE__ */ jsx("p", {
909
1910
  className: "py-12 text-center italic text-mocha-light",
910
1911
  children: "Start a conversation…"
911
1912
  }),
@@ -994,7 +1995,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
994
1995
  })]
995
1996
  }),
996
1997
  attachments.length > 0 && /* @__PURE__ */ jsx("div", {
997
- className: "flex flex-wrap gap-2 border-t border-frost bg-ivory px-5 pt-3",
1998
+ className: "flex max-h-24 shrink-0 flex-wrap gap-2 overflow-y-auto border-t border-frost bg-ivory px-5 pt-3",
998
1999
  children: attachments.map((a, i) => {
999
2000
  const remove = () => setAttachments((prev) => prev.filter((_, j) => j !== i));
1000
2001
  return isImageMime(a.mimeType) ? /* @__PURE__ */ jsxs("div", {
@@ -1029,8 +2030,22 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
1029
2030
  }, `${a.name ?? "file"}-${i}`);
1030
2031
  })
1031
2032
  }),
2033
+ /* @__PURE__ */ jsx(UserQueuePanel, {
2034
+ onViewConversation: (id) => {
2035
+ setQueueConversation(id);
2036
+ setLibraryOpen(true);
2037
+ },
2038
+ ref: queueRef,
2039
+ agentId,
2040
+ selection: () => knownHeadRef.current.selection,
2041
+ onQueueMode: (mode) => {
2042
+ setQueueMode(mode);
2043
+ setQueueReady(true);
2044
+ }
2045
+ }, agentId),
2046
+ /* @__PURE__ */ jsx(AgentQuestions, { agentId }, `questions-${agentId}`),
1032
2047
  /* @__PURE__ */ jsxs("form", {
1033
- className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-end gap-2 border-t border-frost bg-ivory px-3 py-3 sm:px-5",
2048
+ className: "grid shrink-0 grid-cols-[auto_minmax(0,1fr)] items-end gap-2 border-t border-frost bg-ivory px-3 py-3 sm:grid-cols-[auto_minmax(0,1fr)_auto] sm:px-5",
1034
2049
  onSubmit: (e) => {
1035
2050
  e.preventDefault();
1036
2051
  send(input);
@@ -1049,10 +2064,9 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
1049
2064
  /* @__PURE__ */ jsx("button", {
1050
2065
  type: "button",
1051
2066
  onClick: () => fileInputRef.current?.click(),
1052
- disabled: turnBusy,
1053
2067
  title: "attach images or files",
1054
2068
  "aria-label": "attach files",
1055
- className: "rounded-md border-[1.5px] border-frost bg-snow px-3 py-2 text-[1em] text-mocha transition-colors hover:border-sapphire hover:text-sapphire disabled:cursor-not-allowed disabled:opacity-50",
2069
+ className: "flex h-11 items-center justify-center rounded-md border-[1.5px] border-frost bg-snow px-3 py-2 text-[1em] text-mocha transition-colors hover:border-sapphire hover:text-sapphire disabled:cursor-not-allowed disabled:opacity-50",
1056
2070
  children: "📎"
1057
2071
  }),
1058
2072
  /* @__PURE__ */ jsx("textarea", {
@@ -1062,40 +2076,29 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
1062
2076
  onChange: (e) => setInput(e.target.value),
1063
2077
  onKeyDown,
1064
2078
  onPaste,
1065
- disabled: turnBusy,
1066
- placeholder: "say something… (Shift+Enter for newline; paste or 📎 to attach images/files)",
2079
+ placeholder: "Write a message…",
2080
+ title: "Shift+Enter for a new line. Paste or attach files with the paperclip.",
1067
2081
  autoComplete: "off",
1068
2082
  "aria-label": `Message ${agentName}`,
1069
- className: "max-h-[200px] min-h-[2.4rem] flex-1 resize-none overflow-y-auto rounded-md border-[1.5px] border-frost bg-snow px-3 py-2 text-[0.93em] leading-[1.45] text-chocolate outline-none transition-colors focus:border-sapphire focus:shadow-[0_0_0_3px_var(--color-sapphire-glow)]"
2083
+ className: "max-h-[200px] min-h-11 min-w-0 flex-1 resize-none overflow-y-auto rounded-md border-[1.5px] border-frost bg-snow px-3 py-2 text-[0.93em] leading-[1.45] text-chocolate outline-none transition-colors focus:border-sapphire focus:shadow-[0_0_0_3px_var(--color-sapphire-glow)]"
1070
2084
  }),
1071
- turnBusy ? /* @__PURE__ */ jsx("button", {
1072
- type: "button",
1073
- onClick: cancel,
1074
- "aria-label": "Cancel current response",
1075
- className: "rounded-md border-[1.5px] border-danger bg-transparent px-3 py-2 text-[0.92em] font-medium text-danger hover:bg-danger/10",
1076
- children: "Cancel"
1077
- }) : /* @__PURE__ */ jsx("button", {
1078
- type: "submit",
1079
- disabled: !input.trim() && attachments.length === 0,
1080
- className: "rounded-md bg-sapphire px-4 py-2 text-[0.92em] font-semibold text-snow transition-colors hover:bg-sapphire-deep disabled:cursor-not-allowed disabled:opacity-50",
1081
- children: "Send"
2085
+ /* @__PURE__ */ jsxs("div", {
2086
+ className: "col-span-2 flex flex-wrap justify-end gap-2 sm:col-span-1",
2087
+ children: [/* @__PURE__ */ jsx(Button, {
2088
+ variant: "primary",
2089
+ type: "submit",
2090
+ className: "min-h-11 whitespace-normal text-center",
2091
+ disabled: !input.trim() && attachments.length === 0,
2092
+ children: turnBusy || queueMode ? "Queue follow-up" : "Send"
2093
+ }), turnBusy && /* @__PURE__ */ jsx(Button, {
2094
+ variant: "danger",
2095
+ className: "min-h-11",
2096
+ onClick: () => void cancel(),
2097
+ "aria-label": "Stop response and pause queue",
2098
+ children: "Stop"
2099
+ })]
1082
2100
  })
1083
2101
  ]
1084
- }),
1085
- /* @__PURE__ */ jsx(ConfirmDialog, {
1086
- open: resetConfirmOpen,
1087
- onOpenChange: setResetConfirmOpen,
1088
- title: `Reset chat with ${agentName}?`,
1089
- description: /* @__PURE__ */ jsx("p", { children: "This permanently wipes the persisted session history for this Agent. Agent files, Team memory, and other Agent settings are not changed." }),
1090
- confirmLabel: "Reset chat history",
1091
- onConfirm: async () => {
1092
- try {
1093
- await performReset();
1094
- } catch (error) {
1095
- pushSystem(`/reset failed: ${error instanceof Error ? error.message : String(error)}`);
1096
- throw error;
1097
- }
1098
- }
1099
2102
  })
1100
2103
  ]
1101
2104
  });
@@ -1202,6 +2205,7 @@ function Bubble({ entry, markdownReady, isLastUser, isWillDrop, onEdit, onComman
1202
2205
  className: "block max-w-full rounded-lg border border-fawn"
1203
2206
  }, i))
1204
2207
  });
2208
+ if (entry.type === "result") return /* @__PURE__ */ jsx(ResultCard, { resultId: entry.resultId });
1205
2209
  if (entry.type === "file") {
1206
2210
  const isImage = entry.mimeType.startsWith("image/");
1207
2211
  const href = `data:${entry.mimeType};base64,${entry.data}`;
@@ -1425,5 +2429,16 @@ function Dot({ delay = "0s" }) {
1425
2429
  "aria-hidden": "true"
1426
2430
  });
1427
2431
  }
2432
+ /** Read-only projection used when opening the exact available source of a saved result. */
2433
+ function ResultTranscript({ messages }) {
2434
+ const markdownReady = useSyncExternalStore(subscribeToHydration, browserMarkdownReady, serverMarkdownReady);
2435
+ return /* @__PURE__ */ jsx("div", {
2436
+ "aria-label": "Source conversation",
2437
+ children: projectMessages(messages).map((entry, index) => /* @__PURE__ */ jsx(Bubble, {
2438
+ entry,
2439
+ markdownReady
2440
+ }, index))
2441
+ });
2442
+ }
1428
2443
  //#endregion
1429
- export { ChatPane as t };
2444
+ export { ResultTranscript as n, ChatPane as t };