clementine-agent 1.18.96 → 1.18.98
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 +626 -53
- package/package.json +1 -1
package/dist/cli/dashboard.js
CHANGED
|
@@ -24598,10 +24598,43 @@ var _runListState = {
|
|
|
24598
24598
|
filterStatus: 'all', // 'all' | 'failed' | 'ok'
|
|
24599
24599
|
filterWindow: '24h', // '24h' | '7d' | 'all'
|
|
24600
24600
|
filterCategory: 'all', // 'all' | <one of the 11 PRD failure categories>
|
|
24601
|
+
filterTrigger: 'all', // 'all' | manual | scheduled | after | api | webhook (PRD §6 Run.trigger)
|
|
24601
24602
|
filterText: '', // free-text task name match
|
|
24603
|
+
activeView: 'failures-24h', // PRD §5.3: which Saved View chip is lit
|
|
24602
24604
|
data: [], // raw runs from /api/cron/runs
|
|
24603
24605
|
};
|
|
24604
24606
|
|
|
24607
|
+
// PRD §5.3 / 1.18.99: built-in saved views shipped with the dashboard. Users
|
|
24608
|
+
// can layer their own on top via "Save current view" — those live in
|
|
24609
|
+
// localStorage under runListSavedViews. activeView tracks which chip is lit;
|
|
24610
|
+
// "custom" means the user manually edited filters since picking a view.
|
|
24611
|
+
var _runListBuiltinViews = [
|
|
24612
|
+
{ id: 'failures-24h', label: 'Failures · 24h', desc: 'Errored / timed-out / lost runs in the last 24h — the triage queue.',
|
|
24613
|
+
filters: { filterStatus: 'failed', filterWindow: '24h', filterCategory: 'all', filterTrigger: 'all', filterText: '' } },
|
|
24614
|
+
{ id: 'failures-7d', label: 'Failures · 7d', desc: 'All failures across the week — spot recurring categories.',
|
|
24615
|
+
filters: { filterStatus: 'failed', filterWindow: '7d', filterCategory: 'all', filterTrigger: 'all', filterText: '' } },
|
|
24616
|
+
{ id: 'all-24h', label: 'All · 24h', desc: 'Every run from the last 24h regardless of status.',
|
|
24617
|
+
filters: { filterStatus: 'all', filterWindow: '24h', filterCategory: 'all', filterTrigger: 'all', filterText: '' } },
|
|
24618
|
+
{ id: 'manual-24h', label: 'Manual · 24h', desc: 'Only runs you fired by hand.',
|
|
24619
|
+
filters: { filterStatus: 'all', filterWindow: '24h', filterCategory: 'all', filterTrigger: 'manual', filterText: '' } },
|
|
24620
|
+
{ id: 'ok-7d', label: 'OK · 7d', desc: 'Successful runs only — verify a task is healthy.',
|
|
24621
|
+
filters: { filterStatus: 'ok', filterWindow: '7d', filterCategory: 'all', filterTrigger: 'all', filterText: '' } },
|
|
24622
|
+
];
|
|
24623
|
+
|
|
24624
|
+
function _runListLoadSavedViews() {
|
|
24625
|
+
try {
|
|
24626
|
+
var raw = localStorage.getItem('runListSavedViews');
|
|
24627
|
+
if (!raw) return [];
|
|
24628
|
+
var parsed = JSON.parse(raw);
|
|
24629
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
24630
|
+
} catch (e) { return []; }
|
|
24631
|
+
}
|
|
24632
|
+
|
|
24633
|
+
function _runListPersistSavedViews(views) {
|
|
24634
|
+
try { localStorage.setItem('runListSavedViews', JSON.stringify(views)); }
|
|
24635
|
+
catch (e) { /* quota or disabled — non-fatal */ }
|
|
24636
|
+
}
|
|
24637
|
+
|
|
24605
24638
|
function _runListLoadDefaultView() {
|
|
24606
24639
|
// First-time visit: PRD §5.3 — default Saved View is "Failures (last 24h)".
|
|
24607
24640
|
try {
|
|
@@ -24611,15 +24644,18 @@ function _runListLoadDefaultView() {
|
|
|
24611
24644
|
_runListState.filterStatus = saved.filterStatus || 'all';
|
|
24612
24645
|
_runListState.filterWindow = saved.filterWindow || '24h';
|
|
24613
24646
|
_runListState.filterCategory = saved.filterCategory || 'all';
|
|
24647
|
+
_runListState.filterTrigger = saved.filterTrigger || 'all';
|
|
24614
24648
|
_runListState.filterText = saved.filterText || '';
|
|
24649
|
+
_runListState.activeView = saved.activeView || 'custom';
|
|
24615
24650
|
return;
|
|
24616
24651
|
}
|
|
24617
24652
|
} catch (e) { /* ignore */ }
|
|
24618
|
-
// Default: failures, last 24h, all categories.
|
|
24619
24653
|
_runListState.filterStatus = 'failed';
|
|
24620
24654
|
_runListState.filterWindow = '24h';
|
|
24621
24655
|
_runListState.filterCategory = 'all';
|
|
24656
|
+
_runListState.filterTrigger = 'all';
|
|
24622
24657
|
_runListState.filterText = '';
|
|
24658
|
+
_runListState.activeView = 'failures-24h';
|
|
24623
24659
|
}
|
|
24624
24660
|
|
|
24625
24661
|
function _runListSaveView() {
|
|
@@ -24628,11 +24664,71 @@ function _runListSaveView() {
|
|
|
24628
24664
|
filterStatus: _runListState.filterStatus,
|
|
24629
24665
|
filterWindow: _runListState.filterWindow,
|
|
24630
24666
|
filterCategory: _runListState.filterCategory,
|
|
24667
|
+
filterTrigger: _runListState.filterTrigger,
|
|
24631
24668
|
filterText: _runListState.filterText,
|
|
24669
|
+
activeView: _runListState.activeView,
|
|
24632
24670
|
}));
|
|
24633
24671
|
} catch (e) { /* ignore */ }
|
|
24634
24672
|
}
|
|
24635
24673
|
|
|
24674
|
+
function applyRunListView(id) {
|
|
24675
|
+
if (id === 'custom') { _runListState.activeView = 'custom'; _runListSaveView(); return; }
|
|
24676
|
+
var view = null;
|
|
24677
|
+
for (var i = 0; i < _runListBuiltinViews.length; i++) {
|
|
24678
|
+
if (_runListBuiltinViews[i].id === id) { view = _runListBuiltinViews[i]; break; }
|
|
24679
|
+
}
|
|
24680
|
+
if (!view) {
|
|
24681
|
+
var saved = _runListLoadSavedViews();
|
|
24682
|
+
for (var j = 0; j < saved.length; j++) {
|
|
24683
|
+
if (saved[j].id === id) { view = saved[j]; break; }
|
|
24684
|
+
}
|
|
24685
|
+
}
|
|
24686
|
+
if (!view) return;
|
|
24687
|
+
_runListState.filterStatus = view.filters.filterStatus || 'all';
|
|
24688
|
+
_runListState.filterWindow = view.filters.filterWindow || '24h';
|
|
24689
|
+
_runListState.filterCategory = view.filters.filterCategory || 'all';
|
|
24690
|
+
_runListState.filterTrigger = view.filters.filterTrigger || 'all';
|
|
24691
|
+
_runListState.filterText = view.filters.filterText || '';
|
|
24692
|
+
_runListState.activeView = view.id;
|
|
24693
|
+
_runListSaveView();
|
|
24694
|
+
if (typeof refreshRunList === 'function') refreshRunList();
|
|
24695
|
+
}
|
|
24696
|
+
|
|
24697
|
+
function saveCurrentRunListView() {
|
|
24698
|
+
var name = prompt('Name this view:', '');
|
|
24699
|
+
if (!name) return;
|
|
24700
|
+
name = String(name).trim();
|
|
24701
|
+
if (!name) return;
|
|
24702
|
+
var id = 'user-' + name.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 40);
|
|
24703
|
+
var views = _runListLoadSavedViews();
|
|
24704
|
+
views = views.filter(function(v) { return v.id !== id; });
|
|
24705
|
+
views.push({
|
|
24706
|
+
id: id, label: name, desc: 'User-defined view',
|
|
24707
|
+
filters: {
|
|
24708
|
+
filterStatus: _runListState.filterStatus,
|
|
24709
|
+
filterWindow: _runListState.filterWindow,
|
|
24710
|
+
filterCategory: _runListState.filterCategory,
|
|
24711
|
+
filterTrigger: _runListState.filterTrigger,
|
|
24712
|
+
filterText: _runListState.filterText,
|
|
24713
|
+
},
|
|
24714
|
+
});
|
|
24715
|
+
_runListPersistSavedViews(views);
|
|
24716
|
+
_runListState.activeView = id;
|
|
24717
|
+
_runListSaveView();
|
|
24718
|
+
toast('Saved view "' + name + '"', 'success');
|
|
24719
|
+
if (typeof refreshRunList === 'function') refreshRunList();
|
|
24720
|
+
}
|
|
24721
|
+
|
|
24722
|
+
function deleteRunListView(id) {
|
|
24723
|
+
var views = _runListLoadSavedViews();
|
|
24724
|
+
var filtered = views.filter(function(v) { return v.id !== id; });
|
|
24725
|
+
if (filtered.length === views.length) return;
|
|
24726
|
+
if (!confirm('Delete this saved view? This cannot be undone.')) return;
|
|
24727
|
+
_runListPersistSavedViews(filtered);
|
|
24728
|
+
if (_runListState.activeView === id) applyRunListView('failures-24h');
|
|
24729
|
+
else if (typeof refreshRunList === 'function') refreshRunList();
|
|
24730
|
+
}
|
|
24731
|
+
|
|
24636
24732
|
function _runListApplyFilters(runs) {
|
|
24637
24733
|
var now = Date.now();
|
|
24638
24734
|
var windowMs = _runListState.filterWindow === '24h' ? 24 * 60 * 60 * 1000
|
|
@@ -24640,6 +24736,7 @@ function _runListApplyFilters(runs) {
|
|
|
24640
24736
|
: Infinity;
|
|
24641
24737
|
var query = (_runListState.filterText || '').trim().toLowerCase();
|
|
24642
24738
|
var catFilter = _runListState.filterCategory;
|
|
24739
|
+
var trigFilter = _runListState.filterTrigger;
|
|
24643
24740
|
return runs.filter(function(r) {
|
|
24644
24741
|
if (_runListState.filterStatus === 'failed') {
|
|
24645
24742
|
if (r.status !== 'error' && r.status !== 'timeout' && r.status !== 'lost') return false;
|
|
@@ -24649,6 +24746,12 @@ function _runListApplyFilters(runs) {
|
|
|
24649
24746
|
if (catFilter && catFilter !== 'all') {
|
|
24650
24747
|
if (r.failureCategory !== catFilter) return false;
|
|
24651
24748
|
}
|
|
24749
|
+
// PRD §6 / 1.18.99: trigger filter. Pre-1.18.84 runs lack the field;
|
|
24750
|
+
// they fall through unless the user has explicitly picked a single
|
|
24751
|
+
// trigger (those rows then drop out as "unknown source").
|
|
24752
|
+
if (trigFilter && trigFilter !== 'all') {
|
|
24753
|
+
if ((r.trigger || 'scheduled') !== trigFilter) return false;
|
|
24754
|
+
}
|
|
24652
24755
|
if (query && String(r.jobName || '').toLowerCase().indexOf(query) === -1) return false;
|
|
24653
24756
|
if (windowMs !== Infinity && r.startedAt) {
|
|
24654
24757
|
var age = now - new Date(r.startedAt).getTime();
|
|
@@ -24689,6 +24792,37 @@ function renderRunListBody(allRuns) {
|
|
|
24689
24792
|
// Header
|
|
24690
24793
|
html += '<div style="margin-bottom:18px"><h2 style="margin:0 0 4px;font-size:18px;font-weight:600;color:var(--text-primary)">Runs</h2>'
|
|
24691
24794
|
+ '<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>';
|
|
24795
|
+
|
|
24796
|
+
// PRD §5.3 / 1.18.99: Saved Views row. Built-ins + user-saved chips, with
|
|
24797
|
+
// the active view highlighted. "Save current" persists the active filter
|
|
24798
|
+
// set under a user-supplied name.
|
|
24799
|
+
var userViews = _runListLoadSavedViews();
|
|
24800
|
+
html += '<div style="display:flex;gap:6px;align-items:center;margin-bottom:10px;flex-wrap:wrap">';
|
|
24801
|
+
html += '<span style="font-size:11px;color:var(--text-muted);font-weight:500;text-transform:uppercase;letter-spacing:0.04em;margin-right:4px">View</span>';
|
|
24802
|
+
for (var vbi = 0; vbi < _runListBuiltinViews.length; vbi++) {
|
|
24803
|
+
var bv = _runListBuiltinViews[vbi];
|
|
24804
|
+
var bvActive = _runListState.activeView === bv.id;
|
|
24805
|
+
var bvBg = bvActive ? 'var(--accent)' : 'var(--bg-secondary)';
|
|
24806
|
+
var bvFg = bvActive ? '#fff' : 'var(--text-primary)';
|
|
24807
|
+
html += '<button class="btn-sm" title="' + esc(bv.desc) + '" onclick="applyRunListView(\\x27' + jsStr(bv.id) + '\\x27)" style="font-size:11px;padding:4px 10px;background:' + bvBg + ';color:' + bvFg + ';border:1px solid var(--border);border-radius:999px">' + esc(bv.label) + '</button>';
|
|
24808
|
+
}
|
|
24809
|
+
if (userViews.length > 0) {
|
|
24810
|
+
html += '<span style="display:inline-block;width:1px;height:20px;background:var(--border);margin:0 4px"></span>';
|
|
24811
|
+
for (var uvi = 0; uvi < userViews.length; uvi++) {
|
|
24812
|
+
var uv = userViews[uvi];
|
|
24813
|
+
var uvActive = _runListState.activeView === uv.id;
|
|
24814
|
+
var uvBg = uvActive ? 'var(--accent)' : 'var(--bg-secondary)';
|
|
24815
|
+
var uvFg = uvActive ? '#fff' : 'var(--text-primary)';
|
|
24816
|
+
html += '<span style="display:inline-flex;align-items:center;gap:0">'
|
|
24817
|
+
+ '<button class="btn-sm" title="' + esc(uv.desc || 'User-defined view') + '" onclick="applyRunListView(\\x27' + jsStr(uv.id) + '\\x27)" style="font-size:11px;padding:4px 10px;background:' + uvBg + ';color:' + uvFg + ';border:1px solid var(--border);border-radius:999px 0 0 999px;border-right:none">⭐ ' + esc(uv.label) + '</button>'
|
|
24818
|
+
+ '<button class="btn-sm" title="Delete this saved view" onclick="event.stopPropagation();deleteRunListView(\\x27' + jsStr(uv.id) + '\\x27)" style="font-size:11px;padding:4px 8px;background:var(--bg-secondary);color:var(--text-muted);border:1px solid var(--border);border-radius:0 999px 999px 0">×</button>'
|
|
24819
|
+
+ '</span>';
|
|
24820
|
+
}
|
|
24821
|
+
}
|
|
24822
|
+
html += '<span style="flex:1"></span>';
|
|
24823
|
+
html += '<button class="btn-sm" onclick="saveCurrentRunListView()" title="Save the current filter set as a named view" style="font-size:11px;padding:4px 10px">+ Save current</button>';
|
|
24824
|
+
html += '</div>';
|
|
24825
|
+
|
|
24692
24826
|
// Filter row — saved automatically to localStorage on change.
|
|
24693
24827
|
html += '<div style="display:flex;gap:10px;align-items:center;margin-bottom:14px;flex-wrap:wrap">';
|
|
24694
24828
|
html += _runListChip('Status', [
|
|
@@ -24701,6 +24835,16 @@ function renderRunListBody(allRuns) {
|
|
|
24701
24835
|
{ value: '7d', label: 'Last 7 days' },
|
|
24702
24836
|
{ value: 'all', label: 'All time' },
|
|
24703
24837
|
], 'filterWindow');
|
|
24838
|
+
// PRD §6 / 1.18.99: trigger filter. Always shown so users can drill into
|
|
24839
|
+
// "what manual ones did I run today" without depending on data.
|
|
24840
|
+
html += _runListChip('Trigger', [
|
|
24841
|
+
{ value: 'all', label: 'Any source' },
|
|
24842
|
+
{ value: 'manual', label: 'Manual' },
|
|
24843
|
+
{ value: 'scheduled', label: 'Scheduled' },
|
|
24844
|
+
{ value: 'after', label: 'After-chained' },
|
|
24845
|
+
{ value: 'api', label: 'API' },
|
|
24846
|
+
{ value: 'webhook', label: 'Webhook' },
|
|
24847
|
+
], 'filterTrigger');
|
|
24704
24848
|
// PRD §9 / 1.18.87: 11-category failure filter. Build the option list from
|
|
24705
24849
|
// the categories actually present in the loaded data so the chip row stays
|
|
24706
24850
|
// compact (don't show buckets that have zero runs).
|
|
@@ -24844,6 +24988,10 @@ function _runListChip(label, options, stateKey) {
|
|
|
24844
24988
|
|
|
24845
24989
|
function onRunListChipClick(key, value) {
|
|
24846
24990
|
_runListState[key] = value;
|
|
24991
|
+
// PRD §5.3 / 1.18.99: any manual filter change drops out of the active
|
|
24992
|
+
// saved view — the chip row deselects to make it obvious you're now on
|
|
24993
|
+
// a custom filter set.
|
|
24994
|
+
_runListState.activeView = 'custom';
|
|
24847
24995
|
_runListSaveView();
|
|
24848
24996
|
var panel = document.getElementById('panel-runs');
|
|
24849
24997
|
if (panel) panel.innerHTML = renderRunListBody(_runListState.data);
|
|
@@ -24851,6 +24999,7 @@ function onRunListChipClick(key, value) {
|
|
|
24851
24999
|
|
|
24852
25000
|
function onRunListSearch(value) {
|
|
24853
25001
|
_runListState.filterText = value;
|
|
25002
|
+
_runListState.activeView = 'custom';
|
|
24854
25003
|
_runListSaveView();
|
|
24855
25004
|
// Debounce-by-render: just re-render. Filtering is in-memory + cheap.
|
|
24856
25005
|
var panel = document.getElementById('panel-runs');
|
|
@@ -24858,13 +25007,10 @@ function onRunListSearch(value) {
|
|
|
24858
25007
|
}
|
|
24859
25008
|
|
|
24860
25009
|
function resetRunListFilters() {
|
|
24861
|
-
|
|
24862
|
-
|
|
24863
|
-
|
|
24864
|
-
|
|
24865
|
-
_runListSaveView();
|
|
24866
|
-
var panel = document.getElementById('panel-runs');
|
|
24867
|
-
if (panel) panel.innerHTML = renderRunListBody(_runListState.data);
|
|
25010
|
+
// Reset jumps back to the default Failures · 24h built-in view, which
|
|
25011
|
+
// also lights up that chip again so users see the round-trip.
|
|
25012
|
+
applyRunListView('failures-24h');
|
|
25013
|
+
return;
|
|
24868
25014
|
}
|
|
24869
25015
|
|
|
24870
25016
|
// Wire the panel's click handler so clicking anywhere on a row opens the
|
|
@@ -26853,10 +26999,14 @@ async function openPromptHistory() {
|
|
|
26853
26999
|
html += '<span style="font-size:11px;color:var(--text-muted)">· by ' + esc(who) + '</span>';
|
|
26854
27000
|
html += '<span style="flex:1"></span>';
|
|
26855
27001
|
html += '<button class="btn-sm" onclick="document.getElementById(\\x27' + rowId + '\\x27).style.display=document.getElementById(\\x27' + rowId + '\\x27).style.display===\\x27none\\x27?\\x27block\\x27:\\x27none\\x27" style="font-size:11px;padding:3px 8px">Show full</button>';
|
|
27002
|
+
// PRD §13 Phase 5.0c / 1.18.97: line-level diff vs editor draft.
|
|
27003
|
+
html += '<button class="btn-sm" onclick="togglePromptDiff(\\x27' + rowId + '-diff\\x27,\\x27' + promptB64 + '\\x27)" style="font-size:11px;padding:3px 8px">Show diff</button>';
|
|
26856
27004
|
html += '<button class="btn-sm btn-primary" onclick="restorePromptVersion(\\x27' + promptB64 + '\\x27)" style="font-size:11px;padding:3px 10px">Restore</button>';
|
|
26857
27005
|
html += '</div>';
|
|
26858
27006
|
html += '<div style="font-size:12px;color:var(--text-secondary);line-height:1.5;font-family:\\x27JetBrains Mono\\x27,monospace">' + esc(preview) + (prompt.length > 200 ? '…' : '') + '</div>';
|
|
26859
27007
|
html += '<pre id="' + rowId + '" style="display:none;margin-top:10px;font-size:11px;font-family:\\x27JetBrains Mono\\x27,monospace;background:var(--bg-tertiary);border:1px solid var(--border);padding:10px;border-radius:6px;white-space:pre-wrap;word-break:break-word;max-height:280px;overflow-y:auto">' + esc(prompt) + '</pre>';
|
|
27008
|
+
// PRD §13 Phase 5.0c / 1.18.97: diff placeholder filled by togglePromptDiff.
|
|
27009
|
+
html += '<div id="' + rowId + '-diff" style="display:none;margin-top:10px;font-size:11px;font-family:\\x27JetBrains Mono\\x27,monospace;background:var(--bg-tertiary);border:1px solid var(--border);padding:10px;border-radius:6px;max-height:320px;overflow-y:auto"></div>';
|
|
26860
27010
|
html += '</div>';
|
|
26861
27011
|
}
|
|
26862
27012
|
list.innerHTML = html;
|
|
@@ -26870,6 +27020,78 @@ function closePromptHistory() {
|
|
|
26870
27020
|
if (modal) modal.classList.remove('show');
|
|
26871
27021
|
}
|
|
26872
27022
|
|
|
27023
|
+
// PRD §13 Phase 5.0c / 1.18.97: line-level diff between this version and
|
|
27024
|
+
// the editor current prompt text. Uses LCS-based diff. Toggles on click.
|
|
27025
|
+
function togglePromptDiff(elemId, promptB64) {
|
|
27026
|
+
var el = document.getElementById(elemId);
|
|
27027
|
+
if (!el) return;
|
|
27028
|
+
if (el.style.display !== 'none') { el.style.display = 'none'; return; }
|
|
27029
|
+
var oldPrompt;
|
|
27030
|
+
try { oldPrompt = decodeURIComponent(escape(atob(promptB64))); }
|
|
27031
|
+
catch (e) { el.innerHTML = '<span style="color:var(--red)">Failed to decode old prompt: ' + esc(String(e)) + '</span>'; el.style.display = 'block'; return; }
|
|
27032
|
+
var ta = document.getElementById('cron-prompt');
|
|
27033
|
+
var newPrompt = ta ? ta.value : '';
|
|
27034
|
+
el.innerHTML = renderPromptDiff(oldPrompt, newPrompt);
|
|
27035
|
+
el.style.display = 'block';
|
|
27036
|
+
}
|
|
27037
|
+
|
|
27038
|
+
function renderPromptDiff(oldText, newText) {
|
|
27039
|
+
var oldLines = (oldText || '').split('\\n');
|
|
27040
|
+
var newLines = (newText || '').split('\\n');
|
|
27041
|
+
if (oldText === newText) {
|
|
27042
|
+
return '<div style="color:var(--text-muted);font-style:italic">Identical to the current prompt — no changes between this version and now.</div>';
|
|
27043
|
+
}
|
|
27044
|
+
var n = oldLines.length, m = newLines.length;
|
|
27045
|
+
if (n > 1000 || m > 1000) {
|
|
27046
|
+
return '<div style="color:var(--text-muted);font-style:italic">Diff too large to render inline (' + n + ' x ' + m + ' lines). Use Show full to view the old prompt and copy/paste into your favorite diff tool.</div>';
|
|
27047
|
+
}
|
|
27048
|
+
// Build LCS table.
|
|
27049
|
+
var dp = [];
|
|
27050
|
+
for (var i = 0; i <= n; i++) dp.push(new Array(m + 1).fill(0));
|
|
27051
|
+
for (var ii = 1; ii <= n; ii++) {
|
|
27052
|
+
for (var jj = 1; jj <= m; jj++) {
|
|
27053
|
+
if (oldLines[ii - 1] === newLines[jj - 1]) dp[ii][jj] = dp[ii - 1][jj - 1] + 1;
|
|
27054
|
+
else dp[ii][jj] = Math.max(dp[ii - 1][jj], dp[ii][jj - 1]);
|
|
27055
|
+
}
|
|
27056
|
+
}
|
|
27057
|
+
// Walk back to produce ops in reverse.
|
|
27058
|
+
var ops = [];
|
|
27059
|
+
var ri = n, rj = m;
|
|
27060
|
+
while (ri > 0 || rj > 0) {
|
|
27061
|
+
if (ri > 0 && rj > 0 && oldLines[ri - 1] === newLines[rj - 1]) {
|
|
27062
|
+
ops.push({ kind: 'equal', text: oldLines[ri - 1] });
|
|
27063
|
+
ri--; rj--;
|
|
27064
|
+
} else if (rj > 0 && (ri === 0 || dp[ri][rj - 1] >= dp[ri - 1][rj])) {
|
|
27065
|
+
ops.push({ kind: 'add', text: newLines[rj - 1] });
|
|
27066
|
+
rj--;
|
|
27067
|
+
} else {
|
|
27068
|
+
ops.push({ kind: 'remove', text: oldLines[ri - 1] });
|
|
27069
|
+
ri--;
|
|
27070
|
+
}
|
|
27071
|
+
}
|
|
27072
|
+
ops.reverse();
|
|
27073
|
+
var added = 0, removed = 0;
|
|
27074
|
+
for (var oi = 0; oi < ops.length; oi++) {
|
|
27075
|
+
if (ops[oi].kind === 'add') added++;
|
|
27076
|
+
else if (ops[oi].kind === 'remove') removed++;
|
|
27077
|
+
}
|
|
27078
|
+
var html = '<div style="font-size:11px;color:var(--text-muted);margin-bottom:8px">'
|
|
27079
|
+
+ '<span style="color:var(--green)">+' + added + '</span> · '
|
|
27080
|
+
+ '<span style="color:var(--red)">-' + removed + '</span> · '
|
|
27081
|
+
+ 'comparing version above ↔ current editor draft</div>';
|
|
27082
|
+
html += '<div style="white-space:pre-wrap;word-break:break-word;line-height:1.5">';
|
|
27083
|
+
for (var k = 0; k < ops.length; k++) {
|
|
27084
|
+
var op = ops[k];
|
|
27085
|
+
var prefix, color, bg;
|
|
27086
|
+
if (op.kind === 'add') { prefix = '+ '; color = 'var(--green)'; bg = 'rgba(16,185,129,0.08)'; }
|
|
27087
|
+
else if (op.kind === 'remove') { prefix = '- '; color = 'var(--red)'; bg = 'rgba(239,68,68,0.08)'; }
|
|
27088
|
+
else { prefix = ' '; color = 'var(--text-muted)'; bg = 'transparent'; }
|
|
27089
|
+
html += '<div style="color:' + color + ';background:' + bg + ';padding:1px 4px">' + esc(prefix) + esc(op.text || ' ') + '</div>';
|
|
27090
|
+
}
|
|
27091
|
+
html += '</div>';
|
|
27092
|
+
return html;
|
|
27093
|
+
}
|
|
27094
|
+
|
|
26873
27095
|
// Restore copies the old prompt back into the editor's textarea. Doesn't
|
|
26874
27096
|
// auto-save — the user must click Save Changes to commit, which keeps the
|
|
26875
27097
|
// dirty-guard semantics intact and gives them a chance to back out.
|
|
@@ -33959,41 +34181,281 @@ async function loadAgentProjectOptions(selected) {
|
|
|
33959
34181
|
} catch(e) { /* projects are optional */ }
|
|
33960
34182
|
}
|
|
33961
34183
|
|
|
34184
|
+
// ── Equipment panel ────────────────────────────────────────────────
|
|
34185
|
+
// Renders the unified tool/CLI/MCP/Composio/Project picker with type
|
|
34186
|
+
// badges, status pills, and category grouping. Status pills surface the
|
|
34187
|
+
// connected/installed/blocked flags that the previous renderer dropped,
|
|
34188
|
+
// so users can see at a glance whether a tool will actually work for
|
|
34189
|
+
// this agent. Local Projects are first-class entries (name format:
|
|
34190
|
+
// 'project:/abs/path') so a single check grants the agent access to the
|
|
34191
|
+
// project; project-scoped MCP servers appear as their own categories.
|
|
34192
|
+
// Composio toolkits are surfaced when isComposioEnabled.
|
|
34193
|
+
var _agentToolsState = {
|
|
34194
|
+
filter: 'all',
|
|
34195
|
+
search: '',
|
|
34196
|
+
data: null, // {categories, composioError}
|
|
34197
|
+
selected: new Set(),
|
|
34198
|
+
};
|
|
34199
|
+
|
|
33962
34200
|
async function loadAgentToolOptions(selectedTools) {
|
|
34201
|
+
var panel = document.getElementById('agent-tools-panel');
|
|
34202
|
+
if (!panel) return;
|
|
34203
|
+
panel.innerHTML = '<div style="color:var(--text-muted);font-size:12px">Loading tools...</div>';
|
|
34204
|
+
_agentToolsState.selected = new Set(selectedTools || []);
|
|
33963
34205
|
try {
|
|
33964
34206
|
var r = await apiFetch('/api/available-tools');
|
|
33965
34207
|
var d = await r.json();
|
|
33966
|
-
|
|
33967
|
-
|
|
33968
|
-
|
|
33969
|
-
|
|
33970
|
-
|
|
33971
|
-
|
|
33972
|
-
|
|
33973
|
-
|
|
33974
|
-
|
|
33975
|
-
|
|
33976
|
-
|
|
33977
|
-
|
|
33978
|
-
|
|
33979
|
-
|
|
33980
|
-
|
|
33981
|
-
|
|
33982
|
-
|
|
33983
|
-
|
|
33984
|
-
|
|
33985
|
-
|
|
33986
|
-
|
|
33987
|
-
|
|
34208
|
+
_agentToolsState.data = d;
|
|
34209
|
+
renderAgentToolPanel();
|
|
34210
|
+
} catch(e) {
|
|
34211
|
+
panel.innerHTML = '<div style="color:var(--red);font-size:12px">Failed to load tools: ' + esc(String(e)) + '</div>';
|
|
34212
|
+
}
|
|
34213
|
+
}
|
|
34214
|
+
|
|
34215
|
+
function renderAgentToolPanel() {
|
|
34216
|
+
var panel = document.getElementById('agent-tools-panel');
|
|
34217
|
+
var summary = document.getElementById('agent-tools-summary');
|
|
34218
|
+
if (!panel) return;
|
|
34219
|
+
var d = _agentToolsState.data;
|
|
34220
|
+
if (!d || !d.categories) { panel.innerHTML = ''; return; }
|
|
34221
|
+
|
|
34222
|
+
panel.innerHTML = '';
|
|
34223
|
+
var totalEnabled = 0;
|
|
34224
|
+
var totalCount = 0;
|
|
34225
|
+
var typeOrder = { 'sdk': 0, 'cli': 1, 'mcp': 2, 'api': 3, 'project-mcp': 4, 'project': 5, 'composio': 6 };
|
|
34226
|
+
|
|
34227
|
+
Object.keys(d.categories).forEach(function(cat) {
|
|
34228
|
+
var entries = d.categories[cat] || [];
|
|
34229
|
+
var catEnabledCount = 0;
|
|
34230
|
+
entries.forEach(function(t) { if (_agentToolsState.selected.has(typeof t === 'string' ? t : t.name)) catEnabledCount++; });
|
|
34231
|
+
totalCount += entries.length;
|
|
34232
|
+
totalEnabled += catEnabledCount;
|
|
34233
|
+
|
|
34234
|
+
var header = document.createElement('div');
|
|
34235
|
+
header.className = 'agent-tool-cat';
|
|
34236
|
+
header.dataset.category = cat;
|
|
34237
|
+
header.innerHTML = '▸ ' + esc(cat) + '<span class="cat-count">' + catEnabledCount + '/' + entries.length + '</span>';
|
|
34238
|
+
header.onclick = (function(catName) { return function() {
|
|
34239
|
+
var rows = panel.querySelectorAll('.agent-tool-cb[data-cat="' + cssAttrEsc(catName) + '"]');
|
|
34240
|
+
var visible = Array.from(rows).filter(function(cb) {
|
|
34241
|
+
return !cb.closest('.agent-tool-row.hidden');
|
|
34242
|
+
});
|
|
34243
|
+
var allChecked = visible.length > 0 && visible.every(function(cb) { return cb.checked; });
|
|
34244
|
+
visible.forEach(function(cb) {
|
|
34245
|
+
cb.checked = !allChecked;
|
|
34246
|
+
if (cb.checked) _agentToolsState.selected.add(cb.value);
|
|
34247
|
+
else _agentToolsState.selected.delete(cb.value);
|
|
33988
34248
|
});
|
|
34249
|
+
renderAgentToolPanel();
|
|
34250
|
+
}; })(cat);
|
|
34251
|
+
panel.appendChild(header);
|
|
34252
|
+
|
|
34253
|
+
entries.slice().sort(function(a, b) {
|
|
34254
|
+
var ta = typeOrder[a.type]; if (ta === undefined) ta = 9;
|
|
34255
|
+
var tb = typeOrder[b.type]; if (tb === undefined) tb = 9;
|
|
34256
|
+
if (ta !== tb) return ta - tb;
|
|
34257
|
+
return String(a.name || '').localeCompare(String(b.name || ''));
|
|
34258
|
+
}).forEach(function(tool) {
|
|
34259
|
+
panel.appendChild(buildAgentToolRow(cat, tool));
|
|
34260
|
+
});
|
|
34261
|
+
});
|
|
34262
|
+
|
|
34263
|
+
if (summary) {
|
|
34264
|
+
var msg = totalEnabled + ' of ' + totalCount + ' enabled';
|
|
34265
|
+
if (d.composioError) msg += ' · Composio: ' + d.composioError;
|
|
34266
|
+
summary.textContent = msg;
|
|
34267
|
+
}
|
|
34268
|
+
|
|
34269
|
+
filterAgentTools();
|
|
34270
|
+
}
|
|
34271
|
+
|
|
34272
|
+
function buildAgentToolRow(cat, tool) {
|
|
34273
|
+
var name = typeof tool === 'string' ? tool : tool.name;
|
|
34274
|
+
var description = typeof tool === 'string' ? '' : (tool.description || '');
|
|
34275
|
+
var type = typeof tool === 'string' ? '' : (tool.type || '');
|
|
34276
|
+
var displayName = (typeof tool === 'object' && tool.displayName) ? tool.displayName
|
|
34277
|
+
: (type === 'project' && tool.projectName) ? tool.projectName
|
|
34278
|
+
: name;
|
|
34279
|
+
var checked = _agentToolsState.selected.has(name);
|
|
34280
|
+
|
|
34281
|
+
// Status pill — surfaces the flags the old renderer threw away.
|
|
34282
|
+
var statusClass = 'ready';
|
|
34283
|
+
var statusLabel = 'Ready';
|
|
34284
|
+
var setupTarget = '';
|
|
34285
|
+
if (type === 'cli') {
|
|
34286
|
+
if (tool.blocked) { statusClass = 'blocked'; statusLabel = 'Blocked'; }
|
|
34287
|
+
else if (tool.installed === false) { statusClass = 'not-installed'; statusLabel = 'Not installed'; }
|
|
34288
|
+
} else if (type === 'api' || type === 'composio') {
|
|
34289
|
+
if (tool.connected === false) { statusClass = 'needs-setup'; statusLabel = 'Connect'; setupTarget = 'integrations'; }
|
|
34290
|
+
} else if (type === 'project') {
|
|
34291
|
+
if (tool.connected === false) { statusClass = 'needs-setup'; statusLabel = 'Path missing'; }
|
|
34292
|
+
} else if (type === 'mcp' && tool.connected === false) {
|
|
34293
|
+
statusClass = 'needs-setup'; statusLabel = 'Configure'; setupTarget = 'settings';
|
|
34294
|
+
}
|
|
34295
|
+
|
|
34296
|
+
var row = document.createElement('label');
|
|
34297
|
+
row.className = 'agent-tool-row';
|
|
34298
|
+
row.dataset.cat = cat;
|
|
34299
|
+
row.dataset.search = (name + ' ' + description + ' ' + cat + ' ' + (displayName || '')).toLowerCase();
|
|
34300
|
+
row.dataset.status = statusClass;
|
|
34301
|
+
row.dataset.enabled = checked ? '1' : '0';
|
|
34302
|
+
row.title = description ? (name + ' — ' + description) : name;
|
|
34303
|
+
|
|
34304
|
+
var badgeLabel = type === 'project-mcp' ? 'PROJ-MCP' : type;
|
|
34305
|
+
var badge = type ? '<span class="tt-badge ' + esc(type) + '">' + esc(badgeLabel) + '</span>' : '';
|
|
34306
|
+
var pathHint = (type === 'project' && tool.path) ? ' <span style="color:var(--text-muted);font-size:10px">' + esc(tool.path) + '</span>' : '';
|
|
34307
|
+
var statusEl;
|
|
34308
|
+
if (setupTarget) {
|
|
34309
|
+
statusEl = '<a class="tt-status ' + statusClass + '" href="#" onclick="event.preventDefault();event.stopPropagation();navigateTo(\'' + setupTarget + '\');hideAgentModal();" style="text-decoration:none"><span class="tt-dot"></span>' + esc(statusLabel) + ' →</a>';
|
|
34310
|
+
} else {
|
|
34311
|
+
statusEl = '<span class="tt-status ' + statusClass + '"><span class="tt-dot"></span>' + esc(statusLabel) + '</span>';
|
|
34312
|
+
}
|
|
34313
|
+
|
|
34314
|
+
row.innerHTML =
|
|
34315
|
+
'<input type="checkbox" class="agent-tool-cb" data-cat="' + esc(cat) + '" value="' + esc(name) + '"' + (checked ? ' checked' : '') + ' style="accent-color:var(--blue)">' +
|
|
34316
|
+
badge +
|
|
34317
|
+
'<span class="tt-name">' + esc(displayName) + (description && displayName !== description ? ' <span style="color:var(--text-muted);font-weight:400">— ' + esc(description) + '</span>' : '') + pathHint + '</span>' +
|
|
34318
|
+
statusEl;
|
|
34319
|
+
|
|
34320
|
+
var cb = row.querySelector('.agent-tool-cb');
|
|
34321
|
+
cb.addEventListener('change', function() {
|
|
34322
|
+
if (cb.checked) _agentToolsState.selected.add(name);
|
|
34323
|
+
else _agentToolsState.selected.delete(name);
|
|
34324
|
+
renderAgentToolPanel();
|
|
34325
|
+
});
|
|
34326
|
+
return row;
|
|
34327
|
+
}
|
|
34328
|
+
|
|
34329
|
+
function setAgentToolsFilter(filter) {
|
|
34330
|
+
_agentToolsState.filter = filter;
|
|
34331
|
+
document.querySelectorAll('.agent-tools-filter').forEach(function(b) {
|
|
34332
|
+
b.classList.toggle('active', b.dataset.filter === filter);
|
|
34333
|
+
});
|
|
34334
|
+
filterAgentTools();
|
|
34335
|
+
}
|
|
34336
|
+
|
|
34337
|
+
function filterAgentTools() {
|
|
34338
|
+
var searchEl = document.getElementById('agent-tools-search');
|
|
34339
|
+
_agentToolsState.search = searchEl ? searchEl.value.trim().toLowerCase() : '';
|
|
34340
|
+
var f = _agentToolsState.filter;
|
|
34341
|
+
var rows = document.querySelectorAll('#agent-tools-panel .agent-tool-row');
|
|
34342
|
+
var catCounts = {};
|
|
34343
|
+
rows.forEach(function(row) {
|
|
34344
|
+
var matchSearch = !_agentToolsState.search || row.dataset.search.indexOf(_agentToolsState.search) >= 0;
|
|
34345
|
+
var matchFilter =
|
|
34346
|
+
f === 'all' ||
|
|
34347
|
+
(f === 'enabled' && row.dataset.enabled === '1') ||
|
|
34348
|
+
(f === 'ready' && row.dataset.status === 'ready') ||
|
|
34349
|
+
(f === 'needs-setup' && (row.dataset.status === 'needs-setup' || row.dataset.status === 'not-installed' || row.dataset.status === 'blocked'));
|
|
34350
|
+
var visible = matchSearch && matchFilter;
|
|
34351
|
+
row.classList.toggle('hidden', !visible);
|
|
34352
|
+
if (visible) {
|
|
34353
|
+
var cat = row.dataset.cat;
|
|
34354
|
+
catCounts[cat] = (catCounts[cat] || 0) + 1;
|
|
33989
34355
|
}
|
|
33990
|
-
}
|
|
34356
|
+
});
|
|
34357
|
+
document.querySelectorAll('#agent-tools-panel .agent-tool-cat').forEach(function(hdr) {
|
|
34358
|
+
hdr.classList.toggle('hidden', !catCounts[hdr.dataset.category]);
|
|
34359
|
+
});
|
|
33991
34360
|
}
|
|
33992
34361
|
|
|
33993
34362
|
function getSelectedTools() {
|
|
33994
|
-
return Array.from(
|
|
34363
|
+
return Array.from(_agentToolsState.selected);
|
|
34364
|
+
}
|
|
34365
|
+
|
|
34366
|
+
// Split selection into the two persistence buckets the agent schema expects:
|
|
34367
|
+
// - projects[] : entries that were 'project:/abs/path'
|
|
34368
|
+
// - allowedTools[] : everything else (composio:slug, mcp__server, sdk/cli/api names)
|
|
34369
|
+
function splitAgentToolSelection() {
|
|
34370
|
+
var projects = [];
|
|
34371
|
+
var tools = [];
|
|
34372
|
+
Array.from(_agentToolsState.selected).forEach(function(v) {
|
|
34373
|
+
if (typeof v === 'string' && v.indexOf('project:') === 0) projects.push(v.substring('project:'.length));
|
|
34374
|
+
else tools.push(v);
|
|
34375
|
+
});
|
|
34376
|
+
return { projects: projects, tools: tools };
|
|
34377
|
+
}
|
|
34378
|
+
|
|
34379
|
+
// Tab switching inside the agent edit modal.
|
|
34380
|
+
function switchAgentTab(tab) {
|
|
34381
|
+
document.querySelectorAll('#agent-modal-tabs .agent-tab').forEach(function(b) {
|
|
34382
|
+
b.classList.toggle('active', b.dataset.tab === tab);
|
|
34383
|
+
});
|
|
34384
|
+
document.querySelectorAll('#agent-modal .agent-tab-pane').forEach(function(p) {
|
|
34385
|
+
p.style.display = p.dataset.tabPane === tab ? '' : 'none';
|
|
34386
|
+
});
|
|
34387
|
+
if (tab === 'goals') loadAgentGoalsPanel();
|
|
34388
|
+
}
|
|
34389
|
+
|
|
34390
|
+
// Goals tab inside the editor — list every goal with a checkbox tied
|
|
34391
|
+
// to its owner field. Saving the modal calls PUT /api/goals/:id with
|
|
34392
|
+
// the new owner; we collect pending changes in a Map so the user can
|
|
34393
|
+
// flip several before saving.
|
|
34394
|
+
var _agentGoalsCache = null;
|
|
34395
|
+
var _agentGoalsPendingOwners = {}; // goalId -> newOwner
|
|
34396
|
+
|
|
34397
|
+
async function loadAgentGoalsPanel() {
|
|
34398
|
+
var panel = document.getElementById('agent-goals-panel');
|
|
34399
|
+
if (!panel) return;
|
|
34400
|
+
var slug = document.getElementById('agent-edit-slug').value;
|
|
34401
|
+
var mode = document.getElementById('agent-edit-mode').value || 'agent';
|
|
34402
|
+
var thisOwner = mode === 'clementine' ? 'clementine' : (slug || '');
|
|
34403
|
+
if (!thisOwner) { panel.innerHTML = '<div style="color:var(--text-muted);font-size:12px;padding:8px">Save the agent first, then come back to assign goals.</div>'; return; }
|
|
34404
|
+
panel.innerHTML = '<div style="color:var(--text-muted);font-size:12px">Loading goals…</div>';
|
|
34405
|
+
try {
|
|
34406
|
+
var r = await apiFetch('/api/goals/progress');
|
|
34407
|
+
var d = await r.json();
|
|
34408
|
+
_agentGoalsCache = (d && d.goals) || [];
|
|
34409
|
+
_agentGoalsPendingOwners = {};
|
|
34410
|
+
var html = '';
|
|
34411
|
+
if (_agentGoalsCache.length === 0) {
|
|
34412
|
+
html = '<div style="color:var(--text-muted);font-size:12px;padding:8px">No goals yet. Use the Team → Goals tab or call goal_create.</div>';
|
|
34413
|
+
} else {
|
|
34414
|
+
_agentGoalsCache.forEach(function(g) {
|
|
34415
|
+
var assigned = g.owner === thisOwner;
|
|
34416
|
+
var ownerLbl = g.owner ? ('owned by ' + g.owner) : 'unassigned';
|
|
34417
|
+
html += '<label class="agent-tool-row" data-goal-id="' + esc(g.id) + '">' +
|
|
34418
|
+
'<input type="checkbox" class="agent-goal-cb" value="' + esc(g.id) + '"' + (assigned ? ' checked' : '') + ' style="accent-color:var(--blue)">' +
|
|
34419
|
+
'<span class="tt-badge">' + esc(g.status || 'pending') + '</span>' +
|
|
34420
|
+
'<span class="tt-name">' + esc(g.title || g.id) +
|
|
34421
|
+
(g.description ? ' <span style="color:var(--text-muted);font-weight:400">— ' + esc(String(g.description).slice(0, 100)) + '</span>' : '') +
|
|
34422
|
+
' <span style="color:var(--text-muted);font-size:10px">(' + esc(ownerLbl) + ')</span>' +
|
|
34423
|
+
'</span>' +
|
|
34424
|
+
'</label>';
|
|
34425
|
+
});
|
|
34426
|
+
}
|
|
34427
|
+
panel.innerHTML = html;
|
|
34428
|
+
panel.querySelectorAll('.agent-goal-cb').forEach(function(cb) {
|
|
34429
|
+
cb.addEventListener('change', function() {
|
|
34430
|
+
// Checked → take ownership; unchecked → fall back to Clementine
|
|
34431
|
+
// (the system default owner for orphaned goals).
|
|
34432
|
+
_agentGoalsPendingOwners[cb.value] = cb.checked ? thisOwner : 'clementine';
|
|
34433
|
+
});
|
|
34434
|
+
});
|
|
34435
|
+
} catch(e) {
|
|
34436
|
+
panel.innerHTML = '<div style="color:var(--red);font-size:12px">Failed to load goals: ' + esc(String(e)) + '</div>';
|
|
34437
|
+
}
|
|
34438
|
+
}
|
|
34439
|
+
|
|
34440
|
+
async function saveAgentGoalsPanel() {
|
|
34441
|
+
var entries = Object.keys(_agentGoalsPendingOwners);
|
|
34442
|
+
for (var i = 0; i < entries.length; i++) {
|
|
34443
|
+
var goalId = entries[i];
|
|
34444
|
+
var newOwner = _agentGoalsPendingOwners[goalId];
|
|
34445
|
+
try {
|
|
34446
|
+
await apiFetch('/api/goals/' + encodeURIComponent(goalId), {
|
|
34447
|
+
method: 'PUT',
|
|
34448
|
+
headers: { 'Content-Type': 'application/json' },
|
|
34449
|
+
body: JSON.stringify({ owner: newOwner }),
|
|
34450
|
+
});
|
|
34451
|
+
} catch(e) { /* surfaced via toast in caller */ }
|
|
34452
|
+
}
|
|
34453
|
+
_agentGoalsPendingOwners = {};
|
|
33995
34454
|
}
|
|
33996
34455
|
|
|
34456
|
+
// CSS-attribute escape — our category names contain spaces.
|
|
34457
|
+
function cssAttrEsc(s) { return String(s).replace(/"/g, '\\"'); }
|
|
34458
|
+
|
|
33997
34459
|
var _discordChannelsCache = null;
|
|
33998
34460
|
async function loadDiscordChannels(selectedValues) {
|
|
33999
34461
|
// Normalize to array
|
|
@@ -34068,16 +34530,39 @@ function showAgentCreateModal() {
|
|
|
34068
34530
|
document.getElementById('agent-modal-title').textContent = 'Hire a New Team Member';
|
|
34069
34531
|
document.getElementById('agent-submit-btn').textContent = 'Complete Hiring';
|
|
34070
34532
|
document.getElementById('agent-edit-slug').value = '';
|
|
34533
|
+
document.getElementById('agent-edit-mode').value = 'agent';
|
|
34071
34534
|
document.getElementById('agent-form').reset();
|
|
34072
34535
|
document.getElementById('agent-token-hint').style.display = 'none';
|
|
34073
34536
|
document.getElementById('agent-token-setup').style.display = 'none';
|
|
34074
34537
|
document.getElementById('agent-slack-token-hint').style.display = 'none';
|
|
34075
34538
|
document.getElementById('agent-slack-setup').style.display = 'none';
|
|
34539
|
+
// Hidden fields are visible for hired agents (Clementine hides them).
|
|
34540
|
+
setClementineMode(false);
|
|
34541
|
+
switchAgentTab('identity');
|
|
34076
34542
|
loadAgentProjectOptions();
|
|
34077
34543
|
loadAgentToolOptions();
|
|
34078
34544
|
loadDiscordChannels([]);
|
|
34079
34545
|
}
|
|
34080
34546
|
|
|
34547
|
+
// Hide / show fields that don't apply to Clementine herself.
|
|
34548
|
+
// Avatar URL, Primary Project select, Security Tier are managed elsewhere
|
|
34549
|
+
// for the main agent — surfacing them in the same modal would be misleading.
|
|
34550
|
+
function setClementineMode(isClementine) {
|
|
34551
|
+
['agent-identity-row-2', 'agent-project-row', 'agent-tier-row'].forEach(function(id) {
|
|
34552
|
+
var el = document.getElementById(id);
|
|
34553
|
+
if (el) el.style.display = isClementine ? 'none' : '';
|
|
34554
|
+
});
|
|
34555
|
+
// Discord/Slack token sections are also Clementine-managed (env-based);
|
|
34556
|
+
// hide them in the Connections tab when editing the main agent.
|
|
34557
|
+
['discord-section', 'slack-section'].forEach(function(id) {
|
|
34558
|
+
var el = document.getElementById(id);
|
|
34559
|
+
if (el) el.style.display = isClementine ? 'none' : '';
|
|
34560
|
+
});
|
|
34561
|
+
// Toggle the required attribute on Name — Clementine has a fixed name.
|
|
34562
|
+
var nameEl = document.getElementById('agent-name');
|
|
34563
|
+
if (nameEl) nameEl.readOnly = isClementine;
|
|
34564
|
+
}
|
|
34565
|
+
|
|
34081
34566
|
async function editAgent(slug) {
|
|
34082
34567
|
try {
|
|
34083
34568
|
var r = await apiFetch('/api/agents');
|
|
@@ -34089,9 +34574,13 @@ async function editAgent(slug) {
|
|
|
34089
34574
|
document.getElementById('agent-modal-title').textContent = 'Update Team Member: ' + a.name;
|
|
34090
34575
|
document.getElementById('agent-submit-btn').textContent = 'Save';
|
|
34091
34576
|
document.getElementById('agent-edit-slug').value = slug;
|
|
34577
|
+
document.getElementById('agent-edit-mode').value = 'agent';
|
|
34578
|
+
setClementineMode(false);
|
|
34579
|
+
switchAgentTab('identity');
|
|
34092
34580
|
document.getElementById('agent-name').value = a.name || '';
|
|
34093
34581
|
document.getElementById('agent-description').value = a.description || '';
|
|
34094
34582
|
document.getElementById('agent-avatar-url').value = a.avatar || '';
|
|
34583
|
+
document.getElementById('agent-personality').value = a.systemPromptBody || a.personality || '';
|
|
34095
34584
|
loadDiscordChannels(a.channelName || []);
|
|
34096
34585
|
document.getElementById('agent-team-chat').checked = a.teamChat || false;
|
|
34097
34586
|
document.getElementById('agent-respond-all').checked = a.respondToAll || false;
|
|
@@ -34099,7 +34588,12 @@ async function editAgent(slug) {
|
|
|
34099
34588
|
document.getElementById('agent-tier').value = String(a.tier || 2);
|
|
34100
34589
|
document.getElementById('agent-canmessage').value = (a.canMessage || []).join(', ');
|
|
34101
34590
|
loadAgentProjectOptions(a.project || '');
|
|
34102
|
-
|
|
34591
|
+
// Pre-check both tools and project access — projects are stored as
|
|
34592
|
+
// 'project:/abs/path' inside the same selection set the Equipment panel
|
|
34593
|
+
// tracks, so loadAgentToolOptions's normalizer handles them uniformly.
|
|
34594
|
+
var initialSelection = (a.allowedTools || []).slice();
|
|
34595
|
+
(a.projects || []).forEach(function(p) { initialSelection.push('project:' + p); });
|
|
34596
|
+
loadAgentToolOptions(initialSelection);
|
|
34103
34597
|
document.getElementById('agent-allowed-users').value = (a.allowedUsers || []).join(', ');
|
|
34104
34598
|
// Budget
|
|
34105
34599
|
document.getElementById('agent-budget').value = String(a.budgetMonthlyCents || 0);
|
|
@@ -34152,6 +34646,43 @@ function hideAgentModal() {
|
|
|
34152
34646
|
document.getElementById('agent-modal').classList.remove('show');
|
|
34153
34647
|
}
|
|
34154
34648
|
|
|
34649
|
+
// Open the same tabbed modal in Clementine mode — reads/writes the
|
|
34650
|
+
// assistant.profile block of clementine.json instead of an agent.md.
|
|
34651
|
+
async function editClementine() {
|
|
34652
|
+
try {
|
|
34653
|
+
var r = await apiFetch('/api/clementine/profile');
|
|
34654
|
+
var d = await r.json();
|
|
34655
|
+
var p = d.profile || {};
|
|
34656
|
+
document.getElementById('agent-modal').classList.add('show');
|
|
34657
|
+
document.getElementById('agent-modal-title').textContent = 'Configure ' + (d.name || 'Clementine');
|
|
34658
|
+
document.getElementById('agent-submit-btn').textContent = 'Save';
|
|
34659
|
+
document.getElementById('agent-edit-slug').value = '__clementine__';
|
|
34660
|
+
document.getElementById('agent-edit-mode').value = 'clementine';
|
|
34661
|
+
setClementineMode(true);
|
|
34662
|
+
switchAgentTab('identity');
|
|
34663
|
+
document.getElementById('agent-name').value = d.name || 'Clementine';
|
|
34664
|
+
document.getElementById('agent-description').value = 'Master delegator and personal assistant';
|
|
34665
|
+
document.getElementById('agent-personality').value = p.systemPrompt || '';
|
|
34666
|
+
document.getElementById('agent-model').value = p.model || '';
|
|
34667
|
+
document.getElementById('agent-canmessage').value = '';
|
|
34668
|
+
document.getElementById('agent-allowed-users').value = (p.allowedUsers || []).join(', ');
|
|
34669
|
+
document.getElementById('agent-budget').value = String(p.budgetMonthlyCents || 0);
|
|
34670
|
+
if (p.sendPolicy) {
|
|
34671
|
+
document.getElementById('agent-send-max-daily').value = String(p.sendPolicy.maxDailyEmails || 50);
|
|
34672
|
+
document.getElementById('agent-send-approval').value = p.sendPolicy.requiresApproval || '';
|
|
34673
|
+
document.getElementById('agent-send-biz-hours').checked = !!p.sendPolicy.businessHoursOnly;
|
|
34674
|
+
} else {
|
|
34675
|
+
document.getElementById('agent-send-max-daily').value = '50';
|
|
34676
|
+
document.getElementById('agent-send-approval').value = '';
|
|
34677
|
+
document.getElementById('agent-send-biz-hours').checked = false;
|
|
34678
|
+
}
|
|
34679
|
+
loadDiscordChannels(p.channels || []);
|
|
34680
|
+
var initial = (p.allowedTools || []).slice();
|
|
34681
|
+
(p.allowedProjects || []).forEach(function(path) { initial.push('project:' + path); });
|
|
34682
|
+
loadAgentToolOptions(initial);
|
|
34683
|
+
} catch(e) { toast('Failed to load Clementine profile: ' + String(e), 'error'); }
|
|
34684
|
+
}
|
|
34685
|
+
|
|
34155
34686
|
var tokenInputDebounce = null;
|
|
34156
34687
|
function onTokenInput(token) {
|
|
34157
34688
|
clearTimeout(tokenInputDebounce);
|
|
@@ -34197,12 +34728,64 @@ async function submitAgentForm(e) {
|
|
|
34197
34728
|
}
|
|
34198
34729
|
async function _submitAgentFormInner(e) {
|
|
34199
34730
|
var editSlug = document.getElementById('agent-edit-slug').value;
|
|
34200
|
-
var
|
|
34731
|
+
var modeEl = document.getElementById('agent-edit-mode');
|
|
34732
|
+
var mode = modeEl ? modeEl.value || 'agent' : 'agent';
|
|
34733
|
+
var isClementine = mode === 'clementine';
|
|
34734
|
+
var isEdit = !isClementine && Boolean(editSlug);
|
|
34201
34735
|
|
|
34202
34736
|
var selectedChannels = Array.from(document.querySelectorAll('.agent-channel-cb:checked')).map(function(cb) { return cb.value; });
|
|
34203
|
-
var channelName = selectedChannels.length === 0 ? undefined : selectedChannels.length === 1 ? selectedChannels[0] : selectedChannels;
|
|
34204
34737
|
var teamChat = document.getElementById('agent-team-chat').checked;
|
|
34205
34738
|
var respondToAll = document.getElementById('agent-respond-all').checked;
|
|
34739
|
+
|
|
34740
|
+
// Equipment selection — split into the agent schema's two buckets:
|
|
34741
|
+
// a flat allowedTools[] and projects[] of absolute paths.
|
|
34742
|
+
var split = splitAgentToolSelection();
|
|
34743
|
+
|
|
34744
|
+
var au = document.getElementById('agent-allowed-users').value.trim();
|
|
34745
|
+
var allowedUsers = au ? au.split(',').map(function(s) { return s.trim(); }).filter(Boolean) : [];
|
|
34746
|
+
|
|
34747
|
+
var sendApproval = document.getElementById('agent-send-approval').value;
|
|
34748
|
+
var sendPolicy = sendApproval ? {
|
|
34749
|
+
maxDailyEmails: parseInt(document.getElementById('agent-send-max-daily').value) || 50,
|
|
34750
|
+
requiresApproval: sendApproval,
|
|
34751
|
+
businessHoursOnly: document.getElementById('agent-send-biz-hours').checked,
|
|
34752
|
+
} : undefined;
|
|
34753
|
+
|
|
34754
|
+
var budgetVal = parseInt(document.getElementById('agent-budget').value) || 0;
|
|
34755
|
+
|
|
34756
|
+
if (isClementine) {
|
|
34757
|
+
// Clementine path — write the assistant.profile block in clementine.json.
|
|
34758
|
+
var profilePayload = {
|
|
34759
|
+
systemPrompt: document.getElementById('agent-personality').value,
|
|
34760
|
+
model: document.getElementById('agent-model').value || undefined,
|
|
34761
|
+
allowedTools: split.tools,
|
|
34762
|
+
allowedProjects: split.projects,
|
|
34763
|
+
allowedUsers: allowedUsers,
|
|
34764
|
+
channels: selectedChannels,
|
|
34765
|
+
budgetMonthlyCents: budgetVal,
|
|
34766
|
+
sendPolicy: sendPolicy,
|
|
34767
|
+
};
|
|
34768
|
+
try {
|
|
34769
|
+
var rc = await apiFetch('/api/clementine/profile', {
|
|
34770
|
+
method: 'PUT',
|
|
34771
|
+
headers: { 'Content-Type': 'application/json' },
|
|
34772
|
+
body: JSON.stringify(profilePayload),
|
|
34773
|
+
});
|
|
34774
|
+
var dc = await rc.json();
|
|
34775
|
+
if (dc.ok) {
|
|
34776
|
+
await saveAgentGoalsPanel();
|
|
34777
|
+
toast('Saved Clementine profile', 'success');
|
|
34778
|
+
hideAgentModal();
|
|
34779
|
+
refreshTeam();
|
|
34780
|
+
} else {
|
|
34781
|
+
toast(dc.error || 'Failed', 'error');
|
|
34782
|
+
}
|
|
34783
|
+
} catch(err) { toast(String(err), 'error'); }
|
|
34784
|
+
return;
|
|
34785
|
+
}
|
|
34786
|
+
|
|
34787
|
+
// Hired-agent path — same shape as before, plus projects[] from Equipment.
|
|
34788
|
+
var channelName = selectedChannels.length === 0 ? undefined : selectedChannels.length === 1 ? selectedChannels[0] : selectedChannels;
|
|
34206
34789
|
var payload = {
|
|
34207
34790
|
name: document.getElementById('agent-name').value.trim(),
|
|
34208
34791
|
description: document.getElementById('agent-description').value.trim(),
|
|
@@ -34212,6 +34795,7 @@ async function _submitAgentFormInner(e) {
|
|
|
34212
34795
|
respondToAll: channelName ? respondToAll : undefined,
|
|
34213
34796
|
model: document.getElementById('agent-model').value || undefined,
|
|
34214
34797
|
project: document.getElementById('agent-project').value || undefined,
|
|
34798
|
+
projects: split.projects,
|
|
34215
34799
|
tier: parseInt(document.getElementById('agent-tier').value) || 2,
|
|
34216
34800
|
avatar: document.getElementById('agent-avatar-url').value.trim() || undefined,
|
|
34217
34801
|
};
|
|
@@ -34219,11 +34803,8 @@ async function _submitAgentFormInner(e) {
|
|
|
34219
34803
|
var cm = document.getElementById('agent-canmessage').value.trim();
|
|
34220
34804
|
if (cm) payload.canMessage = cm.split(',').map(function(s) { return s.trim(); }).filter(Boolean);
|
|
34221
34805
|
|
|
34222
|
-
|
|
34223
|
-
|
|
34224
|
-
|
|
34225
|
-
var au = document.getElementById('agent-allowed-users').value.trim();
|
|
34226
|
-
payload.allowedUsers = au ? au.split(',').map(function(s) { return s.trim(); }).filter(Boolean) : [];
|
|
34806
|
+
if (split.tools.length) payload.allowedTools = split.tools;
|
|
34807
|
+
payload.allowedUsers = allowedUsers;
|
|
34227
34808
|
|
|
34228
34809
|
var discordToken = document.getElementById('agent-discord-token').value.trim();
|
|
34229
34810
|
if (discordToken) payload.discordToken = discordToken;
|
|
@@ -34240,18 +34821,7 @@ async function _submitAgentFormInner(e) {
|
|
|
34240
34821
|
var slackChannelId = document.getElementById('agent-slack-channel-id').value.trim();
|
|
34241
34822
|
if (slackChannelId) payload.slackChannelId = slackChannelId;
|
|
34242
34823
|
|
|
34243
|
-
|
|
34244
|
-
var sendApproval = document.getElementById('agent-send-approval').value;
|
|
34245
|
-
if (sendApproval) {
|
|
34246
|
-
payload.sendPolicy = {
|
|
34247
|
-
maxDailyEmails: parseInt(document.getElementById('agent-send-max-daily').value) || 50,
|
|
34248
|
-
requiresApproval: sendApproval,
|
|
34249
|
-
businessHoursOnly: document.getElementById('agent-send-biz-hours').checked,
|
|
34250
|
-
};
|
|
34251
|
-
}
|
|
34252
|
-
|
|
34253
|
-
// Budget
|
|
34254
|
-
var budgetVal = parseInt(document.getElementById('agent-budget').value) || 0;
|
|
34824
|
+
if (sendPolicy) payload.sendPolicy = sendPolicy;
|
|
34255
34825
|
if (budgetVal > 0) payload.budgetMonthlyCents = budgetVal;
|
|
34256
34826
|
|
|
34257
34827
|
// Role template — triggers scaffolding on the backend
|
|
@@ -34270,13 +34840,16 @@ async function _submitAgentFormInner(e) {
|
|
|
34270
34840
|
});
|
|
34271
34841
|
var d = await r.json();
|
|
34272
34842
|
if (d.ok) {
|
|
34843
|
+
// Apply pending goal-owner changes after the agent is persisted —
|
|
34844
|
+
// matters most on Create, when the slug only just became real.
|
|
34845
|
+
await saveAgentGoalsPanel();
|
|
34273
34846
|
toast((isEdit ? 'Updated' : 'Created') + ' agent: ' + (d.agent?.name || payload.name), 'success');
|
|
34274
34847
|
hideAgentModal();
|
|
34275
34848
|
refreshTeam();
|
|
34276
34849
|
} else {
|
|
34277
34850
|
toast(d.error || 'Failed', 'error');
|
|
34278
34851
|
}
|
|
34279
|
-
} catch(
|
|
34852
|
+
} catch(err) { toast(String(err), 'error'); }
|
|
34280
34853
|
}
|
|
34281
34854
|
|
|
34282
34855
|
function confirmDeleteAgent(slug, name) { deleteAgent(slug); }
|