@poncho-ai/cli 0.30.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.
@@ -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";
@@ -773,11 +775,86 @@ export const getWebUiClientScript = (markedSource: string): string => `
773
775
  }
774
776
  };
775
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
+
776
849
  const renderConversationList = () => {
777
850
  elements.list.innerHTML = "";
778
851
  const pending = state.conversations.filter(c => c.hasPendingApprovals);
779
852
  const rest = state.conversations.filter(c => !c.hasPendingApprovals);
780
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
+
781
858
  if (pending.length > 0) {
782
859
  const label = document.createElement("div");
783
860
  label.className = "sidebar-section-label";
@@ -796,7 +873,7 @@ export const getWebUiClientScript = (markedSource: string): string => `
796
873
  const latest = [];
797
874
  const previous7 = [];
798
875
  const older = [];
799
- for (const c of rest) {
876
+ for (const c of nonCron) {
800
877
  const ts = c.updatedAt || c.createdAt || 0;
801
878
  if (ts >= startOfToday) {
802
879
  latest.push(c);
@@ -808,6 +885,12 @@ export const getWebUiClientScript = (markedSource: string): string => `
808
885
  }
809
886
 
810
887
  let sectionRendered = pending.length > 0;
888
+
889
+ if (cronConvs.length > 0) {
890
+ appendCronSection(cronConvs, sectionRendered);
891
+ sectionRendered = true;
892
+ }
893
+
811
894
  const appendSection = (items, labelText) => {
812
895
  if (items.length === 0) return;
813
896
  if (sectionRendered) {
@@ -2977,7 +3060,7 @@ export const getWebUiClientScript = (markedSource: string): string => `
2977
3060
  state: "resolved",
2978
3061
  resolvedDecision: decision,
2979
3062
  }));
2980
- api("/api/approvals/" + encodeURIComponent(approvalId), {
3063
+ return api("/api/approvals/" + encodeURIComponent(approvalId), {
2981
3064
  method: "POST",
2982
3065
  body: JSON.stringify({ approved: decision === "approve" }),
2983
3066
  }).catch((error) => {
@@ -3025,16 +3108,54 @@ export const getWebUiClientScript = (markedSource: string): string => `
3025
3108
  if (pending.length === 0) return;
3026
3109
  const wasStreaming = state.isStreaming;
3027
3110
  if (!wasStreaming) setStreaming(true);
3028
- 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
+ }
3029
3120
  renderMessages(state.activeMessages, state.isStreaming);
3030
3121
  loadConversations();
3031
- if (!wasStreaming && state.activeConversationId) {
3032
- const cid = state.activeConversationId;
3033
- await streamConversationEvents(cid, { liveOnly: true });
3034
- if (state.activeConversationId === cid) {
3035
- pollUntilRunIdle(cid);
3036
- }
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
+ });
3037
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
+ })();
3038
3159
  return;
3039
3160
  }
3040
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;
@@ -1572,6 +1607,8 @@ export const WEB_UI_STYLES = `
1572
1607
  line-height: 1.45;
1573
1608
  color: var(--fg-tool-code);
1574
1609
  width: 100%;
1610
+ min-width: 0;
1611
+ overflow: hidden;
1575
1612
  }
1576
1613
  .subagent-result-summary {
1577
1614
  list-style: none;
@@ -1616,6 +1653,19 @@ export const WEB_UI_STYLES = `
1616
1653
  display: grid;
1617
1654
  gap: 6px;
1618
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;
1619
1669
  }
1620
1670
 
1621
1671
  /* Todo panel — inside composer-inner, above the input shell */