From 1fbc1d405bdfb80589a976384b94428968f4f169 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 28 Jul 2026 15:04:42 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=8F=20fix:=20Let=20activityCharLimit?= =?UTF-8?q?=20Reach=20Tool=20Inputs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round-fifteen review: `activityCharLimit` is documented as the per-entry truncation for tool input AND output, but `buildPrompt` hard-coded inputs at 200 characters — so raising the setting could never surface a distinguishing path, query, or operation that appears past the first 200 characters of a long argument. Inputs now truncate at the configured limit alongside outputs; the 200-char constant remains only for the intent line (renamed INTENT_CHAR_LIMIT to match). Config fidelity pinned in runtime.spec: a 400-char argument survives a 450 limit and truncates under a 50 limit. The round's other finding is the fifth restatement of the documented edited+reconnect index-space limitation, answered on-thread with the prior four cross-references. --- .../activityLabels/__tests__/runtime.spec.ts | 36 +++++++++++++++++++ .../api/src/agents/activityLabels/runtime.ts | 10 ++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/api/src/agents/activityLabels/__tests__/runtime.spec.ts b/packages/api/src/agents/activityLabels/__tests__/runtime.spec.ts index 35e006e691..c99a6b0440 100644 --- a/packages/api/src/agents/activityLabels/__tests__/runtime.spec.ts +++ b/packages/api/src/agents/activityLabels/__tests__/runtime.spec.ts @@ -139,6 +139,42 @@ describe('buildPrompt', () => { expect(line).toContain('x'.repeat(100)); }); + /** `activityCharLimit` is documented as the per-entry limit for tool input + * AND output — a hard-coded input cap would make the setting unable to + * reach a distinguishing path or query past the first 200 characters. */ + it('applies the configured charLimit to tool inputs, not a hard-coded cap', () => { + const longQuery = 'q'.repeat(400); + const prompt = buildPrompt( + [ + { + toolName: 'search', + toolInput: { query: longQuery }, + toolUseId: 'a', + status: 'success', + toolOutput: 'ok', + }, + ], + 450, + ); + /** 400-char argument survives intact under a 450 limit (the old 200-char + * cap would have cut it), while a tighter limit still truncates. */ + expect(prompt).toContain(longQuery); + const tight = buildPrompt( + [ + { + toolName: 'search', + toolInput: { query: longQuery }, + toolUseId: 'a', + status: 'success', + toolOutput: 'ok', + }, + ], + 50, + ); + expect(tight).not.toContain('q'.repeat(60)); + expect(tight).toContain('…'); + }); + it('serializes small structured values exactly like JSON.stringify', () => { const toolInput = { q: 'docs', filters: { lang: 'en', page: 2 }, ids: [1, 2] }; const prompt = buildPrompt( diff --git a/packages/api/src/agents/activityLabels/runtime.ts b/packages/api/src/agents/activityLabels/runtime.ts index 0bdd8cc744..8e4c069f8f 100644 --- a/packages/api/src/agents/activityLabels/runtime.ts +++ b/packages/api/src/agents/activityLabels/runtime.ts @@ -167,7 +167,11 @@ export interface ActivityLabelHookOptions { const DEFAULT_MAX_PER_RUN = 20; const DEFAULT_CHAR_LIMIT = 600; -const INPUT_CHAR_LIMIT = 200; +/** Intent-line truncation only. Tool inputs and outputs both truncate at the + * configured `activityCharLimit` — the schema documents it as the per-entry + * limit for BOTH, so a hard-coded input cap would make the setting unable to + * reach a distinguishing path or query past the first 200 characters. */ +const INTENT_CHAR_LIMIT = 200; const SUMMARY_TIMEOUT_MS = 12_000; /** Hard bound on the PERSISTED label. The instruction asks for 4–9 words, but * a model that ignores it — or is steered by injection through untrusted @@ -331,7 +335,7 @@ export function buildPrompt( const sections: string[] = [instruction ?? ACTIVITY_INSTRUCTION]; if (context?.lastAssistantText) { sections.push( - `Intent (assistant's last message): ${truncate(context.lastAssistantText, INPUT_CHAR_LIMIT)}`, + `Intent (assistant's last message): ${truncate(context.lastAssistantText, INTENT_CHAR_LIMIT)}`, ); } if (context?.thinkingExcerpts?.length) { @@ -344,7 +348,7 @@ export function buildPrompt( ); } const lines = entries.map((entry) => { - const input = truncate(stringifyUnknown(entry.toolInput, INPUT_CHAR_LIMIT), INPUT_CHAR_LIMIT); + const input = truncate(stringifyUnknown(entry.toolInput, charLimit), charLimit); const outcome = entry.status === 'error' ? `ERROR: ${truncate(entry.error ?? 'unknown error', charLimit)}`