clementine-agent 1.18.16 → 1.18.17

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/README.md CHANGED
@@ -326,7 +326,9 @@ Your overrides live in `~/.clementine/.env` — **they survive every `npm update
326
326
 
327
327
  The dashboard exposes these spend controls in Settings -> Channels & Env ->
328
328
  Spend Guards & Context Health, including direct dollar-cap editing, Default
329
- Caps, Safe Recovery, and No Caps presets.
329
+ Caps, Safe Recovery, and No Caps presets. When a dashboard change needs the
330
+ daemon to reload, Clementine shows a Restart Clementine prompt and handles the
331
+ restart from the browser.
330
332
 
331
333
  For spend/context tuning, `clementine budgets` gives a safer shortcut:
332
334
 
@@ -15414,7 +15414,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
15414
15414
  <div class="tab-pane active" id="tab-settings-general">
15415
15415
  <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
15416
15416
  <p style="color:var(--text-muted);margin:0">Manage API keys and configuration. Changes are saved to <code>~/.clementine/.env</code>.</p>
15417
- <button class="btn-sm" style="white-space:nowrap;background:var(--bg-tertiary);border:1px solid var(--border);color:var(--text-primary);padding:6px 12px;border-radius:6px;cursor:pointer" onclick="restartDashboard()">Restart Dashboard</button>
15417
+ <button class="btn-sm btn-primary" style="white-space:nowrap;padding:6px 12px;border-radius:6px;cursor:pointer" onclick="restartDaemonFromDashboard()">Restart Clementine</button>
15418
15418
  </div>
15419
15419
  <div id="budget-health-content" style="margin-bottom:16px"><div class="empty-state">Loading budget health...</div></div>
15420
15420
  <div id="settings-content"><div class="empty-state">Loading settings...</div></div>
@@ -15579,8 +15579,8 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
15579
15579
  <div class="card" style="margin-bottom:16px">
15580
15580
  <div class="card-header">Diagnostics &amp; maintenance</div>
15581
15581
  <div class="card-body" style="padding:16px;display:flex;gap:8px;flex-wrap:wrap">
15582
+ <button class="btn-sm btn-primary" onclick="restartDaemonFromDashboard()">Restart Clementine</button>
15582
15583
  <button class="btn-sm" onclick="restartDashboard()">Restart Dashboard</button>
15583
- <button class="btn-sm" onclick="if(confirm('Restart the daemon? Active sessions drain first.')) apiPost('/api/restart')">Restart Daemon</button>
15584
15584
  <button class="btn-sm" onclick="apiFetch('/api/doctor').then(function(r){return r.text()}).then(function(t){alert(t)})">Run Doctor</button>
15585
15585
  <button class="btn-sm" onclick="apiFetch('/api/version').then(function(r){return r.json()}).then(function(d){alert('Version: '+(d.version||'?')+'\\nNode: '+(d.node||'?'))})">Build info</button>
15586
15586
  </div>
@@ -17633,6 +17633,7 @@ async function apiPost(url) {
17633
17633
  if (d.ok) toast(d.message, 'success');
17634
17634
  else toast(d.error || 'Error', 'error');
17635
17635
  setTimeout(refreshAll, 1000);
17636
+ return d;
17636
17637
  } catch(e) { toast(String(e), 'error'); }
17637
17638
  }
17638
17639
  async function apiJson(method, url, body) {
@@ -17656,9 +17657,84 @@ async function apiDelete(url) {
17656
17657
  if (d.ok) toast(d.message, 'success');
17657
17658
  else toast(d.error || 'Error', 'error');
17658
17659
  setTimeout(refreshAll, 500);
17660
+ return d;
17659
17661
  } catch(e) { toast(String(e), 'error'); }
17660
17662
  }
17661
17663
 
17664
+ function settingRequiresDaemonRestart(key) {
17665
+ if (!key) return true;
17666
+ if (key === 'COMPOSIO_API_KEY' || key === 'COMPOSIO_USER_ID') return false;
17667
+ if (key.indexOf('ASSISTANT_') === 0) return false;
17668
+ return true;
17669
+ }
17670
+
17671
+ function renderRestartRequiredBanner() {
17672
+ var reason = '';
17673
+ try { reason = localStorage.getItem('clem-restart-required') || ''; } catch(e) { reason = ''; }
17674
+ var existing = document.getElementById('restart-required-banner');
17675
+ if (!reason) {
17676
+ if (existing) existing.remove();
17677
+ return;
17678
+ }
17679
+ if (!existing) {
17680
+ existing = document.createElement('div');
17681
+ existing.id = 'restart-required-banner';
17682
+ existing.style.cssText = 'position:fixed;left:18px;right:18px;bottom:18px;z-index:9999;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;border:1px solid var(--border);border-radius:8px;background:var(--bg-secondary);box-shadow:0 8px 28px rgba(0,0,0,0.28);padding:12px 14px;color:var(--text-primary)';
17683
+ document.body.appendChild(existing);
17684
+ }
17685
+ existing.innerHTML = '<div style="min-width:220px;flex:1"><div style="font-weight:700;font-size:13px">Restart required</div>'
17686
+ + '<div style="font-size:12px;color:var(--text-secondary);margin-top:2px">' + esc(reason) + '</div></div>'
17687
+ + '<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">'
17688
+ + '<button class="btn-sm btn-primary" onclick="restartDaemonFromDashboard()">Restart Clementine</button>'
17689
+ + '<button class="btn-sm" onclick="dismissRestartRequiredBanner()">Later</button>'
17690
+ + '</div>';
17691
+ }
17692
+
17693
+ function markRestartRequired(reason) {
17694
+ var msg = reason || 'This change needs a Clementine restart before the daemon and channel workers use it.';
17695
+ try { localStorage.setItem('clem-restart-required', msg); } catch(e) { /* ignore */ }
17696
+ renderRestartRequiredBanner();
17697
+ }
17698
+
17699
+ function clearRestartRequired() {
17700
+ try { localStorage.removeItem('clem-restart-required'); } catch(e) { /* ignore */ }
17701
+ var existing = document.getElementById('restart-required-banner');
17702
+ if (existing) existing.remove();
17703
+ }
17704
+
17705
+ function dismissRestartRequiredBanner() {
17706
+ var existing = document.getElementById('restart-required-banner');
17707
+ if (existing) existing.remove();
17708
+ }
17709
+
17710
+ async function restartDaemonFromDashboard(skipConfirm) {
17711
+ if (!skipConfirm && !confirm('Restart Clementine now? Active work may pause briefly while the daemon reloads.')) return;
17712
+ toast('Restarting Clementine...', 'info');
17713
+ try {
17714
+ var r = await apiFetch('/api/restart', { method: 'POST' });
17715
+ var d = {};
17716
+ try { d = await r.json(); } catch(e) { d = {}; }
17717
+ if (!r.ok || d.error) {
17718
+ var err = String(d.error || 'Restart failed');
17719
+ if (/not running/i.test(err)) {
17720
+ var launch = await apiFetch('/api/launch', { method: 'POST' });
17721
+ var launchData = await launch.json();
17722
+ if (!launch.ok || launchData.error) throw new Error(launchData.error || 'Launch failed');
17723
+ clearRestartRequired();
17724
+ toast('Clementine started', 'success');
17725
+ setTimeout(refreshAll, 2000);
17726
+ return;
17727
+ }
17728
+ throw new Error(err);
17729
+ }
17730
+ clearRestartRequired();
17731
+ toast('Clementine restart requested', 'success');
17732
+ setTimeout(refreshAll, 2500);
17733
+ } catch(e) {
17734
+ toast('Restart failed: ' + String(e), 'error');
17735
+ }
17736
+ }
17737
+
17662
17738
  // ── Status + Overview ─────────────────────
17663
17739
  let lastStatusData = {};
17664
17740
  async function refreshStatus(preloaded) {
@@ -18809,6 +18885,7 @@ async function saveHeartbeatControl() {
18809
18885
  if (!r.ok || d.error) throw new Error(d.error || 'Save failed');
18810
18886
  if (statusEl) { statusEl.textContent = 'Saved. Restart daemon for schedule changes.'; statusEl.style.color = 'var(--green)'; }
18811
18887
  toast('Heartbeat controls saved', 'success');
18888
+ markRestartRequired('Heartbeat control changes need a Clementine restart before the schedule uses them.');
18812
18889
  } catch(e) {
18813
18890
  if (statusEl) { statusEl.textContent = 'Save failed'; statusEl.style.color = 'var(--red)'; }
18814
18891
  toast(String(e), 'error');
@@ -20071,13 +20148,15 @@ async function postBudgetAction(url, body) {
20071
20148
  }
20072
20149
 
20073
20150
  async function applySafeBudgetPreset() {
20074
- await postBudgetAction('/api/budgets/safe', {});
20151
+ var d = await postBudgetAction('/api/budgets/safe', {});
20152
+ if (d && d.ok) markRestartRequired('Safe Recovery changed spend/context settings. Restart Clementine to apply them to chat and background workers.');
20075
20153
  refreshSettings();
20076
20154
  }
20077
20155
 
20078
20156
  async function applyBudgetPreset(preset) {
20079
20157
  if (preset === 'uncapped' && !confirm('Remove all spend caps? Clementine can still hit account limits or credits if a job runs long.')) return;
20080
- await postBudgetAction('/api/budgets/preset', { preset: preset });
20158
+ var d = await postBudgetAction('/api/budgets/preset', { preset: preset });
20159
+ if (d && d.ok) markRestartRequired('Spend guard changes need a Clementine restart before running workers use the new caps.');
20081
20160
  refreshSettings();
20082
20161
  }
20083
20162
 
@@ -20090,12 +20169,14 @@ async function saveBudgetCap(key) {
20090
20169
  toast('Budget must be a non-negative dollar amount. Use 0 for no cap.', 'error');
20091
20170
  return;
20092
20171
  }
20093
- await postBudgetAction('/api/budgets/set', { key: key, value: value });
20172
+ var d = await postBudgetAction('/api/budgets/set', { key: key, value: value });
20173
+ if (d && d.ok) markRestartRequired('Budget cap changes need a Clementine restart before running workers use the new value.');
20094
20174
  refreshSettings();
20095
20175
  }
20096
20176
 
20097
20177
  async function setBudgetContextMode(mode) {
20098
- await postBudgetAction('/api/budgets/1m', { mode: mode });
20178
+ var d = await postBudgetAction('/api/budgets/1m', { mode: mode });
20179
+ if (d && d.ok) markRestartRequired('1M context changes need a Clementine restart before new Claude calls use the setting.');
20099
20180
  refreshSettings();
20100
20181
  }
20101
20182
 
@@ -20105,7 +20186,10 @@ async function forceBudgetOneMillion() {
20105
20186
  }
20106
20187
 
20107
20188
  async function applyBudgetDoctorFix() {
20108
- await postBudgetAction('/api/budgets/doctor-fix', {});
20189
+ var d = await postBudgetAction('/api/budgets/doctor-fix', {});
20190
+ if (d && d.ok && d.result && d.result.changed && d.result.changed.length) {
20191
+ markRestartRequired('Doctor Fix changed Clementine configuration. Restart Clementine to apply the fixes.');
20192
+ }
20109
20193
  refreshSettings();
20110
20194
  }
20111
20195
 
@@ -20209,8 +20293,7 @@ async function refreshSettings() {
20209
20293
  + '</div></div>';
20210
20294
 
20211
20295
  html += '<div style="padding:12px;color:var(--text-muted);font-size:12px">'
20212
- + '<strong>Note:</strong> Changes to API keys require a daemon restart to take effect. '
20213
- + 'Use <code>clementine restart</code> after updating channel tokens.'
20296
+ + '<strong>Note:</strong> Changes that require a daemon restart show a Restart Clementine prompt here in the dashboard.'
20214
20297
  + '</div>';
20215
20298
  container.innerHTML = html;
20216
20299
 
@@ -20273,8 +20356,11 @@ async function toggleSetting(el) {
20273
20356
  async function saveSettingValue(key, value) {
20274
20357
  var statusEl = document.getElementById('setting-' + key + '-status');
20275
20358
  try {
20276
- await apiJson('PUT', '/api/settings/' + encodeURIComponent(key), { value: value });
20359
+ var result = await apiJson('PUT', '/api/settings/' + encodeURIComponent(key), { value: value });
20277
20360
  if (statusEl) { statusEl.textContent = 'Saved'; statusEl.style.color = 'var(--green)'; setTimeout(function(){ statusEl.textContent = ''; }, 2000); }
20361
+ if (result && result.ok && settingRequiresDaemonRestart(key)) {
20362
+ markRestartRequired(key + ' changed. Restart Clementine so the daemon and channel workers use the new value.');
20363
+ }
20278
20364
  } catch(e) {
20279
20365
  if (statusEl) { statusEl.textContent = 'Error'; statusEl.style.color = 'var(--red)'; }
20280
20366
  }
@@ -20299,8 +20385,11 @@ async function saveAssistantPreferences() {
20299
20385
  async function removeSetting(key) {
20300
20386
  if (!confirm('Remove ' + key + ' from .env?')) return;
20301
20387
  try {
20302
- await apiDelete('/api/settings/' + encodeURIComponent(key));
20388
+ var result = await apiDelete('/api/settings/' + encodeURIComponent(key));
20303
20389
  toast(key + ' removed', 'success');
20390
+ if (result && result.ok && settingRequiresDaemonRestart(key)) {
20391
+ markRestartRequired(key + ' was removed. Restart Clementine so running workers stop using the old value.');
20392
+ }
20304
20393
  refreshSettings();
20305
20394
  } catch(e) { toast('Failed: ' + e, 'error'); }
20306
20395
  }
@@ -20330,8 +20419,11 @@ async function addCustomEnv() {
20330
20419
  if (!key || !value) { toast('Both key and value are required', 'error'); return; }
20331
20420
  if (!/^[A-Z_][A-Z0-9_]*$/.test(key)) { toast('Invalid key format — use UPPER_SNAKE_CASE', 'error'); return; }
20332
20421
  try {
20333
- await apiJson('PUT', '/api/settings/' + encodeURIComponent(key), { value: value });
20422
+ var result = await apiJson('PUT', '/api/settings/' + encodeURIComponent(key), { value: value });
20334
20423
  toast(key + ' added', 'success');
20424
+ if (result && result.ok && settingRequiresDaemonRestart(key)) {
20425
+ markRestartRequired(key + ' was added. Restart Clementine so the daemon and channel workers can use it.');
20426
+ }
20335
20427
  keyInput.value = '';
20336
20428
  valInput.value = '';
20337
20429
  refreshSettings();
@@ -27701,6 +27793,7 @@ async function refreshSalesforce() {
27701
27793
 
27702
27794
  // ── Initial load — single batch request instead of 12+ parallel fetches ──
27703
27795
  (async function initDashboard() {
27796
+ renderRestartRequiredBanner();
27704
27797
  try {
27705
27798
  var r = await apiFetch('/api/init');
27706
27799
  var d = await r.json();
@@ -27772,6 +27865,7 @@ try {
27772
27865
  if (currentPage === 'home') refreshSessions();
27773
27866
  }
27774
27867
  if (evt.type === 'daemon_restarted') {
27868
+ clearRestartRequired();
27775
27869
  toast('Daemon restarted \u2014 refreshing data...', 'info');
27776
27870
  setTimeout(function() { refreshAll(); }, 1500);
27777
27871
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.16",
3
+ "version": "1.18.17",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",