clementine-agent 1.18.97 → 1.18.99

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.
Files changed (2) hide show
  1. package/dist/cli/dashboard.js +571 -55
  2. package/package.json +1 -1
@@ -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).
@@ -24723,11 +24867,30 @@ function renderRunListBody(allRuns) {
24723
24867
  html += '<div class="empty-state" style="padding:36px 24px;text-align:center;color:var(--text-muted)"><div style="font-size:14px;margin-bottom:6px">No runs match the current filter.</div><div style="font-size:12px">Try widening the time window or clearing the task-name filter.</div></div>';
24724
24868
  return html;
24725
24869
  }
24870
+ // PRD §5.3 Phase 5.3c / 1.18.99: column sort. Apply the current
24871
+ // sortBy/sortDir AFTER filtering so users see the natural ordering of
24872
+ // their narrowed dataset. Stable: missing values sort to the end.
24873
+ filtered.sort(_runListComparator(_runListState.sortBy, _runListState.sortDir));
24726
24874
  // Table — same shape as the Recent History list on the Tasks page,
24727
- // but sortable and with a Trigger + Cost column.
24875
+ // but sortable and with a Trigger + Cost column. Helper renders an
24876
+ // active-state arrow when a column is the current sortBy target.
24877
+ function sortHdr(col, label, title) {
24878
+ var active = _runListState.sortBy === col;
24879
+ var arrow = active ? (_runListState.sortDir === 'asc' ? ' ↑' : ' ↓') : '';
24880
+ var titleAttr = title ? ' title="' + esc(title) + '"' : '';
24881
+ var color = active ? 'var(--text-primary)' : 'var(--text-muted)';
24882
+ return '<div' + titleAttr + ' onclick="setRunListSort(\\x27' + jsStr(col) + '\\x27)" style="cursor:pointer;color:' + color + ';user-select:none">' + esc(label) + esc(arrow) + '</div>';
24883
+ }
24728
24884
  html += '<div style="background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius)">';
24729
24885
  html += '<div style="display:grid;grid-template-columns:24px 24px minmax(180px,1.2fr) 80px minmax(160px,1fr) 70px 70px auto;gap:10px;padding:8px 14px;border-bottom:1px solid var(--border);font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.04em;font-weight:500">'
24730
- + '<div></div><div title="Goal verdict">Goal</div><div>Task</div><div>Trigger</div><div>Started</div><div>Duration</div><div title="Total cost in USD">Cost</div><div></div>'
24886
+ + '<div></div>'
24887
+ + '<div title="Goal verdict">Goal</div>'
24888
+ + sortHdr('jobName', 'Task', 'Click to sort by name')
24889
+ + '<div>Trigger</div>'
24890
+ + sortHdr('startedAt', 'Started', 'Click to sort by start time')
24891
+ + sortHdr('durationMs', 'Duration', 'Click to sort by duration')
24892
+ + sortHdr('totalCostUsd', 'Cost', 'Click to sort by cost')
24893
+ + '<div></div>'
24731
24894
  + '</div>';
24732
24895
  for (var i = 0; i < filtered.length; i++) {
24733
24896
  var entry = filtered[i] || {};
@@ -24844,6 +25007,10 @@ function _runListChip(label, options, stateKey) {
24844
25007
 
24845
25008
  function onRunListChipClick(key, value) {
24846
25009
  _runListState[key] = value;
25010
+ // PRD §5.3 / 1.18.99: any manual filter change drops out of the active
25011
+ // saved view — the chip row deselects to make it obvious you're now on
25012
+ // a custom filter set.
25013
+ _runListState.activeView = 'custom';
24847
25014
  _runListSaveView();
24848
25015
  var panel = document.getElementById('panel-runs');
24849
25016
  if (panel) panel.innerHTML = renderRunListBody(_runListState.data);
@@ -24851,6 +25018,7 @@ function onRunListChipClick(key, value) {
24851
25018
 
24852
25019
  function onRunListSearch(value) {
24853
25020
  _runListState.filterText = value;
25021
+ _runListState.activeView = 'custom';
24854
25022
  _runListSaveView();
24855
25023
  // Debounce-by-render: just re-render. Filtering is in-memory + cheap.
24856
25024
  var panel = document.getElementById('panel-runs');
@@ -24858,13 +25026,10 @@ function onRunListSearch(value) {
24858
25026
  }
24859
25027
 
24860
25028
  function resetRunListFilters() {
24861
- _runListState.filterStatus = 'failed';
24862
- _runListState.filterWindow = '24h';
24863
- _runListState.filterCategory = 'all';
24864
- _runListState.filterText = '';
24865
- _runListSaveView();
24866
- var panel = document.getElementById('panel-runs');
24867
- if (panel) panel.innerHTML = renderRunListBody(_runListState.data);
25029
+ // Reset jumps back to the default Failures · 24h built-in view, which
25030
+ // also lights up that chip again so users see the round-trip.
25031
+ applyRunListView('failures-24h');
25032
+ return;
24868
25033
  }
24869
25034
 
24870
25035
  // Wire the panel's click handler so clicking anywhere on a row opens the
@@ -34035,41 +34200,281 @@ async function loadAgentProjectOptions(selected) {
34035
34200
  } catch(e) { /* projects are optional */ }
34036
34201
  }
34037
34202
 
34203
+ // ── Equipment panel ────────────────────────────────────────────────
34204
+ // Renders the unified tool/CLI/MCP/Composio/Project picker with type
34205
+ // badges, status pills, and category grouping. Status pills surface the
34206
+ // connected/installed/blocked flags that the previous renderer dropped,
34207
+ // so users can see at a glance whether a tool will actually work for
34208
+ // this agent. Local Projects are first-class entries (name format:
34209
+ // 'project:/abs/path') so a single check grants the agent access to the
34210
+ // project; project-scoped MCP servers appear as their own categories.
34211
+ // Composio toolkits are surfaced when isComposioEnabled.
34212
+ var _agentToolsState = {
34213
+ filter: 'all',
34214
+ search: '',
34215
+ data: null, // {categories, composioError}
34216
+ selected: new Set(),
34217
+ };
34218
+
34038
34219
  async function loadAgentToolOptions(selectedTools) {
34220
+ var panel = document.getElementById('agent-tools-panel');
34221
+ if (!panel) return;
34222
+ panel.innerHTML = '<div style="color:var(--text-muted);font-size:12px">Loading tools...</div>';
34223
+ _agentToolsState.selected = new Set(selectedTools || []);
34039
34224
  try {
34040
34225
  var r = await apiFetch('/api/available-tools');
34041
34226
  var d = await r.json();
34042
- var panel = document.getElementById('agent-tools-panel');
34043
- panel.innerHTML = '';
34044
- var selected = selectedTools || [];
34045
- for (var cat in d.categories) {
34046
- var header = document.createElement('div');
34047
- header.style.cssText = 'font-weight:600;font-size:11px;color:var(--text-muted);margin:8px 0 4px;cursor:pointer;user-select:none';
34048
- header.textContent = '▸ ' + cat;
34049
- header.dataset.category = cat;
34050
- header.onclick = (function(catName, hdr) { return function() {
34051
- var cbs = panel.querySelectorAll('.agent-tool-cb[data-cat="' + catName + '"]');
34052
- var allChecked = Array.from(cbs).every(function(cb) { return cb.checked; });
34053
- cbs.forEach(function(cb) { cb.checked = !allChecked; });
34054
- }; })(cat, header);
34055
- panel.appendChild(header);
34056
- d.categories[cat].forEach(function(tool) {
34057
- var toolName = typeof tool === 'string' ? tool : tool.name;
34058
- var toolLabel = typeof tool === 'string' ? tool : (tool.name + (tool.description ? ' — ' + tool.description : ''));
34059
- var label = document.createElement('label');
34060
- label.style.cssText = 'display:block;font-size:12px;padding:2px 0 2px 8px;cursor:pointer';
34061
- var checked = selected.indexOf(toolName) >= 0 ? ' checked' : '';
34062
- label.innerHTML = '<input type="checkbox" class="agent-tool-cb" data-cat="' + cat + '" value="' + toolName + '"' + checked + ' style="margin-right:6px">' + toolLabel;
34063
- panel.appendChild(label);
34227
+ _agentToolsState.data = d;
34228
+ renderAgentToolPanel();
34229
+ } catch(e) {
34230
+ panel.innerHTML = '<div style="color:var(--red);font-size:12px">Failed to load tools: ' + esc(String(e)) + '</div>';
34231
+ }
34232
+ }
34233
+
34234
+ function renderAgentToolPanel() {
34235
+ var panel = document.getElementById('agent-tools-panel');
34236
+ var summary = document.getElementById('agent-tools-summary');
34237
+ if (!panel) return;
34238
+ var d = _agentToolsState.data;
34239
+ if (!d || !d.categories) { panel.innerHTML = ''; return; }
34240
+
34241
+ panel.innerHTML = '';
34242
+ var totalEnabled = 0;
34243
+ var totalCount = 0;
34244
+ var typeOrder = { 'sdk': 0, 'cli': 1, 'mcp': 2, 'api': 3, 'project-mcp': 4, 'project': 5, 'composio': 6 };
34245
+
34246
+ Object.keys(d.categories).forEach(function(cat) {
34247
+ var entries = d.categories[cat] || [];
34248
+ var catEnabledCount = 0;
34249
+ entries.forEach(function(t) { if (_agentToolsState.selected.has(typeof t === 'string' ? t : t.name)) catEnabledCount++; });
34250
+ totalCount += entries.length;
34251
+ totalEnabled += catEnabledCount;
34252
+
34253
+ var header = document.createElement('div');
34254
+ header.className = 'agent-tool-cat';
34255
+ header.dataset.category = cat;
34256
+ header.innerHTML = '▸ ' + esc(cat) + '<span class="cat-count">' + catEnabledCount + '/' + entries.length + '</span>';
34257
+ header.onclick = (function(catName) { return function() {
34258
+ var rows = panel.querySelectorAll('.agent-tool-cb[data-cat="' + cssAttrEsc(catName) + '"]');
34259
+ var visible = Array.from(rows).filter(function(cb) {
34260
+ return !cb.closest('.agent-tool-row.hidden');
34064
34261
  });
34262
+ var allChecked = visible.length > 0 && visible.every(function(cb) { return cb.checked; });
34263
+ visible.forEach(function(cb) {
34264
+ cb.checked = !allChecked;
34265
+ if (cb.checked) _agentToolsState.selected.add(cb.value);
34266
+ else _agentToolsState.selected.delete(cb.value);
34267
+ });
34268
+ renderAgentToolPanel();
34269
+ }; })(cat);
34270
+ panel.appendChild(header);
34271
+
34272
+ entries.slice().sort(function(a, b) {
34273
+ var ta = typeOrder[a.type]; if (ta === undefined) ta = 9;
34274
+ var tb = typeOrder[b.type]; if (tb === undefined) tb = 9;
34275
+ if (ta !== tb) return ta - tb;
34276
+ return String(a.name || '').localeCompare(String(b.name || ''));
34277
+ }).forEach(function(tool) {
34278
+ panel.appendChild(buildAgentToolRow(cat, tool));
34279
+ });
34280
+ });
34281
+
34282
+ if (summary) {
34283
+ var msg = totalEnabled + ' of ' + totalCount + ' enabled';
34284
+ if (d.composioError) msg += ' · Composio: ' + d.composioError;
34285
+ summary.textContent = msg;
34286
+ }
34287
+
34288
+ filterAgentTools();
34289
+ }
34290
+
34291
+ function buildAgentToolRow(cat, tool) {
34292
+ var name = typeof tool === 'string' ? tool : tool.name;
34293
+ var description = typeof tool === 'string' ? '' : (tool.description || '');
34294
+ var type = typeof tool === 'string' ? '' : (tool.type || '');
34295
+ var displayName = (typeof tool === 'object' && tool.displayName) ? tool.displayName
34296
+ : (type === 'project' && tool.projectName) ? tool.projectName
34297
+ : name;
34298
+ var checked = _agentToolsState.selected.has(name);
34299
+
34300
+ // Status pill — surfaces the flags the old renderer threw away.
34301
+ var statusClass = 'ready';
34302
+ var statusLabel = 'Ready';
34303
+ var setupTarget = '';
34304
+ if (type === 'cli') {
34305
+ if (tool.blocked) { statusClass = 'blocked'; statusLabel = 'Blocked'; }
34306
+ else if (tool.installed === false) { statusClass = 'not-installed'; statusLabel = 'Not installed'; }
34307
+ } else if (type === 'api' || type === 'composio') {
34308
+ if (tool.connected === false) { statusClass = 'needs-setup'; statusLabel = 'Connect'; setupTarget = 'integrations'; }
34309
+ } else if (type === 'project') {
34310
+ if (tool.connected === false) { statusClass = 'needs-setup'; statusLabel = 'Path missing'; }
34311
+ } else if (type === 'mcp' && tool.connected === false) {
34312
+ statusClass = 'needs-setup'; statusLabel = 'Configure'; setupTarget = 'settings';
34313
+ }
34314
+
34315
+ var row = document.createElement('label');
34316
+ row.className = 'agent-tool-row';
34317
+ row.dataset.cat = cat;
34318
+ row.dataset.search = (name + ' ' + description + ' ' + cat + ' ' + (displayName || '')).toLowerCase();
34319
+ row.dataset.status = statusClass;
34320
+ row.dataset.enabled = checked ? '1' : '0';
34321
+ row.title = description ? (name + ' — ' + description) : name;
34322
+
34323
+ var badgeLabel = type === 'project-mcp' ? 'PROJ-MCP' : type;
34324
+ var badge = type ? '<span class="tt-badge ' + esc(type) + '">' + esc(badgeLabel) + '</span>' : '';
34325
+ var pathHint = (type === 'project' && tool.path) ? ' <span style="color:var(--text-muted);font-size:10px">' + esc(tool.path) + '</span>' : '';
34326
+ var statusEl;
34327
+ if (setupTarget) {
34328
+ 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>';
34329
+ } else {
34330
+ statusEl = '<span class="tt-status ' + statusClass + '"><span class="tt-dot"></span>' + esc(statusLabel) + '</span>';
34331
+ }
34332
+
34333
+ row.innerHTML =
34334
+ '<input type="checkbox" class="agent-tool-cb" data-cat="' + esc(cat) + '" value="' + esc(name) + '"' + (checked ? ' checked' : '') + ' style="accent-color:var(--blue)">' +
34335
+ badge +
34336
+ '<span class="tt-name">' + esc(displayName) + (description && displayName !== description ? ' <span style="color:var(--text-muted);font-weight:400">— ' + esc(description) + '</span>' : '') + pathHint + '</span>' +
34337
+ statusEl;
34338
+
34339
+ var cb = row.querySelector('.agent-tool-cb');
34340
+ cb.addEventListener('change', function() {
34341
+ if (cb.checked) _agentToolsState.selected.add(name);
34342
+ else _agentToolsState.selected.delete(name);
34343
+ renderAgentToolPanel();
34344
+ });
34345
+ return row;
34346
+ }
34347
+
34348
+ function setAgentToolsFilter(filter) {
34349
+ _agentToolsState.filter = filter;
34350
+ document.querySelectorAll('.agent-tools-filter').forEach(function(b) {
34351
+ b.classList.toggle('active', b.dataset.filter === filter);
34352
+ });
34353
+ filterAgentTools();
34354
+ }
34355
+
34356
+ function filterAgentTools() {
34357
+ var searchEl = document.getElementById('agent-tools-search');
34358
+ _agentToolsState.search = searchEl ? searchEl.value.trim().toLowerCase() : '';
34359
+ var f = _agentToolsState.filter;
34360
+ var rows = document.querySelectorAll('#agent-tools-panel .agent-tool-row');
34361
+ var catCounts = {};
34362
+ rows.forEach(function(row) {
34363
+ var matchSearch = !_agentToolsState.search || row.dataset.search.indexOf(_agentToolsState.search) >= 0;
34364
+ var matchFilter =
34365
+ f === 'all' ||
34366
+ (f === 'enabled' && row.dataset.enabled === '1') ||
34367
+ (f === 'ready' && row.dataset.status === 'ready') ||
34368
+ (f === 'needs-setup' && (row.dataset.status === 'needs-setup' || row.dataset.status === 'not-installed' || row.dataset.status === 'blocked'));
34369
+ var visible = matchSearch && matchFilter;
34370
+ row.classList.toggle('hidden', !visible);
34371
+ if (visible) {
34372
+ var cat = row.dataset.cat;
34373
+ catCounts[cat] = (catCounts[cat] || 0) + 1;
34065
34374
  }
34066
- } catch(e) { /* fallback — panel stays empty */ }
34375
+ });
34376
+ document.querySelectorAll('#agent-tools-panel .agent-tool-cat').forEach(function(hdr) {
34377
+ hdr.classList.toggle('hidden', !catCounts[hdr.dataset.category]);
34378
+ });
34067
34379
  }
34068
34380
 
34069
34381
  function getSelectedTools() {
34070
- return Array.from(document.querySelectorAll('.agent-tool-cb:checked')).map(function(cb) { return cb.value; });
34382
+ return Array.from(_agentToolsState.selected);
34383
+ }
34384
+
34385
+ // Split selection into the two persistence buckets the agent schema expects:
34386
+ // - projects[] : entries that were 'project:/abs/path'
34387
+ // - allowedTools[] : everything else (composio:slug, mcp__server, sdk/cli/api names)
34388
+ function splitAgentToolSelection() {
34389
+ var projects = [];
34390
+ var tools = [];
34391
+ Array.from(_agentToolsState.selected).forEach(function(v) {
34392
+ if (typeof v === 'string' && v.indexOf('project:') === 0) projects.push(v.substring('project:'.length));
34393
+ else tools.push(v);
34394
+ });
34395
+ return { projects: projects, tools: tools };
34396
+ }
34397
+
34398
+ // Tab switching inside the agent edit modal.
34399
+ function switchAgentTab(tab) {
34400
+ document.querySelectorAll('#agent-modal-tabs .agent-tab').forEach(function(b) {
34401
+ b.classList.toggle('active', b.dataset.tab === tab);
34402
+ });
34403
+ document.querySelectorAll('#agent-modal .agent-tab-pane').forEach(function(p) {
34404
+ p.style.display = p.dataset.tabPane === tab ? '' : 'none';
34405
+ });
34406
+ if (tab === 'goals') loadAgentGoalsPanel();
34071
34407
  }
34072
34408
 
34409
+ // Goals tab inside the editor — list every goal with a checkbox tied
34410
+ // to its owner field. Saving the modal calls PUT /api/goals/:id with
34411
+ // the new owner; we collect pending changes in a Map so the user can
34412
+ // flip several before saving.
34413
+ var _agentGoalsCache = null;
34414
+ var _agentGoalsPendingOwners = {}; // goalId -> newOwner
34415
+
34416
+ async function loadAgentGoalsPanel() {
34417
+ var panel = document.getElementById('agent-goals-panel');
34418
+ if (!panel) return;
34419
+ var slug = document.getElementById('agent-edit-slug').value;
34420
+ var mode = document.getElementById('agent-edit-mode').value || 'agent';
34421
+ var thisOwner = mode === 'clementine' ? 'clementine' : (slug || '');
34422
+ 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; }
34423
+ panel.innerHTML = '<div style="color:var(--text-muted);font-size:12px">Loading goals…</div>';
34424
+ try {
34425
+ var r = await apiFetch('/api/goals/progress');
34426
+ var d = await r.json();
34427
+ _agentGoalsCache = (d && d.goals) || [];
34428
+ _agentGoalsPendingOwners = {};
34429
+ var html = '';
34430
+ if (_agentGoalsCache.length === 0) {
34431
+ 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>';
34432
+ } else {
34433
+ _agentGoalsCache.forEach(function(g) {
34434
+ var assigned = g.owner === thisOwner;
34435
+ var ownerLbl = g.owner ? ('owned by ' + g.owner) : 'unassigned';
34436
+ html += '<label class="agent-tool-row" data-goal-id="' + esc(g.id) + '">' +
34437
+ '<input type="checkbox" class="agent-goal-cb" value="' + esc(g.id) + '"' + (assigned ? ' checked' : '') + ' style="accent-color:var(--blue)">' +
34438
+ '<span class="tt-badge">' + esc(g.status || 'pending') + '</span>' +
34439
+ '<span class="tt-name">' + esc(g.title || g.id) +
34440
+ (g.description ? ' <span style="color:var(--text-muted);font-weight:400">— ' + esc(String(g.description).slice(0, 100)) + '</span>' : '') +
34441
+ ' <span style="color:var(--text-muted);font-size:10px">(' + esc(ownerLbl) + ')</span>' +
34442
+ '</span>' +
34443
+ '</label>';
34444
+ });
34445
+ }
34446
+ panel.innerHTML = html;
34447
+ panel.querySelectorAll('.agent-goal-cb').forEach(function(cb) {
34448
+ cb.addEventListener('change', function() {
34449
+ // Checked → take ownership; unchecked → fall back to Clementine
34450
+ // (the system default owner for orphaned goals).
34451
+ _agentGoalsPendingOwners[cb.value] = cb.checked ? thisOwner : 'clementine';
34452
+ });
34453
+ });
34454
+ } catch(e) {
34455
+ panel.innerHTML = '<div style="color:var(--red);font-size:12px">Failed to load goals: ' + esc(String(e)) + '</div>';
34456
+ }
34457
+ }
34458
+
34459
+ async function saveAgentGoalsPanel() {
34460
+ var entries = Object.keys(_agentGoalsPendingOwners);
34461
+ for (var i = 0; i < entries.length; i++) {
34462
+ var goalId = entries[i];
34463
+ var newOwner = _agentGoalsPendingOwners[goalId];
34464
+ try {
34465
+ await apiFetch('/api/goals/' + encodeURIComponent(goalId), {
34466
+ method: 'PUT',
34467
+ headers: { 'Content-Type': 'application/json' },
34468
+ body: JSON.stringify({ owner: newOwner }),
34469
+ });
34470
+ } catch(e) { /* surfaced via toast in caller */ }
34471
+ }
34472
+ _agentGoalsPendingOwners = {};
34473
+ }
34474
+
34475
+ // CSS-attribute escape — our category names contain spaces.
34476
+ function cssAttrEsc(s) { return String(s).replace(/"/g, '\\"'); }
34477
+
34073
34478
  var _discordChannelsCache = null;
34074
34479
  async function loadDiscordChannels(selectedValues) {
34075
34480
  // Normalize to array
@@ -34144,16 +34549,39 @@ function showAgentCreateModal() {
34144
34549
  document.getElementById('agent-modal-title').textContent = 'Hire a New Team Member';
34145
34550
  document.getElementById('agent-submit-btn').textContent = 'Complete Hiring';
34146
34551
  document.getElementById('agent-edit-slug').value = '';
34552
+ document.getElementById('agent-edit-mode').value = 'agent';
34147
34553
  document.getElementById('agent-form').reset();
34148
34554
  document.getElementById('agent-token-hint').style.display = 'none';
34149
34555
  document.getElementById('agent-token-setup').style.display = 'none';
34150
34556
  document.getElementById('agent-slack-token-hint').style.display = 'none';
34151
34557
  document.getElementById('agent-slack-setup').style.display = 'none';
34558
+ // Hidden fields are visible for hired agents (Clementine hides them).
34559
+ setClementineMode(false);
34560
+ switchAgentTab('identity');
34152
34561
  loadAgentProjectOptions();
34153
34562
  loadAgentToolOptions();
34154
34563
  loadDiscordChannels([]);
34155
34564
  }
34156
34565
 
34566
+ // Hide / show fields that don't apply to Clementine herself.
34567
+ // Avatar URL, Primary Project select, Security Tier are managed elsewhere
34568
+ // for the main agent — surfacing them in the same modal would be misleading.
34569
+ function setClementineMode(isClementine) {
34570
+ ['agent-identity-row-2', 'agent-project-row', 'agent-tier-row'].forEach(function(id) {
34571
+ var el = document.getElementById(id);
34572
+ if (el) el.style.display = isClementine ? 'none' : '';
34573
+ });
34574
+ // Discord/Slack token sections are also Clementine-managed (env-based);
34575
+ // hide them in the Connections tab when editing the main agent.
34576
+ ['discord-section', 'slack-section'].forEach(function(id) {
34577
+ var el = document.getElementById(id);
34578
+ if (el) el.style.display = isClementine ? 'none' : '';
34579
+ });
34580
+ // Toggle the required attribute on Name — Clementine has a fixed name.
34581
+ var nameEl = document.getElementById('agent-name');
34582
+ if (nameEl) nameEl.readOnly = isClementine;
34583
+ }
34584
+
34157
34585
  async function editAgent(slug) {
34158
34586
  try {
34159
34587
  var r = await apiFetch('/api/agents');
@@ -34165,9 +34593,13 @@ async function editAgent(slug) {
34165
34593
  document.getElementById('agent-modal-title').textContent = 'Update Team Member: ' + a.name;
34166
34594
  document.getElementById('agent-submit-btn').textContent = 'Save';
34167
34595
  document.getElementById('agent-edit-slug').value = slug;
34596
+ document.getElementById('agent-edit-mode').value = 'agent';
34597
+ setClementineMode(false);
34598
+ switchAgentTab('identity');
34168
34599
  document.getElementById('agent-name').value = a.name || '';
34169
34600
  document.getElementById('agent-description').value = a.description || '';
34170
34601
  document.getElementById('agent-avatar-url').value = a.avatar || '';
34602
+ document.getElementById('agent-personality').value = a.systemPromptBody || a.personality || '';
34171
34603
  loadDiscordChannels(a.channelName || []);
34172
34604
  document.getElementById('agent-team-chat').checked = a.teamChat || false;
34173
34605
  document.getElementById('agent-respond-all').checked = a.respondToAll || false;
@@ -34175,7 +34607,12 @@ async function editAgent(slug) {
34175
34607
  document.getElementById('agent-tier').value = String(a.tier || 2);
34176
34608
  document.getElementById('agent-canmessage').value = (a.canMessage || []).join(', ');
34177
34609
  loadAgentProjectOptions(a.project || '');
34178
- loadAgentToolOptions(a.allowedTools || []);
34610
+ // Pre-check both tools and project access — projects are stored as
34611
+ // 'project:/abs/path' inside the same selection set the Equipment panel
34612
+ // tracks, so loadAgentToolOptions's normalizer handles them uniformly.
34613
+ var initialSelection = (a.allowedTools || []).slice();
34614
+ (a.projects || []).forEach(function(p) { initialSelection.push('project:' + p); });
34615
+ loadAgentToolOptions(initialSelection);
34179
34616
  document.getElementById('agent-allowed-users').value = (a.allowedUsers || []).join(', ');
34180
34617
  // Budget
34181
34618
  document.getElementById('agent-budget').value = String(a.budgetMonthlyCents || 0);
@@ -34228,6 +34665,43 @@ function hideAgentModal() {
34228
34665
  document.getElementById('agent-modal').classList.remove('show');
34229
34666
  }
34230
34667
 
34668
+ // Open the same tabbed modal in Clementine mode — reads/writes the
34669
+ // assistant.profile block of clementine.json instead of an agent.md.
34670
+ async function editClementine() {
34671
+ try {
34672
+ var r = await apiFetch('/api/clementine/profile');
34673
+ var d = await r.json();
34674
+ var p = d.profile || {};
34675
+ document.getElementById('agent-modal').classList.add('show');
34676
+ document.getElementById('agent-modal-title').textContent = 'Configure ' + (d.name || 'Clementine');
34677
+ document.getElementById('agent-submit-btn').textContent = 'Save';
34678
+ document.getElementById('agent-edit-slug').value = '__clementine__';
34679
+ document.getElementById('agent-edit-mode').value = 'clementine';
34680
+ setClementineMode(true);
34681
+ switchAgentTab('identity');
34682
+ document.getElementById('agent-name').value = d.name || 'Clementine';
34683
+ document.getElementById('agent-description').value = 'Master delegator and personal assistant';
34684
+ document.getElementById('agent-personality').value = p.systemPrompt || '';
34685
+ document.getElementById('agent-model').value = p.model || '';
34686
+ document.getElementById('agent-canmessage').value = '';
34687
+ document.getElementById('agent-allowed-users').value = (p.allowedUsers || []).join(', ');
34688
+ document.getElementById('agent-budget').value = String(p.budgetMonthlyCents || 0);
34689
+ if (p.sendPolicy) {
34690
+ document.getElementById('agent-send-max-daily').value = String(p.sendPolicy.maxDailyEmails || 50);
34691
+ document.getElementById('agent-send-approval').value = p.sendPolicy.requiresApproval || '';
34692
+ document.getElementById('agent-send-biz-hours').checked = !!p.sendPolicy.businessHoursOnly;
34693
+ } else {
34694
+ document.getElementById('agent-send-max-daily').value = '50';
34695
+ document.getElementById('agent-send-approval').value = '';
34696
+ document.getElementById('agent-send-biz-hours').checked = false;
34697
+ }
34698
+ loadDiscordChannels(p.channels || []);
34699
+ var initial = (p.allowedTools || []).slice();
34700
+ (p.allowedProjects || []).forEach(function(path) { initial.push('project:' + path); });
34701
+ loadAgentToolOptions(initial);
34702
+ } catch(e) { toast('Failed to load Clementine profile: ' + String(e), 'error'); }
34703
+ }
34704
+
34231
34705
  var tokenInputDebounce = null;
34232
34706
  function onTokenInput(token) {
34233
34707
  clearTimeout(tokenInputDebounce);
@@ -34273,12 +34747,64 @@ async function submitAgentForm(e) {
34273
34747
  }
34274
34748
  async function _submitAgentFormInner(e) {
34275
34749
  var editSlug = document.getElementById('agent-edit-slug').value;
34276
- var isEdit = Boolean(editSlug);
34750
+ var modeEl = document.getElementById('agent-edit-mode');
34751
+ var mode = modeEl ? modeEl.value || 'agent' : 'agent';
34752
+ var isClementine = mode === 'clementine';
34753
+ var isEdit = !isClementine && Boolean(editSlug);
34277
34754
 
34278
34755
  var selectedChannels = Array.from(document.querySelectorAll('.agent-channel-cb:checked')).map(function(cb) { return cb.value; });
34279
- var channelName = selectedChannels.length === 0 ? undefined : selectedChannels.length === 1 ? selectedChannels[0] : selectedChannels;
34280
34756
  var teamChat = document.getElementById('agent-team-chat').checked;
34281
34757
  var respondToAll = document.getElementById('agent-respond-all').checked;
34758
+
34759
+ // Equipment selection — split into the agent schema's two buckets:
34760
+ // a flat allowedTools[] and projects[] of absolute paths.
34761
+ var split = splitAgentToolSelection();
34762
+
34763
+ var au = document.getElementById('agent-allowed-users').value.trim();
34764
+ var allowedUsers = au ? au.split(',').map(function(s) { return s.trim(); }).filter(Boolean) : [];
34765
+
34766
+ var sendApproval = document.getElementById('agent-send-approval').value;
34767
+ var sendPolicy = sendApproval ? {
34768
+ maxDailyEmails: parseInt(document.getElementById('agent-send-max-daily').value) || 50,
34769
+ requiresApproval: sendApproval,
34770
+ businessHoursOnly: document.getElementById('agent-send-biz-hours').checked,
34771
+ } : undefined;
34772
+
34773
+ var budgetVal = parseInt(document.getElementById('agent-budget').value) || 0;
34774
+
34775
+ if (isClementine) {
34776
+ // Clementine path — write the assistant.profile block in clementine.json.
34777
+ var profilePayload = {
34778
+ systemPrompt: document.getElementById('agent-personality').value,
34779
+ model: document.getElementById('agent-model').value || undefined,
34780
+ allowedTools: split.tools,
34781
+ allowedProjects: split.projects,
34782
+ allowedUsers: allowedUsers,
34783
+ channels: selectedChannels,
34784
+ budgetMonthlyCents: budgetVal,
34785
+ sendPolicy: sendPolicy,
34786
+ };
34787
+ try {
34788
+ var rc = await apiFetch('/api/clementine/profile', {
34789
+ method: 'PUT',
34790
+ headers: { 'Content-Type': 'application/json' },
34791
+ body: JSON.stringify(profilePayload),
34792
+ });
34793
+ var dc = await rc.json();
34794
+ if (dc.ok) {
34795
+ await saveAgentGoalsPanel();
34796
+ toast('Saved Clementine profile', 'success');
34797
+ hideAgentModal();
34798
+ refreshTeam();
34799
+ } else {
34800
+ toast(dc.error || 'Failed', 'error');
34801
+ }
34802
+ } catch(err) { toast(String(err), 'error'); }
34803
+ return;
34804
+ }
34805
+
34806
+ // Hired-agent path — same shape as before, plus projects[] from Equipment.
34807
+ var channelName = selectedChannels.length === 0 ? undefined : selectedChannels.length === 1 ? selectedChannels[0] : selectedChannels;
34282
34808
  var payload = {
34283
34809
  name: document.getElementById('agent-name').value.trim(),
34284
34810
  description: document.getElementById('agent-description').value.trim(),
@@ -34288,6 +34814,7 @@ async function _submitAgentFormInner(e) {
34288
34814
  respondToAll: channelName ? respondToAll : undefined,
34289
34815
  model: document.getElementById('agent-model').value || undefined,
34290
34816
  project: document.getElementById('agent-project').value || undefined,
34817
+ projects: split.projects,
34291
34818
  tier: parseInt(document.getElementById('agent-tier').value) || 2,
34292
34819
  avatar: document.getElementById('agent-avatar-url').value.trim() || undefined,
34293
34820
  };
@@ -34295,11 +34822,8 @@ async function _submitAgentFormInner(e) {
34295
34822
  var cm = document.getElementById('agent-canmessage').value.trim();
34296
34823
  if (cm) payload.canMessage = cm.split(',').map(function(s) { return s.trim(); }).filter(Boolean);
34297
34824
 
34298
- var tools = getSelectedTools();
34299
- if (tools.length) payload.allowedTools = tools;
34300
-
34301
- var au = document.getElementById('agent-allowed-users').value.trim();
34302
- payload.allowedUsers = au ? au.split(',').map(function(s) { return s.trim(); }).filter(Boolean) : [];
34825
+ if (split.tools.length) payload.allowedTools = split.tools;
34826
+ payload.allowedUsers = allowedUsers;
34303
34827
 
34304
34828
  var discordToken = document.getElementById('agent-discord-token').value.trim();
34305
34829
  if (discordToken) payload.discordToken = discordToken;
@@ -34316,18 +34840,7 @@ async function _submitAgentFormInner(e) {
34316
34840
  var slackChannelId = document.getElementById('agent-slack-channel-id').value.trim();
34317
34841
  if (slackChannelId) payload.slackChannelId = slackChannelId;
34318
34842
 
34319
- // Send policy
34320
- var sendApproval = document.getElementById('agent-send-approval').value;
34321
- if (sendApproval) {
34322
- payload.sendPolicy = {
34323
- maxDailyEmails: parseInt(document.getElementById('agent-send-max-daily').value) || 50,
34324
- requiresApproval: sendApproval,
34325
- businessHoursOnly: document.getElementById('agent-send-biz-hours').checked,
34326
- };
34327
- }
34328
-
34329
- // Budget
34330
- var budgetVal = parseInt(document.getElementById('agent-budget').value) || 0;
34843
+ if (sendPolicy) payload.sendPolicy = sendPolicy;
34331
34844
  if (budgetVal > 0) payload.budgetMonthlyCents = budgetVal;
34332
34845
 
34333
34846
  // Role template — triggers scaffolding on the backend
@@ -34346,13 +34859,16 @@ async function _submitAgentFormInner(e) {
34346
34859
  });
34347
34860
  var d = await r.json();
34348
34861
  if (d.ok) {
34862
+ // Apply pending goal-owner changes after the agent is persisted —
34863
+ // matters most on Create, when the slug only just became real.
34864
+ await saveAgentGoalsPanel();
34349
34865
  toast((isEdit ? 'Updated' : 'Created') + ' agent: ' + (d.agent?.name || payload.name), 'success');
34350
34866
  hideAgentModal();
34351
34867
  refreshTeam();
34352
34868
  } else {
34353
34869
  toast(d.error || 'Failed', 'error');
34354
34870
  }
34355
- } catch(e) { toast(String(e), 'error'); }
34871
+ } catch(err) { toast(String(err), 'error'); }
34356
34872
  }
34357
34873
 
34358
34874
  function confirmDeleteAgent(slug, name) { deleteAgent(slug); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.97",
3
+ "version": "1.18.99",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",