clementine-agent 1.18.33 → 1.18.34
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 +61 -2
- package/dist/agent/tool-router.d.ts +27 -0
- package/dist/agent/tool-router.js +85 -0
- package/package.json +1 -1
package/dist/agent/assistant.js
CHANGED
|
@@ -15,7 +15,7 @@ import { query as rawQuery, listSubagents, getSubagentMessages, SYSTEM_PROMPT_DY
|
|
|
15
15
|
import pino from 'pino';
|
|
16
16
|
import { BASE_DIR, PKG_DIR, VAULT_DIR, DAILY_NOTES_DIR, SOUL_FILE, AGENTS_FILE, MEMORY_FILE, AGENTS_DIR, ASSISTANT_NAME, OWNER_NAME, MODEL, MODELS, HEARTBEAT_MAX_TURNS, SEARCH_CONTEXT_LIMIT, SEARCH_RECENCY_LIMIT, SYSTEM_PROMPT_MAX_CONTEXT_CHARS, SESSION_EXCHANGE_HISTORY_SIZE, SESSION_EXCHANGE_MAX_CHARS, INJECTED_CONTEXT_MAX_CHARS, UNLEASHED_PHASE_TURNS, UNLEASHED_DEFAULT_MAX_HOURS, UNLEASHED_MAX_PHASES, PROJECTS_META_FILE, CRON_PROGRESS_DIR, CRON_REFLECTIONS_DIR, HANDOFFS_DIR, BUDGET, TASK_BUDGET_TOKENS, IDENTITY_FILE, CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY as CONFIG_ANTHROPIC_API_KEY, claudeCodeDisableOneMillionForModel, currentOneMillionContextMode, normalizeClaudeModelForOneMillionContext, normalizeClaudeSdkOptionsForOneMillionContext, applyOneMillionContextRecovery, looksLikeClaudeOneMillionContextError, usesOneMillionContext, envSnapshot, } from '../config.js';
|
|
17
17
|
import { summarizeIntegrationStatus } from '../config/integrations-registry.js';
|
|
18
|
-
import { loadToolPreferences, computeAvailability, buildPromptInstruction, buildComposioStatusBlock, } from '../integrations/tool-preferences.js';
|
|
18
|
+
import { loadToolPreferences, computeAvailability, buildPromptInstruction, buildComposioStatusBlock, KNOWN_SERVICES, } from '../integrations/tool-preferences.js';
|
|
19
19
|
import { loadClaudeIntegrations } from './mcp-bridge.js';
|
|
20
20
|
import { detectFrustrationSignals, detectRepeatedTopics } from './insight-engine.js';
|
|
21
21
|
import { DEFAULT_CHANNEL_CAPABILITIES } from '../types.js';
|
|
@@ -32,7 +32,7 @@ import { PromptCache } from './prompt-cache.js';
|
|
|
32
32
|
import { searchSkills as searchSkillsSync } from './skill-extractor.js';
|
|
33
33
|
import { classifyIntent, getStrategyGuidance } from './intent-classifier.js';
|
|
34
34
|
import { getEventLog } from './session-event-log.js';
|
|
35
|
-
import { routeToolSurface, TOOL_SURFACE_HARD_LIMIT, TOOL_SURFACE_WARN_THRESHOLD } from './tool-router.js';
|
|
35
|
+
import { applyServiceDedup, routeToolSurface, TOOL_SURFACE_HARD_LIMIT, TOOL_SURFACE_WARN_THRESHOLD } from './tool-router.js';
|
|
36
36
|
import { isRestrictedToolset, toolsetAllowsLocalWrites } from './toolsets.js';
|
|
37
37
|
import { looksLikeApprovalPrompt } from './local-turn.js';
|
|
38
38
|
import { decideTurn } from './turn-policy.js';
|
|
@@ -2436,6 +2436,65 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2436
2436
|
whitelist.add(mcpTool('goal_work'));
|
|
2437
2437
|
allowedTools = allowedTools.filter(t => whitelist.has(t));
|
|
2438
2438
|
}
|
|
2439
|
+
// ── Per-service dedup (intelligent routing) ───────────────────
|
|
2440
|
+
// When a service has BOTH Composio + Claude Desktop sources
|
|
2441
|
+
// connected (e.g. Composio outlook + claude.ai Microsoft 365),
|
|
2442
|
+
// bundles in tool-router list both so either path can route to
|
|
2443
|
+
// whichever is connected. But if BOTH are connected, today's
|
|
2444
|
+
// behavior loaded both — and worse, claude.ai's auto-attach
|
|
2445
|
+
// would pull in every other connector the user authorized
|
|
2446
|
+
// (Drive, Gmail, Calendar, Slack…) via the env path. ~300+ tool
|
|
2447
|
+
// schemas leak in this way and leave Sonnet's autocompact no
|
|
2448
|
+
// room to work.
|
|
2449
|
+
//
|
|
2450
|
+
// Dedup walks each (Composio↔claude.ai) pair, picks ONE per
|
|
2451
|
+
// user preference (default Composio), drops the loser from
|
|
2452
|
+
// mcpServers + allowedTools, and turns inheritFullClaudeEnv off
|
|
2453
|
+
// when no claude.ai service survived (so SAFE_ENV is used and
|
|
2454
|
+
// the SDK can't auto-attach the other connectors).
|
|
2455
|
+
if (!toolsDisabledForCall && !isPlanStep && !toolRoute.fullSurface) {
|
|
2456
|
+
const composioConnected = new Set(Object.keys(composioMcpServers));
|
|
2457
|
+
const cdIntegrationsForDedup = loadClaudeIntegrations();
|
|
2458
|
+
const claudeDesktopActive = new Set(Object.values(cdIntegrationsForDedup).filter(i => i.connected).map(i => i.name));
|
|
2459
|
+
const prefs = loadToolPreferences();
|
|
2460
|
+
const dedupResult = applyServiceDedup(toolRoute, {
|
|
2461
|
+
composioConnected,
|
|
2462
|
+
claudeDesktopActive,
|
|
2463
|
+
preferences: prefs.preferences,
|
|
2464
|
+
knownServices: KNOWN_SERVICES,
|
|
2465
|
+
});
|
|
2466
|
+
if (dedupResult.droppedClaudeAi.length > 0 || dedupResult.droppedComposio.length > 0) {
|
|
2467
|
+
const beforeAllowed = allowedTools.length;
|
|
2468
|
+
const beforeInherit = toolRoute.inheritFullClaudeEnv;
|
|
2469
|
+
toolRoute = dedupResult.route;
|
|
2470
|
+
for (const name of dedupResult.droppedClaudeAi) {
|
|
2471
|
+
delete externalMcpServers[name];
|
|
2472
|
+
}
|
|
2473
|
+
for (const slug of dedupResult.droppedComposio) {
|
|
2474
|
+
delete composioMcpServers[slug];
|
|
2475
|
+
}
|
|
2476
|
+
const droppedServers = new Set([
|
|
2477
|
+
...dedupResult.droppedClaudeAi,
|
|
2478
|
+
...dedupResult.droppedComposio,
|
|
2479
|
+
]);
|
|
2480
|
+
allowedTools = allowedTools.filter(tool => {
|
|
2481
|
+
if (!tool.startsWith('mcp__'))
|
|
2482
|
+
return true;
|
|
2483
|
+
const serverName = tool.slice('mcp__'.length).split('__')[0];
|
|
2484
|
+
return !droppedServers.has(serverName);
|
|
2485
|
+
});
|
|
2486
|
+
logger.info({
|
|
2487
|
+
sessionKey,
|
|
2488
|
+
droppedClaudeAi: dedupResult.droppedClaudeAi,
|
|
2489
|
+
droppedComposio: dedupResult.droppedComposio,
|
|
2490
|
+
anyClaudeDesktopKept: dedupResult.anyClaudeDesktopKept,
|
|
2491
|
+
inheritFullClaudeEnvBefore: beforeInherit,
|
|
2492
|
+
inheritFullClaudeEnvAfter: toolRoute.inheritFullClaudeEnv,
|
|
2493
|
+
allowedToolCountBefore: beforeAllowed,
|
|
2494
|
+
allowedToolCountAfter: allowedTools.length,
|
|
2495
|
+
}, 'Tool route deduped per user tool-preferences');
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2439
2498
|
// Tool-surface cap. Applies to chat AND to autonomous runs (cron,
|
|
2440
2499
|
// unleashed, heartbeat). Without this cap on cron, a single job got
|
|
2441
2500
|
// 300+ MCP tool schemas in the system prompt — leaving Sonnet's SDK
|
|
@@ -31,5 +31,32 @@ export declare const TOOL_SURFACE_WARN_THRESHOLD = 150;
|
|
|
31
31
|
export declare const TOOL_SURFACE_HARD_LIMIT = 220;
|
|
32
32
|
export declare const TOOL_BUNDLES: readonly ToolBundleDefinition[];
|
|
33
33
|
export declare function routeToolSurface(text: string | undefined): ToolRouteDecision;
|
|
34
|
+
import type { ServiceDefinition, ToolSource } from '../integrations/tool-preferences.js';
|
|
35
|
+
export interface ServiceDedupOptions {
|
|
36
|
+
/** Composio toolkit slugs the user has actually connected. */
|
|
37
|
+
composioConnected: Set<string>;
|
|
38
|
+
/** Claude Desktop integration names the user has actually connected. */
|
|
39
|
+
claudeDesktopActive: Set<string>;
|
|
40
|
+
/** User-selected source per service id (from tool-preferences.json). */
|
|
41
|
+
preferences: Record<string, ToolSource>;
|
|
42
|
+
/** The KNOWN_SERVICES registry. Passed in so this module stays
|
|
43
|
+
* decoupled from tool-preferences (and tests can stub the table). */
|
|
44
|
+
knownServices: readonly ServiceDefinition[];
|
|
45
|
+
}
|
|
46
|
+
export interface ServiceDedupResult {
|
|
47
|
+
/** The route with losing sources removed from external + composio sets. */
|
|
48
|
+
route: ToolRouteDecision;
|
|
49
|
+
/** Claude Desktop integration names that were dropped. Used by the
|
|
50
|
+
* caller to add disallowedTools and decide whether the SDK subprocess
|
|
51
|
+
* needs claude.ai env inheritance at all. */
|
|
52
|
+
droppedClaudeAi: string[];
|
|
53
|
+
/** Composio toolkit slugs that were dropped. Mirror of the above. */
|
|
54
|
+
droppedComposio: string[];
|
|
55
|
+
/** True if any claude.ai integration survived dedup. When false, the
|
|
56
|
+
* caller can drop CLAUDE_CODE_OAUTH_TOKEN from the subprocess env so
|
|
57
|
+
* Claude Code doesn't auto-attach claude.ai connectors. */
|
|
58
|
+
anyClaudeDesktopKept: boolean;
|
|
59
|
+
}
|
|
60
|
+
export declare function applyServiceDedup(route: ToolRouteDecision, opts: ServiceDedupOptions): ServiceDedupResult;
|
|
34
61
|
export {};
|
|
35
62
|
//# sourceMappingURL=tool-router.d.ts.map
|
|
@@ -193,4 +193,89 @@ export function routeToolSurface(text) {
|
|
|
193
193
|
reason: bundles.size > 0 || external.size > 0 || composio.size > 0 ? 'matched' : 'empty',
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
|
+
export function applyServiceDedup(route, opts) {
|
|
197
|
+
const droppedClaudeAi = [];
|
|
198
|
+
const droppedComposio = [];
|
|
199
|
+
// fullSurface routes intentionally load everything — admin/debug paths.
|
|
200
|
+
// Skip dedup so behavior matches the user's explicit "all tools" intent.
|
|
201
|
+
if (route.fullSurface) {
|
|
202
|
+
return {
|
|
203
|
+
route,
|
|
204
|
+
droppedClaudeAi,
|
|
205
|
+
droppedComposio,
|
|
206
|
+
anyClaudeDesktopKept: true,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const externalSet = new Set(route.externalMcpServers ?? []);
|
|
210
|
+
const composioSet = new Set(route.composioToolkits ?? []);
|
|
211
|
+
for (const service of opts.knownServices) {
|
|
212
|
+
const cdName = service.claudeDesktopName;
|
|
213
|
+
const composioSlug = service.composioSlug;
|
|
214
|
+
if (!cdName || !composioSlug)
|
|
215
|
+
continue;
|
|
216
|
+
const routeHasCd = externalSet.has(cdName);
|
|
217
|
+
const routeHasComposio = composioSet.has(composioSlug);
|
|
218
|
+
if (!routeHasCd || !routeHasComposio)
|
|
219
|
+
continue;
|
|
220
|
+
// Both sources are in the route. Resolve based on availability + pref.
|
|
221
|
+
const cdAvailable = opts.claudeDesktopActive.has(cdName);
|
|
222
|
+
const composioAvailable = opts.composioConnected.has(composioSlug);
|
|
223
|
+
if (!cdAvailable && !composioAvailable) {
|
|
224
|
+
// Neither connected — drop both (the route lists them, but they'll
|
|
225
|
+
// fail at attach time). Cleaner to remove now.
|
|
226
|
+
externalSet.delete(cdName);
|
|
227
|
+
composioSet.delete(composioSlug);
|
|
228
|
+
droppedClaudeAi.push(cdName);
|
|
229
|
+
droppedComposio.push(composioSlug);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (!cdAvailable) {
|
|
233
|
+
// Only Composio connected — drop the claude.ai entry.
|
|
234
|
+
externalSet.delete(cdName);
|
|
235
|
+
droppedClaudeAi.push(cdName);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (!composioAvailable) {
|
|
239
|
+
composioSet.delete(composioSlug);
|
|
240
|
+
droppedComposio.push(composioSlug);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
// Conflict: both connected. Pick per user preference, default Composio.
|
|
244
|
+
const userPref = opts.preferences[service.id];
|
|
245
|
+
const effective = userPref === 'off'
|
|
246
|
+
? 'off'
|
|
247
|
+
: userPref ?? 'composio';
|
|
248
|
+
if (effective === 'off') {
|
|
249
|
+
externalSet.delete(cdName);
|
|
250
|
+
composioSet.delete(composioSlug);
|
|
251
|
+
droppedClaudeAi.push(cdName);
|
|
252
|
+
droppedComposio.push(composioSlug);
|
|
253
|
+
}
|
|
254
|
+
else if (effective === 'composio') {
|
|
255
|
+
externalSet.delete(cdName);
|
|
256
|
+
droppedClaudeAi.push(cdName);
|
|
257
|
+
}
|
|
258
|
+
else if (effective === 'claude-desktop') {
|
|
259
|
+
composioSet.delete(composioSlug);
|
|
260
|
+
droppedComposio.push(composioSlug);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// After dedup, the SDK subprocess needs claude.ai env inheritance ONLY
|
|
264
|
+
// if some claude.ai integration is still in the route. If everything
|
|
265
|
+
// routed to Composio, force inheritFullClaudeEnv off so Claude Code
|
|
266
|
+
// can't auto-attach the rest of the user's authorized integrations.
|
|
267
|
+
const anyClaudeDesktopKept = externalSet.size > 0
|
|
268
|
+
&& [...externalSet].some(name => opts.claudeDesktopActive.has(name));
|
|
269
|
+
return {
|
|
270
|
+
route: {
|
|
271
|
+
...route,
|
|
272
|
+
externalMcpServers: [...externalSet],
|
|
273
|
+
composioToolkits: [...composioSet],
|
|
274
|
+
inheritFullClaudeEnv: route.inheritFullClaudeEnv && anyClaudeDesktopKept,
|
|
275
|
+
},
|
|
276
|
+
droppedClaudeAi,
|
|
277
|
+
droppedComposio,
|
|
278
|
+
anyClaudeDesktopKept,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
196
281
|
//# sourceMappingURL=tool-router.js.map
|