clementine-agent 1.18.74 → 1.18.75

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 +215 -29
  2. package/package.json +1 -1
@@ -4267,6 +4267,10 @@ export async function cmdDashboard(opts) {
4267
4267
  const agentSlug = body.agent ? (String(body.agent).trim() || undefined) : undefined;
4268
4268
  // Parse the agent's draft if present; on any parse error fall back to the
4269
4269
  // single-step stub so the user lands in the editor with something usable.
4270
+ // ── KEY FIX (1.18.75): forward EVERY step-kind body the agent produces.
4271
+ // Previously this parser dropped mcp/cli/channel/transform/conditional/loop
4272
+ // configs, leaving the user with prompt-only stubs and forcing them to
4273
+ // rebuild every Slack-send / shell-cmd / branch by hand.
4270
4274
  let steps = null;
4271
4275
  if (body.draftYaml && typeof body.draftYaml === 'string') {
4272
4276
  try {
@@ -4277,16 +4281,39 @@ export async function cmdDashboard(opts) {
4277
4281
  const dependsOn = Array.isArray(r.dependsOn)
4278
4282
  ? r.dependsOn.map(String)
4279
4283
  : (typeof r.dependsOn === 'string' ? r.dependsOn.split(',').map(s => s.trim()).filter(Boolean) : []);
4280
- return {
4284
+ const kind = typeof r.kind === 'string' ? r.kind : 'prompt';
4285
+ const step = {
4281
4286
  id: String(id),
4282
- prompt: String(r.prompt ?? ''),
4283
4287
  dependsOn,
4284
4288
  tier: typeof r.tier === 'number' ? r.tier : 1,
4285
4289
  maxTurns: typeof r.maxTurns === 'number' ? r.maxTurns : 15,
4286
- ...(typeof r.model === 'string' ? { model: r.model } : {}),
4287
- ...(typeof r.workDir === 'string' ? { workDir: r.workDir } : {}),
4288
- ...(typeof r.kind === 'string' && r.kind !== 'prompt' ? { kind: r.kind } : {}),
4289
4290
  };
4291
+ if (kind !== 'prompt')
4292
+ step.kind = kind;
4293
+ if (typeof r.model === 'string')
4294
+ step.model = r.model;
4295
+ if (typeof r.workDir === 'string')
4296
+ step.workDir = r.workDir;
4297
+ // Prompt body — present on prompt steps and optionally on others.
4298
+ if (r.prompt != null)
4299
+ step.prompt = String(r.prompt);
4300
+ else if (kind === 'prompt')
4301
+ step.prompt = '';
4302
+ // Non-prompt step bodies. We forward them as-is when shaped like
4303
+ // an object; the validator on save will catch missing required
4304
+ // sub-fields and surface a structured error to the chat UI.
4305
+ const passThrough = (key) => {
4306
+ const v = r[key];
4307
+ if (v && typeof v === 'object' && !Array.isArray(v))
4308
+ step[key] = v;
4309
+ };
4310
+ passThrough('mcp');
4311
+ passThrough('cli');
4312
+ passThrough('channel');
4313
+ passThrough('transform');
4314
+ passThrough('conditional');
4315
+ passThrough('loop');
4316
+ return step;
4290
4317
  }).filter(s => s.id);
4291
4318
  if (steps.length === 0)
4292
4319
  steps = null;
@@ -6576,7 +6603,52 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
6576
6603
  }
6577
6604
  jobs.splice(idx, 1);
6578
6605
  writeCronFileAt(cronFile, parsed, jobs);
6579
- res.json({ ok: true, message: `Deleted cron job: ${jobName}` });
6606
+ // Cascade cleanup unless explicitly opted out (?purge=false). Storing the
6607
+ // job name's safe filename form once — same sanitization the run-log and
6608
+ // trace writers use, so we hit the right files.
6609
+ const purge = String(req.query.purge ?? 'true') !== 'false';
6610
+ const purged = [];
6611
+ if (purge) {
6612
+ const safe = bareJobName.replace(/[^a-zA-Z0-9_-]/g, '_');
6613
+ const runLog = path.join(BASE_DIR, 'cron', 'runs', `${safe}.jsonl`);
6614
+ try {
6615
+ if (existsSync(runLog)) {
6616
+ unlinkSync(runLog);
6617
+ purged.push('runs.jsonl');
6618
+ }
6619
+ }
6620
+ catch { /* non-fatal */ }
6621
+ try {
6622
+ const traceDir = path.join(BASE_DIR, 'cron', 'traces');
6623
+ if (existsSync(traceDir)) {
6624
+ const traceFiles = readdirSync(traceDir).filter(f => f.startsWith(`${safe}_`) && f.endsWith('.json'));
6625
+ for (const f of traceFiles) {
6626
+ try {
6627
+ unlinkSync(path.join(traceDir, f));
6628
+ }
6629
+ catch { /* skip */ }
6630
+ }
6631
+ if (traceFiles.length > 0)
6632
+ purged.push(`${traceFiles.length} trace${traceFiles.length === 1 ? '' : 's'}`);
6633
+ }
6634
+ }
6635
+ catch { /* non-fatal */ }
6636
+ try {
6637
+ const uploadsDir = path.join(BASE_DIR, 'uploads', `cron-${safe}`);
6638
+ if (existsSync(uploadsDir)) {
6639
+ rmSync(uploadsDir, { recursive: true, force: true });
6640
+ purged.push('attachments');
6641
+ }
6642
+ }
6643
+ catch { /* non-fatal */ }
6644
+ }
6645
+ // Notify other dashboard tabs so they drop the card without polling.
6646
+ try {
6647
+ broadcastEvent({ type: 'cron_deleted', data: { job: bareJobName, purged } });
6648
+ }
6649
+ catch { /* non-fatal */ }
6650
+ const purgeNote = purged.length > 0 ? ` (purged: ${purged.join(', ')})` : '';
6651
+ res.json({ ok: true, message: `Deleted cron job: ${jobName}${purgeNote}`, purged });
6580
6652
  }
6581
6653
  catch (err) {
6582
6654
  res.status(500).json({ error: String(err) });
@@ -16422,7 +16494,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
16422
16494
  apiFetch('/api/routines/' + encodeURIComponent(id) + '/toggle', { method: 'POST' })
16423
16495
  .then(function(r){ return r.json(); })
16424
16496
  .then(function(){ R.refreshList(); })
16425
- .catch(function(err){ alert('Toggle failed: ' + err); });
16497
+ .catch(function(err){ toast('Toggle failed: ' + err, 'error'); });
16426
16498
  },
16427
16499
  run: function(id, approvedSideEffects) {
16428
16500
  apiFetch('/api/routines/' + encodeURIComponent(id) + '/run', {
@@ -16438,19 +16510,19 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
16438
16510
  }
16439
16511
  return r.json().then(function(j){
16440
16512
  if (j.ok) R.flash('Triggered.');
16441
- else alert('Run failed: ' + (j.error || 'unknown'));
16513
+ else toast('Run failed: ' + (j.error || 'unknown'), 'error');
16442
16514
  });
16443
- }).catch(function(err){ alert('Run failed: ' + err); });
16515
+ }).catch(function(err){ toast('Run failed: ' + err, 'error'); });
16444
16516
  },
16445
16517
  // ── editor ──────────────────────────────────────────────────
16446
16518
  openEditor: function(id) {
16447
16519
  apiFetch('/api/routines/' + encodeURIComponent(id))
16448
16520
  .then(function(r){ return r.json(); })
16449
16521
  .then(function(data){
16450
- if (!data || !data.routine) { alert('Failed to load workflow'); return; }
16522
+ if (!data || !data.routine) { toast('Failed to load workflow', 'error'); return; }
16451
16523
  R.state.editing = { id: data.id, routine: data.routine, dirty: false, validation: data.validation };
16452
16524
  R.showEditor();
16453
- }).catch(function(err){ alert('Open failed: ' + err); });
16525
+ }).catch(function(err){ toast('Open failed: ' + err, 'error'); });
16454
16526
  },
16455
16527
  showEditor: function() {
16456
16528
  document.getElementById('routines-list-pane').style.display = 'none';
@@ -16702,7 +16774,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
16702
16774
  },
16703
16775
  removeStep: function(idx) {
16704
16776
  if (!R.state.editing) return;
16705
- if (R.state.editing.routine.steps.length <= 1) { alert('A workflow must have at least one step.'); return; }
16777
+ if (R.state.editing.routine.steps.length <= 1) { toast('A workflow must have at least one step.', 'error'); return; }
16706
16778
  if (!confirm('Remove this step?')) return;
16707
16779
  var removed = R.state.editing.routine.steps.splice(idx, 1)[0];
16708
16780
  // Strip lingering dependsOn references.
@@ -16803,7 +16875,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
16803
16875
  (d.steps || []).forEach(function(s){ lines.push('• ' + s.description + (s.warnings.length ? '\\n ⚠ ' + s.warnings.join('; ') : '')); });
16804
16876
  if (d.notes && d.notes.length) lines.push('\\n' + d.notes.join('\\n'));
16805
16877
  alert(lines.join('\\n'));
16806
- }).catch(function(err){ alert('Dry-run failed: ' + err); });
16878
+ }).catch(function(err){ toast('Dry-run failed: ' + err, 'error'); });
16807
16879
  },
16808
16880
  testCurrent: function() {
16809
16881
  if (!R.state.editing) return;
@@ -16822,9 +16894,9 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
16822
16894
  apiFetch('/api/routines/' + encodeURIComponent(R.state.editing.id), { method: 'DELETE' })
16823
16895
  .then(function(r){ return r.json(); })
16824
16896
  .then(function(j){
16825
- if (j.ok) { R.state.editing = null; R.closeEditor(); R.refreshList(); }
16826
- else alert('Delete failed: ' + (j.error || 'unknown'));
16827
- }).catch(function(err){ alert('Delete error: ' + err); });
16897
+ if (j.ok) { R.state.editing = null; R.closeEditor(); R.refreshList(); toast('Deleted.', 'success'); }
16898
+ else toast('Delete failed: ' + (j.error || 'unknown'), 'error');
16899
+ }).catch(function(err){ toast('Delete error: ' + err, 'error'); });
16828
16900
  },
16829
16901
  setStatus: function(msg) {
16830
16902
  var el = document.getElementById('re-status');
@@ -16879,7 +16951,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
16879
16951
  },
16880
16952
  submitCreate: function() {
16881
16953
  var name = document.getElementById('routines-create-name').value.trim();
16882
- if (!name) { alert('Name is required'); return; }
16954
+ if (!name) { toast('Name is required', 'error'); document.getElementById('routines-create-name').focus(); return; }
16883
16955
  var body = {
16884
16956
  name: name,
16885
16957
  description: document.getElementById('routines-create-description').value.trim(),
@@ -16892,11 +16964,12 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
16892
16964
  body: JSON.stringify(body)
16893
16965
  }).then(function(r){ return r.json().then(function(j){ return { ok: r.ok, body: j }; }); })
16894
16966
  .then(function(res){
16895
- if (!res.ok) { alert('Create failed: ' + (res.body.error || 'unknown')); return; }
16967
+ if (!res.ok) { toast('Create failed: ' + (res.body.error || 'unknown'), 'error'); return; }
16896
16968
  R.closeCreate();
16897
16969
  R.refreshList();
16898
16970
  R.openEditor(res.body.id);
16899
- }).catch(function(err){ alert('Create error: ' + err); });
16971
+ toast('Workflow created.', 'success');
16972
+ }).catch(function(err){ toast('Create error: ' + err, 'error'); });
16900
16973
  },
16901
16974
  // ── chat-first builder ──────────────────────────────────────
16902
16975
  // Multi-turn conversation that asks clarifying questions and
@@ -17153,14 +17226,15 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
17153
17226
  .then(function(res){
17154
17227
  if (btn) { btn.textContent = 'Save workflow'; btn.disabled = false; }
17155
17228
  if (!res.ok) {
17156
- alert('Save failed: ' + (res.body && res.body.error || 'unknown'));
17229
+ toast('Save failed: ' + (res.body && res.body.error || 'unknown'), 'error');
17157
17230
  return;
17158
17231
  }
17232
+ toast('Workflow saved.', 'success');
17159
17233
  R.closeChat();
17160
17234
  if (res.body && res.body.id) R.openEditor(res.body.id);
17161
17235
  }).catch(function(err){
17162
17236
  if (btn) { btn.textContent = 'Save workflow'; btn.disabled = false; }
17163
- alert('Save failed: ' + err);
17237
+ toast('Save failed: ' + err, 'error');
17164
17238
  });
17165
17239
  },
17166
17240
  // ── helpers ─────────────────────────────────────────────────
@@ -19950,7 +20024,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
19950
20024
  <div class="cap-section">
19951
20025
  <label class="cap-section-label">Tags</label>
19952
20026
  <div class="cap-picker-chips" id="cron-tags-chips"></div>
19953
- <input type="text" class="cap-tag-input" id="cron-tags-input" placeholder="Type a tag and press Enter (e.g. morning, ops)" onkeydown="handleTagInputKeydown(event)">
20027
+ <input type="text" class="cap-tag-input" id="cron-tags-input" placeholder="Type a tag and press Enter, Tab, or comma (e.g. morning, ops)" onkeydown="handleTagInputKeydown(event)" onblur="handleTagInputBlur()">
19954
20028
  </div>
19955
20029
  </div>
19956
20030
 
@@ -24472,8 +24546,24 @@ function toggleAllowedToolsPanel() {
24472
24546
  }
24473
24547
 
24474
24548
  function handleTagInputKeydown(event) {
24475
- if (event.key !== 'Enter' && event.key !== ',') return;
24549
+ // Accept Enter, Tab, and comma as commit keys. Tab still moves focus
24550
+ // afterward when the field is empty (no preventDefault). On-blur
24551
+ // commit is handled by handleTagInputBlur below.
24552
+ if (event.key !== 'Enter' && event.key !== ',' && event.key !== 'Tab') return;
24553
+ var inp = document.getElementById('cron-tags-input');
24554
+ if (!inp) return;
24555
+ var val = inp.value.trim().replace(/^#+/, '');
24556
+ if (!val) return; // Tab on empty input → let it move focus naturally
24476
24557
  event.preventDefault();
24558
+ if (_cronTags.indexOf(val) === -1) {
24559
+ _cronTags.push(val);
24560
+ renderTagsPickerChips();
24561
+ }
24562
+ inp.value = '';
24563
+ }
24564
+
24565
+ function handleTagInputBlur() {
24566
+ // Catch tags the user typed but forgot to commit before clicking elsewhere.
24477
24567
  var inp = document.getElementById('cron-tags-input');
24478
24568
  if (!inp) return;
24479
24569
  var val = inp.value.trim().replace(/^#+/, '');
@@ -24669,6 +24759,9 @@ function openCreateCronModal(agentSlug) {
24669
24759
  switchCronTab('configure');
24670
24760
  onPredictableChange();
24671
24761
  document.getElementById('cron-modal').classList.add('show');
24762
+ // Snapshot AFTER all defaults are populated so an immediate close with no
24763
+ // edits doesn't trigger the "discard changes?" prompt.
24764
+ setTimeout(captureCronModalSnapshot, 0);
24672
24765
  }
24673
24766
 
24674
24767
  function openEditCronModal(jobName) {
@@ -24730,6 +24823,7 @@ function openEditCronModal(jobName) {
24730
24823
  if (previewBtn) previewBtn.removeAttribute('disabled');
24731
24824
  switchCronTab('configure');
24732
24825
  document.getElementById('cron-modal').classList.add('show');
24826
+ setTimeout(captureCronModalSnapshot, 0);
24733
24827
  }
24734
24828
 
24735
24829
  /**
@@ -24864,10 +24958,68 @@ function renderCronPreview(d) {
24864
24958
  return html;
24865
24959
  }
24866
24960
 
24867
- function closeCronModal() {
24961
+ // Snapshot of the form values at the moment the modal opened. closeCronModal
24962
+ // compares against this and prompts the user before discarding edits.
24963
+ var _cronModalSnapshot = null;
24964
+
24965
+ function captureCronModalSnapshot() {
24966
+ // String concatenation is enough for a dirty check; we don't need the
24967
+ // structured object back. Order must match restoreCheck below.
24968
+ function v(id) { var el = document.getElementById(id); return el ? (el.value || '') : ''; }
24969
+ _cronModalSnapshot = [
24970
+ v('cron-name'),
24971
+ v('cron-schedule'),
24972
+ v('cron-prompt'),
24973
+ v('cron-context'),
24974
+ v('cron-tier'),
24975
+ v('cron-mode'),
24976
+ v('cron-maxhours'),
24977
+ v('cron-max-retries'),
24978
+ v('cron-after'),
24979
+ v('cron-workdir'),
24980
+ v('cron-allowed-tools'),
24981
+ v('cron-category'),
24982
+ (document.getElementById('cron-predictable') || {}).checked ? '1' : '0',
24983
+ JSON.stringify(_cronSelectedSkills || []),
24984
+ JSON.stringify(_cronSelectedMcp || []),
24985
+ JSON.stringify(_cronTags || []),
24986
+ String((_pendingAttachments || []).length),
24987
+ ].join('\\u0001');
24988
+ }
24989
+
24990
+ function isCronModalDirty() {
24991
+ if (_cronModalSnapshot === null) return false;
24992
+ function v(id) { var el = document.getElementById(id); return el ? (el.value || '') : ''; }
24993
+ var current = [
24994
+ v('cron-name'),
24995
+ v('cron-schedule'),
24996
+ v('cron-prompt'),
24997
+ v('cron-context'),
24998
+ v('cron-tier'),
24999
+ v('cron-mode'),
25000
+ v('cron-maxhours'),
25001
+ v('cron-max-retries'),
25002
+ v('cron-after'),
25003
+ v('cron-workdir'),
25004
+ v('cron-allowed-tools'),
25005
+ v('cron-category'),
25006
+ (document.getElementById('cron-predictable') || {}).checked ? '1' : '0',
25007
+ JSON.stringify(_cronSelectedSkills || []),
25008
+ JSON.stringify(_cronSelectedMcp || []),
25009
+ JSON.stringify(_cronTags || []),
25010
+ String((_pendingAttachments || []).length),
25011
+ ].join('\\u0001');
25012
+ return current !== _cronModalSnapshot;
25013
+ }
25014
+
25015
+ function closeCronModal(force) {
25016
+ if (force !== true && isCronModalDirty()) {
25017
+ if (!confirm('You have unsaved changes. Discard them?')) return;
25018
+ }
24868
25019
  document.getElementById('cron-modal').classList.remove('show');
24869
25020
  editingCronJob = null;
24870
25021
  _cronPreviewLoadedFor = null;
25022
+ _cronModalSnapshot = null;
24871
25023
  var attachList = document.getElementById('cron-attachments-list');
24872
25024
  if (attachList) attachList.innerHTML = '';
24873
25025
  var bannerHost = document.getElementById('cron-legacy-banner-host');
@@ -25001,10 +25153,29 @@ async function saveCronJob() {
25001
25153
  const allowedTools = parseAllowedToolsRaw();
25002
25154
  const predictable = !!document.getElementById('cron-predictable')?.checked;
25003
25155
 
25004
- if (!name || !schedule || !prompt) {
25005
- toast('Please fill in all fields', 'error');
25156
+ // Field-specific validation. Toasting one message per problem is more
25157
+ // actionable than the old "Please fill in all fields" — and we bail before
25158
+ // hitting the API so the user sees the issue without a round-trip.
25159
+ if (!name) { toast('Task name is required', 'error'); document.getElementById('cron-name').focus(); return; }
25160
+ if (!/^[a-z][a-z0-9-]{0,63}$/.test(name)) {
25161
+ toast('Task name must start with a lowercase letter and contain only a–z, 0–9, and hyphens (max 64 chars)', 'error');
25162
+ document.getElementById('cron-name').focus();
25006
25163
  return;
25007
25164
  }
25165
+ if (!editingCronJob && Array.isArray(cronJobsData)) {
25166
+ var dup = cronJobsData.find(function(j) { return String(j.name || '').toLowerCase() === name.toLowerCase(); });
25167
+ if (dup) { toast('A task named "' + name + '" already exists', 'error'); document.getElementById('cron-name').focus(); return; }
25168
+ }
25169
+ if (!schedule) { toast('Schedule is required', 'error'); return; }
25170
+ // Light client-side cron sanity check — server uses real cron-parser.
25171
+ // Catches obvious garbage like "foo bar" without making a round-trip.
25172
+ // Note: the hyphen sits at the END of the character class to avoid being
25173
+ // interpreted as a range. \\\\s in source → \\s in served JS → \s in regex.
25174
+ if (!/^([0-9*/, -]+|@(yearly|annually|monthly|weekly|daily|hourly|reboot))$/i.test(schedule) && schedule.split(/\\s+/).length < 5) {
25175
+ toast('Schedule does not look like a valid cron expression', 'error');
25176
+ return;
25177
+ }
25178
+ if (!prompt) { toast('Prompt is required — tell the agent what to do', 'error'); document.getElementById('cron-prompt').focus(); return; }
25008
25179
 
25009
25180
  const body = {
25010
25181
  name, schedule, tier, prompt, enabled: true,
@@ -25026,12 +25197,18 @@ async function saveCronJob() {
25026
25197
  };
25027
25198
 
25028
25199
  var wasEditing = !!editingCronJob;
25200
+ // Don't celebrate before the round-trip lands. apiJson toasts on its own;
25201
+ // we just check the {ok} flag and bail so the modal doesn't close (and
25202
+ // erase the user's input) on a 400/409/500.
25203
+ var resp;
25029
25204
  if (editingCronJob) {
25030
- await apiJson('PUT', '/api/cron/' + encodeURIComponent(editingCronJob), body);
25205
+ resp = await apiJson('PUT', '/api/cron/' + encodeURIComponent(editingCronJob), body);
25206
+ if (!resp || resp.ok !== true) return;
25031
25207
  if (_pendingAttachments.length > 0) await uploadPendingAttachments(editingCronJob);
25032
25208
  } else {
25033
25209
  var createBody = _cronAgentContext ? Object.assign({}, body, { agent: _cronAgentContext }) : body;
25034
- await apiJson('POST', '/api/cron', createBody);
25210
+ resp = await apiJson('POST', '/api/cron', createBody);
25211
+ if (!resp || resp.ok !== true) return;
25035
25212
  var attachJobName = _cronAgentContext ? (_cronAgentContext + ':' + name) : name;
25036
25213
  if (_pendingAttachments.length > 0) await uploadPendingAttachments(attachJobName);
25037
25214
  }
@@ -25041,11 +25218,14 @@ async function saveCronJob() {
25041
25218
  // confirm what they just saved actually runs the way they intended.
25042
25219
  // This is the close-the-loop UX move that makes Predictable Mode visible.
25043
25220
  markCronPreviewDirty();
25221
+ // Re-snapshot now that the form matches what's on disk — prevents the
25222
+ // dirty-guard from firing if the user closes the modal without further edits.
25223
+ captureCronModalSnapshot();
25044
25224
  if (wasEditing) {
25045
25225
  toast('Saved. Showing what will run…', 'success');
25046
25226
  switchCronTab('preview');
25047
25227
  } else {
25048
- closeCronModal();
25228
+ closeCronModal(true); // force; we just saved, no dirty prompt
25049
25229
  }
25050
25230
  }
25051
25231
 
@@ -34500,6 +34680,12 @@ try {
34500
34680
  if (currentPage === 'build') refreshCron();
34501
34681
  refreshTeamNav();
34502
34682
  }
34683
+ // A delete on one tab should drop the card from every open dashboard
34684
+ // without waiting for the next poll. cron_toggled is similar but lighter.
34685
+ if (evt.type === 'cron_deleted' || evt.type === 'cron_toggled') {
34686
+ if (currentPage === 'build') refreshCron();
34687
+ refreshTeamNav();
34688
+ }
34503
34689
  if (evt.type === 'agent_created' || evt.type === 'agent_updated' || evt.type === 'agent_deleted' || evt.type === 'agent_status') {
34504
34690
  refreshTeamNav();
34505
34691
  refreshActivity();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.74",
3
+ "version": "1.18.75",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",