clementine-agent 1.18.12 → 1.18.13
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.
|
@@ -126,6 +126,15 @@ export const TOOL_BUNDLES = [
|
|
|
126
126
|
function uniqueStrings(values) {
|
|
127
127
|
return [...new Set([...values].filter((v) => !!v && v.trim().length > 0))];
|
|
128
128
|
}
|
|
129
|
+
function explicitMcpServers(scopeText) {
|
|
130
|
+
const servers = new Set();
|
|
131
|
+
const re = /\bmcp__([A-Za-z0-9_-]+)__[A-Za-z0-9_.:-]+\b/g;
|
|
132
|
+
let match;
|
|
133
|
+
while ((match = re.exec(scopeText)) !== null) {
|
|
134
|
+
servers.add(match[1]);
|
|
135
|
+
}
|
|
136
|
+
return uniqueStrings(servers);
|
|
137
|
+
}
|
|
129
138
|
export function routeToolSurface(text) {
|
|
130
139
|
const scopeText = text?.trim() ?? '';
|
|
131
140
|
if (!scopeText) {
|
|
@@ -162,13 +171,26 @@ export function routeToolSurface(text) {
|
|
|
162
171
|
composio.add(slug);
|
|
163
172
|
inheritFullClaudeEnv = inheritFullClaudeEnv || bundle.inheritFullClaudeEnv === true;
|
|
164
173
|
}
|
|
174
|
+
for (const server of explicitMcpServers(scopeText)) {
|
|
175
|
+
if (server.startsWith('claude_ai_')) {
|
|
176
|
+
external.add(server.slice('claude_ai_'.length));
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// Exact `mcp__<server>__<tool>` mentions are authoritative. Add the
|
|
180
|
+
// name as both a direct MCP server and a Composio toolkit; whichever
|
|
181
|
+
// source is actually connected will mount, and the other path no-ops.
|
|
182
|
+
external.add(server);
|
|
183
|
+
composio.add(server);
|
|
184
|
+
}
|
|
185
|
+
inheritFullClaudeEnv = true;
|
|
186
|
+
}
|
|
165
187
|
return {
|
|
166
188
|
bundles: uniqueStrings(bundles),
|
|
167
189
|
externalMcpServers: uniqueStrings(external),
|
|
168
190
|
composioToolkits: uniqueStrings(composio),
|
|
169
191
|
inheritFullClaudeEnv,
|
|
170
192
|
fullSurface: false,
|
|
171
|
-
reason: bundles.size > 0 ? 'matched' : 'empty',
|
|
193
|
+
reason: bundles.size > 0 || external.size > 0 || composio.size > 0 ? 'matched' : 'empty',
|
|
172
194
|
};
|
|
173
195
|
}
|
|
174
196
|
//# sourceMappingURL=tool-router.js.map
|
|
@@ -59,7 +59,7 @@ export interface ConnectorRecipe {
|
|
|
59
59
|
description: string;
|
|
60
60
|
/** Emoji shown next to the label. */
|
|
61
61
|
icon: string;
|
|
62
|
-
/** Matches the
|
|
62
|
+
/** Matches the tool source name; "*" recipes are offered for every source. */
|
|
63
63
|
integration: string;
|
|
64
64
|
/** Tools we rely on for this recipe. Used only to warn if the integration
|
|
65
65
|
* hasn't surfaced them yet in claude-integrations.json. */
|
|
@@ -25,6 +25,10 @@ function slugify(s) {
|
|
|
25
25
|
.replace(/^-+|-+$/g, '')
|
|
26
26
|
.slice(0, 40) || 'feed';
|
|
27
27
|
}
|
|
28
|
+
function inferToolServer(toolName) {
|
|
29
|
+
const match = String(toolName).match(/^mcp__([^_]+(?:_[^_]+)*)__/);
|
|
30
|
+
return match?.[1] ?? 'tool';
|
|
31
|
+
}
|
|
28
32
|
const COMMIT_INSTRUCTIONS = `When you have the records collected, call the \`brain_ingest_folder\` MCP tool with:
|
|
29
33
|
- \`slug\`: "{{slug}}"
|
|
30
34
|
- \`records\`: an array of \`{title, externalId, content, metadata}\` objects (one per item). \`externalId\` should be the source provider's stable id so re-runs dedup. \`metadata\` can include any fields you want preserved (url, modifiedAt, author).
|
|
@@ -35,6 +39,102 @@ If the tool returns an error, include the error text in your summary.`;
|
|
|
35
39
|
const MEMORY_DELTA_INSTRUCTIONS = `Before committing, call \`memory_recall\` for the feed slug/topic and use the returned chunks as the current memory state for this source. Keep records that are new, materially changed, or contain a new finding. Drop exact duplicates and rows that add no useful information. The ingestion pipeline will write markdown and embeddings; do not call \`memory_write\` for these feed records.`;
|
|
36
40
|
// ── Recipes ────────────────────────────────────────────────────────────
|
|
37
41
|
export const RECIPES = [
|
|
42
|
+
{
|
|
43
|
+
id: 'tool-backed-memory-seed',
|
|
44
|
+
label: 'Any tool: call and seed memory',
|
|
45
|
+
description: 'Call a selected tool from this connector, compare results with current memory, and ingest new or changed findings.',
|
|
46
|
+
icon: '🔌',
|
|
47
|
+
integration: '*',
|
|
48
|
+
requiredTools: [],
|
|
49
|
+
fields: [
|
|
50
|
+
{
|
|
51
|
+
key: 'topic',
|
|
52
|
+
label: 'Memory topic',
|
|
53
|
+
placeholder: 'customers, calls, leads, deals, meetings...',
|
|
54
|
+
required: true,
|
|
55
|
+
help: 'Used for recall, deduping, and the generated feed slug.',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
key: 'toolName',
|
|
59
|
+
label: 'Tool to call',
|
|
60
|
+
required: true,
|
|
61
|
+
help: 'Pick the exact tool this feed should call when it runs.',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: 'callGoal',
|
|
65
|
+
label: 'What to pull',
|
|
66
|
+
placeholder: 'Fetch updated HubSpot contacts modified since the last run...',
|
|
67
|
+
required: true,
|
|
68
|
+
help: 'Describe the records to fetch, filters to apply, and any pagination bounds.',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
key: 'variablesJson',
|
|
72
|
+
label: 'Variables JSON',
|
|
73
|
+
placeholder: '{"listId":"123","limit":100,"updatedAfter":"last_run"}',
|
|
74
|
+
help: 'Optional arguments, IDs, ranges, filters, or query variables the tool should use.',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
key: 'recordStrategy',
|
|
78
|
+
label: 'Record strategy',
|
|
79
|
+
placeholder: 'One record per contact. Use email as stable id. Summarize lifecycle stage, owner, last activity, and new changes.',
|
|
80
|
+
help: 'Tell the agent how to convert the tool output into memory records.',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
key: 'slug',
|
|
84
|
+
label: 'Slug override',
|
|
85
|
+
placeholder: 'hubspot-contacts',
|
|
86
|
+
help: 'Optional. Leave blank to derive one from the connector and topic.',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
key: 'limit',
|
|
90
|
+
label: 'Max records per run',
|
|
91
|
+
placeholder: '100',
|
|
92
|
+
defaultValue: '100',
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
defaultSchedule: '0 8 * * *',
|
|
96
|
+
tier: 2,
|
|
97
|
+
slugFromValues: (v) => `tool-${slugify(v.slug || `${v.toolSourceName || inferToolServer(v.toolName || '')}-${v.topic || v.toolName || 'feed'}`)}`,
|
|
98
|
+
buildPrompt: (v, ctx) => {
|
|
99
|
+
const sourceName = v.toolSourceName || inferToolServer(v.toolName || '');
|
|
100
|
+
const sourceKind = v.toolSourceKind || 'mcp';
|
|
101
|
+
const sourceLabel = v.toolSourceLabel || sourceName;
|
|
102
|
+
const topic = v.topic || 'tool-backed memory';
|
|
103
|
+
const limit = v.limit || '100';
|
|
104
|
+
return `You are running a generic tool-backed memory seed feed.
|
|
105
|
+
|
|
106
|
+
Tool source:
|
|
107
|
+
- Label: "${sourceLabel}"
|
|
108
|
+
- Source name: "${sourceName}"
|
|
109
|
+
- Source kind: "${sourceKind}"
|
|
110
|
+
- Tool: \`${v.toolName}\`
|
|
111
|
+
|
|
112
|
+
Goal: ${v.callGoal || `Call ${v.toolName} and ingest useful returned data into memory.`}
|
|
113
|
+
|
|
114
|
+
Variables JSON:
|
|
115
|
+
\`\`\`json
|
|
116
|
+
${(v.variablesJson || '{}').trim() || '{}'}
|
|
117
|
+
\`\`\`
|
|
118
|
+
|
|
119
|
+
Record strategy:
|
|
120
|
+
${v.recordStrategy || 'Convert the tool response into one memory record per returned entity or event. Use the provider stable id when available; otherwise use a deterministic hash of the source, topic, and meaningful record key.'}
|
|
121
|
+
|
|
122
|
+
Steps:
|
|
123
|
+
1. Call exactly this selected tool: \`${v.toolName}\`. Use the Variables JSON and the Goal above as the tool-call inputs. If the tool schema needs differently named arguments, map the provided variables to that schema. Do not switch to a different external tool unless this tool returns a clear instruction that another tool is required to read the selected records.
|
|
124
|
+
2. If the tool supports pagination or modified-since filters, prefer new/updated records and stop after ${limit} records. If no modified-since filter is available, fetch the most relevant ${limit} records.
|
|
125
|
+
3. Normalize the tool result into candidate records. Preserve stable ids, URLs, timestamps, owners/authors, status fields, and provider metadata. Skip empty or purely administrative records.
|
|
126
|
+
4. ${MEMORY_DELTA_INSTRUCTIONS}
|
|
127
|
+
Use this recall query: \`source:${ctx.slug} ${topic} ${sourceLabel} ${v.toolName}\`.
|
|
128
|
+
5. Compare the normalized candidates with recalled memory. Keep only candidates that are new, materially changed, or produce a new useful finding. Drop exact duplicates and trivial timestamp-only changes unless the timestamp itself is the useful fact.
|
|
129
|
+
6. For each kept candidate, build one record:
|
|
130
|
+
- \`title\`: a compact human label including the topic and record name/id.
|
|
131
|
+
- \`externalId\`: \`${sourceName}:${topic}:<providerStableIdOrDeterministicHash>\`.
|
|
132
|
+
- \`content\`: markdown containing the current facts, the new/changed finding, and a "Source data" section with relevant returned fields.
|
|
133
|
+
- \`metadata\`: \`{provider:"${sourceName}", toolSource:"${sourceKind}", toolName:"${v.toolName}", topic:"${topic}", fetchedAt, sourceUrl, updatedAt}\` plus any provider-specific keys worth preserving.
|
|
134
|
+
7. ${COMMIT_INSTRUCTIONS.replace(/{{slug}}/g, ctx.slug).replace(/{{targetFolder}}/g, ctx.targetFolder)}
|
|
135
|
+
`;
|
|
136
|
+
},
|
|
137
|
+
},
|
|
38
138
|
{
|
|
39
139
|
id: 'gdrive-watch-folder',
|
|
40
140
|
label: 'Google Drive: watch a folder',
|
package/dist/cli/dashboard.js
CHANGED
|
@@ -3890,7 +3890,7 @@ export async function cmdDashboard(opts) {
|
|
|
3890
3890
|
const connected = await composio.listConnectedToolkits();
|
|
3891
3891
|
const activeSlugs = [...new Set(connected
|
|
3892
3892
|
.filter((c) => c.status === 'ACTIVE')
|
|
3893
|
-
.filter((c) => recipeIntegrations.has(c.slug))
|
|
3893
|
+
.filter((c) => recipeIntegrations.has('*') || recipeIntegrations.has(c.slug))
|
|
3894
3894
|
.map((c) => c.slug))];
|
|
3895
3895
|
if (activeSlugs.length) {
|
|
3896
3896
|
const { listComposioToolkitTools } = await import('../integrations/composio/mcp-bridge.js');
|
|
@@ -14115,6 +14115,11 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
14115
14115
|
for (const f of (s.recipe.fields || [])) {
|
|
14116
14116
|
if (f.defaultValue) s.values[f.key] = f.defaultValue;
|
|
14117
14117
|
}
|
|
14118
|
+
if (s.recipe.integration === '*' && s.pick) {
|
|
14119
|
+
s.values.toolSourceName = s.pick.name;
|
|
14120
|
+
s.values.toolSourceKind = s.pick.kind;
|
|
14121
|
+
s.values.toolSourceLabel = s.pick.label;
|
|
14122
|
+
}
|
|
14118
14123
|
s.schedule = s.recipe.defaultSchedule;
|
|
14119
14124
|
s.step = 2;
|
|
14120
14125
|
} else if (s.step === 2) {
|
|
@@ -14290,6 +14295,13 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
14290
14295
|
if (field) await brainRenderFieldPicker(field, s.values);
|
|
14291
14296
|
}
|
|
14292
14297
|
|
|
14298
|
+
function brainFullToolNameForPick(pick, tool) {
|
|
14299
|
+
if (!pick || !tool) return tool || '';
|
|
14300
|
+
if (String(tool).startsWith('mcp__')) return tool;
|
|
14301
|
+
const server = pick.kind === 'claude-desktop' ? ('claude_ai_' + pick.name) : pick.name;
|
|
14302
|
+
return 'mcp__' + server + '__' + tool;
|
|
14303
|
+
}
|
|
14304
|
+
|
|
14293
14305
|
function brainFeedWizardRender() {
|
|
14294
14306
|
if (!brainFeedWizardState) return;
|
|
14295
14307
|
const s = brainFeedWizardState;
|
|
@@ -14317,7 +14329,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
14317
14329
|
}).join('') + '</div>';
|
|
14318
14330
|
}
|
|
14319
14331
|
} else if (s.step === 1) {
|
|
14320
|
-
const recipes = (s.catalog.recipes || []).filter(function(r) { return r.integration === s.pick.name; });
|
|
14332
|
+
const recipes = (s.catalog.recipes || []).filter(function(r) { return r.integration === s.pick.name || r.integration === '*'; });
|
|
14321
14333
|
if (!recipes.length) {
|
|
14322
14334
|
html = '<div style="color:var(--muted)">No recipes for this connector yet.</div>';
|
|
14323
14335
|
} else {
|
|
@@ -14346,6 +14358,25 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
14346
14358
|
'<div style="color:var(--muted);font-size:13px;padding:6px">Loading choices…</div>' +
|
|
14347
14359
|
'</div>' +
|
|
14348
14360
|
'<input type="hidden" data-field="' + f.key + '" value="' + escapeHtml(val) + '">';
|
|
14361
|
+
} else if (s.recipe.integration === '*' && f.key === 'toolName') {
|
|
14362
|
+
const tools = (s.pick && s.pick.tools) || [];
|
|
14363
|
+
if (!tools.length) {
|
|
14364
|
+
control = '<input type="text" data-field="' + f.key + '" value="' + escapeHtml(val) + '" placeholder="mcp__server__TOOL_NAME" style="width:100%">';
|
|
14365
|
+
} else {
|
|
14366
|
+
const options = tools.map(function(t) {
|
|
14367
|
+
const full = brainFullToolNameForPick(s.pick, t);
|
|
14368
|
+
const selected = full === val ? ' selected' : '';
|
|
14369
|
+
return '<option value="' + escapeHtml(full) + '"' + selected + '>' + escapeHtml(t) + '</option>';
|
|
14370
|
+
}).join('');
|
|
14371
|
+
control = '<select data-field="' + f.key + '" style="width:100%;padding:6px">' +
|
|
14372
|
+
'<option value="">— pick a tool —</option>' +
|
|
14373
|
+
options +
|
|
14374
|
+
'</select>' +
|
|
14375
|
+
'<div style="font-size:11px;color:var(--muted);margin-top:4px">The feed will call the selected tool exactly, then compare returned records with memory.</div>';
|
|
14376
|
+
}
|
|
14377
|
+
} else if (s.recipe.integration === '*' && ['callGoal', 'variablesJson', 'recordStrategy'].includes(f.key)) {
|
|
14378
|
+
const minHeight = f.key === 'variablesJson' ? '70px' : '92px';
|
|
14379
|
+
control = '<textarea data-field="' + f.key + '" placeholder="' + escapeHtml(f.placeholder || '') + '" style="width:100%;min-height:' + minHeight + ';resize:vertical">' + escapeHtml(val) + '</textarea>';
|
|
14349
14380
|
} else {
|
|
14350
14381
|
control = '<input type="text" data-field="' + f.key + '" value="' + escapeHtml(val) + '" placeholder="' + escapeHtml(f.placeholder || '') + '" style="width:100%">';
|
|
14351
14382
|
}
|
|
@@ -14387,6 +14418,11 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
14387
14418
|
function brainFeedWizardPickRecipe(id) {
|
|
14388
14419
|
const r = (brainFeedWizardState.catalog.recipes || []).find(function(x) { return x.id === id; });
|
|
14389
14420
|
brainFeedWizardState.recipe = r;
|
|
14421
|
+
if (r && r.integration === '*' && brainFeedWizardState.pick) {
|
|
14422
|
+
brainFeedWizardState.values.toolSourceName = brainFeedWizardState.pick.name;
|
|
14423
|
+
brainFeedWizardState.values.toolSourceKind = brainFeedWizardState.pick.kind;
|
|
14424
|
+
brainFeedWizardState.values.toolSourceLabel = brainFeedWizardState.pick.label;
|
|
14425
|
+
}
|
|
14390
14426
|
brainFeedWizardRender();
|
|
14391
14427
|
}
|
|
14392
14428
|
|