clementine-agent 1.18.113 → 1.18.114

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 +239 -55
  2. package/package.json +1 -1
@@ -6458,6 +6458,44 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
6458
6458
  res.status(500).json({ error: String(err) });
6459
6459
  }
6460
6460
  });
6461
+ // POST /api/tools/probe — force-refresh the SDK tool inventory cache
6462
+ // (`~/.clementine/.tool-inventory.json`). Without this the catalog page's
6463
+ // Claude Desktop connectors (claude_ai_*) and per-server tool counts stay
6464
+ // empty until the daemon happens to fire its first agent run. The probe
6465
+ // takes ~3-8s because it boots a real SDK query against haiku to capture
6466
+ // `system/init.tools`. Cached results are returned without re-probing
6467
+ // unless `force=true` is passed.
6468
+ app.post('/api/tools/probe', async (req, res) => {
6469
+ try {
6470
+ const { probeAvailableTools } = await import('../agent/mcp-bridge.js');
6471
+ const force = req.body?.force === true;
6472
+ const inv = await probeAvailableTools(force);
6473
+ res.json({ ok: true, probedAt: inv.probedAt, toolCount: inv.tools.length, tools: inv.tools });
6474
+ }
6475
+ catch (err) {
6476
+ res.status(500).json({ ok: false, error: String(err?.message ?? err) });
6477
+ }
6478
+ });
6479
+ // GET /api/tools/inventory — cached SDK probe result. Returns the raw
6480
+ // tool list the SDK currently advertises (built-ins + every MCP server's
6481
+ // tools + claude_ai_* connectors + Composio toolkits). `null` when the
6482
+ // probe hasn't run yet. Reads the on-disk cache; never blocks on probing.
6483
+ app.get('/api/tools/inventory', (_req, res) => {
6484
+ try {
6485
+ // Inline the cache read to avoid module imports in the hot path.
6486
+ const TOOL_INVENTORY_FILE = path.join(BASE_DIR, '.tool-inventory.json');
6487
+ if (!existsSync(TOOL_INVENTORY_FILE)) {
6488
+ res.json({ probed: false, probedAt: null, toolCount: 0, tools: [] });
6489
+ return;
6490
+ }
6491
+ const data = JSON.parse(readFileSync(TOOL_INVENTORY_FILE, 'utf-8'));
6492
+ const tools = Array.isArray(data.tools) ? data.tools : [];
6493
+ res.json({ probed: true, probedAt: data.probedAt, toolCount: tools.length, tools });
6494
+ }
6495
+ catch (err) {
6496
+ res.status(500).json({ ok: false, error: String(err?.message ?? err) });
6497
+ }
6498
+ });
6461
6499
  // ── Composio (1000+ third-party services via OAuth broker) ────
6462
6500
  app.get('/api/composio/status', async (_req, res) => {
6463
6501
  // Use isComposioEnabled — checks both process.env (dashboard hot-reload)
@@ -25299,85 +25337,231 @@ async function refreshToolsMcpCatalog() {
25299
25337
  var panel = document.getElementById('panel-toolsmcp');
25300
25338
  if (!panel) return;
25301
25339
  panel.innerHTML = '<div class="empty-state" style="padding:24px;color:var(--text-muted)">Loading Tools &amp; MCP catalog…</div>';
25302
- var statusMap = {};
25303
- var servers = [];
25340
+
25341
+ // Fetch every input the catalog needs in parallel:
25342
+ // - /api/available-tools — 12 categories of curated tool entries (Core SDK, CLI, Memory&Vault, Composio, etc.)
25343
+ // - /api/tools/inventory — the SDK probe cache (every tool the daemon's Claude Agent SDK can actually see, including claude_ai_* connectors)
25344
+ // - /api/mcp-servers — configured MCP servers (transport, command, env)
25345
+ // - /api/mcp-status — live connection status keyed by server name
25346
+ var avail = null, inventory = null, statusMap = {}, servers = [];
25304
25347
  try {
25305
- var sR = await apiFetch('/api/mcp-status');
25306
- var statusJson = await sR.json();
25307
- // /api/mcp-status returns { servers: [{name, status}], updatedAt }.
25308
- // Build a name entry lookup so renderMcpCatalogCard can probe by name.
25348
+ var results = await Promise.all([
25349
+ apiFetch('/api/available-tools').then(function(r){ return r.json(); }).catch(function(){ return null; }),
25350
+ apiFetch('/api/tools/inventory').then(function(r){ return r.json(); }).catch(function(){ return null; }),
25351
+ apiFetch('/api/mcp-status').then(function(r){ return r.json(); }).catch(function(){ return null; }),
25352
+ apiFetch('/api/mcp-servers').then(function(r){ return r.json(); }).catch(function(){ return null; }),
25353
+ ]);
25354
+ avail = results[0];
25355
+ inventory = results[1];
25356
+ var statusJson = results[2];
25309
25357
  if (statusJson && Array.isArray(statusJson.servers)) {
25310
25358
  for (var si = 0; si < statusJson.servers.length; si++) {
25311
25359
  var entry = statusJson.servers[si];
25312
25360
  if (entry && entry.name) statusMap[entry.name] = entry;
25313
25361
  }
25314
25362
  }
25315
- } catch (e) { /* status is optional — servers still render without it */ }
25316
- try {
25317
- var lR = await apiFetch('/api/mcp-servers');
25318
- var lJson = await lR.json();
25363
+ var lJson = results[3];
25319
25364
  servers = (lJson && lJson.servers) || [];
25320
25365
  } catch (e) {
25321
- panel.innerHTML = '<div class="empty-state" style="padding:24px;color:var(--red)">Failed to load MCP servers: ' + esc(String(e)) + '</div>';
25366
+ panel.innerHTML = '<div class="empty-state" style="padding:24px;color:var(--red)">Failed to load tool catalog: ' + esc(String(e)) + '</div>';
25322
25367
  return;
25323
25368
  }
25369
+
25370
+ // Total tools across every curated category. Composio toolkits are 1k+
25371
+ // in user installs — so the count includes them but the rendered list
25372
+ // collapses by default (see below).
25373
+ var totalTools = 0;
25374
+ var categoryEntries = [];
25375
+ if (avail && avail.categories && typeof avail.categories === 'object') {
25376
+ var keys = Object.keys(avail.categories);
25377
+ for (var ki = 0; ki < keys.length; ki++) {
25378
+ var arr = avail.categories[keys[ki]] || [];
25379
+ categoryEntries.push({ name: keys[ki], items: Array.isArray(arr) ? arr : [] });
25380
+ totalTools += Array.isArray(arr) ? arr.length : 0;
25381
+ }
25382
+ }
25383
+
25384
+ // SDK probe — when populated, gives us the live system/init.tools list
25385
+ // so each card can show its real tool count and the "Live" badge can flip
25386
+ // on for tools that are actually registered with the SDK right now.
25387
+ var liveTools = inventory && Array.isArray(inventory.tools) ? inventory.tools : [];
25388
+ var liveSet = {};
25389
+ for (var lt = 0; lt < liveTools.length; lt++) liveSet[liveTools[lt]] = true;
25390
+ var probedAt = inventory && inventory.probedAt;
25391
+
25324
25392
  var tabCount = document.getElementById('build-tab-toolsmcp-count');
25325
25393
  if (tabCount) {
25326
- tabCount.textContent = servers.length;
25327
- tabCount.style.display = servers.length > 0 ? '' : 'none';
25328
- }
25329
- // Bucket servers into the four PRD categories. The existing
25330
- // ManagedMcpServer type doesn't have an explicit "kind" field, so we
25331
- // infer: stdio with a known shell binary → 'shell', stdio bundled with
25332
- // clementine → 'builtin', stdio external command → 'external_stdio',
25333
- // http/sse → 'external_remote'. The bucket keys map to the PRD's four
25334
- // taxonomy cards.
25335
- var buckets = { builtin: [], custom: [], shell: [], external: [] };
25336
- for (var i = 0; i < servers.length; i++) {
25337
- var s = servers[i];
25338
- var name = s.name || '';
25339
- var type = s.type || 'stdio';
25340
- var cmd = s.command || '';
25341
- var kind;
25342
- // The clementine-tools server is an in-process bundle
25343
- if (name === 'clementine-tools' || name === 'kernel') kind = 'builtin';
25344
- else if (type === 'http' || type === 'sse') kind = 'external';
25345
- else if (/^(sf|gh|gcloud|kubectl|docker|aws|az|terraform)$/.test(cmd) || /\\b(sf|gh|gcloud|kubectl)$/.test(cmd)) kind = 'shell';
25346
- else kind = 'external'; // default for stdio external MCP
25347
- buckets[kind].push(s);
25394
+ tabCount.textContent = totalTools;
25395
+ tabCount.style.display = totalTools > 0 ? '' : 'none';
25348
25396
  }
25397
+
25349
25398
  var html = '';
25350
- // Header strip
25351
- html += '<div style="margin-bottom:18px"><h2 style="margin:0 0 4px;font-size:18px;font-weight:600;color:var(--text-primary)">Tools &amp; MCP catalog</h2>'
25352
- + '<div style="font-size:12px;color:var(--text-muted)">'+ esc(servers.length) +' MCP server' + (servers.length === 1 ? '' : 's') + ' configured. Click any task in the Tasks tab to bind specific tools to that task.</div></div>';
25353
- // Four-card taxonomy. Each section is a labeled bucket of cards.
25354
- var sections = [
25355
- { key: 'builtin', label: 'Built-in', desc: 'Claude SDK native tools — always available to every task at the agent profile\\x27s permission tier.' },
25356
- { key: 'custom', label: 'Custom in-process MCP', desc: 'MCP servers defined in clementine\\x27s code, loaded inside the daemon process.' },
25357
- { key: 'shell', label: 'Shell commands', desc: 'Local CLI binaries (sf, gh, gcloud…) wrapped as MCP servers.' },
25358
- { key: 'external', label: 'External MCP servers', desc: 'Third-party MCP servers reached over stdio, SSE, or HTTP.' },
25359
- ];
25360
- for (var k = 0; k < sections.length; k++) {
25361
- var sec = sections[k];
25362
- var bucket = buckets[sec.key] || [];
25363
- html += '<div style="margin-bottom:24px">';
25399
+
25400
+ // ── Header strip: total + Refresh button + last-probed timestamp ─────
25401
+ html += '<div style="display:flex;align-items:flex-end;gap:14px;margin-bottom:18px;flex-wrap:wrap">';
25402
+ html += '<div style="flex:1;min-width:240px">'
25403
+ + '<h2 style="margin:0 0 4px;font-size:18px;font-weight:600;color:var(--text-primary)">Tools &amp; MCP catalog</h2>'
25404
+ + '<div style="font-size:12px;color:var(--text-muted)">'
25405
+ + esc(totalTools) + ' tool' + (totalTools === 1 ? '' : 's') + ' across ' + esc(categoryEntries.length) + ' categories'
25406
+ + ' · ' + esc(servers.length) + ' MCP server' + (servers.length === 1 ? '' : 's')
25407
+ + ' · ' + esc(liveTools.length) + ' live in SDK'
25408
+ + (probedAt ? ' (probed ' + esc(timeAgo(probedAt)) + ')' : ' (not probed yet — click Refresh)')
25409
+ + '</div>'
25410
+ + '</div>';
25411
+ html += '<div style="display:flex;gap:8px;align-items:center">'
25412
+ + '<input id="toolsmcp-search" type="text" placeholder="Search tools…" oninput="filterToolsCatalog()" style="padding:6px 10px;border:1px solid var(--border);border-radius:6px;background:var(--bg-secondary);color:var(--text-primary);font-size:12px;width:200px"/>'
25413
+ + '<button class="btn-sm" id="toolsmcp-probe-btn" onclick="probeToolsMcpInventory()" title="Run a one-shot SDK query to refresh the tool inventory (~5s). Picks up new connectors and any tool the SDK is currently surfacing.">Refresh inventory</button>'
25414
+ + '</div>';
25415
+ html += '</div>';
25416
+
25417
+ // ── Tool categories from /api/available-tools ─────────────────────────
25418
+ // Composio (1k+ entries) collapses by default; everything else opens.
25419
+ var compositeRender = function(entry) {
25420
+ var t = entry || {};
25421
+ var name = t.name || '(unnamed)';
25422
+ var desc = t.description || '';
25423
+ var typeLabel = t.type || '';
25424
+ var connected = t.connected;
25425
+ var apiName = t.api;
25426
+ var liveBadge = liveSet[name] ? '<span style="font-size:10px;color:var(--green);background:rgba(34,197,94,0.12);padding:1px 6px;border-radius:999px;margin-left:6px">LIVE</span>' : '';
25427
+ var connBadge = '';
25428
+ if (connected === true) connBadge = '<span style="font-size:10px;color:var(--green);background:rgba(34,197,94,0.12);padding:1px 6px;border-radius:999px;margin-left:6px">connected</span>';
25429
+ else if (connected === false) connBadge = '<span style="font-size:10px;color:var(--text-muted);background:rgba(148,163,184,0.12);padding:1px 6px;border-radius:999px;margin-left:6px">offline</span>';
25430
+ var typeBadge = typeLabel ? '<span style="font-size:10px;color:var(--text-muted);background:var(--bg-tertiary);padding:1px 6px;border-radius:3px;margin-left:6px;text-transform:uppercase;letter-spacing:0.04em">' + esc(typeLabel) + '</span>' : '';
25431
+ var apiBadge = apiName ? '<span style="font-size:10px;color:var(--text-muted);margin-left:6px">via ' + esc(apiName) + '</span>' : '';
25432
+ return '<div class="toolsmcp-tool" data-tool-name="' + esc(name.toLowerCase()) + '" data-tool-desc="' + esc(String(desc).toLowerCase()) + '" style="padding:8px 10px;border-bottom:1px solid var(--border-subtle);font-size:12px;display:flex;align-items:center;gap:6px;flex-wrap:wrap">'
25433
+ + '<code style="background:var(--bg-tertiary);padding:1px 6px;border-radius:3px;color:var(--accent);font-size:11px">' + esc(name) + '</code>'
25434
+ + typeBadge + apiBadge + liveBadge + connBadge
25435
+ + (desc ? '<span style="color:var(--text-secondary);flex:1;min-width:0">' + esc(String(desc).slice(0, 240)) + '</span>' : '')
25436
+ + '</div>';
25437
+ };
25438
+
25439
+ // Stable order — system surfaces first, then APIs, then the giant Composio.
25440
+ var orderedKeys = ['Core SDK', 'CLI Tools', 'Memory & Vault', 'Notes & Tasks', 'API Integrations',
25441
+ 'Goals & Workflows', 'Agent Management', 'Team', 'System', 'Global MCP Servers',
25442
+ 'Local Projects', 'Composio Toolkits'];
25443
+ var orderedCats = orderedKeys
25444
+ .map(function(k){ return categoryEntries.find(function(c){ return c.name === k; }); })
25445
+ .filter(function(c){ return c && c.items.length > 0; });
25446
+ // Append any unknown categories the server returned that aren't in our preferred order.
25447
+ for (var ci = 0; ci < categoryEntries.length; ci++) {
25448
+ if (orderedKeys.indexOf(categoryEntries[ci].name) === -1 && categoryEntries[ci].items.length > 0) {
25449
+ orderedCats.push(categoryEntries[ci]);
25450
+ }
25451
+ }
25452
+
25453
+ for (var c = 0; c < orderedCats.length; c++) {
25454
+ var cat = orderedCats[c];
25455
+ var openByDefault = cat.name !== 'Composio Toolkits' && cat.items.length <= 50;
25456
+ html += '<details class="toolsmcp-cat" data-cat-name="' + esc(cat.name.toLowerCase()) + '"' + (openByDefault ? ' open' : '') + ' style="background:var(--bg-secondary);border:1px solid var(--border);border-radius:8px;margin-bottom:10px;overflow:hidden">';
25457
+ html += '<summary style="padding:10px 14px;cursor:pointer;display:flex;align-items:center;gap:10px;font-size:13px;font-weight:600;color:var(--text-primary);user-select:none">'
25458
+ + '<span style="flex:1">' + esc(cat.name) + '</span>'
25459
+ + '<span style="font-size:11px;color:var(--text-muted);font-weight:500">' + cat.items.length + ' tool' + (cat.items.length === 1 ? '' : 's') + '</span>'
25460
+ + '</summary>';
25461
+ html += '<div class="toolsmcp-cat-body" style="background:var(--bg-primary);max-height:480px;overflow-y:auto">';
25462
+ // For Composio specifically, show first 200 + "show all" — 1037 tools at once is heavy DOM
25463
+ var items = cat.name === 'Composio Toolkits' ? cat.items.slice(0, 200) : cat.items;
25464
+ for (var ii = 0; ii < items.length; ii++) html += compositeRender(items[ii]);
25465
+ if (cat.name === 'Composio Toolkits' && cat.items.length > items.length) {
25466
+ html += '<div style="padding:10px 14px;text-align:center;font-size:11px;color:var(--text-muted);background:var(--bg-secondary);border-top:1px solid var(--border-subtle)">'
25467
+ + 'Showing ' + items.length + ' of ' + cat.items.length + ' Composio toolkits. Use the search box to find a specific service.'
25468
+ + '</div>';
25469
+ }
25470
+ html += '</div></details>';
25471
+ }
25472
+
25473
+ // ── MCP server transport panel — kept for connection status, reconnect, edit ─────
25474
+ if (servers.length > 0) {
25475
+ html += '<div style="margin-top:24px">';
25364
25476
  html += '<div style="display:flex;align-items:baseline;gap:10px;margin-bottom:10px">'
25365
- + '<h3 style="margin:0;font-size:14px;font-weight:600;color:var(--text-primary)">' + esc(sec.label) + '</h3>'
25366
- + '<span style="font-size:11px;color:var(--text-muted);font-weight:500">' + bucket.length + '</span>'
25477
+ + '<h3 style="margin:0;font-size:14px;font-weight:600;color:var(--text-primary)">MCP servers</h3>'
25478
+ + '<span style="font-size:11px;color:var(--text-muted);font-weight:500">' + servers.length + '</span>'
25367
25479
  + '</div>';
25368
- html += '<div style="font-size:11px;color:var(--text-muted);margin-bottom:12px">' + esc(sec.desc) + '</div>';
25369
- if (bucket.length === 0) {
25370
- html += '<div class="empty-state" style="padding:14px;color:var(--text-muted);font-size:12px;background:var(--bg-secondary);border:1px dashed var(--border);border-radius:6px">No servers in this bucket.</div>';
25371
- } else {
25372
- html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px">';
25480
+ html += '<div style="font-size:11px;color:var(--text-muted);margin-bottom:12px">Configured MCP servers and their live connection status. Use Reconnect to clear cached errors; Edit to view command, args, env.</div>';
25481
+ // Bucket by transport so the page reads cleanly. Built-in / Custom buckets
25482
+ // are kept (some installs DO have clementine-tools or kernel) but absent
25483
+ // ones don't render an empty card grid.
25484
+ var buckets = { builtin: [], shell: [], external: [] };
25485
+ for (var i = 0; i < servers.length; i++) {
25486
+ var s = servers[i];
25487
+ var sname = s.name || '';
25488
+ var stype = s.type || 'stdio';
25489
+ var scmd = s.command || '';
25490
+ var kind;
25491
+ if (sname === 'clementine-tools' || sname === 'kernel') kind = 'builtin';
25492
+ else if (/^(sf|gh|gcloud|kubectl|docker|aws|az|terraform)$/.test(scmd) || /\\b(sf|gh|gcloud|kubectl)$/.test(scmd)) kind = 'shell';
25493
+ else kind = 'external';
25494
+ buckets[kind].push(s);
25495
+ }
25496
+ var bucketLabels = [
25497
+ { key: 'builtin', label: 'Built-in / In-process' },
25498
+ { key: 'shell', label: 'Shell wrappers' },
25499
+ { key: 'external', label: 'External (stdio / sse / http)' },
25500
+ ];
25501
+ for (var bk = 0; bk < bucketLabels.length; bk++) {
25502
+ var bucket = buckets[bucketLabels[bk].key] || [];
25503
+ if (bucket.length === 0) continue;
25504
+ html += '<div style="margin-bottom:18px">'
25505
+ + '<div style="font-size:12px;font-weight:500;color:var(--text-secondary);margin-bottom:8px">' + esc(bucketLabels[bk].label) + ' <span style="color:var(--text-muted);font-weight:400">· ' + bucket.length + '</span></div>'
25506
+ + '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px">';
25373
25507
  for (var b = 0; b < bucket.length; b++) html += renderMcpCatalogCard(bucket[b], statusMap);
25374
- html += '</div>';
25508
+ html += '</div></div>';
25375
25509
  }
25376
25510
  html += '</div>';
25377
25511
  }
25512
+
25378
25513
  panel.innerHTML = html;
25379
25514
  }
25380
25515
 
25516
+ // Filter tools by the search box at the top of the catalog. Lightweight
25517
+ // client-side filter — hides individual tool rows + collapses categories
25518
+ // that have zero matches, so 1k+ Composio entries don't slow typing.
25519
+ function filterToolsCatalog() {
25520
+ var input = document.getElementById('toolsmcp-search');
25521
+ if (!input) return;
25522
+ var q = (input.value || '').toLowerCase().trim();
25523
+ var cats = document.querySelectorAll('.toolsmcp-cat');
25524
+ for (var ci = 0; ci < cats.length; ci++) {
25525
+ var cat = cats[ci];
25526
+ var rows = cat.querySelectorAll('.toolsmcp-tool');
25527
+ var anyMatch = false;
25528
+ for (var ri = 0; ri < rows.length; ri++) {
25529
+ var name = rows[ri].getAttribute('data-tool-name') || '';
25530
+ var desc = rows[ri].getAttribute('data-tool-desc') || '';
25531
+ var match = !q || name.indexOf(q) !== -1 || desc.indexOf(q) !== -1;
25532
+ rows[ri].style.display = match ? '' : 'none';
25533
+ if (match) anyMatch = true;
25534
+ }
25535
+ cat.style.display = anyMatch || !q ? '' : 'none';
25536
+ // Auto-open categories with hits when the user is actively searching.
25537
+ if (q && anyMatch) cat.setAttribute('open', '');
25538
+ }
25539
+ }
25540
+
25541
+ // POST /api/tools/probe — runs probeAvailableTools(true) which boots a one-
25542
+ // shot SDK query to capture every tool currently surfaced. Updates the cache
25543
+ // at ~/.clementine/.tool-inventory.json. Then reloads the catalog so the
25544
+ // "LIVE" badges and probed-at timestamp reflect the new data.
25545
+ async function probeToolsMcpInventory() {
25546
+ var btn = document.getElementById('toolsmcp-probe-btn');
25547
+ var originalText = btn ? btn.textContent : '';
25548
+ if (btn) { btn.disabled = true; btn.textContent = 'Probing… (~5s)'; }
25549
+ try {
25550
+ var r = await apiFetch('/api/tools/probe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ force: true }) });
25551
+ var d = await r.json();
25552
+ if (d && d.ok) {
25553
+ toast('Probed ' + d.toolCount + ' tools', 'success');
25554
+ } else {
25555
+ toast(d && d.error ? 'Probe failed: ' + d.error : 'Probe failed', 'error');
25556
+ }
25557
+ } catch (e) {
25558
+ toast('Probe failed: ' + e, 'error');
25559
+ } finally {
25560
+ if (btn) { btn.disabled = false; btn.textContent = originalText || 'Refresh inventory'; }
25561
+ refreshToolsMcpCatalog();
25562
+ }
25563
+ }
25564
+
25381
25565
  // Render one MCP server card. Status pill colors mirror the PRD's five
25382
25566
  // states (connected / failed / needs-auth / pending / disabled). The
25383
25567
  // statusMap shape comes from gw.getMcpStatus() — varies a bit between
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.113",
3
+ "version": "1.18.114",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",