clementine-agent 1.18.82 β†’ 1.18.83

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.
@@ -16389,6 +16389,9 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
16389
16389
  <button class="build-tab-btn active" data-build-tab="crons" onclick="switchBuildTab('crons')" style="padding:8px 14px;border-radius:6px 6px 0 0;border:none;background:transparent;color:var(--text-primary);font-size:13px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent">
16390
16390
  <span style="margin-right:6px">πŸ“…</span>Tasks <span id="build-tab-cron-count" style="display:none;margin-left:4px;font-size:10px;background:var(--bg-tertiary);padding:1px 6px;border-radius:999px;color:var(--text-muted)">0</span>
16391
16391
  </button>
16392
+ <button class="build-tab-btn" data-build-tab="runs" onclick="switchBuildTab('runs')" style="padding:8px 14px;border-radius:6px 6px 0 0;border:none;background:transparent;color:var(--text-secondary);font-size:13px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent">
16393
+ <span style="margin-right:6px">πŸ•’</span>Runs <span id="build-tab-runs-count" style="display:none;margin-left:4px;font-size:10px;background:var(--bg-tertiary);padding:1px 6px;border-radius:999px;color:var(--text-muted)">0</span>
16394
+ </button>
16392
16395
  <button class="build-tab-btn" data-build-tab="toolsmcp" onclick="switchBuildTab('toolsmcp')" style="padding:8px 14px;border-radius:6px 6px 0 0;border:none;background:transparent;color:var(--text-secondary);font-size:13px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent">
16393
16396
  <span style="margin-right:6px">🧰</span>Tools &amp; MCP <span id="build-tab-toolsmcp-count" style="display:none;margin-left:4px;font-size:10px;background:var(--bg-tertiary);padding:1px 6px;border-radius:999px;color:var(--text-muted)">0</span>
16394
16397
  </button>
@@ -16404,6 +16407,12 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
16404
16407
  <div id="build-tab-crons" style="display:none;flex:1;min-height:0;overflow-y:auto;padding:18px;background:var(--bg-primary)">
16405
16408
  <div id="panel-cron"><div class="empty-state" style="padding:24px;color:var(--text-muted)">Loading scheduled tasks…</div></div>
16406
16409
  </div>
16410
+ <!-- ── PRD Phase 3: Run list ───────────────────────────────────────────
16411
+ Single table of every run across all tasks, with filters + saved
16412
+ views. Default view is "Failures (last 24h)". -->
16413
+ <div id="build-tab-runs" style="display:none;flex:1;min-height:0;overflow-y:auto;padding:18px;background:var(--bg-primary)">
16414
+ <div id="panel-runs"><div class="empty-state" style="padding:24px;color:var(--text-muted)">Loading run history…</div></div>
16415
+ </div>
16407
16416
  <!-- ── PRD Phase 2: Tools & MCP catalog ────────────────────────────────
16408
16417
  Read-only foundation in 1.18.81. Future slices: per-tool bindings,
16409
16418
  Reconnect/Toggle/Edit actions, Approval Mode + Max-auto-runs config. -->
@@ -21261,8 +21270,10 @@ function switchBuildTab(tab) {
21261
21270
  // Always close any open workflow when changing tabs β€” switching context
21262
21271
  // is a clean slate, not a stale node hanging on the canvas.
21263
21272
  if (typeof closeBuilderCanvas === 'function') closeBuilderCanvas();
21264
- // Default: hide the Tools & MCP pane unless we're explicitly on it.
21273
+ var runsPane = document.getElementById('build-tab-runs');
21274
+ // Default: hide the Tools & MCP + Runs panes unless we're explicitly on them.
21265
21275
  if (toolsmcpPane && tab !== 'toolsmcp') toolsmcpPane.style.display = 'none';
21276
+ if (runsPane && tab !== 'runs') runsPane.style.display = 'none';
21266
21277
  if (tab === 'toolsmcp') {
21267
21278
  // PRD Phase 2: Tools & MCP catalog. Read-only foundation in 1.18.81.
21268
21279
  if (workPane) workPane.style.display = 'none';
@@ -21275,6 +21286,18 @@ function switchBuildTab(tab) {
21275
21286
  if (typeof refreshToolsMcpCatalog === 'function') refreshToolsMcpCatalog();
21276
21287
  return;
21277
21288
  }
21289
+ if (tab === 'runs') {
21290
+ // PRD Phase 3: Run list β€” every run across every task.
21291
+ if (workPane) workPane.style.display = 'none';
21292
+ if (cronPane) cronPane.style.display = 'none';
21293
+ if (tplPane) tplPane.style.display = 'none';
21294
+ if (runsPane) runsPane.style.display = 'block';
21295
+ if (headerStrip) headerStrip.style.display = 'none';
21296
+ if (usagePanel) usagePanel.style.display = 'none';
21297
+ if (newBtn) newBtn.style.display = 'none';
21298
+ if (typeof refreshRunList === 'function') refreshRunList();
21299
+ return;
21300
+ }
21278
21301
  if (tab === 'templates') {
21279
21302
  if (workPane) workPane.style.display = 'none';
21280
21303
  if (cronPane) cronPane.style.display = 'none';
@@ -23653,6 +23676,245 @@ function renderRunningCard(item) {
23653
23676
  + '</div></div>';
23654
23677
  }
23655
23678
 
23679
+ // ── PRD Phase 3: Run list ──────────────────────────────────────────────
23680
+ // Single sortable/filterable table of every CronRunEntry across all tasks.
23681
+ // Filters: status, task name, time window. Browser-local saved views.
23682
+ // Default view: "Failures (last 24h)". No new endpoints β€” reuses
23683
+ // /api/cron/runs (CronRunLog.readAllRecent).
23684
+
23685
+ var _runListState = {
23686
+ filterStatus: 'all', // 'all' | 'failed' | 'ok'
23687
+ filterWindow: '24h', // '24h' | '7d' | 'all'
23688
+ filterText: '', // free-text task name match
23689
+ data: [], // raw runs from /api/cron/runs
23690
+ };
23691
+
23692
+ function _runListLoadDefaultView() {
23693
+ // First-time visit: PRD Β§5.3 β€” default Saved View is "Failures (last 24h)".
23694
+ try {
23695
+ var raw = localStorage.getItem('runListView');
23696
+ if (raw) {
23697
+ var saved = JSON.parse(raw);
23698
+ _runListState.filterStatus = saved.filterStatus || 'all';
23699
+ _runListState.filterWindow = saved.filterWindow || '24h';
23700
+ _runListState.filterText = saved.filterText || '';
23701
+ return;
23702
+ }
23703
+ } catch (e) { /* ignore */ }
23704
+ // Default: failures, last 24h.
23705
+ _runListState.filterStatus = 'failed';
23706
+ _runListState.filterWindow = '24h';
23707
+ _runListState.filterText = '';
23708
+ }
23709
+
23710
+ function _runListSaveView() {
23711
+ try {
23712
+ localStorage.setItem('runListView', JSON.stringify({
23713
+ filterStatus: _runListState.filterStatus,
23714
+ filterWindow: _runListState.filterWindow,
23715
+ filterText: _runListState.filterText,
23716
+ }));
23717
+ } catch (e) { /* ignore */ }
23718
+ }
23719
+
23720
+ function _runListApplyFilters(runs) {
23721
+ var now = Date.now();
23722
+ var windowMs = _runListState.filterWindow === '24h' ? 24 * 60 * 60 * 1000
23723
+ : _runListState.filterWindow === '7d' ? 7 * 24 * 60 * 60 * 1000
23724
+ : Infinity;
23725
+ var query = (_runListState.filterText || '').trim().toLowerCase();
23726
+ return runs.filter(function(r) {
23727
+ if (_runListState.filterStatus === 'failed') {
23728
+ if (r.status !== 'error' && r.status !== 'timeout' && r.status !== 'lost') return false;
23729
+ } else if (_runListState.filterStatus === 'ok') {
23730
+ if (r.status !== 'ok') return false;
23731
+ }
23732
+ if (query && String(r.jobName || '').toLowerCase().indexOf(query) === -1) return false;
23733
+ if (windowMs !== Infinity && r.startedAt) {
23734
+ var age = now - new Date(r.startedAt).getTime();
23735
+ if (age > windowMs) return false;
23736
+ }
23737
+ return true;
23738
+ });
23739
+ }
23740
+
23741
+ async function refreshRunList() {
23742
+ var panel = document.getElementById('panel-runs');
23743
+ if (!panel) return;
23744
+ if (!_runListState.data.length) {
23745
+ _runListLoadDefaultView();
23746
+ }
23747
+ panel.innerHTML = '<div class="empty-state" style="padding:24px;color:var(--text-muted)">Loading run history…</div>';
23748
+ try {
23749
+ var r = await apiFetch('/api/cron/runs?limit=200');
23750
+ var d = await r.json();
23751
+ _runListState.data = (d && d.runs) || [];
23752
+ } catch (e) {
23753
+ panel.innerHTML = '<div class="empty-state" style="padding:24px;color:var(--red)">Failed to load runs: ' + esc(String(e)) + '</div>';
23754
+ return;
23755
+ }
23756
+ panel.innerHTML = renderRunListBody(_runListState.data);
23757
+ // Update tab count badge with total runs (not filtered count β€” that's
23758
+ // shown alongside the filter chips).
23759
+ var tabCount = document.getElementById('build-tab-runs-count');
23760
+ if (tabCount) {
23761
+ tabCount.textContent = _runListState.data.length;
23762
+ tabCount.style.display = _runListState.data.length > 0 ? '' : 'none';
23763
+ }
23764
+ }
23765
+
23766
+ function renderRunListBody(allRuns) {
23767
+ var filtered = _runListApplyFilters(allRuns);
23768
+ var html = '';
23769
+ // Header
23770
+ html += '<div style="margin-bottom:18px"><h2 style="margin:0 0 4px;font-size:18px;font-weight:600;color:var(--text-primary)">Runs</h2>'
23771
+ + '<div style="font-size:12px;color:var(--text-muted)">'+ filtered.length +' of '+ allRuns.length +' total runs Β· default view: <strong>Failures (last 24h)</strong></div></div>';
23772
+ // Filter row β€” saved automatically to localStorage on change.
23773
+ html += '<div style="display:flex;gap:10px;align-items:center;margin-bottom:14px;flex-wrap:wrap">';
23774
+ html += _runListChip('Status', [
23775
+ { value: 'all', label: 'All' },
23776
+ { value: 'ok', label: 'OK' },
23777
+ { value: 'failed', label: 'Failed' },
23778
+ ], 'filterStatus');
23779
+ html += _runListChip('Window', [
23780
+ { value: '24h', label: 'Last 24h' },
23781
+ { value: '7d', label: 'Last 7 days' },
23782
+ { value: 'all', label: 'All time' },
23783
+ ], 'filterWindow');
23784
+ html += '<input type="search" placeholder="Filter by task name…" value="' + esc(_runListState.filterText) + '" oninput="onRunListSearch(this.value)" style="flex:1;min-width:200px;max-width:320px;padding:6px 10px;font-size:12px;border:1px solid var(--border);border-radius:6px;background:var(--bg-secondary);color:var(--text-primary)">';
23785
+ html += '<button class="btn-sm" onclick="resetRunListFilters()" style="font-size:11px">Reset to default</button>';
23786
+ html += '</div>';
23787
+ if (filtered.length === 0) {
23788
+ html += '<div class="empty-state" style="padding:36px 24px;text-align:center;color:var(--text-muted)"><div style="font-size:14px;margin-bottom:6px">No runs match the current filter.</div><div style="font-size:12px">Try widening the time window or clearing the task-name filter.</div></div>';
23789
+ return html;
23790
+ }
23791
+ // Table β€” same shape as the Recent History list on the Tasks page,
23792
+ // but sortable and with a Trigger column.
23793
+ html += '<div style="background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius)">';
23794
+ html += '<div style="display:grid;grid-template-columns:24px 24px minmax(180px,1.2fr) 90px minmax(180px,1fr) 90px auto;gap:10px;padding:8px 14px;border-bottom:1px solid var(--border);font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.04em;font-weight:500">'
23795
+ + '<div></div><div title="Goal verdict">Goal</div><div>Task</div><div>Trigger</div><div>Started</div><div>Duration</div><div></div>'
23796
+ + '</div>';
23797
+ for (var i = 0; i < filtered.length; i++) {
23798
+ var entry = filtered[i] || {};
23799
+ var status = entry.status || 'unknown';
23800
+ var statusColor, statusIcon;
23801
+ if (status === 'ok') { statusColor = 'var(--green)'; statusIcon = '&#10003;'; }
23802
+ else if (status === 'error') { statusColor = 'var(--red)'; statusIcon = '&#10007;'; }
23803
+ else if (status === 'retried') { statusColor = 'var(--yellow)'; statusIcon = '&#8635;'; }
23804
+ else if (status === 'timeout') { statusColor = 'var(--yellow)'; statusIcon = '&#9203;'; }
23805
+ else if (status === 'lost') { statusColor = 'var(--red)'; statusIcon = '?'; }
23806
+ else if (status === 'running') { statusColor = 'var(--accent)'; statusIcon = '●'; }
23807
+ else if (status === 'skipped') { statusColor = 'var(--text-muted)'; statusIcon = '&minus;'; }
23808
+ else { statusColor = 'var(--text-muted)'; statusIcon = '&middot;'; }
23809
+ var jobName = entry.jobName || '(unknown)';
23810
+ var safeName = jsStr(jobName);
23811
+ var startedAt = entry.startedAt ? new Date(entry.startedAt) : null;
23812
+ var startedLabel = startedAt ? startedAt.toLocaleString() : 'β€”';
23813
+ var durationLabel = entry.durationMs != null ? formatDurationMs(entry.durationMs) : 'β€”';
23814
+ // Trigger heuristic until we persist it for real (Phase 3.1):
23815
+ // 'unleashed' mode + manual cron run = manual; otherwise scheduled.
23816
+ // The cron-running.json sidecar already carries pid which can hint at
23817
+ // manual but isn't on terminal entries. Best-effort label only.
23818
+ var triggerLabel = entry.attempt > 1 ? 'retry' : 'scheduled';
23819
+ var triggerColor = 'var(--text-muted)';
23820
+ // Goal cell
23821
+ var goalCell = '<div></div>';
23822
+ if (entry.goalCheck) {
23823
+ var gc = entry.goalCheck;
23824
+ var gIcon = gc.status === 'pass' ? '🎯' : gc.status === 'fail' ? 'βœ—' : gc.status === 'error' ? '⚠' : '';
23825
+ var gColor = gc.status === 'pass' ? 'var(--green)' : gc.status === 'fail' ? 'var(--red)' : 'var(--yellow)';
23826
+ var gTip = gc.evaluatorReason || (Array.isArray(gc.schemaErrors) ? gc.schemaErrors.join('; ') : gc.status);
23827
+ goalCell = '<div style="color:' + gColor + ';font-size:13px;line-height:18px;text-align:center" title="' + esc(gTip) + '">' + gIcon + '</div>';
23828
+ }
23829
+ var preview = '';
23830
+ if (status === 'error' && entry.error) {
23831
+ preview = '<div style="font-size:11px;color:var(--red);margin-top:2px;word-break:break-word">' + esc(String(entry.error).slice(0, 140)) + '</div>';
23832
+ } else if (entry.outputPreview) {
23833
+ preview = '<div style="font-size:11px;color:var(--text-muted);margin-top:2px;word-break:break-word">' + esc(String(entry.outputPreview).slice(0, 120)) + '</div>';
23834
+ }
23835
+ html += '<div class="history-row" data-trace-job="' + esc(jobName) + '" style="display:grid;grid-template-columns:24px 24px minmax(180px,1.2fr) 90px minmax(180px,1fr) 90px auto;gap:10px;align-items:start;padding:8px 14px;border-bottom:1px solid var(--border);cursor:pointer">'
23836
+ + '<div style="color:' + statusColor + ';font-size:14px;line-height:18px;text-align:center" title="' + esc(status) + '">' + statusIcon + '</div>'
23837
+ + goalCell
23838
+ + '<div style="min-width:0">'
23839
+ + '<div style="font-weight:500;color:var(--text-primary);font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + esc(jobName) + '">' + esc(jobName) + (entry.attempt > 1 ? ' Β· attempt ' + esc(entry.attempt) : '') + '</div>'
23840
+ + preview
23841
+ + '</div>'
23842
+ + '<div style="font-size:11px;color:' + triggerColor + ';line-height:18px">' + esc(triggerLabel) + '</div>'
23843
+ + '<div style="font-size:12px;color:var(--text-secondary);line-height:18px">' + esc(startedLabel) + '</div>'
23844
+ + '<div style="font-size:12px;color:var(--text-muted);line-height:18px">' + esc(durationLabel) + '</div>'
23845
+ + '<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>'
23846
+ + '</div>';
23847
+ }
23848
+ html += '</div>';
23849
+ return html;
23850
+ }
23851
+
23852
+ function _runListChip(label, options, stateKey) {
23853
+ var current = _runListState[stateKey];
23854
+ var html = '<span style="display:inline-flex;align-items:center;gap:4px">';
23855
+ html += '<span style="font-size:11px;color:var(--text-muted);font-weight:500;text-transform:uppercase;letter-spacing:0.04em;margin-right:2px">' + esc(label) + '</span>';
23856
+ for (var i = 0; i < options.length; i++) {
23857
+ var o = options[i];
23858
+ var active = o.value === current;
23859
+ var bg = active ? 'var(--accent)' : 'var(--bg-secondary)';
23860
+ var fg = active ? '#fff' : 'var(--text-primary)';
23861
+ html += '<button class="btn-sm" onclick="onRunListChipClick(\\x27' + jsStr(stateKey) + '\\x27,\\x27' + jsStr(o.value) + '\\x27)" style="font-size:11px;padding:4px 10px;background:' + bg + ';color:' + fg + ';border:1px solid var(--border);border-radius:999px">' + esc(o.label) + '</button>';
23862
+ }
23863
+ html += '</span>';
23864
+ return html;
23865
+ }
23866
+
23867
+ function onRunListChipClick(key, value) {
23868
+ _runListState[key] = value;
23869
+ _runListSaveView();
23870
+ var panel = document.getElementById('panel-runs');
23871
+ if (panel) panel.innerHTML = renderRunListBody(_runListState.data);
23872
+ }
23873
+
23874
+ function onRunListSearch(value) {
23875
+ _runListState.filterText = value;
23876
+ _runListSaveView();
23877
+ // Debounce-by-render: just re-render. Filtering is in-memory + cheap.
23878
+ var panel = document.getElementById('panel-runs');
23879
+ if (panel) panel.innerHTML = renderRunListBody(_runListState.data);
23880
+ }
23881
+
23882
+ function resetRunListFilters() {
23883
+ _runListState.filterStatus = 'failed';
23884
+ _runListState.filterWindow = '24h';
23885
+ _runListState.filterText = '';
23886
+ _runListSaveView();
23887
+ var panel = document.getElementById('panel-runs');
23888
+ if (panel) panel.innerHTML = renderRunListBody(_runListState.data);
23889
+ }
23890
+
23891
+ // Wire the panel's click handler so clicking anywhere on a row opens the
23892
+ // trace viewer (the row's data-trace-job attribute is what the existing
23893
+ // global panel-cron click handler reads).
23894
+ function _runListAttachClickHandler() {
23895
+ var pane = document.getElementById('build-tab-runs');
23896
+ if (!pane || pane._handlerAttached) return;
23897
+ pane.addEventListener('click', function(ev) {
23898
+ var t = ev.target;
23899
+ while (t && t !== pane) {
23900
+ if (t.dataset && t.dataset.traceJob) {
23901
+ openTraceViewer(t.dataset.traceJob);
23902
+ return;
23903
+ }
23904
+ t = t.parentElement;
23905
+ }
23906
+ });
23907
+ pane._handlerAttached = true;
23908
+ }
23909
+ // Attach once on first DOM ready β€” runs idempotent thanks to the flag.
23910
+ if (typeof document !== 'undefined') {
23911
+ if (document.readyState === 'complete' || document.readyState === 'interactive') {
23912
+ setTimeout(_runListAttachClickHandler, 0);
23913
+ } else {
23914
+ document.addEventListener('DOMContentLoaded', _runListAttachClickHandler);
23915
+ }
23916
+ }
23917
+
23656
23918
  // ── PRD Phase 2: Tools & MCP catalog ──────────────────────────────────
23657
23919
  // Read-only foundation in 1.18.81. Renders the four-card taxonomy:
23658
23920
  // β€’ Built-in β€” Claude SDK native tools (Read/Write/Bash/etc.)
@@ -35595,6 +35857,14 @@ try {
35595
35857
  if (evt.type === 'cron_complete' && evt.data && evt.data.job && typeof handleCronRunOnceComplete === 'function') {
35596
35858
  try { handleCronRunOnceComplete(evt.data.job); } catch (err) { /* non-fatal */ }
35597
35859
  }
35860
+ // PRD Phase 3: if the Runs tab is visible, refresh it too so a new
35861
+ // run appears at the top without a manual reload.
35862
+ if (currentPage === 'build' && typeof refreshRunList === 'function') {
35863
+ var runsPane = document.getElementById('build-tab-runs');
35864
+ if (runsPane && runsPane.style.display !== 'none') {
35865
+ try { refreshRunList(); } catch (err) { /* non-fatal */ }
35866
+ }
35867
+ }
35598
35868
  }
35599
35869
  // A delete on one tab should drop the card from every open dashboard
35600
35870
  // without waiting for the next poll. cron_toggled is similar but lighter.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.82",
3
+ "version": "1.18.83",
4
4
  "description": "Clementine β€” Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",