jinzd-ai-cli 0.4.213 → 0.4.215
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 +5 -2
- package/README.zh-CN.md +5 -2
- package/dist/{batch-PKY63TME.js → batch-74H5SA7P.js} +2 -2
- package/dist/{chunk-MRGWPCFQ.js → chunk-2224JGA6.js} +1 -1
- package/dist/{chunk-UDMU4FUF.js → chunk-3TSHNZKI.js} +1 -1
- package/dist/{chunk-THNECAAY.js → chunk-6C3JYNM6.js} +380 -74
- package/dist/{chunk-IWLVH32D.js → chunk-7JES2NWR.js} +1 -1
- package/dist/{chunk-FGUKBIYO.js → chunk-KL7UBVSQ.js} +2 -2
- package/dist/{chunk-J35V3IAH.js → chunk-RS4WBI73.js} +1 -1
- package/dist/{chunk-7NTFMLJJ.js → chunk-X6OXS7KU.js} +1 -1
- package/dist/{chunk-G3PW3AOB.js → chunk-ZOF5NFKW.js} +1 -1
- package/dist/{ci-HBZ6CIW5.js → ci-3ALK2XJN.js} +2 -2
- package/dist/{constants-CAUBTSFW.js → constants-IN2HXJK7.js} +1 -1
- package/dist/{doctor-cli-5Z5UWYW6.js → doctor-cli-7XL4TCVT.js} +4 -4
- package/dist/electron-server.js +558 -184
- package/dist/{hub-ZRADBR76.js → hub-VAOG5EY6.js} +1 -1
- package/dist/index.js +17 -16
- package/dist/{run-tests-7J3PNWVS.js → run-tests-J2JQ57R3.js} +1 -1
- package/dist/{run-tests-DSOJBJWA.js → run-tests-QJD43AML.js} +2 -2
- package/dist/{server-U4I7RI42.js → server-VGPZOISQ.js} +88 -8
- package/dist/{server-PWBWHVYB.js → server-WDLV3W4F.js} +4 -4
- package/dist/{task-orchestrator-RUXWRMQV.js → task-orchestrator-QFOCO3N7.js} +4 -4
- package/dist/{usage-2P6TIBTN.js → usage-IEB476NE.js} +2 -2
- package/dist/web/client/actions.js +1 -0
- package/dist/web/client/app.js +183 -11
- package/dist/web/client/index.html +21 -3
- package/dist/web/client/style.css +62 -0
- package/package.json +1 -1
package/dist/web/client/app.js
CHANGED
|
@@ -27,6 +27,11 @@ let inputHistory = []; // Previous user inputs for ↑/↓ navigation
|
|
|
27
27
|
let historyIndex = -1; // -1 = not browsing history
|
|
28
28
|
let savedInputDraft = ''; // Saved current input when entering history mode
|
|
29
29
|
let toolTimers = new Map(); // callId → { startTime, intervalId }
|
|
30
|
+
let latestStatus = null;
|
|
31
|
+
let pendingApprovals = new Map(); // requestId -> approval metadata
|
|
32
|
+
let lastToolResults = [];
|
|
33
|
+
let replayCurrentMessages = [];
|
|
34
|
+
let replayCurrentFilter = 'all';
|
|
30
35
|
|
|
31
36
|
// ── Multi-Tab state (P2-1) ────────────────────────────────────────
|
|
32
37
|
// Each "tab" represents an open session within the single page.
|
|
@@ -60,6 +65,8 @@ let cachedSessions = [];
|
|
|
60
65
|
let cachedToolsData = null;
|
|
61
66
|
const sessionTabsListEl = document.getElementById('session-tabs-list');
|
|
62
67
|
const btnAddTab = document.getElementById('btn-add-tab');
|
|
68
|
+
const dashboardPanel = document.getElementById('dashboard-panel');
|
|
69
|
+
const btnDashboardRefresh = document.getElementById('btn-dashboard-refresh');
|
|
63
70
|
|
|
64
71
|
// ── Configure marked.js ────────────────────────────────────────────
|
|
65
72
|
|
|
@@ -283,6 +290,9 @@ function handleToolCallStart(msg) {
|
|
|
283
290
|
}
|
|
284
291
|
|
|
285
292
|
function handleToolCallResult(msg) {
|
|
293
|
+
lastToolResults.push({ toolName: msg.toolName, content: msg.content || '', isError: !!msg.isError, timestamp: Date.now() });
|
|
294
|
+
if (lastToolResults.length > 30) lastToolResults.shift();
|
|
295
|
+
renderDashboard();
|
|
286
296
|
// Stop live timer
|
|
287
297
|
const timer = toolTimers.get(msg.callId);
|
|
288
298
|
if (timer) {
|
|
@@ -325,6 +335,8 @@ function handleToolCallResult(msg) {
|
|
|
325
335
|
}
|
|
326
336
|
|
|
327
337
|
function handleConfirmRequest(msg) {
|
|
338
|
+
pendingApprovals.set(msg.requestId, { kind: 'tool', requestId: msg.requestId, toolName: msg.toolName, args: msg.args, dangerLevel: msg.dangerLevel, diff: msg.diff, timestamp: Date.now() });
|
|
339
|
+
renderDashboard();
|
|
328
340
|
const isDestructive = msg.dangerLevel === 'destructive';
|
|
329
341
|
const el = document.createElement('div');
|
|
330
342
|
el.className = `confirm-card ${isDestructive ? 'tool-border-destructive' : 'tool-border-write'} my-1`;
|
|
@@ -334,7 +346,7 @@ function handleConfirmRequest(msg) {
|
|
|
334
346
|
<span class="badge ${isDestructive ? 'badge-error' : 'badge-warning'} badge-sm">${isDestructive ? '⚠ DESTRUCTIVE' : '✎ Write'}</span>
|
|
335
347
|
<span class="text-sm font-semibold">${escapeHtml(msg.toolName)}</span>
|
|
336
348
|
</div>
|
|
337
|
-
${msg.diff ? `<div class="confirm-diff w-full">${renderDiffHtml(msg.diff)}</div>` : ''}
|
|
349
|
+
${msg.diff ? `<div class="flex justify-end mb-1"><button class="btn btn-xs btn-ghost" data-action="copy-confirm-diff" data-diff="${escapeHtml(msg.diff)}">Copy patch</button></div><div class="confirm-diff w-full">${renderDiffHtml(msg.diff)}</div>` : ''}
|
|
338
350
|
<div class="flex gap-2 mt-2">
|
|
339
351
|
<button class="btn btn-success btn-sm btn-outline" data-action="confirm-response" data-request-id="${msg.requestId}" data-approved="true">✓ Approve</button>
|
|
340
352
|
<button class="btn btn-error btn-sm btn-outline" data-action="confirm-response" data-request-id="${msg.requestId}" data-approved="false">✗ Deny</button>
|
|
@@ -345,15 +357,21 @@ function handleConfirmRequest(msg) {
|
|
|
345
357
|
}
|
|
346
358
|
|
|
347
359
|
function handleBatchConfirmRequest(msg) {
|
|
360
|
+
pendingApprovals.set(msg.requestId, { kind: 'batch', requestId: msg.requestId, files: msg.files, dangerLevel: 'write', timestamp: Date.now() });
|
|
361
|
+
renderDashboard();
|
|
348
362
|
const el = document.createElement('div');
|
|
349
363
|
el.className = 'confirm-card tool-border-write my-1 batch-confirm';
|
|
350
364
|
el.setAttribute('data-request-id', msg.requestId);
|
|
351
365
|
|
|
352
|
-
const fileListHtml = msg.files.map(f => `
|
|
353
|
-
<
|
|
354
|
-
<
|
|
355
|
-
|
|
356
|
-
|
|
366
|
+
const fileListHtml = msg.files.map((f, idx) => `
|
|
367
|
+
<details class="diff-file-details" ${idx === 0 ? 'open' : ''}>
|
|
368
|
+
<summary class="flex items-center gap-2">
|
|
369
|
+
<input type="checkbox" checked class="checkbox checkbox-sm checkbox-primary" data-index="${f.index}" onclick="event.stopPropagation()">
|
|
370
|
+
<span class="truncate flex-1">${escapeHtml(f.filePath)} <span class="badge badge-ghost badge-xs">${escapeHtml(f.toolName)}</span></span>
|
|
371
|
+
${f.diff ? `<button class="btn btn-xs btn-ghost" data-action="copy-confirm-diff" data-diff="${escapeHtml(f.diff)}" onclick="event.stopPropagation()">Copy</button>` : ''}
|
|
372
|
+
</summary>
|
|
373
|
+
${f.diff ? `<div class="confirm-diff w-full">${renderDiffHtml(f.diff)}</div>` : '<div class="text-xs opacity-50 p-2">No diff preview.</div>'}
|
|
374
|
+
</details>
|
|
357
375
|
`).join('');
|
|
358
376
|
|
|
359
377
|
el.innerHTML = `
|
|
@@ -372,6 +390,8 @@ function handleBatchConfirmRequest(msg) {
|
|
|
372
390
|
}
|
|
373
391
|
|
|
374
392
|
function handleAskUserRequest(msg) {
|
|
393
|
+
pendingApprovals.set(msg.requestId, { kind: 'ask_user', requestId: msg.requestId, toolName: 'ask_user', dangerLevel: 'safe', question: msg.question, timestamp: Date.now() });
|
|
394
|
+
renderDashboard();
|
|
375
395
|
const el = document.createElement('div');
|
|
376
396
|
el.className = 'confirm-card tool-border-safe my-1';
|
|
377
397
|
el.innerHTML = `
|
|
@@ -548,7 +568,10 @@ function handleStatus(msg) {
|
|
|
548
568
|
// Active tab: full UI reflection
|
|
549
569
|
btnThink.classList.toggle('btn-active-toggle', msg.thinkingMode);
|
|
550
570
|
btnPlan.classList.toggle('btn-active-toggle', msg.planMode);
|
|
551
|
-
|
|
571
|
+
const runningAgents = Array.isArray(msg.agents) ? msg.agents.filter(a => a.status === 'running').length : 0;
|
|
572
|
+
const failedAgents = Array.isArray(msg.agents) ? msg.agents.filter(a => a.status === 'error').length : 0;
|
|
573
|
+
const agentStatus = (runningAgents ? ' · agents:' + runningAgents : '') + (failedAgents ? ' · agent-errors:' + failedAgents : '');
|
|
574
|
+
statusSession.textContent = '📋 ' + (msg.sessionId?.slice(0, 8) || '—') + ' (' + msg.messageCount + ' msgs) · ' + (msg.permissionProfile || 'legacy') + (msg.networkPolicyEnabled ? ' · net:on' : '') + (msg.autoMode ? ' · auto:on' : '') + (msg.pendingHookTrustCount ? ' · hooks:' + msg.pendingHookTrustCount : '') + agentStatus;
|
|
552
575
|
if (msg.tokenUsage) {
|
|
553
576
|
const u = msg.tokenUsage;
|
|
554
577
|
const cacheRead = u.cacheReadTokens || 0;
|
|
@@ -570,16 +593,98 @@ function handleStatus(msg) {
|
|
|
570
593
|
targetTab.isProcessing = processing;
|
|
571
594
|
const title = msg.sessionTitle || msg.sessionId?.slice(0, 8) || 'New Session';
|
|
572
595
|
document.title = `ai-cli — ${title}`;
|
|
596
|
+
latestStatus = msg;
|
|
597
|
+
renderDashboard();
|
|
573
598
|
}
|
|
574
599
|
|
|
575
600
|
renderTabBar();
|
|
576
601
|
saveTabState();
|
|
577
602
|
}
|
|
578
603
|
|
|
604
|
+
|
|
605
|
+
function renderDashboard() {
|
|
606
|
+
if (!dashboardPanel) return;
|
|
607
|
+
const s = latestStatus || {};
|
|
608
|
+
const agents = Array.isArray(s.agents) ? s.agents : [];
|
|
609
|
+
const runningAgents = agents.filter(a => a.status === 'running');
|
|
610
|
+
const erroredAgents = agents.filter(a => a.status === 'error');
|
|
611
|
+
const pending = [...pendingApprovals.values()];
|
|
612
|
+
const tokens = s.tokenUsage || {};
|
|
613
|
+
const totalTokens = (tokens.inputTokens || 0) + (tokens.outputTokens || 0) + (tokens.cacheCreationTokens || 0) + (tokens.cacheReadTokens || 0);
|
|
614
|
+
const cost = typeof s.costUsd === 'number'
|
|
615
|
+
? (s.costUsd === 0 ? '$0' : s.costUsd < 0.01 ? `$${s.costUsd.toFixed(4)}` : s.costUsd < 1 ? `$${s.costUsd.toFixed(3)}` : `$${s.costUsd.toFixed(2)}`)
|
|
616
|
+
: 'unknown';
|
|
617
|
+
const recentTests = lastToolResults.filter(r => r.toolName === 'run_tests').slice(-3).reverse();
|
|
618
|
+
const recentToolErrors = lastToolResults.filter(r => r.isError).slice(-3).reverse();
|
|
619
|
+
|
|
620
|
+
dashboardPanel.innerHTML = `
|
|
621
|
+
<section class="dashboard-section">
|
|
622
|
+
<div class="dashboard-section-title">Current Session</div>
|
|
623
|
+
${dashRow('Session', s.sessionTitle || (s.sessionId ? s.sessionId.slice(0, 8) : '-'))}
|
|
624
|
+
${dashRow('Provider', `${s.provider || '-'} / ${s.model || '-'}`)}
|
|
625
|
+
${dashRow('Messages', String(s.messageCount ?? 0))}
|
|
626
|
+
${dashRow('Mode', `${s.planMode ? 'plan' : 'execute'}${s.thinkingMode ? ' · thinking' : ''}${s.autoMode ? ' · auto' : ''}`)}
|
|
627
|
+
</section>
|
|
628
|
+
<section class="dashboard-section">
|
|
629
|
+
<div class="dashboard-section-title">Approvals <span class="confirm-queue-count">${pending.length}</span></div>
|
|
630
|
+
${pending.length ? pending.map(renderDashboardApproval).join('') : '<div class="dashboard-empty">No pending approvals.</div>'}
|
|
631
|
+
</section>
|
|
632
|
+
<section class="dashboard-section">
|
|
633
|
+
<div class="dashboard-section-title">Sub-Agents</div>
|
|
634
|
+
${dashRow('Running', String(runningAgents.length))}
|
|
635
|
+
${dashRow('Errors', String(erroredAgents.length))}
|
|
636
|
+
${agents.length ? agents.slice(0, 5).map(renderDashboardAgent).join('') : '<div class="dashboard-empty">No agent runs yet.</div>'}
|
|
637
|
+
<div class="dashboard-actions"><button class="btn btn-xs btn-ghost" data-dashboard-command="agent summary">summary</button><button class="btn btn-xs btn-ghost" data-dashboard-command="agent list">list</button></div>
|
|
638
|
+
</section>
|
|
639
|
+
<section class="dashboard-section">
|
|
640
|
+
<div class="dashboard-section-title">Permissions & Cost</div>
|
|
641
|
+
${dashRow('Profile', s.permissionProfile || 'legacy')}
|
|
642
|
+
${dashRow('Network', s.networkPolicyEnabled ? 'enabled' : 'off')}
|
|
643
|
+
${dashRow('Hooks', String(s.pendingHookTrustCount || 0))}
|
|
644
|
+
${dashRow('Tokens', totalTokens.toLocaleString())}
|
|
645
|
+
${dashRow('Cost', cost)}
|
|
646
|
+
</section>
|
|
647
|
+
<section class="dashboard-section">
|
|
648
|
+
<div class="dashboard-section-title">Recent Tests / Errors</div>
|
|
649
|
+
${recentTests.length ? recentTests.map(renderDashboardToolResult).join('') : '<div class="dashboard-empty">No recent run_tests result.</div>'}
|
|
650
|
+
${recentToolErrors.length ? '<div class="dashboard-section-title mt-2">Tool Errors</div>' + recentToolErrors.map(renderDashboardToolResult).join('') : ''}
|
|
651
|
+
</section>
|
|
652
|
+
`;
|
|
653
|
+
|
|
654
|
+
dashboardPanel.querySelectorAll('[data-dashboard-command]').forEach(btn => {
|
|
655
|
+
btn.addEventListener('click', () => {
|
|
656
|
+
const parts = btn.dataset.dashboardCommand.split(/\s+/);
|
|
657
|
+
send({ type: 'command', name: parts[0], args: parts.slice(1) });
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function dashRow(label, value) {
|
|
663
|
+
return `<div class="dashboard-row"><span class="label">${escapeHtml(label)}</span><span class="value" title="${escapeHtml(value)}">${escapeHtml(value)}</span></div>`;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function renderDashboardApproval(item) {
|
|
667
|
+
const label = item.kind === 'batch' ? `${item.files?.length || 0} file batch` : item.toolName;
|
|
668
|
+
return `<div class="dashboard-item running"><div class="font-semibold truncate">${escapeHtml(label || item.kind)}</div><div class="text-xs opacity-60 truncate">${escapeHtml(item.dangerLevel || item.kind)} · ${escapeHtml(item.requestId)}</div></div>`;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function renderDashboardAgent(agent) {
|
|
672
|
+
const cls = agent.status || 'ok';
|
|
673
|
+
const task = agent.task || '';
|
|
674
|
+
return `<div class="dashboard-item ${escapeHtml(cls)}"><div class="font-semibold truncate">${escapeHtml(agent.agentName || 'agent')} · ${escapeHtml(cls)}</div><div class="text-xs opacity-60 truncate" title="${escapeHtml(task)}">${escapeHtml(task || '(no task)')}</div>${agent.summary ? `<div class="text-xs opacity-80 truncate">${escapeHtml(agent.summary)}</div>` : ''}</div>`;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function renderDashboardToolResult(result) {
|
|
678
|
+
const cls = result.isError ? 'error' : 'ok';
|
|
679
|
+
const content = result.content || '';
|
|
680
|
+
return `<div class="dashboard-item ${cls}"><div class="font-semibold truncate">${escapeHtml(result.toolName || 'tool')} · ${result.isError ? 'error' : 'ok'}</div><div class="text-xs opacity-60 truncate" title="${escapeHtml(content)}">${escapeHtml(content.slice(0, 120))}</div></div>`;
|
|
681
|
+
}
|
|
579
682
|
// ── Response helpers ───────────────────────────────────────────────
|
|
580
683
|
|
|
581
684
|
window.respondConfirm = function(requestId, approved) {
|
|
582
685
|
send({ type: 'confirm_response', requestId, approved });
|
|
686
|
+
pendingApprovals.delete(requestId);
|
|
687
|
+
renderDashboard();
|
|
583
688
|
const el = document.querySelector(`[data-request-id="${requestId}"]`);
|
|
584
689
|
if (el) {
|
|
585
690
|
el.className = `confirm-card ${approved ? 'tool-border-safe' : 'tool-border-destructive'} my-1 opacity-60`;
|
|
@@ -591,7 +696,7 @@ window.respondBatchConfirm = function(requestId, btn) {
|
|
|
591
696
|
const dialog = btn.closest('.batch-confirm');
|
|
592
697
|
const checks = dialog.querySelectorAll('input[type="checkbox"]');
|
|
593
698
|
const selected = [];
|
|
594
|
-
checks.forEach(c => { if (c.checked) selected.push(parseInt(c.dataset.index)); });
|
|
699
|
+
checks.forEach(c => { if (c.checked) selected.push(parseInt(c.dataset.index, 10)); });
|
|
595
700
|
|
|
596
701
|
if (selected.length === checks.length) {
|
|
597
702
|
send({ type: 'batch_confirm_response', requestId, decision: 'all' });
|
|
@@ -600,12 +705,16 @@ window.respondBatchConfirm = function(requestId, btn) {
|
|
|
600
705
|
} else {
|
|
601
706
|
send({ type: 'batch_confirm_response', requestId, decision: selected });
|
|
602
707
|
}
|
|
708
|
+
pendingApprovals.delete(requestId);
|
|
709
|
+
renderDashboard();
|
|
603
710
|
dialog.className = 'confirm-card tool-border-safe my-1 opacity-60';
|
|
604
711
|
dialog.innerHTML = `<span class="text-sm text-success">✓ Approved ${selected.length}/${checks.length} files</span>`;
|
|
605
712
|
};
|
|
606
713
|
|
|
607
714
|
window.respondBatchDeny = function(requestId, btn) {
|
|
608
715
|
send({ type: 'batch_confirm_response', requestId, decision: 'none' });
|
|
716
|
+
pendingApprovals.delete(requestId);
|
|
717
|
+
renderDashboard();
|
|
609
718
|
const dialog = btn.closest('.batch-confirm');
|
|
610
719
|
dialog.className = 'confirm-card tool-border-destructive my-1 opacity-60';
|
|
611
720
|
dialog.innerHTML = `<span class="text-sm text-error">✗ All rejected</span>`;
|
|
@@ -615,6 +724,8 @@ window.respondAskUser = function(requestId) {
|
|
|
615
724
|
const input = document.getElementById(`ask-input-${requestId}`);
|
|
616
725
|
const answer = input ? input.value : '';
|
|
617
726
|
send({ type: 'ask_user_response', requestId, answer: answer || null });
|
|
727
|
+
pendingApprovals.delete(requestId);
|
|
728
|
+
renderDashboard();
|
|
618
729
|
const dialog = input?.closest('.confirm-card');
|
|
619
730
|
if (dialog) {
|
|
620
731
|
dialog.className = 'confirm-card tool-border-safe my-1 opacity-60';
|
|
@@ -646,6 +757,16 @@ window.respondAutoPause = function(requestId, action) {
|
|
|
646
757
|
}
|
|
647
758
|
};
|
|
648
759
|
|
|
760
|
+
|
|
761
|
+
window.copyConfirmDiff = function(el) {
|
|
762
|
+
const diff = el?.dataset?.diff || '';
|
|
763
|
+
if (!diff) return;
|
|
764
|
+
navigator.clipboard.writeText(diff).then(() => {
|
|
765
|
+
const old = el.textContent;
|
|
766
|
+
el.textContent = 'Copied';
|
|
767
|
+
setTimeout(() => { el.textContent = old || 'Copy'; }, 1200);
|
|
768
|
+
}).catch(() => addInfoMessage('Could not copy patch.'));
|
|
769
|
+
};
|
|
649
770
|
// DaisyUI light themes → highlight.js light stylesheet; others → dark
|
|
650
771
|
const LIGHT_DAISYUI_THEMES = new Set(['light', 'cupcake', 'bumblebee', 'emerald', 'corporate', 'garden', 'lofi', 'pastel', 'fantasy', 'wireframe', 'cmyk', 'autumn', 'acid', 'lemonade', 'winter', 'nord']);
|
|
651
772
|
// v0.4.153: vendored locally (was cdn.jsdelivr.net) — see index.html.
|
|
@@ -1121,7 +1242,11 @@ function renderReplay(session, metaEl, usageEl, timelineEl) {
|
|
|
1121
1242
|
`<span class="badge badge-sm badge-ghost">cache-read ${tu.cacheReadTokens || 0}</span>`;
|
|
1122
1243
|
|
|
1123
1244
|
const messages = Array.isArray(session.messages) ? session.messages : [];
|
|
1245
|
+
replayCurrentMessages = messages;
|
|
1246
|
+
replayCurrentFilter = 'all';
|
|
1124
1247
|
timelineEl.innerHTML = messages.map((m, i) => renderReplayStep(m, i)).join('');
|
|
1248
|
+
wireReplayFilters();
|
|
1249
|
+
applyReplayFilter();
|
|
1125
1250
|
|
|
1126
1251
|
// Wire 🌿 fork-from-here buttons.
|
|
1127
1252
|
timelineEl.querySelectorAll('.replay-fork-btn').forEach((btn) => {
|
|
@@ -1156,6 +1281,9 @@ function renderReplayStep(m, idx) {
|
|
|
1156
1281
|
const role = m.role || 'user';
|
|
1157
1282
|
const ts = m.timestamp ? new Date(m.timestamp).toLocaleTimeString() : '';
|
|
1158
1283
|
const isError = !!m.isError;
|
|
1284
|
+
const hasToolCalls = Array.isArray(m.toolCalls) && m.toolCalls.length > 0;
|
|
1285
|
+
const isAgentRelated = (hasToolCalls && m.toolCalls.some(tc => (tc.name || tc.function?.name || '').includes('agent'))) || String(m.toolName || '').includes('agent');
|
|
1286
|
+
const isCostRelated = !!(m.usage || m.tokenUsage) || /token|cost|usage/i.test(String(m.content || ''));
|
|
1159
1287
|
const cls = 'replay-step role-' + role + (isError ? ' error' : '');
|
|
1160
1288
|
const roleTag = role + (m.toolName ? ' · ' + m.toolName : '') + (isError ? ' · ERROR' : '');
|
|
1161
1289
|
|
|
@@ -1194,7 +1322,7 @@ function renderReplayStep(m, idx) {
|
|
|
1194
1322
|
body += `<details class="replay-tool-block"><summary class="opacity-70">💭 reasoning</summary><pre>${escapeHtml(m.reasoningContent)}</pre></details>`;
|
|
1195
1323
|
}
|
|
1196
1324
|
|
|
1197
|
-
return `<div class="${cls}">
|
|
1325
|
+
return `<div class="${cls}" data-has-tools="${hasToolCalls || role === 'tool' ? '1' : '0'}" data-is-agent="${isAgentRelated ? '1' : '0'}" data-is-error="${isError ? '1' : '0'}" data-is-cost="${isCostRelated ? '1' : '0'}">
|
|
1198
1326
|
<div class="replay-step-header">
|
|
1199
1327
|
<span class="opacity-50">#${idx + 1}</span>
|
|
1200
1328
|
<span class="role-tag">${escapeHtml(roleTag)}</span>
|
|
@@ -1206,6 +1334,40 @@ function renderReplayStep(m, idx) {
|
|
|
1206
1334
|
</div>`;
|
|
1207
1335
|
}
|
|
1208
1336
|
|
|
1337
|
+
|
|
1338
|
+
function wireReplayFilters() {
|
|
1339
|
+
document.querySelectorAll('[data-replay-filter]').forEach(btn => {
|
|
1340
|
+
const active = btn.dataset.replayFilter === replayCurrentFilter;
|
|
1341
|
+
btn.classList.toggle('btn-primary', active);
|
|
1342
|
+
btn.classList.toggle('btn-outline', active);
|
|
1343
|
+
btn.classList.toggle('btn-ghost', !active);
|
|
1344
|
+
if (btn.dataset.replayFilterWired === '1') return;
|
|
1345
|
+
btn.dataset.replayFilterWired = '1';
|
|
1346
|
+
btn.addEventListener('click', () => {
|
|
1347
|
+
replayCurrentFilter = btn.dataset.replayFilter || 'all';
|
|
1348
|
+
document.querySelectorAll('[data-replay-filter]').forEach(b => {
|
|
1349
|
+
const active = b.dataset.replayFilter === replayCurrentFilter;
|
|
1350
|
+
b.classList.toggle('btn-primary', active);
|
|
1351
|
+
b.classList.toggle('btn-outline', active);
|
|
1352
|
+
b.classList.toggle('btn-ghost', !active);
|
|
1353
|
+
});
|
|
1354
|
+
applyReplayFilter();
|
|
1355
|
+
});
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
|
|
1360
|
+
function applyReplayFilter() {
|
|
1361
|
+
const steps = document.querySelectorAll('#replay-timeline .replay-step');
|
|
1362
|
+
steps.forEach(step => {
|
|
1363
|
+
let show = true;
|
|
1364
|
+
if (replayCurrentFilter === 'tools') show = step.dataset.hasTools === '1';
|
|
1365
|
+
else if (replayCurrentFilter === 'agents') show = step.dataset.isAgent === '1';
|
|
1366
|
+
else if (replayCurrentFilter === 'errors') show = step.dataset.isError === '1';
|
|
1367
|
+
else if (replayCurrentFilter === 'cost') show = step.dataset.isCost === '1';
|
|
1368
|
+
step.classList.toggle('hidden-by-filter', !show);
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1209
1371
|
// ── B2: Branch sidebar panel ─────────────────────────────
|
|
1210
1372
|
let _cachedBranches = [];
|
|
1211
1373
|
let _cachedActiveBranchId = 'main';
|
|
@@ -1571,11 +1733,16 @@ function switchSidebarTab(tabName) {
|
|
|
1571
1733
|
fileTreeLoaded = true;
|
|
1572
1734
|
initFileTree();
|
|
1573
1735
|
}
|
|
1736
|
+
if (tabName === 'dashboard') renderDashboard();
|
|
1574
1737
|
}
|
|
1575
1738
|
|
|
1576
1739
|
document.querySelectorAll('.sidebar-tab').forEach(btn => {
|
|
1577
1740
|
btn.addEventListener('click', () => switchSidebarTab(btn.dataset.tab));
|
|
1578
1741
|
});
|
|
1742
|
+
btnDashboardRefresh?.addEventListener('click', () => {
|
|
1743
|
+
send({ type: 'command', name: 'status', args: [] });
|
|
1744
|
+
renderDashboard();
|
|
1745
|
+
});
|
|
1579
1746
|
|
|
1580
1747
|
// ── Tools list rendering ──────────────────────────────────────────────
|
|
1581
1748
|
|
|
@@ -2850,7 +3017,7 @@ function renderMemoryHits(msg) {
|
|
|
2850
3017
|
const header = `
|
|
2851
3018
|
<div class="flex items-center gap-1 text-xs pb-1 mb-1 border-b border-base-content/10">
|
|
2852
3019
|
<span class="opacity-60 flex-1">${msg.hits.length} hit(s) for "${escapeHtml(msg.query)}"</span>
|
|
2853
|
-
<button class="btn btn-xs btn-primary btn-outline" id="btn-memory-inject-top3" title="
|
|
3020
|
+
<button class="btn btn-xs btn-primary btn-outline" id="btn-memory-inject-top3" title="Manually approve injecting top 3 hits into chat input">Approve top 3</button>
|
|
2854
3021
|
</div>
|
|
2855
3022
|
`;
|
|
2856
3023
|
|
|
@@ -2862,13 +3029,18 @@ function renderMemoryHits(msg) {
|
|
|
2862
3029
|
return `
|
|
2863
3030
|
<div class="memory-hit card bg-base-100 border border-base-content/10 p-2 hover:border-primary/40"
|
|
2864
3031
|
data-hit-id="${escapeHtml(h.id)}">
|
|
3032
|
+
<div class="memory-meta-badges">
|
|
3033
|
+
<span class="badge badge-xs badge-ghost">scope: chat</span>
|
|
3034
|
+
<span class="badge badge-xs badge-ghost">source: ${escapeHtml(h.sessionId.slice(0, 8))}</span>
|
|
3035
|
+
<span class="badge badge-xs badge-ghost">manual inject</span>
|
|
3036
|
+
</div>
|
|
2865
3037
|
<div class="flex items-center gap-1 text-xs opacity-70 mb-1">
|
|
2866
3038
|
<span class="font-semibold truncate flex-1">${escapeHtml(title)}</span>
|
|
2867
3039
|
<span class="badge badge-xs">${score}</span>
|
|
2868
3040
|
</div>
|
|
2869
3041
|
<div class="text-xs opacity-50 mb-1 flex items-center gap-2">
|
|
2870
3042
|
<span class="flex-1">${ts} · session ${escapeHtml(h.sessionId.slice(0, 8))}</span>
|
|
2871
|
-
<button class="btn btn-xs btn-ghost memory-hit-inject" title="
|
|
3043
|
+
<button class="btn btn-xs btn-ghost memory-hit-inject" title="Manually approve injecting this hit into chat input">Approve inject</button>
|
|
2872
3044
|
<button class="btn btn-xs btn-ghost memory-hit-load" title="Load this session in current tab">↗</button>
|
|
2873
3045
|
</div>
|
|
2874
3046
|
<div class="text-xs whitespace-pre-wrap leading-snug opacity-90" style="max-height: 8em; overflow: hidden;">${escapeHtml(preview)}</div>
|
|
@@ -108,15 +108,26 @@
|
|
|
108
108
|
<aside id="sidebar" class="sidebar bg-base-200 border-r border-base-content/10 flex flex-col flex-shrink-0 overflow-hidden">
|
|
109
109
|
<!-- Sidebar tabs -->
|
|
110
110
|
<div class="flex border-b border-base-content/10 flex-shrink-0">
|
|
111
|
-
<button class="sidebar-tab active flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="
|
|
111
|
+
<button class="sidebar-tab active flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="dashboard" title="Agent control dashboard">⌘ Dash</button>
|
|
112
|
+
<button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="sessions">📋 Sessions</button>
|
|
112
113
|
<button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="branches" title="Conversation branches (B2)">🌿 Branches</button>
|
|
113
114
|
<button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="tools">🔧 Tools</button>
|
|
114
115
|
<button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="files">📁 Files</button>
|
|
115
116
|
<button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="memory" title="Chat memory semantic recall (B4, v0.4.90)">🧠 Memory</button>
|
|
116
117
|
<button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="hub" title="Multi-agent brainstorm room (P4)">🏛 Hub</button>
|
|
117
118
|
</div>
|
|
119
|
+
<!-- Dashboard tab (Phase 6) -->
|
|
120
|
+
<div id="tab-dashboard" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden">
|
|
121
|
+
<div class="p-2 border-b border-base-content/10 flex items-center gap-1">
|
|
122
|
+
<span class="text-xs font-semibold flex-1">Agent Console</span>
|
|
123
|
+
<button id="btn-dashboard-refresh" class="btn btn-xs btn-ghost" title="Refresh dashboard">↻</button>
|
|
124
|
+
</div>
|
|
125
|
+
<div id="dashboard-panel" class="dashboard-panel flex-1 overflow-y-auto p-2 text-sm">
|
|
126
|
+
<div class="text-xs opacity-40 text-center py-4">Waiting for status…</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
118
129
|
<!-- Sessions tab -->
|
|
119
|
-
<div id="tab-sessions" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden">
|
|
130
|
+
<div id="tab-sessions" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
|
|
120
131
|
<div class="p-2 border-b border-base-content/10 flex items-center justify-between gap-1">
|
|
121
132
|
<input id="session-search" type="text" class="input input-xs input-bordered flex-1 min-w-0" placeholder="Search...">
|
|
122
133
|
<button id="btn-batch-select" class="btn btn-xs btn-ghost flex-shrink-0" title="Select multiple" data-action="toggle-batch-select">☑</button>
|
|
@@ -332,7 +343,14 @@
|
|
|
332
343
|
<form method="dialog"><button class="btn btn-sm btn-ghost">✕</button></form>
|
|
333
344
|
</div>
|
|
334
345
|
<div id="replay-meta" class="text-xs opacity-70 mb-2"></div>
|
|
335
|
-
<div id="replay-usage" class="text-xs mb-
|
|
346
|
+
<div id="replay-usage" class="text-xs mb-2"></div>
|
|
347
|
+
<div id="replay-filters" class="replay-filters flex flex-wrap gap-1 mb-3">
|
|
348
|
+
<button class="btn btn-xs btn-primary btn-outline" data-replay-filter="all">All</button>
|
|
349
|
+
<button class="btn btn-xs btn-ghost" data-replay-filter="tools">Tools</button>
|
|
350
|
+
<button class="btn btn-xs btn-ghost" data-replay-filter="agents">Agents</button>
|
|
351
|
+
<button class="btn btn-xs btn-ghost" data-replay-filter="errors">Errors</button>
|
|
352
|
+
<button class="btn btn-xs btn-ghost" data-replay-filter="cost">Cost</button>
|
|
353
|
+
</div>
|
|
336
354
|
<div id="replay-timeline" class="flex flex-col gap-2 max-h-[70vh] overflow-y-auto pr-2"></div>
|
|
337
355
|
</div>
|
|
338
356
|
<form method="dialog" class="modal-backdrop"><button>close</button></form>
|
|
@@ -989,3 +989,65 @@ button, a, .session-item, .file-tree-row, .template-item, .tool-item, .mcp-serve
|
|
|
989
989
|
font-size: 0.72rem;
|
|
990
990
|
white-space: pre;
|
|
991
991
|
}
|
|
992
|
+
|
|
993
|
+
/* ── Phase 6 Dashboard ─────────────────────────────── */
|
|
994
|
+
.dashboard-panel {
|
|
995
|
+
display: flex;
|
|
996
|
+
flex-direction: column;
|
|
997
|
+
gap: 0.5rem;
|
|
998
|
+
}
|
|
999
|
+
.dashboard-section {
|
|
1000
|
+
border: 1px solid oklch(var(--bc) / 0.1);
|
|
1001
|
+
border-radius: 0.5rem;
|
|
1002
|
+
background: oklch(var(--b1));
|
|
1003
|
+
padding: 0.55rem;
|
|
1004
|
+
}
|
|
1005
|
+
.dashboard-section-title {
|
|
1006
|
+
display: flex;
|
|
1007
|
+
align-items: center;
|
|
1008
|
+
gap: 0.35rem;
|
|
1009
|
+
font-size: 0.72rem;
|
|
1010
|
+
font-weight: 700;
|
|
1011
|
+
text-transform: uppercase;
|
|
1012
|
+
letter-spacing: 0.04em;
|
|
1013
|
+
opacity: 0.65;
|
|
1014
|
+
margin-bottom: 0.4rem;
|
|
1015
|
+
}
|
|
1016
|
+
.dashboard-row {
|
|
1017
|
+
display: flex;
|
|
1018
|
+
align-items: center;
|
|
1019
|
+
gap: 0.4rem;
|
|
1020
|
+
min-width: 0;
|
|
1021
|
+
padding: 0.18rem 0;
|
|
1022
|
+
font-size: 0.78rem;
|
|
1023
|
+
}
|
|
1024
|
+
.dashboard-row .label { opacity: 0.55; min-width: 4.2rem; }
|
|
1025
|
+
.dashboard-row .value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1026
|
+
.dashboard-item {
|
|
1027
|
+
border-left: 2px solid oklch(var(--bc) / 0.15);
|
|
1028
|
+
padding: 0.35rem 0.45rem;
|
|
1029
|
+
margin-top: 0.3rem;
|
|
1030
|
+
background: oklch(var(--b2));
|
|
1031
|
+
border-radius: 0.35rem;
|
|
1032
|
+
}
|
|
1033
|
+
.dashboard-item.running { border-left-color: oklch(var(--wa)); }
|
|
1034
|
+
.dashboard-item.ok { border-left-color: oklch(var(--su)); }
|
|
1035
|
+
.dashboard-item.error { border-left-color: oklch(var(--er)); }
|
|
1036
|
+
.dashboard-item.stopped { border-left-color: oklch(var(--bc) / 0.35); }
|
|
1037
|
+
.dashboard-actions { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-top: 0.4rem; }
|
|
1038
|
+
.dashboard-empty { font-size: 0.75rem; opacity: 0.45; padding: 0.35rem 0; }
|
|
1039
|
+
.confirm-queue-count { font-weight: 700; color: oklch(var(--wa)); }
|
|
1040
|
+
.diff-file-details {
|
|
1041
|
+
border: 1px solid oklch(var(--bc) / 0.1);
|
|
1042
|
+
border-radius: 0.4rem;
|
|
1043
|
+
margin: 0.35rem 0;
|
|
1044
|
+
overflow: hidden;
|
|
1045
|
+
}
|
|
1046
|
+
.diff-file-details summary {
|
|
1047
|
+
padding: 0.35rem 0.45rem;
|
|
1048
|
+
cursor: pointer;
|
|
1049
|
+
background: oklch(var(--b3));
|
|
1050
|
+
}
|
|
1051
|
+
.diff-file-details .confirm-diff { margin: 0; border-radius: 0; max-height: 180px; }
|
|
1052
|
+
.memory-meta-badges { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-bottom: 0.35rem; }
|
|
1053
|
+
.replay-step.hidden-by-filter { display: none; }
|