clementine-agent 1.0.39 → 1.0.41

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.
@@ -694,10 +694,29 @@ export class PersonalAssistant {
694
694
  /** Capture MCP server status from system init messages. */
695
695
  captureMcpStatus(message) {
696
696
  const sysMsg = message;
697
- if (sysMsg.subtype === 'init' && sysMsg.mcp_servers) {
697
+ if (sysMsg.subtype !== 'init')
698
+ return;
699
+ if (sysMsg.mcp_servers) {
698
700
  this._lastMcpStatus = sysMsg.mcp_servers;
699
701
  this._lastMcpStatusTime = new Date().toISOString();
700
702
  }
703
+ // Auto-register Claude Desktop integrations from the authoritative tool
704
+ // list the SDK reports on init. Previously the claude-integrations file
705
+ // was written reactively — only after the agent successfully called an
706
+ // integration tool — which meant a freshly-connected Google Drive or
707
+ // Gmail was invisible until used. Now every session-init rewrites the
708
+ // list to match reality.
709
+ if (Array.isArray(sysMsg.tools) && sysMsg.tools.length > 0 && _mcpBridge) {
710
+ try {
711
+ const { added, updated } = _mcpBridge.registerClaudeIntegrationsFromToolList(sysMsg.tools);
712
+ if (added.length > 0 || updated.length > 0) {
713
+ logger.info({ added, updated }, 'Registered Claude Desktop integrations from SDK tool inventory');
714
+ }
715
+ }
716
+ catch (err) {
717
+ logger.debug({ err }, 'Integration auto-registration failed (non-fatal)');
718
+ }
719
+ }
701
720
  }
702
721
  setUnleashedCompleteCallback(cb) {
703
722
  this.onUnleashedComplete = cb;
@@ -1046,12 +1065,40 @@ Delegate data-heavy work (SEO, analytics, bulk API calls for 3+ entities) to sub
1046
1065
  Never spawn a sub-agent with vague instructions like "handle this brief" — tell it exactly what to read, what to change, and where to write the result.
1047
1066
  `);
1048
1067
  }
1049
- // Inject Claude Desktop integration awareness (compact — ~20 tokens per integration)
1068
+ // Inject Claude Desktop integration awareness. Two lists:
1069
+ // - Known-confirmed: integrations the agent has actually used before
1070
+ // (persisted in claude-integrations.json). High confidence.
1071
+ // - Possibly-available: common integrations the owner may have connected
1072
+ // at claude.ai level that we don't have a usage record for yet. The
1073
+ // integrations-file is written reactively — only entries with
1074
+ // successful tool uses get captured — so a freshly-connected
1075
+ // Google Drive / Gmail / Slack connector is invisible to us until we
1076
+ // try it. Hint the agent that it can try these blindly; the tool
1077
+ // call will either succeed or return an auth error, at which point
1078
+ // the record gets captured.
1050
1079
  try {
1051
1080
  const integrations = _mcpBridge?.getClaudeIntegrations() ?? [];
1081
+ const knownNames = new Set(integrations.map(ig => ig.name));
1052
1082
  if (integrations.length > 0) {
1053
1083
  const names = integrations.map(ig => ig.label).join(', ');
1054
- parts.push(`**Connected via Claude Desktop:** ${names}. Use their \`mcp__claude_ai_*\` tools for email, calendar, etc.`);
1084
+ parts.push(`**Connected via Claude Desktop:** ${names}. Use their \`mcp__claude_ai_*\` tools (e.g. \`mcp__claude_ai_Google_Drive__search_files\`).`);
1085
+ }
1086
+ // Common integrations to speculatively mention. If the owner has
1087
+ // connected any of these at claude.ai, the corresponding tool names
1088
+ // will work even if no prior record exists.
1089
+ const SPECULATIVE = [
1090
+ ['Google_Drive', 'Google Drive'], ['Gmail', 'Gmail'],
1091
+ ['Google_Calendar', 'Google Calendar'], ['Google_Workspace', 'Google Workspace'],
1092
+ ['Slack', 'Slack'], ['Notion', 'Notion'], ['GitHub', 'GitHub'],
1093
+ ['Linear', 'Linear'], ['Asana', 'Asana'], ['Jira', 'Jira'],
1094
+ ['Dropbox', 'Dropbox'], ['Salesforce', 'Salesforce'],
1095
+ ['Microsoft_365', 'Microsoft 365'],
1096
+ ];
1097
+ const maybe = SPECULATIVE.filter(([name]) => !knownNames.has(name)).map(([, label]) => label);
1098
+ if (maybe.length > 0) {
1099
+ parts.push(`**Possibly connected (try them — they'll either work or return an auth error):** ${maybe.join(', ')}. ` +
1100
+ `Tool names follow \`mcp__claude_ai_<IntegrationName>__<tool>\` — e.g. \`mcp__claude_ai_Google_Drive__search_files\`, \`mcp__claude_ai_Gmail__authenticate\`. ` +
1101
+ `Don't tell ${owner} an integration is "not available" without first attempting the tool call — a fresh connection won't be in your recorded list until after first use.`);
1055
1102
  }
1056
1103
  }
1057
1104
  catch { /* non-fatal */ }
@@ -54,6 +54,21 @@ export declare function loadClaudeIntegrations(): Record<string, ClaudeIntegrati
54
54
  export declare function recordClaudeIntegrationUse(toolName: string): void;
55
55
  /** Get all discovered Claude Desktop integrations as a list. */
56
56
  export declare function getClaudeIntegrations(): ClaudeIntegration[];
57
+ /**
58
+ * Register every integration found in a tool inventory. The SDK's system
59
+ * init message (subtype='init') includes a `tools: string[]` with the full
60
+ * set of tools the agent actually has access to this session — including
61
+ * every mcp__claude_ai_* tool Claude Desktop is surfacing. Walking that
62
+ * list on init gives us the authoritative, up-to-date integration set
63
+ * without waiting for the agent to blindly try each one.
64
+ *
65
+ * Idempotent: if an entry already exists, we merge new tool names into it
66
+ * and bump `connected = true` without touching firstSeen/lastUsed.
67
+ */
68
+ export declare function registerClaudeIntegrationsFromToolList(tools: string[]): {
69
+ added: string[];
70
+ updated: string[];
71
+ };
57
72
  /**
58
73
  * Bootstrap integrations from the audit log.
59
74
  * Call once on startup to seed the integrations file from historical data.
@@ -329,7 +329,12 @@ function parseClaudeDesktopTool(toolName) {
329
329
  const match = toolName.match(/^mcp__claude_ai_([^_]+(?:_[^_]+)*)__(.+)$/);
330
330
  if (!match)
331
331
  return null;
332
- return { integration: match[1], tool: match[2] };
332
+ const raw = match[1];
333
+ // Normalize against the known canonical set (case-insensitive) so we don't
334
+ // create both `Microsoft_365` and `microsoft_365` entries when the SDK
335
+ // occasionally hands us a lowercased tool name.
336
+ const canonical = Object.keys(INTEGRATION_LABELS).find(k => k.toLowerCase() === raw.toLowerCase()) ?? raw;
337
+ return { integration: canonical, tool: match[2] };
333
338
  }
334
339
  /** Load persisted integrations from disk. */
335
340
  export function loadClaudeIntegrations() {
@@ -379,6 +384,60 @@ export function recordClaudeIntegrationUse(toolName) {
379
384
  export function getClaudeIntegrations() {
380
385
  return Object.values(loadClaudeIntegrations());
381
386
  }
387
+ /**
388
+ * Register every integration found in a tool inventory. The SDK's system
389
+ * init message (subtype='init') includes a `tools: string[]` with the full
390
+ * set of tools the agent actually has access to this session — including
391
+ * every mcp__claude_ai_* tool Claude Desktop is surfacing. Walking that
392
+ * list on init gives us the authoritative, up-to-date integration set
393
+ * without waiting for the agent to blindly try each one.
394
+ *
395
+ * Idempotent: if an entry already exists, we merge new tool names into it
396
+ * and bump `connected = true` without touching firstSeen/lastUsed.
397
+ */
398
+ export function registerClaudeIntegrationsFromToolList(tools) {
399
+ const added = [];
400
+ const updated = [];
401
+ if (!Array.isArray(tools) || tools.length === 0)
402
+ return { added, updated };
403
+ const integrations = loadClaudeIntegrations();
404
+ const now = new Date().toISOString();
405
+ let dirty = false;
406
+ for (const toolName of tools) {
407
+ const parsed = parseClaudeDesktopTool(toolName);
408
+ if (!parsed)
409
+ continue;
410
+ const existing = integrations[parsed.integration];
411
+ if (existing) {
412
+ if (!existing.tools.includes(parsed.tool)) {
413
+ existing.tools.push(parsed.tool);
414
+ existing.tools.sort();
415
+ dirty = true;
416
+ }
417
+ if (!existing.connected) {
418
+ existing.connected = true;
419
+ dirty = true;
420
+ }
421
+ if (!updated.includes(parsed.integration))
422
+ updated.push(parsed.integration);
423
+ }
424
+ else {
425
+ integrations[parsed.integration] = {
426
+ name: parsed.integration,
427
+ label: INTEGRATION_LABELS[parsed.integration] ?? parsed.integration.replace(/_/g, ' '),
428
+ tools: [parsed.tool],
429
+ firstSeen: now,
430
+ lastUsed: now,
431
+ connected: true,
432
+ };
433
+ added.push(parsed.integration);
434
+ dirty = true;
435
+ }
436
+ }
437
+ if (dirty)
438
+ saveClaudeIntegrations(integrations);
439
+ return { added, updated };
440
+ }
382
441
  /**
383
442
  * Bootstrap integrations from the audit log.
384
443
  * Call once on startup to seed the integrations file from historical data.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.0.39",
3
+ "version": "1.0.41",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",