@poncho-ai/cli 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.turbo/turbo-build.log +5 -5
  2. package/CHANGELOG.md +17 -0
  3. package/dist/chunk-2AZ3Y6R2.js +4121 -0
  4. package/dist/chunk-3273VMA7.js +4182 -0
  5. package/dist/chunk-6REJ5J4T.js +4142 -0
  6. package/dist/chunk-BSW557BB.js +4058 -0
  7. package/dist/chunk-GJYE4S3D.js +4164 -0
  8. package/dist/chunk-HGZTVHBT.js +4089 -0
  9. package/dist/chunk-HNYADV2K.js +4164 -0
  10. package/dist/chunk-IE47LJ33.js +4166 -0
  11. package/dist/chunk-MFVXK3SX.js +4177 -0
  12. package/dist/chunk-N7ZAHMBR.js +4178 -0
  13. package/dist/chunk-TYL4SGJE.js +4177 -0
  14. package/dist/chunk-VIX7Y2YC.js +4169 -0
  15. package/dist/chunk-WCIUVLV3.js +4171 -0
  16. package/dist/chunk-WXFCSFXF.js +4178 -0
  17. package/dist/cli.js +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/run-interactive-ink-4EWW4AJ6.js +463 -0
  20. package/dist/run-interactive-ink-642LZIYZ.js +494 -0
  21. package/dist/run-interactive-ink-72H5IUTC.js +494 -0
  22. package/dist/run-interactive-ink-7P4WWB2Y.js +494 -0
  23. package/dist/run-interactive-ink-EP7GIKLH.js +463 -0
  24. package/dist/run-interactive-ink-FASKW7SN.js +463 -0
  25. package/dist/run-interactive-ink-LLNLDCES.js +494 -0
  26. package/dist/run-interactive-ink-MO6MGLEY.js +494 -0
  27. package/dist/run-interactive-ink-OQZN5DQE.js +463 -0
  28. package/dist/run-interactive-ink-QKB6CG3W.js +494 -0
  29. package/dist/run-interactive-ink-RJCA5IQA.js +494 -0
  30. package/dist/run-interactive-ink-RU2PH6R5.js +494 -0
  31. package/dist/run-interactive-ink-T36C6TJ2.js +463 -0
  32. package/dist/run-interactive-ink-ZL6RAS2O.js +494 -0
  33. package/package.json +3 -2
  34. package/src/index.ts +46 -10
  35. package/src/run-interactive-ink.ts +45 -10
  36. package/src/web-ui.ts +206 -103
@@ -443,6 +443,9 @@ export const runInteractiveInk = async ({
443
443
  let sawChunk = false;
444
444
  let toolEvents = 0;
445
445
  const toolTimeline: string[] = [];
446
+ const sections: Array<{ type: "text" | "tools"; content: string | string[] }> = [];
447
+ let currentText = "";
448
+ let currentTools: string[] = [];
446
449
  let runFailed = false;
447
450
  let usage: TokenUsage | undefined;
448
451
  let latestRunId = "";
@@ -459,8 +462,14 @@ export const runInteractiveInk = async ({
459
462
  }
460
463
  if (event.type === "model:chunk") {
461
464
  sawChunk = true;
465
+ // If we have tools accumulated and text starts again, push tools as a section
466
+ if (currentTools.length > 0) {
467
+ sections.push({ type: "tools", content: currentTools });
468
+ currentTools = [];
469
+ }
462
470
  responseText += event.content;
463
471
  streamedText += event.content;
472
+ currentText += event.content;
464
473
 
465
474
  if (!thinkingCleared) {
466
475
  clearThinking();
@@ -485,11 +494,18 @@ export const runInteractiveInk = async ({
485
494
  clearThinking();
486
495
 
487
496
  if (event.type === "tool:started") {
497
+ // If we have text accumulated, push it as a text section
498
+ if (currentText.length > 0) {
499
+ sections.push({ type: "text", content: currentText });
500
+ currentText = "";
501
+ }
488
502
  const preview = showToolPayloads
489
503
  ? compactPreview(event.input, 400)
490
504
  : compactPreview(event.input, 100);
491
505
  console.log(yellow(`tools> start ${event.tool} input=${preview}`));
492
- toolTimeline.push(`- start \`${event.tool}\``);
506
+ const toolText = `- start \`${event.tool}\``;
507
+ toolTimeline.push(toolText);
508
+ currentTools.push(toolText);
493
509
  toolEvents += 1;
494
510
  } else if (event.type === "tool:completed") {
495
511
  const preview = showToolPayloads
@@ -503,29 +519,37 @@ export const runInteractiveInk = async ({
503
519
  if (showToolPayloads) {
504
520
  console.log(yellow(`tools> output ${preview}`));
505
521
  }
506
- toolTimeline.push(
507
- `- done \`${event.tool}\` in ${formatDuration(event.duration)}`,
508
- );
522
+ const toolText = `- done \`${event.tool}\` in ${formatDuration(event.duration)}`;
523
+ toolTimeline.push(toolText);
524
+ currentTools.push(toolText);
509
525
  } else if (event.type === "tool:error") {
510
526
  console.log(
511
527
  red(`tools> error ${event.tool}: ${event.error}`),
512
528
  );
513
- toolTimeline.push(`- error \`${event.tool}\`: ${event.error}`);
529
+ const toolText = `- error \`${event.tool}\`: ${event.error}`;
530
+ toolTimeline.push(toolText);
531
+ currentTools.push(toolText);
514
532
  } else if (event.type === "tool:approval:required") {
515
533
  console.log(
516
534
  magenta(`tools> approval required for ${event.tool}`),
517
535
  );
518
- toolTimeline.push(`- approval required \`${event.tool}\``);
536
+ const toolText = `- approval required \`${event.tool}\``;
537
+ toolTimeline.push(toolText);
538
+ currentTools.push(toolText);
519
539
  } else if (event.type === "tool:approval:granted") {
520
540
  console.log(
521
541
  gray(`tools> approval granted (${event.approvalId})`),
522
542
  );
523
- toolTimeline.push(`- approval granted (${event.approvalId})`);
543
+ const toolText = `- approval granted (${event.approvalId})`;
544
+ toolTimeline.push(toolText);
545
+ currentTools.push(toolText);
524
546
  } else if (event.type === "tool:approval:denied") {
525
547
  console.log(
526
548
  magenta(`tools> approval denied (${event.approvalId})`),
527
549
  );
528
- toolTimeline.push(`- approval denied (${event.approvalId})`);
550
+ const toolText = `- approval denied (${event.approvalId})`;
551
+ toolTimeline.push(toolText);
552
+ currentTools.push(toolText);
529
553
  }
530
554
  } else if (event.type === "run:error") {
531
555
  clearThinking();
@@ -588,13 +612,24 @@ export const runInteractiveInk = async ({
588
612
  activeConversationId = created.conversationId;
589
613
  }
590
614
 
615
+ // Finalize sections
616
+ if (currentTools.length > 0) {
617
+ sections.push({ type: "tools", content: currentTools });
618
+ }
619
+ if (currentText.length > 0) {
620
+ sections.push({ type: "text", content: currentText });
621
+ }
622
+
591
623
  messages.push({ role: "user", content: trimmed });
592
624
  messages.push({
593
625
  role: "assistant",
594
626
  content: responseText,
595
627
  metadata:
596
- toolTimeline.length > 0
597
- ? ({ toolActivity: toolTimeline } as Message["metadata"])
628
+ toolTimeline.length > 0 || sections.length > 0
629
+ ? ({
630
+ toolActivity: toolTimeline,
631
+ sections: sections.length > 0 ? sections : undefined,
632
+ } as Message["metadata"])
598
633
  : undefined,
599
634
  });
600
635
  turn = computeTurn(messages);
package/src/web-ui.ts CHANGED
@@ -1,10 +1,18 @@
1
1
  import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
2
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
- import { basename, dirname, resolve } from "node:path";
3
+ import { readFileSync } from "node:fs";
4
+ import { basename, dirname, resolve, join } from "node:path";
4
5
  import { homedir } from "node:os";
6
+ import { createRequire } from "node:module";
5
7
  import type { IncomingMessage, ServerResponse } from "node:http";
6
8
  import type { Message } from "@poncho-ai/sdk";
7
9
 
10
+ // Load marked library at module initialization (ESM compatible)
11
+ const require = createRequire(import.meta.url);
12
+ const markedPackagePath = require.resolve("marked");
13
+ const markedDir = dirname(markedPackagePath);
14
+ const markedSource = readFileSync(join(markedDir, "marked.umd.js"), "utf-8");
15
+
8
16
  export interface WebUiConversation {
9
17
  conversationId: string;
10
18
  title: string;
@@ -783,8 +791,72 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
783
791
  font-size: 13px;
784
792
  line-height: 1.5;
785
793
  }
794
+ .tool-activity-inline {
795
+ margin: 8px 0;
796
+ font-size: 12px;
797
+ line-height: 1.45;
798
+ color: #8a8a8a;
799
+ }
800
+ .tool-activity-inline code {
801
+ font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
802
+ background: rgba(255,255,255,0.04);
803
+ border: 1px solid rgba(255,255,255,0.08);
804
+ padding: 4px 8px;
805
+ border-radius: 6px;
806
+ color: #bcbcbc;
807
+ font-size: 11px;
808
+ }
809
+ .tool-status {
810
+ color: #8a8a8a;
811
+ font-style: italic;
812
+ }
813
+ .tool-done {
814
+ color: #6a9955;
815
+ }
816
+ .tool-error {
817
+ color: #f48771;
818
+ }
819
+ .assistant-content table {
820
+ border-collapse: collapse;
821
+ width: 100%;
822
+ margin: 14px 0;
823
+ font-size: 13px;
824
+ border: 1px solid rgba(255,255,255,0.08);
825
+ border-radius: 8px;
826
+ overflow: hidden;
827
+ display: block;
828
+ max-width: 100%;
829
+ overflow-x: auto;
830
+ white-space: nowrap;
831
+ }
832
+ .assistant-content th {
833
+ background: rgba(255,255,255,0.06);
834
+ padding: 10px 12px;
835
+ text-align: left;
836
+ font-weight: 600;
837
+ border-bottom: 1px solid rgba(255,255,255,0.12);
838
+ color: #fff;
839
+ min-width: 100px;
840
+ }
841
+ .assistant-content td {
842
+ padding: 10px 12px;
843
+ border-bottom: 1px solid rgba(255,255,255,0.06);
844
+ min-width: 100px;
845
+ }
846
+ .assistant-content tr:last-child td {
847
+ border-bottom: none;
848
+ }
849
+ .assistant-content tbody tr:hover {
850
+ background: rgba(255,255,255,0.02);
851
+ }
852
+ .assistant-content hr {
853
+ border: 0;
854
+ border-top: 1px solid rgba(255,255,255,0.1);
855
+ margin: 20px 0;
856
+ }
786
857
  .tool-activity {
787
858
  margin-top: 12px;
859
+ margin-bottom: 12px;
788
860
  border: 1px solid rgba(255,255,255,0.08);
789
861
  background: rgba(255,255,255,0.03);
790
862
  border-radius: 10px;
@@ -793,6 +865,9 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
793
865
  color: #bcbcbc;
794
866
  max-width: 300px;
795
867
  }
868
+ .assistant-content > .tool-activity:first-child {
869
+ margin-top: 0;
870
+ }
796
871
  .tool-activity-disclosure {
797
872
  display: block;
798
873
  }
@@ -1064,6 +1139,15 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
1064
1139
  </div>
1065
1140
 
1066
1141
  <script>
1142
+ // Marked library (inlined)
1143
+ ${markedSource}
1144
+
1145
+ // Configure marked for GitHub Flavored Markdown (tables, etc.)
1146
+ marked.setOptions({
1147
+ gfm: true,
1148
+ breaks: true
1149
+ });
1150
+
1067
1151
  const state = {
1068
1152
  csrfToken: "",
1069
1153
  conversations: [],
@@ -1149,75 +1233,17 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
1149
1233
  .replace(/"/g, "&quot;")
1150
1234
  .replace(/'/g, "&#39;");
1151
1235
 
1152
- const renderInlineMarkdown = (value) => {
1153
- let html = escapeHtml(value);
1154
- html = html.replace(/\\*\\*([^*]+)\\*\\*/g, "<strong>$1</strong>");
1155
- html = html.replace(/\\x60([^\\x60]+)\\x60/g, "<code>$1</code>");
1156
- return html;
1157
- };
1158
-
1159
- const renderMarkdownBlock = (value) => {
1160
- const lines = String(value || "").split("\\n");
1161
- let html = "";
1162
- let inList = false;
1163
-
1164
- for (const rawLine of lines) {
1165
- const line = rawLine.trimEnd();
1166
- const trimmed = line.trim();
1167
- const headingMatch = trimmed.match(/^(#{1,3})\\s+(.+)$/);
1168
-
1169
- if (headingMatch) {
1170
- if (inList) {
1171
- html += "</ul>";
1172
- inList = false;
1173
- }
1174
- const level = Math.min(3, headingMatch[1].length);
1175
- const tag = level === 1 ? "h2" : level === 2 ? "h3" : "p";
1176
- html += "<" + tag + ">" + renderInlineMarkdown(headingMatch[2]) + "</" + tag + ">";
1177
- continue;
1178
- }
1179
-
1180
- if (/^\\s*-\\s+/.test(line)) {
1181
- if (!inList) {
1182
- html += "<ul>";
1183
- inList = true;
1184
- }
1185
- html += "<li>" + renderInlineMarkdown(line.replace(/^\\s*-\\s+/, "")) + "</li>";
1186
- continue;
1187
- }
1188
- if (inList) {
1189
- html += "</ul>";
1190
- inList = false;
1191
- }
1192
- if (trimmed.length === 0) {
1193
- continue;
1194
- }
1195
- html += "<p>" + renderInlineMarkdown(line) + "</p>";
1196
- }
1197
-
1198
- if (inList) {
1199
- html += "</ul>";
1200
- }
1201
- return html;
1202
- };
1203
-
1204
1236
  const renderAssistantMarkdown = (value) => {
1205
- const source = String(value || "");
1206
- const fenceRegex = /\\x60\\x60\\x60([\\s\\S]*?)\\x60\\x60\\x60/g;
1207
- let html = "";
1208
- let lastIndex = 0;
1209
- let match;
1210
-
1211
- while ((match = fenceRegex.exec(source))) {
1212
- const before = source.slice(lastIndex, match.index);
1213
- html += renderMarkdownBlock(before);
1214
- const codeText = String(match[1] || "").replace(/^\\n+|\\n+$/g, "");
1215
- html += "<pre><code>" + escapeHtml(codeText) + "</code></pre>";
1216
- lastIndex = match.index + match[0].length;
1217
- }
1237
+ const source = String(value || "").trim();
1238
+ if (!source) return "<p></p>";
1218
1239
 
1219
- html += renderMarkdownBlock(source.slice(lastIndex));
1220
- return html || "<p></p>";
1240
+ try {
1241
+ return marked.parse(source);
1242
+ } catch (error) {
1243
+ console.error("Markdown parsing error:", error);
1244
+ // Fallback to escaped text
1245
+ return "<p>" + escapeHtml(source) + "</p>";
1246
+ }
1221
1247
  };
1222
1248
 
1223
1249
  const extractToolActivity = (value) => {
@@ -1360,23 +1386,13 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
1360
1386
  const content = document.createElement("div");
1361
1387
  content.className = "assistant-content";
1362
1388
  const text = String(m.content || "");
1363
- const parsed = extractToolActivity(text);
1364
- const metadataToolActivity =
1365
- m.metadata && Array.isArray(m.metadata.toolActivity)
1366
- ? m.metadata.toolActivity
1367
- : [];
1368
- const toolActivity =
1369
- Array.isArray(m._toolActivity) && m._toolActivity.length > 0
1370
- ? m._toolActivity
1371
- : metadataToolActivity.length > 0
1372
- ? metadataToolActivity
1373
- : parsed.activities;
1389
+
1374
1390
  if (m._error) {
1375
1391
  const errorEl = document.createElement("div");
1376
1392
  errorEl.className = "message-error";
1377
1393
  errorEl.innerHTML = "<strong>Error</strong><br>" + escapeHtml(m._error);
1378
1394
  content.appendChild(errorEl);
1379
- } else if (isStreaming && i === messages.length - 1 && !parsed.content) {
1395
+ } else if (isStreaming && i === messages.length - 1 && !text && (!m._chunks || m._chunks.length === 0)) {
1380
1396
  const spinner = document.createElement("span");
1381
1397
  spinner.className = "thinking-indicator";
1382
1398
  const starFrames = ["✶","✸","✹","✺","✹","✷"];
@@ -1385,10 +1401,44 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
1385
1401
  spinner._interval = setInterval(() => { frame = (frame + 1) % starFrames.length; spinner.textContent = starFrames[frame]; }, 70);
1386
1402
  content.appendChild(spinner);
1387
1403
  } else {
1388
- content.innerHTML = renderAssistantMarkdown(parsed.content);
1389
- }
1390
- if (toolActivity.length > 0) {
1391
- content.insertAdjacentHTML("beforeend", renderToolActivity(toolActivity));
1404
+ // Check for sections in _sections (streaming) or metadata.sections (stored)
1405
+ const sections = m._sections || (m.metadata && m.metadata.sections);
1406
+
1407
+ if (sections && sections.length > 0) {
1408
+ // Render sections interleaved
1409
+ sections.forEach(section => {
1410
+ if (section.type === "text") {
1411
+ const textDiv = document.createElement("div");
1412
+ textDiv.innerHTML = renderAssistantMarkdown(section.content);
1413
+ content.appendChild(textDiv);
1414
+ } else if (section.type === "tools") {
1415
+ content.insertAdjacentHTML("beforeend", renderToolActivity(section.content));
1416
+ }
1417
+ });
1418
+ // While streaming, show current tools if any
1419
+ if (isStreaming && i === messages.length - 1 && m._currentTools && m._currentTools.length > 0) {
1420
+ content.insertAdjacentHTML("beforeend", renderToolActivity(m._currentTools));
1421
+ }
1422
+ // Show current text being typed
1423
+ if (isStreaming && i === messages.length - 1 && m._currentText) {
1424
+ const textDiv = document.createElement("div");
1425
+ textDiv.innerHTML = renderAssistantMarkdown(m._currentText);
1426
+ content.appendChild(textDiv);
1427
+ }
1428
+ } else {
1429
+ // Fallback: render text and tools the old way (for old messages without sections)
1430
+ if (text) {
1431
+ const parsed = extractToolActivity(text);
1432
+ content.innerHTML = renderAssistantMarkdown(parsed.content);
1433
+ }
1434
+ const metadataToolActivity =
1435
+ m.metadata && Array.isArray(m.metadata.toolActivity)
1436
+ ? m.metadata.toolActivity
1437
+ : [];
1438
+ if (metadataToolActivity.length > 0) {
1439
+ content.insertAdjacentHTML("beforeend", renderToolActivity(metadataToolActivity));
1440
+ }
1441
+ }
1392
1442
  }
1393
1443
  wrap.appendChild(content);
1394
1444
  row.appendChild(wrap);
@@ -1496,7 +1546,14 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
1496
1546
  return;
1497
1547
  }
1498
1548
  const localMessages = [...(state.activeMessages || []), { role: "user", content: messageText }];
1499
- let assistantMessage = { role: "assistant", content: "", metadata: { toolActivity: [] } };
1549
+ let assistantMessage = {
1550
+ role: "assistant",
1551
+ content: "",
1552
+ _sections: [], // Array of {type: 'text'|'tools', content: string|array}
1553
+ _currentText: "",
1554
+ _currentTools: [],
1555
+ metadata: { toolActivity: [] }
1556
+ };
1500
1557
  localMessages.push(assistantMessage);
1501
1558
  state.activeMessages = localMessages;
1502
1559
  renderMessages(localMessages, true);
@@ -1526,44 +1583,88 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
1526
1583
  buffer += decoder.decode(value, { stream: true });
1527
1584
  buffer = parseSseChunk(buffer, (eventName, payload) => {
1528
1585
  if (eventName === "model:chunk") {
1529
- assistantMessage.content += String(payload.content || "");
1586
+ const chunk = String(payload.content || "");
1587
+ // If we have tools accumulated and text starts again, push tools as a section
1588
+ if (assistantMessage._currentTools.length > 0 && chunk.length > 0) {
1589
+ assistantMessage._sections.push({ type: "tools", content: assistantMessage._currentTools });
1590
+ assistantMessage._currentTools = [];
1591
+ }
1592
+ assistantMessage.content += chunk;
1593
+ assistantMessage._currentText += chunk;
1530
1594
  renderMessages(localMessages, true);
1531
1595
  }
1532
1596
  if (eventName === "tool:started") {
1533
- pushToolActivity(assistantMessage, "start " + (payload.tool || "tool"));
1597
+ const toolName = payload.tool || "tool";
1598
+ // If we have text accumulated, push it as a text section
1599
+ if (assistantMessage._currentText.length > 0) {
1600
+ assistantMessage._sections.push({ type: "text", content: assistantMessage._currentText });
1601
+ assistantMessage._currentText = "";
1602
+ }
1603
+ const toolText = "- start \\x60" + toolName + "\\x60";
1604
+ assistantMessage._currentTools.push(toolText);
1605
+ if (!assistantMessage.metadata) assistantMessage.metadata = {};
1606
+ if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
1607
+ assistantMessage.metadata.toolActivity.push(toolText);
1534
1608
  renderMessages(localMessages, true);
1535
1609
  }
1536
1610
  if (eventName === "tool:completed") {
1611
+ const toolName = payload.tool || "tool";
1537
1612
  const duration = typeof payload.duration === "number" ? payload.duration : null;
1538
- pushToolActivity(
1539
- assistantMessage,
1540
- "done " +
1541
- (payload.tool || "tool") +
1542
- (duration !== null ? " (" + duration + "ms)" : ""),
1543
- );
1613
+ const toolText = "- done \\x60" + toolName + "\\x60" + (duration !== null ? " (" + duration + "ms)" : "");
1614
+ assistantMessage._currentTools.push(toolText);
1615
+ if (!assistantMessage.metadata) assistantMessage.metadata = {};
1616
+ if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
1617
+ assistantMessage.metadata.toolActivity.push(toolText);
1544
1618
  renderMessages(localMessages, true);
1545
1619
  }
1546
1620
  if (eventName === "tool:error") {
1547
- pushToolActivity(
1548
- assistantMessage,
1549
- "error " + (payload.tool || "tool") + ": " + (payload.error || "unknown error"),
1550
- );
1621
+ const toolName = payload.tool || "tool";
1622
+ const errorMsg = payload.error || "unknown error";
1623
+ const toolText = "- error \\x60" + toolName + "\\x60: " + errorMsg;
1624
+ assistantMessage._currentTools.push(toolText);
1625
+ if (!assistantMessage.metadata) assistantMessage.metadata = {};
1626
+ if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
1627
+ assistantMessage.metadata.toolActivity.push(toolText);
1551
1628
  renderMessages(localMessages, true);
1552
1629
  }
1553
1630
  if (eventName === "tool:approval:required") {
1554
- pushToolActivity(assistantMessage, "approval required for " + (payload.tool || "tool"));
1631
+ const toolName = payload.tool || "tool";
1632
+ const toolText = "- approval required \\x60" + toolName + "\\x60";
1633
+ assistantMessage._currentTools.push(toolText);
1634
+ if (!assistantMessage.metadata) assistantMessage.metadata = {};
1635
+ if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
1636
+ assistantMessage.metadata.toolActivity.push(toolText);
1555
1637
  renderMessages(localMessages, true);
1556
1638
  }
1557
1639
  if (eventName === "tool:approval:granted") {
1558
- pushToolActivity(assistantMessage, "approval granted");
1640
+ const toolText = "- approval granted";
1641
+ assistantMessage._currentTools.push(toolText);
1642
+ if (!assistantMessage.metadata) assistantMessage.metadata = {};
1643
+ if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
1644
+ assistantMessage.metadata.toolActivity.push(toolText);
1559
1645
  renderMessages(localMessages, true);
1560
1646
  }
1561
1647
  if (eventName === "tool:approval:denied") {
1562
- pushToolActivity(assistantMessage, "approval denied");
1648
+ const toolText = "- approval denied";
1649
+ assistantMessage._currentTools.push(toolText);
1650
+ if (!assistantMessage.metadata) assistantMessage.metadata = {};
1651
+ if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
1652
+ assistantMessage.metadata.toolActivity.push(toolText);
1563
1653
  renderMessages(localMessages, true);
1564
1654
  }
1565
- if (eventName === "run:completed" && (!assistantMessage.content || assistantMessage.content.length === 0)) {
1566
- assistantMessage.content = String(payload.result?.response || "");
1655
+ if (eventName === "run:completed") {
1656
+ if (!assistantMessage.content || assistantMessage.content.length === 0) {
1657
+ assistantMessage.content = String(payload.result?.response || "");
1658
+ }
1659
+ // Finalize sections: push any remaining tools and text
1660
+ if (assistantMessage._currentTools.length > 0) {
1661
+ assistantMessage._sections.push({ type: "tools", content: assistantMessage._currentTools });
1662
+ assistantMessage._currentTools = [];
1663
+ }
1664
+ if (assistantMessage._currentText.length > 0) {
1665
+ assistantMessage._sections.push({ type: "text", content: assistantMessage._currentText });
1666
+ assistantMessage._currentText = "";
1667
+ }
1567
1668
  renderMessages(localMessages, false);
1568
1669
  }
1569
1670
  if (eventName === "run:error") {
@@ -1574,8 +1675,10 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
1574
1675
  }
1575
1676
  });
1576
1677
  }
1678
+ // Update the state with our local messages (don't reload and lose tool chips)
1679
+ state.activeMessages = localMessages;
1577
1680
  await loadConversations();
1578
- await loadConversation(conversationId);
1681
+ // Don't reload the conversation - we already have the latest state with tool chips
1579
1682
  } finally {
1580
1683
  setStreaming(false);
1581
1684
  elements.prompt.focus();