clementine-agent 1.18.13 → 1.18.15
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/README.md +5 -3
- package/dist/agent/assistant.js +30 -20
- package/dist/brain/adapters/markdown.js +6 -1
- package/dist/brain/connector-recipes.d.ts +4 -4
- package/dist/brain/connector-recipes.js +19 -19
- package/dist/cli/dashboard.js +341 -10
- package/dist/cli/index.js +67 -15
- package/dist/config/config-doctor.js +20 -5
- package/dist/config/effective-config.js +2 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.js +78 -25
- package/dist/tools/brain-tools.d.ts +26 -2
- package/dist/tools/brain-tools.js +97 -72
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -328,8 +328,9 @@ For spend/context tuning, `clementine budgets` gives a safer shortcut:
|
|
|
328
328
|
|
|
329
329
|
```bash
|
|
330
330
|
clementine budgets # show chat/cron/heartbeat caps and 1M context state
|
|
331
|
-
clementine budgets safe # lower background budgets and
|
|
332
|
-
clementine budgets 1m
|
|
331
|
+
clementine budgets safe # lower background budgets and force standard 200K context
|
|
332
|
+
clementine budgets 1m auto # allow included Opus 1M, keep Sonnet on 200K
|
|
333
|
+
clementine budgets 1m on # force 1M context for Extra Usage/API users
|
|
333
334
|
clementine budgets 1m off # disable 1M context for maximum compatibility
|
|
334
335
|
clementine budgets set chat 10 # raise one budget cap
|
|
335
336
|
```
|
|
@@ -342,7 +343,8 @@ clementine budgets set chat 10 # raise one budget cap
|
|
|
342
343
|
| `BUDGET_CRON_T1_USD` | `0.75` | Max spend per tier-1 cron job |
|
|
343
344
|
| `BUDGET_CRON_T2_USD` | `1.50` | Max spend per tier-2 cron job |
|
|
344
345
|
| `BUDGET_HEARTBEAT_USD` | `0.25` | Max spend per heartbeat tick |
|
|
345
|
-
| `
|
|
346
|
+
| `CLEMENTINE_1M_CONTEXT_MODE` | `auto` | `auto` allows included Opus 1M on Max/Team/Enterprise while keeping Sonnet on 200K; `off` forces 200K; `on` forces 1M |
|
|
347
|
+
| `CLAUDE_CODE_DISABLE_1M_CONTEXT` | legacy | Backward-compatible Claude Code switch; `budgets safe` writes `1`, `budgets 1m auto` removes it |
|
|
346
348
|
| `DEFAULT_MODEL_TIER` | `sonnet` | Default model: `haiku` / `sonnet` / `opus` |
|
|
347
349
|
| `HEARTBEAT_INTERVAL_MINUTES` | `30` | How often the agent auto-checks in |
|
|
348
350
|
| `HEARTBEAT_ACTIVE_START` | `8` | First hour of the active window (0–23) |
|
package/dist/agent/assistant.js
CHANGED
|
@@ -13,7 +13,7 @@ import fs from 'node:fs';
|
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { query as rawQuery, listSubagents, getSubagentMessages, SYSTEM_PROMPT_DYNAMIC_BOUNDARY, } from '@anthropic-ai/claude-agent-sdk';
|
|
15
15
|
import pino from 'pino';
|
|
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, envSnapshot, } from '../config.js';
|
|
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, usesOneMillionContext, envSnapshot, } from '../config.js';
|
|
17
17
|
import { summarizeIntegrationStatus } from '../config/integrations-registry.js';
|
|
18
18
|
import { loadToolPreferences, computeAvailability, buildPromptInstruction, buildComposioStatusBlock, } from '../integrations/tool-preferences.js';
|
|
19
19
|
import { loadClaudeIntegrations } from './mcp-bridge.js';
|
|
@@ -320,6 +320,8 @@ const MODEL_CONTEXT_WINDOWS = {
|
|
|
320
320
|
'opus': 200_000,
|
|
321
321
|
};
|
|
322
322
|
function getContextWindow(model) {
|
|
323
|
+
if (usesOneMillionContext(model))
|
|
324
|
+
return 1_000_000;
|
|
323
325
|
for (const [family, size] of Object.entries(MODEL_CONTEXT_WINDOWS)) {
|
|
324
326
|
if (model.includes(family))
|
|
325
327
|
return size;
|
|
@@ -338,10 +340,6 @@ function resultInputTokens(result) {
|
|
|
338
340
|
}
|
|
339
341
|
return total;
|
|
340
342
|
}
|
|
341
|
-
function oneMillionContextDisabled() {
|
|
342
|
-
const value = process.env.CLAUDE_CODE_DISABLE_1M_CONTEXT;
|
|
343
|
-
return value === undefined || !/^(0|false|no)$/i.test(value);
|
|
344
|
-
}
|
|
345
343
|
export function looksLikeOneMillionContextError(value) {
|
|
346
344
|
const text = String(value ?? '');
|
|
347
345
|
return /extra usage.*1m context|1m context.*extra usage|context-1m/i.test(text);
|
|
@@ -537,12 +535,6 @@ function buildSafeEnv() {
|
|
|
537
535
|
sanitized.ANTHROPIC_API_KEY = apiKeyVal;
|
|
538
536
|
}
|
|
539
537
|
// When all are absent: HOME lets the subprocess find Keychain OAuth automatically.
|
|
540
|
-
// Preserve trusted Claude Code runtime flags set by config.ts. In
|
|
541
|
-
// particular, CLAUDE_CODE_DISABLE_1M_CONTEXT defaults on so background
|
|
542
|
-
// helper queries do not silently re-enable the 1M context beta.
|
|
543
|
-
if (process.env.CLAUDE_CODE_DISABLE_1M_CONTEXT !== undefined) {
|
|
544
|
-
sanitized.CLAUDE_CODE_DISABLE_1M_CONTEXT = process.env.CLAUDE_CODE_DISABLE_1M_CONTEXT;
|
|
545
|
-
}
|
|
546
538
|
// Step 3: Add trusted markers AFTER sanitization
|
|
547
539
|
sanitized.CLEMENTINE_HOME = BASE_DIR;
|
|
548
540
|
return sanitized;
|
|
@@ -2148,7 +2140,10 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2148
2140
|
&& toolsDisabledForCall
|
|
2149
2141
|
&& turnPolicy?.retrievalTier === 'none'
|
|
2150
2142
|
&& turnPolicy.effort === 'low';
|
|
2151
|
-
const
|
|
2143
|
+
const rawResolvedModel = resolveModel(requestedModel) ?? (lightweightModelEligible ? MODELS.haiku : MODEL);
|
|
2144
|
+
const resolvedModel = normalizeClaudeModelForOneMillionContext(rawResolvedModel);
|
|
2145
|
+
const oneMillionModeValue = currentOneMillionContextMode();
|
|
2146
|
+
const oneMillionDisableValue = claudeCodeDisableOneMillionForModel(resolvedModel);
|
|
2152
2147
|
const modelRouteReason = model
|
|
2153
2148
|
? 'explicit'
|
|
2154
2149
|
: profile?.model
|
|
@@ -2421,7 +2416,7 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2421
2416
|
systemPrompt: fullSystemPrompt,
|
|
2422
2417
|
model: resolvedModel,
|
|
2423
2418
|
...(fallback ? { fallbackModel: fallback } : {}),
|
|
2424
|
-
...(
|
|
2419
|
+
...(oneMillionDisableValue === '1' ? { betas: [] } : {}),
|
|
2425
2420
|
permissionMode: effectivePermissionMode,
|
|
2426
2421
|
allowDangerouslySkipPermissions: true,
|
|
2427
2422
|
...(sessionStore ? { sessionStore } : {}),
|
|
@@ -2459,6 +2454,10 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2459
2454
|
CLEMENTINE_TEAM_AGENT: profile?.slug ?? 'clementine',
|
|
2460
2455
|
CLEMENTINE_INTERACTION_SOURCE: sourceOverride ?? inferInteractionSource(sessionKey),
|
|
2461
2456
|
CLEMENTINE_TOOL_ALLOWLIST: clementineToolAllowlist,
|
|
2457
|
+
CLEMENTINE_1M_CONTEXT_MODE: oneMillionModeValue,
|
|
2458
|
+
...(oneMillionDisableValue !== undefined
|
|
2459
|
+
? { CLAUDE_CODE_DISABLE_1M_CONTEXT: oneMillionDisableValue }
|
|
2460
|
+
: {}),
|
|
2462
2461
|
},
|
|
2463
2462
|
},
|
|
2464
2463
|
...externalMcpServers,
|
|
@@ -2472,14 +2471,21 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2472
2471
|
// env only when the prompt/job mentions a connector-backed service.
|
|
2473
2472
|
// Per-MCP-server env isolation still happens inside each mcpServers
|
|
2474
2473
|
// entry; this only affects the Claude Code subprocess itself.
|
|
2475
|
-
|
|
2476
|
-
|
|
2474
|
+
env: shouldInheritClaudeEnv
|
|
2475
|
+
? {
|
|
2476
|
+
...process.env,
|
|
2477
|
+
CLEMENTINE_1M_CONTEXT_MODE: oneMillionModeValue,
|
|
2478
|
+
...(oneMillionDisableValue !== undefined
|
|
2479
|
+
? { CLAUDE_CODE_DISABLE_1M_CONTEXT: oneMillionDisableValue }
|
|
2480
|
+
: {}),
|
|
2481
|
+
}
|
|
2482
|
+
: {
|
|
2477
2483
|
...SAFE_ENV,
|
|
2478
|
-
|
|
2479
|
-
|
|
2484
|
+
CLEMENTINE_1M_CONTEXT_MODE: oneMillionModeValue,
|
|
2485
|
+
...(oneMillionDisableValue !== undefined
|
|
2486
|
+
? { CLAUDE_CODE_DISABLE_1M_CONTEXT: oneMillionDisableValue }
|
|
2480
2487
|
: {}),
|
|
2481
2488
|
},
|
|
2482
|
-
}),
|
|
2483
2489
|
// Avoid ambient Claude Code user/project/local settings and plugins by
|
|
2484
2490
|
// default. Those can silently attach hundreds of tools. Explicit MCP
|
|
2485
2491
|
// servers above still work; "all integrations/full tool surface" keeps
|
|
@@ -3421,13 +3427,14 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
3421
3427
|
responseText = responseText || ('Claude says the account credit balance is too low. I paused background jobs for a few hours so they stop draining/retrying, but interactive chat will also fail until credits are available again.');
|
|
3422
3428
|
}
|
|
3423
3429
|
else if (looksLikeOneMillionContextError(errorText)) {
|
|
3430
|
+
process.env.CLEMENTINE_1M_CONTEXT_MODE = 'off';
|
|
3424
3431
|
process.env.CLAUDE_CODE_DISABLE_1M_CONTEXT = '1';
|
|
3425
3432
|
if (sessionKey) {
|
|
3426
3433
|
this.sessions.delete(sessionKey);
|
|
3427
3434
|
this.exchangeCounts.set(sessionKey, 0);
|
|
3428
3435
|
this._compactedSessions.delete(sessionKey);
|
|
3429
3436
|
}
|
|
3430
|
-
responseText = responseText || ("Claude rejected
|
|
3437
|
+
responseText = responseText || ("Claude rejected 1M context for this account. I've switched this process to 200K recovery mode and reset the session. To persist the fix across restarts, run `clementine budgets safe`, then `clementine restart`.");
|
|
3431
3438
|
}
|
|
3432
3439
|
else if (lower.includes('rate') && lower.includes('limit')) {
|
|
3433
3440
|
hitRateLimit = true;
|
|
@@ -3552,13 +3559,14 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
3552
3559
|
responseText = responseText || ('Claude says the account credit balance is too low. I paused background jobs for a few hours so they stop draining/retrying, but interactive chat will also fail until credits are available again.');
|
|
3553
3560
|
}
|
|
3554
3561
|
else if (looksLikeOneMillionContextError(e)) {
|
|
3562
|
+
process.env.CLEMENTINE_1M_CONTEXT_MODE = 'off';
|
|
3555
3563
|
process.env.CLAUDE_CODE_DISABLE_1M_CONTEXT = '1';
|
|
3556
3564
|
if (sessionKey) {
|
|
3557
3565
|
this.sessions.delete(sessionKey);
|
|
3558
3566
|
this.exchangeCounts.set(sessionKey, 0);
|
|
3559
3567
|
this._compactedSessions.delete(sessionKey);
|
|
3560
3568
|
}
|
|
3561
|
-
responseText = responseText || ("Claude rejected
|
|
3569
|
+
responseText = responseText || ("Claude rejected 1M context for this account. I've switched this process to 200K recovery mode and reset the session. To persist the fix across restarts, run `clementine budgets safe`, then `clementine restart`.");
|
|
3562
3570
|
}
|
|
3563
3571
|
else if (errStr.includes('rate') && (errStr.includes('limit') || errStr.includes('rate_limit'))) {
|
|
3564
3572
|
hitRateLimit = true;
|
|
@@ -4814,6 +4822,7 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
4814
4822
|
throw new Error(errText);
|
|
4815
4823
|
}
|
|
4816
4824
|
if (looksLikeOneMillionContextError(errText)) {
|
|
4825
|
+
process.env.CLEMENTINE_1M_CONTEXT_MODE = 'off';
|
|
4817
4826
|
process.env.CLAUDE_CODE_DISABLE_1M_CONTEXT = '1';
|
|
4818
4827
|
throw new Error(errText);
|
|
4819
4828
|
}
|
|
@@ -5162,6 +5171,7 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
5162
5171
|
throw new Error(exitText);
|
|
5163
5172
|
}
|
|
5164
5173
|
if (looksLikeOneMillionContextError(exitText)) {
|
|
5174
|
+
process.env.CLEMENTINE_1M_CONTEXT_MODE = 'off';
|
|
5165
5175
|
process.env.CLAUDE_CODE_DISABLE_1M_CONTEXT = '1';
|
|
5166
5176
|
throw new Error(exitText);
|
|
5167
5177
|
}
|
|
@@ -33,8 +33,13 @@ export async function* parseMarkdown(filePath) {
|
|
|
33
33
|
mtime = statSync(filePath).mtime.toISOString();
|
|
34
34
|
}
|
|
35
35
|
catch { /* ignore */ }
|
|
36
|
+
const frontmatterExternalId = typeof parsed.data?.externalId === 'string' && parsed.data.externalId.trim()
|
|
37
|
+
? parsed.data.externalId.trim()
|
|
38
|
+
: typeof parsed.data?.external_id === 'string' && parsed.data.external_id.trim()
|
|
39
|
+
? parsed.data.external_id.trim()
|
|
40
|
+
: null;
|
|
36
41
|
yield {
|
|
37
|
-
externalId: `md-${hint}-${contentHash(body)}`,
|
|
42
|
+
externalId: frontmatterExternalId ?? `md-${hint}-${contentHash(body)}`,
|
|
38
43
|
content: body,
|
|
39
44
|
rawPayload: raw,
|
|
40
45
|
metadata: {
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Each recipe is a blueprint for a one-click "auto-seed feed" that turns an
|
|
5
5
|
* authenticated tool source (Claude Desktop connector, Composio toolkit, or
|
|
6
|
-
* local MCP server) into a scheduled data feed that writes
|
|
7
|
-
* ingest folder.
|
|
6
|
+
* local MCP server) into a scheduled data feed that writes distilled notes
|
|
7
|
+
* into the brain's ingest folder.
|
|
8
8
|
*
|
|
9
9
|
* A feed materializes as:
|
|
10
10
|
* 1. A CRON.md job entry with `managed: connector-feed` frontmatter
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
*
|
|
13
13
|
* The cron prompt tells the Claude Code agent to use the integration's MCP
|
|
14
14
|
* tools to pull records, compare them with current memory when appropriate,
|
|
15
|
-
* then call `brain_ingest_folder` to commit them — which writes
|
|
16
|
-
*
|
|
15
|
+
* then call `brain_ingest_folder` to commit them — which writes distilled
|
|
16
|
+
* markdown notes and indexes them in one step.
|
|
17
17
|
*
|
|
18
18
|
* Field syntax in prompt templates:
|
|
19
19
|
* {{fieldKey}} — user-supplied value
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Each recipe is a blueprint for a one-click "auto-seed feed" that turns an
|
|
5
5
|
* authenticated tool source (Claude Desktop connector, Composio toolkit, or
|
|
6
|
-
* local MCP server) into a scheduled data feed that writes
|
|
7
|
-
* ingest folder.
|
|
6
|
+
* local MCP server) into a scheduled data feed that writes distilled notes
|
|
7
|
+
* into the brain's ingest folder.
|
|
8
8
|
*
|
|
9
9
|
* A feed materializes as:
|
|
10
10
|
* 1. A CRON.md job entry with `managed: connector-feed` frontmatter
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
*
|
|
13
13
|
* The cron prompt tells the Claude Code agent to use the integration's MCP
|
|
14
14
|
* tools to pull records, compare them with current memory when appropriate,
|
|
15
|
-
* then call `brain_ingest_folder` to commit them — which writes
|
|
16
|
-
*
|
|
15
|
+
* then call `brain_ingest_folder` to commit them — which writes distilled
|
|
16
|
+
* markdown notes and indexes them in one step.
|
|
17
17
|
*
|
|
18
18
|
* Field syntax in prompt templates:
|
|
19
19
|
* {{fieldKey}} — user-supplied value
|
|
@@ -33,16 +33,16 @@ const COMMIT_INSTRUCTIONS = `When you have the records collected, call the \`bra
|
|
|
33
33
|
- \`slug\`: "{{slug}}"
|
|
34
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
35
|
|
|
36
|
-
That tool writes
|
|
36
|
+
That tool runs the brain's distillation pipeline and writes the final notes to \`{{targetFolder}}/\`. You do NOT need to use Write — brain_ingest_folder handles note creation and indexing. Finish by reporting a one-line summary like "Ingested N new records, M unchanged".
|
|
37
37
|
|
|
38
38
|
If the tool returns an error, include the error text in your summary.`;
|
|
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
|
|
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, chunk it, and index it for recall; do not call \`memory_write\` for these feed records.`;
|
|
40
40
|
// ── Recipes ────────────────────────────────────────────────────────────
|
|
41
41
|
export const RECIPES = [
|
|
42
42
|
{
|
|
43
43
|
id: 'tool-backed-memory-seed',
|
|
44
|
-
label: '
|
|
45
|
-
description: '
|
|
44
|
+
label: 'Seed memory from this tool',
|
|
45
|
+
description: 'Pick one tool, fetch records from it, compare them with current memory, and save only new or changed findings.',
|
|
46
46
|
icon: '🔌',
|
|
47
47
|
integration: '*',
|
|
48
48
|
requiredTools: [],
|
|
@@ -52,36 +52,36 @@ export const RECIPES = [
|
|
|
52
52
|
label: 'Memory topic',
|
|
53
53
|
placeholder: 'customers, calls, leads, deals, meetings...',
|
|
54
54
|
required: true,
|
|
55
|
-
help: 'Used
|
|
55
|
+
help: 'Used to search current memory and name this feed.',
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
key: 'toolName',
|
|
59
59
|
label: 'Tool to call',
|
|
60
60
|
required: true,
|
|
61
|
-
help: 'Pick the exact tool this feed should call
|
|
61
|
+
help: 'Pick the exact tool this feed should call each time it runs.',
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
key: 'callGoal',
|
|
65
|
-
label: 'What
|
|
65
|
+
label: 'What should Clementine fetch?',
|
|
66
66
|
placeholder: 'Fetch updated HubSpot contacts modified since the last run...',
|
|
67
67
|
required: true,
|
|
68
68
|
help: 'Describe the records to fetch, filters to apply, and any pagination bounds.',
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
key: 'variablesJson',
|
|
72
|
-
label: '
|
|
72
|
+
label: 'Tool variables (JSON)',
|
|
73
73
|
placeholder: '{"listId":"123","limit":100,"updatedAfter":"last_run"}',
|
|
74
|
-
help: 'Optional
|
|
74
|
+
help: 'Optional. Use {} if the tool needs no arguments.',
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
key: 'recordStrategy',
|
|
78
|
-
label: '
|
|
78
|
+
label: 'How to save each result',
|
|
79
79
|
placeholder: 'One record per contact. Use email as stable id. Summarize lifecycle stage, owner, last activity, and new changes.',
|
|
80
|
-
help: 'Tell
|
|
80
|
+
help: 'Tell Clementine what counts as one memory record and which field is the stable id.',
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
key: 'slug',
|
|
84
|
-
label: '
|
|
84
|
+
label: 'Memory bucket name (optional)',
|
|
85
85
|
placeholder: 'hubspot-contacts',
|
|
86
86
|
help: 'Optional. Leave blank to derive one from the connector and topic.',
|
|
87
87
|
},
|
|
@@ -111,16 +111,16 @@ Tool source:
|
|
|
111
111
|
|
|
112
112
|
Goal: ${v.callGoal || `Call ${v.toolName} and ingest useful returned data into memory.`}
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
Tool variables JSON:
|
|
115
115
|
\`\`\`json
|
|
116
116
|
${(v.variablesJson || '{}').trim() || '{}'}
|
|
117
117
|
\`\`\`
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
How to save each result:
|
|
120
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
121
|
|
|
122
122
|
Steps:
|
|
123
|
-
1. Call exactly this selected tool: \`${v.toolName}\`. Use the
|
|
123
|
+
1. Call exactly this selected tool: \`${v.toolName}\`. Use the Tool 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
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
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
126
|
4. ${MEMORY_DELTA_INSTRUCTIONS}
|