clementine-agent 1.0.67 → 1.0.68
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 +89 -14
- package/package.json +1 -1
package/dist/agent/assistant.js
CHANGED
|
@@ -174,6 +174,55 @@ function stripLoneSurrogates(s) {
|
|
|
174
174
|
// Replace any surrogate not properly paired with the Unicode replacement char
|
|
175
175
|
return s.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, '\uFFFD');
|
|
176
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Build a context-recovered retry prompt that carries mid-task state
|
|
179
|
+
* forward across an autocompact-rotation. The old session was blown by
|
|
180
|
+
* too-large tool outputs; the new session must know which tool calls
|
|
181
|
+
* were already made this turn (so it doesn't redo them) AND tighten its
|
|
182
|
+
* output discipline (the thing that caused the blow-up in the first
|
|
183
|
+
* place). Called from both thrash-handling paths in runQuery.
|
|
184
|
+
*
|
|
185
|
+
* `snapshot` is captured BEFORE session rotation from stallGuard + the
|
|
186
|
+
* partial responseText. Safe to pass null when no snapshot exists.
|
|
187
|
+
*/
|
|
188
|
+
function buildContextRecoveredPrompt(originalPrompt, snapshot) {
|
|
189
|
+
const parts = [
|
|
190
|
+
'[CONTEXT RECOVERED] Your previous session was rotated because tool outputs filled the context window. A fresh session has been started.',
|
|
191
|
+
'',
|
|
192
|
+
'**Rules for this session (non-negotiable):**',
|
|
193
|
+
'- Add `LIMIT 20` to every SQL query unless you need a count (use `SELECT COUNT(*)`).',
|
|
194
|
+
'- Pipe long Bash / API / log output through `head -50` (or redirect to a file and read the path in a later turn).',
|
|
195
|
+
'- Break multi-entity work into batches of ≤ 20 and deliver partial results between batches.',
|
|
196
|
+
];
|
|
197
|
+
if (snapshot && snapshot.toolCalls.length > 0) {
|
|
198
|
+
// De-duplicate the list while preserving order — agents often make the
|
|
199
|
+
// same API call against different inputs; collapse to tool name only
|
|
200
|
+
// so the continuation prompt fits a short budget.
|
|
201
|
+
const uniqueTools = [];
|
|
202
|
+
const seen = new Set();
|
|
203
|
+
for (const c of snapshot.toolCalls) {
|
|
204
|
+
const name = c.replace(/\(.*$/, '').trim();
|
|
205
|
+
if (!seen.has(name)) {
|
|
206
|
+
seen.add(name);
|
|
207
|
+
uniqueTools.push(name);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
parts.push('');
|
|
211
|
+
parts.push('**Progress from the rotated session (DO NOT repeat these calls):**');
|
|
212
|
+
parts.push(`- ${snapshot.toolCalls.length} tool calls across ${uniqueTools.length} distinct tools: ${uniqueTools.slice(0, 12).join(', ')}${uniqueTools.length > 12 ? ', …' : ''}`);
|
|
213
|
+
parts.push(`- Total calls made: ${snapshot.toolCalls.slice(-20).join(' → ')}`);
|
|
214
|
+
}
|
|
215
|
+
if (snapshot && snapshot.partialText.trim().length > 0) {
|
|
216
|
+
parts.push('');
|
|
217
|
+
parts.push(`**Partial response you had already started (last ${Math.min(snapshot.partialText.length, 1000)} chars):**`);
|
|
218
|
+
parts.push('> ' + snapshot.partialText.trim().replace(/\n/g, '\n> ').slice(0, 1200));
|
|
219
|
+
parts.push('Continue from where that left off — don\'t restart the reasoning.');
|
|
220
|
+
}
|
|
221
|
+
parts.push('');
|
|
222
|
+
parts.push('**Original user request to continue working on:**');
|
|
223
|
+
parts.push(originalPrompt);
|
|
224
|
+
return parts.join('\n');
|
|
225
|
+
}
|
|
177
226
|
/**
|
|
178
227
|
* Wrapper around the SDK's query() that sanitizes lone Unicode surrogates in
|
|
179
228
|
* prompt, systemPrompt, and appendSystemPrompt. Covers every call site in one
|
|
@@ -961,6 +1010,23 @@ export class PersonalAssistant {
|
|
|
961
1010
|
parts.push(isAutonomous ? soulEntry.content.slice(0, 1500) : soulEntry.content);
|
|
962
1011
|
}
|
|
963
1012
|
}
|
|
1013
|
+
// Universal output discipline — applies to Clementine AND every team agent.
|
|
1014
|
+
// Autocompact thrashing (SDK mid-turn session rotation from too-large
|
|
1015
|
+
// tool outputs) is almost always caused by unbounded Bash / SQL / API
|
|
1016
|
+
// responses filling the context window. The `[CONTEXT RECOVERED]`
|
|
1017
|
+
// prefix already tells agents these rules, but only AFTER thrash. This
|
|
1018
|
+
// block lands them in the cacheable prefix so they're active from turn 1.
|
|
1019
|
+
parts.push(`## Output discipline (required to avoid context thrashing)
|
|
1020
|
+
|
|
1021
|
+
Large tool outputs blow the context window and rotate your session mid-task — you lose state and start over. Prevent it:
|
|
1022
|
+
|
|
1023
|
+
- **Bash / shell**: always pipe to \`head -50\` (or \`tail -50\`) for logs, JSON dumps, SQL rows, API blobs. If you need the full output, redirect to a file under \`~/.clementine/vault/07-Inbox/\` or a dedicated scratch dir, then read the path in a later turn.
|
|
1024
|
+
- **SQL**: add \`LIMIT 20\` to every query unless you genuinely need more. If you need a count, use \`SELECT COUNT(*)\` not \`SELECT * \`.
|
|
1025
|
+
- **Web scrapes / API fetches**: paginate instead of asking for everything at once. Page size ≤ 20 rows / 5 pages at a time.
|
|
1026
|
+
- **File reads**: for anything bigger than ~300 lines, read with an offset+limit or grep for what you need rather than reading whole.
|
|
1027
|
+
- **Summarize as you go**: if you've done 5+ tool calls in a turn, write a one-line progress note to working memory before the next call. That state survives if the session rotates.
|
|
1028
|
+
|
|
1029
|
+
**If you see "[CONTEXT RECOVERED]"** in your next prompt: the session was just rotated mid-work because output ballooned. Read the "progress so far" notes, DO NOT repeat completed work, and continue from where you left off with tighter outputs.`);
|
|
964
1030
|
// Skip AGENTS.md for autonomous runs — not relevant for heartbeats/cron
|
|
965
1031
|
if (!isAutonomous) {
|
|
966
1032
|
const agentsEntry = this.promptCache.get(AGENTS_FILE);
|
|
@@ -2256,6 +2322,13 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2256
2322
|
// blocks) so we can pair them and compare against the outgoing reply.
|
|
2257
2323
|
const collectedSdkMessages = [];
|
|
2258
2324
|
const queryStartMs = Date.now();
|
|
2325
|
+
// Mid-task state snapshotted when autocompact thrashing rotates the
|
|
2326
|
+
// session. Captures the tool-call sequence + partial responseText
|
|
2327
|
+
// so the retried session gets a "you've already done X, Y, Z —
|
|
2328
|
+
// continue from Z+1" note instead of starting over and redoing
|
|
2329
|
+
// the same Bash/API calls that blew the context in the first place.
|
|
2330
|
+
// Cleared once consumed in the retry prompt.
|
|
2331
|
+
let preRotationSnapshot = null;
|
|
2259
2332
|
// Event log: track query lifecycle
|
|
2260
2333
|
const eventLog = getEventLog();
|
|
2261
2334
|
if (sessionKey) {
|
|
@@ -2352,6 +2425,12 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2352
2425
|
else if (lower.includes('autocompact') || lower.includes('thrash') || lower.includes('context refilled to the limit')) {
|
|
2353
2426
|
// Autocompact thrashing — treat like the exception path
|
|
2354
2427
|
logger.warn({ sessionKey }, 'Autocompact thrashing (result error) — will rotate session');
|
|
2428
|
+
// Capture mid-task state BEFORE rotating, so the retry
|
|
2429
|
+
// prompt can tell the new session what's already done.
|
|
2430
|
+
preRotationSnapshot = {
|
|
2431
|
+
toolCalls: stallGuard?.getToolCalls() ?? [],
|
|
2432
|
+
partialText: responseText.slice(-1000),
|
|
2433
|
+
};
|
|
2355
2434
|
if (sessionKey) {
|
|
2356
2435
|
try {
|
|
2357
2436
|
this.compactContext(sessionKey);
|
|
@@ -2447,6 +2526,12 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2447
2526
|
// SDK autocompact thrashing — tool outputs are too large for the context window.
|
|
2448
2527
|
// Rotate session and retry with a fresh context so the agent can continue.
|
|
2449
2528
|
logger.warn({ sessionKey }, 'Autocompact thrashing — rotating session and retrying');
|
|
2529
|
+
// Capture mid-task state BEFORE rotating so the retry prompt
|
|
2530
|
+
// can reference completed work and avoid redoing it.
|
|
2531
|
+
preRotationSnapshot = {
|
|
2532
|
+
toolCalls: stallGuard?.getToolCalls() ?? [],
|
|
2533
|
+
partialText: responseText.slice(-1000),
|
|
2534
|
+
};
|
|
2450
2535
|
if (sessionKey) {
|
|
2451
2536
|
try {
|
|
2452
2537
|
this.compactContext(sessionKey);
|
|
@@ -2457,13 +2542,8 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2457
2542
|
this._compactedSessions.delete(sessionKey);
|
|
2458
2543
|
}
|
|
2459
2544
|
if (attempt < PersonalAssistant.RATE_LIMIT_MAX_RETRIES) {
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
`A fresh session has been started. Key rules for this session:\n` +
|
|
2463
|
-
`- Add LIMIT clauses to database queries (max 20 rows)\n` +
|
|
2464
|
-
`- Pipe large command output through \`head -50\` or similar\n` +
|
|
2465
|
-
`- If a task needs many queries, break it into smaller batches and deliver partial results between batches\n\n` +
|
|
2466
|
-
`Continue with the user's request: ${prompt}`;
|
|
2545
|
+
prompt = buildContextRecoveredPrompt(prompt, preRotationSnapshot);
|
|
2546
|
+
preRotationSnapshot = null;
|
|
2467
2547
|
responseText = '';
|
|
2468
2548
|
continue;
|
|
2469
2549
|
}
|
|
@@ -2513,13 +2593,8 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2513
2593
|
if (staleSession && attempt < PersonalAssistant.RATE_LIMIT_MAX_RETRIES) {
|
|
2514
2594
|
responseText = '';
|
|
2515
2595
|
if (contextRecovery) {
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
`A fresh session has been started. Key rules for this session:\n` +
|
|
2519
|
-
`- Add LIMIT clauses to database queries (max 20 rows)\n` +
|
|
2520
|
-
`- Pipe large command output through \`head -50\` or similar\n` +
|
|
2521
|
-
`- If a task needs many queries, break it into smaller batches and deliver partial results between batches\n\n` +
|
|
2522
|
-
`Continue with the user's request: ${prompt}`;
|
|
2596
|
+
prompt = buildContextRecoveredPrompt(prompt, preRotationSnapshot);
|
|
2597
|
+
preRotationSnapshot = null;
|
|
2523
2598
|
contextRecovery = false;
|
|
2524
2599
|
}
|
|
2525
2600
|
continue;
|