clementine-agent 1.18.102 → 1.18.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/dashboard.js +109 -5
- package/package.json +1 -1
package/dist/cli/dashboard.js
CHANGED
|
@@ -24750,7 +24750,7 @@ async function refreshMiniDashboards() {
|
|
|
24750
24750
|
+ '<div class="mini-card">'
|
|
24751
24751
|
+ '<div class="mini-card-head"><span class="mini-card-title">Latency · avg</span><span class="mini-card-figure">' + esc(latFigure) + '</span></div>'
|
|
24752
24752
|
+ splitHtml
|
|
24753
|
-
+ '<div class="mini-card-sub">' + esc(latSub) + '
|
|
24753
|
+
+ '<div class="mini-card-sub">' + esc(latSub) + ' · split is heuristic until path B hooks land per-task (enable in cron editor → Last run)</div>'
|
|
24754
24754
|
+ '</div>'
|
|
24755
24755
|
+ '<div class="mini-card">'
|
|
24756
24756
|
+ '<div class="mini-card-head"><span class="mini-card-title">Reliability · 7d</span><span class="mini-card-figure">' + totalFails7 + ' fail' + (totalFails7 === 1 ? '' : 's') + '</span></div>'
|
|
@@ -27341,11 +27341,115 @@ function renderCronLastRunPane(job) {
|
|
|
27341
27341
|
return;
|
|
27342
27342
|
}
|
|
27343
27343
|
var lr = job && job.lastRun;
|
|
27344
|
-
|
|
27345
|
-
|
|
27346
|
-
|
|
27344
|
+
var topHtml = lr ? renderCronRunDetails(lr)
|
|
27345
|
+
: '<div style="padding:36px 24px;color:var(--text-muted);text-align:center;font-size:13px">No runs yet. Click <strong>Run task once</strong> below to fire it now and watch the result here.</div>';
|
|
27346
|
+
// PRD §6 Phase 4d / 1.18.103: Per-task observability section. Shows hook
|
|
27347
|
+
// installation status + a toggle. Appears under the run details so the
|
|
27348
|
+
// most important info (last result) stays primary. Renders a placeholder
|
|
27349
|
+
// immediately and async-loads status; if the dashboard daemon is older
|
|
27350
|
+
// than 1.18.101 the section quietly hides itself when the API 404s.
|
|
27351
|
+
topHtml += '<div id="cron-hooks-section" style="margin-top:14px;padding:14px 18px;background:var(--bg-secondary);border:1px solid var(--border);border-radius:8px">'
|
|
27352
|
+
+ '<div style="font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:8px">Per-task observability</div>'
|
|
27353
|
+
+ '<div id="cron-hooks-body" style="font-size:12px;color:var(--text-muted)">Loading hook status…</div>'
|
|
27354
|
+
+ '</div>';
|
|
27355
|
+
pane.innerHTML = topHtml;
|
|
27356
|
+
// Fire the async fetch separately. job.name is the key the endpoints take.
|
|
27357
|
+
if (job && job.name) loadCronHooksStatus(job.name);
|
|
27358
|
+
}
|
|
27359
|
+
|
|
27360
|
+
// PRD §6 Phase 4d / 1.18.103: fetch hooks-status for the editing job and
|
|
27361
|
+
// render the appropriate UI (install / installed / conflict). Best-effort —
|
|
27362
|
+
// older daemons don't have the endpoint and we just hide the section.
|
|
27363
|
+
async function loadCronHooksStatus(jobName) {
|
|
27364
|
+
var body = document.getElementById('cron-hooks-body');
|
|
27365
|
+
if (!body) return;
|
|
27366
|
+
try {
|
|
27367
|
+
var r = await apiFetch('/api/cron/' + encodeURIComponent(jobName) + '/hooks-status');
|
|
27368
|
+
if (!r.ok) {
|
|
27369
|
+
var section = document.getElementById('cron-hooks-section');
|
|
27370
|
+
if (section) section.style.display = 'none';
|
|
27371
|
+
return;
|
|
27372
|
+
}
|
|
27373
|
+
var d = await r.json();
|
|
27374
|
+
var st = d.status || {};
|
|
27375
|
+
if (!d.workDir) {
|
|
27376
|
+
body.innerHTML = '<div style="color:var(--text-muted);font-size:12px;line-height:1.5">'
|
|
27377
|
+
+ 'This task has no <code>work_dir</code> set, so hooks can\\x27t be installed. '
|
|
27378
|
+
+ 'Add a <code>work_dir</code> in the <strong>Scope</strong> tab pointing at a project directory and the hooks toggle will appear here.'
|
|
27379
|
+
+ '</div>';
|
|
27380
|
+
return;
|
|
27381
|
+
}
|
|
27382
|
+
var safeName = jsStr(jobName);
|
|
27383
|
+
if (st.installed && st.managedByUs) {
|
|
27384
|
+
var installedAt = st.installedAt ? new Date(st.installedAt).toLocaleString() : 'unknown';
|
|
27385
|
+
body.innerHTML = '<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">'
|
|
27386
|
+
+ '<span style="display:inline-flex;align-items:center;gap:6px;color:var(--green);font-size:13px;font-weight:500">'
|
|
27387
|
+
+ '<span style="font-size:14px">🪝</span> Hooks installed'
|
|
27388
|
+
+ '</span>'
|
|
27389
|
+
+ '<span style="color:var(--text-muted);font-size:11px">since ' + esc(installedAt) + '</span>'
|
|
27390
|
+
+ '<span style="flex:1"></span>'
|
|
27391
|
+
+ '<button class="btn-sm btn-danger" onclick="disableCronHooks(\\x27' + safeName + '\\x27)" style="font-size:11px">Disable hooks</button>'
|
|
27392
|
+
+ '</div>'
|
|
27393
|
+
+ '<div style="margin-top:8px;font-size:11px;color:var(--text-muted);line-height:1.5">'
|
|
27394
|
+
+ 'PreToolUse, PostToolUse, SubagentStart/Stop, Stop, Notification, UserPromptSubmit, SessionStart, and PreCompact events are forwarded to the dashboard\\x27s event store. '
|
|
27395
|
+
+ 'This unlocks per-tool latency in the Latency mini-card and richer waterfall span detail in Run detail.'
|
|
27396
|
+
+ '</div>';
|
|
27397
|
+
} else if (st.installed && st.conflictsWithUser) {
|
|
27398
|
+
body.innerHTML = '<div style="color:var(--yellow);font-size:13px;font-weight:500;margin-bottom:6px">⚠ Hook config conflict</div>'
|
|
27399
|
+
+ '<div style="color:var(--text-muted);font-size:12px;line-height:1.5">'
|
|
27400
|
+
+ 'A <code>.claude/settings.local.json</code> exists in <code>' + esc(d.workDir) + '</code> but it wasn\\x27t created by Clementine. '
|
|
27401
|
+
+ 'Move or delete that file and click below to install our hooks alongside.'
|
|
27402
|
+
+ '</div>'
|
|
27403
|
+
+ '<div style="margin-top:10px;display:flex;gap:8px">'
|
|
27404
|
+
+ '<button class="btn-sm" onclick="loadCronHooksStatus(\\x27' + safeName + '\\x27)" style="font-size:11px">Re-check</button>'
|
|
27405
|
+
+ '</div>';
|
|
27406
|
+
} else {
|
|
27407
|
+
body.innerHTML = '<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">'
|
|
27408
|
+
+ '<span style="color:var(--text-secondary);font-size:13px">'
|
|
27409
|
+
+ '<span style="font-size:14px">🪝</span> Hooks not installed'
|
|
27410
|
+
+ '</span>'
|
|
27411
|
+
+ '<span style="flex:1"></span>'
|
|
27412
|
+
+ '<button class="btn-sm btn-success" onclick="enableCronHooks(\\x27' + safeName + '\\x27)" style="font-size:11px">Enable hooks</button>'
|
|
27413
|
+
+ '</div>'
|
|
27414
|
+
+ '<div style="margin-top:8px;font-size:11px;color:var(--text-muted);line-height:1.5">'
|
|
27415
|
+
+ 'Drops a <code>.claude/settings.local.json</code> into <code>' + esc(d.workDir) + '</code> registering command-type hooks for the SDK\\x27s 9 hook events. '
|
|
27416
|
+
+ 'Hooks POST event JSON to the dashboard so per-tool durations land in the Run detail viewer + Latency mini-card. '
|
|
27417
|
+
+ 'Per-event overhead is <5 ms (curl with --max-time 2). The file is gitignored by convention so this stays per-machine.'
|
|
27418
|
+
+ '</div>';
|
|
27419
|
+
}
|
|
27420
|
+
} catch (err) {
|
|
27421
|
+
body.innerHTML = '<div style="color:var(--text-muted);font-size:12px">Could not load hook status: ' + esc(String(err)) + '</div>';
|
|
27347
27422
|
}
|
|
27348
|
-
|
|
27423
|
+
}
|
|
27424
|
+
|
|
27425
|
+
async function enableCronHooks(jobName) {
|
|
27426
|
+
try {
|
|
27427
|
+
var r = await apiFetch('/api/cron/' + encodeURIComponent(jobName) + '/enable-hooks', { method: 'POST' });
|
|
27428
|
+
var d = await r.json().catch(function() { return {}; });
|
|
27429
|
+
if (!r.ok) {
|
|
27430
|
+
toast(d.error || 'Failed to enable hooks (HTTP ' + r.status + ')', 'error');
|
|
27431
|
+
// Refresh anyway so the conflict path renders.
|
|
27432
|
+
loadCronHooksStatus(jobName);
|
|
27433
|
+
return;
|
|
27434
|
+
}
|
|
27435
|
+
toast(d.message || 'Hooks installed.', 'success');
|
|
27436
|
+
loadCronHooksStatus(jobName);
|
|
27437
|
+
} catch (err) { toast('Enable hooks failed: ' + err, 'error'); }
|
|
27438
|
+
}
|
|
27439
|
+
|
|
27440
|
+
async function disableCronHooks(jobName) {
|
|
27441
|
+
if (!confirm('Disable hooks for "' + jobName + '"? The next run will only use the in-process tap (path A).')) return;
|
|
27442
|
+
try {
|
|
27443
|
+
var r = await apiFetch('/api/cron/' + encodeURIComponent(jobName) + '/disable-hooks', { method: 'POST' });
|
|
27444
|
+
var d = await r.json().catch(function() { return {}; });
|
|
27445
|
+
if (!r.ok) {
|
|
27446
|
+
toast(d.error || 'Failed to disable hooks (HTTP ' + r.status + ')', 'error');
|
|
27447
|
+
loadCronHooksStatus(jobName);
|
|
27448
|
+
return;
|
|
27449
|
+
}
|
|
27450
|
+
toast(d.message || 'Hooks disabled.', 'success');
|
|
27451
|
+
loadCronHooksStatus(jobName);
|
|
27452
|
+
} catch (err) { toast('Disable hooks failed: ' + err, 'error'); }
|
|
27349
27453
|
}
|
|
27350
27454
|
|
|
27351
27455
|
function renderCronRunningState(startedAtMs) {
|