jarvis-hq 0.0.6 → 0.0.7
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/dist/client-html.js +232 -15
- package/dist/start-claude-code.js +21 -1
- package/package.json +1 -1
package/dist/client-html.js
CHANGED
|
@@ -5,7 +5,7 @@ exports.html = `<!DOCTYPE html>
|
|
|
5
5
|
<html lang="en">
|
|
6
6
|
<head>
|
|
7
7
|
<meta charset="UTF-8" />
|
|
8
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
8
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
|
9
9
|
<title>Jarvis</title>
|
|
10
10
|
<style>
|
|
11
11
|
:root {
|
|
@@ -349,12 +349,25 @@ exports.html = `<!DOCTYPE html>
|
|
|
349
349
|
.msg.user .who { text-align: right; }
|
|
350
350
|
|
|
351
351
|
.tool {
|
|
352
|
-
display: flex; align-items: center; gap: 9px; margin-top: 7px;
|
|
352
|
+
display: flex; align-items: center; gap: 9px; margin-top: 7px; width: 100%; text-align: left;
|
|
353
353
|
background: var(--surface-2); border-radius: 10px; padding: 7px 11px;
|
|
354
354
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--muted);
|
|
355
|
+
cursor: default;
|
|
356
|
+
}
|
|
357
|
+
.tool b { color: var(--text); font-weight: 600; flex: none; align-self: flex-start; }
|
|
358
|
+
.tool-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
359
|
+
/* Only chips with more to show become interactive; the rest stay plain labels. */
|
|
360
|
+
.tool.more { cursor: pointer; }
|
|
361
|
+
.tool.more:hover { background: var(--border); }
|
|
362
|
+
.tool-chev { display: none; flex: none; align-self: flex-start; color: var(--faint); font-size: 15px; line-height: 1.3; transition: transform .15s ease; }
|
|
363
|
+
.tool.more .tool-chev { display: block; }
|
|
364
|
+
@media (prefers-reduced-motion: reduce) { .tool-chev { transition: none; } }
|
|
365
|
+
.tool.open { align-items: flex-start; }
|
|
366
|
+
.tool.open .tool-chev { transform: rotate(90deg); }
|
|
367
|
+
.tool.open .tool-text {
|
|
368
|
+
white-space: pre-wrap; overflow-wrap: anywhere; text-overflow: clip; cursor: auto;
|
|
369
|
+
max-height: 40vh; overflow-y: auto; overscroll-behavior: contain;
|
|
355
370
|
}
|
|
356
|
-
.tool b { color: var(--text); font-weight: 600; flex: none; }
|
|
357
|
-
.tool span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
358
371
|
.note { color: var(--faint); font-size: 12.5px; text-align: center; font-style: italic; }
|
|
359
372
|
|
|
360
373
|
.perm {
|
|
@@ -383,10 +396,46 @@ exports.html = `<!DOCTYPE html>
|
|
|
383
396
|
.ask-other:focus { border-color: var(--accent); }
|
|
384
397
|
|
|
385
398
|
/* --- Composer --- */
|
|
386
|
-
.composer { border-top: 1px solid var(--border); background: var(--bg); }
|
|
399
|
+
.composer { border-top: 1px solid var(--border); background: var(--bg); position: relative; }
|
|
400
|
+
/* Only on screen once the reader has left the tail and missed something. */
|
|
401
|
+
.jump {
|
|
402
|
+
position: absolute; bottom: calc(100% + 12px); left: 0; right: 0; margin: 0 auto;
|
|
403
|
+
display: none; align-items: center; gap: 7px; width: max-content;
|
|
404
|
+
padding: 8px 15px; border-radius: 999px; border: none;
|
|
405
|
+
background: var(--accent); color: var(--accent-text);
|
|
406
|
+
font-size: 13px; font-weight: 600; box-shadow: var(--shadow-md);
|
|
407
|
+
}
|
|
408
|
+
.jump.show { display: flex; }
|
|
409
|
+
.jump .chev { font-size: 15px; line-height: 1; }
|
|
387
410
|
.composer .wrap { padding-top: 12px; padding-bottom: 18px; }
|
|
388
411
|
.activity { display: flex; align-items: center; gap: 8px; height: 22px; padding-left: 4px; color: var(--muted); font-size: 12.5px; }
|
|
389
412
|
.activity:empty { display: none; }
|
|
413
|
+
|
|
414
|
+
/* --- Plan panel --- */
|
|
415
|
+
/* The agent's todo list, kept above the input so the current step is always
|
|
416
|
+
in view. Collapsed to a single line by default; the list it hides is capped
|
|
417
|
+
in height so a long plan can never crowd out the conversation. */
|
|
418
|
+
.plan { margin-bottom: 9px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); overflow: hidden; }
|
|
419
|
+
.plan-bar {
|
|
420
|
+
display: flex; align-items: center; gap: 9px; width: 100%;
|
|
421
|
+
padding: 9px 12px; font-size: 12.5px; text-align: left;
|
|
422
|
+
}
|
|
423
|
+
.plan-now { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }
|
|
424
|
+
.plan.active .plan-now { color: var(--accent); font-weight: 500; }
|
|
425
|
+
.plan-count { flex: none; color: var(--faint); font-variant-numeric: tabular-nums; }
|
|
426
|
+
.plan-chev { flex: none; color: var(--faint); font-size: 15px; line-height: 1; transition: transform .15s ease; }
|
|
427
|
+
.plan.open .plan-chev { transform: rotate(90deg); }
|
|
428
|
+
@media (prefers-reduced-motion: reduce) { .plan-chev { transition: none; } }
|
|
429
|
+
.plan-list {
|
|
430
|
+
margin: 0; padding: 3px 12px 10px; list-style: none;
|
|
431
|
+
max-height: 33vh; overflow-y: auto; border-top: 1px solid var(--border);
|
|
432
|
+
}
|
|
433
|
+
.plan-item { display: flex; gap: 9px; align-items: baseline; padding: 4px 0; font-size: 13px; }
|
|
434
|
+
.plan-mark { flex: none; width: 13px; text-align: center; color: var(--faint); }
|
|
435
|
+
.plan-item.in_progress .plan-mark, .plan-item.in_progress { color: var(--accent); }
|
|
436
|
+
.plan-item.in_progress { font-weight: 500; }
|
|
437
|
+
.plan-item.completed { color: var(--faint); text-decoration: line-through; }
|
|
438
|
+
.plan-item.completed .plan-mark { color: var(--ok); text-decoration: none; }
|
|
390
439
|
.spinner {
|
|
391
440
|
width: 11px; height: 11px; flex: none; border-radius: 50%;
|
|
392
441
|
border: 1.5px solid var(--border); border-top-color: var(--accent); animation: spin .7s linear infinite;
|
|
@@ -399,7 +448,7 @@ exports.html = `<!DOCTYPE html>
|
|
|
399
448
|
padding: 9px 9px 9px 16px; box-shadow: var(--shadow-sm);
|
|
400
449
|
}
|
|
401
450
|
.box:focus-within { border-color: var(--accent); }
|
|
402
|
-
.box textarea { flex: 1; border: 0; background: none; resize: none; outline: none; max-height: 200px; padding: 6px 0; }
|
|
451
|
+
.box textarea { flex: 1; border: 0; background: none; resize: none; outline: none; max-height: 200px; padding: 6px 0; font-size: 16px; }
|
|
403
452
|
.send {
|
|
404
453
|
width: 36px; height: 36px; border-radius: 11px; flex: none;
|
|
405
454
|
background: var(--accent); color: var(--accent-text); font-size: 17px;
|
|
@@ -597,14 +646,25 @@ exports.html = `<!DOCTYPE html>
|
|
|
597
646
|
</div>
|
|
598
647
|
</div></div>
|
|
599
648
|
<div class="messages" id="messages"><div class="wrap narrow" id="messages-inner"></div></div>
|
|
600
|
-
<div class="composer"
|
|
601
|
-
<
|
|
602
|
-
<div class="
|
|
603
|
-
<
|
|
604
|
-
|
|
605
|
-
|
|
649
|
+
<div class="composer">
|
|
650
|
+
<button class="jump" id="jump"><span class="chev">↓</span> New messages</button>
|
|
651
|
+
<div class="wrap narrow">
|
|
652
|
+
<div class="plan" id="plan" hidden>
|
|
653
|
+
<button class="plan-bar" id="plan-bar" aria-expanded="false">
|
|
654
|
+
<span class="plan-now" id="plan-now"></span>
|
|
655
|
+
<span class="plan-count" id="plan-count"></span>
|
|
656
|
+
<span class="plan-chev" id="plan-chev">›</span>
|
|
657
|
+
</button>
|
|
658
|
+
<ul class="plan-list" id="plan-list" hidden></ul>
|
|
659
|
+
</div>
|
|
660
|
+
<div class="activity" id="activity"></div>
|
|
661
|
+
<div class="box">
|
|
662
|
+
<textarea id="input" rows="1" placeholder="Message…"></textarea>
|
|
663
|
+
<button class="mic" id="mic" title="Dictate" aria-label="Dictate"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 15a4 4 0 0 0 4-4V6a4 4 0 0 0-8 0v5a4 4 0 0 0 4 4zm6-4a6 6 0 0 1-5 5.92V20h3v2H8v-2h3v-3.08A6 6 0 0 1 6 11h2a4 4 0 0 0 8 0h2z"/></svg></button>
|
|
664
|
+
<button class="send" id="send" title="Send" aria-label="Send">↑</button>
|
|
665
|
+
</div>
|
|
606
666
|
</div>
|
|
607
|
-
</div
|
|
667
|
+
</div>
|
|
608
668
|
</section>
|
|
609
669
|
|
|
610
670
|
<script>
|
|
@@ -1346,6 +1406,65 @@ exports.html = `<!DOCTYPE html>
|
|
|
1346
1406
|
box.hidden = false;
|
|
1347
1407
|
}
|
|
1348
1408
|
|
|
1409
|
+
// --- Plan panel ---
|
|
1410
|
+
// The agent's own todo list, mirrored above the input so the step it is on is
|
|
1411
|
+
// always in view. TodoWrite always writes the whole plan, so each call simply
|
|
1412
|
+
// replaces what the panel shows; there is nothing to merge or diff. Collapsed
|
|
1413
|
+
// to one line by default, and the line itself is the cue — it changes as the
|
|
1414
|
+
// agent moves through the list.
|
|
1415
|
+
|
|
1416
|
+
var planOpen = false; // sticky across updates, reset when a thread opens
|
|
1417
|
+
var PLAN_MARKS = { completed: "\\u2713", in_progress: "\\u25b8", pending: "\\u2610" };
|
|
1418
|
+
|
|
1419
|
+
function renderTodos(todos) {
|
|
1420
|
+
var box = $("plan");
|
|
1421
|
+
if (!todos || !todos.length) { box.hidden = true; return; }
|
|
1422
|
+
|
|
1423
|
+
var done = 0, current = null, firstPending = null;
|
|
1424
|
+
todos.forEach(function (t) {
|
|
1425
|
+
if (t.status === "completed") done++;
|
|
1426
|
+
else if (t.status === "in_progress" && !current) current = t;
|
|
1427
|
+
else if (t.status === "pending" && !firstPending) firstPending = t;
|
|
1428
|
+
});
|
|
1429
|
+
var shown = current || firstPending;
|
|
1430
|
+
|
|
1431
|
+
// activeForm is the present-continuous wording the tool asks for, which is
|
|
1432
|
+
// exactly what a status line wants; content is the fallback.
|
|
1433
|
+
$("plan-now").textContent = shown ? (shown.activeForm || shown.content) : "Plan complete";
|
|
1434
|
+
$("plan-count").textContent = done + "/" + todos.length;
|
|
1435
|
+
|
|
1436
|
+
var list = $("plan-list");
|
|
1437
|
+
list.innerHTML = "";
|
|
1438
|
+
todos.forEach(function (t) {
|
|
1439
|
+
var li = el("li", "plan-item " + t.status);
|
|
1440
|
+
li.appendChild(el("span", "plan-mark", PLAN_MARKS[t.status] || PLAN_MARKS.pending));
|
|
1441
|
+
li.appendChild(el("span", null, t.content));
|
|
1442
|
+
list.appendChild(li);
|
|
1443
|
+
});
|
|
1444
|
+
|
|
1445
|
+
setPlanOpen(planOpen);
|
|
1446
|
+
box.hidden = false;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
function setPlanOpen(open) {
|
|
1450
|
+
planOpen = open;
|
|
1451
|
+
var box = $("plan");
|
|
1452
|
+
var lit = box.querySelector(".plan-item.in_progress");
|
|
1453
|
+
box.className = "plan" + (lit ? " active" : "") + (open ? " open" : "");
|
|
1454
|
+
$("plan-list").hidden = !open;
|
|
1455
|
+
$("plan-bar").setAttribute("aria-expanded", open ? "true" : "false");
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/** The last plan the thread ever wrote, so reopening it days later still
|
|
1459
|
+
* shows where the work got to rather than an empty panel. */
|
|
1460
|
+
function seedPlan(msgs) {
|
|
1461
|
+
var last = null;
|
|
1462
|
+
msgs.forEach(function (m) {
|
|
1463
|
+
(m.content || []).forEach(function (b) { if (b.todos) last = b.todos; });
|
|
1464
|
+
});
|
|
1465
|
+
renderTodos(last);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1349
1468
|
// --- Thread ---
|
|
1350
1469
|
function openThread(thread) {
|
|
1351
1470
|
activeThread = thread;
|
|
@@ -1354,6 +1473,8 @@ exports.html = `<!DOCTYPE html>
|
|
|
1354
1473
|
$("thread-sub").textContent = shortPath(thread.repoPath, 30);
|
|
1355
1474
|
$("thread-sub").title = thread.repoPath || "";
|
|
1356
1475
|
renderContext(thread.context);
|
|
1476
|
+
planOpen = false;
|
|
1477
|
+
renderTodos(null);
|
|
1357
1478
|
$("to-bot-label").textContent = threadBack === "jarvis" ? "Jarvis" : (activeBot ? activeBot.name : "Back");
|
|
1358
1479
|
var av = $("thread-avatar");
|
|
1359
1480
|
av.textContent = activeBot ? (activeBot.emoji || "\\u{1F916}") : "\\u{1F916}";
|
|
@@ -1382,6 +1503,7 @@ exports.html = `<!DOCTYPE html>
|
|
|
1382
1503
|
function renderTranscript(msgs) {
|
|
1383
1504
|
var box = $("messages-inner");
|
|
1384
1505
|
box.innerHTML = "";
|
|
1506
|
+
forceBottom(); // a freshly opened transcript always starts at its tail
|
|
1385
1507
|
if (!msgs.length) {
|
|
1386
1508
|
var e = el("div", "empty");
|
|
1387
1509
|
e.appendChild(el("h3", null, "Say the first thing"));
|
|
@@ -1389,6 +1511,7 @@ exports.html = `<!DOCTYPE html>
|
|
|
1389
1511
|
box.appendChild(e);
|
|
1390
1512
|
return;
|
|
1391
1513
|
}
|
|
1514
|
+
seedPlan(msgs);
|
|
1392
1515
|
msgs.forEach(function (m) {
|
|
1393
1516
|
var content = startMessage(m.role === "user" ? "user" : "assistant");
|
|
1394
1517
|
(m.content || []).forEach(function (b) {
|
|
@@ -1397,6 +1520,7 @@ exports.html = `<!DOCTYPE html>
|
|
|
1397
1520
|
else if (b.type === "image_url") {
|
|
1398
1521
|
var img = document.createElement("img");
|
|
1399
1522
|
img.src = b.url; img.style.maxWidth = "100%"; img.style.borderRadius = "12px";
|
|
1523
|
+
img.onload = scrollDown; // an image finishing late grows the list under us
|
|
1400
1524
|
content.appendChild(img);
|
|
1401
1525
|
}
|
|
1402
1526
|
});
|
|
@@ -1466,21 +1590,105 @@ exports.html = `<!DOCTYPE html>
|
|
|
1466
1590
|
}
|
|
1467
1591
|
}
|
|
1468
1592
|
|
|
1593
|
+
var TOOL_PEEK = 200; // how much of the input the collapsed one-liner carries
|
|
1594
|
+
|
|
1595
|
+
/** A chip's full text, laid out to be read: dense JSON gets indented, anything
|
|
1596
|
+
* else is shown as the agent wrote it. */
|
|
1597
|
+
function toolFullText(summary) {
|
|
1598
|
+
var t = summary.charAt(0);
|
|
1599
|
+
if (t === "{" || t === "[") {
|
|
1600
|
+
try { return JSON.stringify(JSON.parse(summary), null, 2); } catch (e) {}
|
|
1601
|
+
}
|
|
1602
|
+
return summary;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1469
1605
|
function appendTool(content, name, input) {
|
|
1470
1606
|
var chip = el("div", "tool");
|
|
1471
1607
|
// Jarvis's own tools read better without the MCP plumbing in the name.
|
|
1472
1608
|
chip.appendChild(el("b", null, name.replace(/^mcp__jarvis__/, "")));
|
|
1473
1609
|
var summary = typeof input === "string" ? input : JSON.stringify(input || {});
|
|
1474
|
-
|
|
1610
|
+
var text = el("span", "tool-text", summary.slice(0, TOOL_PEEK));
|
|
1611
|
+
chip.appendChild(text);
|
|
1612
|
+
chip.appendChild(el("span", "tool-chev", "\u203A"));
|
|
1475
1613
|
content.appendChild(chip);
|
|
1614
|
+
|
|
1615
|
+
// The chip only becomes clickable when the one-liner is actually hiding
|
|
1616
|
+
// something: text cut by the peek, newlines flattened into it, or an
|
|
1617
|
+
// ellipsis from the overflow. Measured now that it is laid out.
|
|
1618
|
+
var hidden = summary.length > TOOL_PEEK || summary.indexOf("\\n") >= 0
|
|
1619
|
+
|| text.scrollWidth > text.clientWidth + 1;
|
|
1620
|
+
if (hidden) {
|
|
1621
|
+
var open = false;
|
|
1622
|
+
chip.className = "tool more";
|
|
1623
|
+
chip.setAttribute("role", "button");
|
|
1624
|
+
chip.setAttribute("tabindex", "0");
|
|
1625
|
+
chip.setAttribute("aria-expanded", "false");
|
|
1626
|
+
chip.title = "Show the full input";
|
|
1627
|
+
var toggle = function () {
|
|
1628
|
+
open = !open;
|
|
1629
|
+
chip.className = "tool more" + (open ? " open" : "");
|
|
1630
|
+
chip.setAttribute("aria-expanded", open ? "true" : "false");
|
|
1631
|
+
chip.title = open ? "Hide the full input" : "Show the full input";
|
|
1632
|
+
text.textContent = open ? toolFullText(summary) : summary.slice(0, TOOL_PEEK);
|
|
1633
|
+
};
|
|
1634
|
+
// Once open the text belongs to the reader: clicks land in it to select or
|
|
1635
|
+
// scroll it, and the name and chevron are what close it again.
|
|
1636
|
+
chip.onclick = function (ev) {
|
|
1637
|
+
if (!open || !text.contains(ev.target)) toggle();
|
|
1638
|
+
};
|
|
1639
|
+
chip.onkeydown = function (ev) {
|
|
1640
|
+
if (ev.key === "Enter" || ev.key === " ") { ev.preventDefault(); toggle(); }
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1476
1644
|
scrollDown();
|
|
1477
1645
|
}
|
|
1478
1646
|
|
|
1479
|
-
|
|
1647
|
+
// --- Sticking to the tail ---
|
|
1648
|
+
// The list follows new content only while the reader is already at the bottom.
|
|
1649
|
+
// Scroll up to read something and the conversation stays where you put it,
|
|
1650
|
+
// with a pill offering the way back down once it has moved on without you.
|
|
1651
|
+
var stick = true; // following the tail?
|
|
1652
|
+
var missed = false; // has anything arrived since we stopped following?
|
|
1653
|
+
var SLACK = 64; // px from the bottom that still counts as "at the bottom"
|
|
1654
|
+
|
|
1655
|
+
function atBottom() {
|
|
1656
|
+
var m = $("messages");
|
|
1657
|
+
return m.scrollHeight - m.scrollTop - m.clientHeight <= SLACK;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
function pin() {
|
|
1480
1661
|
var m = $("messages");
|
|
1481
1662
|
m.scrollTop = m.scrollHeight;
|
|
1482
1663
|
}
|
|
1483
1664
|
|
|
1665
|
+
function renderJump() {
|
|
1666
|
+
$("jump").classList.toggle("show", !stick && missed);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
/** New content arrived: follow it, or note that the reader has missed it. */
|
|
1670
|
+
function scrollDown() {
|
|
1671
|
+
if (stick) pin();
|
|
1672
|
+
else if (!missed) { missed = true; renderJump(); }
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
/** Go to the tail whatever the reader was doing — for the moments that mean
|
|
1676
|
+
* "show me the latest": opening a thread, sending, tapping the pill. */
|
|
1677
|
+
function forceBottom() {
|
|
1678
|
+
stick = true;
|
|
1679
|
+
missed = false;
|
|
1680
|
+
pin();
|
|
1681
|
+
renderJump();
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
function watchScroll() {
|
|
1685
|
+
var here = atBottom();
|
|
1686
|
+
if (here === stick) return;
|
|
1687
|
+
stick = here;
|
|
1688
|
+
if (stick) missed = false;
|
|
1689
|
+
renderJump();
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1484
1692
|
function showError(err) {
|
|
1485
1693
|
var content = startMessage("error");
|
|
1486
1694
|
appendText(content, err && err.message ? err.message : String(err));
|
|
@@ -1500,6 +1708,7 @@ exports.html = `<!DOCTYPE html>
|
|
|
1500
1708
|
function sendText(text) {
|
|
1501
1709
|
if (!text || !activeThread || state !== "idle") return;
|
|
1502
1710
|
appendText(startMessage("user"), text);
|
|
1711
|
+
forceBottom(); // your own message always brings you back down
|
|
1503
1712
|
setState("starting");
|
|
1504
1713
|
setActivity("Sending\u2026");
|
|
1505
1714
|
|
|
@@ -1629,6 +1838,7 @@ exports.html = `<!DOCTYPE html>
|
|
|
1629
1838
|
case "tool_use":
|
|
1630
1839
|
if (!liveBubble) liveBubble = startMessage("assistant");
|
|
1631
1840
|
appendTool(liveBubble, data.tool_name, data.tool_input);
|
|
1841
|
+
if (data.todos) renderTodos(data.todos);
|
|
1632
1842
|
break;
|
|
1633
1843
|
|
|
1634
1844
|
case "context":
|
|
@@ -2450,6 +2660,7 @@ exports.html = `<!DOCTYPE html>
|
|
|
2450
2660
|
$("to-jarvis").onclick = function () { goJarvis(); };
|
|
2451
2661
|
$("to-projects").onclick = function () { goHome(); };
|
|
2452
2662
|
$("to-bot").onclick = function () { if (threadBack === "jarvis") goJarvis(); else goBotView(); };
|
|
2663
|
+
$("plan-bar").onclick = function () { setPlanOpen(!planOpen); };
|
|
2453
2664
|
$("ask-send").onclick = function () { askJarvis($("ask-input").value); };
|
|
2454
2665
|
var ask = $("ask-input");
|
|
2455
2666
|
ask.addEventListener("input", function () {
|
|
@@ -2463,10 +2674,16 @@ exports.html = `<!DOCTYPE html>
|
|
|
2463
2674
|
wireMic($("ask-mic"), $("ask-input"));
|
|
2464
2675
|
wireMic($("mic"), $("input"));
|
|
2465
2676
|
|
|
2677
|
+
$("messages").addEventListener("scroll", watchScroll, { passive: true });
|
|
2678
|
+
$("jump").onclick = forceBottom;
|
|
2679
|
+
// The on-screen keyboard and rotation both shrink the list out from under us.
|
|
2680
|
+
window.addEventListener("resize", function () { if (stick) pin(); });
|
|
2681
|
+
|
|
2466
2682
|
var input = $("input");
|
|
2467
2683
|
input.addEventListener("input", function () {
|
|
2468
2684
|
input.style.height = "auto";
|
|
2469
2685
|
input.style.height = Math.min(input.scrollHeight, 200) + "px";
|
|
2686
|
+
if (stick) pin(); // a growing composer eats into the list; hold the tail
|
|
2470
2687
|
});
|
|
2471
2688
|
input.addEventListener("keydown", function (ev) {
|
|
2472
2689
|
if (ev.key === "Enter" && !ev.shiftKey) { ev.preventDefault(); if (state === "idle") send(); }
|
|
@@ -332,6 +332,7 @@ function formatMessage(msg) {
|
|
|
332
332
|
type: "tool_use",
|
|
333
333
|
tool_name: block.name,
|
|
334
334
|
tool_input: formatToolInput(block.name, block.input),
|
|
335
|
+
...todoList(block.name, block.input),
|
|
335
336
|
});
|
|
336
337
|
}
|
|
337
338
|
}
|
|
@@ -445,7 +446,7 @@ async function loadTranscript(sessionId, cwd) {
|
|
|
445
446
|
catch {
|
|
446
447
|
tool_input = JSON.stringify(b.input) ?? "";
|
|
447
448
|
}
|
|
448
|
-
blocks.push({ type: "tool_use", tool_name: b.name, tool_input });
|
|
449
|
+
blocks.push({ type: "tool_use", tool_name: b.name, tool_input, ...todoList(b.name, b.input) });
|
|
449
450
|
}
|
|
450
451
|
}
|
|
451
452
|
}
|
|
@@ -537,6 +538,25 @@ async function listSessions(cwd) {
|
|
|
537
538
|
return [];
|
|
538
539
|
}
|
|
539
540
|
}
|
|
541
|
+
/**
|
|
542
|
+
* The plan panel needs the list as a list, not as the one-line summary the tool
|
|
543
|
+
* chip shows. Carried as a separate field so the chip keeps rendering unchanged,
|
|
544
|
+
* and spread in so every other tool's payload stays exactly as it was.
|
|
545
|
+
*/
|
|
546
|
+
function todoList(toolName, input) {
|
|
547
|
+
if (toolName !== "TodoWrite")
|
|
548
|
+
return {};
|
|
549
|
+
const todos = input.todos;
|
|
550
|
+
if (!Array.isArray(todos))
|
|
551
|
+
return {};
|
|
552
|
+
return {
|
|
553
|
+
todos: todos.map((t) => ({
|
|
554
|
+
content: String(t?.content ?? ""),
|
|
555
|
+
status: String(t?.status ?? "pending"),
|
|
556
|
+
activeForm: String(t?.activeForm ?? ""),
|
|
557
|
+
})),
|
|
558
|
+
};
|
|
559
|
+
}
|
|
540
560
|
function formatToolInput(toolName, input) {
|
|
541
561
|
switch (toolName) {
|
|
542
562
|
case "Bash":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jarvis-hq",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Jarvis — a persistent AI engineering partner. One entry point from any device; project-aware handoff and delegation to Claude Code running on your machine.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|