clementine-agent 1.12.3 → 1.12.4

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.
@@ -1108,7 +1108,7 @@ export class PersonalAssistant {
1108
1108
  }
1109
1109
  // ── System Prompt Builder ─────────────────────────────────────────
1110
1110
  buildSystemPrompt(opts = {}) {
1111
- const { isHeartbeat = false, cronTier = null, retrievalContext = '', profile = null, sessionKey = null, model = null, verboseLevel, intentClassification = null } = opts;
1111
+ const { isHeartbeat = false, cronTier = null, retrievalContext = '', profile = null, sessionKey = null, model = null, verboseLevel, intentClassification = null, composioConnectedSlugs = [] } = opts;
1112
1112
  const isAutonomous = isHeartbeat || cronTier !== null;
1113
1113
  // `parts` = stable prefix (cacheable across turns). `volatileParts` =
1114
1114
  // suffix that changes per-turn (date/time, live integration status).
@@ -1566,6 +1566,25 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
1566
1566
  }
1567
1567
  catch { /* non-fatal */ }
1568
1568
  }
1569
+ // Composio tool preference — when a Composio toolkit has an active
1570
+ // connection AND a Claude Desktop equivalent exists, the agent tends to
1571
+ // default to the Claude Desktop tool (mcp__claude_ai_*) because those
1572
+ // names appear in Claude's training and feel familiar. The Composio
1573
+ // versions (mcp__<slug>__*) usually have broader scope (full inbox vs.
1574
+ // limited preview, write access vs. read-only, etc.), so we explicitly
1575
+ // steer toward them when present. Only emitted when at least one
1576
+ // connection is live — otherwise it's noise.
1577
+ if (composioConnectedSlugs.length > 0) {
1578
+ const slugs = composioConnectedSlugs.slice().sort().join(', ');
1579
+ volatileParts.push(`## Composio Tools (Preferred)\n\n` +
1580
+ `Connected Composio toolkits: ${slugs}.\n\n` +
1581
+ `**Always prefer the Composio tool over the Claude Desktop equivalent** when both are available for the same service:\n` +
1582
+ `- For Outlook/email: use \`mcp__outlook__*\` (NOT \`mcp__claude_ai_Microsoft_365__*\`)\n` +
1583
+ `- For Gmail: use \`mcp__gmail__*\` (NOT \`mcp__claude_ai_Gmail__*\`)\n` +
1584
+ `- For Google Drive: use \`mcp__googledrive__*\` (NOT \`mcp__claude_ai_Google_Drive__*\`)\n` +
1585
+ `- For Google Calendar/Sheets/Docs/Slack/Notion/etc.: same pattern — Composio first.\n\n` +
1586
+ `Why: Composio tools have broader scope (full inbox/file access, write capabilities) and are tied to OAuth tokens you control directly. Use Claude Desktop tools only as fallback when no Composio equivalent is connected.`);
1587
+ }
1569
1588
  // Conversational context — same signals the insight engine surfaces
1570
1589
  // proactively (Phase 10), but injected directly into the agent's prompt
1571
1590
  // so it can adjust its own approach. Scoped to chat sessions because
@@ -1852,8 +1871,28 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
1852
1871
  // (added in @anthropic-ai/claude-agent-sdk 0.2.119) tells the prompt
1853
1872
  // cache exactly where the boundary is, so cross-turn cache hits work
1854
1873
  // even when our per-turn goals/memory block changes.
1874
+ // Composio toolkits — build first so we can pass connected slugs into
1875
+ // buildSystemPrompt for the "prefer Composio over claude.ai" rule.
1876
+ // Each connected toolkit becomes an in-process MCP server
1877
+ // (mcp__gmail__*, mcp__slack__*, …). Profile-level allowlist
1878
+ // (profile.allowedComposioToolkits) constrains which toolkits this
1879
+ // agent sees; omit to surface every active connection.
1880
+ let composioMcpServers = {};
1881
+ if (!disableAllTools && !isPlanStep) {
1882
+ try {
1883
+ const { buildComposioMcpServers } = await import('../integrations/composio/mcp-bridge.js');
1884
+ const allowList = profile?.allowedComposioToolkits;
1885
+ composioMcpServers = await buildComposioMcpServers(allowList);
1886
+ }
1887
+ catch (err) {
1888
+ // Composio is purely additive — never block the agent if it fails.
1889
+ logger.debug({ err }, 'Composio MCP servers unavailable');
1890
+ }
1891
+ }
1892
+ const composioConnectedSlugs = Object.keys(composioMcpServers);
1855
1893
  const { stable, volatile: volatilePromptPart } = this.buildSystemPrompt({
1856
1894
  isHeartbeat, cronTier: isPlanStep ? null : cronTier, retrievalContext, profile, sessionKey, model, verboseLevel, intentClassification,
1895
+ composioConnectedSlugs,
1857
1896
  });
1858
1897
  const stablePrefixParts = [stable, securityPrompt]
1859
1898
  .filter(s => s && s.trim().length > 0);
@@ -1928,22 +1967,6 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
1928
1967
  }
1929
1968
  }
1930
1969
  catch { /* non-fatal — run with just Clementine's own server */ }
1931
- // Composio toolkits — each connected toolkit becomes an in-process MCP
1932
- // server (mcp__gmail__*, mcp__slack__*, …). Profile-level allowlist
1933
- // (profile.allowedComposioToolkits) constrains which toolkits this agent
1934
- // sees; omit it to surface every active connection.
1935
- let composioMcpServers = {};
1936
- if (!disableAllTools && !isPlanStep) {
1937
- try {
1938
- const { buildComposioMcpServers } = await import('../integrations/composio/mcp-bridge.js');
1939
- const allowList = profile?.allowedComposioToolkits;
1940
- composioMcpServers = await buildComposioMcpServers(allowList);
1941
- }
1942
- catch (err) {
1943
- // Composio is purely additive — never block the agent if it fails.
1944
- logger.debug({ err }, 'Composio MCP servers unavailable');
1945
- }
1946
- }
1947
1970
  // Permission mode: always 'bypassPermissions' — this is a daemon/harness with no interactive
1948
1971
  // terminal, so 'auto' mode (which requires plan support + human approval) doesn't apply.
1949
1972
  const effectivePermissionMode = 'bypassPermissions';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.12.3",
3
+ "version": "1.12.4",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",