From d5994ff6ae433f149fc78c3cb660e0b2cc609c68 Mon Sep 17 00:00:00 2001 From: Kernel Bot Date: Fri, 20 Feb 2026 02:33:57 +0300 Subject: [PATCH] refactor: extract shared truncateToolResult utility to reduce duplication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both OrchestratorAgent (agent.js) and WorkerAgent (worker.js) had identical copies of MAX_RESULT_LENGTH, LARGE_FIELDS, and _truncateResult(). This extracts the logic into src/utils/truncate.js — a single source of truth that both classes now delegate to. Co-Authored-By: Claude Opus 4.6 --- src/agent.js | 22 +++------------------- src/utils/truncate.js | 42 ++++++++++++++++++++++++++++++++++++++++++ src/worker.js | 20 ++------------------ 3 files changed, 47 insertions(+), 37 deletions(-) create mode 100644 src/utils/truncate.js diff --git a/src/agent.js b/src/agent.js index c50f3bb..ec3f844 100644 --- a/src/agent.js +++ b/src/agent.js @@ -9,9 +9,7 @@ import { WorkerAgent } from './worker.js'; import { getLogger } from './utils/logger.js'; import { getMissingCredential, saveCredential, saveProviderToYaml, saveOrchestratorToYaml, saveClaudeCodeModelToYaml, saveClaudeCodeAuth } from './utils/config.js'; import { resetClaudeCodeSpawner, getSpawner } from './tools/coding.js'; - -const MAX_RESULT_LENGTH = 3000; -const LARGE_FIELDS = ['stdout', 'stderr', 'content', 'diff', 'output', 'body', 'html', 'text', 'log', 'logs']; +import { truncateToolResult } from './utils/truncate.js'; export class OrchestratorAgent { constructor({ config, conversationManager, personaManager, selfManager, jobManager, automationManager, memoryManager, shareQueue }) { @@ -282,23 +280,9 @@ export class OrchestratorAgent { } } - /** Truncate a tool result. */ + /** Truncate a tool result. Delegates to shared utility. */ _truncateResult(name, result) { - let str = JSON.stringify(result); - if (str.length <= MAX_RESULT_LENGTH) return str; - - if (result && typeof result === 'object') { - const truncated = { ...result }; - for (const field of LARGE_FIELDS) { - if (typeof truncated[field] === 'string' && truncated[field].length > 500) { - truncated[field] = truncated[field].slice(0, 500) + `\n... [truncated ${truncated[field].length - 500} chars]`; - } - } - str = JSON.stringify(truncated); - if (str.length <= MAX_RESULT_LENGTH) return str; - } - - return str.slice(0, MAX_RESULT_LENGTH) + `\n... [truncated, total ${str.length} chars]`; + return truncateToolResult(name, result); } async processMessage(chatId, userMessage, user, onUpdate, sendPhoto, opts = {}) { diff --git a/src/utils/truncate.js b/src/utils/truncate.js new file mode 100644 index 0000000..af1bf73 --- /dev/null +++ b/src/utils/truncate.js @@ -0,0 +1,42 @@ +/** + * Shared tool-result truncation logic. + * Used by both OrchestratorAgent and WorkerAgent to cap tool outputs + * before feeding them back into the LLM context window. + */ + +const MAX_RESULT_LENGTH = 3000; + +const LARGE_FIELDS = [ + 'stdout', 'stderr', 'content', 'diff', 'output', + 'body', 'html', 'text', 'log', 'logs', +]; + +/** + * Truncate a serialized tool result to fit within the context budget. + * + * Strategy: + * 1. If JSON.stringify(result) fits, return it as-is. + * 2. Otherwise, trim known large string fields to 500 chars each and retry. + * 3. If still too large, hard-slice the serialized string. + * + * @param {string} _name - Tool name (reserved for future per-tool limits) + * @param {any} result - Raw tool result object + * @returns {string} JSON string, guaranteed ≤ MAX_RESULT_LENGTH (+tail note) + */ +export function truncateToolResult(_name, result) { + let str = JSON.stringify(result); + if (str.length <= MAX_RESULT_LENGTH) return str; + + if (result && typeof result === 'object') { + const truncated = { ...result }; + for (const field of LARGE_FIELDS) { + if (typeof truncated[field] === 'string' && truncated[field].length > 500) { + truncated[field] = truncated[field].slice(0, 500) + `\n... [truncated ${truncated[field].length - 500} chars]`; + } + } + str = JSON.stringify(truncated); + if (str.length <= MAX_RESULT_LENGTH) return str; + } + + return str.slice(0, MAX_RESULT_LENGTH) + `\n... [truncated, total ${str.length} chars]`; +} diff --git a/src/worker.js b/src/worker.js index ad3e3fa..1371d9f 100644 --- a/src/worker.js +++ b/src/worker.js @@ -5,9 +5,7 @@ import { getMissingCredential } from './utils/config.js'; import { getWorkerPrompt } from './prompts/workers.js'; import { getUnifiedSkillById } from './skills/custom.js'; import { getLogger } from './utils/logger.js'; - -const MAX_RESULT_LENGTH = 3000; -const LARGE_FIELDS = ['stdout', 'stderr', 'content', 'diff', 'output', 'body', 'html', 'text', 'log', 'logs']; +import { truncateToolResult } from './utils/truncate.js'; /** * WorkerAgent — runs a scoped agent loop in the background. @@ -371,21 +369,7 @@ export class WorkerAgent { } _truncateResult(name, result) { - let str = JSON.stringify(result); - if (str.length <= MAX_RESULT_LENGTH) return str; - - if (result && typeof result === 'object') { - const truncated = { ...result }; - for (const field of LARGE_FIELDS) { - if (typeof truncated[field] === 'string' && truncated[field].length > 500) { - truncated[field] = truncated[field].slice(0, 500) + `\n... [truncated ${truncated[field].length - 500} chars]`; - } - } - str = JSON.stringify(truncated); - if (str.length <= MAX_RESULT_LENGTH) return str; - } - - return str.slice(0, MAX_RESULT_LENGTH) + `\n... [truncated, total ${str.length} chars]`; + return truncateToolResult(name, result); } _formatToolSummary(name, input) {