@yemi33/squad 0.1.13 → 0.1.15
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/README.md +2 -5
- package/dashboard.html +516 -29
- package/dashboard.js +353 -3
- package/docs/blog-first-successful-dispatch.md +1 -1
- package/engine.js +45 -44
- package/package.json +1 -1
- package/playbooks/explore.md +1 -0
- package/playbooks/plan-to-prd.md +2 -0
package/dashboard.html
CHANGED
|
@@ -47,6 +47,48 @@
|
|
|
47
47
|
.status-badge.working { background: rgba(210,153,34,0.15); color: var(--yellow); border: 1px solid var(--yellow); animation: pulse 1.5s infinite; }
|
|
48
48
|
.status-badge.done { background: rgba(63,185,80,0.15); color: var(--green); border: 1px solid var(--green); }
|
|
49
49
|
.agent-action { font-size: 11px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
|
|
50
|
+
.modal-qa { border-top: 1px solid var(--border); padding: 10px 20px; }
|
|
51
|
+
.modal-qa-thread { max-height: 200px; overflow-y: auto; margin-bottom: 8px; }
|
|
52
|
+
.modal-qa-q { font-size: 12px; color: var(--blue); margin-bottom: 4px; font-weight: 600; }
|
|
53
|
+
.modal-qa-q .selection-ref { font-weight: 400; color: var(--muted); font-style: italic; display: block; font-size: 10px; margin-top: 2px; }
|
|
54
|
+
.modal-qa-a { font-size: 12px; color: var(--text); margin-bottom: 12px; padding: 8px 10px; background: var(--surface2); border-radius: 6px; border-left: 2px solid var(--blue); white-space: pre-wrap; word-break: break-word; line-height: 1.5; }
|
|
55
|
+
.modal-qa-loading { font-size: 11px; color: var(--muted); padding: 8px 10px; display: flex; align-items: center; gap: 8px; }
|
|
56
|
+
.modal-qa-loading .dot-pulse { display: inline-flex; gap: 3px; }
|
|
57
|
+
.modal-qa-loading .dot-pulse span { width: 5px; height: 5px; background: var(--blue); border-radius: 50%; animation: dotPulse 1.2s infinite; }
|
|
58
|
+
.modal-qa-loading .dot-pulse span:nth-child(2) { animation-delay: 0.2s; }
|
|
59
|
+
.modal-qa-loading .dot-pulse span:nth-child(3) { animation-delay: 0.4s; }
|
|
60
|
+
@keyframes dotPulse { 0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); } 40% { opacity: 1; transform: scale(1); } }
|
|
61
|
+
.modal-qa-selection-pill { display: flex; align-items: center; gap: 6px; padding: 4px 8px; margin-bottom: 6px; background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.25); border-radius: 4px; font-size: 11px; }
|
|
62
|
+
.modal-qa-selection-pill .pill-label { color: var(--blue); font-weight: 600; white-space: nowrap; }
|
|
63
|
+
.modal-qa-selection-pill .pill-text { color: var(--muted); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-style: italic; }
|
|
64
|
+
.modal-qa-selection-pill .pill-clear { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 14px; padding: 0 2px; line-height: 1; }
|
|
65
|
+
.modal-qa-selection-pill .pill-clear:hover { color: var(--red); }
|
|
66
|
+
.modal-qa-input-wrap { display: flex; gap: 6px; }
|
|
67
|
+
.modal-qa-input { flex: 1; background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 6px 10px; font-size: 12px; color: var(--text); font-family: inherit; }
|
|
68
|
+
.modal-qa-input:focus { border-color: var(--blue); outline: none; }
|
|
69
|
+
.modal-qa-btn { background: var(--blue); color: #fff; border: none; border-radius: 4px; padding: 6px 14px; font-size: 12px; cursor: pointer; }
|
|
70
|
+
.modal-qa-btn:hover { opacity: 0.9; }
|
|
71
|
+
.modal-qa-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
72
|
+
.ask-selection-btn { display: none; position: fixed; z-index: 500; background: var(--blue); color: #fff; font-size: 11px; padding: 5px 12px; border-radius: 4px; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.3); }
|
|
73
|
+
.ask-selection-btn:hover { opacity: 0.9; }
|
|
74
|
+
.plan-card { background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; padding: 12px; margin-bottom: 8px; }
|
|
75
|
+
.plan-card.awaiting { border-left: 3px solid var(--yellow, #d29922); }
|
|
76
|
+
.plan-card.approved { border-left: 3px solid var(--green); }
|
|
77
|
+
.plan-card.rejected { border-left: 3px solid var(--red); opacity: 0.6; }
|
|
78
|
+
.plan-card.revision-requested { border-left: 3px solid var(--purple, #a855f7); }
|
|
79
|
+
.plan-card-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; }
|
|
80
|
+
.plan-card-title { font-size: 13px; font-weight: 600; color: var(--text); }
|
|
81
|
+
.plan-card-meta { font-size: 10px; color: var(--muted); margin-top: 4px; display: flex; gap: 8px; flex-wrap: wrap; }
|
|
82
|
+
.plan-card-actions { display: flex; gap: 4px; margin-top: 8px; flex-wrap: wrap; }
|
|
83
|
+
.plan-btn { font-size: 11px; padding: 4px 10px; border-radius: 4px; cursor: pointer; border: 1px solid var(--border); background: var(--surface); color: var(--text); transition: all 0.15s; }
|
|
84
|
+
.plan-btn:hover { border-color: var(--text); }
|
|
85
|
+
.plan-btn.approve { color: var(--green); border-color: var(--green); }
|
|
86
|
+
.plan-btn.approve:hover { background: rgba(63,185,80,0.1); }
|
|
87
|
+
.plan-btn.revise { color: var(--yellow, #d29922); border-color: var(--yellow, #d29922); }
|
|
88
|
+
.plan-btn.revise:hover { background: rgba(210,153,34,0.1); }
|
|
89
|
+
.plan-btn.reject { color: var(--red); border-color: var(--red); }
|
|
90
|
+
.plan-btn.reject:hover { background: rgba(248,81,73,0.1); }
|
|
91
|
+
.plan-feedback-input { width: 100%; margin-top: 6px; padding: 6px 8px; font-size: 11px; background: var(--bg); border: 1px solid var(--border); border-radius: 4px; color: var(--text); font-family: inherit; resize: vertical; min-height: 50px; }
|
|
50
92
|
.token-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px; margin-bottom: 12px; }
|
|
51
93
|
.token-tile { background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; padding: 10px 12px; }
|
|
52
94
|
.token-tile-label { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
|
|
@@ -202,7 +244,19 @@
|
|
|
202
244
|
transition: border-color 0.2s, box-shadow 0.2s; padding: 0;
|
|
203
245
|
}
|
|
204
246
|
.cmd-input-wrap:focus-within { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(88,166,255,0.12); }
|
|
247
|
+
.cmd-highlight-layer {
|
|
248
|
+
position: absolute; top: 0; left: 0; right: 0; bottom: 0;
|
|
249
|
+
padding: 14px 16px; font-size: 14px; font-family: inherit; line-height: 1.5;
|
|
250
|
+
white-space: pre-wrap; word-wrap: break-word; overflow: hidden;
|
|
251
|
+
pointer-events: none; color: transparent; border-radius: 10px;
|
|
252
|
+
}
|
|
253
|
+
.cmd-highlight-layer .hl-cmd { color: transparent; background: rgba(88,166,255,0.18); border-radius: 3px; }
|
|
254
|
+
.cmd-highlight-layer .hl-mention { color: transparent; background: rgba(63,185,80,0.18); border-radius: 3px; }
|
|
255
|
+
.cmd-highlight-layer .hl-priority { color: transparent; background: rgba(210,153,34,0.18); border-radius: 3px; }
|
|
256
|
+
.cmd-highlight-layer .hl-project { color: transparent; background: rgba(188,140,255,0.18); border-radius: 3px; }
|
|
257
|
+
.cmd-highlight-layer .hl-flag { color: transparent; background: rgba(248,81,73,0.18); border-radius: 3px; }
|
|
205
258
|
.cmd-input-wrap textarea {
|
|
259
|
+
position: relative; z-index: 1;
|
|
206
260
|
flex: 1; background: transparent; border: none; color: var(--text);
|
|
207
261
|
padding: 14px 16px; font-size: 14px; font-family: inherit; resize: none;
|
|
208
262
|
outline: none; line-height: 1.5; min-height: 48px; max-height: 200px;
|
|
@@ -321,11 +375,15 @@
|
|
|
321
375
|
.dispatch-item { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; font-size: 12px; }
|
|
322
376
|
.dispatch-type { font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 8px; text-transform: uppercase; white-space: nowrap; }
|
|
323
377
|
.dispatch-type.implement { background: rgba(88,166,255,0.15); color: var(--blue); }
|
|
378
|
+
.dispatch-type.implement\:large { background: rgba(88,166,255,0.25); color: var(--blue); }
|
|
324
379
|
.dispatch-type.review { background: rgba(188,140,255,0.15); color: var(--purple); }
|
|
325
380
|
.dispatch-type.fix { background: rgba(210,153,34,0.15); color: var(--yellow); }
|
|
326
381
|
.dispatch-type.analyze { background: rgba(63,185,80,0.15); color: var(--green); }
|
|
327
382
|
.dispatch-type.explore { background: rgba(139,148,158,0.15); color: var(--muted); }
|
|
328
383
|
.dispatch-type.test { background: rgba(227,179,65,0.15); color: var(--orange); }
|
|
384
|
+
.dispatch-type.plan { background: rgba(168,85,247,0.15); color: #a855f7; }
|
|
385
|
+
.dispatch-type.plan-to-prd { background: rgba(168,85,247,0.1); color: #a855f7; }
|
|
386
|
+
.dispatch-type.ask { background: rgba(63,185,80,0.15); color: var(--green); }
|
|
329
387
|
.dispatch-type.manual { background: rgba(139,148,158,0.15); color: var(--muted); }
|
|
330
388
|
.dispatch-agent { font-weight: 600; color: var(--text); }
|
|
331
389
|
.dispatch-task { flex: 1; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
@@ -441,8 +499,9 @@
|
|
|
441
499
|
<section class="cmd-center">
|
|
442
500
|
<h2>Command Center</h2>
|
|
443
501
|
<div class="cmd-input-wrap" id="cmd-input-wrap">
|
|
502
|
+
<div class="cmd-highlight-layer" id="cmd-highlight" aria-hidden="true"></div>
|
|
444
503
|
<textarea id="cmd-input" rows="1" placeholder='What do you need? e.g. "Fix the auth bug @dallas", "explain the dispatch flow", or "/note always use feature flags"'
|
|
445
|
-
oninput="cmdInputChanged()" onkeydown="cmdKeyDown(event)"></textarea>
|
|
504
|
+
oninput="cmdInputChanged()" onkeydown="cmdKeyDown(event)" onscroll="syncHighlightScroll()"></textarea>
|
|
446
505
|
<button class="cmd-send-btn" id="cmd-send-btn" onclick="cmdSubmit()">Send <kbd>Ctrl+Enter</kbd></button>
|
|
447
506
|
</div>
|
|
448
507
|
<div class="cmd-mention-popup" id="cmd-mention-popup"></div>
|
|
@@ -450,10 +509,12 @@
|
|
|
450
509
|
<div class="cmd-hints">
|
|
451
510
|
<span><code>@agent</code> assign</span>
|
|
452
511
|
<span><code>@everyone</code> fan-out</span>
|
|
512
|
+
<span><code>#project</code> target</span>
|
|
453
513
|
<span><code>!high</code> / <code>!low</code> priority</span>
|
|
454
|
-
<span><code>/
|
|
514
|
+
<span><code>/plan</code> feature plan</span>
|
|
455
515
|
<span><code>/prd</code> PRD item</span>
|
|
456
|
-
<span><code
|
|
516
|
+
<span><code>/note</code> team note</span>
|
|
517
|
+
<span>or just type a task</span>
|
|
457
518
|
<button class="cmd-history-btn" onclick="cmdShowHistory()">Past Commands</button>
|
|
458
519
|
</div>
|
|
459
520
|
<div class="cmd-toast" id="cmd-toast"></div>
|
|
@@ -482,7 +543,12 @@
|
|
|
482
543
|
</section>
|
|
483
544
|
|
|
484
545
|
<section>
|
|
485
|
-
<h2>
|
|
546
|
+
<h2>Plans <span class="count" id="plans-count">0</span></h2>
|
|
547
|
+
<div id="plans-list"><p class="empty">No plans yet. Use /plan in the command center to create one.</p></div>
|
|
548
|
+
</section>
|
|
549
|
+
|
|
550
|
+
<section>
|
|
551
|
+
<h2>Notes Inbox <span class="count" id="inbox-count">0</span> <span style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">auto-consolidates at 3 notes</span></h2>
|
|
486
552
|
<div class="inbox-list" id="inbox-list">Loading...</div>
|
|
487
553
|
</section>
|
|
488
554
|
|
|
@@ -502,6 +568,11 @@
|
|
|
502
568
|
<div id="skills-list"><p class="empty">No skills yet. Agents create these when they discover repeatable workflows.</p></div>
|
|
503
569
|
</section>
|
|
504
570
|
|
|
571
|
+
<section>
|
|
572
|
+
<h2>MCP Servers <span class="count" id="mcp-count">0</span></h2>
|
|
573
|
+
<div id="mcp-list"><p class="empty">No MCP servers synced.</p></div>
|
|
574
|
+
</section>
|
|
575
|
+
|
|
505
576
|
<section>
|
|
506
577
|
<h2>Dispatch Queue</h2>
|
|
507
578
|
<div class="dispatch-stats" id="dispatch-stats"></div>
|
|
@@ -555,9 +626,24 @@
|
|
|
555
626
|
</div>
|
|
556
627
|
</div>
|
|
557
628
|
<div class="modal-body" id="modal-body"></div>
|
|
629
|
+
<div class="modal-qa" id="modal-qa">
|
|
630
|
+
<div class="modal-qa-thread" id="modal-qa-thread"></div>
|
|
631
|
+
<div class="modal-qa-selection-pill" id="modal-qa-pill" style="display:none">
|
|
632
|
+
<span class="pill-label">Selection:</span>
|
|
633
|
+
<span class="pill-text" id="modal-qa-pill-text"></span>
|
|
634
|
+
<button class="pill-clear" onclick="clearQaSelection()" title="Clear selection">×</button>
|
|
635
|
+
</div>
|
|
636
|
+
<div class="modal-qa-input-wrap">
|
|
637
|
+
<input type="text" class="modal-qa-input" id="modal-qa-input" placeholder="Ask about this document (or select text first)..." onkeydown="if(event.key==='Enter')modalAskSubmit()">
|
|
638
|
+
<button class="modal-qa-btn" id="modal-qa-btn" onclick="modalAskSubmit()">Ask</button>
|
|
639
|
+
</div>
|
|
640
|
+
</div>
|
|
558
641
|
</div>
|
|
559
642
|
</div>
|
|
560
643
|
|
|
644
|
+
<!-- Floating "Ask about selection" button -->
|
|
645
|
+
<div class="ask-selection-btn" id="ask-selection-btn" onclick="modalAskAboutSelection()">Ask about this</div>
|
|
646
|
+
|
|
561
647
|
<script>
|
|
562
648
|
let inboxData = [];
|
|
563
649
|
let agentData = [];
|
|
@@ -668,7 +754,26 @@ function renderDetailContent(detail, tab) {
|
|
|
668
754
|
} else if (tab === 'charter') {
|
|
669
755
|
el.innerHTML = '<div class="section">' + escHtml(detail.charter || 'No charter found.') + '</div>';
|
|
670
756
|
} else if (tab === 'history') {
|
|
671
|
-
|
|
757
|
+
let html = '';
|
|
758
|
+
// Recent dispatch results
|
|
759
|
+
if (detail.recentDispatches && detail.recentDispatches.length > 0) {
|
|
760
|
+
html += '<h4>Recent Dispatches</h4><table class="pr-table" style="margin-bottom:16px"><thead><tr><th>Task</th><th>Type</th><th>Result</th><th>Completed</th></tr></thead><tbody>';
|
|
761
|
+
detail.recentDispatches.forEach(d => {
|
|
762
|
+
const isError = d.result === 'error';
|
|
763
|
+
const color = isError ? 'var(--red)' : 'var(--green)';
|
|
764
|
+
const reason = d.reason ? ' title="' + escHtml(d.reason) + '"' : '';
|
|
765
|
+
html += '<tr>' +
|
|
766
|
+
'<td style="max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(d.task) + '">' + escHtml(d.task.slice(0, 80)) + '</td>' +
|
|
767
|
+
'<td><span class="dispatch-type ' + d.type + '">' + escHtml(d.type) + '</span></td>' +
|
|
768
|
+
'<td style="color:' + color + '"' + reason + '>' + escHtml(d.result) + (isError && d.reason ? ' <span style="font-size:10px;color:var(--muted)">(' + escHtml(d.reason.slice(0, 50)) + ')</span>' : '') + '</td>' +
|
|
769
|
+
'<td style="font-size:10px;color:var(--muted)">' + (d.completed_at ? new Date(d.completed_at).toLocaleString() : '') + '</td>' +
|
|
770
|
+
'</tr>';
|
|
771
|
+
});
|
|
772
|
+
html += '</tbody></table>';
|
|
773
|
+
}
|
|
774
|
+
// Raw history.md
|
|
775
|
+
html += '<h4>Task History</h4><div class="section">' + escHtml(detail.history || 'No history yet.') + '</div>';
|
|
776
|
+
el.innerHTML = html;
|
|
672
777
|
} else if (tab === 'output') {
|
|
673
778
|
el.innerHTML = '<div class="section">' + escHtml(detail.outputLog || 'No output log. The coordinator will save agent output here when tasks complete.') + '</div>';
|
|
674
779
|
}
|
|
@@ -777,23 +882,45 @@ function renderInbox(inbox) {
|
|
|
777
882
|
<span>${escHtml(item.name)}</span><span>${item.age}</span>
|
|
778
883
|
</div>
|
|
779
884
|
<div class="inbox-preview" onclick="openModal(${i})" style="cursor:pointer">${escHtml(item.content.slice(0,200))}</div>
|
|
780
|
-
<div style="display:flex;gap:6px;margin-top:6px">
|
|
781
|
-
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="event.stopPropagation();
|
|
885
|
+
<div style="display:flex;gap:6px;margin-top:6px;align-items:center">
|
|
886
|
+
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="event.stopPropagation();promoteToKB('${escHtml(item.name)}')">Add to Knowledge Base</button>
|
|
782
887
|
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="event.stopPropagation();openInboxInExplorer('${escHtml(item.name)}')">Open in Explorer</button>
|
|
783
888
|
</div>
|
|
784
889
|
</div>
|
|
785
890
|
`).join('');
|
|
786
891
|
}
|
|
787
892
|
|
|
788
|
-
|
|
893
|
+
function promoteToKB(name) {
|
|
894
|
+
const categories = [
|
|
895
|
+
{ id: 'architecture', label: 'Architecture' },
|
|
896
|
+
{ id: 'conventions', label: 'Conventions' },
|
|
897
|
+
{ id: 'project-notes', label: 'Project Notes' },
|
|
898
|
+
{ id: 'build-reports', label: 'Build Reports' },
|
|
899
|
+
{ id: 'reviews', label: 'Reviews' },
|
|
900
|
+
];
|
|
901
|
+
const picker = '<div style="padding:16px 20px">' +
|
|
902
|
+
'<p style="font-size:13px;color:var(--text);margin-bottom:12px">Choose a category for <strong>' + escHtml(name) + '</strong>:</p>' +
|
|
903
|
+
'<div style="display:flex;flex-direction:column;gap:8px">' +
|
|
904
|
+
categories.map(c =>
|
|
905
|
+
'<button class="pr-pager-btn" style="font-size:12px;padding:8px 16px;text-align:left" onclick="doPromoteToKB(\'' + escHtml(name) + '\',\'' + c.id + '\')">' + c.label + '</button>'
|
|
906
|
+
).join('') +
|
|
907
|
+
'</div></div>';
|
|
908
|
+
document.getElementById('modal-title').textContent = 'Add to Knowledge Base';
|
|
909
|
+
document.getElementById('modal-body').innerHTML = picker;
|
|
910
|
+
document.getElementById('modal').classList.add('open');
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
async function doPromoteToKB(name, category) {
|
|
789
914
|
try {
|
|
790
|
-
const res = await fetch('/api/inbox/
|
|
915
|
+
const res = await fetch('/api/inbox/promote-kb', {
|
|
791
916
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
792
|
-
body: JSON.stringify({ name })
|
|
917
|
+
body: JSON.stringify({ name, category })
|
|
793
918
|
});
|
|
794
919
|
const data = await res.json();
|
|
795
920
|
if (res.ok) {
|
|
921
|
+
closeModal();
|
|
796
922
|
refresh();
|
|
923
|
+
refreshKnowledgeBase();
|
|
797
924
|
} else {
|
|
798
925
|
alert('Failed: ' + (data.error || 'unknown'));
|
|
799
926
|
}
|
|
@@ -929,10 +1056,21 @@ function openModal(i) {
|
|
|
929
1056
|
const item = inboxData[i];
|
|
930
1057
|
if (!item) return;
|
|
931
1058
|
document.getElementById('modal-title').textContent = item.name;
|
|
932
|
-
document.getElementById('modal-body').
|
|
1059
|
+
document.getElementById('modal-body').innerHTML =
|
|
1060
|
+
'<div style="margin-bottom:12px"><button class="pr-pager-btn" style="font-size:10px;padding:3px 10px" onclick="promoteToKB(\'' + escHtml(item.name) + '\')">Add to Knowledge Base</button></div>' +
|
|
1061
|
+
'<pre style="white-space:pre-wrap;word-wrap:break-word;margin:0;font-family:Consolas,monospace;font-size:12px;line-height:1.7;color:var(--muted)">' + escHtml(item.content) + '</pre>';
|
|
933
1062
|
document.getElementById('modal').classList.add('open');
|
|
934
1063
|
}
|
|
935
|
-
function closeModal() {
|
|
1064
|
+
function closeModal() {
|
|
1065
|
+
document.getElementById('modal').classList.remove('open');
|
|
1066
|
+
// Clear Q&A state
|
|
1067
|
+
_modalDocContext = { title: '', content: '', selection: '' };
|
|
1068
|
+
document.getElementById('modal-qa-thread').innerHTML = '';
|
|
1069
|
+
document.getElementById('modal-qa-input').value = '';
|
|
1070
|
+
document.getElementById('modal-qa-input').placeholder = 'Ask about this document (or select text first)...';
|
|
1071
|
+
document.getElementById('modal-qa-pill').style.display = 'none';
|
|
1072
|
+
document.getElementById('ask-selection-btn').style.display = 'none';
|
|
1073
|
+
}
|
|
936
1074
|
|
|
937
1075
|
document.addEventListener('keydown', e => {
|
|
938
1076
|
if (e.key === 'Escape') { closeDetail(); closeModal(); }
|
|
@@ -1173,9 +1311,10 @@ async function refresh() {
|
|
|
1173
1311
|
renderMetrics(data.metrics || {});
|
|
1174
1312
|
renderWorkItems(data.workItems || []);
|
|
1175
1313
|
renderSkills(data.skills || []);
|
|
1176
|
-
|
|
1314
|
+
renderMcpServers(data.mcpServers || []);
|
|
1315
|
+
// Refresh KB and plans less frequently (every 3rd cycle = ~12s)
|
|
1177
1316
|
if (!window._kbRefreshCount) window._kbRefreshCount = 0;
|
|
1178
|
-
if (window._kbRefreshCount++ % 3 === 0) refreshKnowledgeBase();
|
|
1317
|
+
if (window._kbRefreshCount++ % 3 === 0) { refreshKnowledgeBase(); refreshPlans(); }
|
|
1179
1318
|
} catch(e) { console.error('refresh error', e); }
|
|
1180
1319
|
}
|
|
1181
1320
|
|
|
@@ -1201,6 +1340,25 @@ function renderProjects(projects) {
|
|
|
1201
1340
|
|
|
1202
1341
|
}
|
|
1203
1342
|
|
|
1343
|
+
// -- MCP Servers --
|
|
1344
|
+
function renderMcpServers(servers) {
|
|
1345
|
+
const el = document.getElementById('mcp-list');
|
|
1346
|
+
const countEl = document.getElementById('mcp-count');
|
|
1347
|
+
countEl.textContent = servers.length;
|
|
1348
|
+
if (!servers.length) {
|
|
1349
|
+
el.innerHTML = '<p class="empty">No MCP servers found. Add them to <code>~/.claude.json</code> and they\'ll appear here automatically.</p>';
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
el.innerHTML = '<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px">' +
|
|
1353
|
+
servers.map(s =>
|
|
1354
|
+
'<div style="font-size:11px;padding:5px 10px;background:var(--surface2);border:1px solid var(--border);border-radius:6px;color:var(--text)" title="' + escHtml(s.args || s.command) + '">' +
|
|
1355
|
+
escHtml(s.name) +
|
|
1356
|
+
'</div>'
|
|
1357
|
+
).join('') +
|
|
1358
|
+
'</div>' +
|
|
1359
|
+
'<p style="font-size:10px;color:var(--muted);margin:0">Synced from <code style="color:var(--blue)">~/.claude.json</code> — add MCP servers there to make them available to all agents.</p>';
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1204
1362
|
// -- Squad Skills --
|
|
1205
1363
|
function renderSkills(skills) {
|
|
1206
1364
|
const el = document.getElementById('skills-list');
|
|
@@ -1526,7 +1684,7 @@ function detectWorkItemType(text) {
|
|
|
1526
1684
|
const t = text.toLowerCase();
|
|
1527
1685
|
const patterns = [
|
|
1528
1686
|
{ type: 'ask', words: ['explain', 'why does', 'why is', 'what does', 'how do i', 'how do you', 'what\'s the', 'tell me', 'can you explain', 'walk me through'] },
|
|
1529
|
-
{ type: 'explore', words: ['explore', 'investigate', 'understand', 'analyze', 'audit', 'document', 'architecture', 'how does', 'what is', 'look into', 'research', 'survey', 'map out', 'codebase'] },
|
|
1687
|
+
{ type: 'explore', words: ['explore', 'investigate', 'understand', 'analyze', 'audit', 'document', 'architecture', 'how does', 'what is', 'look into', 'research', 'survey', 'map out', 'codebase', 'make a note of', 'find out'] },
|
|
1530
1688
|
{ type: 'fix', words: ['fix', 'bug', 'broken', 'crash', 'error', 'issue', 'patch', 'repair', 'resolve', 'regression', 'failing', 'doesn\'t work', 'not working'] },
|
|
1531
1689
|
{ type: 'review', words: ['review', 'code review', 'check pr', 'look at pr', 'audit code', 'inspect'] },
|
|
1532
1690
|
{ type: 'test', words: ['test', 'write tests', 'add tests', 'unit test', 'e2e test', 'coverage', 'testing', 'build', 'run locally', 'localhost', 'start the', 'spin up', 'verify', 'check if it works'] },
|
|
@@ -1557,18 +1715,18 @@ function cmdParseInput(raw) {
|
|
|
1557
1715
|
if (/^\/decide\b/i.test(text) || /^\/note\b/i.test(text) || rememberPattern.test(text)) {
|
|
1558
1716
|
result.intent = 'note';
|
|
1559
1717
|
text = text.replace(/^\/decide\s*/i, '').replace(/^\/note\s*/i, '').replace(rememberPattern, '').trim();
|
|
1560
|
-
} else if (/^\/plan\b/i.test(text)) {
|
|
1718
|
+
} else if (/^\/plan\b/i.test(text) || /^(make a plan|plan out|plan for|plan how|create a plan|design a plan|come up with a plan|draft a plan|write a plan)\b/i.test(text)) {
|
|
1561
1719
|
result.intent = 'plan';
|
|
1562
|
-
text = text.replace(/^\/plan\s*/i, '');
|
|
1720
|
+
text = text.replace(/^\/plan\s*/i, '').replace(/^(make a plan for|plan out how|plan for how|plan how|create a plan for|design a plan for|come up with a plan for|draft a plan for|write a plan for|make a plan|plan out|create a plan|design a plan|come up with a plan|draft a plan|write a plan)\s*/i, '').trim();
|
|
1563
1721
|
// Extract branch strategy flag
|
|
1564
1722
|
if (/--parallel\b/i.test(text)) {
|
|
1565
1723
|
result.branchStrategy = 'parallel';
|
|
1566
1724
|
text = text.replace(/--parallel\b/i, '').trim();
|
|
1567
|
-
} else if (/--
|
|
1725
|
+
} else if (/--stack\b/i.test(text)) {
|
|
1568
1726
|
result.branchStrategy = 'shared-branch';
|
|
1569
|
-
text = text.replace(/--
|
|
1727
|
+
text = text.replace(/--stack\b/i, '').trim();
|
|
1570
1728
|
} else {
|
|
1571
|
-
result.branchStrategy = '
|
|
1729
|
+
result.branchStrategy = 'parallel'; // default — items without depends_on get independent branches
|
|
1572
1730
|
}
|
|
1573
1731
|
} else if (/^\/prd\b/i.test(text)) {
|
|
1574
1732
|
result.intent = 'prd';
|
|
@@ -1631,19 +1789,19 @@ function cmdRenderMeta() {
|
|
|
1631
1789
|
let chips = [];
|
|
1632
1790
|
|
|
1633
1791
|
// Intent chip
|
|
1634
|
-
|
|
1635
|
-
|
|
1792
|
+
if (parsed.intent === 'plan') {
|
|
1793
|
+
const strategy = parsed.branchStrategy || 'parallel';
|
|
1794
|
+
const stratLabel = strategy === 'parallel' ? 'parallel branches' : 'stacked';
|
|
1795
|
+
chips.push('<span class="cmd-chip" style="background:var(--purple,#a855f7);color:#fff">Plan → PRD → Dispatch (' + stratLabel + ')</span>');
|
|
1796
|
+
} else {
|
|
1797
|
+
const intentLabels = { 'work-item': 'Work Item', 'note': 'Note', 'prd': 'PRD Item' };
|
|
1798
|
+
chips.push('<span class="cmd-chip intent">' + intentLabels[parsed.intent] + '</span>');
|
|
1799
|
+
}
|
|
1636
1800
|
|
|
1637
1801
|
// Type chip (only for work items)
|
|
1638
1802
|
if (parsed.intent === 'work-item') {
|
|
1639
1803
|
chips.push('<span class="cmd-chip">' + parsed.type + '</span>');
|
|
1640
1804
|
}
|
|
1641
|
-
// Pipeline chip for /plan
|
|
1642
|
-
if (parsed.intent === 'plan') {
|
|
1643
|
-
const strategy = parsed.branchStrategy || 'shared-branch';
|
|
1644
|
-
const stratLabel = strategy === 'parallel' ? 'parallel branches' : 'shared branch';
|
|
1645
|
-
chips.push('<span class="cmd-chip" style="background:var(--purple,#a855f7);color:#fff">plan → prd → agents (' + stratLabel + ')</span>');
|
|
1646
|
-
}
|
|
1647
1805
|
|
|
1648
1806
|
// Priority chip
|
|
1649
1807
|
chips.push('<span class="cmd-chip priority-' + parsed.priority + '">' + parsed.priority + ' priority</span>');
|
|
@@ -1751,8 +1909,30 @@ function cmdAutoResize() {
|
|
|
1751
1909
|
ta.style.height = Math.min(ta.scrollHeight, 200) + 'px';
|
|
1752
1910
|
}
|
|
1753
1911
|
|
|
1912
|
+
function cmdUpdateHighlight() {
|
|
1913
|
+
const text = document.getElementById('cmd-input').value;
|
|
1914
|
+
const hl = document.getElementById('cmd-highlight');
|
|
1915
|
+
if (!text) { hl.innerHTML = ''; return; }
|
|
1916
|
+
// Escape HTML then wrap tokens with highlight spans
|
|
1917
|
+
let html = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1918
|
+
html = html.replace(/(\/(?:plan|prd|note|decide)\b)/gi, '<span class="hl-cmd">$1</span>');
|
|
1919
|
+
html = html.replace(/(@\w+)/g, '<span class="hl-mention">$1</span>');
|
|
1920
|
+
html = html.replace(/(![a-z]+\b)/gi, '<span class="hl-priority">$1</span>');
|
|
1921
|
+
html = html.replace(/(#\S+)/g, '<span class="hl-project">$1</span>');
|
|
1922
|
+
html = html.replace(/(--(?:stack|parallel)\b)/gi, '<span class="hl-flag">$1</span>');
|
|
1923
|
+
hl.innerHTML = html + '\n'; // trailing newline prevents layout shift
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
function syncHighlightScroll() {
|
|
1927
|
+
const ta = document.getElementById('cmd-input');
|
|
1928
|
+
const hl = document.getElementById('cmd-highlight');
|
|
1929
|
+
hl.scrollTop = ta.scrollTop;
|
|
1930
|
+
hl.scrollLeft = ta.scrollLeft;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1754
1933
|
function cmdInputChanged() {
|
|
1755
1934
|
cmdAutoResize();
|
|
1935
|
+
cmdUpdateHighlight();
|
|
1756
1936
|
cmdRenderMeta();
|
|
1757
1937
|
|
|
1758
1938
|
// Check for @ mention or # project trigger
|
|
@@ -1933,7 +2113,7 @@ async function cmdSubmitPlan(parsed) {
|
|
|
1933
2113
|
title: parsed.title,
|
|
1934
2114
|
description: parsed.description || '',
|
|
1935
2115
|
priority: parsed.priority,
|
|
1936
|
-
branch_strategy: parsed.branchStrategy || '
|
|
2116
|
+
branch_strategy: parsed.branchStrategy || 'parallel',
|
|
1937
2117
|
};
|
|
1938
2118
|
if (parsed.project) body.project = parsed.project;
|
|
1939
2119
|
if (parsed.agents.length === 1) body.agent = parsed.agents[0];
|
|
@@ -1968,6 +2148,313 @@ async function cmdSubmitPrd(parsed) {
|
|
|
1968
2148
|
const projLabel = (parsed.projects || []).length > 0 ? ' (' + parsed.projects.join(', ') + ')' : '';
|
|
1969
2149
|
showToast('cmd-toast', 'PRD item ' + (data.id || id) + ' added' + projLabel, true);
|
|
1970
2150
|
}
|
|
2151
|
+
// ─── Modal Q&A (Ask about document) ──────────────────────────────────────────
|
|
2152
|
+
|
|
2153
|
+
let _modalDocContext = { title: '', content: '', selection: '' };
|
|
2154
|
+
|
|
2155
|
+
// Track text selection anywhere in content areas for the floating "Ask about this" button
|
|
2156
|
+
document.addEventListener('mouseup', function(e) {
|
|
2157
|
+
// Small delay to let the selection finalize
|
|
2158
|
+
setTimeout(() => {
|
|
2159
|
+
const btn = document.getElementById('ask-selection-btn');
|
|
2160
|
+
if (!btn) return;
|
|
2161
|
+
const sel = window.getSelection();
|
|
2162
|
+
const text = sel?.toString()?.trim();
|
|
2163
|
+
|
|
2164
|
+
if (!text || text.length < 3) {
|
|
2165
|
+
btn.style.display = 'none';
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
// Check if selection is in any content area: modal body, detail panel, notes, kb
|
|
2170
|
+
const anchor = sel?.anchorNode;
|
|
2171
|
+
if (!anchor) { btn.style.display = 'none'; return; }
|
|
2172
|
+
|
|
2173
|
+
const modalBody = document.getElementById('modal-body');
|
|
2174
|
+
const detailContent = document.getElementById('detail-content');
|
|
2175
|
+
const isInModal = modalBody?.contains(anchor);
|
|
2176
|
+
const isInDetail = detailContent?.contains(anchor);
|
|
2177
|
+
|
|
2178
|
+
if (isInModal || isInDetail) {
|
|
2179
|
+
_modalDocContext.selection = text;
|
|
2180
|
+
// If we don't have document context yet (e.g. detail panel), capture it
|
|
2181
|
+
if (!_modalDocContext.content && isInDetail) {
|
|
2182
|
+
_modalDocContext.content = detailContent.textContent || '';
|
|
2183
|
+
_modalDocContext.title = document.getElementById('detail-agent-name')?.textContent || 'Agent Detail';
|
|
2184
|
+
}
|
|
2185
|
+
btn.style.display = 'block';
|
|
2186
|
+
// Position near the cursor but within viewport
|
|
2187
|
+
const x = Math.min(e.clientX, window.innerWidth - 140);
|
|
2188
|
+
const y = Math.max(e.clientY - 35, 10);
|
|
2189
|
+
btn.style.left = x + 'px';
|
|
2190
|
+
btn.style.top = y + 'px';
|
|
2191
|
+
btn.style.position = 'fixed';
|
|
2192
|
+
} else {
|
|
2193
|
+
btn.style.display = 'none';
|
|
2194
|
+
}
|
|
2195
|
+
}, 10);
|
|
2196
|
+
});
|
|
2197
|
+
|
|
2198
|
+
function modalAskAboutSelection() {
|
|
2199
|
+
document.getElementById('ask-selection-btn').style.display = 'none';
|
|
2200
|
+
|
|
2201
|
+
// If the modal isn't open but we have a selection (from detail panel), open modal for Q&A
|
|
2202
|
+
const modal = document.getElementById('modal');
|
|
2203
|
+
if (!modal.classList.contains('open')) {
|
|
2204
|
+
document.getElementById('modal-title').textContent = 'Q&A: ' + (_modalDocContext.title || 'Document');
|
|
2205
|
+
document.getElementById('modal-body').textContent = _modalDocContext.content.slice(0, 3000) + (_modalDocContext.content.length > 3000 ? '\n\n...(truncated for display)' : '');
|
|
2206
|
+
modal.classList.add('open');
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
// Show the selection pill
|
|
2210
|
+
const pill = document.getElementById('modal-qa-pill');
|
|
2211
|
+
const pillText = document.getElementById('modal-qa-pill-text');
|
|
2212
|
+
const sel = _modalDocContext.selection || '';
|
|
2213
|
+
if (sel) {
|
|
2214
|
+
pillText.textContent = sel.slice(0, 80) + (sel.length > 80 ? '...' : '');
|
|
2215
|
+
pill.style.display = 'flex';
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
const input = document.getElementById('modal-qa-input');
|
|
2219
|
+
input.value = '';
|
|
2220
|
+
input.placeholder = 'What do you want to know about this?';
|
|
2221
|
+
input.focus();
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
function clearQaSelection() {
|
|
2225
|
+
_modalDocContext.selection = '';
|
|
2226
|
+
document.getElementById('modal-qa-pill').style.display = 'none';
|
|
2227
|
+
document.getElementById('modal-qa-input').placeholder = 'Ask about this document (or select text first)...';
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
async function modalAskSubmit() {
|
|
2231
|
+
const input = document.getElementById('modal-qa-input');
|
|
2232
|
+
const question = input.value.trim();
|
|
2233
|
+
if (!question) return;
|
|
2234
|
+
|
|
2235
|
+
// Capture content from modal body if not already set
|
|
2236
|
+
if (!_modalDocContext.content) {
|
|
2237
|
+
const body = document.getElementById('modal-body');
|
|
2238
|
+
if (body) {
|
|
2239
|
+
_modalDocContext.content = body.textContent || body.innerText || '';
|
|
2240
|
+
_modalDocContext.title = document.getElementById('modal-title')?.textContent || '';
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
if (!_modalDocContext.content) {
|
|
2244
|
+
showToast('cmd-toast', 'No document content to ask about', false);
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
const thread = document.getElementById('modal-qa-thread');
|
|
2249
|
+
const btn = document.getElementById('modal-qa-btn');
|
|
2250
|
+
|
|
2251
|
+
// Show question
|
|
2252
|
+
let qHtml = '<div class="modal-qa-q">' + escHtml(question);
|
|
2253
|
+
if (_modalDocContext.selection) {
|
|
2254
|
+
qHtml += '<span class="selection-ref">Re: "' + escHtml(_modalDocContext.selection.slice(0, 100)) + ((_modalDocContext.selection.length > 100) ? '...' : '') + '"</span>';
|
|
2255
|
+
}
|
|
2256
|
+
qHtml += '</div>';
|
|
2257
|
+
thread.innerHTML += qHtml;
|
|
2258
|
+
|
|
2259
|
+
// Show loading
|
|
2260
|
+
const loadingId = 'qa-loading-' + Date.now();
|
|
2261
|
+
thread.innerHTML += '<div class="modal-qa-loading" id="' + loadingId + '"><div class="dot-pulse"><span></span><span></span><span></span></div> Thinking...</div>';
|
|
2262
|
+
thread.scrollTop = thread.scrollHeight;
|
|
2263
|
+
|
|
2264
|
+
input.value = '';
|
|
2265
|
+
input.placeholder = 'Ask another question...';
|
|
2266
|
+
btn.disabled = true;
|
|
2267
|
+
|
|
2268
|
+
try {
|
|
2269
|
+
const res = await fetch('/api/ask-about', {
|
|
2270
|
+
method: 'POST',
|
|
2271
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2272
|
+
body: JSON.stringify({
|
|
2273
|
+
question,
|
|
2274
|
+
document: _modalDocContext.content,
|
|
2275
|
+
title: _modalDocContext.title,
|
|
2276
|
+
selection: _modalDocContext.selection || '',
|
|
2277
|
+
}),
|
|
2278
|
+
});
|
|
2279
|
+
const data = await res.json();
|
|
2280
|
+
const loadingEl = document.getElementById(loadingId);
|
|
2281
|
+
if (loadingEl) loadingEl.remove();
|
|
2282
|
+
|
|
2283
|
+
if (data.ok && data.answer) {
|
|
2284
|
+
thread.innerHTML += '<div class="modal-qa-a">' + escHtml(data.answer) + '</div>';
|
|
2285
|
+
} else {
|
|
2286
|
+
thread.innerHTML += '<div class="modal-qa-a" style="color:var(--red)">Error: ' + escHtml(data.error || 'No answer') + '</div>';
|
|
2287
|
+
}
|
|
2288
|
+
} catch (e) {
|
|
2289
|
+
const loadingEl = document.getElementById(loadingId);
|
|
2290
|
+
if (loadingEl) loadingEl.remove();
|
|
2291
|
+
thread.innerHTML += '<div class="modal-qa-a" style="color:var(--red)">Error: ' + escHtml(e.message) + '</div>';
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
_modalDocContext.selection = ''; // Clear selection after asking
|
|
2295
|
+
document.getElementById('modal-qa-pill').style.display = 'none';
|
|
2296
|
+
document.getElementById('modal-qa-input').placeholder = 'Ask another question...';
|
|
2297
|
+
btn.disabled = false;
|
|
2298
|
+
thread.scrollTop = thread.scrollHeight;
|
|
2299
|
+
input.focus();
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
// Override closeModal to clear Q&A state
|
|
2303
|
+
const _origCloseModal = typeof closeModal === 'function' ? closeModal : null;
|
|
2304
|
+
|
|
2305
|
+
// ─── Plans (Approval Gate) ────────────────────────────────────────────────────
|
|
2306
|
+
|
|
2307
|
+
async function refreshPlans() {
|
|
2308
|
+
try {
|
|
2309
|
+
const plans = await fetch('/api/plans').then(r => r.json());
|
|
2310
|
+
renderPlans(plans);
|
|
2311
|
+
} catch {}
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
function renderPlans(plans) {
|
|
2315
|
+
const el = document.getElementById('plans-list');
|
|
2316
|
+
const countEl = document.getElementById('plans-count');
|
|
2317
|
+
countEl.textContent = plans.length;
|
|
2318
|
+
|
|
2319
|
+
if (plans.length === 0) {
|
|
2320
|
+
el.innerHTML = '<p class="empty">No plans yet. Use /plan in the command center to create one.</p>';
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
const statusLabels = { 'awaiting-approval': 'Awaiting Approval', 'approved': 'Approved', 'rejected': 'Rejected', 'revision-requested': 'Revision Requested', 'completed': 'Completed', 'active': 'Active' };
|
|
2325
|
+
const statusClass = (s) => s === 'awaiting-approval' ? 'awaiting' : s || '';
|
|
2326
|
+
|
|
2327
|
+
el.innerHTML = plans.map(p => {
|
|
2328
|
+
const status = p.status || 'active';
|
|
2329
|
+
const label = statusLabels[status] || status;
|
|
2330
|
+
const needsAction = status === 'awaiting-approval';
|
|
2331
|
+
const isRevision = status === 'revision-requested';
|
|
2332
|
+
|
|
2333
|
+
let actions = '';
|
|
2334
|
+
if (needsAction) {
|
|
2335
|
+
actions = '<div class="plan-card-actions">' +
|
|
2336
|
+
'<button class="plan-btn approve" onclick="planApprove(\'' + escHtml(p.file) + '\')">Approve</button>' +
|
|
2337
|
+
'<button class="plan-btn" style="color:var(--blue);border-color:var(--blue)" onclick="planDiscuss(\'' + escHtml(p.file) + '\')">Discuss & Revise</button>' +
|
|
2338
|
+
'<button class="plan-btn reject" onclick="planReject(\'' + escHtml(p.file) + '\')">Reject</button>' +
|
|
2339
|
+
'<button class="plan-btn" onclick="planView(\'' + escHtml(p.file) + '\')">View Full Plan</button>' +
|
|
2340
|
+
'</div>' +
|
|
2341
|
+
'<div id="revise-input-' + escHtml(p.file).replace(/\./g, '-') + '" style="display:none">' +
|
|
2342
|
+
'<textarea class="plan-feedback-input" placeholder="What should be changed? Be specific..." id="revise-feedback-' + escHtml(p.file).replace(/\./g, '-') + '"></textarea>' +
|
|
2343
|
+
'<div class="plan-card-actions" style="margin-top:4px">' +
|
|
2344
|
+
'<button class="plan-btn revise" onclick="planSubmitRevise(\'' + escHtml(p.file) + '\')">Submit Revision Request</button>' +
|
|
2345
|
+
'<button class="plan-btn" onclick="planHideRevise(\'' + escHtml(p.file) + '\')">Cancel</button>' +
|
|
2346
|
+
'</div>' +
|
|
2347
|
+
'</div>';
|
|
2348
|
+
} else if (isRevision) {
|
|
2349
|
+
actions = '<div class="plan-card-meta" style="margin-top:6px;color:var(--purple,#a855f7)">Revision in progress: ' + escHtml((p.revisionFeedback || '').slice(0, 100)) + '</div>';
|
|
2350
|
+
} else if (status === 'approved' || status === 'active') {
|
|
2351
|
+
actions = '<div class="plan-card-actions"><button class="plan-btn" onclick="planView(\'' + escHtml(p.file) + '\')">View</button></div>';
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
return '<div class="plan-card ' + statusClass(status) + '">' +
|
|
2355
|
+
'<div class="plan-card-header">' +
|
|
2356
|
+
'<div><div class="plan-card-title">' + escHtml(p.summary || p.file) + '</div>' +
|
|
2357
|
+
'<div class="plan-card-meta">' +
|
|
2358
|
+
'<span style="font-weight:600;color:' + (needsAction ? 'var(--yellow,#d29922)' : status === 'approved' ? 'var(--green)' : 'var(--muted)') + '">' + label + '</span>' +
|
|
2359
|
+
'<span>' + escHtml(p.project) + '</span>' +
|
|
2360
|
+
'<span>' + p.itemCount + ' items</span>' +
|
|
2361
|
+
'<span>' + escHtml(p.branchStrategy) + '</span>' +
|
|
2362
|
+
(p.generatedBy ? '<span>by ' + escHtml(p.generatedBy) + '</span>' : '') +
|
|
2363
|
+
(p.generatedAt ? '<span>' + p.generatedAt + '</span>' : '') +
|
|
2364
|
+
'</div>' +
|
|
2365
|
+
'</div>' +
|
|
2366
|
+
'</div>' +
|
|
2367
|
+
actions +
|
|
2368
|
+
'</div>';
|
|
2369
|
+
}).join('');
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
async function planApprove(file) {
|
|
2373
|
+
try {
|
|
2374
|
+
await fetch('/api/plans/approve', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ file }) });
|
|
2375
|
+
showToast('cmd-toast', 'Plan approved — work will begin on next engine tick', true);
|
|
2376
|
+
refreshPlans();
|
|
2377
|
+
} catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); }
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
async function planReject(file) {
|
|
2381
|
+
if (!confirm('Reject this plan? It will not be executed.')) return;
|
|
2382
|
+
const reason = prompt('Reason for rejection (optional):') || '';
|
|
2383
|
+
try {
|
|
2384
|
+
await fetch('/api/plans/reject', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ file, reason }) });
|
|
2385
|
+
showToast('cmd-toast', 'Plan rejected', true);
|
|
2386
|
+
refreshPlans();
|
|
2387
|
+
} catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); }
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
function planShowRevise(file) {
|
|
2391
|
+
const id = 'revise-input-' + file.replace(/\./g, '-');
|
|
2392
|
+
document.getElementById(id).style.display = 'block';
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
function planHideRevise(file) {
|
|
2396
|
+
const id = 'revise-input-' + file.replace(/\./g, '-');
|
|
2397
|
+
document.getElementById(id).style.display = 'none';
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
async function planSubmitRevise(file) {
|
|
2401
|
+
const id = 'revise-feedback-' + file.replace(/\./g, '-');
|
|
2402
|
+
const feedback = document.getElementById(id).value.trim();
|
|
2403
|
+
if (!feedback) { showToast('cmd-toast', 'Please enter feedback', false); return; }
|
|
2404
|
+
try {
|
|
2405
|
+
const res = await fetch('/api/plans/revise', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ file, feedback }) });
|
|
2406
|
+
const data = await res.json();
|
|
2407
|
+
showToast('cmd-toast', 'Revision requested — agent will update the plan (' + data.workItemId + ')', true);
|
|
2408
|
+
planHideRevise(file);
|
|
2409
|
+
refreshPlans();
|
|
2410
|
+
} catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); }
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
async function planDiscuss(file) {
|
|
2414
|
+
try {
|
|
2415
|
+
const res = await fetch('/api/plans/discuss', {
|
|
2416
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
2417
|
+
body: JSON.stringify({ file })
|
|
2418
|
+
});
|
|
2419
|
+
const data = await res.json();
|
|
2420
|
+
if (!res.ok) throw new Error(data.error);
|
|
2421
|
+
|
|
2422
|
+
// Show the launch command in a modal
|
|
2423
|
+
const content = `To discuss and revise this plan interactively, run this command in a terminal:\n\n` +
|
|
2424
|
+
`━━━ Bash / Git Bash ━━━\n${data.command}\n\n` +
|
|
2425
|
+
`━━━ PowerShell ━━━\n${data.psCommand}\n\n` +
|
|
2426
|
+
`━━━━━━━━━━━━━━━━━━━━━━━\n\n` +
|
|
2427
|
+
`This launches an interactive Claude session with the plan pre-loaded.\n` +
|
|
2428
|
+
`Chat naturally to review and refine. When you're satisfied, say "approve" and the session will write the approved plan back to disk.\n\n` +
|
|
2429
|
+
`The engine will pick it up on the next tick and start dispatching work.`;
|
|
2430
|
+
|
|
2431
|
+
document.getElementById('modal-title').textContent = 'Discuss Plan: ' + file;
|
|
2432
|
+
document.getElementById('modal-body').textContent = content;
|
|
2433
|
+
document.getElementById('modal').classList.add('open');
|
|
2434
|
+
} catch (e) {
|
|
2435
|
+
showToast('cmd-toast', 'Error: ' + e.message, false);
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
async function planView(file) {
|
|
2440
|
+
try {
|
|
2441
|
+
const plan = await fetch('/api/plans/' + encodeURIComponent(file)).then(r => r.json());
|
|
2442
|
+
const items = (plan.missing_features || []).map((f, i) =>
|
|
2443
|
+
(i + 1) + '. [' + f.id + '] ' + f.name + ' (' + f.estimated_complexity + ', ' + f.priority + ')' +
|
|
2444
|
+
(f.depends_on?.length ? ' → depends on: ' + f.depends_on.join(', ') : '') +
|
|
2445
|
+
'\n ' + (f.description || '').slice(0, 150)
|
|
2446
|
+
).join('\n\n');
|
|
2447
|
+
const text = 'Project: ' + plan.project + '\nStrategy: ' + (plan.branch_strategy || 'parallel') +
|
|
2448
|
+
'\nBranch: ' + (plan.feature_branch || 'per-item') +
|
|
2449
|
+
'\nStatus: ' + (plan.status || 'active') +
|
|
2450
|
+
'\n\n--- Items ---\n\n' + items +
|
|
2451
|
+
(plan.open_questions?.length ? '\n\n--- Open Questions ---\n\n' + plan.open_questions.join('\n') : '');
|
|
2452
|
+
document.getElementById('modal-title').textContent = plan.plan_summary || file;
|
|
2453
|
+
document.getElementById('modal-body').textContent = text;
|
|
2454
|
+
document.getElementById('modal').classList.add('open');
|
|
2455
|
+
} catch (e) { console.error(e); }
|
|
2456
|
+
}
|
|
2457
|
+
|
|
1971
2458
|
// ─── Knowledge Base ──────────────────────────────────────────────────────────
|
|
1972
2459
|
let _kbData = {};
|
|
1973
2460
|
let _kbActiveTab = 'all';
|