clementine-agent 1.12.4 → 1.13.0
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
CHANGED
|
@@ -1566,24 +1566,30 @@ 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
|
-
//
|
|
1570
|
-
//
|
|
1571
|
-
//
|
|
1572
|
-
//
|
|
1573
|
-
//
|
|
1574
|
-
//
|
|
1575
|
-
//
|
|
1576
|
-
//
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1569
|
+
// Tool source preferences — only emit a prompt instruction when:
|
|
1570
|
+
// 1. A service has BOTH Composio AND Claude Desktop sources connected
|
|
1571
|
+
// (a real conflict the agent could disambiguate the wrong way), AND
|
|
1572
|
+
// 2. The user has explicitly picked a preference for that service.
|
|
1573
|
+
//
|
|
1574
|
+
// No conflict → 0 chars. Conflict but no user preference → silent
|
|
1575
|
+
// default (Composio), still 0 chars. Only configured preferences cost
|
|
1576
|
+
// tokens, and only the affected services are listed (~50 chars each).
|
|
1577
|
+
// Compare to the previous hardcoded block which was ~700 chars on
|
|
1578
|
+
// every turn regardless.
|
|
1579
|
+
if (!isAutonomous) {
|
|
1580
|
+
try {
|
|
1581
|
+
const { loadToolPreferences, computeAvailability, buildPromptInstruction } = require('../integrations/tool-preferences.js');
|
|
1582
|
+
const { loadClaudeIntegrations } = require('./mcp-bridge.js');
|
|
1583
|
+
const composioSet = new Set(composioConnectedSlugs);
|
|
1584
|
+
const cdIntegrations = loadClaudeIntegrations();
|
|
1585
|
+
const cdActive = new Set(Object.values(cdIntegrations).filter(i => i.connected).map(i => i.name));
|
|
1586
|
+
const prefs = loadToolPreferences();
|
|
1587
|
+
const availability = computeAvailability(composioSet, cdActive, prefs.preferences);
|
|
1588
|
+
const instruction = buildPromptInstruction(availability, prefs.preferences);
|
|
1589
|
+
if (instruction)
|
|
1590
|
+
volatileParts.push(instruction);
|
|
1591
|
+
}
|
|
1592
|
+
catch { /* non-fatal — agent runs without the preference rule */ }
|
|
1587
1593
|
}
|
|
1588
1594
|
// Conversational context — same signals the insight engine surfaces
|
|
1589
1595
|
// proactively (Phase 10), but injected directly into the agent's prompt
|
package/dist/cli/dashboard.js
CHANGED
|
@@ -4570,6 +4570,66 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
4570
4570
|
res.status(500).json({ error: String(err) });
|
|
4571
4571
|
}
|
|
4572
4572
|
});
|
|
4573
|
+
// ── Tool source preferences ─────────────────────────────────
|
|
4574
|
+
// When a service is reachable from multiple MCP sources (Composio + Claude
|
|
4575
|
+
// Desktop), let the user pick which one. See src/integrations/tool-preferences.ts
|
|
4576
|
+
// for design notes.
|
|
4577
|
+
app.get('/api/tool-preferences', async (_req, res) => {
|
|
4578
|
+
try {
|
|
4579
|
+
const tp = await import('../integrations/tool-preferences.js');
|
|
4580
|
+
const composio = await import('../integrations/composio/client.js');
|
|
4581
|
+
const mcp = await import('../agent/mcp-bridge.js');
|
|
4582
|
+
const prefs = tp.loadToolPreferences();
|
|
4583
|
+
const composioSlugs = composio.isComposioEnabled()
|
|
4584
|
+
? new Set((await composio.listConnectedToolkits()).filter(c => c.status === 'ACTIVE').map(c => c.slug))
|
|
4585
|
+
: new Set();
|
|
4586
|
+
const cdActive = new Set(Object.values(mcp.loadClaudeIntegrations()).filter(i => i.connected).map(i => i.name));
|
|
4587
|
+
const availability = tp.computeAvailability(composioSlugs, cdActive, prefs.preferences);
|
|
4588
|
+
res.json({
|
|
4589
|
+
preferences: prefs.preferences,
|
|
4590
|
+
services: availability.map(a => ({
|
|
4591
|
+
id: a.service.id,
|
|
4592
|
+
label: a.service.label,
|
|
4593
|
+
composio: a.service.composioSlug
|
|
4594
|
+
? { slug: a.service.composioSlug, available: a.composioAvailable }
|
|
4595
|
+
: null,
|
|
4596
|
+
claudeDesktop: a.service.claudeDesktopName
|
|
4597
|
+
? { name: a.service.claudeDesktopName, available: a.claudeDesktopAvailable }
|
|
4598
|
+
: null,
|
|
4599
|
+
hasConflict: a.hasConflict,
|
|
4600
|
+
effective: a.effective,
|
|
4601
|
+
})),
|
|
4602
|
+
});
|
|
4603
|
+
}
|
|
4604
|
+
catch (err) {
|
|
4605
|
+
res.status(500).json({ error: String(err) });
|
|
4606
|
+
}
|
|
4607
|
+
});
|
|
4608
|
+
app.put('/api/tool-preferences', async (req, res) => {
|
|
4609
|
+
try {
|
|
4610
|
+
const body = req.body;
|
|
4611
|
+
const incoming = body?.preferences;
|
|
4612
|
+
if (!incoming || typeof incoming !== 'object') {
|
|
4613
|
+
res.status(400).json({ error: 'preferences (object) required in body' });
|
|
4614
|
+
return;
|
|
4615
|
+
}
|
|
4616
|
+
const tp = await import('../integrations/tool-preferences.js');
|
|
4617
|
+
const valid = {};
|
|
4618
|
+
const knownIds = new Set(tp.KNOWN_SERVICES.map(s => s.id));
|
|
4619
|
+
for (const [id, source] of Object.entries(incoming)) {
|
|
4620
|
+
if (!knownIds.has(id))
|
|
4621
|
+
continue;
|
|
4622
|
+
if (source === 'composio' || source === 'claude-desktop' || source === 'off') {
|
|
4623
|
+
valid[id] = source;
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
tp.saveToolPreferences({ preferences: valid });
|
|
4627
|
+
res.json({ ok: true, preferences: valid });
|
|
4628
|
+
}
|
|
4629
|
+
catch (err) {
|
|
4630
|
+
res.status(500).json({ error: String(err) });
|
|
4631
|
+
}
|
|
4632
|
+
});
|
|
4573
4633
|
// ── CRON CRUD routes ──────────────────────────────────────────
|
|
4574
4634
|
app.get('/api/projects', (_req, res) => {
|
|
4575
4635
|
try {
|
|
@@ -14546,6 +14606,15 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
14546
14606
|
<div class="empty-state">Loading...</div>
|
|
14547
14607
|
</div>
|
|
14548
14608
|
</div>
|
|
14609
|
+
<div class="card" style="margin-bottom:20px">
|
|
14610
|
+
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
|
|
14611
|
+
<span>Tool Source Preferences</span>
|
|
14612
|
+
<span style="font-size:11px;color:var(--text-muted)">Pick which source the agent uses when a service has multiple</span>
|
|
14613
|
+
</div>
|
|
14614
|
+
<div class="card-body" style="padding:16px" id="tool-preferences">
|
|
14615
|
+
<div class="empty-state">Loading...</div>
|
|
14616
|
+
</div>
|
|
14617
|
+
</div>
|
|
14549
14618
|
<div class="card" style="margin-bottom:20px">
|
|
14550
14619
|
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
|
|
14551
14620
|
<span>Claude Desktop Integrations</span>
|
|
@@ -15869,7 +15938,7 @@ function switchTab(group, tab) {
|
|
|
15869
15938
|
}
|
|
15870
15939
|
if (group === 'settings') {
|
|
15871
15940
|
if (tab === 'general' && typeof refreshSettings === 'function') refreshSettings();
|
|
15872
|
-
if (tab === 'integrations') { refreshSalesforce(); refreshComposioConnections(); }
|
|
15941
|
+
if (tab === 'integrations') { refreshSalesforce(); refreshComposioConnections(); refreshToolPreferences(); }
|
|
15873
15942
|
if (tab === 'remote') refreshRemoteAccess();
|
|
15874
15943
|
if (tab === 'security') refreshAuthSessions();
|
|
15875
15944
|
if (tab === 'projects' && typeof refreshProjects === 'function') refreshProjects();
|
|
@@ -25940,6 +26009,89 @@ function renderComposioCatalog(query) {
|
|
|
25940
26009
|
setTimeout(function() { var s = document.getElementById('composio-search'); if (s) { s.focus(); s.setSelectionRange(s.value.length, s.value.length); } }, 0);
|
|
25941
26010
|
}
|
|
25942
26011
|
|
|
26012
|
+
async function refreshToolPreferences() {
|
|
26013
|
+
var container = document.getElementById('tool-preferences');
|
|
26014
|
+
if (!container) return;
|
|
26015
|
+
try {
|
|
26016
|
+
var r = await apiFetch('/api/tool-preferences');
|
|
26017
|
+
var d = await r.json();
|
|
26018
|
+
var services = d.services || [];
|
|
26019
|
+
var prefs = d.preferences || {};
|
|
26020
|
+
// Seed the click-handler's working copy with what's currently saved,
|
|
26021
|
+
// so single-click edits merge instead of replacing the whole object.
|
|
26022
|
+
window._toolPrefs = Object.assign({}, prefs);
|
|
26023
|
+
|
|
26024
|
+
var conflicts = services.filter(function(s) { return s.hasConflict; });
|
|
26025
|
+
var single = services.filter(function(s) { return !s.hasConflict && s.effective; });
|
|
26026
|
+
var none = services.filter(function(s) { return !s.effective; });
|
|
26027
|
+
|
|
26028
|
+
var html = '';
|
|
26029
|
+
if (conflicts.length === 0 && single.length === 0) {
|
|
26030
|
+
html += '<div style="font-size:13px;color:var(--text-muted);line-height:1.6">No services connected yet from either Composio or Claude Desktop. Once you connect something, it\\'ll appear here.</div>';
|
|
26031
|
+
container.innerHTML = html;
|
|
26032
|
+
return;
|
|
26033
|
+
}
|
|
26034
|
+
|
|
26035
|
+
if (conflicts.length > 0) {
|
|
26036
|
+
html += '<div style="font-size:12px;color:var(--text-secondary);margin-bottom:12px;line-height:1.5">'
|
|
26037
|
+
+ '<strong>' + conflicts.length + ' service' + (conflicts.length === 1 ? ' has' : 's have') + ' multiple sources connected.</strong> '
|
|
26038
|
+
+ 'Pick which one the agent should use. Default (no selection) = Composio.'
|
|
26039
|
+
+ '</div>';
|
|
26040
|
+
html += '<div style="display:flex;flex-direction:column;gap:10px;margin-bottom:14px">';
|
|
26041
|
+
conflicts.forEach(function(s) {
|
|
26042
|
+
var picked = prefs[s.id] || 'composio';
|
|
26043
|
+
html += '<div style="display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid var(--border);border-radius:6px;background:var(--bg-secondary)">';
|
|
26044
|
+
html += '<div style="flex:1;font-size:13px;font-weight:500">' + esc(s.label) + '</div>';
|
|
26045
|
+
html += '<div style="display:flex;gap:6px">';
|
|
26046
|
+
['composio','claude-desktop','off'].forEach(function(opt) {
|
|
26047
|
+
var selected = picked === opt;
|
|
26048
|
+
var label = opt === 'composio' ? 'Composio' : (opt === 'claude-desktop' ? 'Claude Desktop' : 'Off');
|
|
26049
|
+
html += '<button onclick="setToolPreference(\\'' + esc(s.id) + '\\',\\'' + opt + '\\')" '
|
|
26050
|
+
+ 'style="padding:4px 10px;font-size:11px;border-radius:4px;cursor:pointer;border:1px solid '
|
|
26051
|
+
+ (selected ? 'var(--accent)' : 'var(--border)') + ';'
|
|
26052
|
+
+ 'background:' + (selected ? 'var(--accent)' : 'transparent') + ';'
|
|
26053
|
+
+ 'color:' + (selected ? '#fff' : 'var(--text-secondary)') + '">' + esc(label) + '</button>';
|
|
26054
|
+
});
|
|
26055
|
+
html += '</div>';
|
|
26056
|
+
html += '</div>';
|
|
26057
|
+
});
|
|
26058
|
+
html += '</div>';
|
|
26059
|
+
}
|
|
26060
|
+
|
|
26061
|
+
if (single.length > 0) {
|
|
26062
|
+
html += '<div style="font-size:11px;color:var(--text-muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:0.5px">Single source — using automatically</div>';
|
|
26063
|
+
html += '<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px">';
|
|
26064
|
+
single.forEach(function(s) {
|
|
26065
|
+
var srcLabel = s.effective === 'composio' ? 'Composio' : (s.effective === 'claude-desktop' ? 'Claude Desktop' : 'Off');
|
|
26066
|
+
html += '<span style="display:inline-flex;align-items:center;gap:6px;padding:4px 10px;font-size:11px;background:var(--bg-secondary);border:1px solid var(--border);border-radius:12px">'
|
|
26067
|
+
+ esc(s.label) + ' <span style="color:var(--text-muted);font-size:10px">via ' + esc(srcLabel) + '</span>'
|
|
26068
|
+
+ '</span>';
|
|
26069
|
+
});
|
|
26070
|
+
html += '</div>';
|
|
26071
|
+
}
|
|
26072
|
+
|
|
26073
|
+
container.innerHTML = html;
|
|
26074
|
+
} catch (e) {
|
|
26075
|
+
container.innerHTML = '<div class="empty-state" style="color:var(--red);padding:8px">Failed to load tool preferences: ' + esc(String(e)) + '</div>';
|
|
26076
|
+
}
|
|
26077
|
+
}
|
|
26078
|
+
|
|
26079
|
+
async function setToolPreference(serviceId, source) {
|
|
26080
|
+
try {
|
|
26081
|
+
// Get current prefs, merge in the change, send back. Server validates
|
|
26082
|
+
// each entry, so we don't need to pre-filter unknown IDs.
|
|
26083
|
+
var current = window._toolPrefs || {};
|
|
26084
|
+
current[serviceId] = source;
|
|
26085
|
+
window._toolPrefs = current;
|
|
26086
|
+
await apiFetch('/api/tool-preferences', {
|
|
26087
|
+
method: 'PUT',
|
|
26088
|
+
headers: { 'content-type': 'application/json' },
|
|
26089
|
+
body: JSON.stringify({ preferences: current }),
|
|
26090
|
+
});
|
|
26091
|
+
refreshToolPreferences();
|
|
26092
|
+
} catch (e) { toast('Failed to save preference: ' + e, 'error'); }
|
|
26093
|
+
}
|
|
26094
|
+
|
|
25943
26095
|
async function saveComposioApiKey() {
|
|
25944
26096
|
var input = document.getElementById('composio-key-input');
|
|
25945
26097
|
var status = document.getElementById('composio-key-status');
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool source preferences — when a service has tools available from
|
|
3
|
+
* multiple MCP sources (e.g., Composio's outlook AND Claude Desktop's
|
|
4
|
+
* Microsoft 365), let the user pick which one the agent should use.
|
|
5
|
+
*
|
|
6
|
+
* Storage: ~/.clementine/tool-preferences.json
|
|
7
|
+
*
|
|
8
|
+
* Design decisions:
|
|
9
|
+
* - Only services with multiple available sources show up. No noise.
|
|
10
|
+
* - When a conflict exists but the user hasn't picked, silently default
|
|
11
|
+
* to Composio (broader scope, OAuth tokens you control).
|
|
12
|
+
* - When no conflict exists (one or zero sources), no preference is
|
|
13
|
+
* needed and no system-prompt clutter is emitted.
|
|
14
|
+
* - The mapping between Composio slugs and Claude Desktop integration
|
|
15
|
+
* names lives here (small, bounded — only services where Claude
|
|
16
|
+
* Desktop has a connector).
|
|
17
|
+
*/
|
|
18
|
+
export type ToolSource = 'composio' | 'claude-desktop' | 'off';
|
|
19
|
+
/**
|
|
20
|
+
* Canonical service registry. Each entry maps a stable service ID to its
|
|
21
|
+
* Composio toolkit slug (if any) and Claude Desktop integration name (if
|
|
22
|
+
* any). Adding a new service = one line here.
|
|
23
|
+
*/
|
|
24
|
+
export interface ServiceDefinition {
|
|
25
|
+
/** Stable canonical ID — what the user sees and what we key prefs by. */
|
|
26
|
+
id: string;
|
|
27
|
+
/** Friendly name for the dashboard. */
|
|
28
|
+
label: string;
|
|
29
|
+
/** Composio toolkit slug, if Composio offers this service. */
|
|
30
|
+
composioSlug?: string;
|
|
31
|
+
/** Claude Desktop integration name (matches mcp__claude_ai_<name>__*). */
|
|
32
|
+
claudeDesktopName?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare const KNOWN_SERVICES: ServiceDefinition[];
|
|
35
|
+
export interface ToolPreferences {
|
|
36
|
+
version: 1;
|
|
37
|
+
/** id → chosen source. Missing = use silent default (composio when conflict). */
|
|
38
|
+
preferences: Record<string, ToolSource>;
|
|
39
|
+
updatedAt?: string;
|
|
40
|
+
}
|
|
41
|
+
export declare function loadToolPreferences(): ToolPreferences;
|
|
42
|
+
export declare function saveToolPreferences(prefs: Omit<ToolPreferences, 'version' | 'updatedAt'>): void;
|
|
43
|
+
export interface ServiceAvailability {
|
|
44
|
+
service: ServiceDefinition;
|
|
45
|
+
composioAvailable: boolean;
|
|
46
|
+
claudeDesktopAvailable: boolean;
|
|
47
|
+
/** True when both sources are connected — preference matters. */
|
|
48
|
+
hasConflict: boolean;
|
|
49
|
+
/** Effective source: user pref if set, else "composio" when conflict, else
|
|
50
|
+
* whichever single source is connected, else null. */
|
|
51
|
+
effective: ToolSource | null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Walk every known service and compute availability + effective preference.
|
|
55
|
+
* Pure function — caller passes in what's connected.
|
|
56
|
+
*/
|
|
57
|
+
export declare function computeAvailability(composioConnectedSlugs: Set<string>, claudeDesktopActiveNames: Set<string>, preferences: Record<string, ToolSource>): ServiceAvailability[];
|
|
58
|
+
/**
|
|
59
|
+
* Build the system-prompt instruction listing which tool source the agent
|
|
60
|
+
* should use for each service. Only includes services where:
|
|
61
|
+
* - There IS a conflict (both sources connected), AND
|
|
62
|
+
* - The user has explicitly picked a non-default preference, OR
|
|
63
|
+
* - The user picked 'off' (so we tell the agent NOT to use it)
|
|
64
|
+
*
|
|
65
|
+
* Returns empty string when no instruction is needed — that's the goal:
|
|
66
|
+
* silent default, zero prompt overhead, until the user actually configures.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildPromptInstruction(availability: ServiceAvailability[], preferences: Record<string, ToolSource>): string;
|
|
69
|
+
//# sourceMappingURL=tool-preferences.d.ts.map
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool source preferences — when a service has tools available from
|
|
3
|
+
* multiple MCP sources (e.g., Composio's outlook AND Claude Desktop's
|
|
4
|
+
* Microsoft 365), let the user pick which one the agent should use.
|
|
5
|
+
*
|
|
6
|
+
* Storage: ~/.clementine/tool-preferences.json
|
|
7
|
+
*
|
|
8
|
+
* Design decisions:
|
|
9
|
+
* - Only services with multiple available sources show up. No noise.
|
|
10
|
+
* - When a conflict exists but the user hasn't picked, silently default
|
|
11
|
+
* to Composio (broader scope, OAuth tokens you control).
|
|
12
|
+
* - When no conflict exists (one or zero sources), no preference is
|
|
13
|
+
* needed and no system-prompt clutter is emitted.
|
|
14
|
+
* - The mapping between Composio slugs and Claude Desktop integration
|
|
15
|
+
* names lives here (small, bounded — only services where Claude
|
|
16
|
+
* Desktop has a connector).
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { BASE_DIR } from '../config.js';
|
|
21
|
+
const PREFS_FILE = path.join(BASE_DIR, 'tool-preferences.json');
|
|
22
|
+
export const KNOWN_SERVICES = [
|
|
23
|
+
{ id: 'outlook', label: 'Outlook / Microsoft 365', composioSlug: 'outlook', claudeDesktopName: 'Microsoft_365' },
|
|
24
|
+
{ id: 'gmail', label: 'Gmail', composioSlug: 'gmail', claudeDesktopName: 'Gmail' },
|
|
25
|
+
{ id: 'googledrive', label: 'Google Drive', composioSlug: 'googledrive', claudeDesktopName: 'Google_Drive' },
|
|
26
|
+
{ id: 'googlecalendar', label: 'Google Calendar', composioSlug: 'googlecalendar', claudeDesktopName: 'Google_Calendar' },
|
|
27
|
+
{ id: 'googlesheets', label: 'Google Sheets', composioSlug: 'googlesheets', claudeDesktopName: 'Google_Workspace' },
|
|
28
|
+
{ id: 'slack', label: 'Slack', composioSlug: 'slack', claudeDesktopName: 'Slack' },
|
|
29
|
+
{ id: 'notion', label: 'Notion', composioSlug: 'notion', claudeDesktopName: 'Notion' },
|
|
30
|
+
{ id: 'github', label: 'GitHub', composioSlug: 'github', claudeDesktopName: 'GitHub' },
|
|
31
|
+
{ id: 'linear', label: 'Linear', composioSlug: 'linear', claudeDesktopName: 'Linear' },
|
|
32
|
+
];
|
|
33
|
+
const EMPTY_PREFS = { version: 1, preferences: {} };
|
|
34
|
+
export function loadToolPreferences() {
|
|
35
|
+
try {
|
|
36
|
+
if (!existsSync(PREFS_FILE))
|
|
37
|
+
return { ...EMPTY_PREFS, preferences: {} };
|
|
38
|
+
const data = JSON.parse(readFileSync(PREFS_FILE, 'utf-8'));
|
|
39
|
+
if (data?.version !== 1 || typeof data.preferences !== 'object') {
|
|
40
|
+
return { ...EMPTY_PREFS, preferences: {} };
|
|
41
|
+
}
|
|
42
|
+
return data;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return { ...EMPTY_PREFS, preferences: {} };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function saveToolPreferences(prefs) {
|
|
49
|
+
const out = {
|
|
50
|
+
version: 1,
|
|
51
|
+
preferences: prefs.preferences,
|
|
52
|
+
updatedAt: new Date().toISOString(),
|
|
53
|
+
};
|
|
54
|
+
writeFileSync(PREFS_FILE, JSON.stringify(out, null, 2), { mode: 0o600 });
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Walk every known service and compute availability + effective preference.
|
|
58
|
+
* Pure function — caller passes in what's connected.
|
|
59
|
+
*/
|
|
60
|
+
export function computeAvailability(composioConnectedSlugs, claudeDesktopActiveNames, preferences) {
|
|
61
|
+
return KNOWN_SERVICES.map(service => {
|
|
62
|
+
const composioAvailable = !!service.composioSlug && composioConnectedSlugs.has(service.composioSlug);
|
|
63
|
+
const claudeDesktopAvailable = !!service.claudeDesktopName && claudeDesktopActiveNames.has(service.claudeDesktopName);
|
|
64
|
+
const hasConflict = composioAvailable && claudeDesktopAvailable;
|
|
65
|
+
let effective = null;
|
|
66
|
+
const userPref = preferences[service.id];
|
|
67
|
+
if (userPref === 'off') {
|
|
68
|
+
effective = 'off';
|
|
69
|
+
}
|
|
70
|
+
else if (hasConflict) {
|
|
71
|
+
effective = userPref ?? 'composio'; // default to composio when conflict + no pref
|
|
72
|
+
}
|
|
73
|
+
else if (composioAvailable) {
|
|
74
|
+
effective = 'composio';
|
|
75
|
+
}
|
|
76
|
+
else if (claudeDesktopAvailable) {
|
|
77
|
+
effective = 'claude-desktop';
|
|
78
|
+
}
|
|
79
|
+
return { service, composioAvailable, claudeDesktopAvailable, hasConflict, effective };
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Build the system-prompt instruction listing which tool source the agent
|
|
84
|
+
* should use for each service. Only includes services where:
|
|
85
|
+
* - There IS a conflict (both sources connected), AND
|
|
86
|
+
* - The user has explicitly picked a non-default preference, OR
|
|
87
|
+
* - The user picked 'off' (so we tell the agent NOT to use it)
|
|
88
|
+
*
|
|
89
|
+
* Returns empty string when no instruction is needed — that's the goal:
|
|
90
|
+
* silent default, zero prompt overhead, until the user actually configures.
|
|
91
|
+
*/
|
|
92
|
+
export function buildPromptInstruction(availability, preferences) {
|
|
93
|
+
const lines = [];
|
|
94
|
+
for (const a of availability) {
|
|
95
|
+
if (!a.hasConflict)
|
|
96
|
+
continue;
|
|
97
|
+
const userPref = preferences[a.service.id];
|
|
98
|
+
if (!userPref)
|
|
99
|
+
continue; // no explicit pref → silent default, no prompt cost
|
|
100
|
+
if (userPref === 'off') {
|
|
101
|
+
lines.push(`- ${a.service.label}: do NOT use any of its tools (user disabled)`);
|
|
102
|
+
}
|
|
103
|
+
else if (userPref === 'composio' && a.service.composioSlug) {
|
|
104
|
+
lines.push(`- ${a.service.label}: use \`mcp__${a.service.composioSlug}__*\` (NOT \`mcp__claude_ai_${a.service.claudeDesktopName}__*\`)`);
|
|
105
|
+
}
|
|
106
|
+
else if (userPref === 'claude-desktop' && a.service.claudeDesktopName) {
|
|
107
|
+
lines.push(`- ${a.service.label}: use \`mcp__claude_ai_${a.service.claudeDesktopName}__*\` (NOT \`mcp__${a.service.composioSlug}__*\`)`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (lines.length === 0)
|
|
111
|
+
return '';
|
|
112
|
+
return `## Tool Source Preferences\n\n${lines.join('\n')}`;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=tool-preferences.js.map
|