bazilion 0.5.0 → 0.6.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 (118) hide show
  1. package/README.md +14 -9
  2. package/dist/cli.js +365 -140
  3. package/dist/cli.js.map +1 -1
  4. package/dist/daemon.js +4452 -16989
  5. package/dist/daemon.js.map +1 -1
  6. package/dist/web/client/assets/AgentAvatar-Cq7PVBE_.js +1 -0
  7. package/dist/web/client/assets/AgentTabs-M5K8emJ8.js +1 -0
  8. package/dist/web/client/assets/Button-BefUtjMZ.js +1 -0
  9. package/dist/web/client/assets/ChatPane-C8zQfpzH.js +74 -0
  10. package/dist/web/client/assets/ConfigTabs-DRqAymcN.js +1 -0
  11. package/dist/web/client/assets/FieldRow-BqWnxZiX.js +1 -0
  12. package/dist/web/client/assets/MembersEditor-B-47GLUb.js +1 -0
  13. package/dist/web/client/assets/SpawnTeamModal-DiiC7Nc0.js +1 -0
  14. package/dist/web/client/assets/TemplatesTabs-DB4KERTQ.js +1 -0
  15. package/dist/web/client/assets/_id-B7gv1aPQ.js +1 -0
  16. package/dist/web/client/assets/_id-C7d1AqS0.js +1 -0
  17. package/dist/web/client/assets/_id-DhiwjC8u.js +1 -0
  18. package/dist/web/client/assets/_id-p8lUuNFm.js +1 -0
  19. package/dist/web/client/assets/agents-v8G-wt0M.js +1 -0
  20. package/dist/web/client/assets/config-BdmYpS_y.js +3 -0
  21. package/dist/web/client/assets/groups-DiY_ExGM.js +1 -0
  22. package/dist/web/client/assets/inbox-DtfVx246.js +1 -0
  23. package/dist/web/client/assets/index-BQUL6oyH.js +12 -0
  24. package/dist/web/client/assets/login-CVS70W9g.js +1 -0
  25. package/dist/web/client/assets/mcp-BDLQq8OZ.js +1 -0
  26. package/dist/web/client/assets/memory-6GGuAXwx.js +1 -0
  27. package/dist/web/client/assets/profile-groups-BA8rwZMA.js +1 -0
  28. package/dist/web/client/assets/profiles-C0aAcPZg.js +1 -0
  29. package/dist/web/client/assets/routes-DgAhnKFw.js +1 -0
  30. package/dist/web/client/assets/services-BZ86QEah.js +1 -0
  31. package/dist/web/client/assets/skills-mv_NNNwo.js +1 -0
  32. package/dist/web/client/assets/styles-CmqUskDu.css +2 -0
  33. package/dist/web/client/assets/telegram-5f9WXs3x.js +1 -0
  34. package/dist/web/client/assets/tokens-DHiYsFUh.js +1 -0
  35. package/dist/web/client/assets/triggers-CjC8Ljrz.js +1 -0
  36. package/dist/web/client/assets/welcome-C-Cu-ziL.js +1 -0
  37. package/dist/web/client/assets/wire-constants-BkT5-597.js +1 -0
  38. package/dist/web/client/baziu.svg +8 -0
  39. package/dist/web/server/assets/AgentAvatar-8gTAy_tP.js +37 -0
  40. package/dist/web/server/assets/AgentTabs-BOjTN7QF.js +34 -0
  41. package/dist/web/server/assets/Button-Dsnw22OF.js +17 -0
  42. package/dist/web/server/assets/ChatPane-BvpswYvF.js +1084 -0
  43. package/dist/web/server/assets/ConfigTabs-CyuxzesF.js +41 -0
  44. package/dist/web/server/assets/FieldRow-DwyqycWI.js +94 -0
  45. package/dist/web/server/assets/MembersEditor-BWQztvrX.js +131 -0
  46. package/dist/web/server/assets/Sidebar-DmEiewRN.js +529 -0
  47. package/dist/web/server/assets/SpawnTeamModal-dSN421k_.js +107 -0
  48. package/dist/web/server/assets/TemplatesTabs---7h5GYa.js +30 -0
  49. package/dist/web/server/assets/_id-BZ-YCWSw.js +16 -0
  50. package/dist/web/server/assets/_id-Bm4-_AP-.js +367 -0
  51. package/dist/web/server/assets/_id-C78M9_j9.js +12 -0
  52. package/dist/web/server/assets/_id-CGZbudCl.js +28 -0
  53. package/dist/web/server/assets/_id-CHNm7EAd.js +161 -0
  54. package/dist/web/server/assets/_id-CrQbC5L8.js +16 -0
  55. package/dist/web/server/assets/_id-Crcw2HhT.js +38 -0
  56. package/dist/web/server/assets/_id-CvIGhq4U.js +16 -0
  57. package/dist/web/server/assets/_id-DF3pfi9M.js +27 -0
  58. package/dist/web/server/assets/_id-Dc8P9sy2.js +24 -0
  59. package/dist/web/server/assets/_id-GJk3EYTT.js +662 -0
  60. package/dist/web/server/assets/_id-pd4S7LTi.js +467 -0
  61. package/dist/web/server/assets/_tanstack-start-manifest_v-CPyKYMJg.js +174 -0
  62. package/dist/web/server/assets/agents-70EwQJR6.js +210 -0
  63. package/dist/web/server/assets/agents-Ds4lcREv.js +26 -0
  64. package/dist/web/server/assets/agents-K4Smhnq3.js +14 -0
  65. package/dist/web/server/assets/auth-DpAGLHpA.js +26 -0
  66. package/dist/web/server/assets/config-CMXUx_IF.js +19 -0
  67. package/dist/web/server/assets/config-DQbCsY_l.js +382 -0
  68. package/dist/web/server/assets/config-DxCGYgR9.js +12 -0
  69. package/dist/web/server/assets/createServerRpc-CBTntoKx.js +12 -0
  70. package/dist/web/server/assets/createSsrRpc-DHBVUN8O.js +16 -0
  71. package/dist/web/server/assets/daemon-client-2NRMzlTr.js +96 -0
  72. package/dist/web/server/assets/empty-plugin-adapters-Bos_V2s-.js +5 -0
  73. package/dist/web/server/assets/groups-CXVew7BP.js +150 -0
  74. package/dist/web/server/assets/groups-Cms8cvH5.js +12 -0
  75. package/dist/web/server/assets/groups-Dmcvw5ya.js +21 -0
  76. package/dist/web/server/assets/inbox-0QfwsrGo.js +27 -0
  77. package/dist/web/server/assets/inbox-8HybA2Ws.js +133 -0
  78. package/dist/web/server/assets/inbox-B7UWoICA.js +42 -0
  79. package/dist/web/server/assets/login-CNznncgA.js +58 -0
  80. package/dist/web/server/assets/login-CQybKk7Y.js +74 -0
  81. package/dist/web/server/assets/mcp-42BrkI1I.js +12 -0
  82. package/dist/web/server/assets/mcp-DIa0kIOf.js +268 -0
  83. package/dist/web/server/assets/mcp-DsTjY3uz.js +12 -0
  84. package/dist/web/server/assets/memory-BfrMp8iY.js +284 -0
  85. package/dist/web/server/assets/memory-Dr0lOM2y.js +28 -0
  86. package/dist/web/server/assets/memory-EEgLDfCj.js +16 -0
  87. package/dist/web/server/assets/profile-groups-BW5gU-fG.js +12 -0
  88. package/dist/web/server/assets/profile-groups-B_kIT6BU.js +208 -0
  89. package/dist/web/server/assets/profile-groups-C5isDI2t.js +28 -0
  90. package/dist/web/server/assets/profiles-BP1v-jna.js +453 -0
  91. package/dist/web/server/assets/profiles-BbPTpmyq.js +26 -0
  92. package/dist/web/server/assets/profiles-Cy5auEj8.js +12 -0
  93. package/dist/web/server/assets/router-CL100-Ly.js +460 -0
  94. package/dist/web/server/assets/routes-CzJLQxcS.js +14 -0
  95. package/dist/web/server/assets/routes-D2H1Hnoh.js +57 -0
  96. package/dist/web/server/assets/routes-Dzk8jvIo.js +33 -0
  97. package/dist/web/server/assets/services-Bgj6M2gC.js +12 -0
  98. package/dist/web/server/assets/services-DEgYsWJ4.js +17 -0
  99. package/dist/web/server/assets/services-DFenjiw0.js +94 -0
  100. package/dist/web/server/assets/skills-BASIP_rT.js +12 -0
  101. package/dist/web/server/assets/skills-C-JPRCHh.js +12 -0
  102. package/dist/web/server/assets/skills-DZXN680t.js +261 -0
  103. package/dist/web/server/assets/start-cOXH7eTI.js +4 -0
  104. package/dist/web/server/assets/telegram-BbvqVWxa.js +583 -0
  105. package/dist/web/server/assets/telegram-Bq49AtnP.js +12 -0
  106. package/dist/web/server/assets/telegram-sUckI94O.js +12 -0
  107. package/dist/web/server/assets/tokens-BERRE0wB.js +14 -0
  108. package/dist/web/server/assets/tokens-CImgxhDI.js +193 -0
  109. package/dist/web/server/assets/tokens-Dwn00iV7.js +12 -0
  110. package/dist/web/server/assets/triggers-DtexqbNH.js +16 -0
  111. package/dist/web/server/assets/triggers-TyIjwSYn.js +26 -0
  112. package/dist/web/server/assets/triggers-_iSeahif.js +191 -0
  113. package/dist/web/server/assets/welcome-BkQ9NC5K.js +81 -0
  114. package/dist/web/server/assets/wire-constants-Deb4NxBb.js +16 -0
  115. package/dist/web/server/server.js +5899 -0
  116. package/dist/web-server.js +42 -0
  117. package/dist/web-server.js.map +1 -0
  118. package/package.json +10 -4
@@ -0,0 +1,1084 @@
1
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import DOMPurify from "dompurify";
4
+ import { marked } from "marked";
5
+ //#region src/lib/md.ts
6
+ var installed = false;
7
+ function install() {
8
+ if (installed) return;
9
+ marked.setOptions({
10
+ breaks: true,
11
+ gfm: true
12
+ });
13
+ DOMPurify.addHook("afterSanitizeAttributes", (node) => {
14
+ if (node.nodeName === "A") {
15
+ node.setAttribute("target", "_blank");
16
+ node.setAttribute("rel", "noopener noreferrer");
17
+ }
18
+ });
19
+ installed = true;
20
+ }
21
+ function renderMd(text) {
22
+ if (!text) return "";
23
+ if (typeof window === "undefined") return escapeHtml(text);
24
+ install();
25
+ try {
26
+ const html = marked.parse(text, { async: false });
27
+ return DOMPurify.sanitize(html);
28
+ } catch {
29
+ return escapeHtml(text);
30
+ }
31
+ }
32
+ function escapeHtml(s) {
33
+ return String(s).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
34
+ }
35
+ //#endregion
36
+ //#region src/components/ChatPane.tsx
37
+ var INBOX_WAKE_PREFIX = "[[bazilion:inbox-wake]]\n";
38
+ var COMPACTION_REPLAY_PREFIX = "[conversation summary]";
39
+ var AUTOSCROLL_THRESHOLD_PX = 40;
40
+ var SESSION_POLL_MS = 1e4;
41
+ var TOOL_GROUP_MAX_HEIGHT_PX = 320;
42
+ var MAX_INPUT_HEIGHT = 200;
43
+ 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";
44
+ function prettyArgs(raw) {
45
+ if (!raw) return "";
46
+ try {
47
+ return JSON.stringify(JSON.parse(raw), null, 2);
48
+ } catch {
49
+ return raw;
50
+ }
51
+ }
52
+ function formatBytes(n) {
53
+ if (n < 1024) return `${n} B`;
54
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KiB`;
55
+ return `${(n / (1024 * 1024)).toFixed(2)} MiB`;
56
+ }
57
+ /** Read any File into a base64 Attachment (strips the data: URL prefix). */
58
+ function fileToAttachment(file) {
59
+ return new Promise((resolve, reject) => {
60
+ const reader = new FileReader();
61
+ reader.onload = () => {
62
+ const result = String(reader.result);
63
+ const comma = result.indexOf(",");
64
+ resolve({
65
+ name: file.name,
66
+ data: comma >= 0 ? result.slice(comma + 1) : result,
67
+ mimeType: file.type || "application/octet-stream"
68
+ });
69
+ };
70
+ reader.onerror = () => reject(reader.error);
71
+ reader.readAsDataURL(file);
72
+ });
73
+ }
74
+ var isImageMime = (m) => m.startsWith("image/");
75
+ function projectMessages(msgs) {
76
+ const entries = [];
77
+ let openTool = null;
78
+ for (const m of msgs) if (m.role === "user") {
79
+ openTool = null;
80
+ entries.push({
81
+ type: "user",
82
+ content: m.content,
83
+ ...m.images && m.images.length > 0 ? { images: m.images } : {}
84
+ });
85
+ } else if (m.role === "assistant") {
86
+ if (m.content) {
87
+ openTool = null;
88
+ entries.push({
89
+ type: "assistant",
90
+ content: m.content
91
+ });
92
+ }
93
+ if (m.toolCalls && m.toolCalls.length > 0) {
94
+ if (!openTool) {
95
+ openTool = {
96
+ type: "tool",
97
+ items: []
98
+ };
99
+ entries.push(openTool);
100
+ }
101
+ for (const tc of m.toolCalls) openTool.items.push({
102
+ kind: "call",
103
+ id: tc.id,
104
+ name: tc.name,
105
+ body: tc.arguments
106
+ });
107
+ }
108
+ } else if (m.role === "tool") {
109
+ if (!openTool) {
110
+ openTool = {
111
+ type: "tool",
112
+ items: []
113
+ };
114
+ entries.push(openTool);
115
+ }
116
+ const isError = m.content.startsWith("Error:") || m.content.startsWith("error:");
117
+ openTool.items.push({
118
+ kind: isError ? "error" : "result",
119
+ id: m.toolCallId ?? "",
120
+ name: m.toolName ?? "",
121
+ body: m.content
122
+ });
123
+ if (m.images && m.images.length > 0) {
124
+ entries.push({
125
+ type: "images",
126
+ images: m.images
127
+ });
128
+ openTool = null;
129
+ }
130
+ }
131
+ return entries;
132
+ }
133
+ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
134
+ const [serverMessages, setServerMessages] = useState(initialMessages);
135
+ const [liveEntries, setLiveEntries] = useState([]);
136
+ const [systemBubbles, setSystemBubbles] = useState([]);
137
+ const [thinking, setThinking] = useState(false);
138
+ const [streaming, setStreaming] = useState(false);
139
+ const [editIdx, setEditIdx] = useState(null);
140
+ const [input, setInput] = useState("");
141
+ const [attachments, setAttachments] = useState([]);
142
+ const [dragging, setDragging] = useState(false);
143
+ const [staleBanner, setStaleBanner] = useState(false);
144
+ const messagesRef = useRef(null);
145
+ const fileInputRef = useRef(null);
146
+ const inputRef = useRef(null);
147
+ const currentAbortRef = useRef(null);
148
+ const systemIdRef = useRef(0);
149
+ const visibleEntryCountRef = useRef(0);
150
+ const knownHeadRef = useRef(initialSessionHead ?? {
151
+ file: null,
152
+ size: 0
153
+ });
154
+ const streamingRef = useRef(false);
155
+ streamingRef.current = streaming;
156
+ useEffect(() => {
157
+ setServerMessages(initialMessages);
158
+ setLiveEntries([]);
159
+ setSystemBubbles([]);
160
+ setEditIdx(null);
161
+ setInput("");
162
+ setThinking(false);
163
+ setStreaming(false);
164
+ setStaleBanner(false);
165
+ knownHeadRef.current = initialSessionHead ?? {
166
+ file: null,
167
+ size: 0
168
+ };
169
+ }, [agentId]);
170
+ const wasNearBottomRef = useRef(true);
171
+ useLayoutEffect(() => {
172
+ const el = messagesRef.current;
173
+ if (!el) return;
174
+ if (wasNearBottomRef.current) el.scrollTop = el.scrollHeight;
175
+ });
176
+ function captureScroll() {
177
+ const el = messagesRef.current;
178
+ if (!el) {
179
+ wasNearBottomRef.current = true;
180
+ return;
181
+ }
182
+ wasNearBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < AUTOSCROLL_THRESHOLD_PX;
183
+ }
184
+ useLayoutEffect(() => {
185
+ const el = inputRef.current;
186
+ if (!el) return;
187
+ el.style.height = "auto";
188
+ el.style.height = `${Math.min(el.scrollHeight, MAX_INPUT_HEIGHT)}px`;
189
+ }, [input]);
190
+ useEffect(() => {
191
+ const key = `bz_pending_${agentId}`;
192
+ function onVis() {
193
+ if (document.visibilityState === "visible" && sessionStorage.getItem(key)) {
194
+ sessionStorage.removeItem(key);
195
+ window.location.reload();
196
+ }
197
+ }
198
+ document.addEventListener("visibilitychange", onVis);
199
+ return () => document.removeEventListener("visibilitychange", onVis);
200
+ }, [agentId]);
201
+ useEffect(() => {
202
+ if (!initialSessionHead) return;
203
+ let stopped = false;
204
+ let timer = null;
205
+ async function tick() {
206
+ if (stopped) return;
207
+ if (!document.hidden && !streamingRef.current && !staleBanner) try {
208
+ const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/sessions/head`);
209
+ if (res.ok) {
210
+ const body = await res.json();
211
+ if (typeof body.size === "number") {
212
+ const known = knownHeadRef.current;
213
+ if (body.file !== known.file || body.size !== known.size) setStaleBanner(true);
214
+ }
215
+ }
216
+ } catch {}
217
+ timer = setTimeout(tick, SESSION_POLL_MS);
218
+ }
219
+ timer = setTimeout(tick, SESSION_POLL_MS);
220
+ return () => {
221
+ stopped = true;
222
+ if (timer) clearTimeout(timer);
223
+ };
224
+ }, [
225
+ agentId,
226
+ initialSessionHead,
227
+ staleBanner
228
+ ]);
229
+ async function refreshKnownHead() {
230
+ try {
231
+ const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/sessions/head`);
232
+ if (!res.ok) return;
233
+ const body = await res.json();
234
+ if (typeof body.size === "number") knownHeadRef.current = {
235
+ file: body.file ?? null,
236
+ size: body.size
237
+ };
238
+ } catch {}
239
+ }
240
+ function pushSystem(text) {
241
+ captureScroll();
242
+ setSystemBubbles((prev) => [...prev, {
243
+ id: systemIdRef.current++,
244
+ content: text,
245
+ afterIdx: visibleEntryCountRef.current
246
+ }]);
247
+ }
248
+ async function runContextCommand() {
249
+ try {
250
+ const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/context`);
251
+ if (!res.ok) {
252
+ pushSystem(`/context failed: ${res.statusText}`);
253
+ return;
254
+ }
255
+ const ctx = await res.json();
256
+ const fmt = (chars, tokens) => `${chars.toLocaleString()} chars (~${tokens.toLocaleString()} tok)`;
257
+ const lines = [];
258
+ lines.push(`context for ${ctx.agentId}`);
259
+ lines.push(`model: ${ctx.model}`);
260
+ lines.push("");
261
+ lines.push(`system prompt: ${fmt(ctx.systemPrompt.chars, ctx.systemPrompt.tokens)}`);
262
+ for (const f of ctx.systemPrompt.files) lines.push(` - ${f.name}: ${f.chars.toLocaleString()} chars`);
263
+ lines.push("");
264
+ lines.push(`tools: ${ctx.tools.count} (${ctx.tools.schemaChars.toLocaleString()} chars of schema)`);
265
+ for (const t of ctx.tools.entries.slice(0, 5)) lines.push(` - ${t.name}: ${t.schemaChars.toLocaleString()} chars`);
266
+ if (ctx.skills.count > 0) {
267
+ lines.push("");
268
+ lines.push(`skills: ${ctx.skills.count}`);
269
+ for (const s of ctx.skills.entries.slice(0, 10)) lines.push(` - ${s.name}: ${s.blockChars.toLocaleString()} chars`);
270
+ }
271
+ lines.push("");
272
+ lines.push(`history: ${ctx.history.messageEntries} messages / ${ctx.history.compactionEntries} compactions`);
273
+ lines.push(` ${fmt(ctx.history.chars, ctx.history.tokensEstimate)}`);
274
+ lines.push(` bytes on disk: ${formatBytes(ctx.history.bytes)}`);
275
+ lines.push("");
276
+ lines.push(`TOTAL: ${fmt(ctx.totals.chars, ctx.totals.tokens)}`);
277
+ pushSystem(lines.join("\n"));
278
+ } catch (err) {
279
+ pushSystem(`/context failed: ${err.message}`);
280
+ }
281
+ }
282
+ async function runResetCommand() {
283
+ if (serverMessages.length === 0) {
284
+ pushSystem("/reset: history already empty");
285
+ return;
286
+ }
287
+ if (!confirm("reset chat history for this agent?")) return;
288
+ exitEditMode();
289
+ try {
290
+ const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/reset`, { method: "POST" });
291
+ if (!res.ok) {
292
+ let msg = res.statusText;
293
+ try {
294
+ msg = (await res.json()).error || msg;
295
+ } catch {}
296
+ pushSystem(`/reset failed: ${msg}`);
297
+ return;
298
+ }
299
+ setServerMessages([]);
300
+ setLiveEntries([]);
301
+ setSystemBubbles([]);
302
+ pushSystem("/reset: history wiped");
303
+ } catch (err) {
304
+ pushSystem(`/reset failed: ${err.message}`);
305
+ }
306
+ }
307
+ async function runCompactCommand(rest) {
308
+ if (serverMessages.length < 2) {
309
+ pushSystem("/compact: need ≥2 messages to compact");
310
+ return;
311
+ }
312
+ let keepTail;
313
+ const argText = rest.trim();
314
+ if (argText) {
315
+ const n = Number(argText);
316
+ if (Number.isFinite(n) && n >= 0) keepTail = Math.floor(n);
317
+ }
318
+ exitEditMode();
319
+ setStreaming(true);
320
+ setThinking(true);
321
+ try {
322
+ const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/compact`, {
323
+ method: "POST",
324
+ headers: { "content-type": "application/json" },
325
+ body: JSON.stringify(keepTail !== void 0 ? { keepTail } : {})
326
+ });
327
+ if (!res.ok) {
328
+ let msg = res.statusText;
329
+ try {
330
+ msg = (await res.json()).error || msg;
331
+ } catch {}
332
+ pushSystem(`/compact failed: ${msg}`);
333
+ return;
334
+ }
335
+ const body = await res.json();
336
+ pushSystem(`/compact: ${body.before} → ${body.after} entries (${body.summarized} summarized, ${body.keptTail} kept verbatim; ~${body.tokensBefore.toLocaleString()} → ~${body.tokensAfter.toLocaleString()} tok). reloading…`);
337
+ setTimeout(() => window.location.reload(), 400);
338
+ } catch (err) {
339
+ pushSystem(`/compact failed: ${err.message}`);
340
+ } finally {
341
+ setThinking(false);
342
+ setStreaming(false);
343
+ }
344
+ }
345
+ async function maybeHandleSlashCommand(text) {
346
+ const trimmed = text.trim();
347
+ if (!trimmed.startsWith("/")) return false;
348
+ const parts = trimmed.split(/\s+/);
349
+ const cmd = (parts[0] ?? "").toLowerCase();
350
+ const rest = parts.slice(1).join(" ");
351
+ switch (cmd) {
352
+ case "/help":
353
+ pushSystem(SLASH_HELP);
354
+ return true;
355
+ case "/context":
356
+ await runContextCommand();
357
+ return true;
358
+ case "/reset":
359
+ await runResetCommand();
360
+ return true;
361
+ case "/compact":
362
+ await runCompactCommand(rest);
363
+ return true;
364
+ default: return false;
365
+ }
366
+ }
367
+ function findLastUserIdx() {
368
+ for (let i = serverMessages.length - 1; i >= 0; i--) if (serverMessages[i]?.role === "user") return i;
369
+ return -1;
370
+ }
371
+ function enterEditMode() {
372
+ if (liveEntries.length > 0 || streaming) return;
373
+ const idx = findLastUserIdx();
374
+ if (idx === -1) return;
375
+ const userMsg = serverMessages[idx];
376
+ if (!userMsg) return;
377
+ setEditIdx(idx);
378
+ setInput(userMsg.content);
379
+ inputRef.current?.focus();
380
+ }
381
+ function exitEditMode() {
382
+ setEditIdx(null);
383
+ }
384
+ async function addFiles(files) {
385
+ if (!files) return;
386
+ const arr = Array.from(files);
387
+ if (arr.length === 0) return;
388
+ const encoded = await Promise.all(arr.map(fileToAttachment));
389
+ setAttachments((prev) => [...prev, ...encoded].slice(0, 16));
390
+ }
391
+ function onPaste(e) {
392
+ const files = Array.from(e.clipboardData.files);
393
+ if (files.some((f) => f.type.startsWith("image/"))) {
394
+ e.preventDefault();
395
+ addFiles(files);
396
+ }
397
+ }
398
+ function onDragOver(e) {
399
+ if (streaming) return;
400
+ if (Array.from(e.dataTransfer.types).includes("Files")) {
401
+ e.preventDefault();
402
+ setDragging(true);
403
+ }
404
+ }
405
+ function onDragLeave(e) {
406
+ if (e.currentTarget === e.target) setDragging(false);
407
+ }
408
+ function onDrop(e) {
409
+ if (e.dataTransfer.files.length > 0) {
410
+ e.preventDefault();
411
+ setDragging(false);
412
+ addFiles(e.dataTransfer.files);
413
+ }
414
+ }
415
+ const send = useCallback(async (text) => {
416
+ const atts = attachments;
417
+ if (!text.trim() && atts.length === 0 || streaming) return;
418
+ setInput("");
419
+ if (await maybeHandleSlashCommand(text)) return;
420
+ setAttachments([]);
421
+ let truncatedServerMessages = null;
422
+ if (editIdx !== null) {
423
+ const keep = editIdx;
424
+ try {
425
+ const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/truncate`, {
426
+ method: "POST",
427
+ headers: { "content-type": "application/json" },
428
+ body: JSON.stringify({ keepCount: keep })
429
+ });
430
+ if (!res.ok) {
431
+ let err = res.statusText;
432
+ try {
433
+ err = (await res.json()).error || err;
434
+ } catch {}
435
+ setInput(text);
436
+ alert(`failed to replace last turn: ${err}`);
437
+ return;
438
+ }
439
+ truncatedServerMessages = serverMessages.slice(0, keep);
440
+ setServerMessages(truncatedServerMessages);
441
+ setEditIdx(null);
442
+ } catch (err) {
443
+ setInput(text);
444
+ alert(`failed to replace last turn: ${err.message}`);
445
+ return;
446
+ }
447
+ }
448
+ captureScroll();
449
+ const previewImages = atts.filter((a) => isImageMime(a.mimeType));
450
+ const previewFiles = atts.filter((a) => !isImageMime(a.mimeType));
451
+ setLiveEntries([{
452
+ type: "user",
453
+ content: text,
454
+ ...previewImages.length > 0 ? { images: previewImages } : {},
455
+ ...previewFiles.length > 0 ? { files: previewFiles.map((f) => ({ name: f.name ?? "file" })) } : {}
456
+ }]);
457
+ sessionStorage.setItem(`bz_pending_${agentId}`, "1");
458
+ const abort = new AbortController();
459
+ currentAbortRef.current = abort;
460
+ setStreaming(true);
461
+ setThinking(true);
462
+ try {
463
+ const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat`, {
464
+ method: "POST",
465
+ headers: { "content-type": "application/json" },
466
+ body: JSON.stringify({
467
+ message: text,
468
+ attachments: atts
469
+ }),
470
+ signal: abort.signal
471
+ });
472
+ if (!res.ok || !res.body) {
473
+ let err = res.statusText;
474
+ try {
475
+ err = (await res.json()).error || err;
476
+ } catch {}
477
+ setLiveEntries((prev) => [...prev, {
478
+ type: "error",
479
+ content: `[error] ${err}`
480
+ }]);
481
+ return;
482
+ }
483
+ const reader = res.body.getReader();
484
+ const decoder = new TextDecoder();
485
+ let buffer = "";
486
+ while (true) {
487
+ const { done, value } = await reader.read();
488
+ if (done) break;
489
+ buffer += decoder.decode(value, { stream: true });
490
+ const lines = buffer.split("\n");
491
+ buffer = lines.pop() ?? "";
492
+ for (const line of lines) {
493
+ if (!line.trim()) continue;
494
+ let frame;
495
+ try {
496
+ frame = JSON.parse(line);
497
+ } catch {
498
+ continue;
499
+ }
500
+ handleFrame(frame);
501
+ }
502
+ }
503
+ if (buffer.trim()) try {
504
+ handleFrame(JSON.parse(buffer));
505
+ } catch {}
506
+ await refreshKnownHead();
507
+ sessionStorage.removeItem(`bz_pending_${agentId}`);
508
+ } catch (err) {
509
+ if (err.name !== "AbortError") setLiveEntries((prev) => [...prev, {
510
+ type: "error",
511
+ content: `[network error] ${err.message}`
512
+ }]);
513
+ sessionStorage.removeItem(`bz_pending_${agentId}`);
514
+ } finally {
515
+ setThinking(false);
516
+ setStreaming(false);
517
+ currentAbortRef.current = null;
518
+ }
519
+ }, [
520
+ agentId,
521
+ editIdx,
522
+ serverMessages,
523
+ streaming,
524
+ attachments
525
+ ]);
526
+ function handleFrame(frame) {
527
+ if (frame.kind === "fatal") {
528
+ setLiveEntries((prev) => [...prev, {
529
+ type: "error",
530
+ content: `[fatal] ${frame.error}`
531
+ }]);
532
+ return;
533
+ }
534
+ if (frame.kind === "done") {
535
+ setServerMessages(frame.messages);
536
+ setLiveEntries([]);
537
+ return;
538
+ }
539
+ if (frame.kind !== "event") return;
540
+ const ev = frame.event;
541
+ captureScroll();
542
+ if (ev.type === "user_message") return;
543
+ if (ev.type === "assistant_delta") {
544
+ setThinking(false);
545
+ setLiveEntries((prev) => {
546
+ const next = [...prev];
547
+ const last = next[next.length - 1];
548
+ if (last?.type === "assistant") next[next.length - 1] = {
549
+ type: "assistant",
550
+ content: last.content + ev.delta
551
+ };
552
+ else next.push({
553
+ type: "assistant",
554
+ content: ev.delta
555
+ });
556
+ return next;
557
+ });
558
+ return;
559
+ }
560
+ if (ev.type === "assistant_message") {
561
+ setThinking(false);
562
+ setLiveEntries((prev) => {
563
+ const next = [...prev];
564
+ if (next[next.length - 1]?.type === "assistant") next[next.length - 1] = {
565
+ type: "assistant",
566
+ content: ev.text
567
+ };
568
+ else next.push({
569
+ type: "assistant",
570
+ content: ev.text
571
+ });
572
+ return next;
573
+ });
574
+ return;
575
+ }
576
+ if (ev.type === "tool_call" || ev.type === "tool_result" || ev.type === "tool_error") {
577
+ setThinking(ev.type !== "tool_call");
578
+ const item = ev.type === "tool_call" ? {
579
+ kind: "call",
580
+ id: ev.id,
581
+ name: ev.name,
582
+ body: ev.arguments
583
+ } : ev.type === "tool_result" ? {
584
+ kind: "result",
585
+ id: ev.id,
586
+ name: ev.name,
587
+ body: ev.result
588
+ } : {
589
+ kind: "error",
590
+ id: ev.id,
591
+ name: ev.name,
592
+ body: ev.error
593
+ };
594
+ const images = ev.type === "tool_result" ? ev.images : void 0;
595
+ setLiveEntries((prev) => {
596
+ const next = [...prev];
597
+ const last = next[next.length - 1];
598
+ if (last?.type === "tool") next[next.length - 1] = {
599
+ type: "tool",
600
+ items: [...last.items, item]
601
+ };
602
+ else next.push({
603
+ type: "tool",
604
+ items: [item]
605
+ });
606
+ if (images && images.length > 0) next.push({
607
+ type: "images",
608
+ images
609
+ });
610
+ return next;
611
+ });
612
+ return;
613
+ }
614
+ if (ev.type === "file") {
615
+ setLiveEntries((prev) => [...prev, {
616
+ type: "file",
617
+ name: ev.name,
618
+ mimeType: ev.mimeType,
619
+ data: ev.data
620
+ }]);
621
+ return;
622
+ }
623
+ if (ev.type === "error") setLiveEntries((prev) => [...prev, {
624
+ type: "error",
625
+ content: `[error] ${ev.error}`
626
+ }]);
627
+ }
628
+ async function cancel() {
629
+ try {
630
+ const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/cancel`, { method: "POST" });
631
+ if (res.ok || res.status === 204) return;
632
+ } catch {}
633
+ currentAbortRef.current?.abort();
634
+ }
635
+ function onKeyDown(e) {
636
+ if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
637
+ e.preventDefault();
638
+ send(input);
639
+ }
640
+ }
641
+ const baseEntries = projectMessages(serverMessages);
642
+ visibleEntryCountRef.current = baseEntries.length + liveEntries.length;
643
+ const lastUserIdx = (() => {
644
+ if (liveEntries.length > 0 || streaming) return -1;
645
+ for (let i = baseEntries.length - 1; i >= 0; i--) if (baseEntries[i]?.type === "user") return i;
646
+ return -1;
647
+ })();
648
+ const willDropFromIdx = (() => {
649
+ if (editIdx === null) return -1;
650
+ let userCount = 0;
651
+ for (let i = 0; i < baseEntries.length; i++) if (baseEntries[i]?.type === "user") {
652
+ if (userCount === serverMessages.slice(0, editIdx + 1).filter((m) => m.role === "user").length - 1) return i;
653
+ userCount++;
654
+ }
655
+ return lastUserIdx;
656
+ })();
657
+ return /* @__PURE__ */ jsxs("div", {
658
+ className: "relative flex h-full flex-col overflow-hidden rounded-[16px] border border-frost bg-snow shadow-baziu-sm",
659
+ onDragOver,
660
+ onDragLeave,
661
+ onDrop,
662
+ children: [
663
+ dragging && /* @__PURE__ */ jsx("div", {
664
+ className: "pointer-events-none absolute inset-0 z-10 flex items-center justify-center rounded-[16px] border-2 border-dashed border-sapphire bg-sapphire-glow/80 text-[0.95em] font-medium text-sapphire-deep",
665
+ children: "drop images or files to attach"
666
+ }),
667
+ /* @__PURE__ */ jsxs("div", {
668
+ className: "flex items-baseline justify-between border-b border-frost px-4 py-2.5",
669
+ children: [/* @__PURE__ */ jsx("h1", {
670
+ className: "font-display text-[1.2rem] text-charcoal",
671
+ children: agentName
672
+ }), /* @__PURE__ */ jsx("a", {
673
+ href: `/agents/${agentId}`,
674
+ className: "text-xs text-mocha-light hover:text-sapphire",
675
+ children: "settings →"
676
+ })]
677
+ }),
678
+ staleBanner && /* @__PURE__ */ jsxs("div", {
679
+ className: "mx-5 mt-2 flex items-center gap-2 rounded-md border border-sapphire bg-frost px-4 py-2 text-[0.86em] text-mocha",
680
+ children: [
681
+ /* @__PURE__ */ jsx("span", {
682
+ className: "h-2 w-2 flex-none rounded-full bg-sapphire",
683
+ "aria-hidden": "true"
684
+ }),
685
+ /* @__PURE__ */ jsx("span", {
686
+ className: "flex-1",
687
+ children: "new activity from another source — reload to see it"
688
+ }),
689
+ /* @__PURE__ */ jsx("button", {
690
+ type: "button",
691
+ onClick: () => window.location.reload(),
692
+ className: "rounded-sm bg-sapphire px-3 py-1 text-[0.92em] text-snow hover:opacity-90",
693
+ children: "reload"
694
+ }),
695
+ /* @__PURE__ */ jsx("button", {
696
+ type: "button",
697
+ onClick: () => setStaleBanner(false),
698
+ className: "px-1 text-mocha-light hover:text-mocha",
699
+ "aria-label": "dismiss",
700
+ children: "×"
701
+ })
702
+ ]
703
+ }),
704
+ /* @__PURE__ */ jsxs("div", {
705
+ ref: messagesRef,
706
+ onScroll: captureScroll,
707
+ className: `min-h-[240px] flex-1 overflow-y-auto px-5 py-5 ${editIdx !== null ? "is-editing" : ""}`,
708
+ children: [
709
+ baseEntries.length === 0 && liveEntries.length === 0 && systemBubbles.length === 0 && /* @__PURE__ */ jsx("p", {
710
+ className: "py-12 text-center italic text-fawn",
711
+ children: "start a conversation…"
712
+ }),
713
+ (() => {
714
+ const out = [];
715
+ let sysIdx = 0;
716
+ const flushSysUpTo = (idx) => {
717
+ while (sysIdx < systemBubbles.length && systemBubbles[sysIdx].afterIdx <= idx) {
718
+ const sb = systemBubbles[sysIdx];
719
+ out.push(/* @__PURE__ */ jsx(Bubble, { entry: {
720
+ type: "system",
721
+ content: sb.content
722
+ } }, `y-${sb.id}`));
723
+ sysIdx++;
724
+ }
725
+ };
726
+ flushSysUpTo(0);
727
+ for (let i = 0; i < baseEntries.length; i++) {
728
+ const entry = baseEntries[i];
729
+ out.push(/* @__PURE__ */ jsx(Bubble, {
730
+ entry,
731
+ isLastUser: i === lastUserIdx && editIdx === null,
732
+ isWillDrop: willDropFromIdx !== -1 && i >= willDropFromIdx,
733
+ onEdit: enterEditMode
734
+ }, `s-${i}`));
735
+ flushSysUpTo(i + 1);
736
+ }
737
+ for (let i = 0; i < liveEntries.length; i++) {
738
+ const entry = liveEntries[i];
739
+ out.push(/* @__PURE__ */ jsx(Bubble, { entry }, `l-${i}`));
740
+ flushSysUpTo(baseEntries.length + i + 1);
741
+ }
742
+ while (sysIdx < systemBubbles.length) {
743
+ const sb = systemBubbles[sysIdx];
744
+ out.push(/* @__PURE__ */ jsx(Bubble, { entry: {
745
+ type: "system",
746
+ content: sb.content
747
+ } }, `y-${sb.id}`));
748
+ sysIdx++;
749
+ }
750
+ return out;
751
+ })(),
752
+ thinking && /* @__PURE__ */ jsxs("div", {
753
+ className: "flex items-center gap-2 px-1 py-1 text-[0.85em] text-mocha-light",
754
+ children: [
755
+ /* @__PURE__ */ jsx(Dot, {}),
756
+ /* @__PURE__ */ jsx(Dot, { delay: "0.15s" }),
757
+ /* @__PURE__ */ jsx(Dot, { delay: "0.3s" }),
758
+ /* @__PURE__ */ jsx("span", { children: "agent is thinking…" })
759
+ ]
760
+ })
761
+ ]
762
+ }),
763
+ editIdx !== null && /* @__PURE__ */ jsxs("div", {
764
+ className: "flex items-center gap-2 border-t border-frost bg-sapphire-glow px-5 py-2 text-[0.85em] text-sapphire-deep",
765
+ children: [/* @__PURE__ */ jsx("span", { children: "editing last message — submit to replace, or" }), /* @__PURE__ */ jsx("button", {
766
+ type: "button",
767
+ onClick: exitEditMode,
768
+ className: "ml-auto rounded-sm border border-sapphire-light bg-transparent px-2 py-0.5 text-[0.92em] text-sapphire-deep hover:bg-snow",
769
+ children: "cancel"
770
+ })]
771
+ }),
772
+ attachments.length > 0 && /* @__PURE__ */ jsx("div", {
773
+ className: "flex flex-wrap gap-2 border-t border-frost bg-ivory px-5 pt-3",
774
+ children: attachments.map((a, i) => {
775
+ const remove = () => setAttachments((prev) => prev.filter((_, j) => j !== i));
776
+ return isImageMime(a.mimeType) ? /* @__PURE__ */ jsxs("div", {
777
+ className: "relative",
778
+ children: [/* @__PURE__ */ jsx("img", {
779
+ src: `data:${a.mimeType};base64,${a.data}`,
780
+ alt: "pending attachment",
781
+ className: "h-16 w-16 rounded-md border border-frost object-cover"
782
+ }), /* @__PURE__ */ jsx("button", {
783
+ type: "button",
784
+ onClick: remove,
785
+ className: "absolute -right-1.5 -top-1.5 flex h-5 w-5 items-center justify-center rounded-full border border-frost bg-snow text-[0.7em] text-mocha hover:border-sapphire hover:text-sapphire",
786
+ "aria-label": "remove attachment",
787
+ children: "✕"
788
+ })]
789
+ }, `${a.mimeType}-${i}`) : /* @__PURE__ */ jsxs("div", {
790
+ className: "flex items-center gap-1.5 rounded-md border border-frost bg-snow px-2 py-1 text-[0.82em] text-mocha",
791
+ children: [
792
+ /* @__PURE__ */ jsx("span", { children: "📄" }),
793
+ /* @__PURE__ */ jsx("span", {
794
+ className: "max-w-[160px] truncate",
795
+ children: a.name ?? "file"
796
+ }),
797
+ /* @__PURE__ */ jsx("button", {
798
+ type: "button",
799
+ onClick: remove,
800
+ className: "text-[0.85em] text-mocha hover:text-sapphire",
801
+ "aria-label": "remove attachment",
802
+ children: "✕"
803
+ })
804
+ ]
805
+ }, `${a.name ?? "file"}-${i}`);
806
+ })
807
+ }),
808
+ /* @__PURE__ */ jsxs("form", {
809
+ className: "flex items-end gap-2 border-t border-frost bg-ivory px-5 py-3",
810
+ onSubmit: (e) => {
811
+ e.preventDefault();
812
+ send(input);
813
+ },
814
+ children: [
815
+ /* @__PURE__ */ jsx("input", {
816
+ ref: fileInputRef,
817
+ type: "file",
818
+ multiple: true,
819
+ className: "hidden",
820
+ onChange: (e) => {
821
+ addFiles(e.target.files);
822
+ e.target.value = "";
823
+ }
824
+ }),
825
+ /* @__PURE__ */ jsx("button", {
826
+ type: "button",
827
+ onClick: () => fileInputRef.current?.click(),
828
+ disabled: streaming,
829
+ title: "attach images or files",
830
+ "aria-label": "attach files",
831
+ 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",
832
+ children: "📎"
833
+ }),
834
+ /* @__PURE__ */ jsx("textarea", {
835
+ ref: inputRef,
836
+ rows: 1,
837
+ value: input,
838
+ onChange: (e) => setInput(e.target.value),
839
+ onKeyDown,
840
+ onPaste,
841
+ disabled: streaming,
842
+ placeholder: "say something… (Shift+Enter for newline; paste or 📎 to attach images/files)",
843
+ autoComplete: "off",
844
+ 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)]"
845
+ }),
846
+ /* @__PURE__ */ jsx("button", {
847
+ type: "submit",
848
+ disabled: streaming || !input.trim() && attachments.length === 0,
849
+ 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",
850
+ children: "send"
851
+ }),
852
+ streaming && /* @__PURE__ */ jsx("button", {
853
+ type: "button",
854
+ onClick: cancel,
855
+ className: "rounded-md border-[1.5px] border-frost bg-transparent px-3 py-1.5 text-[0.92em] font-medium text-mocha hover:border-sapphire-light hover:bg-sapphire-glow hover:text-sapphire",
856
+ children: "cancel"
857
+ })
858
+ ]
859
+ })
860
+ ]
861
+ });
862
+ }
863
+ function Bubble({ entry, isLastUser, isWillDrop, onEdit }) {
864
+ const dropCls = isWillDrop ? "opacity-40 [&_.bubble-content]:line-through" : "";
865
+ if (entry.type === "user") {
866
+ if (entry.content.startsWith(INBOX_WAKE_PREFIX)) {
867
+ const body = entry.content.slice(24);
868
+ return /* @__PURE__ */ jsxs("div", {
869
+ className: `my-4 flex flex-col items-end ${dropCls}`,
870
+ children: [/* @__PURE__ */ jsx("span", {
871
+ className: "mb-1 text-[0.72em] font-semibold uppercase tracking-wider text-mocha opacity-90",
872
+ children: "inbox"
873
+ }), /* @__PURE__ */ jsx("div", {
874
+ className: "bubble-content max-w-[85%] whitespace-pre-wrap break-words rounded-[14px_14px_4px_14px] border border-fawn border-l-[3px] border-l-mocha-light bg-ivory px-3 py-2 font-mono text-[0.88em] leading-[1.5] text-mocha",
875
+ children: body
876
+ })]
877
+ });
878
+ }
879
+ return /* @__PURE__ */ jsxs("div", {
880
+ className: `group relative my-4 flex flex-col items-end ${dropCls}`,
881
+ children: [
882
+ /* @__PURE__ */ jsx("span", {
883
+ className: "sr-only",
884
+ children: "you"
885
+ }),
886
+ entry.images && entry.images.length > 0 && /* @__PURE__ */ jsx("div", {
887
+ className: "mb-1 flex max-w-[85%] flex-wrap justify-end gap-1",
888
+ children: entry.images.map((img, i) => /* @__PURE__ */ jsx("img", {
889
+ src: `data:${img.mimeType};base64,${img.data}`,
890
+ alt: "attachment",
891
+ className: "max-h-48 rounded-lg border border-sapphire-light"
892
+ }, i))
893
+ }),
894
+ entry.files && entry.files.length > 0 && /* @__PURE__ */ jsx("div", {
895
+ className: "mb-1 flex max-w-[85%] flex-wrap justify-end gap-1",
896
+ children: entry.files.map((f, i) => /* @__PURE__ */ jsxs("span", {
897
+ className: "flex items-center gap-1 rounded-md border border-sapphire-light bg-sapphire-glow px-2 py-1 text-[0.8em] text-chocolate",
898
+ children: ["📄 ", /* @__PURE__ */ jsx("span", {
899
+ className: "max-w-[180px] truncate",
900
+ children: f.name
901
+ })]
902
+ }, i))
903
+ }),
904
+ entry.content && /* @__PURE__ */ jsx("div", {
905
+ className: "bubble-content max-w-[85%] whitespace-pre-wrap break-words rounded-[14px_14px_4px_14px] border border-sapphire-light bg-sapphire-glow px-3 py-2 text-chocolate",
906
+ children: entry.content
907
+ }),
908
+ isLastUser && onEdit && /* @__PURE__ */ jsx("button", {
909
+ type: "button",
910
+ onClick: onEdit,
911
+ title: "edit and resend — replaces the last turn",
912
+ className: "absolute left-1 top-1 rounded-sm border border-fawn bg-ivory px-2 py-0.5 text-[0.78em] font-medium text-mocha opacity-65 transition hover:border-sapphire hover:bg-sapphire-glow hover:text-sapphire hover:opacity-100 group-hover:opacity-100 group-focus-within:opacity-100",
913
+ children: "edit"
914
+ })
915
+ ]
916
+ });
917
+ }
918
+ if (entry.type === "assistant") {
919
+ if (entry.content.startsWith(COMPACTION_REPLAY_PREFIX)) {
920
+ const summary = entry.content.slice(22).trim();
921
+ return /* @__PURE__ */ jsx("div", {
922
+ className: `my-3 border-y border-dashed border-frost py-2 text-[0.85em] italic text-mocha-light ${dropCls}`,
923
+ children: /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsx("summary", {
924
+ className: "cursor-pointer select-none text-center [&::-webkit-details-marker]:hidden",
925
+ children: "— conversation summary (click to expand) —"
926
+ }), /* @__PURE__ */ jsx("div", {
927
+ className: "md-content mt-2 rounded-sm bg-frost/40 p-2 not-italic",
928
+ dangerouslySetInnerHTML: { __html: renderMd(summary) }
929
+ })] })
930
+ });
931
+ }
932
+ if (entry.content.startsWith("[error]") || entry.content.startsWith("[fatal]") || entry.content.startsWith("[network error]")) return /* @__PURE__ */ jsxs("div", {
933
+ className: `my-4 flex flex-col items-start ${dropCls}`,
934
+ children: [/* @__PURE__ */ jsx("span", {
935
+ className: "mb-1 text-[0.72em] font-semibold uppercase tracking-wider text-[#9B3D3D]",
936
+ children: "error"
937
+ }), /* @__PURE__ */ jsx("div", {
938
+ className: "bubble-content rounded-r-sm border-l-[3px] border-l-[#9B3D3D] bg-[rgba(196,135,138,0.06)] py-1 pl-3 pr-2 leading-[1.55]",
939
+ dangerouslySetInnerHTML: { __html: renderMd(entry.content) }
940
+ })]
941
+ });
942
+ return /* @__PURE__ */ jsxs("div", {
943
+ className: `my-4 flex flex-col items-start ${dropCls}`,
944
+ children: [/* @__PURE__ */ jsx("span", {
945
+ className: "sr-only",
946
+ children: "agent"
947
+ }), /* @__PURE__ */ jsx("div", {
948
+ className: "md-content bubble-content w-full leading-[1.55] text-chocolate",
949
+ dangerouslySetInnerHTML: { __html: renderMd(entry.content) }
950
+ })]
951
+ });
952
+ }
953
+ if (entry.type === "tool") return /* @__PURE__ */ jsx(ToolGroup, {
954
+ items: entry.items,
955
+ dropCls
956
+ });
957
+ if (entry.type === "images") return /* @__PURE__ */ jsx("div", {
958
+ className: `my-2 space-y-2 ${dropCls}`,
959
+ children: entry.images.map((img, i) => /* @__PURE__ */ jsx("img", {
960
+ src: `data:${img.mimeType};base64,${img.data}`,
961
+ alt: "screenshot",
962
+ className: "block max-w-full rounded-lg border border-fawn"
963
+ }, i))
964
+ });
965
+ if (entry.type === "file") {
966
+ const isImage = entry.mimeType.startsWith("image/");
967
+ const href = `data:${entry.mimeType};base64,${entry.data}`;
968
+ return /* @__PURE__ */ jsxs("div", {
969
+ className: `my-2 ${dropCls}`,
970
+ children: [isImage && /* @__PURE__ */ jsx("img", {
971
+ src: href,
972
+ alt: entry.name,
973
+ className: "mb-1 block max-w-full rounded-lg border border-fawn"
974
+ }), /* @__PURE__ */ jsxs("a", {
975
+ href,
976
+ download: entry.name,
977
+ className: "inline-flex items-center gap-2 rounded-md border border-fawn bg-ivory px-3 py-2 text-[0.88em] text-mocha hover:border-sapphire hover:text-sapphire",
978
+ children: [
979
+ /* @__PURE__ */ jsx("span", { children: "📄" }),
980
+ /* @__PURE__ */ jsx("span", {
981
+ className: "max-w-[260px] truncate",
982
+ children: entry.name
983
+ }),
984
+ /* @__PURE__ */ jsx("span", {
985
+ className: "text-[0.85em] opacity-70",
986
+ children: "download"
987
+ })
988
+ ]
989
+ })]
990
+ });
991
+ }
992
+ if (entry.type === "system") return /* @__PURE__ */ jsxs("div", {
993
+ className: `my-3 rounded-r-sm border-l-[3px] border-sapphire bg-sapphire-glow px-3 py-1 font-mono text-[0.88em] text-sapphire-deep ${dropCls}`,
994
+ children: [/* @__PURE__ */ jsx("span", {
995
+ className: "mr-2 font-semibold uppercase tracking-wider opacity-80",
996
+ children: "system"
997
+ }), /* @__PURE__ */ jsx("span", {
998
+ className: "whitespace-pre-wrap",
999
+ children: entry.content
1000
+ })]
1001
+ });
1002
+ if (entry.type === "error") return /* @__PURE__ */ jsx("div", {
1003
+ className: `my-3 rounded-r-sm border-l-[3px] border-[#9B3D3D] bg-[rgba(196,135,138,0.08)] px-3 py-1 text-[0.92em] text-[#9B3D3D] ${dropCls}`,
1004
+ children: entry.content
1005
+ });
1006
+ return null;
1007
+ }
1008
+ function ToolGroup({ items, dropCls }) {
1009
+ const [expanded, setExpanded] = useState(false);
1010
+ const contentRef = useRef(null);
1011
+ const [overflows, setOverflows] = useState(false);
1012
+ useLayoutEffect(() => {
1013
+ const el = contentRef.current;
1014
+ if (!el) return;
1015
+ setOverflows(el.scrollHeight > 324);
1016
+ }, [items.length]);
1017
+ return /* @__PURE__ */ jsxs("div", {
1018
+ className: `my-1.5 rounded-r-sm border-l-[3px] border-fawn bg-ivory px-3 py-2 font-mono text-[0.82em] leading-[1.5] text-mocha-light ${dropCls}`,
1019
+ children: [/* @__PURE__ */ jsxs("div", {
1020
+ ref: contentRef,
1021
+ className: "relative overflow-hidden",
1022
+ style: { maxHeight: expanded ? "none" : `${TOOL_GROUP_MAX_HEIGHT_PX}px` },
1023
+ children: [items.map((it, i) => /* @__PURE__ */ jsx(ToolLine, { item: it }, i)), overflows && !expanded && /* @__PURE__ */ jsx("div", {
1024
+ className: "pointer-events-none absolute inset-x-0 bottom-0 h-10",
1025
+ style: { background: "linear-gradient(to bottom, rgba(247,240,229,0), var(--color-ivory))" }
1026
+ })]
1027
+ }), overflows && /* @__PURE__ */ jsx("button", {
1028
+ type: "button",
1029
+ onClick: () => setExpanded((v) => !v),
1030
+ className: "mt-2 rounded-sm border border-fawn bg-transparent px-2 py-0.5 text-[0.9em] text-mocha hover:border-sapphire hover:text-sapphire-deep",
1031
+ children: expanded ? "show less ↑" : "show more ↓"
1032
+ })]
1033
+ });
1034
+ }
1035
+ function ToolLine({ item }) {
1036
+ if (item.kind === "call") {
1037
+ const args = prettyArgs(item.body);
1038
+ const multiLine = args.includes("\n");
1039
+ return /* @__PURE__ */ jsxs("div", {
1040
+ className: "whitespace-pre-wrap break-words py-0.5",
1041
+ children: [
1042
+ /* @__PURE__ */ jsx("span", {
1043
+ className: "mr-1 opacity-45",
1044
+ children: "→"
1045
+ }),
1046
+ /* @__PURE__ */ jsx("span", {
1047
+ className: "font-medium text-mocha",
1048
+ children: item.name
1049
+ }),
1050
+ multiLine ? /* @__PURE__ */ jsx("pre", {
1051
+ className: "ml-4 mt-0.5 rounded-sm bg-[rgba(42,31,22,0.04)] px-2 py-1 font-mono text-[0.95em] leading-[1.4] text-mocha",
1052
+ children: args
1053
+ }) : args ? /* @__PURE__ */ jsxs("span", { children: [
1054
+ " (",
1055
+ args,
1056
+ ")"
1057
+ ] }) : null
1058
+ ]
1059
+ });
1060
+ }
1061
+ if (item.kind === "error") return /* @__PURE__ */ jsxs("div", {
1062
+ className: "whitespace-pre-wrap break-words py-0.5 text-rose-baziu",
1063
+ children: [/* @__PURE__ */ jsx("span", {
1064
+ className: "mr-1 opacity-45",
1065
+ children: "←"
1066
+ }), item.body]
1067
+ });
1068
+ return /* @__PURE__ */ jsxs("div", {
1069
+ className: "whitespace-pre-wrap break-words py-0.5",
1070
+ children: [/* @__PURE__ */ jsx("span", {
1071
+ className: "mr-1 opacity-45",
1072
+ children: "←"
1073
+ }), item.body]
1074
+ });
1075
+ }
1076
+ function Dot({ delay = "0s" }) {
1077
+ return /* @__PURE__ */ jsx("span", {
1078
+ className: "inline-block h-1.5 w-1.5 animate-pulse rounded-full bg-mocha-light/60",
1079
+ style: { animationDelay: delay },
1080
+ "aria-hidden": "true"
1081
+ });
1082
+ }
1083
+ //#endregion
1084
+ export { ChatPane as t };