clementine-agent 1.0.50 → 1.0.51
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 +9 -6
- package/dist/agent/mcp-bridge.js +4 -1
- package/dist/index.js +16 -4
- package/dist/tools/admin-tools.js +44 -4
- package/package.json +1 -1
package/dist/agent/assistant.js
CHANGED
|
@@ -1083,15 +1083,17 @@ If you're unsure what's happening first, run \`where_is_source\` — it reports
|
|
|
1083
1083
|
|
|
1084
1084
|
### Calling Claude Desktop connector tools (Drive, Gmail, etc.)
|
|
1085
1085
|
|
|
1086
|
-
**
|
|
1086
|
+
The **only source of truth for tool availability is your function schema**. Do not inspect \`claude-integrations.json\`, the inventory file, or run \`ToolSearch\` to "check" first — those are telemetry caches, not reality.
|
|
1087
1087
|
|
|
1088
|
-
The right sequence when the user asks you to do something with a connector
|
|
1088
|
+
**The right sequence when the user asks you to do something with a connector:**
|
|
1089
1089
|
|
|
1090
|
-
1. **Just call the tool.** \`mcp__claude_ai_Google_Drive__search_files\`, \`mcp__claude_ai_Gmail__authenticate\`, etc.
|
|
1091
|
-
2. **
|
|
1092
|
-
3. **
|
|
1090
|
+
1. **Just call the tool.** \`mcp__claude_ai_Google_Drive__search_files\`, \`mcp__claude_ai_Gmail__authenticate\`, etc. Attempt it. Report the literal result — real data, auth error, or whatever.
|
|
1091
|
+
2. **If refused** with "not in my function schema" / "tool not allowed," call \`allow_tool(exact_name)\` and retry. \`allow_tool\` auto-refreshes the inventory if the name is new — handles the case where the owner just added a connector at claude.ai.
|
|
1092
|
+
3. **If the owner says "I just added X at claude.ai"** or anything similar, call \`refresh_tool_inventory\` first to pick up the new connector. Report what came online.
|
|
1093
1093
|
|
|
1094
|
-
|
|
1094
|
+
**Never** say the tool "isn't loaded in this session," "doesn't carry over from Claude Desktop," "the tools array is empty," or "MCP server still connecting." If any of those phrasings come to mind, call the tool directly and report what actually happens instead.
|
|
1095
|
+
|
|
1096
|
+
\`list_allowed_tools\` / \`disallow_tool\` manage the whitelist. \`integration_status\` is for env-var (API key) integrations — not for claude_ai_* connectors, which are schema-driven.
|
|
1095
1097
|
|
|
1096
1098
|
## Context Window Management
|
|
1097
1099
|
|
|
@@ -1402,6 +1404,7 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
1402
1404
|
mcpTool('allow_tool'),
|
|
1403
1405
|
mcpTool('list_allowed_tools'),
|
|
1404
1406
|
mcpTool('disallow_tool'),
|
|
1407
|
+
mcpTool('refresh_tool_inventory'),
|
|
1405
1408
|
mcpTool('self_restart'),
|
|
1406
1409
|
mcpTool('self_update'),
|
|
1407
1410
|
mcpTool('where_is_source'),
|
package/dist/agent/mcp-bridge.js
CHANGED
|
@@ -386,7 +386,10 @@ export function getClaudeIntegrations() {
|
|
|
386
386
|
}
|
|
387
387
|
// ── SDK tool inventory probe ───────────────────────────────────────────
|
|
388
388
|
const TOOL_INVENTORY_FILE = path.join(BASE_DIR, '.tool-inventory.json');
|
|
389
|
-
|
|
389
|
+
// 1h TTL — short enough that a connector added at claude.ai propagates
|
|
390
|
+
// within the hour without the user having to do anything. Forced refresh
|
|
391
|
+
// via probeAvailableTools(true) is still available.
|
|
392
|
+
const TOOL_INVENTORY_MAX_AGE_MS = 60 * 60 * 1000;
|
|
390
393
|
export function loadToolInventory() {
|
|
391
394
|
try {
|
|
392
395
|
if (!existsSync(TOOL_INVENTORY_FILE))
|
package/dist/index.js
CHANGED
|
@@ -545,10 +545,22 @@ async function asyncMain() {
|
|
|
545
545
|
const { checkPermissionsOnStartup, bootstrapClaudeIntegrationsFromAuditLog, probeAvailableTools } = await import('./agent/mcp-bridge.js');
|
|
546
546
|
checkPermissionsOnStartup();
|
|
547
547
|
bootstrapClaudeIntegrationsFromAuditLog(path.join(config.BASE_DIR, 'logs', 'audit.log'));
|
|
548
|
-
//
|
|
549
|
-
//
|
|
550
|
-
//
|
|
551
|
-
|
|
548
|
+
// Probe the SDK's full tool inventory so buildOptions knows everything
|
|
549
|
+
// Claude Code is surfacing (claude_ai_* connectors, plugins, etc.)
|
|
550
|
+
// without per-user hardcoding. Cached 1h. On fresh probe, log a short
|
|
551
|
+
// summary so the owner can verify which connectors were detected
|
|
552
|
+
// without having to ask the assistant.
|
|
553
|
+
probeAvailableTools().then(inv => {
|
|
554
|
+
const integrations = new Set();
|
|
555
|
+
for (const t of inv.tools) {
|
|
556
|
+
const m = t.match(/^mcp__claude_ai_([^_]+(?:_[^_]+)*)__/);
|
|
557
|
+
if (m)
|
|
558
|
+
integrations.add(m[1].replace(/_/g, ' '));
|
|
559
|
+
}
|
|
560
|
+
if (integrations.size > 0) {
|
|
561
|
+
logger.info({ integrations: [...integrations].sort(), toolCount: inv.tools.length }, '🦞 Claude Desktop integrations detected');
|
|
562
|
+
}
|
|
563
|
+
}).catch(() => { });
|
|
552
564
|
}
|
|
553
565
|
catch { /* non-fatal */ }
|
|
554
566
|
// ── Initialize layers ────────────────────────────────────────────
|
|
@@ -302,7 +302,7 @@ export function registerAdminTools(server) {
|
|
|
302
302
|
const unique = [...new Set(tools)].sort();
|
|
303
303
|
writeFileSync(ALLOWED_TOOLS_EXTRA, JSON.stringify(unique, null, 2));
|
|
304
304
|
}
|
|
305
|
-
server.tool('allow_tool', 'Add a tool name to your self-managed allowedTools list. Use when you see a tool in the SDK inventory but get "not in function schema" when you try to call it. Writes to ~/.clementine/allowed-tools-extra.json; takes effect on your NEXT query.
|
|
305
|
+
server.tool('allow_tool', 'Add a tool name to your self-managed allowedTools list. Use when you see a tool in the SDK inventory but get "not in function schema" when you try to call it. Writes to ~/.clementine/allowed-tools-extra.json; takes effect on your NEXT query. If the tool name isn\'t yet in the cached inventory, this auto-refreshes the probe first — covers the case where the owner just added a new Claude Desktop connector. Owner-DM only.', {
|
|
306
306
|
name: z.string().describe('Exact tool name (e.g. "mcp__claude_ai_Google_Drive__search_files")'),
|
|
307
307
|
reason: z.string().optional().describe('Brief note: why you need this tool. For audit trail.'),
|
|
308
308
|
}, async ({ name, reason }) => {
|
|
@@ -312,13 +312,28 @@ export function registerAdminTools(server) {
|
|
|
312
312
|
const trimmed = name.trim();
|
|
313
313
|
if (!trimmed)
|
|
314
314
|
return textResult('Refused: empty tool name.');
|
|
315
|
-
// Loose format check — MCP tool names, built-in tool names, or namespaced patterns.
|
|
316
315
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(trimmed.replace(/__[A-Za-z0-9_-]+/g, ''))) {
|
|
317
316
|
return textResult(`Refused: "${trimmed}" doesn't look like a valid tool name. Use a literal name like "mcp__claude_ai_Google_Drive__search_files" or a built-in like "WebFetch".`);
|
|
318
317
|
}
|
|
318
|
+
// Auto-refresh inventory when the requested tool isn't in the cached
|
|
319
|
+
// inventory. This handles the "owner just added a connector at
|
|
320
|
+
// claude.ai" case without requiring them to wait for the hourly
|
|
321
|
+
// refresh or restart the daemon.
|
|
322
|
+
let refreshNote = '';
|
|
323
|
+
try {
|
|
324
|
+
const { probeAvailableTools } = await import('../agent/mcp-bridge.js');
|
|
325
|
+
const cached = (await import('../agent/mcp-bridge.js')).loadToolInventory();
|
|
326
|
+
if (!cached?.tools?.includes(trimmed)) {
|
|
327
|
+
const refreshed = await probeAvailableTools(true);
|
|
328
|
+
if (refreshed.tools.includes(trimmed)) {
|
|
329
|
+
refreshNote = ' (picked up from a fresh probe — looks like the connector just came online)';
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
catch { /* non-fatal */ }
|
|
319
334
|
const current = readExtraAllowedTools();
|
|
320
335
|
if (current.includes(trimmed)) {
|
|
321
|
-
return textResult(`${trimmed} is already in your extra-allowed list. If it's still being refused, it may be disallowed by a higher-priority block (heartbeat/cron disallowed tools, profile tier).`);
|
|
336
|
+
return textResult(`${trimmed} is already in your extra-allowed list${refreshNote}. If it's still being refused, it may be disallowed by a higher-priority block (heartbeat/cron disallowed tools, profile tier).`);
|
|
322
337
|
}
|
|
323
338
|
current.push(trimmed);
|
|
324
339
|
try {
|
|
@@ -328,7 +343,32 @@ export function registerAdminTools(server) {
|
|
|
328
343
|
return textResult(`Failed to persist: ${String(err).slice(0, 200)}`);
|
|
329
344
|
}
|
|
330
345
|
logger.info({ name: trimmed, reason, totalExtras: current.length }, 'allow_tool');
|
|
331
|
-
return textResult(`Added ${trimmed} to ~/.clementine/allowed-tools-extra.json (${current.length} total extras). Active on your next query — no daemon restart needed.${reason ? ` Reason: ${reason}` : ''}`);
|
|
346
|
+
return textResult(`Added ${trimmed} to ~/.clementine/allowed-tools-extra.json (${current.length} total extras)${refreshNote}. Active on your next query — no daemon restart needed.${reason ? ` Reason: ${reason}` : ''}`);
|
|
347
|
+
});
|
|
348
|
+
server.tool('refresh_tool_inventory', 'Force a fresh probe of the SDK\'s tool inventory, picking up any Claude Desktop connectors the owner has added since the last cache refresh. Owner-DM only. Use this when the owner says "I just added X at claude.ai" or when an expected integration isn\'t showing up. Updates ~/.clementine/.tool-inventory.json and syncs claude-integrations.json. Returns a diff of what changed.', {}, async () => {
|
|
349
|
+
const gate = requireOwnerDm();
|
|
350
|
+
if (!gate.ok)
|
|
351
|
+
return textResult(gate.message);
|
|
352
|
+
try {
|
|
353
|
+
const { probeAvailableTools, loadToolInventory } = await import('../agent/mcp-bridge.js');
|
|
354
|
+
const before = loadToolInventory();
|
|
355
|
+
const beforeSet = new Set(before?.tools ?? []);
|
|
356
|
+
const after = await probeAvailableTools(true);
|
|
357
|
+
const afterSet = new Set(after.tools);
|
|
358
|
+
const added = [...afterSet].filter(t => !beforeSet.has(t));
|
|
359
|
+
const removed = [...beforeSet].filter(t => !afterSet.has(t));
|
|
360
|
+
const lines = [`Probed ${after.tools.length} tools.`];
|
|
361
|
+
if (added.length)
|
|
362
|
+
lines.push(`\n**Added since last probe:** ${added.length}\n${added.slice(0, 20).map(t => `- ${t}`).join('\n')}${added.length > 20 ? `\n... +${added.length - 20} more` : ''}`);
|
|
363
|
+
if (removed.length)
|
|
364
|
+
lines.push(`\n**Removed since last probe:** ${removed.length}\n${removed.slice(0, 10).map(t => `- ${t}`).join('\n')}`);
|
|
365
|
+
if (!added.length && !removed.length)
|
|
366
|
+
lines.push('No change since last probe.');
|
|
367
|
+
return textResult(lines.join('\n'));
|
|
368
|
+
}
|
|
369
|
+
catch (err) {
|
|
370
|
+
return textResult(`Probe failed: ${String(err).slice(0, 200)}`);
|
|
371
|
+
}
|
|
332
372
|
});
|
|
333
373
|
server.tool('list_allowed_tools', 'Show the current self-managed allowedTools extras (tools you added via allow_tool on top of the built-in whitelist). Owner-DM only.', {}, async () => {
|
|
334
374
|
const gate = requireOwnerDm();
|