@poncho-ai/cli 0.14.0 → 0.14.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.
package/src/web-ui.ts CHANGED
@@ -509,6 +509,8 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
509
509
  --tool-done: #6a9955;
510
510
  --tool-error: #f48771;
511
511
 
512
+ --warning: #e8a735;
513
+
512
514
  --approve: #78e7a6;
513
515
  --approve-border: rgba(58,208,122,0.45);
514
516
  --deny: #f59b9b;
@@ -586,6 +588,8 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
586
588
  --tool-done: #16a34a;
587
589
  --tool-error: #dc2626;
588
590
 
591
+ --warning: #ca8a04;
592
+
589
593
  --approve: #16a34a;
590
594
  --approve-border: rgba(22,163,74,0.35);
591
595
  --deny: #dc2626;
@@ -731,6 +735,20 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
731
735
  flex-direction: column;
732
736
  gap: 2px;
733
737
  }
738
+ .sidebar-section-label {
739
+ font-size: 11px;
740
+ font-weight: 600;
741
+ color: var(--fg-7);
742
+ text-transform: uppercase;
743
+ letter-spacing: 0.04em;
744
+ padding: 10px 10px 4px;
745
+ }
746
+ .sidebar-section-label:first-child { padding-top: 4px; }
747
+ .sidebar-section-divider {
748
+ height: 1px;
749
+ background: var(--border);
750
+ margin: 6px 10px;
751
+ }
734
752
  .conversation-item {
735
753
  height: 36px;
736
754
  min-height: 36px;
@@ -748,6 +766,16 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
748
766
  position: relative;
749
767
  transition: color 0.15s;
750
768
  }
769
+ .conversation-item .approval-dot {
770
+ display: inline-block;
771
+ width: 6px;
772
+ height: 6px;
773
+ border-radius: 50%;
774
+ background: var(--warning, #e8a735);
775
+ margin-right: 6px;
776
+ flex-shrink: 0;
777
+ vertical-align: middle;
778
+ }
751
779
  .conversation-item:hover { color: var(--fg-3); }
752
780
  .conversation-item.active {
753
781
  color: var(--fg);
@@ -971,7 +999,7 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
971
999
  .tool-error {
972
1000
  color: var(--tool-error);
973
1001
  }
974
- .assistant-content table {
1002
+ .assistant-content table:not(.approval-request-table) {
975
1003
  border-collapse: collapse;
976
1004
  width: 100%;
977
1005
  margin: 14px 0;
@@ -984,7 +1012,7 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
984
1012
  overflow-x: auto;
985
1013
  white-space: nowrap;
986
1014
  }
987
- .assistant-content th {
1015
+ .assistant-content table:not(.approval-request-table) th {
988
1016
  background: var(--surface-4);
989
1017
  padding: 10px 12px;
990
1018
  text-align: left;
@@ -993,16 +1021,16 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
993
1021
  color: var(--fg-strong);
994
1022
  min-width: 100px;
995
1023
  }
996
- .assistant-content td {
1024
+ .assistant-content table:not(.approval-request-table) td {
997
1025
  padding: 10px 12px;
998
1026
  border-bottom: 1px solid var(--border-1);
999
1027
  width: 100%;
1000
1028
  min-width: 100px;
1001
1029
  }
1002
- .assistant-content tr:last-child td {
1030
+ .assistant-content table:not(.approval-request-table) tr:last-child td {
1003
1031
  border-bottom: none;
1004
1032
  }
1005
- .assistant-content tbody tr:hover {
1033
+ .assistant-content table:not(.approval-request-table) tbody tr:hover {
1006
1034
  background: var(--surface-1);
1007
1035
  }
1008
1036
  .assistant-content hr {
@@ -1020,6 +1048,10 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
1020
1048
  line-height: 1.45;
1021
1049
  color: var(--fg-tool-code);
1022
1050
  width: 300px;
1051
+ transition: width 0.2s ease;
1052
+ }
1053
+ .tool-activity.has-approvals {
1054
+ width: 100%;
1023
1055
  }
1024
1056
  .assistant-content > .tool-activity:first-child {
1025
1057
  margin-top: 0;
@@ -1079,40 +1111,60 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
1079
1111
  border-top: 1px solid var(--border-2);
1080
1112
  padding: 10px 12px 12px;
1081
1113
  display: grid;
1082
- gap: 8px;
1083
- background: var(--inset-1);
1114
+ gap: 10px;
1084
1115
  }
1085
1116
  .approval-requests-label {
1086
- font-size: 11px;
1117
+ font-size: 12px;
1087
1118
  text-transform: uppercase;
1088
1119
  letter-spacing: 0.06em;
1089
1120
  color: var(--fg-approval-label);
1090
1121
  font-weight: 600;
1091
1122
  }
1123
+ .approval-requests-label code {
1124
+ font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
1125
+ text-transform: none;
1126
+ letter-spacing: 0;
1127
+ color: var(--fg-strong);
1128
+ }
1092
1129
  .approval-request-item {
1093
- border: 1px solid var(--border-3);
1094
- background: var(--surface-2);
1095
- border-radius: 8px;
1096
- padding: 8px;
1097
1130
  display: grid;
1098
- gap: 6px;
1131
+ gap: 8px;
1099
1132
  }
1100
- .approval-request-tool {
1101
- font-size: 12px;
1102
- color: var(--fg-strong);
1133
+ .approval-request-table {
1134
+ width: 100%;
1135
+ border-collapse: collapse;
1136
+ border: none;
1137
+ font-size: 14px;
1138
+ line-height: 1.5;
1139
+ }
1140
+ .approval-request-table tr,
1141
+ .approval-request-table td {
1142
+ border: none;
1143
+ background: none;
1144
+ }
1145
+ .approval-request-table td {
1146
+ padding: 4px 0;
1147
+ vertical-align: top;
1148
+ }
1149
+ .approval-request-table .ak {
1103
1150
  font-weight: 600;
1104
- overflow-wrap: anywhere;
1151
+ color: var(--fg-approval-label);
1152
+ white-space: nowrap;
1153
+ width: 1%;
1154
+ padding-right: 20px;
1105
1155
  }
1106
- .approval-request-input {
1107
- font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
1108
- font-size: 11px;
1109
- color: var(--fg-approval-input);
1110
- background: var(--inset-2);
1111
- border-radius: 6px;
1112
- padding: 6px;
1156
+ .approval-request-table .av,
1157
+ .approval-request-table .av-complex {
1158
+ color: var(--fg);
1113
1159
  overflow-wrap: anywhere;
1114
- max-height: 80px;
1160
+ white-space: pre-wrap;
1161
+ max-height: 200px;
1115
1162
  overflow-y: auto;
1163
+ display: block;
1164
+ }
1165
+ .approval-request-table .av-complex {
1166
+ font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
1167
+ font-size: 12px;
1116
1168
  }
1117
1169
  .approval-request-actions {
1118
1170
  display: flex;
@@ -1153,6 +1205,7 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
1153
1205
  line-height: 1.5;
1154
1206
  overflow-wrap: break-word;
1155
1207
  word-break: break-word;
1208
+ white-space: pre-wrap;
1156
1209
  }
1157
1210
  .empty-state {
1158
1211
  display: flex;
@@ -1826,18 +1879,20 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
1826
1879
  .map((req) => {
1827
1880
  const approvalId = typeof req.approvalId === "string" ? req.approvalId : "";
1828
1881
  const tool = typeof req.tool === "string" ? req.tool : "tool";
1829
- const inputPreview = typeof req.inputPreview === "string" ? req.inputPreview : "{}";
1882
+ const input = req.input != null ? req.input : {};
1830
1883
  const submitting = req.state === "submitting";
1831
1884
  const approveLabel = submitting && req.pendingDecision === "approve" ? "Approving..." : "Approve";
1832
1885
  const denyLabel = submitting && req.pendingDecision === "deny" ? "Denying..." : "Deny";
1886
+ const errorHtml = req._error
1887
+ ? '<div style="color: var(--deny); font-size: 11px; margin-top: 4px;">Submit failed: ' + escapeHtml(req._error) + "</div>"
1888
+ : "";
1833
1889
  return (
1834
1890
  '<div class="approval-request-item">' +
1835
- '<div class="approval-request-tool">' +
1891
+ '<div class="approval-requests-label">Approval required: <code>' +
1836
1892
  escapeHtml(tool) +
1837
- "</div>" +
1838
- '<div class="approval-request-input">' +
1839
- escapeHtml(inputPreview) +
1840
- "</div>" +
1893
+ "</code></div>" +
1894
+ renderInputTable(input) +
1895
+ errorHtml +
1841
1896
  '<div class="approval-request-actions">' +
1842
1897
  '<button class="approval-action-btn approve" data-approval-id="' +
1843
1898
  escapeHtml(approvalId) +
@@ -1860,7 +1915,6 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
1860
1915
  .join("");
1861
1916
  return (
1862
1917
  '<div class="approval-requests">' +
1863
- '<div class="approval-requests-label">Approval required</div>' +
1864
1918
  rows +
1865
1919
  "</div>"
1866
1920
  );
@@ -1890,22 +1944,46 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
1890
1944
  "</details>"
1891
1945
  )
1892
1946
  : "";
1947
+ const cls = "tool-activity" + (hasApprovals ? " has-approvals" : "");
1893
1948
  return (
1894
- '<div class="tool-activity">' +
1949
+ '<div class="' + cls + '">' +
1895
1950
  disclosure +
1896
1951
  renderApprovalRequests(approvalRequests) +
1897
1952
  "</div>"
1898
1953
  );
1899
1954
  };
1900
1955
 
1901
- const safeJsonPreview = (value) => {
1902
- try {
1903
- return JSON.stringify(value, (_, nestedValue) =>
1904
- typeof nestedValue === "bigint" ? String(nestedValue) : nestedValue,
1905
- );
1906
- } catch {
1907
- return "[unserializable input]";
1956
+ const renderInputTable = (input) => {
1957
+ if (!input || typeof input !== "object") {
1958
+ return '<div class="av-complex">' + escapeHtml(String(input ?? "{}")) + "</div>";
1908
1959
  }
1960
+ const keys = Object.keys(input);
1961
+ if (keys.length === 0) {
1962
+ return '<div class="av-complex">{}</div>';
1963
+ }
1964
+ const formatValue = (val) => {
1965
+ if (val === null || val === undefined) return escapeHtml("null");
1966
+ if (typeof val === "boolean" || typeof val === "number") return escapeHtml(String(val));
1967
+ if (typeof val === "string") return escapeHtml(val);
1968
+ try {
1969
+ const replacer = (_, v) => typeof v === "bigint" ? String(v) : v;
1970
+ return escapeHtml(JSON.stringify(val, replacer, 2));
1971
+ } catch {
1972
+ return escapeHtml("[unserializable]");
1973
+ }
1974
+ };
1975
+ const rows = keys.map((key) => {
1976
+ const val = input[key];
1977
+ const isComplex = val !== null && typeof val === "object";
1978
+ const cls = isComplex ? "av-complex" : "av";
1979
+ return (
1980
+ "<tr>" +
1981
+ '<td class="ak">' + escapeHtml(key) + "</td>" +
1982
+ '<td><div class="' + cls + '">' + formatValue(val) + "</div></td>" +
1983
+ "</tr>"
1984
+ );
1985
+ }).join("");
1986
+ return '<table class="approval-request-table">' + rows + "</table>";
1909
1987
  };
1910
1988
 
1911
1989
  const updatePendingApproval = (approvalId, updater) => {
@@ -1944,12 +2022,10 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
1944
2022
  return null;
1945
2023
  }
1946
2024
  const toolName = item && typeof item.tool === "string" ? item.tool : "tool";
1947
- const preview = safeJsonPreview(item?.input ?? {});
1948
- const inputPreview = preview.length > 600 ? preview.slice(0, 600) + "..." : preview;
1949
2025
  return {
1950
2026
  approvalId,
1951
2027
  tool: toolName,
1952
- inputPreview,
2028
+ input: item?.input ?? {},
1953
2029
  state: "pending",
1954
2030
  };
1955
2031
  })
@@ -2027,53 +2103,87 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
2027
2103
  elements.shell.classList.toggle("sidebar-open", open);
2028
2104
  };
2029
2105
 
2030
- const renderConversationList = () => {
2031
- elements.list.innerHTML = "";
2032
- for (const c of state.conversations) {
2033
- const item = document.createElement("div");
2034
- item.className = "conversation-item" + (c.conversationId === state.activeConversationId ? " active" : "");
2035
- item.textContent = c.title;
2036
-
2037
- const isConfirming = state.confirmDeleteId === c.conversationId;
2038
- const deleteBtn = document.createElement("button");
2039
- deleteBtn.className = "delete-btn" + (isConfirming ? " confirming" : "");
2040
- deleteBtn.textContent = isConfirming ? "sure?" : "\\u00d7";
2041
- deleteBtn.onclick = async (e) => {
2042
- e.stopPropagation();
2043
- if (!isConfirming) {
2044
- state.confirmDeleteId = c.conversationId;
2045
- renderConversationList();
2046
- return;
2047
- }
2048
- await api("/api/conversations/" + c.conversationId, { method: "DELETE" });
2049
- if (state.activeConversationId === c.conversationId) {
2050
- state.activeConversationId = null;
2051
- state.activeMessages = [];
2052
- state.contextTokens = 0;
2053
- state.contextWindow = 0;
2054
- updateContextRing();
2055
- pushConversationUrl(null);
2056
- elements.chatTitle.textContent = "";
2057
- renderMessages([]);
2058
- }
2059
- state.confirmDeleteId = null;
2060
- await loadConversations();
2061
- };
2062
- item.appendChild(deleteBtn);
2106
+ const buildConversationItem = (c) => {
2107
+ const item = document.createElement("div");
2108
+ item.className = "conversation-item" + (c.conversationId === state.activeConversationId ? " active" : "");
2063
2109
 
2064
- item.onclick = async () => {
2065
- // Clear any delete confirmation, but still navigate
2066
- if (state.confirmDeleteId) {
2067
- state.confirmDeleteId = null;
2068
- }
2069
- state.activeConversationId = c.conversationId;
2070
- pushConversationUrl(c.conversationId);
2110
+ if (c.hasPendingApprovals) {
2111
+ const dot = document.createElement("span");
2112
+ dot.className = "approval-dot";
2113
+ item.appendChild(dot);
2114
+ }
2115
+
2116
+ const titleSpan = document.createElement("span");
2117
+ titleSpan.textContent = c.title;
2118
+ item.appendChild(titleSpan);
2119
+
2120
+ const isConfirming = state.confirmDeleteId === c.conversationId;
2121
+ const deleteBtn = document.createElement("button");
2122
+ deleteBtn.className = "delete-btn" + (isConfirming ? " confirming" : "");
2123
+ deleteBtn.textContent = isConfirming ? "sure?" : "\\u00d7";
2124
+ deleteBtn.onclick = async (e) => {
2125
+ e.stopPropagation();
2126
+ if (!isConfirming) {
2127
+ state.confirmDeleteId = c.conversationId;
2071
2128
  renderConversationList();
2072
- await loadConversation(c.conversationId);
2073
- if (isMobile()) setSidebarOpen(false);
2074
- };
2129
+ return;
2130
+ }
2131
+ await api("/api/conversations/" + c.conversationId, { method: "DELETE" });
2132
+ if (state.activeConversationId === c.conversationId) {
2133
+ state.activeConversationId = null;
2134
+ state.activeMessages = [];
2135
+ state.contextTokens = 0;
2136
+ state.contextWindow = 0;
2137
+ updateContextRing();
2138
+ pushConversationUrl(null);
2139
+ elements.chatTitle.textContent = "";
2140
+ renderMessages([]);
2141
+ }
2142
+ state.confirmDeleteId = null;
2143
+ await loadConversations();
2144
+ };
2145
+ item.appendChild(deleteBtn);
2075
2146
 
2076
- elements.list.appendChild(item);
2147
+ item.onclick = async () => {
2148
+ if (state.confirmDeleteId) {
2149
+ state.confirmDeleteId = null;
2150
+ }
2151
+ state.activeConversationId = c.conversationId;
2152
+ pushConversationUrl(c.conversationId);
2153
+ renderConversationList();
2154
+ await loadConversation(c.conversationId);
2155
+ if (isMobile()) setSidebarOpen(false);
2156
+ };
2157
+
2158
+ return item;
2159
+ };
2160
+
2161
+ const renderConversationList = () => {
2162
+ elements.list.innerHTML = "";
2163
+ const pending = state.conversations.filter(c => c.hasPendingApprovals);
2164
+ const rest = state.conversations.filter(c => !c.hasPendingApprovals);
2165
+
2166
+ if (pending.length > 0) {
2167
+ const label = document.createElement("div");
2168
+ label.className = "sidebar-section-label";
2169
+ label.textContent = "Awaiting approval";
2170
+ elements.list.appendChild(label);
2171
+ for (const c of pending) {
2172
+ elements.list.appendChild(buildConversationItem(c));
2173
+ }
2174
+ if (rest.length > 0) {
2175
+ const divider = document.createElement("div");
2176
+ divider.className = "sidebar-section-divider";
2177
+ elements.list.appendChild(divider);
2178
+ const recentLabel = document.createElement("div");
2179
+ recentLabel.className = "sidebar-section-label";
2180
+ recentLabel.textContent = "Recent";
2181
+ elements.list.appendChild(recentLabel);
2182
+ }
2183
+ }
2184
+
2185
+ for (const c of rest) {
2186
+ elements.list.appendChild(buildConversationItem(c));
2077
2187
  }
2078
2188
  };
2079
2189
 
@@ -2502,8 +2612,6 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
2502
2612
  const approvalId =
2503
2613
  typeof payload.approvalId === "string" ? payload.approvalId : "";
2504
2614
  if (approvalId) {
2505
- const preview = safeJsonPreview(payload.input ?? {});
2506
- const inputPreview = preview.length > 600 ? preview.slice(0, 600) + "..." : preview;
2507
2615
  if (!Array.isArray(assistantMessage._pendingApprovals)) {
2508
2616
  assistantMessage._pendingApprovals = [];
2509
2617
  }
@@ -2514,7 +2622,7 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
2514
2622
  assistantMessage._pendingApprovals.push({
2515
2623
  approvalId,
2516
2624
  tool: toolName,
2517
- inputPreview,
2625
+ input: payload.input ?? {},
2518
2626
  state: "pending",
2519
2627
  });
2520
2628
  }
@@ -3162,8 +3270,6 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
3162
3270
  const approvalId =
3163
3271
  typeof payload.approvalId === "string" ? payload.approvalId : "";
3164
3272
  if (approvalId) {
3165
- const preview = safeJsonPreview(payload.input ?? {});
3166
- const inputPreview = preview.length > 600 ? preview.slice(0, 600) + "..." : preview;
3167
3273
  if (!Array.isArray(assistantMessage._pendingApprovals)) {
3168
3274
  assistantMessage._pendingApprovals = [];
3169
3275
  }
@@ -3174,7 +3280,7 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
3174
3280
  assistantMessage._pendingApprovals.push({
3175
3281
  approvalId,
3176
3282
  tool: toolName,
3177
- inputPreview,
3283
+ input: payload.input ?? {},
3178
3284
  state: "pending",
3179
3285
  });
3180
3286
  }
@@ -3523,17 +3629,23 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
3523
3629
  });
3524
3630
  updatePendingApproval(approvalId, () => null);
3525
3631
  renderMessages(state.activeMessages, state.isStreaming);
3632
+ loadConversations();
3526
3633
  if (!wasStreaming && state.activeConversationId) {
3527
- await streamConversationEvents(state.activeConversationId);
3634
+ await streamConversationEvents(state.activeConversationId, { liveOnly: true });
3528
3635
  }
3529
3636
  } catch (error) {
3530
- const errMsg = error instanceof Error ? error.message : String(error);
3531
- updatePendingApproval(approvalId, (request) => ({
3532
- ...request,
3533
- state: "pending",
3534
- pendingDecision: null,
3535
- inputPreview: String(request.inputPreview || "") + " (submit failed: " + errMsg + ")",
3536
- }));
3637
+ const isStale = error && error.payload && error.payload.code === "APPROVAL_NOT_FOUND";
3638
+ if (isStale) {
3639
+ updatePendingApproval(approvalId, () => null);
3640
+ } else {
3641
+ const errMsg = error instanceof Error ? error.message : String(error);
3642
+ updatePendingApproval(approvalId, (request) => ({
3643
+ ...request,
3644
+ state: "pending",
3645
+ pendingDecision: null,
3646
+ _error: errMsg,
3647
+ }));
3648
+ }
3537
3649
  renderMessages(state.activeMessages, state.isStreaming);
3538
3650
  } finally {
3539
3651
  if (!wasStreaming) {