@poncho-ai/cli 0.27.1 → 0.28.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.
Files changed (53) hide show
  1. package/.turbo/turbo-build.log +5 -5
  2. package/CHANGELOG.md +17 -0
  3. package/dist/chunk-3KBZ6IYJ.js +11664 -0
  4. package/dist/chunk-3M36F3UO.js +11608 -0
  5. package/dist/chunk-5AKBY5GK.js +11686 -0
  6. package/dist/chunk-6MNV2A56.js +11669 -0
  7. package/dist/chunk-BHIS23C2.js +11665 -0
  8. package/dist/chunk-CH2VUABC.js +11682 -0
  9. package/dist/chunk-CVCPGIPG.js +11672 -0
  10. package/dist/chunk-EIEDQU2G.js +11600 -0
  11. package/dist/chunk-G3BJFUMT.js +11668 -0
  12. package/dist/chunk-HECB2QW6.js +11623 -0
  13. package/dist/chunk-I3OMA4G4.js +11655 -0
  14. package/dist/chunk-KYGAYXZ7.js +11669 -0
  15. package/dist/chunk-MRIAH4UE.js +11679 -0
  16. package/dist/chunk-N4KUFNGR.js +11398 -0
  17. package/dist/chunk-NEY3V2BR.js +11656 -0
  18. package/dist/chunk-O4G27VDK.js +11413 -0
  19. package/dist/chunk-P7GNEDMA.js +11682 -0
  20. package/dist/chunk-UK2WWNCG.js +11403 -0
  21. package/dist/chunk-VKMSPLAD.js +11687 -0
  22. package/dist/chunk-W5H5ENIB.js +11682 -0
  23. package/dist/chunk-WS5SZJVW.js +11606 -0
  24. package/dist/chunk-YFEPKZCH.js +11667 -0
  25. package/dist/cli.js +1 -1
  26. package/dist/index.js +1 -1
  27. package/dist/run-interactive-ink-2NXNGTCY.js +2112 -0
  28. package/dist/run-interactive-ink-2P7BJFTE.js +2112 -0
  29. package/dist/run-interactive-ink-3CTIVS4B.js +2112 -0
  30. package/dist/run-interactive-ink-3ZNI2D7E.js +2112 -0
  31. package/dist/run-interactive-ink-5SPYMVJR.js +2112 -0
  32. package/dist/run-interactive-ink-AM5B24RW.js +2112 -0
  33. package/dist/run-interactive-ink-COIXPC7V.js +2112 -0
  34. package/dist/run-interactive-ink-DNFDANDJ.js +2112 -0
  35. package/dist/run-interactive-ink-DXABIOVA.js +2112 -0
  36. package/dist/run-interactive-ink-EIX3MSBW.js +2112 -0
  37. package/dist/run-interactive-ink-GIPPQN6E.js +2112 -0
  38. package/dist/run-interactive-ink-I2DZWKAL.js +2112 -0
  39. package/dist/run-interactive-ink-L4D36J5I.js +2112 -0
  40. package/dist/run-interactive-ink-LCQQLBCU.js +2112 -0
  41. package/dist/run-interactive-ink-MXRMDAN7.js +2112 -0
  42. package/dist/run-interactive-ink-NBGGN5CF.js +2112 -0
  43. package/dist/run-interactive-ink-NWQZHI5N.js +2112 -0
  44. package/dist/run-interactive-ink-QCGPGZLG.js +2112 -0
  45. package/dist/run-interactive-ink-TZRKAZ6X.js +2112 -0
  46. package/dist/run-interactive-ink-VTCRYYJQ.js +2112 -0
  47. package/dist/run-interactive-ink-W447UDRN.js +2112 -0
  48. package/dist/run-interactive-ink-ZKN3EFCE.js +2112 -0
  49. package/package.json +2 -2
  50. package/src/index.ts +8 -0
  51. package/src/web-ui-client.ts +210 -12
  52. package/src/web-ui-styles.ts +110 -0
  53. package/src/web-ui.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/cli",
3
- "version": "0.27.1",
3
+ "version": "0.28.1",
4
4
  "description": "CLI for building and deploying AI agents",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,7 +27,7 @@
27
27
  "react": "^19.2.4",
28
28
  "react-devtools-core": "^6.1.5",
29
29
  "yaml": "^2.8.1",
30
- "@poncho-ai/harness": "0.25.0",
30
+ "@poncho-ai/harness": "0.26.0",
31
31
  "@poncho-ai/messaging": "0.7.0",
32
32
  "@poncho-ai/sdk": "1.5.0"
33
33
  },
package/src/index.ts CHANGED
@@ -3594,6 +3594,14 @@ export const createRequestHandler = async (options?: {
3594
3594
  return;
3595
3595
  }
3596
3596
 
3597
+ const todosMatch = pathname.match(/^\/api\/conversations\/([^/]+)\/todos$/);
3598
+ if (todosMatch && request.method === "GET") {
3599
+ const conversationId = decodeURIComponent(todosMatch[1] ?? "");
3600
+ const todos = await harness.getTodos(conversationId);
3601
+ writeJson(response, 200, { todos });
3602
+ return;
3603
+ }
3604
+
3597
3605
  const conversationPathMatch = pathname.match(/^\/api\/conversations\/([^/]+)$/);
3598
3606
  if (conversationPathMatch) {
3599
3607
  const conversationId = decodeURIComponent(conversationPathMatch[1] ?? "");
@@ -27,6 +27,8 @@ export const getWebUiClientScript = (markedSource: string): string => `
27
27
  subagentsParentId: null,
28
28
  viewingSubagentId: null,
29
29
  parentConversationId: null,
30
+ todos: [],
31
+ todoPanelCollapsed: false,
30
32
  };
31
33
 
32
34
  const agentInitial = document.body.dataset.agentInitial || "A";
@@ -578,7 +580,9 @@ export const getWebUiClientScript = (markedSource: string): string => `
578
580
  state.activeMessages = [];
579
581
  state.contextTokens = 0;
580
582
  state.contextWindow = 0;
583
+ state.todos = [];
581
584
  updateContextRing();
585
+ renderTodoPanel();
582
586
  pushConversationUrl(null);
583
587
  elements.chatTitle.textContent = "";
584
588
  renderMessages([]);
@@ -610,6 +614,136 @@ export const getWebUiClientScript = (markedSource: string): string => `
610
614
  return item;
611
615
  };
612
616
 
617
+ const _todoPriorityOrder = { high: 0, medium: 1, low: 2 };
618
+ const _todoStatusOrder = { in_progress: 0, pending: 1, completed: 2 };
619
+ const _todoCompletedTimers = new Map();
620
+
621
+ const _scheduleCompletedHide = () => {
622
+ state.todos.forEach(function(todo) {
623
+ if (todo.status === "completed" && !_todoCompletedTimers.has(todo.id)) {
624
+ _todoCompletedTimers.set(todo.id, todo.updatedAt || Date.now());
625
+ }
626
+ });
627
+ const activeIds = new Set(state.todos.map(function(t) { return t.id; }));
628
+ for (const id of _todoCompletedTimers.keys()) {
629
+ if (!activeIds.has(id)) _todoCompletedTimers.delete(id);
630
+ }
631
+ };
632
+
633
+ const _getVisibleTodos = () => {
634
+ const now = Date.now();
635
+ const sorted = state.todos.slice().sort(function(a, b) {
636
+ const sp = (_todoStatusOrder[a.status] || 1) - (_todoStatusOrder[b.status] || 1);
637
+ if (sp !== 0) return sp;
638
+ const pp = (_todoPriorityOrder[a.priority] || 1) - (_todoPriorityOrder[b.priority] || 1);
639
+ if (pp !== 0) return pp;
640
+ return (a.createdAt || 0) - (b.createdAt || 0);
641
+ });
642
+ if (!state.todoPanelCollapsed) return sorted;
643
+ return sorted.filter(function(todo) {
644
+ if (todo.status !== "completed") return true;
645
+ const completedAt = _todoCompletedTimers.get(todo.id);
646
+ return !completedAt || (now - completedAt) < 30000;
647
+ });
648
+ };
649
+
650
+ let _todoHideTimer = null;
651
+ const _ensureHideTimer = () => {
652
+ if (_todoHideTimer) return;
653
+ _todoHideTimer = setInterval(function() {
654
+ const hasExpiring = state.todos.some(function(t) {
655
+ if (t.status !== "completed") return false;
656
+ const at = _todoCompletedTimers.get(t.id);
657
+ return at && (Date.now() - at) >= 30000;
658
+ });
659
+ if (hasExpiring && state.todoPanelCollapsed) renderTodoPanel();
660
+ if (!state.todos.length) {
661
+ clearInterval(_todoHideTimer);
662
+ _todoHideTimer = null;
663
+ }
664
+ }, 5000);
665
+ };
666
+
667
+ const _autoCollapseTodos = (live) => {
668
+ if (!live) {
669
+ state.todoPanelCollapsed = true;
670
+ return;
671
+ }
672
+ const hasActive = state.todos.some(function(t) {
673
+ return t.status === "pending" || t.status === "in_progress";
674
+ });
675
+ state.todoPanelCollapsed = !hasActive;
676
+ };
677
+
678
+ const renderTodoPanel = () => {
679
+ let panel = document.getElementById("todo-panel");
680
+ if (!panel) return;
681
+
682
+ _scheduleCompletedHide();
683
+ const visible = _getVisibleTodos();
684
+
685
+ if (!visible.length) {
686
+ panel.classList.add("hidden");
687
+ panel.innerHTML = "";
688
+ return;
689
+ }
690
+
691
+ panel.classList.remove("hidden");
692
+ if (state.todoPanelCollapsed) {
693
+ panel.classList.add("collapsed");
694
+ } else {
695
+ panel.classList.remove("collapsed");
696
+ }
697
+ _ensureHideTimer();
698
+
699
+ const completed = state.todos.filter(t => t.status === "completed").length;
700
+ const total = state.todos.length;
701
+
702
+ const statusIcon = (status) => {
703
+ if (status === "completed") return '<span class="todo-status todo-status-completed">\\u2713</span>';
704
+ if (status === "in_progress") return '<span class="todo-status todo-status-in-progress">\\u25CF</span>';
705
+ return '<span class="todo-status todo-status-pending">\\u25CB</span>';
706
+ };
707
+
708
+ const priorityBadge = (priority) => {
709
+ if (!priority || priority === "medium") return "";
710
+ return '<span class="todo-priority todo-priority-' + priority + '">' + priority + '</span>';
711
+ };
712
+
713
+ const isCollapsed = state.todoPanelCollapsed;
714
+
715
+ let html = '<div class="todo-panel-header" id="todo-panel-header">';
716
+ html += '<div class="todo-panel-title">';
717
+ html += '<span class="todo-panel-label">Todos</span>';
718
+ html += '<span class="todo-panel-progress">' + completed + '/' + total + ' done</span>';
719
+ html += '</div>';
720
+ html += '<span class="todo-panel-toggle' + (isCollapsed ? '' : ' open') + '"><svg viewBox="0 0 12 12" fill="none" width="12" height="12"><path d="M4.5 2.75L8 6L4.5 9.25" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></span>';
721
+ html += '</div>';
722
+
723
+ if (!isCollapsed) {
724
+ html += '<ul class="todo-panel-list">';
725
+ visible.forEach(function(todo) {
726
+ const isDone = todo.status === "completed";
727
+ html += '<li class="todo-item' + (isDone ? ' todo-item-done' : '') + '">';
728
+ html += statusIcon(todo.status);
729
+ html += '<span class="todo-item-content">' + escapeHtml(todo.content) + '</span>';
730
+ html += priorityBadge(todo.priority);
731
+ html += '</li>';
732
+ });
733
+ html += '</ul>';
734
+ }
735
+
736
+ panel.innerHTML = html;
737
+
738
+ const header = document.getElementById("todo-panel-header");
739
+ if (header) {
740
+ header.onclick = function() {
741
+ state.todoPanelCollapsed = !state.todoPanelCollapsed;
742
+ renderTodoPanel();
743
+ };
744
+ }
745
+ };
746
+
613
747
  const renderConversationList = () => {
614
748
  elements.list.innerHTML = "";
615
749
  const pending = state.conversations.filter(c => c.hasPendingApprovals);
@@ -624,21 +758,48 @@ export const getWebUiClientScript = (markedSource: string): string => `
624
758
  elements.list.appendChild(buildConversationItem(c));
625
759
  appendSubagentsIfActive(c.conversationId);
626
760
  }
627
- if (rest.length > 0) {
761
+ }
762
+
763
+ const now = new Date();
764
+ const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
765
+ const sevenDaysAgo = startOfToday - 7 * 86400000;
766
+
767
+ const latest = [];
768
+ const previous7 = [];
769
+ const older = [];
770
+ for (const c of rest) {
771
+ const ts = c.updatedAt || c.createdAt || 0;
772
+ if (ts >= startOfToday) {
773
+ latest.push(c);
774
+ } else if (ts >= sevenDaysAgo) {
775
+ previous7.push(c);
776
+ } else {
777
+ older.push(c);
778
+ }
779
+ }
780
+
781
+ let sectionRendered = pending.length > 0;
782
+ const appendSection = (items, labelText) => {
783
+ if (items.length === 0) return;
784
+ if (sectionRendered) {
628
785
  const divider = document.createElement("div");
629
786
  divider.className = "sidebar-section-divider";
630
787
  elements.list.appendChild(divider);
631
- const recentLabel = document.createElement("div");
632
- recentLabel.className = "sidebar-section-label";
633
- recentLabel.textContent = "Recent";
634
- elements.list.appendChild(recentLabel);
635
788
  }
636
- }
789
+ const sectionLabel = document.createElement("div");
790
+ sectionLabel.className = "sidebar-section-label";
791
+ sectionLabel.textContent = labelText;
792
+ elements.list.appendChild(sectionLabel);
793
+ for (const c of items) {
794
+ elements.list.appendChild(buildConversationItem(c));
795
+ appendSubagentsIfActive(c.conversationId);
796
+ }
797
+ sectionRendered = true;
798
+ };
637
799
 
638
- for (const c of rest) {
639
- elements.list.appendChild(buildConversationItem(c));
640
- appendSubagentsIfActive(c.conversationId);
641
- }
800
+ appendSection(latest, "Latest");
801
+ appendSection(previous7, "Previous 7 days");
802
+ appendSection(older, "Older");
642
803
  };
643
804
 
644
805
  const appendSubagentsIfActive = (conversationId) => {
@@ -1018,12 +1179,22 @@ export const getWebUiClientScript = (markedSource: string): string => `
1018
1179
  loadSubagents(subagentParentId);
1019
1180
  }
1020
1181
 
1182
+ try {
1183
+ const todosPayload = await api("/api/conversations/" + encodeURIComponent(conversationId) + "/todos");
1184
+ state.todos = todosPayload.todos || [];
1185
+ } catch (_e) {
1186
+ state.todos = [];
1187
+ }
1188
+ _autoCollapseTodos();
1189
+ renderTodoPanel();
1190
+
1021
1191
  updateContextRing();
1022
- renderMessages(state.activeMessages, false, { forceScrollBottom: true });
1192
+ var willStream = !!payload.hasActiveRun;
1193
+ renderMessages(state.activeMessages, willStream, { forceScrollBottom: true });
1023
1194
  if (!state.viewingSubagentId) {
1024
1195
  elements.prompt.focus();
1025
1196
  }
1026
- if (payload.hasActiveRun && !state.isStreaming) {
1197
+ if (willStream) {
1027
1198
  setStreaming(true);
1028
1199
  streamConversationEvents(conversationId, { liveOnly: true }).finally(() => {
1029
1200
  if (state.activeConversationId === conversationId) {
@@ -1120,6 +1291,13 @@ export const getWebUiClientScript = (markedSource: string): string => `
1120
1291
  payload.conversation.messages || [],
1121
1292
  allPending,
1122
1293
  );
1294
+ if (typeof payload.conversation.contextTokens === "number") {
1295
+ state.contextTokens = payload.conversation.contextTokens;
1296
+ }
1297
+ if (typeof payload.conversation.contextWindow === "number" && payload.conversation.contextWindow > 0) {
1298
+ state.contextWindow = payload.conversation.contextWindow;
1299
+ }
1300
+ updateContextRing();
1123
1301
  renderMessages(state.activeMessages, payload.hasActiveRun);
1124
1302
  }
1125
1303
  if (payload.hasActiveRun) {
@@ -1335,6 +1513,11 @@ export const getWebUiClientScript = (markedSource: string): string => `
1335
1513
  state.contextTokens += payload.outputTokenEstimate;
1336
1514
  updateContextRing();
1337
1515
  }
1516
+ if (toolName !== "todo_list" && toolName.startsWith("todo_") && payload.output && typeof payload.output === "object" && Array.isArray(payload.output.todos)) {
1517
+ state.todos = payload.output.todos;
1518
+ _autoCollapseTodos(true);
1519
+ renderTodoPanel();
1520
+ }
1338
1521
  renderIfActiveConversation(true);
1339
1522
  }
1340
1523
  if (eventName === "tool:error") {
@@ -1370,6 +1553,10 @@ export const getWebUiClientScript = (markedSource: string): string => `
1370
1553
  if (eventName === "compaction:completed") {
1371
1554
  didCompact = true;
1372
1555
  removeActiveActivityForTool(assistantMessage, "__compaction__");
1556
+ if (typeof payload.tokensAfter === "number") {
1557
+ state.contextTokens = payload.tokensAfter;
1558
+ updateContextRing();
1559
+ }
1373
1560
  renderIfActiveConversation(true);
1374
1561
  }
1375
1562
  if (eventName === "browser:status" && payload.active) {
@@ -2157,6 +2344,11 @@ export const getWebUiClientScript = (markedSource: string): string => `
2157
2344
  state.contextTokens += payload.outputTokenEstimate;
2158
2345
  updateContextRing();
2159
2346
  }
2347
+ if (toolName !== "todo_list" && toolName.startsWith("todo_") && payload.output && typeof payload.output === "object" && Array.isArray(payload.output.todos)) {
2348
+ state.todos = payload.output.todos;
2349
+ _autoCollapseTodos(true);
2350
+ renderTodoPanel();
2351
+ }
2160
2352
  renderIfActiveConversation(true);
2161
2353
  }
2162
2354
  if (eventName === "tool:error") {
@@ -2194,6 +2386,10 @@ export const getWebUiClientScript = (markedSource: string): string => `
2194
2386
  if (eventName === "compaction:completed") {
2195
2387
  didCompact = true;
2196
2388
  removeActiveActivityForTool(assistantMessage, "__compaction__");
2389
+ if (typeof payload.tokensAfter === "number") {
2390
+ state.contextTokens = payload.tokensAfter;
2391
+ updateContextRing();
2392
+ }
2197
2393
  renderIfActiveConversation(true);
2198
2394
  }
2199
2395
  if (eventName === "browser:status" && payload.active) {
@@ -2440,8 +2636,10 @@ export const getWebUiClientScript = (markedSource: string): string => `
2440
2636
  state.parentConversationId = null;
2441
2637
  state.subagents = [];
2442
2638
  state.subagentsParentId = null;
2639
+ state.todos = [];
2443
2640
  updateSubagentUi();
2444
2641
  updateContextRing();
2642
+ renderTodoPanel();
2445
2643
  pushConversationUrl(null);
2446
2644
  elements.chatTitle.textContent = "";
2447
2645
  renderMessages([]);
@@ -983,6 +983,8 @@ export const WEB_UI_STYLES = `
983
983
  }
984
984
  .composer-inner { max-width: 680px; margin: 0 auto; }
985
985
  .composer-shell {
986
+ position: relative;
987
+ z-index: 1;
986
988
  background: var(--bg-alt);
987
989
  border: 1px solid var(--border-3);
988
990
  border-radius: 24px;
@@ -1550,6 +1552,114 @@ export const WEB_UI_STYLES = `
1550
1552
  text-decoration: underline;
1551
1553
  }
1552
1554
 
1555
+ /* Todo panel — inside composer-inner, above the input shell */
1556
+ #todo-panel {
1557
+ max-height: 240px;
1558
+ display: flex;
1559
+ flex-direction: column;
1560
+ overflow: hidden;
1561
+ font-size: 13px;
1562
+ background: var(--surface-2);
1563
+ border-radius: 14px 14px 0 0;
1564
+ padding-bottom: 24px;
1565
+ margin-bottom: -24px;
1566
+ }
1567
+ #todo-panel.hidden { display: none; }
1568
+
1569
+ .todo-panel-header {
1570
+ display: flex;
1571
+ align-items: center;
1572
+ justify-content: space-between;
1573
+ padding: 6px 14px;
1574
+ flex-shrink: 0;
1575
+ cursor: pointer;
1576
+ user-select: none;
1577
+ }
1578
+ .todo-panel-header:hover {
1579
+ background: var(--surface-1);
1580
+ }
1581
+ #todo-panel.collapsed {
1582
+ padding-bottom: 0;
1583
+ }
1584
+ #todo-panel.collapsed .todo-panel-header {
1585
+ padding-bottom: 30px;
1586
+ }
1587
+ .todo-panel-title {
1588
+ display: flex;
1589
+ align-items: center;
1590
+ gap: 8px;
1591
+ }
1592
+ .todo-panel-label {
1593
+ font-weight: 600;
1594
+ color: var(--fg);
1595
+ font-size: 11px;
1596
+ }
1597
+ .todo-panel-progress {
1598
+ font-size: 11px;
1599
+ color: var(--fg-3);
1600
+ }
1601
+ .todo-panel-toggle {
1602
+ color: var(--fg-5);
1603
+ display: flex;
1604
+ align-items: center;
1605
+ transition: transform 0.15s ease;
1606
+ }
1607
+ .todo-panel-toggle.open {
1608
+ transform: rotate(90deg);
1609
+ }
1610
+
1611
+ .todo-panel-list {
1612
+ list-style: none;
1613
+ margin: 0;
1614
+ padding: 0 0 4px;
1615
+ overflow-y: auto;
1616
+ flex: 1;
1617
+ }
1618
+ .todo-item {
1619
+ display: flex;
1620
+ align-items: flex-start;
1621
+ gap: 8px;
1622
+ padding: 4px 14px;
1623
+ color: var(--fg-2);
1624
+ line-height: 1.4;
1625
+ }
1626
+ .todo-item-done {
1627
+ opacity: 0.5;
1628
+ }
1629
+ .todo-item-done .todo-item-content {
1630
+ text-decoration: line-through;
1631
+ }
1632
+ .todo-item-content {
1633
+ flex: 1;
1634
+ word-break: break-word;
1635
+ }
1636
+
1637
+ .todo-status {
1638
+ flex-shrink: 0;
1639
+ width: 16px;
1640
+ text-align: center;
1641
+ line-height: 1.4;
1642
+ }
1643
+ .todo-status-completed { color: #4ade80; }
1644
+ .todo-status-in-progress { color: #facc15; }
1645
+ .todo-status-pending { color: var(--fg-5); }
1646
+
1647
+ .todo-priority {
1648
+ flex-shrink: 0;
1649
+ font-size: 10px;
1650
+ padding: 1px 5px;
1651
+ border-radius: 4px;
1652
+ line-height: 1.4;
1653
+ }
1654
+ .todo-priority-high {
1655
+ color: #f87171;
1656
+ background: rgba(248,113,113,0.12);
1657
+ }
1658
+ .todo-priority-low {
1659
+ color: var(--fg-5);
1660
+ background: var(--surface-1);
1661
+ }
1662
+
1553
1663
  /* Reduced motion */
1554
1664
  @media (prefers-reduced-motion: reduce) {
1555
1665
  *, *::before, *::after {
package/src/web-ui.ts CHANGED
@@ -162,6 +162,7 @@ ${WEB_UI_STYLES}
162
162
  </div>
163
163
  <form id="composer" class="composer">
164
164
  <div class="composer-inner">
165
+ <div id="todo-panel" class="hidden"></div>
165
166
  <div id="attachment-preview" class="attachment-preview" style="display:none"></div>
166
167
  <div class="composer-shell">
167
168
  <button id="attach-btn" class="attach-btn" type="button" title="Attach files">