jinzd-ai-cli 0.4.214 → 0.4.216
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 -0
- package/dist/{batch-NPUUDETK.js → batch-C6HCXZIG.js} +2 -2
- package/dist/{chunk-JQC7IATA.js → chunk-6HLKCSB3.js} +1 -1
- package/dist/{chunk-YMFGGCUP.js → chunk-77HDCGTN.js} +1 -1
- package/dist/{chunk-DBYMX2ZC.js → chunk-7YWS3JLU.js} +1 -1
- package/dist/{ci-3TU7WCY4.js → chunk-GY3C3C2Y.js} +40 -46
- package/dist/{chunk-JOJA35RK.js → chunk-OLN7VUZA.js} +1 -1
- package/dist/{chunk-5FG7PSVM.js → chunk-PNTLA3MN.js} +2 -2
- package/dist/{chunk-R5CJD2AB.js → chunk-TCOC4AUI.js} +3 -3
- package/dist/chunk-TJ6GGN4B.js +105 -0
- package/dist/{chunk-TR6MSPSS.js → chunk-VW7Y27WW.js} +1 -1
- package/dist/{chunk-YFHF3PVB.js → chunk-WAI3WPV2.js} +1 -1
- package/dist/ci-6ZTFO3LX.js +19 -0
- package/dist/ci-format-CLZ6QJRL.js +18 -0
- package/dist/{constants-BKR7T3QH.js → constants-APSORFOH.js} +1 -1
- package/dist/{doctor-cli-TGVSWKDB.js → doctor-cli-GUIX4X5F.js} +4 -4
- package/dist/electron-server.js +2 -2
- package/dist/{hub-GURYEW6F.js → hub-ZGHQWNWE.js} +1 -1
- package/dist/index.js +58 -22
- package/dist/pr-JOL3IAGV.js +267 -0
- package/dist/{run-tests-R6FL6CQ3.js → run-tests-4PKSIVK5.js} +1 -1
- package/dist/{run-tests-F44JA2YW.js → run-tests-ZRK4TQUN.js} +2 -2
- package/dist/{server-FXWCSCDZ.js → server-FXUF5P64.js} +7 -7
- package/dist/{server-GQXC46R5.js → server-O3XHT56X.js} +4 -4
- package/dist/{task-orchestrator-6AZD5WSR.js → task-orchestrator-TUMDTOAX.js} +4 -4
- package/dist/{usage-W7J2QA3G.js → usage-X7MJX4YD.js} +2 -2
- package/dist/web/client/actions.js +1 -0
- package/dist/web/client/app.js +182 -13
- 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,9 +568,9 @@ 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
|
-
const runningAgents = Array.isArray(msg.agents) ? msg.agents.filter(a => a.status === 'running').length : 0;
|
|
552
|
-
const failedAgents = Array.isArray(msg.agents) ? msg.agents.filter(a => a.status === 'error').length : 0;
|
|
553
|
-
const agentStatus = (runningAgents ? ' · agents:' + runningAgents : '') + (failedAgents ? ' · agent-errors:' + failedAgents : '');
|
|
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 : '');
|
|
554
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;
|
|
555
575
|
if (msg.tokenUsage) {
|
|
556
576
|
const u = msg.tokenUsage;
|
|
@@ -573,16 +593,98 @@ function handleStatus(msg) {
|
|
|
573
593
|
targetTab.isProcessing = processing;
|
|
574
594
|
const title = msg.sessionTitle || msg.sessionId?.slice(0, 8) || 'New Session';
|
|
575
595
|
document.title = `ai-cli — ${title}`;
|
|
596
|
+
latestStatus = msg;
|
|
597
|
+
renderDashboard();
|
|
576
598
|
}
|
|
577
599
|
|
|
578
600
|
renderTabBar();
|
|
579
601
|
saveTabState();
|
|
580
602
|
}
|
|
581
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
|
+
}
|
|
582
682
|
// ── Response helpers ───────────────────────────────────────────────
|
|
583
683
|
|
|
584
684
|
window.respondConfirm = function(requestId, approved) {
|
|
585
685
|
send({ type: 'confirm_response', requestId, approved });
|
|
686
|
+
pendingApprovals.delete(requestId);
|
|
687
|
+
renderDashboard();
|
|
586
688
|
const el = document.querySelector(`[data-request-id="${requestId}"]`);
|
|
587
689
|
if (el) {
|
|
588
690
|
el.className = `confirm-card ${approved ? 'tool-border-safe' : 'tool-border-destructive'} my-1 opacity-60`;
|
|
@@ -594,7 +696,7 @@ window.respondBatchConfirm = function(requestId, btn) {
|
|
|
594
696
|
const dialog = btn.closest('.batch-confirm');
|
|
595
697
|
const checks = dialog.querySelectorAll('input[type="checkbox"]');
|
|
596
698
|
const selected = [];
|
|
597
|
-
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)); });
|
|
598
700
|
|
|
599
701
|
if (selected.length === checks.length) {
|
|
600
702
|
send({ type: 'batch_confirm_response', requestId, decision: 'all' });
|
|
@@ -603,12 +705,16 @@ window.respondBatchConfirm = function(requestId, btn) {
|
|
|
603
705
|
} else {
|
|
604
706
|
send({ type: 'batch_confirm_response', requestId, decision: selected });
|
|
605
707
|
}
|
|
708
|
+
pendingApprovals.delete(requestId);
|
|
709
|
+
renderDashboard();
|
|
606
710
|
dialog.className = 'confirm-card tool-border-safe my-1 opacity-60';
|
|
607
711
|
dialog.innerHTML = `<span class="text-sm text-success">✓ Approved ${selected.length}/${checks.length} files</span>`;
|
|
608
712
|
};
|
|
609
713
|
|
|
610
714
|
window.respondBatchDeny = function(requestId, btn) {
|
|
611
715
|
send({ type: 'batch_confirm_response', requestId, decision: 'none' });
|
|
716
|
+
pendingApprovals.delete(requestId);
|
|
717
|
+
renderDashboard();
|
|
612
718
|
const dialog = btn.closest('.batch-confirm');
|
|
613
719
|
dialog.className = 'confirm-card tool-border-destructive my-1 opacity-60';
|
|
614
720
|
dialog.innerHTML = `<span class="text-sm text-error">✗ All rejected</span>`;
|
|
@@ -618,6 +724,8 @@ window.respondAskUser = function(requestId) {
|
|
|
618
724
|
const input = document.getElementById(`ask-input-${requestId}`);
|
|
619
725
|
const answer = input ? input.value : '';
|
|
620
726
|
send({ type: 'ask_user_response', requestId, answer: answer || null });
|
|
727
|
+
pendingApprovals.delete(requestId);
|
|
728
|
+
renderDashboard();
|
|
621
729
|
const dialog = input?.closest('.confirm-card');
|
|
622
730
|
if (dialog) {
|
|
623
731
|
dialog.className = 'confirm-card tool-border-safe my-1 opacity-60';
|
|
@@ -649,6 +757,16 @@ window.respondAutoPause = function(requestId, action) {
|
|
|
649
757
|
}
|
|
650
758
|
};
|
|
651
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
|
+
};
|
|
652
770
|
// DaisyUI light themes → highlight.js light stylesheet; others → dark
|
|
653
771
|
const LIGHT_DAISYUI_THEMES = new Set(['light', 'cupcake', 'bumblebee', 'emerald', 'corporate', 'garden', 'lofi', 'pastel', 'fantasy', 'wireframe', 'cmyk', 'autumn', 'acid', 'lemonade', 'winter', 'nord']);
|
|
654
772
|
// v0.4.153: vendored locally (was cdn.jsdelivr.net) — see index.html.
|
|
@@ -1124,7 +1242,11 @@ function renderReplay(session, metaEl, usageEl, timelineEl) {
|
|
|
1124
1242
|
`<span class="badge badge-sm badge-ghost">cache-read ${tu.cacheReadTokens || 0}</span>`;
|
|
1125
1243
|
|
|
1126
1244
|
const messages = Array.isArray(session.messages) ? session.messages : [];
|
|
1245
|
+
replayCurrentMessages = messages;
|
|
1246
|
+
replayCurrentFilter = 'all';
|
|
1127
1247
|
timelineEl.innerHTML = messages.map((m, i) => renderReplayStep(m, i)).join('');
|
|
1248
|
+
wireReplayFilters();
|
|
1249
|
+
applyReplayFilter();
|
|
1128
1250
|
|
|
1129
1251
|
// Wire 🌿 fork-from-here buttons.
|
|
1130
1252
|
timelineEl.querySelectorAll('.replay-fork-btn').forEach((btn) => {
|
|
@@ -1159,6 +1281,9 @@ function renderReplayStep(m, idx) {
|
|
|
1159
1281
|
const role = m.role || 'user';
|
|
1160
1282
|
const ts = m.timestamp ? new Date(m.timestamp).toLocaleTimeString() : '';
|
|
1161
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 || ''));
|
|
1162
1287
|
const cls = 'replay-step role-' + role + (isError ? ' error' : '');
|
|
1163
1288
|
const roleTag = role + (m.toolName ? ' · ' + m.toolName : '') + (isError ? ' · ERROR' : '');
|
|
1164
1289
|
|
|
@@ -1197,7 +1322,7 @@ function renderReplayStep(m, idx) {
|
|
|
1197
1322
|
body += `<details class="replay-tool-block"><summary class="opacity-70">💭 reasoning</summary><pre>${escapeHtml(m.reasoningContent)}</pre></details>`;
|
|
1198
1323
|
}
|
|
1199
1324
|
|
|
1200
|
-
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'}">
|
|
1201
1326
|
<div class="replay-step-header">
|
|
1202
1327
|
<span class="opacity-50">#${idx + 1}</span>
|
|
1203
1328
|
<span class="role-tag">${escapeHtml(roleTag)}</span>
|
|
@@ -1209,6 +1334,40 @@ function renderReplayStep(m, idx) {
|
|
|
1209
1334
|
</div>`;
|
|
1210
1335
|
}
|
|
1211
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
|
+
}
|
|
1212
1371
|
// ── B2: Branch sidebar panel ─────────────────────────────
|
|
1213
1372
|
let _cachedBranches = [];
|
|
1214
1373
|
let _cachedActiveBranchId = 'main';
|
|
@@ -1574,11 +1733,16 @@ function switchSidebarTab(tabName) {
|
|
|
1574
1733
|
fileTreeLoaded = true;
|
|
1575
1734
|
initFileTree();
|
|
1576
1735
|
}
|
|
1736
|
+
if (tabName === 'dashboard') renderDashboard();
|
|
1577
1737
|
}
|
|
1578
1738
|
|
|
1579
1739
|
document.querySelectorAll('.sidebar-tab').forEach(btn => {
|
|
1580
1740
|
btn.addEventListener('click', () => switchSidebarTab(btn.dataset.tab));
|
|
1581
1741
|
});
|
|
1742
|
+
btnDashboardRefresh?.addEventListener('click', () => {
|
|
1743
|
+
send({ type: 'command', name: 'status', args: [] });
|
|
1744
|
+
renderDashboard();
|
|
1745
|
+
});
|
|
1582
1746
|
|
|
1583
1747
|
// ── Tools list rendering ──────────────────────────────────────────────
|
|
1584
1748
|
|
|
@@ -2853,7 +3017,7 @@ function renderMemoryHits(msg) {
|
|
|
2853
3017
|
const header = `
|
|
2854
3018
|
<div class="flex items-center gap-1 text-xs pb-1 mb-1 border-b border-base-content/10">
|
|
2855
3019
|
<span class="opacity-60 flex-1">${msg.hits.length} hit(s) for "${escapeHtml(msg.query)}"</span>
|
|
2856
|
-
<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>
|
|
2857
3021
|
</div>
|
|
2858
3022
|
`;
|
|
2859
3023
|
|
|
@@ -2865,13 +3029,18 @@ function renderMemoryHits(msg) {
|
|
|
2865
3029
|
return `
|
|
2866
3030
|
<div class="memory-hit card bg-base-100 border border-base-content/10 p-2 hover:border-primary/40"
|
|
2867
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>
|
|
2868
3037
|
<div class="flex items-center gap-1 text-xs opacity-70 mb-1">
|
|
2869
3038
|
<span class="font-semibold truncate flex-1">${escapeHtml(title)}</span>
|
|
2870
3039
|
<span class="badge badge-xs">${score}</span>
|
|
2871
3040
|
</div>
|
|
2872
3041
|
<div class="text-xs opacity-50 mb-1 flex items-center gap-2">
|
|
2873
3042
|
<span class="flex-1">${ts} · session ${escapeHtml(h.sessionId.slice(0, 8))}</span>
|
|
2874
|
-
<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>
|
|
2875
3044
|
<button class="btn btn-xs btn-ghost memory-hit-load" title="Load this session in current tab">↗</button>
|
|
2876
3045
|
</div>
|
|
2877
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; }
|