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.
Files changed (28) hide show
  1. package/README.md +5 -2
  2. package/README.zh-CN.md +5 -2
  3. package/dist/{batch-PKY63TME.js → batch-74H5SA7P.js} +2 -2
  4. package/dist/{chunk-MRGWPCFQ.js → chunk-2224JGA6.js} +1 -1
  5. package/dist/{chunk-UDMU4FUF.js → chunk-3TSHNZKI.js} +1 -1
  6. package/dist/{chunk-THNECAAY.js → chunk-6C3JYNM6.js} +380 -74
  7. package/dist/{chunk-IWLVH32D.js → chunk-7JES2NWR.js} +1 -1
  8. package/dist/{chunk-FGUKBIYO.js → chunk-KL7UBVSQ.js} +2 -2
  9. package/dist/{chunk-J35V3IAH.js → chunk-RS4WBI73.js} +1 -1
  10. package/dist/{chunk-7NTFMLJJ.js → chunk-X6OXS7KU.js} +1 -1
  11. package/dist/{chunk-G3PW3AOB.js → chunk-ZOF5NFKW.js} +1 -1
  12. package/dist/{ci-HBZ6CIW5.js → ci-3ALK2XJN.js} +2 -2
  13. package/dist/{constants-CAUBTSFW.js → constants-IN2HXJK7.js} +1 -1
  14. package/dist/{doctor-cli-5Z5UWYW6.js → doctor-cli-7XL4TCVT.js} +4 -4
  15. package/dist/electron-server.js +558 -184
  16. package/dist/{hub-ZRADBR76.js → hub-VAOG5EY6.js} +1 -1
  17. package/dist/index.js +17 -16
  18. package/dist/{run-tests-7J3PNWVS.js → run-tests-J2JQ57R3.js} +1 -1
  19. package/dist/{run-tests-DSOJBJWA.js → run-tests-QJD43AML.js} +2 -2
  20. package/dist/{server-U4I7RI42.js → server-VGPZOISQ.js} +88 -8
  21. package/dist/{server-PWBWHVYB.js → server-WDLV3W4F.js} +4 -4
  22. package/dist/{task-orchestrator-RUXWRMQV.js → task-orchestrator-QFOCO3N7.js} +4 -4
  23. package/dist/{usage-2P6TIBTN.js → usage-IEB476NE.js} +2 -2
  24. package/dist/web/client/actions.js +1 -0
  25. package/dist/web/client/app.js +183 -11
  26. package/dist/web/client/index.html +21 -3
  27. package/dist/web/client/style.css +62 -0
  28. 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,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
- 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 : '');
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="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>
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="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>
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="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.213",
3
+ "version": "0.4.215",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",