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 +3 -1
- package/dist/cli/dashboard.js +106 -12
- package/package.json +1 -1
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
|
|
package/dist/cli/dashboard.js
CHANGED
|
@@ -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;
|
|
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 & 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
|
|
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
|
}
|