clementine-agent 1.18.85 → 1.18.86

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.
@@ -23700,7 +23700,7 @@ function renderRecentHistoryList(runs) {
23700
23700
  + '</div>'
23701
23701
  + '<div style="font-size:12px;color:var(--text-secondary);line-height:18px">' + esc(startedLabel) + '</div>'
23702
23702
  + '<div style="font-size:12px;color:var(--text-muted);line-height:18px">' + esc(durationLabel) + '</div>'
23703
- + '<div style="display:flex;gap:6px;align-items:center"><button class="btn-sm" onclick="event.stopPropagation();openTraceViewer(\\x27' + safeName + '\\x27)" style="font-size:11px;padding:3px 8px">Trace</button></div>'
23703
+ + '<div style="display:flex;gap:6px;align-items:center"><button class="btn-sm" onclick="event.stopPropagation();openRunOrTrace(\\x27' + safeName + '\\x27,' + (entry.id ? '\\x27' + jsStr(entry.id) + '\\x27' : 'null') + ')" style="font-size:11px;padding:3px 8px">' + (entry.id ? 'Open run' : 'Trace') + '</button></div>'
23704
23704
  + '</div>';
23705
23705
  }
23706
23706
  return '<div class="history-list" style="background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius)">'
@@ -23906,7 +23906,7 @@ function renderRunListBody(allRuns) {
23906
23906
  + '<div style="font-size:11px;color:' + triggerColor + ';line-height:18px">' + esc(triggerLabel) + '</div>'
23907
23907
  + '<div style="font-size:12px;color:var(--text-secondary);line-height:18px">' + esc(startedLabel) + '</div>'
23908
23908
  + '<div style="font-size:12px;color:var(--text-muted);line-height:18px">' + esc(durationLabel) + '</div>'
23909
- + '<div style="display:flex;gap:6px;align-items:center"><button class="btn-sm" onclick="event.stopPropagation();openTraceViewer(\\x27' + safeName + '\\x27)" style="font-size:11px;padding:3px 8px">Trace</button></div>'
23909
+ + '<div style="display:flex;gap:6px;align-items:center"><button class="btn-sm" onclick="event.stopPropagation();openRunOrTrace(\\x27' + safeName + '\\x27,' + (entry.id ? '\\x27' + jsStr(entry.id) + '\\x27' : 'null') + ')" style="font-size:11px;padding:3px 8px">' + (entry.id ? 'Open run' : 'Trace') + '</button></div>'
23910
23910
  + '</div>';
23911
23911
  }
23912
23912
  html += '</div>';
@@ -24384,6 +24384,162 @@ async function refreshCron() {
24384
24384
 
24385
24385
  var traceData = [];
24386
24386
 
24387
+ // PRD Phase 4b / 1.18.86: smart router. If the run entry has a stable
24388
+ // runId (1.18.85+ runs), open the new Run detail viewer reading from the
24389
+ // Event store; otherwise fall back to the legacy trace viewer (which now
24390
+ // just renders the friendly empty state explaining where to find the
24391
+ // real error). Both viewers share the same modal shell.
24392
+ function openRunOrTrace(jobName, runId) {
24393
+ if (runId && typeof runId === 'string') {
24394
+ return openRunDetail(runId, jobName);
24395
+ }
24396
+ return openTraceViewer(jobName);
24397
+ }
24398
+
24399
+ // PRD Phase 4b / 1.18.86: Run detail viewer. Renders a waterfall of
24400
+ // RunEvent rows from /api/runs/:runId/events. Color-coded by kind, paired
24401
+ // tool_call→tool_result by toolUseId, with expandable per-span content.
24402
+ async function openRunDetail(runId, jobName) {
24403
+ document.getElementById('trace-modal-title').textContent = 'Run detail · ' + (jobName || runId);
24404
+ document.getElementById('trace-run-selector').innerHTML = '';
24405
+ document.getElementById('trace-content').innerHTML = '<div style="padding:20px;color:var(--text-muted)">Loading run events…</div>';
24406
+ document.getElementById('trace-modal').classList.add('show');
24407
+ try {
24408
+ var r = await apiFetch('/api/runs/' + encodeURIComponent(runId) + '/events');
24409
+ var d = await r.json();
24410
+ if (!r.ok || d.ok === false) {
24411
+ document.getElementById('trace-content').innerHTML = '<div style="padding:20px;color:var(--red)">Failed to load run: ' + esc(d.error || 'unknown') + '</div>';
24412
+ return;
24413
+ }
24414
+ var events = (d && d.events) || [];
24415
+ if (events.length === 0) {
24416
+ document.getElementById('trace-content').innerHTML = '<div style="padding:24px;color:var(--text-muted);line-height:1.6"><div style="font-weight:500;color:var(--text-secondary);margin-bottom:8px">No events captured for this run</div><div style="font-size:12px">Either the run pre-dates 1.18.85 (when the Event store was added) or the SDK errored before any message landed.<br/>The Recent history row carries the high-level status, error message, and goal verdict.</div></div>';
24417
+ return;
24418
+ }
24419
+ document.getElementById('trace-content').innerHTML = renderRunDetailWaterfall(events, runId, jobName);
24420
+ } catch (e) {
24421
+ document.getElementById('trace-content').innerHTML = '<div style="padding:20px;color:var(--red)">Failed to load run: ' + esc(String(e)) + '</div>';
24422
+ }
24423
+ }
24424
+
24425
+ // Renders the waterfall. Each event becomes a row with:
24426
+ // color border (by kind) · kind badge · time offset · brief preview · expand link
24427
+ // tool_call rows pair with their tool_result by toolUseId so the duration
24428
+ // is computed and shown alongside the call.
24429
+ function renderRunDetailWaterfall(events, runId, jobName) {
24430
+ if (!events.length) return '';
24431
+ var firstTs = events[0].ts ? new Date(events[0].ts).getTime() : Date.now();
24432
+ var lastTs = events[events.length - 1].ts ? new Date(events[events.length - 1].ts).getTime() : firstTs;
24433
+ var totalMs = Math.max(1, lastTs - firstTs);
24434
+
24435
+ // Pair tool_call with its tool_result for duration.
24436
+ var resultByToolUseId = {};
24437
+ for (var i = 0; i < events.length; i++) {
24438
+ var e = events[i];
24439
+ if (e.kind === 'tool_result' && e.toolUseId) {
24440
+ resultByToolUseId[e.toolUseId] = e;
24441
+ }
24442
+ }
24443
+
24444
+ // Per-event color + label
24445
+ function kindColor(k) {
24446
+ if (k === 'session_start' || k === 'session_end') return 'var(--text-muted)';
24447
+ if (k === 'llm_text') return 'var(--accent)';
24448
+ if (k === 'thinking') return 'var(--purple)';
24449
+ if (k === 'tool_call') return '#22c55e';
24450
+ if (k === 'tool_result') return '#22c55e';
24451
+ if (k === 'subagent_start' || k === 'subagent_stop') return '#a855f7';
24452
+ if (k === 'rate_limit') return 'var(--yellow)';
24453
+ if (k === 'hook') return 'var(--blue)';
24454
+ if (k === 'error') return 'var(--red)';
24455
+ return 'var(--text-muted)';
24456
+ }
24457
+ function kindLabel(k) {
24458
+ return (k || 'event').toUpperCase().replace(/_/g, ' ');
24459
+ }
24460
+
24461
+ // Header strip with summary
24462
+ var startLabel = events[0].ts ? new Date(events[0].ts).toLocaleString() : '—';
24463
+ var endEvent = events.find(function(e) { return e.kind === 'session_end'; });
24464
+ var costStr = endEvent && endEvent.costUsd != null ? '$' + endEvent.costUsd.toFixed(4) : '—';
24465
+ var stopReason = endEvent && endEvent.stopReason ? endEvent.stopReason : '—';
24466
+ var html = '<div style="padding:16px 20px;border-bottom:1px solid var(--border);background:var(--bg-secondary);position:sticky;top:0;z-index:1">'
24467
+ + '<div style="display:flex;align-items:center;gap:14px;font-size:11px;color:var(--text-muted);flex-wrap:wrap">'
24468
+ + '<span><strong style="color:var(--text-primary)">' + esc(events.length) + '</strong> events</span>'
24469
+ + '<span>·</span><span>started ' + esc(startLabel) + '</span>'
24470
+ + '<span>·</span><span>duration <strong style="color:var(--text-primary)">' + esc(formatDurationMs(totalMs)) + '</strong></span>'
24471
+ + '<span>·</span><span>cost <strong style="color:var(--text-primary)">' + esc(costStr) + '</strong></span>'
24472
+ + '<span>·</span><span>stop reason <strong style="color:var(--text-primary)">' + esc(stopReason) + '</strong></span>'
24473
+ + '<span style="flex:1"></span>'
24474
+ + '<code style="font-size:10px;color:var(--text-muted)">runId ' + esc(String(runId).slice(0, 12)) + '…</code>'
24475
+ + '</div>'
24476
+ + '</div>';
24477
+
24478
+ // Waterfall rows
24479
+ html += '<div style="padding:0">';
24480
+ for (var j = 0; j < events.length; j++) {
24481
+ var ev = events[j];
24482
+ var color = kindColor(ev.kind);
24483
+ var label = kindLabel(ev.kind);
24484
+ var tsMs = ev.ts ? new Date(ev.ts).getTime() : firstTs;
24485
+ var offsetMs = tsMs - firstTs;
24486
+ var offsetLabel = offsetMs === 0 ? '+0ms' : '+' + formatDurationMs(offsetMs);
24487
+ var widthPct = Math.max(2, Math.min(100, (offsetMs / totalMs) * 100));
24488
+ // For tool_call, compute duration to its paired tool_result.
24489
+ var pairedDuration = '';
24490
+ if (ev.kind === 'tool_call' && ev.toolUseId && resultByToolUseId[ev.toolUseId]) {
24491
+ var resultTs = new Date(resultByToolUseId[ev.toolUseId].ts).getTime();
24492
+ pairedDuration = ' · ran ' + formatDurationMs(Math.max(0, resultTs - tsMs));
24493
+ }
24494
+
24495
+ // Brief preview: text for llm_text, thinking for thinking, tool name + first arg for tool_call/result, error for error.
24496
+ var preview = '';
24497
+ var fullContent = '';
24498
+ if (ev.kind === 'llm_text' && ev.text) {
24499
+ preview = String(ev.text).slice(0, 160).replace(/\\s+/g, ' ');
24500
+ fullContent = String(ev.text);
24501
+ } else if (ev.kind === 'thinking' && ev.thinking) {
24502
+ preview = String(ev.thinking).slice(0, 160).replace(/\\s+/g, ' ');
24503
+ fullContent = String(ev.thinking);
24504
+ } else if (ev.kind === 'tool_call') {
24505
+ preview = (ev.toolName || 'tool') + (ev.toolInput ? ' · ' + JSON.stringify(ev.toolInput).slice(0, 120) : '');
24506
+ fullContent = ev.toolInput ? JSON.stringify(ev.toolInput, null, 2) : '';
24507
+ } else if (ev.kind === 'tool_result') {
24508
+ preview = ev.toolError ? '✗ ' + ev.toolError : (typeof ev.toolResult === 'string' ? ev.toolResult.slice(0, 160) : (ev.toolResult ? JSON.stringify(ev.toolResult).slice(0, 160) : ''));
24509
+ fullContent = typeof ev.toolResult === 'string' ? ev.toolResult : JSON.stringify(ev.toolResult, null, 2);
24510
+ } else if (ev.kind === 'error') {
24511
+ preview = ev.toolError || '';
24512
+ fullContent = ev.toolError || '';
24513
+ } else if (ev.kind === 'session_start') {
24514
+ preview = ev.sessionId ? 'session ' + String(ev.sessionId).slice(0, 8) + '…' : '';
24515
+ } else if (ev.kind === 'session_end') {
24516
+ preview = '$' + (ev.costUsd != null ? ev.costUsd.toFixed(4) : '?') + ' · ' + (ev.stopReason || '?');
24517
+ }
24518
+
24519
+ var rowId = 'run-evt-' + j;
24520
+ var canExpand = !!fullContent && fullContent.length > preview.length;
24521
+ html += '<div style="display:grid;grid-template-columns:90px 110px 1fr;gap:14px;padding:10px 20px;border-bottom:1px solid var(--border);align-items:start">';
24522
+ html += '<div style="font-size:10px;color:var(--text-muted);font-family:\\x27JetBrains Mono\\x27,monospace;line-height:18px">' + esc(offsetLabel) + '</div>';
24523
+ html += '<div><span style="display:inline-block;background:' + color + '20;color:' + color + ';padding:2px 8px;border-radius:4px;font-size:10px;font-weight:600;letter-spacing:0.04em">' + esc(label) + '</span></div>';
24524
+ html += '<div style="min-width:0">';
24525
+ html += '<div style="font-size:12px;color:var(--text-primary);line-height:1.45;word-break:break-word">'
24526
+ + esc(preview)
24527
+ + (pairedDuration ? '<span style="color:var(--text-muted);font-size:11px"> ' + esc(pairedDuration) + '</span>' : '')
24528
+ + '</div>';
24529
+ if (canExpand) {
24530
+ html += '<button class="btn-sm" onclick="document.getElementById(\\x27' + rowId + '\\x27).style.display=document.getElementById(\\x27' + rowId + '\\x27).style.display===\\x27none\\x27?\\x27block\\x27:\\x27none\\x27" style="margin-top:6px;font-size:10px;padding:2px 8px">Expand</button>';
24531
+ html += '<pre id="' + rowId + '" style="display:none;margin-top:8px;font-size:11px;font-family:\\x27JetBrains Mono\\x27,monospace;background:var(--bg-secondary);border:1px solid var(--border);padding:10px;border-radius:6px;white-space:pre-wrap;word-break:break-word;max-height:400px;overflow-y:auto">' + esc(fullContent) + '</pre>';
24532
+ }
24533
+ // Show toolUseId hint when present so user can correlate with logs.
24534
+ if (ev.toolUseId) {
24535
+ html += '<div style="font-size:10px;color:var(--text-muted);font-family:\\x27JetBrains Mono\\x27,monospace;margin-top:4px">use_id ' + esc(String(ev.toolUseId).slice(0, 12)) + '…</div>';
24536
+ }
24537
+ html += '</div></div>';
24538
+ }
24539
+ html += '</div>';
24540
+ return html;
24541
+ }
24542
+
24387
24543
  async function openTraceViewer(jobName) {
24388
24544
  document.getElementById('trace-modal-title').textContent = 'Trace: ' + jobName;
24389
24545
  document.getElementById('trace-content').innerHTML = '<div style="padding:20px;color:var(--text-muted)">Loading...</div>';
@@ -25708,7 +25864,7 @@ function renderCronRunDetails(lr) {
25708
25864
  if (Array.isArray(lr.mcpServersApplied) && lr.mcpServersApplied.length) {
25709
25865
  html += '<div style="font-size:11px;color:var(--text-muted);margin-bottom:6px">MCP servers: ' + esc(lr.mcpServersApplied.join(', ')) + '</div>';
25710
25866
  }
25711
- html += '<div style="margin-top:14px;display:flex;gap:8px"><button class="btn-sm" onclick="openTraceViewer(\\x27' + jsStr(lr.jobName || editingCronJob || '') + '\\x27)" style="font-size:11px">Open trace</button></div>';
25867
+ html += '<div style="margin-top:14px;display:flex;gap:8px"><button class="btn-sm" onclick="openRunOrTrace(\\x27' + jsStr(lr.jobName || editingCronJob || '') + '\\x27,' + (lr.id ? '\\x27' + jsStr(lr.id) + '\\x27' : 'null') + ')" style="font-size:11px">' + (lr.id ? 'Open run' : 'Open trace') + '</button></div>';
25712
25868
  html += '</div>';
25713
25869
  return html;
25714
25870
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.85",
3
+ "version": "1.18.86",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",