clementine-agent 1.18.171 → 1.18.172

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.
@@ -58,10 +58,21 @@ import pino from 'pino';
58
58
  import { searchSkills } from './skill-extractor.js';
59
59
  const logger = pino({ name: 'clementine.chat-skill-resolver' });
60
60
  // ── Tunables ──────────────────────────────────────────────────────────
61
- /** Default minimum score to consider a skill match real. Mirrors the
62
- * legacy `assistant.ts:1492` threshold. Skill auto-match is heuristic;
63
- * this filter keeps weak matches from injecting unrelated tooling. */
61
+ /** Default minimum score for user-authored skill matches. Mirrors the
62
+ * legacy `assistant.ts:1492` threshold. */
64
63
  const DEFAULT_MIN_SCORE = 4;
64
+ /** Higher threshold applied when ALL matches are auto-generated MCP-derived
65
+ * skills (no user-authored signal). 1.18.171 hotfix: a vague chat message
66
+ * ("did our changes break it?") matched three unrelated auto-skills
67
+ * (ElevenLabs + apify) at score 5.5 each because semantic-only matching
68
+ * drifted toward whatever embeddings were closest. Bumping the bar for
69
+ * auto-only match-sets keeps that noise out of the system prompt. */
70
+ const AUTO_ONLY_MIN_SCORE = 8;
71
+ /** When ALL matches are auto-generated AND they reference this many or
72
+ * more distinct servers, the cluster is treated as semantic-noise and
73
+ * the injection is skipped entirely. Three different services have no
74
+ * business being "all relevant" to a single user message. */
75
+ const AUTO_ONLY_SERVER_NOISE_THRESHOLD = 3;
65
76
  /** Default top-K matches to aggregate. Single-tool requests usually
66
77
  * return one strong match; category requests ("salesforce") return
67
78
  * several similarly-scored auto-skills. Top-3 covers both. Raising
@@ -228,9 +239,36 @@ export function resolveSkillsForChat(userMessage, opts = {}) {
228
239
  logger.debug({ err }, 'chat-skill-resolver: searchSkills failed (non-fatal)');
229
240
  return empty;
230
241
  }
231
- const matches = candidates
232
- .filter((m) => m.score >= minScore)
242
+ // 1.18.171 hotfix: detect auto-only match-sets and apply the higher
243
+ // threshold + noise-cluster filter so vague chat messages don't surface
244
+ // unrelated MCP context. See the comment block on AUTO_ONLY_MIN_SCORE.
245
+ const isAutoMatch = (m) => m.name.startsWith('auto-');
246
+ const candidatesAllAuto = candidates.length > 0 && candidates.every(isAutoMatch);
247
+ const effectiveMinScore = candidatesAllAuto
248
+ ? Math.max(minScore, AUTO_ONLY_MIN_SCORE)
249
+ : minScore;
250
+ let matches = candidates
251
+ .filter((m) => m.score >= effectiveMinScore)
233
252
  .slice(0, limit);
253
+ // Auto-only noise cluster filter: when every survivor is auto AND they
254
+ // collectively reference too many distinct servers (no semantic
255
+ // clustering on a single service), treat as drift and drop.
256
+ if (matches.length >= 2 && matches.every(isAutoMatch)) {
257
+ const seenServers = new Set();
258
+ for (const m of matches) {
259
+ for (const s of extractMcpServersFromMatch(m))
260
+ seenServers.add(s);
261
+ }
262
+ if (seenServers.size >= AUTO_ONLY_SERVER_NOISE_THRESHOLD) {
263
+ logger.info({
264
+ droppedMatches: matches.map(m => ({ name: m.name, score: Number(m.score.toFixed(2)) })),
265
+ distinctServers: [...seenServers],
266
+ reason: 'auto_only_server_cluster_too_wide',
267
+ queryChars,
268
+ }, 'chat-skill-resolver: dropped match-set (semantic noise)');
269
+ matches = [];
270
+ }
271
+ }
234
272
  if (matches.length === 0) {
235
273
  return {
236
274
  ...empty,
@@ -21638,25 +21638,34 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
21638
21638
  </div>
21639
21639
  </div>
21640
21640
  <div style="display:flex;align-items:center;gap:8px;flex-shrink:0">
21641
- <button class="btn-secondary" onclick="openSkillStudio()" style="font-size:13px;padding:8px 14px;border-radius:6px;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text-primary);font-weight:500;cursor:pointer;display:inline-flex;align-items:center;gap:6px" title="Open the conversational Skill Studio">
21641
+ <button class="btn-secondary" onclick="openSkillStudio()" style="font-size:13px;padding:8px 14px;border-radius:6px;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text-primary);font-weight:500;cursor:pointer;display:inline-flex;align-items:center;gap:6px" title="Open the natural-language Skill Studio">
21642
21642
  Open Studio
21643
21643
  </button>
21644
- <button class="btn-primary" onclick="openCreateSkillModalFromComposer()" style="font-size:13px;padding:8px 14px;border-radius:6px;border:none;background:var(--accent);color:#fff;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;gap:6px" title="Open a clean blank skill editor">
21645
- Blank skill
21644
+ <button class="btn-primary" onclick="openCreateSkillModalFromComposer()" style="font-size:13px;padding:8px 14px;border-radius:6px;border:none;background:var(--accent);color:#fff;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;gap:6px" title="Open a blank SKILL.md editor">
21645
+ Manual editor
21646
21646
  </button>
21647
21647
  </div>
21648
21648
  </div>
21649
+ <div id="skill-composer-home">
21649
21650
  <div id="skill-composer" style="margin:0 0 16px;border:1px solid var(--border);border-radius:8px;background:var(--bg-secondary);padding:14px 16px">
21650
- <div style="display:grid;grid-template-columns:minmax(280px,1.2fr) minmax(260px,0.8fr);gap:14px;align-items:start">
21651
+ <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:14px;margin-bottom:12px">
21652
+ <div style="min-width:0">
21653
+ <div style="font-size:14px;font-weight:600;color:var(--text-primary);margin-bottom:3px">Skill Studio</div>
21654
+ <div style="font-size:12px;color:var(--text-muted);line-height:1.45">Describe reusable work in plain language. Optional starting points only seed the draft; nothing runs until you save or test it.</div>
21655
+ </div>
21656
+ <div style="font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.04em;white-space:nowrap;margin-top:2px">Natural language first</div>
21657
+ </div>
21658
+ <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:14px;align-items:start">
21651
21659
  <div>
21652
- <label for="skill-composer-text" style="display:block;font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:6px">Describe the skill</label>
21653
- <textarea id="skill-composer-text" rows="4" oninput="updateSkillComposerDraftState()" placeholder="Review my Asana tasks, update a Google Sheet, verify four source systems, then report back in Asana." style="width:100%;box-sizing:border-box;padding:10px 12px;border:1px solid var(--border);border-radius:6px;background:var(--bg-primary);color:var(--text-primary);font-size:13px;line-height:1.45;resize:vertical;min-height:92px"></textarea>
21660
+ <label for="skill-composer-text" style="display:block;font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:6px">What should Clementine learn?</label>
21661
+ <textarea id="skill-composer-text" rows="5" oninput="updateSkillComposerDraftState()" placeholder="Find Salesforce contacts I have not touched in 15 days, enrich the accounts with DataForSEO signals, draft cold prospecting emails, then report the drafts back for review." style="width:100%;box-sizing:border-box;padding:10px 12px;border:1px solid var(--border);border-radius:6px;background:var(--bg-primary);color:var(--text-primary);font-size:13px;line-height:1.45;resize:vertical;min-height:112px"></textarea>
21662
+ <div style="margin-top:8px;font-size:11px;color:var(--text-muted);line-height:1.45">Good skills name the repeatable outcome, required tools or data, approval boundaries, and what counts as done.</div>
21654
21663
  </div>
21655
21664
  <div>
21656
- <div style="font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:6px">Starting point</div>
21665
+ <div style="font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:6px">Optional starting points</div>
21657
21666
  <div id="skill-composer-modes" style="display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:4px;margin-bottom:10px">
21658
21667
  <button type="button" class="skill-composer-mode" data-kind="outcome" onclick="setSkillComposerMode('outcome')" style="padding:7px 6px;border:1px solid var(--accent);border-radius:6px;background:rgba(255,141,0,0.10);color:var(--accent);font-size:11px;font-weight:600;cursor:pointer">Outcome</button>
21659
- <button type="button" class="skill-composer-mode" data-kind="tool" onclick="setSkillComposerMode('tool')" style="padding:7px 6px;border:1px solid var(--border);border-radius:6px;background:var(--bg-primary);color:var(--text-secondary);font-size:11px;font-weight:600;cursor:pointer">Tool/MCP</button>
21668
+ <button type="button" class="skill-composer-mode" data-kind="tool" onclick="setSkillComposerMode('tool')" style="padding:7px 6px;border:1px solid var(--border);border-radius:6px;background:var(--bg-primary);color:var(--text-secondary);font-size:11px;font-weight:600;cursor:pointer">MCP/API</button>
21660
21669
  <button type="button" class="skill-composer-mode" data-kind="cli" onclick="setSkillComposerMode('cli')" style="padding:7px 6px;border:1px solid var(--border);border-radius:6px;background:var(--bg-primary);color:var(--text-secondary);font-size:11px;font-weight:600;cursor:pointer">CLI</button>
21661
21670
  <button type="button" class="skill-composer-mode" data-kind="project" onclick="setSkillComposerMode('project')" style="padding:7px 6px;border:1px solid var(--border);border-radius:6px;background:var(--bg-primary);color:var(--text-secondary);font-size:11px;font-weight:600;cursor:pointer">Project</button>
21662
21671
  <button type="button" class="skill-composer-mode" data-kind="memory" onclick="setSkillComposerMode('memory')" style="padding:7px 6px;border:1px solid var(--border);border-radius:6px;background:var(--bg-primary);color:var(--text-secondary);font-size:11px;font-weight:600;cursor:pointer">Memory</button>
@@ -21670,12 +21679,13 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
21670
21679
  <div id="skill-composer-anchor-summary" style="margin-top:8px;min-height:22px;font-size:11px;color:var(--text-muted);line-height:1.45">No starting point selected.</div>
21671
21680
  <div id="skill-composer-preview" style="margin-top:10px;max-height:180px;overflow:auto;border:1px solid var(--border);border-radius:6px;background:var(--bg-primary);padding:10px 12px;font-size:11px;line-height:1.45;color:var(--text-secondary)"></div>
21672
21681
  <div style="display:flex;align-items:center;justify-content:flex-end;gap:8px;margin-top:12px">
21673
- <button type="button" class="btn-secondary" onclick="openSkillStudio()" style="font-size:12px;padding:7px 12px;border-radius:6px;border:1px solid var(--border);background:transparent;color:var(--text-primary);cursor:pointer">Open Studio</button>
21674
- <button type="button" class="btn-primary" id="skill-composer-draft-btn" onclick="startSkillComposerDraft()" disabled style="font-size:12px;padding:7px 14px;border-radius:6px;border:none;background:var(--accent);color:#fff;font-weight:600;cursor:pointer;opacity:0.55">Draft skill</button>
21682
+ <button type="button" class="btn-secondary" onclick="startSkillComposerChat()" style="font-size:12px;padding:7px 12px;border-radius:6px;border:1px solid var(--border);background:transparent;color:var(--text-primary);cursor:pointer">Build in chat</button>
21683
+ <button type="button" class="btn-primary" id="skill-composer-draft-btn" onclick="startSkillComposerDraft()" disabled style="font-size:12px;padding:7px 14px;border-radius:6px;border:none;background:var(--accent);color:#fff;font-weight:600;cursor:pointer;opacity:0.55">Review draft</button>
21675
21684
  </div>
21676
21685
  </div>
21677
21686
  </div>
21678
21687
  </div>
21688
+ </div>
21679
21689
  <div style="display:grid;grid-template-columns:380px 1fr;gap:18px;height:calc(100vh - 360px);min-height:440px">
21680
21690
  <div id="skills-list-pane" style="overflow-y:auto;border:1px solid var(--border);border-radius:8px;background:var(--bg-secondary)">
21681
21691
  <div style="padding:14px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px">
@@ -29214,6 +29224,17 @@ function startSkillComposerDraft() {
29214
29224
  openCreateSkillModalFromComposer({ draft: true });
29215
29225
  }
29216
29226
 
29227
+ function startSkillComposerChat() {
29228
+ var prompt = buildSkillComposerPrompt();
29229
+ if (typeof askClementineWith !== 'function') {
29230
+ toast('Chat is not ready yet. Try again after the dashboard finishes loading.', 'error');
29231
+ return;
29232
+ }
29233
+ closeSkillStudio({ silent: true });
29234
+ askClementineWith(prompt, { autoSend: false });
29235
+ toast('Skill-creator prompt loaded in chat. Press send when you are ready.', 'info');
29236
+ }
29237
+
29217
29238
  function openCreateSkillModalFromComposer(opts) {
29218
29239
  opts = opts || {};
29219
29240
  var text = ((document.getElementById('skill-composer-text') || {}).value || '').trim();
@@ -29223,6 +29244,7 @@ function openCreateSkillModalFromComposer(opts) {
29223
29244
  return;
29224
29245
  }
29225
29246
  var seed = buildSkillComposerDraftSeed();
29247
+ closeSkillStudio({ silent: true });
29226
29248
  _openSkillModal({ mode: 'create', prefill: seed });
29227
29249
  if (seed.note && typeof toast === 'function') toast(seed.note, 'success');
29228
29250
  }
@@ -30003,7 +30025,7 @@ async function _openSkillModal(opts) {
30003
30025
  + '<strong style="color:var(--text-secondary);font-weight:600">Format:</strong> '
30004
30026
  + '<code style="font-size:10px;background:var(--bg-secondary);padding:1px 4px;border-radius:3px">[WHAT it does] + [WHEN to use it] + [trigger phrases]</code>'
30005
30027
  + ' &nbsp;·&nbsp; under 1024 chars · no <code style="font-size:10px">&lt; &gt;</code> · '
30006
- + '<a href="javascript:void(0)" onclick="askClementineWith(\\x27Use skill-creator to help me write a great Anthropic-canonical description for the skill I am building.\\x27)" style="color:var(--accent);text-decoration:none">use skill-creator</a>'
30028
+ + '<a href="javascript:void(0)" onclick="askSkillCreatorForDescription()" style="color:var(--accent);text-decoration:none">use skill-creator</a>'
30007
30029
  + '</div>'
30008
30030
  + '<textarea id="skill-modal-desc" rows="2" oninput="updateSkillModalCounters()" placeholder="Example: Analyzes Outlook emails and drafts triage replies. Use when user asks to triage email or mentions inbox cleanup." style="width:100%;padding:8px 10px;font-size:13px;border:1px solid var(--border);border-radius:6px;background:var(--bg-secondary);color:var(--text-primary);margin-bottom:12px;font-family:inherit;resize:vertical"></textarea>'
30009
30031
  + '<label style="display:block;font-size:12px;color:var(--text-secondary);margin-bottom:4px;font-weight:500">Allowed tools <span style="color:var(--text-muted)">(comma-separated, leave blank for default)</span></label>'
@@ -30023,7 +30045,9 @@ async function _openSkillModal(opts) {
30023
30045
  + '</div>';
30024
30046
  document.body.appendChild(modal);
30025
30047
  }
30026
- document.getElementById('skill-modal-heading').textContent = opts.mode === 'edit' ? 'Edit skill: ' + nameVal : 'New skill';
30048
+ document.getElementById('skill-modal-heading').textContent = opts.mode === 'edit'
30049
+ ? 'Edit skill: ' + nameVal
30050
+ : (prefill.note ? 'Review skill draft' : 'Manual skill editor');
30027
30051
  document.getElementById('skill-modal-original-name').value = opts.mode === 'edit' ? nameVal : '';
30028
30052
  document.getElementById('skill-modal-name').value = nameVal;
30029
30053
  document.getElementById('skill-modal-name').disabled = opts.mode === 'edit';
@@ -30044,7 +30068,8 @@ async function _openSkillModal(opts) {
30044
30068
  var errEl = document.getElementById('skill-modal-error');
30045
30069
  if (errEl) { errEl.style.display = 'none'; errEl.textContent = ''; }
30046
30070
  modal.style.display = 'flex';
30047
- document.getElementById('skill-modal-name').focus();
30071
+ var initialFocus = prefill.note ? document.getElementById('skill-modal-desc') : document.getElementById('skill-modal-name');
30072
+ if (initialFocus) initialFocus.focus();
30048
30073
  if (typeof updateSkillModalCounters === 'function') updateSkillModalCounters();
30049
30074
  if (typeof renderSkillModalToolsPreview === 'function') renderSkillModalToolsPreview();
30050
30075
  // 1.18.168 — render the compact optional template seed in create mode only.
@@ -35396,7 +35421,7 @@ document.addEventListener('click', function(e) {
35396
35421
  // Back-compat shim — older call sites still reference loadProfiles().
35397
35422
  function loadProfiles() { return refreshChatAgentPicker(); }
35398
35423
 
35399
- // ── Skill Studio — opens the Skills page composer ──────────
35424
+ // ── Skill Studio — opens the natural-language composer as a real modal ──────────
35400
35425
 
35401
35426
  function openSkillStudio() {
35402
35427
  navigateTo('skills');
@@ -35404,15 +35429,56 @@ function openSkillStudio() {
35404
35429
  try { initSkillComposer(); } catch (_) { /* non-fatal */ }
35405
35430
  var composer = document.getElementById('skill-composer');
35406
35431
  var input = document.getElementById('skill-composer-text');
35407
- if (composer && composer.scrollIntoView) composer.scrollIntoView({ behavior: 'smooth', block: 'start' });
35408
- if (composer) {
35409
- composer.style.boxShadow = '0 0 0 2px rgba(255,141,0,0.35)';
35410
- setTimeout(function() { composer.style.boxShadow = ''; }, 1400);
35432
+ var modal = document.getElementById('skill-studio-modal');
35433
+ if (!modal) {
35434
+ modal = document.createElement('div');
35435
+ modal.id = 'skill-studio-modal';
35436
+ modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.42);z-index:1050;display:none;align-items:center;justify-content:center;padding:20px';
35437
+ modal.innerHTML =
35438
+ '<div style="width:min(1040px,96vw);max-height:92vh;background:var(--bg-primary);border:1px solid var(--border);border-radius:10px;box-shadow:0 18px 56px rgba(0,0,0,0.35);display:flex;flex-direction:column;overflow:hidden">'
35439
+ + '<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:14px;padding:16px 20px;border-bottom:1px solid var(--border);background:var(--bg-secondary)">'
35440
+ + '<div style="min-width:0">'
35441
+ + '<div style="font-size:16px;font-weight:600;color:var(--text-primary);margin-bottom:3px">Skill Studio</div>'
35442
+ + '<div style="font-size:12px;color:var(--text-muted);line-height:1.45">Start with the outcome. Add MCP, CLI, project, or memory anchors only when they are real dependencies. Review the generated SKILL.md before saving.</div>'
35443
+ + '</div>'
35444
+ + '<button type="button" onclick="closeSkillStudio()" title="Close Skill Studio" style="background:none;border:none;font-size:20px;color:var(--text-muted);cursor:pointer;padding:0 4px;line-height:1">&times;</button>'
35445
+ + '</div>'
35446
+ + '<div id="skill-studio-modal-body" style="padding:18px 20px;overflow:auto;flex:1;min-height:0"></div>'
35447
+ + '<div style="display:flex;align-items:center;gap:8px;justify-content:flex-end;padding:14px 20px;border-top:1px solid var(--border);background:var(--bg-secondary)">'
35448
+ + '<button type="button" onclick="closeSkillStudio()" style="font-size:13px;padding:7px 14px;border-radius:6px;border:1px solid var(--border);background:transparent;color:var(--text-primary);cursor:pointer">Keep on page</button>'
35449
+ + '<button type="button" onclick="startSkillComposerChat()" style="font-size:13px;padding:7px 14px;border-radius:6px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text-primary);cursor:pointer">Build in chat</button>'
35450
+ + '<button type="button" onclick="startSkillComposerDraft()" class="btn-primary" style="font-size:13px;padding:7px 16px;border-radius:6px;border:none;background:var(--accent);color:#fff;font-weight:600;cursor:pointer">Review draft</button>'
35451
+ + '</div>'
35452
+ + '</div>';
35453
+ document.body.appendChild(modal);
35411
35454
  }
35455
+ var body = document.getElementById('skill-studio-modal-body');
35456
+ if (composer && body && composer.parentElement !== body) {
35457
+ composer.dataset.originalMargin = composer.style.margin || '';
35458
+ body.appendChild(composer);
35459
+ composer.style.margin = '0';
35460
+ }
35461
+ modal.style.display = 'flex';
35462
+ updateSkillComposerDraftState();
35412
35463
  if (input) input.focus();
35413
35464
  }, 80);
35414
35465
  }
35415
35466
 
35467
+ function closeSkillStudio(opts) {
35468
+ opts = opts || {};
35469
+ var modal = document.getElementById('skill-studio-modal');
35470
+ var composer = document.getElementById('skill-composer');
35471
+ var home = document.getElementById('skill-composer-home');
35472
+ if (composer && home && composer.parentElement !== home) {
35473
+ home.appendChild(composer);
35474
+ composer.style.margin = composer.dataset.originalMargin || '0 0 16px';
35475
+ }
35476
+ if (modal) modal.style.display = 'none';
35477
+ if (!opts.silent && composer && composer.scrollIntoView) {
35478
+ composer.scrollIntoView({ behavior: 'smooth', block: 'start' });
35479
+ }
35480
+ }
35481
+
35416
35482
  function updateBuilderMode() {
35417
35483
  var type = (document.getElementById('builder-type') || {}).value || 'skill';
35418
35484
  var title = document.getElementById('builder-page-title');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.171",
3
+ "version": "1.18.172",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",