@poncho-ai/cli 0.29.0 → 0.30.1

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.
@@ -567,6 +567,9 @@ export const runInteractiveInk = async ({
567
567
  if (currentTools.length > 0) {
568
568
  sections.push({ type: "tools", content: currentTools });
569
569
  currentTools = [];
570
+ if (responseText.length > 0 && !/\s$/.test(responseText)) {
571
+ responseText += " ";
572
+ }
570
573
  }
571
574
  responseText += event.content;
572
575
  streamedText += event.content;
@@ -30,6 +30,8 @@ export const getWebUiClientScript = (markedSource: string): string => `
30
30
  parentConversationId: null,
31
31
  todos: [],
32
32
  todoPanelCollapsed: false,
33
+ cronSectionCollapsed: true,
34
+ cronShowAll: false,
33
35
  };
34
36
 
35
37
  const agentInitial = document.body.dataset.agentInitial || "A";
@@ -342,14 +344,24 @@ export const getWebUiClientScript = (markedSource: string): string => `
342
344
  return "";
343
345
  }
344
346
  const subagentLinkRe = /\\s*\\[subagent:([^\\]]+)\\]$/;
347
+ const toolLinkRe = /\\s*\\[link:(https?:\\/\\/[^\\]]+)\\]$/;
345
348
  const renderActivityItem = (item) => {
346
- const match = item.match(subagentLinkRe);
347
- if (match) {
349
+ const subMatch = item.match(subagentLinkRe);
350
+ if (subMatch) {
348
351
  const cleaned = escapeHtml(item.replace(subagentLinkRe, ""));
349
- const subId = escapeHtml(match[1]);
352
+ const subId = escapeHtml(subMatch[1]);
350
353
  return '<div class="tool-activity-item">' + cleaned +
351
354
  ' <a class="subagent-link" href="javascript:void(0)" data-subagent-id="' + subId + '">View subagent</a></div>';
352
355
  }
356
+ const linkMatch = item.match(toolLinkRe);
357
+ if (linkMatch) {
358
+ const cleaned = escapeHtml(item.replace(toolLinkRe, ""));
359
+ const href = escapeHtml(linkMatch[1]);
360
+ var displayUrl = linkMatch[1].replace(/^https?:\\/\\//, "");
361
+ if (displayUrl.length > 55) displayUrl = displayUrl.slice(0, 52) + "...";
362
+ return '<div class="tool-activity-item">' + cleaned +
363
+ ' <a class="tool-link" href="' + href + '" target="_blank" rel="noopener">' + escapeHtml(displayUrl) + '</a></div>';
364
+ }
353
365
  return '<div class="tool-activity-item">' + escapeHtml(item) + "</div>";
354
366
  };
355
367
  const chips = hasItems
@@ -763,11 +775,86 @@ export const getWebUiClientScript = (markedSource: string): string => `
763
775
  }
764
776
  };
765
777
 
778
+ const cronCaretSvg = '<svg viewBox="0 0 12 12" fill="none" width="10" height="10"><path d="M4.5 2.75L8 6L4.5 9.25" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
779
+
780
+ const parseCronTitle = (title) => {
781
+ const rest = title.replace(/^\[cron\]\s*/, "");
782
+ const isoMatch = rest.match(/\s(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)$/);
783
+ if (isoMatch) {
784
+ return { jobName: rest.slice(0, isoMatch.index).trim(), timestamp: isoMatch[1] };
785
+ }
786
+ return { jobName: rest, timestamp: "" };
787
+ };
788
+
789
+ const formatCronTimestamp = (isoStr) => {
790
+ if (!isoStr) return "";
791
+ try {
792
+ const d = new Date(isoStr);
793
+ if (isNaN(d.getTime())) return isoStr;
794
+ return d.toLocaleString(undefined, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" });
795
+ } catch { return isoStr; }
796
+ };
797
+
798
+ const CRON_PAGE_SIZE = 20;
799
+
800
+ const appendCronSection = (cronConvs, needsDivider) => {
801
+ if (needsDivider) {
802
+ const divider = document.createElement("div");
803
+ divider.className = "sidebar-section-divider";
804
+ elements.list.appendChild(divider);
805
+ }
806
+
807
+ cronConvs.sort((a, b) => (b.updatedAt || b.createdAt || 0) - (a.updatedAt || a.createdAt || 0));
808
+
809
+ const isOpen = !state.cronSectionCollapsed;
810
+ const header = document.createElement("div");
811
+ header.className = "cron-section-header";
812
+ header.innerHTML =
813
+ '<span class="cron-section-caret' + (isOpen ? ' open' : '') + '">' + cronCaretSvg + '</span>' +
814
+ '<span>Cron jobs</span>' +
815
+ '<span class="cron-section-count">' + cronConvs.length + '</span>';
816
+ header.onclick = () => {
817
+ state.cronSectionCollapsed = !state.cronSectionCollapsed;
818
+ state.cronShowAll = false;
819
+ renderConversationList();
820
+ };
821
+ elements.list.appendChild(header);
822
+
823
+ if (state.cronSectionCollapsed) return;
824
+
825
+ const limit = state.cronShowAll ? cronConvs.length : CRON_PAGE_SIZE;
826
+ const visible = cronConvs.slice(0, limit);
827
+
828
+ for (const c of visible) {
829
+ const { jobName, timestamp } = parseCronTitle(c.title);
830
+ const fmtTime = formatCronTimestamp(timestamp);
831
+ const displayTitle = fmtTime ? jobName + " \\u00b7 " + fmtTime : c.title;
832
+ elements.list.appendChild(buildConversationItem(Object.assign({}, c, { title: displayTitle })));
833
+ appendSubagentsIfActive(c.conversationId);
834
+ }
835
+
836
+ if (!state.cronShowAll && cronConvs.length > CRON_PAGE_SIZE) {
837
+ const remaining = cronConvs.length - CRON_PAGE_SIZE;
838
+ const viewMore = document.createElement("div");
839
+ viewMore.className = "cron-view-more";
840
+ viewMore.textContent = "View " + remaining + " more\\u2026";
841
+ viewMore.onclick = () => {
842
+ state.cronShowAll = true;
843
+ renderConversationList();
844
+ };
845
+ elements.list.appendChild(viewMore);
846
+ }
847
+ };
848
+
766
849
  const renderConversationList = () => {
767
850
  elements.list.innerHTML = "";
768
851
  const pending = state.conversations.filter(c => c.hasPendingApprovals);
769
852
  const rest = state.conversations.filter(c => !c.hasPendingApprovals);
770
853
 
854
+ const isCron = (c) => c.title && c.title.startsWith("[cron]");
855
+ const cronConvs = rest.filter(isCron);
856
+ const nonCron = rest.filter(c => !isCron(c));
857
+
771
858
  if (pending.length > 0) {
772
859
  const label = document.createElement("div");
773
860
  label.className = "sidebar-section-label";
@@ -786,7 +873,7 @@ export const getWebUiClientScript = (markedSource: string): string => `
786
873
  const latest = [];
787
874
  const previous7 = [];
788
875
  const older = [];
789
- for (const c of rest) {
876
+ for (const c of nonCron) {
790
877
  const ts = c.updatedAt || c.createdAt || 0;
791
878
  if (ts >= startOfToday) {
792
879
  latest.push(c);
@@ -798,6 +885,12 @@ export const getWebUiClientScript = (markedSource: string): string => `
798
885
  }
799
886
 
800
887
  let sectionRendered = pending.length > 0;
888
+
889
+ if (cronConvs.length > 0) {
890
+ appendCronSection(cronConvs, sectionRendered);
891
+ sectionRendered = true;
892
+ }
893
+
801
894
  const appendSection = (items, labelText) => {
802
895
  if (items.length === 0) return;
803
896
  if (sectionRendered) {
@@ -1595,10 +1688,17 @@ export const getWebUiClientScript = (markedSource: string): string => `
1595
1688
  );
1596
1689
  const duration =
1597
1690
  typeof payload.duration === "number" ? payload.duration : null;
1598
- const detail =
1691
+ var detail =
1599
1692
  activeActivity && typeof activeActivity.detail === "string"
1600
1693
  ? activeActivity.detail.trim()
1601
1694
  : "";
1695
+ const out = payload.output && typeof payload.output === "object" ? payload.output : {};
1696
+ if (!detail && toolName === "web_search" && typeof out.query === "string") {
1697
+ detail = "\\x22" + (out.query.length > 60 ? out.query.slice(0, 57) + "..." : out.query) + "\\x22";
1698
+ }
1699
+ if (!detail && toolName === "web_fetch" && typeof out.url === "string") {
1700
+ detail = out.url;
1701
+ }
1602
1702
  const meta = [];
1603
1703
  if (duration !== null) meta.push(duration + "ms");
1604
1704
  if (detail) meta.push(detail);
@@ -1610,6 +1710,12 @@ export const getWebUiClientScript = (markedSource: string): string => `
1610
1710
  if (toolName === "spawn_subagent" && payload.output && typeof payload.output === "object" && payload.output.subagentId) {
1611
1711
  toolText += " [subagent:" + payload.output.subagentId + "]";
1612
1712
  }
1713
+ if (toolName === "web_fetch" && typeof out.url === "string") {
1714
+ toolText += " [link:" + out.url + "]";
1715
+ }
1716
+ if (toolName === "web_search" && Array.isArray(out.results)) {
1717
+ toolText += " \\u2014 " + out.results.length + " result" + (out.results.length !== 1 ? "s" : "");
1718
+ }
1613
1719
  assistantMessage._currentTools.push(toolText);
1614
1720
  assistantMessage.metadata.toolActivity.push(toolText);
1615
1721
  if (typeof payload.outputTokenEstimate === "number" && payload.outputTokenEstimate > 0 && state.contextWindow > 0) {
@@ -2049,6 +2155,28 @@ export const getWebUiClientScript = (markedSource: string): string => `
2049
2155
  };
2050
2156
  }
2051
2157
 
2158
+ if (toolName === "web_search") {
2159
+ const query = getStringInputField(input, "query");
2160
+ const short = query && query.length > 60 ? query.slice(0, 57) + "..." : query;
2161
+ return {
2162
+ kind: "tool",
2163
+ tool: toolName,
2164
+ label: "Searching" + (short ? " \\x22" + short + "\\x22" : ""),
2165
+ detail: short ? "\\x22" + short + "\\x22" : "",
2166
+ };
2167
+ }
2168
+
2169
+ if (toolName === "web_fetch") {
2170
+ const url = getStringInputField(input, "url");
2171
+ const short = url && url.length > 60 ? url.slice(0, 57) + "..." : url;
2172
+ return {
2173
+ kind: "tool",
2174
+ tool: toolName,
2175
+ label: "Fetching " + (short || "page"),
2176
+ detail: url || "",
2177
+ };
2178
+ }
2179
+
2052
2180
  return {
2053
2181
  kind: "tool",
2054
2182
  tool: toolName,
@@ -2436,10 +2564,17 @@ export const getWebUiClientScript = (markedSource: string): string => `
2436
2564
  toolName,
2437
2565
  );
2438
2566
  const duration = typeof payload.duration === "number" ? payload.duration : null;
2439
- const detail =
2567
+ var detail =
2440
2568
  activeActivity && typeof activeActivity.detail === "string"
2441
2569
  ? activeActivity.detail.trim()
2442
2570
  : "";
2571
+ const out = payload.output && typeof payload.output === "object" ? payload.output : {};
2572
+ if (!detail && toolName === "web_search" && typeof out.query === "string") {
2573
+ detail = "\\x22" + (out.query.length > 60 ? out.query.slice(0, 57) + "..." : out.query) + "\\x22";
2574
+ }
2575
+ if (!detail && toolName === "web_fetch" && typeof out.url === "string") {
2576
+ detail = out.url;
2577
+ }
2443
2578
  const meta = [];
2444
2579
  if (duration !== null) meta.push(duration + "ms");
2445
2580
  if (detail) meta.push(detail);
@@ -2448,6 +2583,12 @@ export const getWebUiClientScript = (markedSource: string): string => `
2448
2583
  if (toolName === "spawn_subagent" && payload.output && typeof payload.output === "object" && payload.output.subagentId) {
2449
2584
  toolText += " [subagent:" + payload.output.subagentId + "]";
2450
2585
  }
2586
+ if (toolName === "web_fetch" && typeof out.url === "string") {
2587
+ toolText += " [link:" + out.url + "]";
2588
+ }
2589
+ if (toolName === "web_search" && Array.isArray(out.results)) {
2590
+ toolText += " \\u2014 " + out.results.length + " result" + (out.results.length !== 1 ? "s" : "");
2591
+ }
2451
2592
  assistantMessage._currentTools.push(toolText);
2452
2593
  if (!assistantMessage.metadata) assistantMessage.metadata = {};
2453
2594
  if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
@@ -2919,7 +3060,7 @@ export const getWebUiClientScript = (markedSource: string): string => `
2919
3060
  state: "resolved",
2920
3061
  resolvedDecision: decision,
2921
3062
  }));
2922
- api("/api/approvals/" + encodeURIComponent(approvalId), {
3063
+ return api("/api/approvals/" + encodeURIComponent(approvalId), {
2923
3064
  method: "POST",
2924
3065
  body: JSON.stringify({ approved: decision === "approve" }),
2925
3066
  }).catch((error) => {
@@ -2967,16 +3108,54 @@ export const getWebUiClientScript = (markedSource: string): string => `
2967
3108
  if (pending.length === 0) return;
2968
3109
  const wasStreaming = state.isStreaming;
2969
3110
  if (!wasStreaming) setStreaming(true);
2970
- pending.forEach((aid) => submitApproval(aid, decision));
3111
+ // Mark all items as resolved in the UI immediately
3112
+ for (const aid of pending) {
3113
+ state.approvalRequestsInFlight[aid] = true;
3114
+ updatePendingApproval(aid, (request) => ({
3115
+ ...request,
3116
+ state: "resolved",
3117
+ resolvedDecision: decision,
3118
+ }));
3119
+ }
2971
3120
  renderMessages(state.activeMessages, state.isStreaming);
2972
3121
  loadConversations();
2973
- if (!wasStreaming && state.activeConversationId) {
2974
- const cid = state.activeConversationId;
2975
- await streamConversationEvents(cid, { liveOnly: true });
2976
- if (state.activeConversationId === cid) {
2977
- pollUntilRunIdle(cid);
2978
- }
3122
+ const streamCid = !wasStreaming && state.activeConversationId
3123
+ ? state.activeConversationId
3124
+ : null;
3125
+ if (streamCid) {
3126
+ streamConversationEvents(streamCid, { liveOnly: true }).finally(() => {
3127
+ if (state.activeConversationId === streamCid) {
3128
+ pollUntilRunIdle(streamCid);
3129
+ }
3130
+ });
2979
3131
  }
3132
+ // Send API calls sequentially so each store write completes
3133
+ // before the next read (avoids last-writer-wins in serverless).
3134
+ void (async () => {
3135
+ for (const aid of pending) {
3136
+ await api("/api/approvals/" + encodeURIComponent(aid), {
3137
+ method: "POST",
3138
+ body: JSON.stringify({ approved: decision === "approve" }),
3139
+ }).catch((error) => {
3140
+ const isStale = error && error.payload && error.payload.code === "APPROVAL_NOT_FOUND";
3141
+ if (isStale) {
3142
+ updatePendingApproval(aid, () => null);
3143
+ } else {
3144
+ const errMsg = error instanceof Error ? error.message : String(error);
3145
+ updatePendingApproval(aid, (request) => ({
3146
+ ...request,
3147
+ state: "pending",
3148
+ pendingDecision: null,
3149
+ resolvedDecision: null,
3150
+ _error: errMsg,
3151
+ }));
3152
+ }
3153
+ renderMessages(state.activeMessages, state.isStreaming);
3154
+ }).finally(() => {
3155
+ delete state.approvalRequestsInFlight[aid];
3156
+ });
3157
+ }
3158
+ })();
2980
3159
  return;
2981
3160
  }
2982
3161
 
@@ -401,6 +401,41 @@ export const WEB_UI_STYLES = `
401
401
  .conversation-item .delete-btn.confirming:hover {
402
402
  color: var(--error-alt);
403
403
  }
404
+ .cron-section-header {
405
+ display: flex;
406
+ align-items: center;
407
+ gap: 6px;
408
+ padding: 8px 10px 4px;
409
+ cursor: pointer;
410
+ font-size: 11px;
411
+ font-weight: 600;
412
+ color: var(--fg-7);
413
+ text-transform: uppercase;
414
+ letter-spacing: 0.04em;
415
+ user-select: none;
416
+ transition: color 0.15s;
417
+ }
418
+ .cron-section-header:hover { color: var(--fg-5); }
419
+ .cron-section-caret {
420
+ display: inline-flex;
421
+ transition: transform 0.15s;
422
+ }
423
+ .cron-section-caret.open { transform: rotate(90deg); }
424
+ .cron-section-count {
425
+ font-weight: 400;
426
+ color: var(--fg-8);
427
+ font-size: 11px;
428
+ }
429
+ .cron-view-more {
430
+ padding: 6px 10px;
431
+ font-size: 12px;
432
+ color: var(--fg-7);
433
+ cursor: pointer;
434
+ text-align: center;
435
+ transition: color 0.15s;
436
+ user-select: none;
437
+ }
438
+ .cron-view-more:hover { color: var(--fg-3); }
404
439
  .sidebar-footer {
405
440
  margin-top: auto;
406
441
  padding-top: 8px;
@@ -1551,6 +1586,15 @@ export const WEB_UI_STYLES = `
1551
1586
  .subagent-link:hover {
1552
1587
  text-decoration: underline;
1553
1588
  }
1589
+ .tool-link {
1590
+ color: var(--accent);
1591
+ text-decoration: none;
1592
+ font-size: 11px;
1593
+ margin-left: 4px;
1594
+ }
1595
+ .tool-link:hover {
1596
+ text-decoration: underline;
1597
+ }
1554
1598
  .subagent-callback-wrap {
1555
1599
  padding: 0;
1556
1600
  }
@@ -1563,6 +1607,8 @@ export const WEB_UI_STYLES = `
1563
1607
  line-height: 1.45;
1564
1608
  color: var(--fg-tool-code);
1565
1609
  width: 100%;
1610
+ min-width: 0;
1611
+ overflow: hidden;
1566
1612
  }
1567
1613
  .subagent-result-summary {
1568
1614
  list-style: none;
@@ -1607,6 +1653,19 @@ export const WEB_UI_STYLES = `
1607
1653
  display: grid;
1608
1654
  gap: 6px;
1609
1655
  padding: 0 12px 10px;
1656
+ min-width: 0;
1657
+ overflow-x: auto;
1658
+ overflow-wrap: break-word;
1659
+ word-break: break-word;
1660
+ }
1661
+ .subagent-result-body pre {
1662
+ max-width: 100%;
1663
+ overflow-x: auto;
1664
+ }
1665
+ .subagent-result-body table {
1666
+ max-width: 100%;
1667
+ overflow-x: auto;
1668
+ display: block;
1610
1669
  }
1611
1670
 
1612
1671
  /* Todo panel — inside composer-inner, above the input shell */