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.
Files changed (31) hide show
  1. package/README.md +2 -0
  2. package/dist/{batch-NPUUDETK.js → batch-C6HCXZIG.js} +2 -2
  3. package/dist/{chunk-JQC7IATA.js → chunk-6HLKCSB3.js} +1 -1
  4. package/dist/{chunk-YMFGGCUP.js → chunk-77HDCGTN.js} +1 -1
  5. package/dist/{chunk-DBYMX2ZC.js → chunk-7YWS3JLU.js} +1 -1
  6. package/dist/{ci-3TU7WCY4.js → chunk-GY3C3C2Y.js} +40 -46
  7. package/dist/{chunk-JOJA35RK.js → chunk-OLN7VUZA.js} +1 -1
  8. package/dist/{chunk-5FG7PSVM.js → chunk-PNTLA3MN.js} +2 -2
  9. package/dist/{chunk-R5CJD2AB.js → chunk-TCOC4AUI.js} +3 -3
  10. package/dist/chunk-TJ6GGN4B.js +105 -0
  11. package/dist/{chunk-TR6MSPSS.js → chunk-VW7Y27WW.js} +1 -1
  12. package/dist/{chunk-YFHF3PVB.js → chunk-WAI3WPV2.js} +1 -1
  13. package/dist/ci-6ZTFO3LX.js +19 -0
  14. package/dist/ci-format-CLZ6QJRL.js +18 -0
  15. package/dist/{constants-BKR7T3QH.js → constants-APSORFOH.js} +1 -1
  16. package/dist/{doctor-cli-TGVSWKDB.js → doctor-cli-GUIX4X5F.js} +4 -4
  17. package/dist/electron-server.js +2 -2
  18. package/dist/{hub-GURYEW6F.js → hub-ZGHQWNWE.js} +1 -1
  19. package/dist/index.js +58 -22
  20. package/dist/pr-JOL3IAGV.js +267 -0
  21. package/dist/{run-tests-R6FL6CQ3.js → run-tests-4PKSIVK5.js} +1 -1
  22. package/dist/{run-tests-F44JA2YW.js → run-tests-ZRK4TQUN.js} +2 -2
  23. package/dist/{server-FXWCSCDZ.js → server-FXUF5P64.js} +7 -7
  24. package/dist/{server-GQXC46R5.js → server-O3XHT56X.js} +4 -4
  25. package/dist/{task-orchestrator-6AZD5WSR.js → task-orchestrator-TUMDTOAX.js} +4 -4
  26. package/dist/{usage-W7J2QA3G.js → usage-X7MJX4YD.js} +2 -2
  27. package/dist/web/client/actions.js +1 -0
  28. package/dist/web/client/app.js +182 -13
  29. package/dist/web/client/index.html +21 -3
  30. package/dist/web/client/style.css +62 -0
  31. package/package.json +1 -1
@@ -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
- <div class="file-item">
354
- <input type="checkbox" checked class="checkbox checkbox-sm checkbox-primary" data-index="${f.index}">
355
- <span class="opacity-80">${escapeHtml(f.filePath)} <span class="badge badge-ghost badge-xs">${escapeHtml(f.toolName)}</span></span>
356
- </div>
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="Inject top 3 hits into chat input as a quoted context block">➕ Inject top 3</button>
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="Inject this hit into chat input">➕</button>
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="sessions">📋 Sessions</button>
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-3"></div>
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; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.214",
3
+ "version": "0.4.216",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",