@poncho-ai/cli 0.27.0 → 0.28.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @poncho-ai/cli@0.27.0 build /home/runner/work/poncho-ai/poncho-ai/packages/cli
2
+ > @poncho-ai/cli@0.28.0 build /home/runner/work/poncho-ai/poncho-ai/packages/cli
3
3
  > tsup src/index.ts src/cli.ts --format esm --dts
4
4
 
5
5
  CLI Building entry: src/cli.ts, src/index.ts
@@ -9,10 +9,10 @@
9
9
  ESM Build start
10
10
  ESM dist/cli.js 94.00 B
11
11
  ESM dist/index.js 857.00 B
12
- ESM dist/run-interactive-ink-OUX42GU4.js 56.74 KB
13
- ESM dist/chunk-LPT2RFZ5.js 422.53 KB
14
- ESM ⚡️ Build success in 63ms
12
+ ESM dist/run-interactive-ink-5SPYMVJR.js 56.74 KB
13
+ ESM dist/chunk-5AKBY5GK.js 434.40 KB
14
+ ESM ⚡️ Build success in 56ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 4319ms
16
+ DTS ⚡️ Build success in 3589ms
17
17
  DTS dist/cli.d.ts 20.00 B
18
18
  DTS dist/index.d.ts 3.70 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @poncho-ai/cli
2
2
 
3
+ ## 0.28.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#40](https://github.com/cesr/poncho-ai/pull/40) [`95ae86b`](https://github.com/cesr/poncho-ai/commit/95ae86b4ea0d913357ccca9a43a227c83e46b9c4) Thanks [@cesr](https://github.com/cesr)! - Add built-in todo tools (todo_list, todo_add, todo_update, todo_remove) with per-conversation storage and a live todo panel in the web UI
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [[`95ae86b`](https://github.com/cesr/poncho-ai/commit/95ae86b4ea0d913357ccca9a43a227c83e46b9c4)]:
12
+ - @poncho-ai/harness@0.26.0
13
+
14
+ ## 0.27.1
15
+
16
+ ### Patch Changes
17
+
18
+ - [`5a103ca`](https://github.com/cesr/poncho-ai/commit/5a103ca62238cceaa4f4b31769a96637330d6b84) Thanks [@cesr](https://github.com/cesr)! - Split `memory_main_update` into `memory_main_write` (full overwrite) and `memory_main_edit` (targeted string replacement). Hot-reload AGENT.md and skills in dev mode without restarting the server. Merge agent + skill MCP tool patterns additively. Fix MissingToolResultsError when resuming from nested approval checkpoints.
19
+
20
+ - Updated dependencies [[`5a103ca`](https://github.com/cesr/poncho-ai/commit/5a103ca62238cceaa4f4b31769a96637330d6b84)]:
21
+ - @poncho-ai/harness@0.25.0
22
+
3
23
  ## 0.27.0
4
24
 
5
25
  ### Minor Changes
@@ -1024,6 +1024,8 @@ var WEB_UI_STYLES = `
1024
1024
  }
1025
1025
  .composer-inner { max-width: 680px; margin: 0 auto; }
1026
1026
  .composer-shell {
1027
+ position: relative;
1028
+ z-index: 1;
1027
1029
  background: var(--bg-alt);
1028
1030
  border: 1px solid var(--border-3);
1029
1031
  border-radius: 24px;
@@ -1591,6 +1593,114 @@ var WEB_UI_STYLES = `
1591
1593
  text-decoration: underline;
1592
1594
  }
1593
1595
 
1596
+ /* Todo panel \u2014 inside composer-inner, above the input shell */
1597
+ #todo-panel {
1598
+ max-height: 240px;
1599
+ display: flex;
1600
+ flex-direction: column;
1601
+ overflow: hidden;
1602
+ font-size: 13px;
1603
+ background: var(--surface-2);
1604
+ border-radius: 14px 14px 0 0;
1605
+ padding-bottom: 24px;
1606
+ margin-bottom: -24px;
1607
+ }
1608
+ #todo-panel.hidden { display: none; }
1609
+
1610
+ .todo-panel-header {
1611
+ display: flex;
1612
+ align-items: center;
1613
+ justify-content: space-between;
1614
+ padding: 6px 14px;
1615
+ flex-shrink: 0;
1616
+ cursor: pointer;
1617
+ user-select: none;
1618
+ }
1619
+ .todo-panel-header:hover {
1620
+ background: var(--surface-1);
1621
+ }
1622
+ #todo-panel.collapsed {
1623
+ padding-bottom: 0;
1624
+ }
1625
+ #todo-panel.collapsed .todo-panel-header {
1626
+ padding-bottom: 30px;
1627
+ }
1628
+ .todo-panel-title {
1629
+ display: flex;
1630
+ align-items: center;
1631
+ gap: 8px;
1632
+ }
1633
+ .todo-panel-label {
1634
+ font-weight: 600;
1635
+ color: var(--fg);
1636
+ font-size: 11px;
1637
+ }
1638
+ .todo-panel-progress {
1639
+ font-size: 11px;
1640
+ color: var(--fg-3);
1641
+ }
1642
+ .todo-panel-toggle {
1643
+ color: var(--fg-5);
1644
+ display: flex;
1645
+ align-items: center;
1646
+ transition: transform 0.15s ease;
1647
+ }
1648
+ .todo-panel-toggle.open {
1649
+ transform: rotate(90deg);
1650
+ }
1651
+
1652
+ .todo-panel-list {
1653
+ list-style: none;
1654
+ margin: 0;
1655
+ padding: 0 0 4px;
1656
+ overflow-y: auto;
1657
+ flex: 1;
1658
+ }
1659
+ .todo-item {
1660
+ display: flex;
1661
+ align-items: flex-start;
1662
+ gap: 8px;
1663
+ padding: 4px 14px;
1664
+ color: var(--fg-2);
1665
+ line-height: 1.4;
1666
+ }
1667
+ .todo-item-done {
1668
+ opacity: 0.5;
1669
+ }
1670
+ .todo-item-done .todo-item-content {
1671
+ text-decoration: line-through;
1672
+ }
1673
+ .todo-item-content {
1674
+ flex: 1;
1675
+ word-break: break-word;
1676
+ }
1677
+
1678
+ .todo-status {
1679
+ flex-shrink: 0;
1680
+ width: 16px;
1681
+ text-align: center;
1682
+ line-height: 1.4;
1683
+ }
1684
+ .todo-status-completed { color: #4ade80; }
1685
+ .todo-status-in-progress { color: #facc15; }
1686
+ .todo-status-pending { color: var(--fg-5); }
1687
+
1688
+ .todo-priority {
1689
+ flex-shrink: 0;
1690
+ font-size: 10px;
1691
+ padding: 1px 5px;
1692
+ border-radius: 4px;
1693
+ line-height: 1.4;
1694
+ }
1695
+ .todo-priority-high {
1696
+ color: #f87171;
1697
+ background: rgba(248,113,113,0.12);
1698
+ }
1699
+ .todo-priority-low {
1700
+ color: var(--fg-5);
1701
+ background: var(--surface-1);
1702
+ }
1703
+
1594
1704
  /* Reduced motion */
1595
1705
  @media (prefers-reduced-motion: reduce) {
1596
1706
  *, *::before, *::after {
@@ -1630,6 +1740,8 @@ var getWebUiClientScript = (markedSource2) => `
1630
1740
  subagentsParentId: null,
1631
1741
  viewingSubagentId: null,
1632
1742
  parentConversationId: null,
1743
+ todos: [],
1744
+ todoPanelCollapsed: false,
1633
1745
  };
1634
1746
 
1635
1747
  const agentInitial = document.body.dataset.agentInitial || "A";
@@ -2181,7 +2293,9 @@ var getWebUiClientScript = (markedSource2) => `
2181
2293
  state.activeMessages = [];
2182
2294
  state.contextTokens = 0;
2183
2295
  state.contextWindow = 0;
2296
+ state.todos = [];
2184
2297
  updateContextRing();
2298
+ renderTodoPanel();
2185
2299
  pushConversationUrl(null);
2186
2300
  elements.chatTitle.textContent = "";
2187
2301
  renderMessages([]);
@@ -2213,6 +2327,136 @@ var getWebUiClientScript = (markedSource2) => `
2213
2327
  return item;
2214
2328
  };
2215
2329
 
2330
+ const _todoPriorityOrder = { high: 0, medium: 1, low: 2 };
2331
+ const _todoStatusOrder = { in_progress: 0, pending: 1, completed: 2 };
2332
+ const _todoCompletedTimers = new Map();
2333
+
2334
+ const _scheduleCompletedHide = () => {
2335
+ state.todos.forEach(function(todo) {
2336
+ if (todo.status === "completed" && !_todoCompletedTimers.has(todo.id)) {
2337
+ _todoCompletedTimers.set(todo.id, todo.updatedAt || Date.now());
2338
+ }
2339
+ });
2340
+ const activeIds = new Set(state.todos.map(function(t) { return t.id; }));
2341
+ for (const id of _todoCompletedTimers.keys()) {
2342
+ if (!activeIds.has(id)) _todoCompletedTimers.delete(id);
2343
+ }
2344
+ };
2345
+
2346
+ const _getVisibleTodos = () => {
2347
+ const now = Date.now();
2348
+ const sorted = state.todos.slice().sort(function(a, b) {
2349
+ const sp = (_todoStatusOrder[a.status] || 1) - (_todoStatusOrder[b.status] || 1);
2350
+ if (sp !== 0) return sp;
2351
+ const pp = (_todoPriorityOrder[a.priority] || 1) - (_todoPriorityOrder[b.priority] || 1);
2352
+ if (pp !== 0) return pp;
2353
+ return (a.createdAt || 0) - (b.createdAt || 0);
2354
+ });
2355
+ if (!state.todoPanelCollapsed) return sorted;
2356
+ return sorted.filter(function(todo) {
2357
+ if (todo.status !== "completed") return true;
2358
+ const completedAt = _todoCompletedTimers.get(todo.id);
2359
+ return !completedAt || (now - completedAt) < 30000;
2360
+ });
2361
+ };
2362
+
2363
+ let _todoHideTimer = null;
2364
+ const _ensureHideTimer = () => {
2365
+ if (_todoHideTimer) return;
2366
+ _todoHideTimer = setInterval(function() {
2367
+ const hasExpiring = state.todos.some(function(t) {
2368
+ if (t.status !== "completed") return false;
2369
+ const at = _todoCompletedTimers.get(t.id);
2370
+ return at && (Date.now() - at) >= 30000;
2371
+ });
2372
+ if (hasExpiring && state.todoPanelCollapsed) renderTodoPanel();
2373
+ if (!state.todos.length) {
2374
+ clearInterval(_todoHideTimer);
2375
+ _todoHideTimer = null;
2376
+ }
2377
+ }, 5000);
2378
+ };
2379
+
2380
+ const _autoCollapseTodos = (live) => {
2381
+ if (!live) {
2382
+ state.todoPanelCollapsed = true;
2383
+ return;
2384
+ }
2385
+ const hasActive = state.todos.some(function(t) {
2386
+ return t.status === "pending" || t.status === "in_progress";
2387
+ });
2388
+ state.todoPanelCollapsed = !hasActive;
2389
+ };
2390
+
2391
+ const renderTodoPanel = () => {
2392
+ let panel = document.getElementById("todo-panel");
2393
+ if (!panel) return;
2394
+
2395
+ _scheduleCompletedHide();
2396
+ const visible = _getVisibleTodos();
2397
+
2398
+ if (!visible.length) {
2399
+ panel.classList.add("hidden");
2400
+ panel.innerHTML = "";
2401
+ return;
2402
+ }
2403
+
2404
+ panel.classList.remove("hidden");
2405
+ if (state.todoPanelCollapsed) {
2406
+ panel.classList.add("collapsed");
2407
+ } else {
2408
+ panel.classList.remove("collapsed");
2409
+ }
2410
+ _ensureHideTimer();
2411
+
2412
+ const completed = state.todos.filter(t => t.status === "completed").length;
2413
+ const total = state.todos.length;
2414
+
2415
+ const statusIcon = (status) => {
2416
+ if (status === "completed") return '<span class="todo-status todo-status-completed">\\u2713</span>';
2417
+ if (status === "in_progress") return '<span class="todo-status todo-status-in-progress">\\u25CF</span>';
2418
+ return '<span class="todo-status todo-status-pending">\\u25CB</span>';
2419
+ };
2420
+
2421
+ const priorityBadge = (priority) => {
2422
+ if (!priority || priority === "medium") return "";
2423
+ return '<span class="todo-priority todo-priority-' + priority + '">' + priority + '</span>';
2424
+ };
2425
+
2426
+ const isCollapsed = state.todoPanelCollapsed;
2427
+
2428
+ let html = '<div class="todo-panel-header" id="todo-panel-header">';
2429
+ html += '<div class="todo-panel-title">';
2430
+ html += '<span class="todo-panel-label">Todos</span>';
2431
+ html += '<span class="todo-panel-progress">' + completed + '/' + total + ' done</span>';
2432
+ html += '</div>';
2433
+ 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>';
2434
+ html += '</div>';
2435
+
2436
+ if (!isCollapsed) {
2437
+ html += '<ul class="todo-panel-list">';
2438
+ visible.forEach(function(todo) {
2439
+ const isDone = todo.status === "completed";
2440
+ html += '<li class="todo-item' + (isDone ? ' todo-item-done' : '') + '">';
2441
+ html += statusIcon(todo.status);
2442
+ html += '<span class="todo-item-content">' + escapeHtml(todo.content) + '</span>';
2443
+ html += priorityBadge(todo.priority);
2444
+ html += '</li>';
2445
+ });
2446
+ html += '</ul>';
2447
+ }
2448
+
2449
+ panel.innerHTML = html;
2450
+
2451
+ const header = document.getElementById("todo-panel-header");
2452
+ if (header) {
2453
+ header.onclick = function() {
2454
+ state.todoPanelCollapsed = !state.todoPanelCollapsed;
2455
+ renderTodoPanel();
2456
+ };
2457
+ }
2458
+ };
2459
+
2216
2460
  const renderConversationList = () => {
2217
2461
  elements.list.innerHTML = "";
2218
2462
  const pending = state.conversations.filter(c => c.hasPendingApprovals);
@@ -2227,21 +2471,48 @@ var getWebUiClientScript = (markedSource2) => `
2227
2471
  elements.list.appendChild(buildConversationItem(c));
2228
2472
  appendSubagentsIfActive(c.conversationId);
2229
2473
  }
2230
- if (rest.length > 0) {
2474
+ }
2475
+
2476
+ const now = new Date();
2477
+ const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
2478
+ const sevenDaysAgo = startOfToday - 7 * 86400000;
2479
+
2480
+ const latest = [];
2481
+ const previous7 = [];
2482
+ const older = [];
2483
+ for (const c of rest) {
2484
+ const ts = c.updatedAt || c.createdAt || 0;
2485
+ if (ts >= startOfToday) {
2486
+ latest.push(c);
2487
+ } else if (ts >= sevenDaysAgo) {
2488
+ previous7.push(c);
2489
+ } else {
2490
+ older.push(c);
2491
+ }
2492
+ }
2493
+
2494
+ let sectionRendered = pending.length > 0;
2495
+ const appendSection = (items, labelText) => {
2496
+ if (items.length === 0) return;
2497
+ if (sectionRendered) {
2231
2498
  const divider = document.createElement("div");
2232
2499
  divider.className = "sidebar-section-divider";
2233
2500
  elements.list.appendChild(divider);
2234
- const recentLabel = document.createElement("div");
2235
- recentLabel.className = "sidebar-section-label";
2236
- recentLabel.textContent = "Recent";
2237
- elements.list.appendChild(recentLabel);
2238
2501
  }
2239
- }
2502
+ const sectionLabel = document.createElement("div");
2503
+ sectionLabel.className = "sidebar-section-label";
2504
+ sectionLabel.textContent = labelText;
2505
+ elements.list.appendChild(sectionLabel);
2506
+ for (const c of items) {
2507
+ elements.list.appendChild(buildConversationItem(c));
2508
+ appendSubagentsIfActive(c.conversationId);
2509
+ }
2510
+ sectionRendered = true;
2511
+ };
2240
2512
 
2241
- for (const c of rest) {
2242
- elements.list.appendChild(buildConversationItem(c));
2243
- appendSubagentsIfActive(c.conversationId);
2244
- }
2513
+ appendSection(latest, "Latest");
2514
+ appendSection(previous7, "Previous 7 days");
2515
+ appendSection(older, "Older");
2245
2516
  };
2246
2517
 
2247
2518
  const appendSubagentsIfActive = (conversationId) => {
@@ -2621,6 +2892,15 @@ var getWebUiClientScript = (markedSource2) => `
2621
2892
  loadSubagents(subagentParentId);
2622
2893
  }
2623
2894
 
2895
+ try {
2896
+ const todosPayload = await api("/api/conversations/" + encodeURIComponent(conversationId) + "/todos");
2897
+ state.todos = todosPayload.todos || [];
2898
+ } catch (_e) {
2899
+ state.todos = [];
2900
+ }
2901
+ _autoCollapseTodos();
2902
+ renderTodoPanel();
2903
+
2624
2904
  updateContextRing();
2625
2905
  renderMessages(state.activeMessages, false, { forceScrollBottom: true });
2626
2906
  if (!state.viewingSubagentId) {
@@ -2723,6 +3003,13 @@ var getWebUiClientScript = (markedSource2) => `
2723
3003
  payload.conversation.messages || [],
2724
3004
  allPending,
2725
3005
  );
3006
+ if (typeof payload.conversation.contextTokens === "number") {
3007
+ state.contextTokens = payload.conversation.contextTokens;
3008
+ }
3009
+ if (typeof payload.conversation.contextWindow === "number" && payload.conversation.contextWindow > 0) {
3010
+ state.contextWindow = payload.conversation.contextWindow;
3011
+ }
3012
+ updateContextRing();
2726
3013
  renderMessages(state.activeMessages, payload.hasActiveRun);
2727
3014
  }
2728
3015
  if (payload.hasActiveRun) {
@@ -2938,6 +3225,11 @@ var getWebUiClientScript = (markedSource2) => `
2938
3225
  state.contextTokens += payload.outputTokenEstimate;
2939
3226
  updateContextRing();
2940
3227
  }
3228
+ if (toolName !== "todo_list" && toolName.startsWith("todo_") && payload.output && typeof payload.output === "object" && Array.isArray(payload.output.todos)) {
3229
+ state.todos = payload.output.todos;
3230
+ _autoCollapseTodos(true);
3231
+ renderTodoPanel();
3232
+ }
2941
3233
  renderIfActiveConversation(true);
2942
3234
  }
2943
3235
  if (eventName === "tool:error") {
@@ -2973,6 +3265,10 @@ var getWebUiClientScript = (markedSource2) => `
2973
3265
  if (eventName === "compaction:completed") {
2974
3266
  didCompact = true;
2975
3267
  removeActiveActivityForTool(assistantMessage, "__compaction__");
3268
+ if (typeof payload.tokensAfter === "number") {
3269
+ state.contextTokens = payload.tokensAfter;
3270
+ updateContextRing();
3271
+ }
2976
3272
  renderIfActiveConversation(true);
2977
3273
  }
2978
3274
  if (eventName === "browser:status" && payload.active) {
@@ -3760,6 +4056,11 @@ var getWebUiClientScript = (markedSource2) => `
3760
4056
  state.contextTokens += payload.outputTokenEstimate;
3761
4057
  updateContextRing();
3762
4058
  }
4059
+ if (toolName !== "todo_list" && toolName.startsWith("todo_") && payload.output && typeof payload.output === "object" && Array.isArray(payload.output.todos)) {
4060
+ state.todos = payload.output.todos;
4061
+ _autoCollapseTodos(true);
4062
+ renderTodoPanel();
4063
+ }
3763
4064
  renderIfActiveConversation(true);
3764
4065
  }
3765
4066
  if (eventName === "tool:error") {
@@ -3797,6 +4098,10 @@ var getWebUiClientScript = (markedSource2) => `
3797
4098
  if (eventName === "compaction:completed") {
3798
4099
  didCompact = true;
3799
4100
  removeActiveActivityForTool(assistantMessage, "__compaction__");
4101
+ if (typeof payload.tokensAfter === "number") {
4102
+ state.contextTokens = payload.tokensAfter;
4103
+ updateContextRing();
4104
+ }
3800
4105
  renderIfActiveConversation(true);
3801
4106
  }
3802
4107
  if (eventName === "browser:status" && payload.active) {
@@ -4043,8 +4348,10 @@ var getWebUiClientScript = (markedSource2) => `
4043
4348
  state.parentConversationId = null;
4044
4349
  state.subagents = [];
4045
4350
  state.subagentsParentId = null;
4351
+ state.todos = [];
4046
4352
  updateSubagentUi();
4047
4353
  updateContextRing();
4354
+ renderTodoPanel();
4048
4355
  pushConversationUrl(null);
4049
4356
  elements.chatTitle.textContent = "";
4050
4357
  renderMessages([]);
@@ -5193,6 +5500,7 @@ ${WEB_UI_STYLES}
5193
5500
  </div>
5194
5501
  <form id="composer" class="composer">
5195
5502
  <div class="composer-inner">
5503
+ <div id="todo-panel" class="hidden"></div>
5196
5504
  <div id="attachment-preview" class="attachment-preview" style="display:none"></div>
5197
5505
  <div class="composer-shell">
5198
5506
  <button id="attach-btn" class="attach-btn" type="button" title="Attach files">
@@ -7778,7 +8086,7 @@ data: ${JSON.stringify(statusPayload)}
7778
8086
  runId: null
7779
8087
  });
7780
8088
  childHarness.setSubagentManager(subagentManager);
7781
- childHarness.unregisterTools(["memory_main_update"]);
8089
+ childHarness.unregisterTools(["memory_main_write", "memory_main_edit"]);
7782
8090
  let assistantResponse = "";
7783
8091
  let latestRunId = "";
7784
8092
  const toolTimeline = [];
@@ -8137,6 +8445,32 @@ data: ${JSON.stringify(statusPayload)}
8137
8445
  let runContextWindow = conversation.contextWindow ?? 0;
8138
8446
  const baseMessages = checkpoint.baseMessageCount != null ? conversation.messages.slice(0, checkpoint.baseMessageCount) : [];
8139
8447
  const fullCheckpointMessages = [...baseMessages, ...checkpoint.checkpointMessages];
8448
+ let resumeToolResultMsg;
8449
+ const lastCpMsg = fullCheckpointMessages[fullCheckpointMessages.length - 1];
8450
+ if (lastCpMsg?.role === "assistant") {
8451
+ try {
8452
+ const parsed = JSON.parse(typeof lastCpMsg.content === "string" ? lastCpMsg.content : "");
8453
+ const cpToolCalls = parsed.tool_calls ?? [];
8454
+ if (cpToolCalls.length > 0) {
8455
+ const providedMap = new Map(toolResults.map((r) => [r.callId, r]));
8456
+ resumeToolResultMsg = {
8457
+ role: "tool",
8458
+ content: JSON.stringify(cpToolCalls.map((tc) => {
8459
+ const provided = providedMap.get(tc.id);
8460
+ return {
8461
+ type: "tool_result",
8462
+ tool_use_id: tc.id,
8463
+ tool_name: provided?.toolName ?? tc.name,
8464
+ content: provided ? provided.error ? `Tool error: ${provided.error}` : JSON.stringify(provided.result ?? null) : "Tool error: Tool execution deferred (pending approval checkpoint)"
8465
+ };
8466
+ })),
8467
+ metadata: { timestamp: Date.now() }
8468
+ };
8469
+ }
8470
+ } catch {
8471
+ }
8472
+ }
8473
+ const fullCheckpointWithResults = resumeToolResultMsg ? [...fullCheckpointMessages, resumeToolResultMsg] : fullCheckpointMessages;
8140
8474
  try {
8141
8475
  for await (const event of harness.continueFromToolResult({
8142
8476
  messages: fullCheckpointMessages,
@@ -8202,7 +8536,7 @@ data: ${JSON.stringify(statusPayload)}
8202
8536
  tool: a.tool,
8203
8537
  toolCallId: a.toolCallId,
8204
8538
  input: a.input,
8205
- checkpointMessages: [...fullCheckpointMessages, ...event.checkpointMessages],
8539
+ checkpointMessages: [...fullCheckpointWithResults, ...event.checkpointMessages],
8206
8540
  baseMessageCount: 0,
8207
8541
  pendingToolCalls: event.pendingToolCalls
8208
8542
  }));
@@ -9407,6 +9741,13 @@ data: ${JSON.stringify(frame)}
9407
9741
  writeJson(response, 200, { subagents });
9408
9742
  return;
9409
9743
  }
9744
+ const todosMatch = pathname.match(/^\/api\/conversations\/([^/]+)\/todos$/);
9745
+ if (todosMatch && request.method === "GET") {
9746
+ const conversationId = decodeURIComponent(todosMatch[1] ?? "");
9747
+ const todos = await harness.getTodos(conversationId);
9748
+ writeJson(response, 200, { todos });
9749
+ return;
9750
+ }
9410
9751
  const conversationPathMatch = pathname.match(/^\/api\/conversations\/([^/]+)$/);
9411
9752
  if (conversationPathMatch) {
9412
9753
  const conversationId = decodeURIComponent(conversationPathMatch[1] ?? "");
@@ -10613,7 +10954,7 @@ var runInteractive = async (workingDir, params) => {
10613
10954
  await harness.initialize();
10614
10955
  const identity = await ensureAgentIdentity2(workingDir);
10615
10956
  try {
10616
- const { runInteractiveInk } = await import("./run-interactive-ink-OUX42GU4.js");
10957
+ const { runInteractiveInk } = await import("./run-interactive-ink-5SPYMVJR.js");
10617
10958
  await runInteractiveInk({
10618
10959
  harness,
10619
10960
  params,
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  main
4
- } from "./chunk-LPT2RFZ5.js";
4
+ } from "./chunk-5AKBY5GK.js";
5
5
 
6
6
  // src/cli.ts
7
7
  void main();
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  runTests,
24
24
  startDevServer,
25
25
  updateAgentGuidance
26
- } from "./chunk-LPT2RFZ5.js";
26
+ } from "./chunk-5AKBY5GK.js";
27
27
  export {
28
28
  addSkill,
29
29
  buildCli,
@@ -2,7 +2,7 @@ import {
2
2
  consumeFirstRunIntro,
3
3
  inferConversationTitle,
4
4
  resolveHarnessEnvironment
5
- } from "./chunk-LPT2RFZ5.js";
5
+ } from "./chunk-5AKBY5GK.js";
6
6
 
7
7
  // src/run-interactive-ink.ts
8
8
  import * as readline from "readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/cli",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
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.24.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
@@ -1681,8 +1681,8 @@ export const createRequestHandler = async (options?: {
1681
1681
 
1682
1682
  // Wire up subagent manager on the child so it can spawn sub-subagents
1683
1683
  childHarness.setSubagentManager(subagentManager);
1684
- // Subagents get read-only memory -- strip the write tool
1685
- childHarness.unregisterTools(["memory_main_update"]);
1684
+ // Subagents get read-only memory -- strip the write tools
1685
+ childHarness.unregisterTools(["memory_main_write", "memory_main_edit"]);
1686
1686
 
1687
1687
  let assistantResponse = "";
1688
1688
  let latestRunId = "";
@@ -2098,6 +2098,41 @@ export const createRequestHandler = async (options?: {
2098
2098
  : [];
2099
2099
  const fullCheckpointMessages = [...baseMessages, ...checkpoint.checkpointMessages!];
2100
2100
 
2101
+ // Build the tool result message that continueFromToolResult will also
2102
+ // construct internally. We need it here so that if the resumed run hits
2103
+ // another approval checkpoint, the nested checkpoint includes complete
2104
+ // tool-call/result pairs — otherwise the Vercel AI SDK throws
2105
+ // MissingToolResultsError when converting the history on the next resume.
2106
+ let resumeToolResultMsg: Message | undefined;
2107
+ const lastCpMsg = fullCheckpointMessages[fullCheckpointMessages.length - 1];
2108
+ if (lastCpMsg?.role === "assistant") {
2109
+ try {
2110
+ const parsed = JSON.parse(typeof lastCpMsg.content === "string" ? lastCpMsg.content : "");
2111
+ const cpToolCalls: Array<{ id: string; name: string }> = parsed.tool_calls ?? [];
2112
+ if (cpToolCalls.length > 0) {
2113
+ const providedMap = new Map(toolResults.map(r => [r.callId, r]));
2114
+ resumeToolResultMsg = {
2115
+ role: "tool",
2116
+ content: JSON.stringify(cpToolCalls.map(tc => {
2117
+ const provided = providedMap.get(tc.id);
2118
+ return {
2119
+ type: "tool_result",
2120
+ tool_use_id: tc.id,
2121
+ tool_name: provided?.toolName ?? tc.name,
2122
+ content: provided
2123
+ ? (provided.error ? `Tool error: ${provided.error}` : JSON.stringify(provided.result ?? null))
2124
+ : "Tool error: Tool execution deferred (pending approval checkpoint)",
2125
+ };
2126
+ })),
2127
+ metadata: { timestamp: Date.now() },
2128
+ };
2129
+ }
2130
+ } catch { /* last message is not a parseable assistant-with-tools — skip */ }
2131
+ }
2132
+ const fullCheckpointWithResults = resumeToolResultMsg
2133
+ ? [...fullCheckpointMessages, resumeToolResultMsg]
2134
+ : fullCheckpointMessages;
2135
+
2101
2136
  try {
2102
2137
  for await (const event of harness.continueFromToolResult({
2103
2138
  messages: fullCheckpointMessages,
@@ -2163,7 +2198,7 @@ export const createRequestHandler = async (options?: {
2163
2198
  tool: a.tool,
2164
2199
  toolCallId: a.toolCallId,
2165
2200
  input: a.input,
2166
- checkpointMessages: [...fullCheckpointMessages, ...event.checkpointMessages],
2201
+ checkpointMessages: [...fullCheckpointWithResults, ...event.checkpointMessages],
2167
2202
  baseMessageCount: 0,
2168
2203
  pendingToolCalls: event.pendingToolCalls,
2169
2204
  }));
@@ -3559,6 +3594,14 @@ export const createRequestHandler = async (options?: {
3559
3594
  return;
3560
3595
  }
3561
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
+
3562
3605
  const conversationPathMatch = pathname.match(/^\/api\/conversations\/([^/]+)$/);
3563
3606
  if (conversationPathMatch) {
3564
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,6 +1179,15 @@ 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
1192
  renderMessages(state.activeMessages, false, { forceScrollBottom: true });
1023
1193
  if (!state.viewingSubagentId) {
@@ -1120,6 +1290,13 @@ export const getWebUiClientScript = (markedSource: string): string => `
1120
1290
  payload.conversation.messages || [],
1121
1291
  allPending,
1122
1292
  );
1293
+ if (typeof payload.conversation.contextTokens === "number") {
1294
+ state.contextTokens = payload.conversation.contextTokens;
1295
+ }
1296
+ if (typeof payload.conversation.contextWindow === "number" && payload.conversation.contextWindow > 0) {
1297
+ state.contextWindow = payload.conversation.contextWindow;
1298
+ }
1299
+ updateContextRing();
1123
1300
  renderMessages(state.activeMessages, payload.hasActiveRun);
1124
1301
  }
1125
1302
  if (payload.hasActiveRun) {
@@ -1335,6 +1512,11 @@ export const getWebUiClientScript = (markedSource: string): string => `
1335
1512
  state.contextTokens += payload.outputTokenEstimate;
1336
1513
  updateContextRing();
1337
1514
  }
1515
+ if (toolName !== "todo_list" && toolName.startsWith("todo_") && payload.output && typeof payload.output === "object" && Array.isArray(payload.output.todos)) {
1516
+ state.todos = payload.output.todos;
1517
+ _autoCollapseTodos(true);
1518
+ renderTodoPanel();
1519
+ }
1338
1520
  renderIfActiveConversation(true);
1339
1521
  }
1340
1522
  if (eventName === "tool:error") {
@@ -1370,6 +1552,10 @@ export const getWebUiClientScript = (markedSource: string): string => `
1370
1552
  if (eventName === "compaction:completed") {
1371
1553
  didCompact = true;
1372
1554
  removeActiveActivityForTool(assistantMessage, "__compaction__");
1555
+ if (typeof payload.tokensAfter === "number") {
1556
+ state.contextTokens = payload.tokensAfter;
1557
+ updateContextRing();
1558
+ }
1373
1559
  renderIfActiveConversation(true);
1374
1560
  }
1375
1561
  if (eventName === "browser:status" && payload.active) {
@@ -2157,6 +2343,11 @@ export const getWebUiClientScript = (markedSource: string): string => `
2157
2343
  state.contextTokens += payload.outputTokenEstimate;
2158
2344
  updateContextRing();
2159
2345
  }
2346
+ if (toolName !== "todo_list" && toolName.startsWith("todo_") && payload.output && typeof payload.output === "object" && Array.isArray(payload.output.todos)) {
2347
+ state.todos = payload.output.todos;
2348
+ _autoCollapseTodos(true);
2349
+ renderTodoPanel();
2350
+ }
2160
2351
  renderIfActiveConversation(true);
2161
2352
  }
2162
2353
  if (eventName === "tool:error") {
@@ -2194,6 +2385,10 @@ export const getWebUiClientScript = (markedSource: string): string => `
2194
2385
  if (eventName === "compaction:completed") {
2195
2386
  didCompact = true;
2196
2387
  removeActiveActivityForTool(assistantMessage, "__compaction__");
2388
+ if (typeof payload.tokensAfter === "number") {
2389
+ state.contextTokens = payload.tokensAfter;
2390
+ updateContextRing();
2391
+ }
2197
2392
  renderIfActiveConversation(true);
2198
2393
  }
2199
2394
  if (eventName === "browser:status" && payload.active) {
@@ -2440,8 +2635,10 @@ export const getWebUiClientScript = (markedSource: string): string => `
2440
2635
  state.parentConversationId = null;
2441
2636
  state.subagents = [];
2442
2637
  state.subagentsParentId = null;
2638
+ state.todos = [];
2443
2639
  updateSubagentUi();
2444
2640
  updateContextRing();
2641
+ renderTodoPanel();
2445
2642
  pushConversationUrl(null);
2446
2643
  elements.chatTitle.textContent = "";
2447
2644
  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">