claude-code-kanban 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/index.html +201 -95
- package/server.js +28 -11
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -1208,6 +1208,7 @@
|
|
|
1208
1208
|
border-radius: 4px;
|
|
1209
1209
|
border: 1px solid transparent;
|
|
1210
1210
|
transition: border-color 0.15s ease;
|
|
1211
|
+
overflow-x: auto;
|
|
1211
1212
|
}
|
|
1212
1213
|
|
|
1213
1214
|
.detail-desc:hover {
|
|
@@ -1665,13 +1666,13 @@
|
|
|
1665
1666
|
.pinned-sessions-divider {
|
|
1666
1667
|
height: 1px; margin: 4px 8px; background: color-mix(in srgb, var(--accent) 30%, transparent);
|
|
1667
1668
|
}
|
|
1668
|
-
.agent-tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); margin-top: 16px; }
|
|
1669
|
+
.agent-tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); margin-top: 16px; align-items: center; }
|
|
1669
1670
|
.agent-tab { padding: 8px 16px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-tertiary); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color 0.15s, border-color 0.15s; user-select: none; }
|
|
1670
1671
|
.agent-tab:hover { color: var(--text-secondary); border-bottom-color: var(--text-muted); }
|
|
1671
1672
|
.agent-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
|
|
1672
1673
|
.agent-tab-panel { display: none; padding-top: 12px; overflow: hidden; position: relative; }
|
|
1673
1674
|
.agent-tab-panel.active { display: block; }
|
|
1674
|
-
.agent-tab-copy {
|
|
1675
|
+
.agent-tab-copy { margin-left: auto; background: var(--surface-hover); border: 1px solid var(--border); border-radius: 6px; padding: 4px 6px; cursor: pointer; color: var(--text-tertiary); opacity: 0.7; transition: opacity 0.15s, color 0.15s; margin-bottom: -1px; }
|
|
1675
1676
|
.agent-tab-copy:hover { opacity: 1; color: var(--text-primary); }
|
|
1676
1677
|
.toast { position: fixed; bottom: 24px; left: 24px; transform: translateY(20px); background: var(--bg-elevated); color: var(--accent-text); border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: 8px; padding: 10px 20px; font-size: 13px; font-weight: 600; z-index: 10000; opacity: 0; transition: opacity 0.25s, transform 0.25s; pointer-events: none; box-shadow: 0 4px 16px rgba(0,0,0,0.3); }
|
|
1677
1678
|
.toast.visible { opacity: 1; transform: translateY(0); }
|
|
@@ -1764,9 +1765,9 @@
|
|
|
1764
1765
|
.agent-footer.collapsed .agent-footer-content { display: none; }
|
|
1765
1766
|
.agent-card {
|
|
1766
1767
|
display: flex;
|
|
1767
|
-
|
|
1768
|
-
gap:
|
|
1769
|
-
padding: 8px
|
|
1768
|
+
flex-direction: column;
|
|
1769
|
+
gap: 3px;
|
|
1770
|
+
padding: 8px 12px;
|
|
1770
1771
|
background: var(--bg-elevated);
|
|
1771
1772
|
border: 1px solid var(--border);
|
|
1772
1773
|
border-radius: 8px;
|
|
@@ -1775,11 +1776,30 @@
|
|
|
1775
1776
|
overflow: hidden;
|
|
1776
1777
|
transition: opacity 0.3s;
|
|
1777
1778
|
cursor: pointer;
|
|
1779
|
+
position: relative;
|
|
1778
1780
|
}
|
|
1779
1781
|
.agent-card:hover { border-color: var(--accent); }
|
|
1780
1782
|
.agent-card.fading { opacity: 0.4; }
|
|
1783
|
+
.agent-type-row {
|
|
1784
|
+
display: flex;
|
|
1785
|
+
align-items: center;
|
|
1786
|
+
gap: 4px;
|
|
1787
|
+
min-width: 0;
|
|
1788
|
+
}
|
|
1789
|
+
.agent-type-ns {
|
|
1790
|
+
font-size: 11px; color: var(--text-muted); font-weight: 400;
|
|
1791
|
+
}
|
|
1792
|
+
.agent-type-name {
|
|
1793
|
+
font-size: 13px; font-weight: 600; color: var(--text-primary);
|
|
1794
|
+
overflow: hidden; text-overflow: ellipsis;
|
|
1795
|
+
}
|
|
1796
|
+
.agent-status-row {
|
|
1797
|
+
display: flex;
|
|
1798
|
+
align-items: center;
|
|
1799
|
+
gap: 5px;
|
|
1800
|
+
}
|
|
1781
1801
|
.agent-dot {
|
|
1782
|
-
width:
|
|
1802
|
+
width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
|
|
1783
1803
|
}
|
|
1784
1804
|
.agent-dot.active { background: var(--success); box-shadow: 0 0 6px var(--success); }
|
|
1785
1805
|
.agent-dot.idle { background: var(--warning); box-shadow: 0 0 6px var(--warning); }
|
|
@@ -1792,9 +1812,14 @@
|
|
|
1792
1812
|
}
|
|
1793
1813
|
.agent-message {
|
|
1794
1814
|
font-size: 11px; color: var(--text-muted);
|
|
1795
|
-
max-width:
|
|
1815
|
+
max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
1796
1816
|
}
|
|
1797
1817
|
|
|
1818
|
+
.agent-dismiss-btn {
|
|
1819
|
+
font-size: 12px; padding: 4px 10px;
|
|
1820
|
+
background: var(--bg-tertiary); color: var(--text-secondary);
|
|
1821
|
+
border: 1px solid var(--border);
|
|
1822
|
+
}
|
|
1798
1823
|
.agent-badge { font-size: 12px; cursor: default; }
|
|
1799
1824
|
|
|
1800
1825
|
/* Permission pending — indicated by ❓ badge only */
|
|
@@ -1948,10 +1973,17 @@
|
|
|
1948
1973
|
background: rgba(0, 0, 0, 0.6);
|
|
1949
1974
|
}
|
|
1950
1975
|
|
|
1976
|
+
.modal.fullscreen {
|
|
1977
|
+
width: 76vw !important;
|
|
1978
|
+
max-width: 76vw !important;
|
|
1979
|
+
height: 85vh !important;
|
|
1980
|
+
max-height: 85vh !important;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1951
1983
|
.modal.plan-modal {
|
|
1952
1984
|
width: 60vw;
|
|
1953
1985
|
max-width: 60vw;
|
|
1954
|
-
max-height:
|
|
1986
|
+
max-height: 85vh;
|
|
1955
1987
|
display: flex;
|
|
1956
1988
|
flex-direction: column;
|
|
1957
1989
|
}
|
|
@@ -3470,9 +3502,18 @@
|
|
|
3470
3502
|
}
|
|
3471
3503
|
const toolParamsHtml = renderToolParamsHtml(m.params);
|
|
3472
3504
|
const toolResultHtml = renderToolResultHtml(m.toolResult, m.toolResultTruncated, m.toolResultFull);
|
|
3473
|
-
const
|
|
3474
|
-
|
|
3475
|
-
|
|
3505
|
+
const hasAgentTabs = m.tool === 'Agent' && m.agentId && (m.agentLastMessage || m.agentPrompt);
|
|
3506
|
+
let mainHtml;
|
|
3507
|
+
if (hasAgentTabs) {
|
|
3508
|
+
mainHtml = descHtml || '';
|
|
3509
|
+
} else if (fullText) {
|
|
3510
|
+
const detailEscaped = escapeHtml(fullText);
|
|
3511
|
+
const detailRendered = m.tool === 'Bash' ? highlightBash(detailEscaped) : detailEscaped;
|
|
3512
|
+
mainHtml = descHtml + `<pre class="msg-detail-pre">${detailRendered}</pre>`;
|
|
3513
|
+
} else {
|
|
3514
|
+
mainHtml = '<em>No details</em>';
|
|
3515
|
+
}
|
|
3516
|
+
body.innerHTML = mainHtml + toolParamsHtml + (hasAgentTabs ? '' : toolResultHtml) + agentExtraHtml;
|
|
3476
3517
|
} else {
|
|
3477
3518
|
const text = stripAnsi(m.fullText || m.text);
|
|
3478
3519
|
document.getElementById('msg-detail-title').textContent = m.type === 'assistant' ? 'Claude' : (m.systemLabel ? 'System' : 'User');
|
|
@@ -3491,6 +3532,7 @@
|
|
|
3491
3532
|
|
|
3492
3533
|
const meta = [formatDate(m.timestamp)];
|
|
3493
3534
|
if (m.model) meta.unshift(m.model);
|
|
3535
|
+
meta.push(`${idx + 1} of ${currentMessages.length}`);
|
|
3494
3536
|
document.getElementById('msg-detail-meta').textContent = meta.join(' · ');
|
|
3495
3537
|
currentPinDetailId = null;
|
|
3496
3538
|
updateMsgDetailPinState();
|
|
@@ -3498,10 +3540,32 @@
|
|
|
3498
3540
|
}
|
|
3499
3541
|
|
|
3500
3542
|
function closeMsgDetailModal() {
|
|
3501
|
-
|
|
3543
|
+
resetModalFullscreen('msg-detail-modal');
|
|
3502
3544
|
msgDetailFollowLatest = false;
|
|
3503
3545
|
}
|
|
3504
3546
|
|
|
3547
|
+
function toggleModalFullscreen(modalId) {
|
|
3548
|
+
const modal = document.querySelector(`#${modalId} .modal`);
|
|
3549
|
+
const isFs = modal.classList.toggle('fullscreen');
|
|
3550
|
+
updateFullscreenBtnIcon(`${modalId}-fullscreen-btn`, isFs);
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
function resetModalFullscreen(modalId) {
|
|
3554
|
+
const modal = document.getElementById(modalId);
|
|
3555
|
+
modal.classList.remove('visible');
|
|
3556
|
+
modal.querySelector('.modal').classList.remove('fullscreen');
|
|
3557
|
+
updateFullscreenBtnIcon(`${modalId}-fullscreen-btn`, false);
|
|
3558
|
+
return modal;
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
function updateFullscreenBtnIcon(btnId, isFullscreen) {
|
|
3562
|
+
const btn = document.getElementById(btnId);
|
|
3563
|
+
if (!btn) return;
|
|
3564
|
+
btn.innerHTML = isFullscreen
|
|
3565
|
+
? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/><line x1="14" y1="10" x2="21" y2="3"/><line x1="3" y1="21" x2="10" y2="14"/></svg>'
|
|
3566
|
+
: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>';
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3505
3569
|
let _toastTimer = null;
|
|
3506
3570
|
function showToast(msg) {
|
|
3507
3571
|
const el = document.getElementById('toast');
|
|
@@ -3605,7 +3669,9 @@
|
|
|
3605
3669
|
function autoSizeModal(modal, body) {
|
|
3606
3670
|
const hasTable = body.querySelector('table') !== null;
|
|
3607
3671
|
const hasPre = body.querySelector('pre') !== null;
|
|
3608
|
-
|
|
3672
|
+
const desired = hasTable ? 1100 : (body.textContent.length > 2000 || hasPre) ? 960 : 860;
|
|
3673
|
+
const current = parseFloat(getComputedStyle(modal).maxWidth) || 0;
|
|
3674
|
+
if (desired > current) modal.style.maxWidth = desired + 'px';
|
|
3609
3675
|
}
|
|
3610
3676
|
|
|
3611
3677
|
function renderToolResultHtml(toolResult, isTruncated, fullResult) {
|
|
@@ -3743,16 +3809,14 @@
|
|
|
3743
3809
|
const promptTrunc = promptTrimmed.length > 60 ? promptTrimmed.substring(0, 60) + '…' : promptTrimmed;
|
|
3744
3810
|
const msgHtml = promptTrunc
|
|
3745
3811
|
? `<div class="agent-message" title="${escapeHtml(promptTrimmed)}">${escapeHtml(promptTrunc)}</div>` : '';
|
|
3746
|
-
const
|
|
3747
|
-
const
|
|
3812
|
+
const rawType = a.type || 'unknown';
|
|
3813
|
+
const colonIdx = rawType.indexOf(':');
|
|
3814
|
+
const typeNs = colonIdx > 0 ? rawType.substring(0, colonIdx + 1) : '';
|
|
3815
|
+
const typeName = colonIdx > 0 ? rawType.substring(colonIdx + 1) : rawType;
|
|
3748
3816
|
return `<div class="agent-card" onclick="showAgentModal('${a.agentId}')">
|
|
3749
|
-
<span class="agent-
|
|
3750
|
-
<div
|
|
3751
|
-
|
|
3752
|
-
<div class="agent-status">${statusText}</div>
|
|
3753
|
-
${msgHtml}
|
|
3754
|
-
</div>
|
|
3755
|
-
${agentPinBtn}
|
|
3817
|
+
<div class="agent-type-row">${typeNs ? `<span class="agent-type-ns">${escapeHtml(typeNs)}</span>` : ''}<span class="agent-type-name">${escapeHtml(typeName)}</span></div>
|
|
3818
|
+
<div class="agent-status-row"><span class="agent-dot ${a.status}"></span><span class="agent-status">${statusText}</span></div>
|
|
3819
|
+
${msgHtml}
|
|
3756
3820
|
</div>`;
|
|
3757
3821
|
}).join('');
|
|
3758
3822
|
|
|
@@ -3797,6 +3861,17 @@
|
|
|
3797
3861
|
updateAgentModalPinState();
|
|
3798
3862
|
}
|
|
3799
3863
|
|
|
3864
|
+
async function dismissAgent(agentId) {
|
|
3865
|
+
if (!currentSessionId || !agentId) return;
|
|
3866
|
+
try {
|
|
3867
|
+
const res = await fetch(`/api/sessions/${currentSessionId}/agents/${agentId}/stop`, { method: 'POST' });
|
|
3868
|
+
if (res.ok) {
|
|
3869
|
+
currentWaiting = null;
|
|
3870
|
+
fetchAgents(currentSessionId);
|
|
3871
|
+
}
|
|
3872
|
+
} catch (e) { console.error('[dismissAgent]', e); }
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3800
3875
|
function showAgentModal(agentId) {
|
|
3801
3876
|
const agent = currentAgents.find(a => a.agentId === agentId);
|
|
3802
3877
|
if (!agent) return;
|
|
@@ -3839,6 +3914,9 @@
|
|
|
3839
3914
|
|
|
3840
3915
|
body.innerHTML = html;
|
|
3841
3916
|
updateAgentModalPinState();
|
|
3917
|
+
autoSizeModal(modal.querySelector('.modal'), body);
|
|
3918
|
+
const dismissBtn = document.getElementById('agent-modal-dismiss-btn');
|
|
3919
|
+
dismissBtn.style.display = (agent.status === 'active' || agent.status === 'idle') ? '' : 'none';
|
|
3842
3920
|
modal.classList.add('visible');
|
|
3843
3921
|
const keyHandler = (e) => {
|
|
3844
3922
|
if (e.key === 'Escape') {
|
|
@@ -3851,7 +3929,7 @@
|
|
|
3851
3929
|
}
|
|
3852
3930
|
|
|
3853
3931
|
function closeAgentModal() {
|
|
3854
|
-
|
|
3932
|
+
resetModalFullscreen('agent-modal');
|
|
3855
3933
|
currentAgentModalId = null;
|
|
3856
3934
|
}
|
|
3857
3935
|
|
|
@@ -4865,11 +4943,11 @@
|
|
|
4865
4943
|
if (e.key === 'Escape') {
|
|
4866
4944
|
e.preventDefault();
|
|
4867
4945
|
closeDeleteConfirmModal();
|
|
4868
|
-
} else if (e
|
|
4946
|
+
} else if (matchKey(e, 'ArrowLeft', 'KeyH')) {
|
|
4869
4947
|
e.preventDefault();
|
|
4870
4948
|
focusIdx = 0;
|
|
4871
4949
|
buttons[focusIdx].focus();
|
|
4872
|
-
} else if (e
|
|
4950
|
+
} else if (matchKey(e, 'ArrowRight', 'KeyL')) {
|
|
4873
4951
|
e.preventDefault();
|
|
4874
4952
|
focusIdx = 1;
|
|
4875
4953
|
buttons[focusIdx].focus();
|
|
@@ -4951,35 +5029,58 @@
|
|
|
4951
5029
|
|
|
4952
5030
|
document.getElementById('close-detail').onclick = closeDetailPanel;
|
|
4953
5031
|
|
|
5032
|
+
// Layout-independent key matching: pass Arrow* for e.key, Key* for e.code (physical position)
|
|
5033
|
+
function matchKey(e, ...keys) {
|
|
5034
|
+
if (e.ctrlKey || e.altKey || e.metaKey || e.shiftKey) return false;
|
|
5035
|
+
return keys.some(k => e.key === k || e.code === k);
|
|
5036
|
+
}
|
|
5037
|
+
|
|
4954
5038
|
document.addEventListener('keydown', (e) => {
|
|
4955
5039
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') {
|
|
4956
5040
|
return;
|
|
4957
5041
|
}
|
|
4958
5042
|
|
|
5043
|
+
// Modal guard — only Escape, Shift+M, and msg-detail J/K navigation pass through
|
|
4959
5044
|
if (document.querySelector('.modal-overlay.visible')) {
|
|
4960
5045
|
if (e.key === 'Escape') {
|
|
4961
5046
|
document.querySelectorAll('.modal-overlay.visible').forEach(m => m.classList.remove('visible'));
|
|
4962
5047
|
msgDetailFollowLatest = false;
|
|
4963
|
-
} else if (
|
|
5048
|
+
} else if (e.code === 'KeyM' && e.shiftKey && document.getElementById('msg-detail-modal').classList.contains('visible')) {
|
|
4964
5049
|
e.preventDefault();
|
|
4965
5050
|
closeMsgDetailModal();
|
|
5051
|
+
} else if (document.getElementById('msg-detail-modal').classList.contains('visible')) {
|
|
5052
|
+
if (matchKey(e, 'ArrowDown', 'KeyJ')) {
|
|
5053
|
+
e.preventDefault();
|
|
5054
|
+
if (currentMsgDetailIdx < currentMessages.length - 1) {
|
|
5055
|
+
msgDetailFollowLatest = false;
|
|
5056
|
+
showMsgDetail(currentMsgDetailIdx + 1);
|
|
5057
|
+
} else if (currentMsgDetailIdx === currentMessages.length - 1) {
|
|
5058
|
+
msgDetailFollowLatest = true;
|
|
5059
|
+
showMsgDetail(currentMsgDetailIdx);
|
|
5060
|
+
}
|
|
5061
|
+
} else if (matchKey(e, 'ArrowUp', 'KeyK')) {
|
|
5062
|
+
e.preventDefault();
|
|
5063
|
+
if (currentMsgDetailIdx > 0) {
|
|
5064
|
+
msgDetailFollowLatest = false;
|
|
5065
|
+
showMsgDetail(currentMsgDetailIdx - 1);
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
4966
5068
|
}
|
|
4967
5069
|
return;
|
|
4968
5070
|
}
|
|
4969
5071
|
|
|
5072
|
+
// Global shortcuts
|
|
4970
5073
|
if (e.key === '[') {
|
|
4971
5074
|
e.preventDefault();
|
|
4972
5075
|
toggleSidebar();
|
|
4973
5076
|
return;
|
|
4974
5077
|
}
|
|
4975
|
-
|
|
4976
|
-
if ((e.key === 'L' || e.key === 'l') && e.shiftKey) {
|
|
5078
|
+
if (e.code === 'KeyL' && e.shiftKey) {
|
|
4977
5079
|
e.preventDefault();
|
|
4978
5080
|
toggleMessagePanel();
|
|
4979
5081
|
return;
|
|
4980
5082
|
}
|
|
4981
|
-
|
|
4982
|
-
if ((e.key === 'M' || e.key === 'm') && e.shiftKey) {
|
|
5083
|
+
if (e.code === 'KeyM' && e.shiftKey) {
|
|
4983
5084
|
e.preventDefault();
|
|
4984
5085
|
const msgDetailModal = document.getElementById('msg-detail-modal');
|
|
4985
5086
|
if (msgDetailModal.classList.contains('visible')) {
|
|
@@ -5004,22 +5105,22 @@
|
|
|
5004
5105
|
|
|
5005
5106
|
// Sidebar navigation
|
|
5006
5107
|
if (focusZone === 'sidebar') {
|
|
5007
|
-
if (e
|
|
5108
|
+
if (matchKey(e, 'ArrowDown', 'KeyJ')) {
|
|
5008
5109
|
e.preventDefault();
|
|
5009
5110
|
navigateSession(1);
|
|
5010
5111
|
return;
|
|
5011
5112
|
}
|
|
5012
|
-
if (e
|
|
5113
|
+
if (matchKey(e, 'ArrowUp', 'KeyK')) {
|
|
5013
5114
|
e.preventDefault();
|
|
5014
5115
|
navigateSession(-1);
|
|
5015
5116
|
return;
|
|
5016
5117
|
}
|
|
5017
|
-
if (e
|
|
5118
|
+
if (matchKey(e, 'ArrowLeft', 'KeyH')) {
|
|
5018
5119
|
e.preventDefault();
|
|
5019
5120
|
handleSidebarHorizontal(-1);
|
|
5020
5121
|
return;
|
|
5021
5122
|
}
|
|
5022
|
-
if (e
|
|
5123
|
+
if (matchKey(e, 'ArrowRight', 'KeyL')) {
|
|
5023
5124
|
e.preventDefault();
|
|
5024
5125
|
handleSidebarHorizontal(1);
|
|
5025
5126
|
return;
|
|
@@ -5033,86 +5134,70 @@
|
|
|
5033
5134
|
setFocusZone('board');
|
|
5034
5135
|
return;
|
|
5035
5136
|
}
|
|
5036
|
-
if (e.key === 'p' || e.key === 'P') {
|
|
5037
|
-
e.preventDefault();
|
|
5038
|
-
const highlighted = sessionsList.querySelector('.kb-selected');
|
|
5039
|
-
const sid = highlighted?.dataset.sessionId || currentSessionId;
|
|
5040
|
-
if (sid) openPlanForSession(sid);
|
|
5041
|
-
return;
|
|
5042
|
-
}
|
|
5043
|
-
if (e.key === 'i' || e.key === 'I') {
|
|
5044
|
-
e.preventDefault();
|
|
5045
|
-
const highlighted = sessionsList.querySelector('.kb-selected');
|
|
5046
|
-
const sid = highlighted?.dataset.sessionId || currentSessionId;
|
|
5047
|
-
if (sid) showSessionInfoModal(sid);
|
|
5048
|
-
return;
|
|
5049
|
-
}
|
|
5050
|
-
if (e.key === '?' || (e.key === '/' && e.shiftKey)) {
|
|
5051
|
-
e.preventDefault();
|
|
5052
|
-
showHelpModal();
|
|
5053
|
-
}
|
|
5054
|
-
return;
|
|
5055
5137
|
}
|
|
5056
5138
|
|
|
5057
5139
|
// Board navigation
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5140
|
+
if (focusZone === 'board') {
|
|
5141
|
+
if (matchKey(e, 'ArrowDown', 'KeyJ', 'ArrowUp', 'KeyK', 'ArrowLeft', 'KeyH', 'ArrowRight', 'KeyL')) {
|
|
5142
|
+
e.preventDefault();
|
|
5143
|
+
if (!selectedTaskId && !document.querySelector('.task-card.selected')) {
|
|
5144
|
+
setFocusZone('sidebar');
|
|
5145
|
+
return;
|
|
5146
|
+
}
|
|
5147
|
+
if (matchKey(e, 'ArrowDown', 'KeyJ')) navigateVertical(1);
|
|
5148
|
+
else if (matchKey(e, 'ArrowUp', 'KeyK')) navigateVertical(-1);
|
|
5149
|
+
else if (matchKey(e, 'ArrowLeft', 'KeyH')) navigateHorizontal(-1);
|
|
5150
|
+
else if (matchKey(e, 'ArrowRight', 'KeyL')) navigateHorizontal(1);
|
|
5069
5151
|
|
|
5070
|
-
|
|
5071
|
-
|
|
5152
|
+
if (selectedTaskId && detailPanel.classList.contains('visible')) {
|
|
5153
|
+
showTaskDetail(selectedTaskId, selectedSessionId);
|
|
5154
|
+
}
|
|
5155
|
+
return;
|
|
5072
5156
|
}
|
|
5073
|
-
return;
|
|
5074
|
-
}
|
|
5075
5157
|
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5158
|
+
if ((e.key === 'Enter' || e.key === ' ') && selectedTaskId && e.target.tagName !== 'BUTTON') {
|
|
5159
|
+
e.preventDefault();
|
|
5160
|
+
if (detailPanel.classList.contains('visible')) {
|
|
5161
|
+
const labelEl = document.querySelector('.detail-label');
|
|
5162
|
+
const shownId = labelEl?.textContent.match(/\d+/)?.[0];
|
|
5163
|
+
if (shownId === selectedTaskId) {
|
|
5164
|
+
closeDetailPanel();
|
|
5165
|
+
} else {
|
|
5166
|
+
showTaskDetail(selectedTaskId, selectedSessionId);
|
|
5167
|
+
}
|
|
5083
5168
|
} else {
|
|
5084
5169
|
showTaskDetail(selectedTaskId, selectedSessionId);
|
|
5085
5170
|
}
|
|
5086
|
-
|
|
5087
|
-
|
|
5171
|
+
return;
|
|
5172
|
+
}
|
|
5173
|
+
|
|
5174
|
+
if (matchKey(e, 'KeyD') && selectedTaskId) {
|
|
5175
|
+
e.preventDefault();
|
|
5176
|
+
deleteTask(selectedTaskId, selectedSessionId || currentSessionId);
|
|
5177
|
+
return;
|
|
5088
5178
|
}
|
|
5089
|
-
return;
|
|
5090
5179
|
}
|
|
5091
5180
|
|
|
5092
5181
|
if (e.key === 'Escape') {
|
|
5093
5182
|
if (detailPanel.classList.contains('visible')) closeDetailPanel();
|
|
5094
5183
|
else if (messagePanelOpen) toggleMessagePanel();
|
|
5095
|
-
}
|
|
5096
|
-
|
|
5097
|
-
if (e.key === 'p' || e.key === 'P') {
|
|
5098
|
-
e.preventDefault();
|
|
5099
|
-
const sid = selectedSessionId || currentSessionId;
|
|
5100
|
-
if (sid) openPlanForSession(sid);
|
|
5101
5184
|
return;
|
|
5102
5185
|
}
|
|
5103
5186
|
|
|
5104
|
-
|
|
5187
|
+
// Shared actions — work in both sidebar and board
|
|
5188
|
+
const contextSid = focusZone === 'sidebar'
|
|
5189
|
+
? (sessionsList.querySelector('.kb-selected')?.dataset.sessionId || currentSessionId)
|
|
5190
|
+
: (selectedSessionId || currentSessionId);
|
|
5191
|
+
if (matchKey(e, 'KeyP') && !e.shiftKey) {
|
|
5105
5192
|
e.preventDefault();
|
|
5106
|
-
|
|
5107
|
-
if (sid) showSessionInfoModal(sid);
|
|
5193
|
+
if (contextSid) openPlanForSession(contextSid);
|
|
5108
5194
|
return;
|
|
5109
5195
|
}
|
|
5110
|
-
|
|
5111
|
-
if ((e.key === 'd' || e.key === 'D') && selectedTaskId) {
|
|
5196
|
+
if (matchKey(e, 'KeyI') && !e.shiftKey) {
|
|
5112
5197
|
e.preventDefault();
|
|
5113
|
-
|
|
5198
|
+
if (contextSid) showSessionInfoModal(contextSid);
|
|
5199
|
+
return;
|
|
5114
5200
|
}
|
|
5115
|
-
|
|
5116
5201
|
if (e.key === '?' || (e.key === '/' && e.shiftKey)) {
|
|
5117
5202
|
e.preventDefault();
|
|
5118
5203
|
showHelpModal();
|
|
@@ -5279,14 +5364,14 @@
|
|
|
5279
5364
|
}
|
|
5280
5365
|
if (!tabs.length) return '';
|
|
5281
5366
|
const defaultTab = responseHtml ? 'response' : tabs[0].key;
|
|
5282
|
-
const
|
|
5367
|
+
const copyBtnHtml = `<button class="agent-tab-copy" title="Copy" onclick="copyAgentTabActive('${id}',this)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></button>`;
|
|
5283
5368
|
const tabsHtml = tabs.map(t =>
|
|
5284
5369
|
`<div class="agent-tab${t.key === defaultTab ? ' active' : ''}" data-tab-group="${id}" data-tab-key="${t.key}" onclick="document.querySelectorAll('[data-tab-group=\\'${id}\\']').forEach(el=>{el.classList.toggle('active',el.dataset.tabKey==='${t.key}')})">${t.label}</div>`
|
|
5285
5370
|
).join('');
|
|
5286
5371
|
const panelsHtml = panels.map(p =>
|
|
5287
|
-
`<div class="agent-tab-panel${p.key === defaultTab ? ' active' : ''}" data-tab-group="${id}" data-tab-key="${p.key}"
|
|
5372
|
+
`<div class="agent-tab-panel${p.key === defaultTab ? ' active' : ''}" data-tab-group="${id}" data-tab-key="${p.key}"><div class="detail-desc rendered-md" style="font-size:13px;">${p.html}</div></div>`
|
|
5288
5373
|
).join('');
|
|
5289
|
-
return `<div class="agent-tabs">${tabsHtml}</div>${panelsHtml}`;
|
|
5374
|
+
return `<div class="agent-tabs">${tabsHtml}${copyBtnHtml}</div>${panelsHtml}`;
|
|
5290
5375
|
}
|
|
5291
5376
|
|
|
5292
5377
|
async function copyAgentTab(key, btn) {
|
|
@@ -5295,6 +5380,13 @@
|
|
|
5295
5380
|
copyWithFeedback(text, btn);
|
|
5296
5381
|
}
|
|
5297
5382
|
|
|
5383
|
+
async function copyAgentTabActive(groupId, btn) {
|
|
5384
|
+
const activePanel = document.querySelector(`.agent-tab-panel.active[data-tab-group="${groupId}"]`);
|
|
5385
|
+
if (!activePanel) return;
|
|
5386
|
+
const key = groupId + '-' + activePanel.dataset.tabKey;
|
|
5387
|
+
copyAgentTab(key, btn);
|
|
5388
|
+
}
|
|
5389
|
+
|
|
5298
5390
|
const ownerColors = [
|
|
5299
5391
|
{ bg: 'rgba(37, 99, 235, 0.14)', color: '#1d5bbf' }, // blue
|
|
5300
5392
|
{ bg: 'rgba(168, 85, 247, 0.14)', color: '#7c3aed' }, // purple
|
|
@@ -5783,7 +5875,7 @@
|
|
|
5783
5875
|
}
|
|
5784
5876
|
|
|
5785
5877
|
function closePlanModal() {
|
|
5786
|
-
|
|
5878
|
+
resetModalFullscreen('plan-modal');
|
|
5787
5879
|
}
|
|
5788
5880
|
|
|
5789
5881
|
function openPlanInEditor() {
|
|
@@ -5936,6 +6028,9 @@
|
|
|
5936
6028
|
<button class="icon-btn" aria-label="Open in editor" title="Open in editor" onclick="openMsgInEditor()">
|
|
5937
6029
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
|
5938
6030
|
</button>
|
|
6031
|
+
<button id="msg-detail-fullscreen-btn" class="icon-btn" aria-label="Toggle fullscreen" title="Toggle fullscreen" onclick="toggleModalFullscreen('msg-detail-modal')">
|
|
6032
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
|
|
6033
|
+
</button>
|
|
5939
6034
|
<button class="modal-close" aria-label="Close" onclick="closeMsgDetailModal()">
|
|
5940
6035
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
|
5941
6036
|
</button>
|
|
@@ -6030,6 +6125,10 @@
|
|
|
6030
6125
|
<td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">Shift+M</kbd></td>
|
|
6031
6126
|
<td style="padding: 4px 0; color: var(--text-primary);">Open last message detail</td>
|
|
6032
6127
|
</tr>
|
|
6128
|
+
<tr>
|
|
6129
|
+
<td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">J/K</kbd></td>
|
|
6130
|
+
<td style="padding: 4px 0; color: var(--text-primary);">Navigate messages in detail modal</td>
|
|
6131
|
+
</tr>
|
|
6033
6132
|
</table>
|
|
6034
6133
|
</div>
|
|
6035
6134
|
</div>
|
|
@@ -6134,6 +6233,9 @@
|
|
|
6134
6233
|
<button class="icon-btn" aria-label="Copy plan" title="Copy plan" onclick="copyWithFeedback(_pendingPlanContent||'',this)">
|
|
6135
6234
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
|
|
6136
6235
|
</button>
|
|
6236
|
+
<button id="plan-modal-fullscreen-btn" class="icon-btn" aria-label="Toggle fullscreen" title="Toggle fullscreen" onclick="toggleModalFullscreen('plan-modal')">
|
|
6237
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
|
|
6238
|
+
</button>
|
|
6137
6239
|
<button class="modal-close" aria-label="Close dialog" onclick="closePlanModal()">
|
|
6138
6240
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
6139
6241
|
<path d="M18 6L6 18M6 6l12 12"/>
|
|
@@ -6186,6 +6288,9 @@
|
|
|
6186
6288
|
<button class="icon-btn" id="agent-modal-copy-all" aria-label="Copy all" title="Copy prompt + response" onclick="copyAgentModalAll(this)">
|
|
6187
6289
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
|
|
6188
6290
|
</button>
|
|
6291
|
+
<button id="agent-modal-fullscreen-btn" class="icon-btn" aria-label="Toggle fullscreen" title="Toggle fullscreen" onclick="toggleModalFullscreen('agent-modal')">
|
|
6292
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
|
|
6293
|
+
</button>
|
|
6189
6294
|
<button class="modal-close" aria-label="Close dialog" onclick="closeAgentModal()">
|
|
6190
6295
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
6191
6296
|
<path d="M18 6L6 18M6 6l12 12"/>
|
|
@@ -6195,6 +6300,7 @@
|
|
|
6195
6300
|
</div>
|
|
6196
6301
|
<div id="agent-modal-body" class="modal-body" style="overflow-y: auto; overflow-x: hidden; flex: 0 1 auto; min-height: 0;"></div>
|
|
6197
6302
|
<div class="modal-footer">
|
|
6303
|
+
<button id="agent-modal-dismiss-btn" class="btn agent-dismiss-btn" style="display:none" onclick="dismissAgent(currentAgentModalId);closeAgentModal()">Dismiss</button>
|
|
6198
6304
|
<button class="btn btn-primary" onclick="closeAgentModal()">Close</button>
|
|
6199
6305
|
</div>
|
|
6200
6306
|
</div>
|
package/server.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const express = require('express');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs').promises;
|
|
6
|
-
const { existsSync, readdirSync, readFileSync, statSync, createReadStream } = require('fs');
|
|
6
|
+
const { existsSync, readdirSync, readFileSync, writeFileSync, statSync, createReadStream, unlinkSync } = require('fs');
|
|
7
7
|
const readline = require('readline');
|
|
8
8
|
const chokidar = require('chokidar');
|
|
9
9
|
const os = require('os');
|
|
@@ -128,6 +128,11 @@ function loadTeamConfig(teamName) {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
function resolveSessionId(sessionId) {
|
|
132
|
+
const teamConfig = loadTeamConfig(sessionId);
|
|
133
|
+
return (teamConfig && teamConfig.leadSessionId) ? teamConfig.leadSessionId : sessionId;
|
|
134
|
+
}
|
|
135
|
+
|
|
131
136
|
// SSE clients for live updates
|
|
132
137
|
const clients = new Set();
|
|
133
138
|
|
|
@@ -755,12 +760,7 @@ app.get('/api/teams/:name', (req, res) => {
|
|
|
755
760
|
|
|
756
761
|
// API: Get agents for a session
|
|
757
762
|
app.get('/api/sessions/:sessionId/agents', (req, res) => {
|
|
758
|
-
|
|
759
|
-
// For team sessions, resolve to leader's session UUID
|
|
760
|
-
const teamConfig = loadTeamConfig(sessionId);
|
|
761
|
-
if (teamConfig && teamConfig.leadSessionId) {
|
|
762
|
-
sessionId = teamConfig.leadSessionId;
|
|
763
|
-
}
|
|
763
|
+
const sessionId = resolveSessionId(req.params.sessionId);
|
|
764
764
|
const agentDir = path.join(AGENT_ACTIVITY_DIR, sessionId);
|
|
765
765
|
if (!existsSync(agentDir)) return res.json({ agents: [], waitingForUser: null });
|
|
766
766
|
try {
|
|
@@ -809,6 +809,25 @@ app.get('/api/sessions/:sessionId/agents', (req, res) => {
|
|
|
809
809
|
}
|
|
810
810
|
});
|
|
811
811
|
|
|
812
|
+
app.post('/api/sessions/:sessionId/agents/:agentId/stop', (req, res) => {
|
|
813
|
+
const sessionId = resolveSessionId(req.params.sessionId);
|
|
814
|
+
const agentId = path.basename(req.params.agentId).replace(/[^a-zA-Z0-9_-]/g, '');
|
|
815
|
+
const agentFile = path.join(AGENT_ACTIVITY_DIR, sessionId, agentId + '.json');
|
|
816
|
+
if (!existsSync(agentFile)) return res.status(404).json({ error: 'Agent not found' });
|
|
817
|
+
try {
|
|
818
|
+
const agent = JSON.parse(readFileSync(agentFile, 'utf8'));
|
|
819
|
+
agent.status = 'stopped';
|
|
820
|
+
agent.stoppedAt = new Date().toISOString();
|
|
821
|
+
writeFileSync(agentFile, JSON.stringify(agent), 'utf8');
|
|
822
|
+
// Also remove waiting state if present
|
|
823
|
+
const waitingFile = path.join(AGENT_ACTIVITY_DIR, sessionId, '_waiting.json');
|
|
824
|
+
if (existsSync(waitingFile)) unlinkSync(waitingFile);
|
|
825
|
+
res.json({ ok: true });
|
|
826
|
+
} catch (e) {
|
|
827
|
+
res.status(500).json({ error: 'Failed to stop agent' });
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
|
|
812
831
|
app.get('/api/sessions/:sessionId/messages', (req, res) => {
|
|
813
832
|
const limit = Math.min(parseInt(req.query.limit, 10) || 10, 50);
|
|
814
833
|
const metadata = loadSessionMetadata();
|
|
@@ -819,10 +838,8 @@ app.get('/api/sessions/:sessionId/messages', (req, res) => {
|
|
|
819
838
|
const agentMessages = messages.filter(m => m.tool === 'Agent' && m.toolUseId);
|
|
820
839
|
if (agentMessages.length) {
|
|
821
840
|
const progressMap = getProgressMap(jsonlPath);
|
|
822
|
-
|
|
823
|
-
const
|
|
824
|
-
if (teamConfig && teamConfig.leadSessionId) sessionId = teamConfig.leadSessionId;
|
|
825
|
-
const agentDir = path.join(AGENT_ACTIVITY_DIR, sessionId);
|
|
841
|
+
const resolvedSid = resolveSessionId(req.params.sessionId);
|
|
842
|
+
const agentDir = path.join(AGENT_ACTIVITY_DIR, resolvedSid);
|
|
826
843
|
for (const msg of agentMessages) {
|
|
827
844
|
const entry = progressMap[msg.toolUseId];
|
|
828
845
|
if (entry) {
|