clementine-agent 1.0.44 → 1.0.45
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 +35 -64
- package/dist/agent/mcp-bridge.d.ts +16 -0
- package/dist/agent/mcp-bridge.js +67 -0
- package/dist/index.js +5 -1
- package/package.json +1 -1
package/dist/agent/assistant.js
CHANGED
|
@@ -1058,28 +1058,20 @@ When ${owner} gives you an API key, access token, or similar credential in chat,
|
|
|
1058
1058
|
|
|
1059
1059
|
Use \`env_list\` to show what's configured (values masked) and \`env_unset\` to remove one. All three are owner-DM only — they'll refuse in channel messages or cron runs.
|
|
1060
1060
|
|
|
1061
|
-
###
|
|
1061
|
+
### When a tool call is refused
|
|
1062
1062
|
|
|
1063
|
-
|
|
1063
|
+
If any tool call fails with "not in my function schema" / "tool not allowed" / "unknown tool" while you can see the tool exists in your SDK inventory:
|
|
1064
1064
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
- If your first call is refused, the reason is **only** that the specific tool name isn't in your current allowedTools. Nothing to do with Claude Desktop vs. agent contexts. Nothing to do with the integrations.json file having an empty tools array.
|
|
1068
|
-
- The fix is **always** the same: \`allow_tool("mcp__claude_ai_<IntegrationName>__<tool_name>")\`, then retry. Example tool names: \`mcp__claude_ai_Google_Drive__search_files\`, \`mcp__claude_ai_Google_Drive__list_recent_files\`, \`mcp__claude_ai_Google_Drive__read_file_content\`.
|
|
1065
|
+
1. Call \`allow_tool("<exact_tool_name>")\` — persists to your own whitelist
|
|
1066
|
+
2. Retry the original call
|
|
1069
1067
|
|
|
1070
|
-
|
|
1071
|
-
- "Google Drive isn't available to agents"
|
|
1072
|
-
- "connector tools only work in Claude Desktop chat"
|
|
1073
|
-
- "please open Claude Desktop and try there first"
|
|
1074
|
-
- "the integrations.json has an empty tools array, so I can't use it"
|
|
1075
|
-
- "you need to edit my config file"
|
|
1076
|
-
- "the daemon needs someone to add this"
|
|
1068
|
+
Takes effect on your next query. No restart, no config editing, no asking ${owner} to help.
|
|
1077
1069
|
|
|
1078
|
-
|
|
1070
|
+
**Never** respond with "this tool isn't available to agents" or "you need to edit my config" or "try it in a different app instead." If the tool name appears in your inventory, you can add it. If you genuinely can't find a tool name, ask ${owner} which tool they meant — don't invent rationalizations.
|
|
1079
1071
|
|
|
1080
|
-
|
|
1072
|
+
\`list_allowed_tools\` shows what you've added. \`disallow_tool\` removes one.
|
|
1081
1073
|
|
|
1082
|
-
For \`.env\` credentials, same pattern:
|
|
1074
|
+
For \`.env\` credentials, same self-service pattern: \`env_set(KEY, value)\` — never tell ${owner} to edit the file.
|
|
1083
1075
|
|
|
1084
1076
|
## Context Window Management
|
|
1085
1077
|
|
|
@@ -1094,40 +1086,21 @@ Delegate data-heavy work (SEO, analytics, bulk API calls for 3+ entities) to sub
|
|
|
1094
1086
|
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.
|
|
1095
1087
|
`);
|
|
1096
1088
|
}
|
|
1097
|
-
// Inject Claude Desktop integration awareness.
|
|
1098
|
-
//
|
|
1099
|
-
//
|
|
1100
|
-
// - Possibly-available: common integrations the owner may have connected
|
|
1101
|
-
// at claude.ai level that we don't have a usage record for yet. The
|
|
1102
|
-
// integrations-file is written reactively — only entries with
|
|
1103
|
-
// successful tool uses get captured — so a freshly-connected
|
|
1104
|
-
// Google Drive / Gmail / Slack connector is invisible to us until we
|
|
1105
|
-
// try it. Hint the agent that it can try these blindly; the tool
|
|
1106
|
-
// call will either succeed or return an auth error, at which point
|
|
1107
|
-
// the record gets captured.
|
|
1089
|
+
// Inject Claude Desktop integration awareness. Derived from the probed
|
|
1090
|
+
// SDK tool inventory — whatever the current user has connected at the
|
|
1091
|
+
// claude.ai level shows up here automatically, no per-install hardcoding.
|
|
1108
1092
|
try {
|
|
1109
|
-
const
|
|
1110
|
-
const
|
|
1111
|
-
if (
|
|
1112
|
-
const
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
['
|
|
1120
|
-
['Google_Calendar', 'Google Calendar'], ['Google_Workspace', 'Google Workspace'],
|
|
1121
|
-
['Slack', 'Slack'], ['Notion', 'Notion'], ['GitHub', 'GitHub'],
|
|
1122
|
-
['Linear', 'Linear'], ['Asana', 'Asana'], ['Jira', 'Jira'],
|
|
1123
|
-
['Dropbox', 'Dropbox'], ['Salesforce', 'Salesforce'],
|
|
1124
|
-
['Microsoft_365', 'Microsoft 365'],
|
|
1125
|
-
];
|
|
1126
|
-
const maybe = SPECULATIVE.filter(([name]) => !knownNames.has(name)).map(([, label]) => label);
|
|
1127
|
-
if (maybe.length > 0) {
|
|
1128
|
-
parts.push(`**Possibly connected (try them — they'll either work or return an auth error):** ${maybe.join(', ')}. ` +
|
|
1129
|
-
`Tool names follow \`mcp__claude_ai_<IntegrationName>__<tool>\` — e.g. \`mcp__claude_ai_Google_Drive__search_files\`, \`mcp__claude_ai_Gmail__authenticate\`. ` +
|
|
1130
|
-
`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.`);
|
|
1093
|
+
const inv = _mcpBridge?.loadToolInventory();
|
|
1094
|
+
const labels = new Set();
|
|
1095
|
+
if (inv?.tools) {
|
|
1096
|
+
for (const t of inv.tools) {
|
|
1097
|
+
const m = t.match(/^mcp__claude_ai_([^_]+(?:_[^_]+)*)__/);
|
|
1098
|
+
if (m)
|
|
1099
|
+
labels.add(m[1].replace(/_/g, ' '));
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
if (labels.size > 0) {
|
|
1103
|
+
parts.push(`**Claude Desktop integrations connected for this user:** ${[...labels].sort().join(', ')}. Call \`mcp__claude_ai_<Integration>__<tool>\` directly; the tool names and schemas are in your SDK inventory.`);
|
|
1131
1104
|
}
|
|
1132
1105
|
}
|
|
1133
1106
|
catch { /* non-fatal */ }
|
|
@@ -1449,28 +1422,26 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
1449
1422
|
}
|
|
1450
1423
|
}
|
|
1451
1424
|
catch { /* non-fatal — dynamic tools are supplementary */ }
|
|
1452
|
-
//
|
|
1453
|
-
//
|
|
1454
|
-
//
|
|
1455
|
-
//
|
|
1456
|
-
//
|
|
1457
|
-
//
|
|
1458
|
-
// the whitelist matches reality.
|
|
1425
|
+
// Merge the SDK's probed tool inventory. On daemon startup we run a
|
|
1426
|
+
// one-shot query with no whitelist to discover every tool Claude Code
|
|
1427
|
+
// surfaces (mcp__claude_ai_* connectors, plugins, custom MCP servers,
|
|
1428
|
+
// built-ins). The inventory is cached 24h. This makes the whitelist
|
|
1429
|
+
// match whatever the *current user* has connected — no per-install
|
|
1430
|
+
// hardcoding of tool names in the system prompt or code.
|
|
1459
1431
|
try {
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1462
|
-
for (const
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
allowedTools.push(fullName);
|
|
1432
|
+
const inv = _mcpBridge?.loadToolInventory();
|
|
1433
|
+
if (inv && Array.isArray(inv.tools)) {
|
|
1434
|
+
for (const t of inv.tools) {
|
|
1435
|
+
if (typeof t === 'string' && !allowedTools.includes(t))
|
|
1436
|
+
allowedTools.push(t);
|
|
1466
1437
|
}
|
|
1467
1438
|
}
|
|
1468
1439
|
}
|
|
1469
1440
|
catch { /* non-fatal */ }
|
|
1470
1441
|
// Self-service extension — Clementine can add tools to her own whitelist
|
|
1471
1442
|
// at runtime via the `allow_tool` MCP tool, writing to allowed-tools-
|
|
1472
|
-
// extra.json.
|
|
1473
|
-
//
|
|
1443
|
+
// extra.json. Covers the case where a user connects a new integration
|
|
1444
|
+
// between daemon startup probes (< 24h window).
|
|
1474
1445
|
try {
|
|
1475
1446
|
const extraPath = path.join(BASE_DIR, 'allowed-tools-extra.json');
|
|
1476
1447
|
if (fs.existsSync(extraPath)) {
|
|
@@ -54,6 +54,22 @@ 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
|
+
export interface ToolInventory {
|
|
58
|
+
/** ISO timestamp of the probe */
|
|
59
|
+
probedAt: string;
|
|
60
|
+
/** Full list of tool names the SDK reported in init.tools when no whitelist was applied */
|
|
61
|
+
tools: string[];
|
|
62
|
+
}
|
|
63
|
+
export declare function loadToolInventory(): ToolInventory | null;
|
|
64
|
+
/**
|
|
65
|
+
* Run a minimal SDK query with NO tool whitelist so the init message
|
|
66
|
+
* returns every tool Claude Code is actually surfacing — claude_ai_*
|
|
67
|
+
* connectors, plugins, built-ins, custom MCP servers. Cached for 24h.
|
|
68
|
+
* This removes any need to hardcode user-specific tool names in the
|
|
69
|
+
* system prompt; whatever Claude Desktop is currently connecting to the
|
|
70
|
+
* user's account becomes automatically available to the agent.
|
|
71
|
+
*/
|
|
72
|
+
export declare function probeAvailableTools(force?: boolean): Promise<ToolInventory>;
|
|
57
73
|
/**
|
|
58
74
|
* Register every integration found in a tool inventory. The SDK's system
|
|
59
75
|
* init message (subtype='init') includes a `tools: string[]` with the full
|
package/dist/agent/mcp-bridge.js
CHANGED
|
@@ -384,6 +384,73 @@ export function recordClaudeIntegrationUse(toolName) {
|
|
|
384
384
|
export function getClaudeIntegrations() {
|
|
385
385
|
return Object.values(loadClaudeIntegrations());
|
|
386
386
|
}
|
|
387
|
+
// ── SDK tool inventory probe ───────────────────────────────────────────
|
|
388
|
+
const TOOL_INVENTORY_FILE = path.join(BASE_DIR, '.tool-inventory.json');
|
|
389
|
+
const TOOL_INVENTORY_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24h
|
|
390
|
+
export function loadToolInventory() {
|
|
391
|
+
try {
|
|
392
|
+
if (!existsSync(TOOL_INVENTORY_FILE))
|
|
393
|
+
return null;
|
|
394
|
+
const data = JSON.parse(readFileSync(TOOL_INVENTORY_FILE, 'utf-8'));
|
|
395
|
+
if (!Array.isArray(data.tools))
|
|
396
|
+
return null;
|
|
397
|
+
return data;
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function saveToolInventory(inv) {
|
|
404
|
+
try {
|
|
405
|
+
writeFileSync(TOOL_INVENTORY_FILE, JSON.stringify(inv, null, 2));
|
|
406
|
+
}
|
|
407
|
+
catch { /* non-fatal */ }
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Run a minimal SDK query with NO tool whitelist so the init message
|
|
411
|
+
* returns every tool Claude Code is actually surfacing — claude_ai_*
|
|
412
|
+
* connectors, plugins, built-ins, custom MCP servers. Cached for 24h.
|
|
413
|
+
* This removes any need to hardcode user-specific tool names in the
|
|
414
|
+
* system prompt; whatever Claude Desktop is currently connecting to the
|
|
415
|
+
* user's account becomes automatically available to the agent.
|
|
416
|
+
*/
|
|
417
|
+
export async function probeAvailableTools(force = false) {
|
|
418
|
+
const cached = loadToolInventory();
|
|
419
|
+
if (!force && cached) {
|
|
420
|
+
const age = Date.now() - Date.parse(cached.probedAt);
|
|
421
|
+
if (Number.isFinite(age) && age < TOOL_INVENTORY_MAX_AGE_MS)
|
|
422
|
+
return cached;
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
const { query } = await import('@anthropic-ai/claude-agent-sdk');
|
|
426
|
+
const stream = query({
|
|
427
|
+
prompt: 'ok',
|
|
428
|
+
options: {
|
|
429
|
+
systemPrompt: 'Reply ok.',
|
|
430
|
+
model: 'claude-haiku-4-5',
|
|
431
|
+
permissionMode: 'bypassPermissions',
|
|
432
|
+
allowDangerouslySkipPermissions: true,
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
let tools = [];
|
|
436
|
+
for await (const msg of stream) {
|
|
437
|
+
if (msg?.type === 'system' && msg?.subtype === 'init' && Array.isArray(msg.tools)) {
|
|
438
|
+
tools = msg.tools;
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
if (msg?.type === 'result')
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
const inv = { probedAt: new Date().toISOString(), tools };
|
|
445
|
+
saveToolInventory(inv);
|
|
446
|
+
logger.info({ toolCount: tools.length }, 'Tool inventory probed');
|
|
447
|
+
return inv;
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
logger.warn({ err }, 'Tool inventory probe failed — using cached or empty');
|
|
451
|
+
return cached ?? { probedAt: new Date(0).toISOString(), tools: [] };
|
|
452
|
+
}
|
|
453
|
+
}
|
|
387
454
|
/**
|
|
388
455
|
* Register every integration found in a tool inventory. The SDK's system
|
|
389
456
|
* init message (subtype='init') includes a `tools: string[]` with the full
|
package/dist/index.js
CHANGED
|
@@ -536,9 +536,13 @@ async function asyncMain() {
|
|
|
536
536
|
}
|
|
537
537
|
// ── Check MCP extension permissions ────────────────────────────
|
|
538
538
|
try {
|
|
539
|
-
const { checkPermissionsOnStartup, bootstrapClaudeIntegrationsFromAuditLog } = await import('./agent/mcp-bridge.js');
|
|
539
|
+
const { checkPermissionsOnStartup, bootstrapClaudeIntegrationsFromAuditLog, probeAvailableTools } = await import('./agent/mcp-bridge.js');
|
|
540
540
|
checkPermissionsOnStartup();
|
|
541
541
|
bootstrapClaudeIntegrationsFromAuditLog(path.join(config.BASE_DIR, 'logs', 'audit.log'));
|
|
542
|
+
// Fire-and-forget: probe the SDK's full tool inventory so buildOptions
|
|
543
|
+
// knows everything Claude Code is surfacing (claude_ai_* connectors,
|
|
544
|
+
// plugins, etc.) without per-user hardcoding. Cached 24h.
|
|
545
|
+
probeAvailableTools().catch(() => { });
|
|
542
546
|
}
|
|
543
547
|
catch { /* non-fatal */ }
|
|
544
548
|
// ── Initialize layers ────────────────────────────────────────────
|