clementine-agent 1.12.2 → 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.
- package/dist/agent/assistant.js +40 -17
- package/dist/cli/dashboard.js +15 -0
- package/dist/integrations/composio/client.js +16 -1
- package/package.json +1 -1
package/dist/agent/assistant.js
CHANGED
|
@@ -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/dist/cli/dashboard.js
CHANGED
|
@@ -4555,6 +4555,21 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
4555
4555
|
res.status(500).json({ error: String(err) });
|
|
4556
4556
|
}
|
|
4557
4557
|
});
|
|
4558
|
+
// Force-refresh the Composio caches. Useful when the user just made a
|
|
4559
|
+
// connection in Composio's web UI and wants the agent to pick it up
|
|
4560
|
+
// immediately instead of waiting up to 60s for the user_id cache to
|
|
4561
|
+
// expire. Agents can also call this after surfacing a "needs OAuth"
|
|
4562
|
+
// error to retry cleanly.
|
|
4563
|
+
app.post('/api/composio/refresh', async (_req, res) => {
|
|
4564
|
+
try {
|
|
4565
|
+
const c = await import('../integrations/composio/client.js');
|
|
4566
|
+
c.resetComposioClient();
|
|
4567
|
+
res.json({ ok: true, message: 'Composio caches cleared — next query will re-detect.' });
|
|
4568
|
+
}
|
|
4569
|
+
catch (err) {
|
|
4570
|
+
res.status(500).json({ error: String(err) });
|
|
4571
|
+
}
|
|
4572
|
+
});
|
|
4558
4573
|
// ── CRON CRUD routes ──────────────────────────────────────────
|
|
4559
4574
|
app.get('/api/projects', (_req, res) => {
|
|
4560
4575
|
try {
|
|
@@ -108,13 +108,22 @@ export function clementineUserId() {
|
|
|
108
108
|
return readComposioEnv('COMPOSIO_USER_ID') || DEFAULT_NEW_CONNECTION_USER_ID;
|
|
109
109
|
}
|
|
110
110
|
// Cached after first detection — avoids extra API calls per authorize.
|
|
111
|
+
// Cache the detected user_id but only briefly. New connections made via
|
|
112
|
+
// Composio's web UI (outside Clementine) need to be picked up without a
|
|
113
|
+
// daemon restart — long-lived caching breaks that. 60s is short enough
|
|
114
|
+
// for "hit dashboard → connect in Composio web → use it from agent" to
|
|
115
|
+
// work without explicit invalidation, and long enough that within-burst
|
|
116
|
+
// queries don't hammer the API.
|
|
111
117
|
let detectedPreferredUserId = null;
|
|
118
|
+
let detectedAt = 0;
|
|
119
|
+
const USER_ID_CACHE_TTL_MS = 60_000;
|
|
112
120
|
async function detectPreferredUserId(composio) {
|
|
113
121
|
const explicit = readComposioEnv('COMPOSIO_USER_ID');
|
|
114
122
|
if (explicit)
|
|
115
123
|
return explicit;
|
|
116
|
-
if (detectedPreferredUserId !== null)
|
|
124
|
+
if (detectedPreferredUserId !== null && Date.now() - detectedAt < USER_ID_CACHE_TTL_MS) {
|
|
117
125
|
return detectedPreferredUserId;
|
|
126
|
+
}
|
|
118
127
|
// The high-level wrapper's list() drops the snake_case `user_id` field
|
|
119
128
|
// during its camelCase transformation, so connections look like they have
|
|
120
129
|
// no user_id. Use the raw client (snake_case shape) to actually read the
|
|
@@ -141,6 +150,7 @@ async function detectPreferredUserId(composio) {
|
|
|
141
150
|
const top = [...counts.entries()].sort((a, b) => b[1] - a[1])[0][0];
|
|
142
151
|
logger.info({ userId: top, candidates: counts.size }, 'Detected Composio user_id from existing connections');
|
|
143
152
|
detectedPreferredUserId = top;
|
|
153
|
+
detectedAt = Date.now();
|
|
144
154
|
return top;
|
|
145
155
|
}
|
|
146
156
|
}
|
|
@@ -151,6 +161,7 @@ async function detectPreferredUserId(composio) {
|
|
|
151
161
|
// requires a non-empty string, and "default" is the conventional
|
|
152
162
|
// single-tenant value that Composio's quickstart uses.
|
|
153
163
|
detectedPreferredUserId = DEFAULT_NEW_CONNECTION_USER_ID;
|
|
164
|
+
detectedAt = Date.now();
|
|
154
165
|
return DEFAULT_NEW_CONNECTION_USER_ID;
|
|
155
166
|
}
|
|
156
167
|
export function displayNameFor(slug) {
|
|
@@ -517,6 +528,10 @@ _opts) {
|
|
|
517
528
|
try {
|
|
518
529
|
const conn = await composio.toolkits.authorize(userId, slug);
|
|
519
530
|
logger.info({ slug, userId, connectionId: conn.id }, 'Composio authorize OK');
|
|
531
|
+
// Force re-detect on the next query so the new connection (and any
|
|
532
|
+
// others created in parallel via Composio's web UI) get picked up
|
|
533
|
+
// immediately, even within the 60s TTL window.
|
|
534
|
+
detectedPreferredUserId = null;
|
|
520
535
|
return { redirectUrl: conn.redirectUrl ?? null, connectionId: conn.id };
|
|
521
536
|
}
|
|
522
537
|
catch (err) {
|