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.
@@ -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) + ' (split is heuristic; per-tool timing lands with hooks)</div>'
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
- if (!lr) {
27345
- pane.innerHTML = '<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
- return;
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 &lt;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
- pane.innerHTML = renderCronRunDetails(lr);
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.102",
3
+ "version": "1.18.103",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",