@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.
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +20 -0
- package/dist/{chunk-LPT2RFZ5.js → chunk-5AKBY5GK.js} +354 -13
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-interactive-ink-OUX42GU4.js → run-interactive-ink-5SPYMVJR.js} +1 -1
- package/package.json +2 -2
- package/src/index.ts +46 -3
- package/src/web-ui-client.ts +207 -10
- package/src/web-ui-styles.ts +110 -0
- package/src/web-ui.ts +1 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/cli@0.
|
|
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
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
[34mESM[39m Build start
|
|
10
10
|
[32mESM[39m [1mdist/cli.js [22m[32m94.00 B[39m
|
|
11
11
|
[32mESM[39m [1mdist/index.js [22m[32m857.00 B[39m
|
|
12
|
-
[32mESM[39m [1mdist/run-interactive-ink-
|
|
13
|
-
[32mESM[39m [1mdist/chunk-
|
|
14
|
-
[32mESM[39m ⚡️ Build success in
|
|
12
|
+
[32mESM[39m [1mdist/run-interactive-ink-5SPYMVJR.js [22m[32m56.74 KB[39m
|
|
13
|
+
[32mESM[39m [1mdist/chunk-5AKBY5GK.js [22m[32m434.40 KB[39m
|
|
14
|
+
[32mESM[39m ⚡️ Build success in 56ms
|
|
15
15
|
[34mDTS[39m Build start
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 3589ms
|
|
17
17
|
[32mDTS[39m [1mdist/cli.d.ts [22m[32m20.00 B[39m
|
|
18
18
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m3.70 KB[39m
|
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
|
-
|
|
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
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
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(["
|
|
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: [...
|
|
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-
|
|
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
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poncho-ai/cli",
|
|
3
|
-
"version": "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.
|
|
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
|
|
1685
|
-
childHarness.unregisterTools(["
|
|
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: [...
|
|
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] ?? "");
|
package/src/web-ui-client.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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([]);
|
package/src/web-ui-styles.ts
CHANGED
|
@@ -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">
|